MvvmLazy Android framework

Currently, there are many popular Android MVC and MVP development frameworks, but a development framework based on MVVM pattern is rare. Searching the individual on the market a lot of open source framework, under the principle of reduce duplication made wheels, drew everyone a great god, the framework of integrated a number of commonly used open source framework and tools, on the part of the utility module encapsulation, enriched BindingAdapter custom data binding, created the Android lazy development framework, has been in more than one business Industry project through the test, reliability is trustworthy, in view of the large number of developers are still using Java language development, this set of framework using Java language, support Java, Kotlin mixed development. MvvmLazy is based on Google DataBinding+LiveData+ViewModel framework, integrating Okhttp+RxJava+Retrofit+Glide and other popular modules, plus a variety of native control custom BindingAdapter, An addictive, practical MVVM rapid development framework that perfectly binds events to data sources. Goodbye findViewById(), goodbye setText(), goodbye setOnClickListener()…

Github address: github.com/jirywell/Mv…

Characteristics of framework

  • Rapid development of

    You only need to write the business logic of the project, and you don’t need to worry about network requests, permission applications, View life cycle and other issues. Just roll up your sleeves and do it.

  • Maintenance is convenient

    MVVM development mode, low coupling, logical. The Model layer is responsible for passing the requested data to the ViewModel; ViewModel layer is responsible for the request to the data to do business logic processing, and finally to the View layer to display, and View one to one correspondence; View layer is only responsible for the interface drawing refresh, does not deal with business logic, very suitable for the allocation of independent module development.

  • Popular frameworks

    Retrofit + OKHTTP + rxJava is responsible for network requests; Gson is responsible for parsing json data; Glide is responsible for loading images; Rxlifecycle is responsible for managing the view lifecycle; Co-exist with network requests; Rxbinding with databinding extension UI events; Rxpermissions is responsible for Android 6.0 permissions; LiveEventBus is an Android message bus, based on LiveData, with life cycle awareness, support Sticky, support AndroidX, support cross-process, support cross-app. BaseRecyclerViewAdapterHelper well-known BaseRecyclerViewAdapterHelper RecyclerView adapter management framework TabLayout a powerful TabLayout framework TitleBar SmartRefreshLayout drop-down refresh framework RWidgetHelper instead of selector, Each state background/border/text change color, no longer need to write a large number of shape files ARouter Ali routing framework

  • Data binding

    Satisfies the databinding bidirectional binding currently supported by Google controls, and extends some databinding not supported by the original control. For example, bind the url path of the picture to the ImageView control, and use Glide to load the picture in the BindingAdapter method; View OnClick event in the BindingAdapter method using RxView to prevent repeated clicks, and then the event callback to the ViewModel layer, to achieve data and event binding between XML and ViewModel (some of the framework extension controls and callback commands are used by @Kelin original).

  • The base class encapsulates

    BaseActivity, BaseFragment, and BaseViewModel are created specifically for MVVM mode. ViewDataBinding and ViewModel need not be defined in View layer. Generics can be used directly on BaseActivity and BaseFragment. Normal interfaces just write fragments and use ContainerActivity to host (proxy), so you don’t need to register every interface in the AndroidManifest.

  • Global operations

    1. Global Activity stack management, which allows you to open and end specific activities anywhere in the application, and exit the application with one click.
    2. LoggingInterceptor intercepts network Request logs globally, prints Request and Response, formats JSON and XML data to display, and facilitates debugging in the background.
    3. Global Cookie, support SharedPreferences and memory management mode.
    4. Common network request exception listening, according to different status code or exception set corresponding message.
    5. Global exception capture, the program will not crash when abnormal, you can jump into the abnormal interface to restart the application.
    6. Global event callback: Provides LiveEventBus callback mode.
    7. Global arbitrary position of a line of code to achieve file download progress monitoring (not support multiple file progress monitoring).
    8. Anti-jitter processing of global click events to prevent quick click.

1. Preparation

A lot of information about MVVM on the Internet, here is no longer elaborated what is MVVM, do not know the friends can go to understand. todo-mvvm-live

1.1. Enable databinding

Build. Gradle android {} in the main project app:

dataBinding {
    enabled true
}
Copy the code

1.2. Rely on Library

Dependent from remote:

Add to build.gradle in the root directory

