Jetpack AAC Series:

(1) Lifecycle will be fully mastered!

“Finally get it” series: Jetpack AAC complete analysis (2) Fully master LiveData!

“Finally get it” series: Jetpack AAC complete analysis (3) fully master the ViewModel!

“Finally Get it” series: Jetpack AAC Complete Analysis (iv) MVVM Architecture Exploration!

“Finally Get it” series: Jetpack AAC Complete Analysis (5) DataBinding re-cognition!

Welcome to follow my public account, wechat search Hu Feiyang, article updates can be the first time to receive.

The first three articles introduced the most important components of the Jetpack architecture: the Lifecycle component, the life-cycle aware data component, LiveData, and the ViewModel component, ViewModel. In this article, I’ll explore one of the best and most discussed architectural patterns in Android development — MVVM.

A few months ago, my project completed an architectural overhaul of MVVM. I also read a lot of MVVM articles before I started writing this one. So, this article tries to be as clear about the development architectural patterns and the nature of MVVM as possible, so that there’s a kind of “Oh, I see.”

Note that DataBinding and bidirectional binding are not mentioned at all in this article, and we’ll explain why at the end.

What is the development architecture?

To understand the nature of development architecture, wikipedia describes software architecture as follows:

Software architecture is a rough sketch of a system. Software architecture describes the abstract components that directly make up a system. The connections between the components clearly and relatively carefully describe the communication between the components. In the implementation phase, these abstract components are refined into actual components, such as concrete classes or objects. In the object-oriented world, the connections between components are usually implemented by interfaces.

Broken down into three:

  1. For a complete system, this system can achieve a certain function.
  2. The system consists of several modules, and there are some relationships and connections between modules.
  3. Architecture is the implementation description that implements this system: module responsibilities, connections between modules.

Why do development architecture?

  1. Modularity responsibilities are specific so that each module is internally focused.
  2. The association between modules is simplified, reducing coupling.
  3. Easy to use and maintain
  4. Improve development efficiency

Architectural patterns ultimately serve the developer. If code responsibilities and logic are confused, maintenance costs will rise accordingly.

At a macro level, developing an architecture is an idea. Each domain has a number of mature architectural patterns.

2. Architecture in Android development

Specific to Android development, development architecture is to describe the relationship and implementation among view layer, logic layer and data layer:

  • View layer: the user interface, the presentation of the interface, and the response to interactive events.
  • Logical layer: the necessary logic to implement system functions.
  • Data layer: data acquisition and storage, including local and server.

In a normal development process, there is an architectural design process before you start writing code. It is up to you to choose which architectural pattern to use.

My Android development path has gone through MVC, MVP and MVVM. I believe that many developers and I are the same process. Let’s review the three first.

2.1 the MVC

MVC, Model-view-Controller, responsibility classification is as follows:

  • Model, the Model layer, the data Model, is used to get and store data.
  • View, View layer, XML layout
  • The Controller is responsible for the business logic.

The View layer receives the user action events, notifies the Controller for corresponding logical processing, and then notifies the Model to obtain/update the data. The Model then notifies the new data to the View update interface. This is the data flow of a full MVC.

However, in Android, due to the weak XML layout capability, a lot of View operations are written in the Activity/Fragment, and the business logic is also written in the Activity/Fragment.

So, here’s the problem with MVC:

  1. Activity/Fragment responsibility is not clear, and at the same time responsible for View, Controller, will lead to a large amount of code, do not meet the single responsibility.
  2. The Model is coupled to the View. Modifying the View will cause both the Controller and the Model to change, which does not satisfy the least knowledge principle.

2.2 the MVP

MVP, Model-View-Presenter, responsibilities are classified as follows:

  • Model, the Model layer, the data Model, is used to get and store data.
  • View, View layer, Activity/Fragment
  • Presenter, the control layer, is responsible for business logic.

MVP solves MVC problems: 1.View responsibility is clear. Logic is no longer written in the Activity, but in the Presenter. 2.Model no longer holds the View.

The View layer receives the user operation event and notifishes it to the Presenter. The Presenter processes the data logically and then notifishes the Model to update the data. The Model sends the updated data to the Presenter, and the Presenter notifishes it to the View update interface.

MVP:

  • The UI logic is abstracted into the IView interface, which is completed by the concrete Activity implementation class. And calls Presenter for logical operations.
  • The business logic is abstracted into the IPresenter interface, which is completed by a concrete Presenter implementation class. The IView interface method is called after the logical operation completes to refresh the UI.

MVP is essentially interface oriented programming and implements the dependency inversion principle. MVP addresses the problem of unclear accountability at the View level, but it does not address the problem of code coupling, where the View and Presenter are mutually owned.

