Android Architecture Ideas and MVVM Framework Encapsulation

About the Android project architecture is also a clichéd topic. There are too many articles about the Android architecture on the Internet, but through Google search keywords, most of the popular articles on the homepage are introductions to the concepts of MVC, MVP, and MVVM architectures. Conceptual articles It may not be very helpful for students who do not understand the Android architecture. This article actually originated from the author's internal technology sharing in the company, and it was published as an article after slight modification. The content of the article involves the evolution from
MVC, MVP to MVVM. At the same time, for the sake of understanding, each architecture is demonstrated in code, and finally the MVVM
architecture is encapsulated based on the components provided by Jetpack. The content of the article is relatively basic, and there is almost no obscure knowledge, which will be of great help to students who want to understand the Android architecture.

1. Evolution of Android project architecture

First of all, we should understand that there is no platform for architecture. No matter MVC, MVP or MVVM
are not unique to the Android platform, even because the Android platform started late, the architecture of the Android project more or less refers to the implementation of the front-end architecture.

For front-end or Android-side projects, the code can be divided into three parts, namely UI part, business logic part and data control part. The starting point of these three parts of circulation comes from user input, that is, the user invokes the corresponding business logic to obtain data by operating the UI, and finally feeds the data back to the UI interface. The flow diagram is shown in the figure below.

insert image description here

According to these three parts, we can divide the code into three layers, namely the initial layering of the MVC architecture. However, as the business of the project becomes more and more complex, the disadvantages of the MVC architecture are revealed, and it cannot support the existing business. So in this context, the MVP architecture was derived to make up for the shortcomings of MVC. Even later, Google officially launched some Jetpack components to specifically solve the Android architecture problem, from the main push of MVVM to the current main push of the MVI architecture. But no matter how the architecture evolves, it cannot be separated from the above-mentioned three-layer logic, but the new architecture makes up for the shortcomings of the old architecture on the basis of the existing ones, making the project code easier to maintain.

In the following content, we mainly discuss the evolution of the Android project architecture from MVC to MVVM.

  1. Introduction to MVC

Based on the three-tier logic mentioned above, the initial Android project adopted the MVC architecture. MVC is the abbreviation of Model-View-Controller
. To put it simply, MVC is that the user operates the View, and the View calls the Controller to operate the Model layer, and then the Model layer returns the data to the View layer for display.

  • The model layer (Model) is responsible for communicating with the database and network layer, and obtaining and storing application data;
  • The view layer (View) is responsible for visualizing the data of the Model layer and handling the interaction with users;
  • The control layer (Controller) is used to establish the connection between the Model layer and the View layer, is responsible for processing the main business logic, and updates the Model layer data according to user operations.

The structure diagram of MVC is shown in the figure below.

insert image description here

In the MVC architecture of the Android project, since the Activity acts as the View layer and the Controller layer at the same time, the MVC in Android is more like the following structure:

insert image description here

Based on the above figure, we can take the APP login process as an example to implement the code of the MVC architecture in Android.

(1) Model layer code implementation

The Model layer is used to process the login request and obtain login-related data from the server, and notify the View layer to update the interface after success. code show as below:

public class LoginModel {
    
    
    public void login(String username, String password, LoginListener listener) {
    
    
        RetrofitManager.getApiService()
                .login(username, password)
                .enqueue(new Callback<User>() {
    
    
                    @Override
                    public void onResponse(Call<User> call, Response<User> response) {
    
    
                        listener.onSuccess(response.data);
                    }

                    @Override
                    public void onFailed(Exception e) {
    
    
                        listener.onFailed(e.getMessage());
                    }
                });
        
    }
}

The above code notifies the View layer to update the UI through LoginListener. LoginListener is an interface, as follows:

    public interface LoginListener {
    
    
    void onSuccess(User data);

    void onFailed(String msg);
}

(2) Controller/View code implementation

Since the Controller and View layers in Android's MVC architecture are both in charge of the Activity, the Activity needs to implement LoginListener to update
the UI. Its code is as follows:

public class MainActivity extends AppCompactActivity implements LoginListener {
    
    

