An overview of the

At Google I/O 2017, Google introduced a number of architectural components such as Lifecycle, ViewModel, and LiveData that are more suitable for MVVM development.

The main character of this article is the ViewModel, and some of you may ask:

Something as simple as the ViewModel, from API usage to source code analysis, has been extensively covered. What else can you bring up in this article?

I can’t argue with that. In fact, you’re probably already comfortable with the MVVM code, or you’ve been through a whole project, but I want to take a shot at writing it — even if the idea of the MVVM pattern is in your head, or if you’re already comfortable with MVVM, this article will be as helpful as it can be. At least the reading experience is less boring.

ViewModel’s past and present lives

ViewModel, or MVVM (Model-view-ViewModel), is not a new term. Its definition originated in the front end and represents the idea of a data-driven View.

For example, we can use a String status to represent a TextView, and we can also use a List

status to maintain a RecyclerView List — in practice, we observe the status of these data to maintain automatic UI updates. This is the data-driven view (observer mode).

The View layer detects and automatically updates the UI whenever the String data state changes. Similarly, the RecyclerView automatically refreshes the List whenever the data source List

changes:

For developers, the UI layer and the Model layer can be greatly reduced in the development process, and more focus on writing business code.

This is how the concept of the ViewModel was introduced. I describe it as a state store that stores the various states of the UI. Taking the login interface as an example, it’s easy to think of the two simplest states:

class LoginViewModel {
    val username: String  // The content in the user name input box
    val password: String  // The content in the password input box
}
Copy the code

Without getting into the details of the code, we now know that the ViewModel is focused on maintaining the state of the data. Let’s take a look at how MVVMS blossomed on Android 17 years ago, before Google introduced the ViewModel component.

1. A hundred flowers blossomed in the era of partition

Speaking of MVVM, Google announced the DataBinding library at IO 2015. The release of DataBinding directly promoted MVVM in the Android space by binding data state to XML layout files in the form of pseudo Java code. Thus the MVVM pattern development process is formed into a closed loop:

<?xml version="1.0" encoding="utf-8"? >
<layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable
               name="user"
               type="User" />
       </data>
      <TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{ user.name }"
          android:textSize="20sp" />
</layout>
Copy the code

The DataBinding implementation, which uses pseudo-Java code to simply add UI logic to an XML layout file to bind to a View, has caused a lot of controversy. To this day, DataBinding is still frowned upon by many developers, which is entirely understandable because it makes it difficult to locate syntax errors and run-time crashes.

The MVVM model doesn’t necessarily rely on DataBinding, but developers didn’t have a lot of options other than DataBinding — some MVVM developers still insist on not using DataBinding. Instead, use biosphere rich RxJava (or something) instead of DataBinding’s DataBinding.

If there was at least an official DataBinding for DataBinding libraries, it was very difficult to standardize the ViewModel — the basic constraint of state management based on the ViewModel layer, different projects, different dependency libraries and different developers, How state management is implemented in the final code is very different.

For example, some developers define the ViewModel layer as an interface like MVP:

interface IViewModel 

open class BaseViewModel: IViewModel
Copy the code

Other developers (such as this repo) directly inherit observable properties from the ViewModel layer (such as the dataBinding library’s BaseObservable) and hold references to the Context:

public class CommentViewModel extends BaseObservable {

    @BindingAdapter("containerMargin")
    public static void setContainerMargin(View view, boolean isTopLevelComment) {
        / /...}}Copy the code

There are a thousand hamlets for a thousand people, and different MVVMS have completely different implementations. This variety of code styles and development schools that are difficult to strictly unify lead to uneven code quality, and the readability of the code is extremely different.

Plus DataBinding itself results in the decrease of code readability, really is on the south north send huashan mountain, ideas pouring out – from thought the collision of communication, this is not a bad thing, but to want to learn the MVVM me at that time, was to see dazzling, in the process of learning to contact, I also inevitable many detours.

2.Google’s attempt to standardize the ViewModel

