• ViewModels: A Simple Example
  • Lyla Fujiwara
  • The Nuggets translation Project
  • In this paper, a permanent link: https://github.com/xitu/gold-miner/blob/master/TODO/viewmodels-a-simple-example.md
  • Translator: huanglizhuo
  • Proofreader: Chuanxing Miguoer

Introduction to the

Two years ago, I was doing an introductory course on Android, teaching zero-based students to develop Android apps. Part of it is teaching students to build a simple App called Cour-Counter.

Cour-counter is an App that only has a few buttons to change basketball scores. The final App has a bug that causes the current saved score to magically return to zero if you rotate the phone.

Configuration changes

This performance allows us to do some special things, such as switch to a more laterally specific layout as the device rotates. But for new (and sometimes older) engineers, it can be a headache.

At Google I/O 2017, the Android Framework team released a set of Architecture Components tools, one of which deals with device rotation.

The ViewModel class is designed to store and manage UI-related data in a life-cycle manner. This allows data not to be lost in the event of configuration changes such as screen rotation.

This article is the first in a series exploring the ViewModel in detail. In this article, I will:

  • Explain the basic requirements that the ViewModel satisfies
  • Solve the rotation problem by changing the cour-counter code to use the ViewModel
  • Take a close look at the relationship between the ViewModel and UI components

Potential problems

The potential challenge is that there are many states in the Android Activity lifecycle, and a single Activity can loop into these different states multiple times due to configuration changes.

You may use onRetainNonConfigurationInstance method before saving and restoring the data during the configuration changes. However, if your data doesn’t need to know or manage the Activity’s life cycle state, isn’t this too verbose to write? What if you have a variable like scoreTeamA in your Activity that is tied to the Activity life cycle but stored outside of the Activity? That’s the purpose of the ViewModel class.

In the diagram below, you can see the life cycle of an Activity that goes through a spin before finishing. The ViewModel life cycle is displayed next to the associated Activity life cycle. Note that ViewModels can easily be used with Fragments and Activities, which we call UI controllers. This example focuses on Activities.

A simple example

There are three steps to setting up and using the ViewModel:

  1. Separate your data from the UI controller by creating an extended ViewModel class
  2. Establish communication between your ViewModel and your UI controller
  3. Use your ViewModel in the UI controller

Step 1: Create the ViewModel class **

In general, you need to create a ViewModel class for each interface. This ViewModel class will hold all the data associated with the screen, providing getters and setters. This separates the data from the UI display logic, which is in Activities or Fragments, and the data is stored in the ViewModel. Ok, now create the ViewModel class for one of the screens in cour-counter:

public class ScoreViewModel extends ViewModel {
// Tracks the score for Team A
public int scoreTeamA = 0;

// Tracks the score for Team B
public int scoreTeamB = 0;
}
Copy the code

For brevity, I’ve used public members stored in scoreViewModel.java, or getters and setters to better encapsulate the data.

Step 2: Associate the UI controller and ViewModel

Your UI controller (Activity or Fragment) needs to access your ViewModel. This way, the UI controller can display and update the data as UI interactions occur, such as pressing a button to increase the score in cour-counter.

ViewModels should not hold references to Activities, Fragments, or Context.

Also, ViewModels should not contain elements that contain references to UI controllers such as Views, because this creates indirect references to the Context.

The reason we don’t do this is because viewModels have a longer lifecycle than UI controllers. For example, if you rotate an Activity three times, you’ll get three different instances of the Activity, but the ViewModel only has one.

With this in mind, let’s create the UI controller/ViewMode L association. Create the ViewModel as a member variable in the UI controller. Then call onCreate like this:

ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)
Copy the code

In the court-counter example, it would look like this:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewModel = ViewModelProviders.of(this).get(ScoreViewModel.class);
// Other setup code below...
}
Copy the code