    private LoginModel loginModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        model = new LoginModel();
        findViewbyId(R.id.btn_fetch_data).setOnClickListener(view -> {
    
    
                    String username = findViewById(R.id.et_username).getText().toString();
                    String password = findViewById(R.id.et_password).getText().toString();
                    loginModel.login(username, password, this);
                }
        );
    }

    @Override
    public void onSuccess(User data) {
    
    
        // Update UI
    }

    @Override
    public void onFailed(String msg) {
    
    
        // Update UI
    }
}

As can be seen from the above code, the MVC code in Android is not clear about layering, resulting in the confusion of the Controller layer and the View layer. At the same time, when you write Android code, you generally don't deliberately extract a Model layer, but put
the code of the Model layer into the Activity to implement it.

public class MainActivity extends AppCompactActivity implements LoginListener, View.OnClickListener {
    
    

    private LoginModel loginModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        model = new LoginModel();

        findViewbyId(R.id.btn_fetch_data).setOnClickListener(view -> {
    
    
                    showLoading();
                    String username = findViewById(R.id.et_username).getText().toString();
                    String password = findViewById(R.id.et_password).getText().toString();
                    RetrofitManager.getApiService()
                            .login(username, password)
                            .enqueue(new Callback<User>() {
    
    
                                @Override
                                public void onResponse(Call<User> call, Response<User> response) {
    
    
                                    onSuccess(response.data);
                                }

                                @Override
                                public void onFailed(Exception e) {
    
    
                                    listener.onFailed(e.getMessage());
                                }
                            });
                }
        );
    }

    @Override
    public void onSuccess(User data) {
    
    
        dismissLoading();
        // Update UI
    }

    @Override
    public void onFailed(String msg) {
    
    
        dismissLoading();
        // Update UI
    }

    public void showLoading() {
    
    
        // ...
    }

    public void dismissLoading() {
    
    
        // ...
    }
}

Such code layering becomes more blurred, and it also makes the structural hierarchy of the code more confusing. The code in the activity of some pages that make some businesses more complicated may be as many as thousands of lines. It can be seen that the MVC architecture in the Android project
has many problems. The summary mainly has the following points:

  • Activity/Fragment undertakes the work of View layer and Controller layer at the same time, which violates the single responsibility;
  • The Model layer and the View layer are coupled and interdependent;
  • Layering is not paid attention to during development, and Model layer code is also stuffed into Activity/Fragment, making the code layer more confusing.
  1. Introduction to MVP

For the problems existing in the above MVC architecture, we can optimize and solve them on the basis of MVC.
That is, the logic of the control layer is stripped from the Activity, and the coupling between the Model layer and the View layer is blocked. The Model layer does not directly communicate with the View, but lets the Model notify the control layer when the data changes, and the control layer then notifies the View layer. To update the interface, this is the architectural idea of ​​MVP. MVP is the abbreviation of Model-View-Presenter . To put it simply, MVP is to
change the Controller of MVC to Presenter, that is, to extract the code of the logic layer from the Activity to the Presenter, so that the code level becomes clearer. Secondly, the Model layer no longer holds the View
layer, and the code is more decoupled. .

  • The model layer (Model) is consistent with that in MVC. It is also responsible for communicating with the database and network layer, and obtaining and storing application data. The difference is that the Model layer no longer directly communicates with the View layer, but communicates with the Presenter layer.
  • The view layer (View) is responsible for visualizing the data of the Model layer and interacting with the Presenter layer at the same time. Compared with MVC, MVP's View layer and Model layer are no longer coupled.
  • The control layer (Presenter) is mainly responsible for processing business logic, monitoring data changes in the Model layer, and then calling the View layer to refresh the UI.

The structure diagram of MVP is shown in the figure below.
insert image description here

As can be seen from the figure above, the View directly communicates with the Presenter layer, and when the View layer receives user operations, it will call the
Presenter layer to process business logic. Then the Presenter layer obtains data through the Model, and the Model layer sends the latest data back to the Presenter after obtaining the data.
Since the Presenter layer also holds a reference to the View layer, the data is passed to Viewthe layer for display.

Below we still use login as an example to demonstrate the implementation of the MVP architecture through code.

(1) Model layer code implementation

The Model layer in MVP is consistent with the Model layer in MVC, the code is as follows:

public class LoginModel {
    
    
    public void login(String username, String password, LoginListener listener) {
    
    
        RetrofitManager.getApiService()
                .login(username, password)
                .enqueue(new Callback<User>() {
    
    
                    @Override
                    public void onResponse(Call<User> call, Response<User> response) {
    
    
                        listener.onSuccess(response.data);
                    }

                    @Override
                    public void onFailed(Exception e) {
    
    
                        listener.onFailed(e.getMessage());
                    }
                });
    }
}

After the login interface returns the result, the result is called back through the LoginListener.

public interface LoginListener {
    
    
    void onSuccess(User user);

    void onFailed(String errorInfo);
}

(2) Presenter layer code implementation

Since the Presenter needs to notify the View layer to update the UI, it needs to hold the View, and a View interface can be abstracted here. as follows:

public interface ILoginView {
    
    
    void showLoading();

    void dismissLoading();

    void loginSuccess(User data);

    void loginFailer(String errorMsg);
}

In addition, the Presenter also needs to communicate with the Model layer, so the Presenter layer will also hold the Model layer. After the user triggers the login operation, the login logic of the Presenter is invoked. The Presenter performs the login operation through the Model. After the login is successful, the result is fed back to the View layer. Update interface. The code is implemented as follows:

public class LoginPresenter {
    
    

    // Model 层 
    private LoginModel model;

    // View 层 
    private ILoginView view;

    public LoginPresenter(LoginView view) {
    
    
        this.model = new LoginModel();
        this.view = view;
    }

    public void login(String username, String password) {
    
    
        view.showLoading();
        model.login(username, password, new LoginListener() {
    
    
            @Override
            public void onSuccess(User user) {
    
    
                view.loginSuccess(user);
                view.dismissLoading();
            }

            @Override
            public void onFailed(String msg) {
    
    
                view.loginFailer(msg);
                view.dismissLoading();
            }
        });
    }

    @Override
    public void onDestory() {
    
    
        // recycle instance.
    }
}

(3) View layer code implementation

Activity as the View layer needs to implement the above ILoginView interface, and the View layer needs to hold the Presenter to process business logic. The code implementation of the View layer becomes very simple.

public class MainActivity extends AppCompatActivity implements ILoginView {
    
    

    private LoginPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        presenter = new LoginPresenter(this);
        findViewById(R.id.button).setOnClickListener(v -> {
    
    
            String username = findViewById(R.id.et_username).getText().toString();
            String password = findViewById(R.id.et_password).getText().toString();
            presenter.login(username, password);
        });
    }

    @Override
    public void loginSuccess(User user) {
    
    
        // Update UI.
    }

    @Override
    public void loginFailer(String msg) {
    
    
        // Update UI.
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        presenter.onDestroy();
    }

    @Override
    public void showLoading() {
    
    

    }

    @Override
    public void dismissLoading() {
    
    
    }
}

The essence of MVP is interface-oriented programming to realize the principle of dependency inversion. It can be seen that the MVP architecture is clearer and clearer than MVC in terms of layering, and it decouples the Model layer and the View layer. But MVP also has some disadvantages, listed as follows:

  • A large number of IView and IPresenter interface implementation classes are introduced to increase the complexity of the project.
  • The View layer and the Presenter hold each other, and there is coupling.
  1. Introduction to MVVM

Compared with MVC, MVP undoubtedly has many advantages, but it is not invulnerable. In addition, MVP is not the architecture officially recommended by Google, so it can only be regarded as a wild way for programmers to optimize the architecture of Android projects. Beginning in 2015, Google officially began to guide the Android project architecture, and then launched the DataBinding component and a series of Jetpack components to help developers optimize the project architecture. The set of guidance framework officially given by Google is MVVM. MVVM is the abbreviation of **
Model-View-ViewModel**. This architecture solves the problems in the MVP architecture to a certain extent. Although the official guiding framework has recently changed from MVVM to MVI, MVVM is still the mainstream framework for Android projects.

  • The model layer (Model) is consistent with the Model layer in MVP, and is responsible for communicating with the database and network layer, obtaining and storing data. The difference from MVP is that the Model layer no longer notifies the business logic layer of data changes through callbacks, but through the observer mode.
  • View (View) is responsible for visualizing the data of the Model layer and interacting with the ViewModel layer at the same time.
  • The view model (ViewModel) is mainly responsible for the processing of business logic, and interacts with the Model layer and the View layer at the same time. Compared with MVP's Presenter, ViewModel no longer depends on View, making decoupling more thorough.