So here’s what’s wrong with MVP:

  1. Will introduce a large number of IView, IPresenter interface, increase the complexity of the implementation.
  2. Views and Presenters hold each other and are coupled.

2.3 MVVM

MVVM, Model-view-viewModel, responsibilities are classified as follows:

  • Model, the Model layer, the data Model, is used to get and store data.
  • View, View, Activity/Fragment
  • The ViewModel is responsible for the business logic.

Note that ViewModel in MVVM is just a name and can be understood as a Presenter in MVP. Unlike the ViewModel component in the previous article, the Jetpack ViewModel component is a concrete implementation of MVVM’s ViewModel.

MVVM is data-driven by nature and decouples more thoroughly. Viewmodels do not hold views.

The View generates an event, uses the ViewModel for logical processing, and informs the Model to update the data. The Model sends the updated data to the ViewModel, and the ViewModel automatically informs the View to update the interface, instead of calling the View’s methods.

How is MVVM implemented in Android development? Then see ~

As you can see here, the so-called architectural patterns are essentially simple to understand. For example, MVP, even you can ignore the name and think of it as programming to interfaces at a higher level, implementing the dependency inversion principle. It’s that simple.

MVVM implementation – Jetpack MVVM

As mentioned earlier, choose the architectural pattern that is appropriate for your project. Having said that, the architectural pattern recommended by Google is suitable for most situations and is well worth learning and practicing.

All right, let’s go into the details of the Jetpack MVVM architecture.

3.1 Jetpack MVVM understanding

Jetpack MVVM is a specific implementation of MVVM mode in Android development. It is the official MVVM implementation method provided and recommended by Google in Android.

Not only complete decoupling through data driven, but also take into account other unexpected errors in Android page development. For example, Lifecycle can properly handle the page Lifecycle to avoid the view null pointer problem. ViewModel makes the UI need not request data from the background again when rebuilding, saving the cost. Make the data appear faster when the view is rebuilt.

First, take a look at the figure below, which shows how all the modules should interact with each other:

Each module corresponds to the MVVM architecture:

  • The View layer: the Activity/fragments
  • ViewModel layer: Jetpack ViewModel + Jetpack LivaData
  • Model layer: Repository, which contains both local persistent data and server-side data

The View layer contains the activities, fragments, layout files and other interface related things we usually write.

The ViewModel layer holds the data associated with UI elements, ensures that the data is not lost when the screen rotates, and provides interfaces for the View layer to call and communicate with the warehouse layer.

The main job of the warehouse layer is to determine whether the data requested by the caller should be obtained from a local or network data source and return the obtained data to the caller. Local data sources can be implemented using persistence technologies such as databases, SharedPreferences, and so on, while web data sources are typically implemented using the Webservice interface provided by the Retrofit access server.

In addition, all arrows in the diagram are unidirectional. For example, the View layer points to the ViewModel layer, indicating that the View layer holds a reference to the ViewModel layer, but the ViewModel layer cannot hold a reference to the View layer. In addition, references cannot be held across layers. For example, the View layer cannot hold references to the repository layer. Remember that components in each layer can only interact with components in its neighboring layers.

This design creates a consistent and enjoyable user experience. Whether the user last used the application a few minutes ago or a few days ago, when he or she returns to the application now, he or she will immediately see the data that the application holds locally. If the data is out of date, the application’s Repository will start updating the data in the background.

3.2 the implementation of

Let’s take a complete example of the implementation of Jetpack MVVM by displaying a list of user information on the page.

3.2.1 Building a UI

You start by creating a list page, UserListActivity, and you know that the data that the page needs is a list of user information.

So how do you get a list of user information? According to the architecture diagram above, this is the ViewModel, so we create the UserListViewModel inheriting from the ViewModel and presenting the list of user information as LiveData.

public class UserListViewModel extends ViewModel {
    // User information
    private MutableLiveData<List<User>> userListLiveData;
    // Display of input bar
    private MutableLiveData<Boolean> loadingLiveData;

    public UserListViewModel(a) {
        userListLiveData = new MutableLiveData<>();
        loadingLiveData = new MutableLiveData<>();
    }
    
    public LiveData<List<User>> getUserListLiveData() {
        return userListLiveData;
    }
    public LiveData<Boolean> getLoadingLiveData(a) {
        returnloadingLiveData; }... }Copy the code

LiveData is an observable data store. Other components in the application can use this storage to monitor changes to objects without creating clear and strict dependency paths between them. The LiveData component also follows the lifecycle state of application components such as activities, fragments, and services, and includes cleanup logic to prevent object leaks and excessive memory consumption.