As we all know, Google unveiled a series of architectural components at last year’s I/O conference, and ViewModel is one of them, which is the subject of this article.

Interestingly, compared to Lifecycle and LiveData, the ViewModel is very low-key. It mainly provides these features:

  • Automatic retention of its data during configuration changes (such as horizontal and vertical screen rotation)
  • Activity,FragmentEtc. Communication between UI components

It would be very difficult for me to directly extol how great the ViewModel is, because the features it displays on the surface are not that impressive, but I’ve been fortunate enough to spend some time so far explaining the story of the ViewModel that precedes it — they’re an essential foreshado for the rest of the text.

3. The ViewModel dilemma before this

You may not be aware of some of the dilemmas of the ViewModel layer in the MVVM development mode prior to the official ViewModel release, but I’ve actually tried to describe them as follows:

3.1 More normalized abstract interface

Before the official ViewModel was released, the ViewModel layer had a wide variety of base classes, internal dependencies, and common logic. The new ViewModel component directly standardizes the ViewModel layer, using ViewModel(or its subclass AndroidViewModel).

At the same time, Google officially recommends that the ViewModel be as pure business code as possible and not hold any reference to the View layer (Activity or Fragment) or Lifecycle to ensure the testability of the code inside the ViewModel. Avoid testing code that is difficult to write because of references such as Context (for example, there are additional costs associated with testing Presenter layer code in MVP, such as dependency injection or mocks, to ensure unit testing).

3.2 Easier to save data

The system responds to user interactions or reconstructs components that the user cannot control. When a component is destroyed and rebuilt, the data associated with the original component is lost — the simplest example is the rotation of the screen. If the data type is simple and the amount of data is small, the data can be stored by onSaveInstanceState(), after the component is rebuilt by onCreate(), Read from the Bundle to recover data. However, if it is a large amount of data that is not convenient for serialization and deserialization, the above method will not apply.

The Extension class of the ViewModel will automatically retain its data in this case, and if the Activity is recreated, it will receive the same ViewModel instance as before. When the Activity terminates, the framework calls the ViewModel onCleared() method to release the resources:

As such, the ViewModel is scoped; it does not generate more instances within the specified scope, thus saving more code on state maintenance (storage, serialization, and deserialization of the data).

The ViewModel maintains local singletons within the lifetime of the corresponding scope, which leads to a more useful feature of communication between UI components such as fragments and activities.

3.3 Easier communication between UI components

It is common for multiple fragments in an Activity to communicate with each other. If the ViewModel instantiation scope is the Activity lifecycle, then two fragments can hold an instance of the same ViewModel, which means that data state is shared:

public class AFragment extends Fragment {
    private CommonViewModel model;
    public void onActivityCreated(a) { model = ViewModelProviders.of(getActivity()).get(CommonViewModel.class); }}public class BFragment extends Fragment {
    private CommonViewModel model;
    public void onActivityCreated(a) { model = ViewModelProviders.of(getActivity()).get(CommonViewModel.class); }}Copy the code

The above two fragments getActivity() return the same host Activity, so the two fragments return the same ViewModel.

I don’t know if you’ve been reading this and thought:

Why do these features provided by the ViewModel feel disconnected from each other?

Which brings up the following question, which is:

What is the nature of these features?

ViewModel: Holding and maintaining the state

The ViewModel layer’s primary responsibility is to maintain the state of the UI, and ultimately to maintain the corresponding data — after all, whether it’s MVP or MVVM, the presentation of the UI is the rendering of the data.

  • 1. DefinitionViewModelBase class and recommended through holdingLiveDataMaintaining the state of stored data;
  • 2.ViewModelNot asActivityThe screen rotation while destroying is reducedMaintaining stateCode cost (data storage and reading, serialization and deserialization);
  • 3. In the corresponding scope, only the corresponding unique instance can be produced. Multiple fragments maintain the same data state, which greatly reduces the code cost of data transmission between UI components.

Now that we have a good idea of what the ViewModel is and what the idea is, it’s logical that we should explain how to use the ViewModel, but I want to wait, because I think mastering the essence of the idea will help you in the rest of your code practice more than just using the API.