allprojects {
    repositories{... google() jcenter() maven { url'https://jitpack.io'}}}Copy the code

Build. Gradle in the main project app

dependencies{... apiproject(':mvvmlazy')}Copy the code

1.3. Configure config.gradle

Gradle in your main project root directory. Then add it to the first line of the build.gradle root directory:

apply from: "config.gradle"
Copy the code

Note: in config.gradle

Android = [] is your development related version configuration, can be modified

Android_x = [] is related to android_x and can be modified by yourself

Add dependencies = [] to add dependencies = [

1.4. Configure AndroidManifest

Add permission:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Copy the code

Configure the Application:

Inherits BaseApplication from MvvmLazy, or calls

BaseApplication.setApplication(this);
Copy the code

To initialize your Application

You can configure it in your own AppApplication

// Whether to enable log printing
KLog.init(true);
// Configure the global exception crash operation
CaocConfig.Builder.create()
    .backgroundMode(CaocConfig.BACKGROUND_MODE_SILENT) // Background mode, enable immersion
    .enabled(true) // Whether to enable global exception catching
    .showErrorDetails(true) // Whether to display error details
    .showRestartButton(true) // Whether to display the restart button
    .trackActivities(true) // Whether to trace the Activity
    .minTimeBetweenCrashesMs(2000) // Time between crashes (milliseconds)
    .errorDrawable(R.mipmap.ic_launcher) // Error icon
    .restartActivity(LoginActivity.class) // Restart the activity
    / / errorActivity (YourCustomErrorActivity. Class) / / after the collapse of fault activity
    EventListener(new YourCustomEventListener()) // Error listener after crash
    .apply();
Copy the code

2. Get started quickly

2.1. The first Activity

Java, LoginViewModel. Java, and activity_login.xml

2.1.1 Associate ViewModel

Associate LoginViewModel with activity_login. XML.

<layout>
    <data>
        <variable
            type="com.rui.MvvmLazy.ui.login.LoginViewModel"
            name="viewModel"
        />
    </data>.</layout>
Copy the code

Variable-type: indicates the full path of the class. Variable-name: indicates the variable name

2.1.2 Inherit BaseActivity

LoginActivty


public class LoginActivty extends BaseActivity<ActivityloginBinding.LoginViewModel> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public int initContentView(a) {
        return R.layout.activity_login;
    }

    @Override
    public int initVariableId(a) {
        return BR.viewModel;
    }


    @Override
    public void initData(a) {
        super.initData(); }}Copy the code

Once you save activity_login.xml, Databinding generates an ActivityloginBinding class. (If not, try going to Build->Clean Project)

BaseActivity is an abstract class with two generic parameters, ViewDataBinding and BaseViewModel. The ActivityLoginBinding above inherits ViewDataBinding as the first generic constraint. LoginViewModel inherits BaseViewModel as the second generic constraint.

Override two abstract methods of BaseActivity

InitContentView () returns the id of the layout. InitVariableId () returns the id of the variable, which corresponds to activity_login name=”viewModel”, like the ID of a control, using R.id.xx, Here the BR, like the R file, is generated by the system. Use br.xxx to find the ViewModel ID.

2.1.3 Inherit BaseViewModel

LoginViewModel inheritance BaseViewModel

public class LoginViewModel extends BaseViewModel<HomeRepository> {
    public MainViewModel(@NonNull Application application) {
        super(application);
    }

    @Override
    public void initData(a) {
        super.initData(); }}Copy the code

BaseViewModel and BaseActivity handle common UI logic through LiveData, using the parent showDialog(), startActivity(), and other methods in ViewModel. In this MainViewModel can write your logic as much as you like!

BaseFragment is used in the same way as BaseActivity. See Demo for details.

2.2. Data binding

There are bidirectional bindings that come with the Databinding framework, as well as extensions

2.2.1 traditional binding

Bind user name:

Defined in the ViewModel

// Bind the user name
public MutableLiveData<String> userName = new MutableLiveData<>("");
Copy the code

Bind in the username EditText tag

android:text="@={viewModel.userName}"
Copy the code

UserName. Get () and userName. Set (“”) will display whatever is entered in the input field. Note: the @ sign needs to be followed by the = sign to achieve bidirectional binding; UserName needs to be public, otherwise viewModel won’t be able to find it.

Click event Binding:

Defined in ViewModel