Change the field type in UserListViewModel to MutableLiveData<List>. UserListActivity is now notified when the data is updated. In addition, because this LiveData field is lifecycle aware, they are automatically cleaned up when references are no longer needed.

Also, notice that the exposed method that gets LiveData returns the LiveData type, that is, immutable, not MutableLiveData, with the benefit of avoiding external changes to the data. (See the LivaData article)

Now, we modify UserListActivity to observe the data and update the interface:

//UserListActivity.java.// View the data in the ViewModel, and this data is directly needed by the View, no logical processing is required
    private void observeLivaData(a) {
        mUserListViewModel.getUserListLiveData().observe(this.new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                if (users == null) {
                    Toast.makeText(UserListActivity.this."Failed to get user!", Toast.LENGTH_SHORT).show();
                    return;
                }
                // Refresh the listmUserAdapter.setNewInstance(users); }}); mUserListViewModel.getLoadingLiveData().observe(this.new Observer<Boolean>() {
            @Override
            public void onChanged(Boolean aBoolean) {
            	// Show/hide the loading progress barmProgressBar.setVisibility(aBoolean? View.VISIBLE:View.GONE); }}); }Copy the code

The system calls the onChanged() callback and refreshes the interface each time the user list information data is updated, instead of requiring the ViewModel to proactively call the View layer method to refresh. This is data-driven — the data change drives the View to refresh automatically.

Because LiveData is lifecycle aware, this means that it does not call the onChanged() callback unless the Activity is active. LiveData also automatically removes observers when an Activity’s onDestroy() method is called.

Also, we didn’t add any logic to handle configuration changes (for example, the user rotates the device’s screen). UserListViewModel automatically resumes after configuration changes, so once a new Activity is created, it receives the same ViewModel instance and immediately calls the callback with the current data. Since ViewModel objects are supposed to last longer than their updated counterparts, ViewModel implementations must not contain direct references to View objects, including Context.

3.2.2 Obtaining Data

Now that we have connected the UserListViewModel to the UserListActivity using LiveData, how do we get the user’s personal information list data?

The first idea to implement the ViewModel might be to use the Retrofit/Okhttp call interface to get the data and then set that data to the LiveData object. This design makes sense, but if adopted, it will become harder and harder to maintain as the application expands. This would place too much responsibility on the UserListViewModel class, which would violate the single responsibility principle.

ViewModel delegates the data retrieval process to a new module, called Repository.

The Repository module handles data operations. They provide a clean API so that the rest of the application can easily access the data. When the data is updated, they know where to get the data and what API calls to make. You can think of Repository as a medium between different data sources, such as persistence models, network services, and caches.

public class UserRepository {

    private static UserRepository mUserRepository;
    public static UserRepository getUserRepository(a){
        if (mUserRepository == null) {
            mUserRepository = new UserRepository();
        }
        return mUserRepository;
    }

    //(pretend) to get it from the server
    public void getUsersFromServer(Callback<List<User>> callback){
        new AsyncTask<Void, Void, List<User>>() {
            @Override
            protected void onPostExecute(List<User> users) {
                callback.onSuccess(users);
                // Save the local database
                saveUsersToLocal(users);
            }
            @Override
            protected List<User> doInBackground(Void... voids) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // Pretend to get it from the server
                List<User> users = new ArrayList<>();
                for (int i = 0; i < 20; i++) {
                    User user = new User("user"+i, i);
                    users.add(user);
                }
                return users;
            }
        }.execute();
    }
    
Copy the code

While the Repository module may seem unnecessary, it plays an important role: it extracts data sources from the rest of the application. Now, the UserListViewModel doesn’t know where the data comes from, so we can provide the ViewModel with access to data from several different sources.

3.2.3 Connecting ViewModel to storage area

We provide a method in the UserListViewModel, the user Activity, to get user information. This method executes by calling Repository and setting the data to LiveData.

public class UserListViewModel extends ViewModel {
    // User information
    private MutableLiveData<List<User>> userListLiveData;
    // Display of input bar
    private MutableLiveData<Boolean> loadingLiveData;

    public UserListViewModel(a) {
        userListLiveData = new MutableLiveData<>();
        loadingLiveData = new MutableLiveData<>();
    }
    
    /** * get a list of users * pretend to network request for 2 seconds and return user information */
    public void getUserInfo(a) {

        loadingLiveData.setValue(true);

        UserRepository.getUserRepository().getUsersFromServer(new Callback<List<User>>() {
            @Override
            public void onSuccess(List<User> users) {
                loadingLiveData.setValue(false);
                userListLiveData.setValue(users);
            }

            @Override
            public void onFailed(String msg) {
                loadingLiveData.setValue(false);
                userListLiveData.setValue(null); }}); }// Return the LiveData type
    public LiveData<List<User>> getUserListLiveData() {
        return userListLiveData;
    }
    public LiveData<Boolean> getLoadingLiveData(a) {
        returnloadingLiveData; }}Copy the code

In the Activity, get the UserListViewModel instance and get the user information:

//UserListActivity.java
public class UserListActivity extends AppCompatActivity {
    private UserListViewModel mUserListViewModel;
    private ProgressBar mProgressBar;
    private RecyclerView mRvUserList;
    private UserAdapter mUserAdapter;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_list);

        initView();
        initViewModel();
        getData();
        observeLivaData();
    }
    private void initView(a) {... }private void initViewModel(a) {
        ViewModelProvider viewModelProvider = new ViewModelProvider(this);
        mUserListViewModel = viewModelProvider.get(UserListViewModel.class);
    }

    /** * get the data, call the ViewModel method to get */
    private void getData(a) {
        mUserListViewModel.getUserInfo();
    }
    
    private void observeLivaData(a) {... }Copy the code

3.2.4 Caching data

The problem with UserRepository is that it does not cache data once it gets it from the back end. Therefore, if the user leaves the page and returns, the application must retrieve the data, even if the data has not changed. This wastes valuable network resources and forces users to wait for new queries to complete. So, we added a new data source, the local cache, to UserRepository. The cache implementation can be a database, SharedPreferences, and other persistence technologies. (I won’t write the implementation.)

//UserRepository.java

    // Obtain it from the local database
    public void getUsersFromLocal(a){
        // TODO:2021/1/24 Obtain the data from the local database
    }

    // Save to local database (can be called after fetching data from server)
    private void saveUsersToLocal(List<User> users){
        // TODO:2021/1/24 Save to the local database
    }

Copy the code

That concludes the introduction to Jetpack MVVM.

In fact, this should be fairly easy to understand as long as you are familiar with Lifecycle, LivaData and ViewModel as described earlier.

3.3 pay attention to the point

  1. Establish clearly defined boundaries of responsibility between modules of the application.

  2. ViewModel cannot hold View layer references, including Context.

  3. Specifies a data source as a single trusted source. Whenever data needs to be accessed, it should always come from this single trusted source. UserRepository, for example, stores network service responses in a database. In this way, changes to the database trigger a callback to the LiveData object. The database acts as a single trusted source.

  4. Keep as much relevant and up-to-date data as possible. This way, users can use the functionality of your application even if their device is in offline mode. Note that not all users enjoy a stable high-speed connection.

  5. The page status is displayed. For example, the loading progress bar is operated by observing MutableLiveData loadingLiveData in the ViewModel.

3.4 MVP remodel MVVM

Once you know the implementation of the Jetpack MVVM, it’s easy to reinvent the MVP.

The steps are as follows:

  1. Remove Presener references to the View and context.
  2. The Presener is replaced with an implementation of the ViewModel, and the fetched data is presented as LivaData.
  3. Delete defined interfaces such as IView, Activity/Fragment to get ViewModel instance, call its methods to get data.
  4. Activity/Fragment Observe the required LivaData and refresh the UI.

This has become MVVM. Of course, you should also check that the original MVP’s implementation of the Model layer meets the above requirements.

Four,

This article introduces the meaning of architectural patterns, reviews and compares the architectural patterns in Android MVC, MVP, MVVM, preferably in The Jetpack architectural components based on the detailed implementation of MVVM, attention points, and MVP transformation.

The whole article down, basically very simple easy to understand. The examples are simple, so in real development you need to understand the data-driven nature of MVVM and the difference between MVP.

Some people may be wondering: why is DataBinding not mentioned at all?

Actually, that’s what I was wondering. It’s not mentioned because:

  1. I don’t want readers to associate MVVM with DataBinding
  2. I want readers to grasp the data-driven nature of MVVM.
  3. DataBinding, on the other hand, provides a bidirectional binding tool that is used to complement the Jetpack MVVM and is itself controversial in the industry.
  4. Grasp this content, already is Google recommended development architecture, has implemented the MVVM pattern. There’s also no mention of DataBinding in Google’s official application architecture guidelines.

So, in the next article, I’ll continue with the components of Jetpack AAC: DataBinding, a data-binding component, and Room, a database component, as complementary points to the Jetpack MVVM. It will also be the last in the complete Jetpack AAC parsing series. Stay tuned!

The Demo address

.

Thanks and reference:

ViewModel official documentation

Is a refreshing Jetpack MVVM elaborate ah!

Architectural patterns in Android development — MVC/MVP/MVVM

.

Your likes and comments are a great encouragement to me!

Welcome to follow my public account, wechat search Hu Feiyang, article updates can be the first time to receive.