No, not source parsing…

Through the library provides API interface for a start, reading its internal source, this is a standard master code within the principle of idea, time cost is very high this way, even if there is relevant source code analysis blog guidance, vast expanse of the source code in a text and annotation is enough to let a person, so I took it for granted to think so:

Learn how to use first, take time to learn its principle and thought……

Found no, the and the learning method of the school time was quite the opposite, even said it’s no surprise to putting the cart before the horse – any physical or mathematical formula, before use it to the topic, on the basis of the theory behind it should be preferred to study master of systemic (for example, the mathematical formula of learning generally requires first is derived and proved through a certain way), So THAT I can use this knowledge for the problem set after class. It’s like, if a teacher tells you a formula and then doesn’t tell you to do it, the teacher is not qualified.

I’m also not a big fan of copying source code, so I’m going to take a look at how to design a ViewModel from the perspective of a Google engineer.

From a higher perspective, design the ViewModel

Now that we’re Google engineers, let’s review what the ViewModel should do:

  • 1. NormalizedViewModelThe base class;
  • 2.ViewModelNot asActivityThe screen is rotated and destroyed;
  • 3. In the corresponding scope, ensure that only the corresponding unique instance is produced to ensure communication between UI components.

1. Design the base class

This one is too simple:

public abstract class ViewModel {

    protected void onCleared(a) {}}Copy the code

We define an abstract ViewModel base class and an onCleared() method to release resources. Then the developer needs to make his or her XXXViewModel inherit from this abstract ViewModel base class.

2. Ensure that the data is not destroyed with the rotation of the screen

It’s an amazing feature, but it’s pretty simple to implement. Let’s start with this:

SetRetainInstance (Boolean) is a method in the Fragment. Setting this method to true causes the current Fragment to survive when the Activity is rebuilt

This seems like a good fit for our function, so we can’t help but wonder if the Activity can hold an invisible Fragment(let’s call it a HolderFragment), And have the HolderFragment call the setRetainInstance(Boolean) method and hold the ViewModel — so that when the Activity is destroyed and rebuilt due to screen rotation, The Fragment contains a ViewModel that cannot be destroyed or recycled:

public class HolderFragment extends Fragment {

     public HolderFragment(a) { setRetainInstance(true); }
    
      private ViewModel mViewModel;
      / / the getter and setter...
}
Copy the code

Of course, given that a complex UI component can hold multiple ViewModels, let’s have the invisible HolderFragment hold an array (or Map) of ViewModels — let’s wrap a container object called ViewModelStore. To host and broker all ViewModel management:

public class ViewModelStore {
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
    // put(), get(), clear()....
}

public class HolderFragment extends Fragment {

      public HolderFragment(a) { setRetainInstance(true); }

      private ViewModelStore mViewModelStore = new ViewModelStore();
}
Copy the code

Ok, so the next thing to do is to instantiate the ViewModel:

If the current Activity does not have a HolderFragment, it instantiates and holds a HolderFragment. And tell the HolderFragment to save the ViewModel into the HashMap.

Thus, when an Activity with a life cycle is rebuilt by rotating the screen to destroy, the ViewModel and its internal state are not reclaimed because the ViewModelStore container in the invisible HolderFragment holds the ViewModel.

This requires a condition that, when we instantiate the ViewModel, we also seem to need a reference to the Activity to ensure that we get or instantiate the internal HolderFragment and store the ViewModel.

So we designed an API that adds the required Activity dependencies when instantiating the ViewModel:

CommonViewModel viewModel = ViewModelProviders.of(activity).get(CommonViewModel.class)
Copy the code

We inject the Activity, so instantiating the HolderFragment is left to the internal code to execute:

HolderFragment holderFragmentFor(FragmentActivity activity) {
     FragmentManager fm = activity.getSupportFragmentManager();
     HolderFragment holder = findHolderFragment(fm);
     if(holder ! =null) {
          return holder;
      }
      holder = createHolderFragment(fm);
      return holder;
}
Copy the code