The structure diagram of the MVVM architecture is as follows.

insert image description here

The essence of the MVVM architecture is data-driven, and its biggest feature is one-way dependence. The MVVM architecture decouples the ViewModel from the View through the observer mode, realizing the one-way dependence that the View depends on the ViewModel, and the ViewModel depends on the Model.

Next, we still take login as an example to implement the MVVM architecture code through the observer mode.

(1) Observer mode

Because the MVVM architecture needs to decouple the ViewModel from the View. Therefore, the observer pattern can be used here to achieve. Below is the code to implement the Observer pattern.

  • Implement the observer interface
public interface Observer<T> {
    
    
    // 成功的回调
    void onSuccess(T data);

    // 失败的回调
    void onFailed(String msg);
}
  • abstract observer
public abstract class Observable<T> {
    
    
    // 注册观察者
    void register(Observer observer);

    // 取消注册
    void unregister(Observer observer);

    // 数据改变(设置成功数据)
    protected void setValue(T data) {
    
    

    }

    // 失败,设置失败原因
    void onFailed(String msg);
}

Here we need to note that the setValue method in the observable Observable is set to protect. It means that if the View layer gets an Observable instance, it cannot call setValue to set the data, so as to ensure the security of the code.

  • Observed concrete implementation
public class LoginObservable implements Observable<User> {
    
    

    private final List<Observer<User>> list = new ArrayList<>();

    private User user;

    @Override
    public void register(Observer<User> observer) {
    
    
        list.add(observer);
    }

    @Override
    public void unregister(Observer<User> observer) {
    
    
        liset.remove(observer);
    }

    @Override
    public void setValue(User user) {
    
    
        this.user = user;
        for (Observer observer : list) {
    
    
            observer.onSuccess(user);
        }
    }

    @Override
    public void onFailed(String msg) {
    
    
        for (Observer observer : list) {
    
    
            observer.onFailed(msg);
        }
    }
}

In the implementation class of the observer, the setValue method is set to public, which means that if it is LoginObservable, then the data can be set through setValue.

(2) Model layer code implementation

The Model layer code is basically consistent with the implementation of MVP/MVC, as follows:

public class LoginModel {
    
    
    public void login(String username, String password, LoginObservable observable) {
    
    
        RetrofitManager.getApiService()
                .login(username, password)
                .enqueue(new Callback<User>() {
    
    
                    @Override
                    public void onResponse(Call<User> call, Response<User> response) {
    
    
                        observable.setValue(response.data);
                    }

                    @Override
                    public void onFailed(Exception e) {
    
    
                        observable.onFailed(e.getMessage());
                    }
                });
    }
}

It should be noted that the login method of LoginModel receives the type of LoginObservable, so the data can be set through setValue here.

(3) ViewModel layer implementation

The ViewModel layer needs to hold the Model layer, and the ViewModel layer holds a LoginObservable, and opens a getObservable method for the View layer to use. code show as below:

public class LoginViewModel {
    
    

    private LoginObservable observable;

    private LoginModel loginModel;

    public LoginViewModel() {
    
    
        loginModel = new LoginModel();
        observable = new LoginObservable();
    }

    public void login(String username, String password) {
    
    
        loginModel.login(username, password, observable);
    }

    // 这里返回值是父类Observable,即View层得到的Observable无法调用setValue
    public Observable getObservable() {
    
    
        return observable;
    }
}

It should be noted here that the return value of the getObservable method is Observable, which means that the View layer can only call the register method to observe data changes, but cannot set the data through setValue.

(4) View layer code implementation

The View layer holds the ViewModel, and the user triggers a login event and passes it to the Model for processing through the View layer, and the Model layer passes the data to the observer in the ViewModel after logging in. Therefore, the View layer only needs to obtain the observer of the ViewModel layer and update the UI after observing the data change. code show as below:

public class MainActivity extends AppCompatActivity implements Observer<User> {
    
    

    private LoginViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewModel = new LoginViewModel();
        viewModel.getObservable().register(this);
        findViewById(R.id.button).setOnClickListener(v -> {
    
    
            String username = findViewById(R.id.et_username).getText().toString();
            String password = findViewById(R.id.et_password).getText().toString();
            viewModel.login(username, password);
        });
    }

    @Override
    public void onDestroy() {
    
    
        super.onDestory();
        viewModel.getObservable().unregister(this);
    }

    @Override
    public void onSuccess(User user) {
    
    
        // fetch data success,update UI.
    }

    @Override
    public void onFailed(String message) {
    
    
        // fetch data failed,update UI.
    }
}