Note: There is an exception to the “No Contexts in ViewModels” rule. Sometimes you may need an Application context(as opposed to an Activity context) to invoke system services. In this case, it’s okay to hold the Application Context in the ViewModel, because the Application context exists throughout the life of the App, unlike the Activity context, The Activity context exists only for the Activity lifecycle. In fact, if you need an Application Context, it’s best to inherit AndroidViewModel, which is a ViewModel that holds the Application reference.

Step 3: Use the ViewModel in the UI controller

To access or change UI data, you can use the data in the ViewModel. Here is an example of A new onCreate method and A method to increase team A scores:

// The finished onCreate method
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   mViewModel = ViewModelProviders.of(this).get(ScoreViewModel.class);
   displayForTeamA(mViewModel.scoreTeamA);
   displayForTeamB(mViewModel.scoreTeamB);
Copy the code

}

// An example of both reading and writing to the ViewModel
public void addOneForTeamA(View v) {
    mViewModel.scoreTeamA = mViewModel.scoreTeamA + 1;
    displayForTeamA(mViewModel.scoreTeamA);
}
Copy the code

Tips: ViewModel also works well with another architectural component, LiveData, which I won’t delve into in this series. The added benefit of using LiveData is that it is observable: it can trigger UI updates when data changes. You can learn more about LiveData here.

Further examinationViewModelsProviders.of

The first call to the ViewModelProviders.of method is in the MainActivity, where a new ViewModel instance is created. The onCreate method is called again every time it is called. It returns the ViewModel created earlier in the court-Counter MainActivity. That’s how it holds data.

This can only be done if the UI Controller is supplied with the correct UI controller as a parameter. Remember not to store the UI controller in the ViewModel; the ViewModel tracks the association between the UI controller instance and the ViewModel in the background.

ViewModelProviders._of_(**<THIS ARGUMENT>**).get(ScoreViewModel.**class**);
Copy the code

This allows you to have an application that opens different instances of the same Activity or Fragment but displays different ViewModel information. Let’s imagine if we extended the cour-counter program to support different basketball game scores. Matches are displayed in the list, and clicking on a match in the list will open a screen similar to the one for MainActivity, which I’ll call GameScoreActivity.

For each different match screen that you open, after associating ViewModel with GameScoreActivity in onCreate, it will create a different ViewModel instance. Rotate one of the screens to remain connected to the same ViewModel.

ViewModelProviders.of(<Your UI controller>).get(<Your ViewModel>.class)

Final thought: ViewModels do a great job of separating your UI controller code from your UI data. That said, it doesn’t do the job of persisting data and saving App state. In the next article, I’ll explore the subtle interaction between the Activity lifecycle and ViewModels, and compare ViewModels to onSaveInstanceState.

Conclusion and further study

In this article, I explore the basics of the new ViewModel class. The key points are:

  • The ViewModel class is designed to hold and manage UI-related data over a continuous lifecycle. This allows data to be saved in the event of configuration changes such as screen rotation.
  • ViewModels separate the UI implementation from the App data.
  • In general, if you have transient data in a screen application, you should create a separate ViewModel for that screen’s data.
  • The ViewModel life cycle starts when the associated UI controller is first created until it is completely destroyed.
  • Do not store the UI controller or Context directly or indirectly in the ViewModel. This includes storing the View in the ViewModel. Direct or indirect references to the UI controller defeat the purpose of separating the UI from the data and can lead to memory leaks.
  • ViewModel objects typically store LiveData objects, which you can learn more about here.
  • The viewModelProviders. of method associates the ViewModel with the UI controller passed in as a parameter.

Want to learn more about the benefits of viewModelization? You can read the following article further:

  • Instructions for adding the gradle dependencies
  • ViewModel documentation
  • Guided ViewModel Practice with the Lifecycles Codelab Architecture components were created in response to your feedback. If you have any questions or comments about the ViewModel or any architectural component, please check out our feedback page. Questions or suggestions about the series? Comment!

Thanks to Mark Lu, Florina Muntenescu, and Daniel Galpin.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, React, front-end, back-end, product, design and other fields. If you want to see more high-quality translation, please continue to pay attention to the Project, official Weibo, Zhihu column.