After that, since we passed a ViewModel Class object, we can instantiate the corresponding ViewModel by default and store it in the ViewModelStore container in the HolderFragment:

public <T extends ViewModel> T get(Class<T> modelClass) {
      // Instantiate the ViewModel by reflection and store it in the ViewModelStore
      viewModel = modelClass.getConstructor(Application.class).newInstance(mApplication);
      mViewModelStore.put(key, viewModel);
      return (T) viewModel;
 }
Copy the code

3. In the corresponding scope, ensure that only the corresponding unique instance is produced

How do you ensure that instances of the same ViewModel are generated in different fragments using the following code?

public class AFragment extends Fragment {
    private CommonViewModel model;
    public void onActivityCreated(a) { model = ViewModelProviders.of(getActivity()).get(CommonViewModel.class); }}public class BFragment extends Fragment {
    private CommonViewModel model;
    public void onActivityCreated(a) { model = ViewModelProviders.of(getActivity()).get(CommonViewModel.class); }}Copy the code

We instantiate the ViewModel get() method in the previous step and add a judgment to it:

public <T extends ViewModel> T get(Class<T> modelClass) {
      // Check whether there is an instance of ViewModel in the ViewModelStore container
      ViewModel viewModel = mViewModelStore.get(key);
     
      // If the ViewModel already exists, return it directly
      if (modelClass.isInstance(viewModel)) {
            return (T) viewModel;
      }
       
      // If it does not exist, the ViewModel is instantiated by reflection and stored in the ViewModelStore
      viewModel = modelClass.getConstructor(Application.class).newInstance(mApplication);
      mViewModelStore.put(key, viewModel);
      return (T) viewModel;
 }
Copy the code

At this point, we have successfully implemented the intended functionality — in fact, the code above is the source code for the official core functionality of the ViewModel, even the default ViewModel instantiation API has not changed at all:

CommonViewModel viewModel = ViewModelProviders.of(activity).get(CommonViewModel.class);
Copy the code

Of course, due to space constraints, I’ve simplified the source code, and I haven’t explained how to instantiate viewModels with parameters in constructors, but for those of you who already know the design ideas and principles, learning how to use these apis is almost effortlessly easy.

Summary and reflection

The ViewModel is a very well-designed component. Instead of being complex, it’s incredibly simple. You only need to know how to call the API that instantiates the ViewModel.

At the same time, the thoughts and ideas behind it are worth rethinking. For example, how to ensure that the state is managed properly? How do you sink pure business code into the ViewModel with good design? For very complex interfaces, how do you abstract the various functions into data states for decoupling and reuse? As MVVM development progresses, these issues will surface one by one, and the ViewModel component’s well-designed and small features can become a bright spot to help you conquer the world.

— — — — — — — — — — — — — — — — — — — — — — — — — – advertising line — — — — — — — — — — — — — — — — — — — — — — — — — — — — — –

series

Create the best series of Android Jetpack blogs:

  • A detailed analysis of the official Android architecture component Lifecycle
  • Android’s official architecture component ViewModel: From the past to the present
  • LiveData, the official Android architecture component: Two or three things in the Observer pattern area
  • Paging: The design aesthetics of the Paging library, an official Android architecture component
  • Official Android architecture component Paging-Ex: Adds a Header and Footer to the Paging list
  • Official Architecture component of Android Paging-Ex: reactive management of list states
  • Navigation: a handy Fragment management framework
  • DataBinding-Ex is an official Architecture component for Android

Jetpack for Android

  • Open source project: Github client implemented by MVVM+Jetpack
  • Open source project: Github client based on MVVM, MVI+Jetpack implementation
  • Summary: Using MVVM to try to develop a Github client and some thoughts on programming

About me

If you think this article is of value to you, please feel free to follow me at ❤️ or on my personal blog or Github.

If you think the writing is a little bit worse, please pay attention and push me to write a better writing — just in case I do someday.

  • My Android learning system
  • About article correction
  • About Paying for Knowledge