It can be seen that we have realized
the one-way dependency of View->ViewModel->Model through the observer mode. Compared with MVP, MVVM decoupling is more pure. However, the observer mode above is implemented by ourselves, and it is definitely not safe to use it directly in the project. We mentioned above that Google has provided us with a set of Jetpack components to help developers implement the MVVM architecture. Then let's take a look at the MVVM architecture implemented by Jetpack.

2. Use Jetpack components to encapsulate the MVVM architecture

If you have seen this, I believe you already have a certain understanding of the project architecture. It needs to be emphasized here that architecture is an idea, and it has nothing to do with what tools we use to achieve it. Just like in the previous chapter, we implemented MVVM through the observer mode we wrote ourselves, as long as we follow this architectural idea, it belongs to this architecture. These components officially launched by Google can help us realize MVVM more efficiently.

1. Introduction to Jetpack MVVM

MVVM is an officially recommended framework by Google. To this end, Google provides a series of tools to implement MVVM, which are included in Jetpack components, such as LiveData, ViewModel, and
DataBinding. The following is an official MVVM architecture diagram implemented by Jetpack components.

insert image description here

This diagram is consistent with the MVVM structure diagram in our previous chapter, except that Jetpack components are incorporated here. It can be seen that the arrows in the figure are all unidirectional, and the View layer points to the ViewModel layer, indicating that the View layer holds a reference to the ViewModel layer, but the ViewModel layer does not hold the View layer. The ViewModel layer holds the Repository layer, but the Repository layer doesn't hold the ViewModel. The correspondence between this picture and MVVM is as follows:

  • View (View) corresponds to the Activity/Fragment in the figure, including layout files and interface-related things;
  • The view model (ViewModel) corresponds to the Jetpack ViewModel and LiveData in the figure
    , which is used to hold data related to the UI, and can ensure that the data will not be lost after the screen is rotated. In addition, it also provides an interface for calling the View layer and an interface for communicating with the Repository;
  • The model layer (Model) corresponds to the Repository in the figure, including local data and server data.

2. MVVM framework code encapsulation

After understanding Jetpack MVVM, we usually do some basic packaging for more efficient development. For example, how to combine network requests and databases to obtain data, Loading display logic, etc.
The content of this chapter requires readers to have a certain understanding of Jetpack ViewModel, LiveData and other components.

2.1 Network layer encapsulation

To encapsulate the data returned by the server, a generic Response base class can be extracted. as follows:

class BaseResponse<T>(
    var errorCode: Int = -1,
    var errorMsg: String? = null,
    var data: T? = null,
    var dataState: DataState? = null,
    var exception: Throwable? = null,
) {
    
    
    companion object {
    
    
        const val ERROR_CODE_SUCCESS = 0
    }
    
    val success: Boolean
        get() = errorCode == ERROR_CODE_SUCCESS
}

/**
 * 网络请求状态
 */
enum class DataState {
    
    
    STATE_LOADING, // 开始请求
    STATE_SUCCESS, // 服务器请求成功
    STATE_EMPTY, // 服务器返回数据为null
    STATE_FAILED, // 接口请求成功但是服务器返回error
    STATE_ERROR, // 请求失败
    STATE_FINISH, // 请求结束
}

Generally, we will deal with abnormal situations corresponding to network requests in a unified manner. Here we can do it in Observer. Encapsulate the Observer of LiveData, so as to realize the unified processing of Response and exception.

abstract class ResponseObserver<T> : Observer<BaseResponse<T>> {
    
    
    
    final override fun onChanged(response: BaseResponse<T>?) {
    
    
        response?.let {
    
    
            when (response.dataState) {
    
    
                DataState.STATE_SUCCESS, DataState.STATE_EMPTY -> {
    
    
                    onSuccess(response.data)
                }
                DataState.STATE_FAILED -> {
    
    
                    onFailure(response.errorMsg, response.errorCode)
                }
                DataState.STATE_ERROR -> {
    
    
                    onException(response.exception)
                }
                else -> {
    
    
                }
            }
        }
    }
    