// The login button click event
public View.OnClickListener loginOnClick = new View.OnClickListener() {
    @Override
    public void onClick(View v) {}};Copy the code

Bind in the login button TAB

android:onClick="@{viewModel.loginOnClick}"
Copy the code

In this way, the user’s click events are called back directly to the ViewModel layer, better maintaining the business logic

This is the powerful databinding framework bidirectional binding feature that eliminates the need to define control ids, setText(), setOnClickListener().

However, these alone can not completely meet the needs of our complex business ah! MvvmLazy makes its debut: it has a set of custom binding rules that satisfy most scenarios. Read on.

2.2.2 custom binding

For click events, use custom click event bindings instead of traditional bindings.

Defined in LoginViewModel

// The login button click event
public BindingCommand loginOnClickCommand = new BindingCommand(new BindingAction() {
    @Override
    public void call(a) {}});Copy the code

Define the namespace in activity_login

xmlns:binding="http://schemas.android.com/apk/res-auto"
Copy the code

Bind in the login button TAB

binding:onClickCommand="@{viewModel.loginOnClickCommand}"
Copy the code

Isn’t this the same as the original traditional binding? No, there’s a difference. Using this form of binding, in addition to the original event binding, with anti-double click function, multiple clicks in 1 second will only perform one action. You can add this property if you do not want to prevent repeated clicks

binding:isThrottleFirst="@{Boolean.TRUE}"
Copy the code

So where does this function work? The answer is in the code below.

// Prevent repeat click interval (seconds)
public static final int CLICK_INTERVAL = 1;