    private fun onException(exception: Throwable?) {
    
    
        ToastUtils.showToast(exception.toString())
    }
    
    /**
     * 请求成功
     *  @param  data 请求数据
     */
    abstract fun onSuccess(data: T?)
    
    /**
     * 请求失败
     *  @param  errorCode 错误码
     *  @param  errorMsg 错误信息
     */
    open fun onFailure(errorMsg: String?, errorCode: Int) {
    
    
        ToastUtils.showToast("Login Failed,errorCode:$errorCode,errorMsg:$errorMsg")
    }
}

Encapsulate RetrofitCreator to create API Service.

object RetrofitCreator {
    
    
    
    private val mOkClient = OkHttpClient.Builder()
        .callTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .connectTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .readTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .writeTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .retryOnConnectionFailure(true)
        .followRedirects(false)
        .addInterceptor(HttpHeaderInterceptor())
        .addInterceptor(LogInterceptor())
        .build()
    
    private fun getRetrofitBuilder(baseUrl: String): Retrofit.Builder {
    
    
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(mOkClient)
            .addConverterFactory(GsonConverterFactory.create())
    }
    
    /**
     * Create Api service
     *  @param  cls Api Service
     *  @param  baseUrl Base Url
     */
    fun <T> getApiService(cls: Class<T>, baseUrl: String): T {
    
    
        val retrofit = getRetrofitBuilder(
            baseUrl
        ).build()
        return retrofit.create(cls)
    }
}

2.2 Model layer encapsulation

The Model layer in the official code is implemented through Repository. In order to reduce template code, we can encapsulate BaseRepository to handle network requests.

open class BaseRepository {
    
    
    
    // Loading 状态的 LiveData
    val loadingStateLiveData: MutableLiveData<LoadingState> by lazy {
    
    
        MutableLiveData<LoadingState>()
    }
    
    /**
     * 发起请求
     *  @param  block 真正执行的函数回调
     *  @param responseLiveData 观察请求结果的LiveData
     */
    suspend fun <T : Any> executeRequest(
        block: suspend () -> BaseResponse<T>,
        responseLiveData: ResponseMutableLiveData<T>,
        showLoading: Boolean = true,
        loadingMsg: String? = null,
    ) {
    
    
        var response = BaseResponse<T>()
        try {
    
    
            if (showLoading) {
    
    
                loadingStateLiveData.postValue(LoadingState(loadingMsg, DataState.STATE_LOADING))
            }
            response = block.invoke()
            if (response.errorCode == BaseResponse.ERROR_CODE_SUCCESS) {
    
    
                if (isEmptyData(response.data)) {
    
    
                    response.dataState = DataState.STATE_EMPTY
                } else {
    
    
                    response.dataState = DataState.STATE_SUCCESS
                }
            } else {
    
    
                response.dataState = DataState.STATE_FAILED
// throw ServerResponseException(response.errorCode, response.errorMsg)
            }
        } catch (e: Exception) {
    
    
            response.dataState = DataState.STATE_ERROR
            response.exception = e
        } finally {
    
    
            responseLiveData.postValue(response)
            if (showLoading) {
    
    
                loadingStateLiveData.postValue(LoadingState(loadingMsg, DataState.STATE_FINISH))
            }
        }
    }
    
    private fun <T> isEmptyData(data: T?): Boolean {
    
    
        return data == null || data is List<*> && (data as List<*>).isEmpty()
    }
}

2.3 ViewModel layer encapsulation

If there is no encapsulation, the ViewModel layer will also have template code. Here the common code is completed in BaseViewModel. BaseViewModel needs to hold Repository.

abstract class BaseViewModel<T : BaseRepository> : ViewModel() {
    
    
    val repository: T by lazy {
    
    
        createRepository()
    }
    
    val loadingDataState: LiveData<LoadingState> by lazy {
    
    
        repository.loadingStateLiveData
    }
    
    /**
     * 创建Repository
     */
    @Suppress("UNCHECKED_CAST")
    open fun createRepository(): T {
    
    
        val baseRepository = findActualGenericsClass<T>(BaseRepository::class.java)
            ?: throw NullPointerException("Can not find a BaseRepository Generics in ${
      
      javaClass.simpleName}")
        return baseRepository.newInstance()
    }
}

2.4 View layer encapsulation

ViewModel is held in BaseActivity/BaseFragment, and Loading is displayed and hidden according to LoadingState.

abstract class BaseActivity<VM : BaseViewModel<*>, VB : ViewBinding> : AppCompatActivity() {
    
    
    
    protected val viewModel by lazy {
    
    
        createViewModel()
    }
    
    protected val binding by lazy {
    
    
        createViewBinding()
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        viewModel.loadingDataState.observe(this) {
    
    
            when (it.state) {
    
    
                DataState.STATE_LOADING ->
                    showLoading(it.msg)
                else ->
                    dismissLoading()
            }
        }
        onActivityCreated(savedInstanceState)
    }
    
    /**
     * Activity content view created.
     *  @param  savedInstanceState savedInstanceState
     */
    abstract fun onActivityCreated(savedInstanceState: Bundle?)
    
    /**
     * 显示Loading
     */
    open fun showLoading(msg: String? = null) {
    
    
        ToastUtils.showToast("showLoading")
    }
    
    /**
     * 隐藏Loading
     */
    open fun dismissLoading() {
    
    
        ToastUtils.showToast("hideLoading")
    }
    
    /**
     * Create ViewBinding
     */
    @Suppress("UNCHECKED_CAST")
    open fun createViewBinding(): VB {
    
    
        val actualGenericsClass = findActualGenericsClass<VB>(ViewBinding::class.java)
            ?: throw NullPointerException("Can not find a ViewBinding Generics in ${
      
      javaClass.simpleName}")
        try {
    
    
            val inflate = actualGenericsClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
            return inflate.invoke(null, layoutInflater) as VB
        } catch (e: NoSuchMethodException) {
    
    
            e.printStackTrace()
        } catch (e: IllegalAccessException) {
    
    
            e.printStackTrace()
        } catch (e: InvocationTargetException) {
    
    
            e.printStackTrace()
        }
    }
    
    /**
     * Create ViewModel
     *  @return  ViewModel
     */
    @Suppress("UNCHECKED_CAST")
    open fun createViewModel(): VM {
    
    
        val actualGenericsClass = findActualGenericsClass<VM>(BaseViewModel::class.java)
            ?: throw NullPointerException("Can not find a ViewModel Generics in ${
      
      javaClass.simpleName}")
        if (Modifier.isAbstract(actualGenericsClass.modifiers)) {
    
    
            throw IllegalStateException("$actualGenericsClass is an abstract class,abstract ViewModel class can not create a instance!")
        }
        // 判断如果是 BaseAndroidViewModel,则使用 AppViewModelFactory 来生成
        if (BaseAndroidViewModel::class.java.isAssignableFrom(actualGenericsClass)) {
    
    
            return ViewModelProvider(this, AppViewModelFactory(application))[actualGenericsClass]
        }
        return ViewModelProvider(this)[actualGenericsClass]
    }
}

The process of creating ViewModel is automatically generated in BaseActivity according to the generic type of the subclass. This is done using reflection. If you think it affects performance, you can rewrite the function in the subclass createViewModelto generate ViewModel yourself
.

In addition, if you need to use ViewModel with Application, you can inherit BaseAndroidViewModel, and its implementation refers to AndroidViewModel.

abstract class BaseAndroidViewModel<T : BaseRepository>(@field:SuppressLint("StaticFieldLeak") var application: Application) : BaseViewModel<T>()

Creating BaseAndroidViewModel here requires the use of AppViewModelFactory.

class AppViewModelFactory(private val application: Application) : ViewModelProvider.NewInstanceFactory() {
    
    
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
    
    
        return if (BaseAndroidViewModel::class.java.isAssignableFrom(modelClass)) {
    
    
            try {
    
    
                modelClass.getConstructor(Application::class.java).newInstance(application)
            } catch (e: NoSuchMethodException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            } catch (e: IllegalAccessException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            } catch (e: InstantiationException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            } catch (e: InvocationTargetException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            }
        } else super.create(modelClass)
    }
}

If LoginViewModel implements BaseAndroidViewModel, then when using ViewModelProvider to create LoginViewModel
, you must pass in AppViewModelFactory parameter instead of AndroidViewModelFactory in Jetpack ViewModel library.

ViewModelProvider(this, AppViewModelFactory(application))[LoginViewModel::class.java]