/** * requireAll specifies whether to bind all parameters. False specifies whether to bind all parameters
@BindingAdapter(value = {"onClickCommand", "isThrottleFirst"}, requireAll = false)
public static void onClickCommand(View view, final BindingCommand clickCommand, final boolean isThrottleFirst) {
    if (isThrottleFirst) {
        RxView.clicks(view)
        .subscribe(new Consumer<Object>() {
            @Override
            public void accept(Object object) throws Exception {
                if(clickCommand ! =null) { clickCommand.execute(); }}}); }else {
        RxView.clicks(view)
        .throttleFirst(CLICK_INTERVAL, TimeUnit.SECONDS)// Only one click per second is allowed
        .subscribe(new Consumer<Object>() {
            @Override
            public void accept(Object object) throws Exception {
                if(clickCommand ! =null) { clickCommand.execute(); }}}); }}Copy the code

The onClickCommand method is custom, using the @BindingAdapter annotation to indicate that it is a binding method. Methods use RxView to enhance the Clicks event,.throttleFirst() to restrict the subscriber to executing it repeatedly for a specified period of time, and finally call back the event with BindingCommand, like an interceptor that makes a judgment call before clicking. And then he sends the event in the same direction.

Don’t you think it’s a bit interesting? The fun is still coming!

2.2.3 Custom ImageView image loading

Binding image path:

Defined in the ViewModel

public String imgUrl = "Http://img0.imgtn.bdimg.com/it/u=2183314203, & FM = 26 & gp = 0. 562241301 JPG";
Copy the code

Inside the ImageView tag

Binding :bindImgUrl="@{viewmodel.imgurl}" Binding :bindCircleImgUrl="@{viewmodel.imgurl} binding:bindCorners="@{20}" binding:bindCornersImgUrl="@{viewModel.imgUrl}"Copy the code

The URL is the path to the image, so when it’s bound, this ImageView will display that image, whether it’s an unlimited network image or a local image.

If you need to attach a picture to a default load, you can add this sentence

binding:placeholderRes="@{R.mipmap.ic_launcher_round}"
Copy the code

Center the image if needed

binding:centerCrop="@{true}"
Copy the code

Implementation in BindingAdapter

 @BindingAdapter(value = {"bindImgUrl", "placeholderRes", "centerCrop"}, requireAll = false)
    public static void bindImgUrl(ImageView imageView, String url, Integer placeholderRes, Boolean centerCrop) {
        RequestBuilder<Drawable> requestBuilder = Glide.with(imageView.getContext()).asDrawable().load(url);
        if (centerCrop == null || centerCrop) {
            requestBuilder = requestBuilder.centerCrop();
        }
        requestBuilder.apply(new RequestOptions().placeholder(createDefPlaceHolder(imageView.getContext(), placeholderRes, 0)).override(imageView.getWidth(), imageView.getHeight()))
                .into(imageView);

    }
Copy the code

Easy to customize an ImageView image loading binding, learn this way, can be customized extension.

If you are interested in this, you can download the source code and see the binding implementation of various controls in the Binding package

2.2.4 RecyclerView binding

RecyclerView is also a very common control, the traditional way needs to write a variety of Adapter for a variety of business, if you use MvvmLazy, you can greatly simplify the workload, from the setAdapter(). Using the well-known BaseRecyclerViewAdapterHelper manages RecyclerView adapter;

Define in ViewModel:

/ / declare the adapter
 public DataBindingAdapter<JokeInfo, TestLayoutItemJokeBinding> lineAdapter = new DataBindingAdapter<JokeInfo, TestLayoutItemJokeBinding>(R.layout.test_layout_item_joke) {
        @Override
        protected void convertItem(@NotNull BaseViewHolder holder, @Nullable TestLayoutItemJokeBinding binding, JokeInfo item) { binding.setEntity(item); }};Copy the code

Bind in XML

 <androidx.recyclerview.widget.RecyclerView
                bindAdapter="@{viewModel.lineAdapter}"
                layoutManager="@{LayoutManagers.linear()}"
                lineManager="@{LineManagers.divider(@color/divider,1)}"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
Copy the code

LayoutManager controls whether the arrangement is linear (both horizontal and vertical) or grid, and lineManager sets the dividing line

The HORIZONTAL layout method: binding: layoutManager = “@ {LayoutManagers. Linear (LinearLayoutManager. HORIZONTAL, Boolean FALSE)}” grid layout of writing: Binding :layoutManager=”@{LayoutManagers. Grid (3)} `binding:layoutManager=”@{LayoutManagers.staggeredGrid(3,LayoutManagers.VERTICAL)}”

To use a related class, you need to import the class, similar to importing a Java class

<import type="com.rui.mvvmlazy.binding.viewadapter.recyclerview.LayoutManagers" />

<import type="com.rui.mvvmlazy.binding.viewadapter.recyclerview.LineManagers" />

See the ListViewModel class in the sample application for details.

2.3. Network Request

Network request has always been the core of a project, now the project is basically inseparable from the network, a good network request framework can make development twice the result with half the effort.

2.3.1, Retrofit + Okhttp + RxJava3

Today, these three combinations are standard for web requests. If you are not familiar with these three frameworks, you are advised to consult relevant information.

Square’s framework is really easy to use. MvvmLazy has been introduced

api "Com. Squareup. Okhttp3: okhttp: 4.9.1." "
api "Com. Squareup. Retrofit2: retrofit: 2.9.0"
api "Com. Squareup. Retrofit2: converter - gson: 2.9.0"
api "Com. Squareup. Retrofit2: adapter - rxjava3:2.9.0"
Copy the code

Added when building Retrofit

Retrofit retrofit = new Retrofit.Builder()
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    .build();
Copy the code

Or just use the RetrofitClient packaged in the sample application

2.3.2 Network interceptor

LoggingInterceptor: intercepts Request information globally, formats and prints Request and Response information. You can clearly see the data connected to the background interface.

LoggingInterceptor mLoggingInterceptor = new LoggingInterceptor
    .Builder()// Builder pattern
    .loggable(true) // Whether to enable log printing
    .setLevel(Level.BODY) // Print the level
    .log(Platform.INFO) // Print type
    .request("Request") / / request of the Tag
    .response("Response")/ / the Response of the Tag
    .addHeader("version", BuildConfig.VERSION_NAME)// Print the version
    .build()
Copy the code

Join when building OKHTTP

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .addInterceptor(mLoggingInterceptor)
    .build();
Copy the code

CacheInterceptor: a CacheInterceptor that automatically reads data from the cache when there is no network connection. The cache is stored for three days by default. Creating a cache object

// Cache time
int CACHE_TIMEOUT = 10 * 1024 * 1024
// Cache files stored
File httpCacheDirectory = new File(mContext.getCacheDir(), "goldze_cache");
// Cache objects
Cache cache = new Cache(httpCacheDirectory, CACHE_TIMEOUT);
Copy the code

Join when building OKHTTP

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .cache(cache)
    .addInterceptor(new CacheInterceptor(mContext))
    .build();
Copy the code

2.3.3 Cookie management

MvvmLazy provides two CookieStore types: PersistentCookieStore (SharedPreferences management) and MemoryCookieStore (memory management), depending on your business needs, add the appropriate CookieJars when you build OKHTTP

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .cookieJar(new CookieJarImpl(new PersistentCookieStore(mContext)))
    .build();
Copy the code

or

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .cookieJar(new CookieJarImpl(new MemoryCookieStore()))
    .build();
Copy the code

2.3.4 Bind the life cycle

The request is in the ViewModel layer. LifecycleProvider objects are injected into the ViewModel by default in BaseActivity, which is used to bind the lifecycle of the request. The View and the request live and die together.

RetrofitClient.getInstance().create(DemoApiService.class)
    .demoGet()
    .compose(RxUtils.bindToLifecycle(getLifecycleProvider())) // Request synchronization with View cycles
    .compose(RxUtils.schedulersTransformer())  // Thread scheduling
    .compose(RxUtils.exceptionTransformer())   // Network error exception conversion
    .subscribe(new Consumer<BaseResponse<DemoEntity>>() {
        @Override
        public void accept(BaseResponse<DemoEntity> response) throws Exception {}},new Consumer<ResponseThrowable>() {
        @Override
        public void accept(ResponseThrowable throwable) throws Exception {}});Copy the code

Compose (rxutils.bindtolifecycle (getLifecycleProvider()))) Because BaseActivity/BaseFragment LifecycleProvider interface is achieved, and the default into ViewModel, so when the call request method can directly call getLifecycleProvider interface () to get the life cycle. If you’re not using BaseActivity or BaseFragment in MVVMAbit, use your own Base, So you need to have your own Activity inherit rxappactivity and Fragment inherit RxFragment to use rxutils.bindtolifecycle (lifecycle).

2.3.5 Network exception handling

Network exceptions are very common in network requests, such as request timeout, parsing error, resource non-existence, server internal error, etc., which need to be handled on the client. Request timeout, please check the network connection, at this time secretly send abnormal information to the background (manual funny).

In the use of Retrofit request, to join the combination operator. Compose (RxUtils. ExceptionTransformer ()), when the network exception occurs, the callback onError (ResponseThrowable) method, You can get the code and message of the exception and do the corresponding processing.

ExceptionHandle is customized in MvvmLazy, which has completed the judgment of most network exceptions for you. Logic can also be adjusted according to the specific requirements of the project.

Note: the network exception code here is not the code agreed with the server protocol. Network exceptions can be divided into two parts. One is a protocol exception (HttpException) with code = 404 or 500, and the other is a request exception (connection timeout, parsing error, certificate verification failure, etc.). The code rule agreed with the server is not a network exception, but a service exception. RxJava filters can be used in requests, and BaseSubscriber can be customized to handle business logic exceptions of network requests. Since each company’s business agreement is different, you need to handle this type of exception yourself.

3. Auxiliary functions

A complete rapid development framework, of course, also not commonly used helper classes. Here’s a look at some of the helper features in MVVMabit.

3.1. Event Bus

Event bus there surely it is clear to all, the advantages of android’s own broadcast mechanism for communication between components, use very tedious, communication components subscribe and publish the coupling between each other is more serious, especially in the definition of events, broadcasting mechanism is limited to the serialization of class (pass through Intent), flexible enough.

3.3.1, LiveEventBus

LiveEventBus is an Android message bus, based on LiveData, with life cycle awareness, support Sticky, support AndroidX, support cross-process, support cross-app

Usage:

// Send a message
LiveEventBus.get("key").post("value");
// Send a delayed message with a 3-second jump
LiveEventBus.get("key").postDelay("value".3000);

// Receive the message
LiveEventBus.get("key",String.class).observe(this.new Observer<String>() {
@Override
public void onChanged(@Nullable String s) { Log.i(TAG,s); }});Copy the code

For more information, please refer to github.com/JeremyLiao/…

3.2 file download

File download is almost a necessary function of every app, which is used for image download and software upgrade, etc. MvvmLazy uses Retrofit+Okhttp+RxJava+RxBus to implement a line of code to monitor the file download with progress.

The download file

String loadUrl = "Your file download path";
String destFileDir = context.getCacheDir().getPath();  // Path to the file
String destFileName = System.currentTimeMillis() + ".apk";// The name of the file
DownLoadManager.getInstance().load(loadUrl, new ProgressCallBack<ResponseBody>(destFileDir, destFileName) {
    @Override
    public void onStart(a) {
        / / RxJava onStart ()
    }

    @Override
    public void onCompleted(a) {
        / / RxJava onCompleted ()
    }

    @Override
    public void onSuccess(ResponseBody responseBody) {
        // Download the successful callback
    }

    @Override
    public void progress(final long progress, final long total) {
        // Callback in download progress: current progress, total: total file size
    }

    @Override
    public void onError(Throwable e) {
        // Download the error callback}});Copy the code

RxBus is used in the ProgressResponseBody to send download progress information to the ProgressCallBack. Inheriting the ProgressCallBack can listen for download status. All callback methods are executed in the main thread, which is convenient for UI update. Please refer to the example program for details.

3.3, ContainerActivity

A container (proxy) for an Activity that contains a Fragment. A normal interface only needs to write a Fragment. Using this Activity, you do not need to register every interface in the AndroidManifest

Usage:

Open a Fragment by calling BaseViewModel methods in ViewModel

StartContainerActivity (Name of your Fragment class.class.getCanonicalName ())Copy the code

Call the BaseViewModel method in ViewModel and open a Fragment with a serialized entity

Bundle mBundle = new Bundle();
mBundle.putParcelable("entity", entity); StartContainerActivity (your Fragment class.class.getCanonicalName (), mBundle);Copy the code

Remove entities from your Fragment

Bundle mBundle = getArguments();
if(mBundle ! =null) {
    entity = mBundle.getParcelable("entity");
}
Copy the code

3.4. 6.0 Permission Application

Those already familiar with RxPermissions can skip it.

Usage:

For example, to request camera permissions, call in ViewModel

// Request camera permissions
RxPermissions rxPermissions = new RxPermissions((Activity) context);
rxPermissions.request(Manifest.permission.CAMERA)
    .subscribe(new Consumer<Boolean>() {
        @Override
        public void accept(Boolean aBoolean) throws Exception {
            if (aBoolean) {
                ToastUtils.showShort("Permissions are open. Jump straight into the camera.");
            } else {
                ToastUtils.showShort("Permission denied"); }}});Copy the code

For details about how to apply for permissions, see RxPermissions original project address

3.5. Image compression

In order to save user traffic and speed up the speed of uploading pictures, some scenes will be locally compressed and then transferred to the background, so we hereby provide an auxiliary function of picture compression.

Usage:

RxJava way to compress a single image, get a compressed image file object

String filePath = "mnt/sdcard/1.png";
ImageUtils.compressWithRx(filePath, new Consumer<File>() {
    @Override
    public void accept(File file) throws Exception {
        // Put the file in the RequestBody. }});Copy the code

RxJava compression of multiple images, according to the collection order of each successful compression, will be in the onNext method to get a compressed image file object

List<String> filePaths = new ArrayList<>();
filePaths.add("mnt/sdcard/1.png");
filePaths.add("mnt/sdcard/2.png");
ImageUtils.compressWithRx(filePaths, new Subscriber() {
    @Override
    public void onCompleted(a) {}@Override
    public void onError(Throwable e) {}@Override
    public void onNext(File file) {}});Copy the code

3.6 Other auxiliary classes

ToastUtils: Toast utility class

SPUtils: SharedPreferences utility class

SDCardUtils: SD card-related tool classes

ConvertUtils: Transforms related utility classes

StringUtils: String-related utility class

RegexUtils: Reged-related utility classes

KLog: logs are printed in JSON format

See the utils directory under mvVMLazy for more utility classes

3.7. Demo Examples

The project provides a large number of demo examples, you can download the source code to view

3.8. Componentization scheme

Project componentization schemes refer to [MVVMHabitComponent] [github.com/goldze/MVVM…]

About

** I like to try new technologies, and when I find something useful in the future, I will practice it in enterprise projects. When there is no problem, I will introduce it into MvvmLazy, and I have been maintaining this framework, thank you for your support. If you think this frame is good, please click star, your support is my motivation to move forward! Thank you for your ideas..

MVVMHabit

AndroidProject

E-mail: 664209769 @qq.com

License

Copyright 2021 Zhao Jirui Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.Copy the code