3. Example application after MVVM encapsulation

After completing the above encapsulation, let's see how to implement the login logic.

3.1 Implement Repository

class LoginRepository : BaseRepository() {
    
    
    /**
     * Login
     *  @param  username 用户名
     *  @param  password 密码
     */
    suspend fun login(username: String, password: String, responseLiveData: ResponseMutableLiveData<LoginResponse>, showLoading: Boolean = true) {
    
    
        executeRequest({
    
    
            RetrofitManager.apiService.login(username, password)
        }, responseLiveData, showLoading)
    }
}

RetrofitManager is used to create management API Service:

object RetrofitManager {
    
    
    private const val BASE_URL = "https://www.wanandroid.com/"
    
    val apiService: ApiService by lazy {
    
    
        RetrofitCreator.createApiService(
            ApiService::class.java, BASE_URL
        )
    }
}

3.2 ViewModel instance

class LoginViewModel : BaseViewModel<LoginRepository>() {
    
    
    // 提供给Model层设置数据
    private val _loginLiveData: ResponseMutableLiveData<LoginResponse> = ResponseMutableLiveData()
    
    // 提供给View层观察数据
    val loginLiveData: ResponseLiveData<LoginResponse> = _loginLiveData
    
    /**
     * Login
     *  @param  username 用户名
     *  @param  password 密码
     */
    fun login(username: String, password: String) {
    
    
        viewModelScope.launch {
    
    
            repository.login(username, password, _loginLiveData)
        }
    }
}

3.3 Activity/Fragment instance

class LoginActivity : BaseActivity<LoginViewModel, ActivityBizAMainBinding>() {
    
    
    
    override fun onActivityCreated(savedInstanceState: Bundle?) {
    
    
        binding.tvLogin.setOnClickListener {
    
    
            viewModel.login("110120", "123456")
        }
        
        viewModel.loginLiveData.observe(this, object : ResponseObserver<LoginResponse>() {
    
    
            override fun onSuccess(data: LoginResponse?) {
    
    
                // Update UI
            }
        })
    }
}

What needs to be noted here is that immutable LiveData needs to be used in the Activity to prevent
setValue/postValue through LiveData in the View layer during development, causing wrong UI update problems. Therefore, some changes have been made to LiveData, as follows:

abstract class ResponseLiveData<T> : LiveData<BaseResponse<T>> {
    
    
    /**
     * Creates a MutableLiveData initialized with the given `value`.
     *
     *  @param  value initial value
     */
    constructor(value: BaseResponse<T>?) : super(value)
    
    /**
     * Creates a MutableLiveData with no value assigned to it.
     */
    constructor() : super()
    
    /**
     * Adds the given observer to the observers list within the lifespan of the given owner.
     * The events are dispatched on the main thread. If LiveData already has data set, it
     * will be delivered to the observer.
     */
    fun observe(owner: LifecycleOwner, observer: ResponseObserver<T>) {
    
    
        super.observe(owner, observer)
    }
}

ResponseLiveData inherits from LiveData, so ResponseLiveData is immutable. In addition, a ResponseMutableLiveData is defined, which is a mutable
LiveData. The code is as follows:

class ResponseMutableLiveData<T> : ResponseLiveData<T> {
    
    
    
    /**
     * Creates a MutableLiveData initialized with the given `value`.
     *
     *  @param  value initial value
     */
    constructor(value: BaseResponse<T>?) : super(value)
    
    /**
     * Creates a MutableLiveData with no value assigned to it.
     */
    constructor() : super()
    
    public override fun postValue(value: BaseResponse<T>?) {
    
    
        super.postValue(value)
    }
    
    public override fun setValue(value: BaseResponse<T>?) {
    
    
        super.setValue(value)
    }
}

So far, the encapsulation and use of Jetpack MVVM is over.

Summarize

This article explains in detail the evolution of the Android project architecture from MVC, MVP to MVVM, and lists detailed implementation examples for the three architectures.
At the same time , it encapsulates the current mainstream Jetpack MVVM architecture. Of course, since everyone's understanding of architecture is not necessarily the same, there will inevitably be inconsistencies in all articles with readers' understanding. Welcome to leave a message for discussion.

Guess you like

Origin blog.csdn.net/qq_20521573/article/details/127038987