• ViewModels: Persistence, onSaveInstanceState(), Restoring UI State and Loaders
  • Lyla Fujiwara
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Feximin

introduce

In my last post, I developed a simple use case with the new ViewModel class to hold basketball scores during configuration changes. The ViewModel is designed to hold and manage UI-related data in a life-cycle manner. The ViewModel allows data to survive configuration changes such as screen rotation.

Now, you might have a few questions about what the ViewModel can actually do. In this article, I will answer:

  • Does the ViewModel persist the data? In short, no, persisted as usual.
  • Is ViewModel a replacement for onSaveInstanceState? In short, no, but they’re not unrelated. Read on.
  • How can I efficiently use the ViewModel to save and restore UI state?In short, you can mix and mix ViewModels,onSaveInstanceState()And local persistence.
  • Is ViewModel a replacement for Loader? In short, yes, ViewModel can be used in conjunction with several other classes instead of a Loader.

Does the graph model persist the data?

In short, no. And persist as usual.

Viewmodels hold temporary data in the UI, but they don’t persist it. Once the associated UI controller (Fragment/Activity) is destroyed or the process stops, the ViewModel and any contained data are marked by the garbage collection mechanism.

Data Shared by multiple applications should be persisted as normal through local databases, Shared Preferences, and/or cloud storage. If you want the user to return to the exact same state after the application has been running in the background for three hours, you also need to persist the data. This is because once your activity goes into the background, your application process can be terminated if your device is running low on memory. Here is a manual table in the Activity class documentation that describes the life cycle states in which your application can be terminated:

Activity lifecycle documents

As a reminder, if an application process is terminated due to resource constraints, it is not terminated normally and there are no additional lifecycle callbacks. This means that you cannot rely on the onDestroy call. You don’t have a chance to persist data when the process terminates. So if you want to maximize data loss, you should persist it as soon as the user enters the activity. This means that data will be saved even if your app is terminated due to resource constraints or the device runs out of battery. If you allow data to be lost in the event of such a sudden device shutdown, you can save it during the ‘onStop()’ callback, which is called as soon as the activity enters the background.

Is ViewModel a replacement for onSaveInstanceState?

In short, no, but they’re not unrelated. Read on.

Understand the onSaveInstanceState () and fragments. SetRetainInstance (true) between the two different helps to understand the differences, to understand the nuances of.

OnSaveInstanceState (): This callback is used to save a small amount of UI-related data in two cases:

  • Application processes were terminated in the background due to memory limitations.
  • Configuration changes.

OnSaveInstanceState () is called by the system when the activity is stopped but not finished, not when the user explicitly closes the activity or otherwise causes Finish () to be called.

Note that a lot of UI data is automatically saved and restored:

“The default implementation of this method saves temporary information about the activity’s view-level state, such as the text in the EditText control or the scrollbar position in the ListView control.” — Saving and Restoring Instance State Documentation.

These are also good examples of the types of data stored in the onSaveInstanceState() method. OnSaveInstanceState () is not designed to store large data like bitmaps. The onSaveInstanceState() method is designed to store small UI-specific data that is not complex to serialize or deserialize. Serialization can consume a lot of memory if the object being serialized is complex. Since this happens during a configuration change in the main thread, it needs to be handled quickly so as not to lose frames and cause visual stuttering.

Fragments. SetRetainInstance (true) : Handling for the Configuration Changes documentation describes the Configuration Changes during a process that is used to store data using a reserved fragments. This doesn’t sound as useful as onSaveInstanceState() covers both configuration changes and process shutdowns. The advantage of creating a preserving fragment is that it can hold large data sets like images or complex objects like network connections.

The ViewModel can only be retained in the event of destruction related to configuration changes, not in the terminated process. This makes the ViewModel to be used with setRetainInstance(true) (in fact, The ViewModel uses a fragment behind the scenes and sets the argument in the setRetainInstance method to true) as an alternative to the fragment used.

Other benefits of the ViewModel

ViewModel and onSaveInstanceState() are very different in how UI data is stored. OnSaveInstanceState () is a callback to the lifecycle, and ViewModel fundamentally changes the way UI data is managed in your application. Here are some of the benefits of using ViewModel beyond onSaveInstanceState() :

  • Viewmodels encourage good architectural design. The data is separated from the UI code, which makes the code more modular and simplifies testing.
  • onSaveInstanceState()Designed to store small amounts of temporary data rather than complex lists of object or media data.A ViewModel can be used as a proxy for loading complex data, or as a temporary storage once the load is complete.
  • onSaveInstanceState()Called during configuration changes and when the activity enters the background; In both cases, if your data is stored in the ViewModel, you don’t actually need to reload or process it.

How can I efficiently use the ViewModel to save and restore UI state?

In short, you can mix ViewModel, onSaveInstanceState(), and local persistence. Read on to see how to use it.

It is important that your activity maintains the state expected by the user, even if the screen rotates, the system shuts down, or the user restarts. As I said earlier, it’s also important not to block the onSaveInstanceState method with complex objects. You also don’t want to reload data from the database when you don’t need it. Let’s look at an example of an activity where you can search for your music library:

Examples of Activity status when not searched and after the search.

There are two common ways for users to leave an activity, and two different results that users expect:

  • The first is whether the user has shut down the activity completely. Users can shut down an activity completely if they slide it out of the Recents Screen or navigate out or out of an activity. In both cases, the user is assumed to permanently exit the activity, and if they re-enter the activity, they expect a clean page. For our music app, if the user completely closes the Music search activity and re-opens it, the music search box and search results will be cleared.
  • On the other hand, if the user rotates the phone or enters the background of the activity and comes back, the user expects the search results and the music they want to search to still exist, just as they did before entering the background. There are several ways users can get activities into the background. They can press the home button or navigate through other parts of the app. Or when your phone comes in or you get a notification while checking your search results. What users ultimately want, however, is for the page to be exactly as it was when they left the activity.

To implement the behavior in both cases, you can use local persistence, ViewModel, and onSaveInstanceState() together. Each stores different data for use in the activity:

  • Local persistence is used to store all the data you don’t want to lose when you start or close an activity.

    Example: A collection of all music objects containing audio files and metadata.

  • The ViewModel is used to store all the data needed to display the relevant UI controller.

    Example: Recent search results.

  • OnSaveInstanceState is used to store the small amount of data needed to easily reload the activity state after the UI controller has been terminated and rebuilt by the system. Persist complex objects in local storage and store unique ids for these objects in onSaveInstanceState() instead of storing complex objects directly. Example: A recent search query.

In the music search example, different events should be handled like this:

When a user adds a piece of music – the ViewModel quickly brokers local persistence of this data. If newly added music needs to be displayed on the UI, you should also update the data in the ViewModel to reflect the addition of the music. Remember not to insert data into the database in the main thread.

When a user searches for music — any complex music data loaded from the database for the UI controller should be stored in the ViewModel immediately. You should also store the search query itself in the ViewModel.

OnSaveInstanceState () is called once the activity is in the background and terminated by the system. You should store the search query in the onSaveInstanceState() bundle. These small amounts of data are easy to keep. This is also all the data needed to restore the activity to its current state.

When an activity is created – three different ways can occur:

  • The Activity is created for the first time: In this case,onSaveInstanceState()There’s no data in the bundle in the method, and the ViewModel is empty. When you create a ViewModel, you pass in an empty query, and the ViewModel realizes that there is no data to load yet. The activity starts in a new state.
  • An Activity is created after it is terminated by the system: the activity ofonSaveInstanceState()The query is stored in the bundle. The Activity passes this query into the ViewModel. When the ViewModel finds that there are no search results in the cache, it loads the search results using the given search query agent.
  • The Activity is created after a configuration changeThe: Activity saves the query in theonSaveInstanceState()And the ViewModel also caches the search results. When you go throughonSaveInstanceState()The bundle passes the query to the ViewModel, which determines that it has loaded the necessary data thusDon’tThe database needs to be queried again.

This is a good way to save and restore the activity state. Depending on the implementation of your activity, you may not need onSaveInstanceState() at all. For example, some activities do not open in a new state after being closed by the user. Typically, when I close and then reopen Chrome on my Android phone, I’m back to the page I was browsing before I closed Chrome. If your activity behaves this way, you can persist all data locally without using onSaveInstanceState(). In the same case of music search, that means persisting the most recent query in, for example, Shared Preferences.

In addition, when you start an activity with an intent, the bundle parameter is passed in when configuration changes are made and when the system resumes the activity. If the search query is extras in the intent, then you can use the bundle in the extras instead of the bundle in onSaveInstanceState().

However, in both scenarios, you still need a ViewModel to avoid the resource waste of reloading data from the database due to configuration changes.

Is ViewModel a replacement for Loader?

In short, yes, ViewModel can be used in conjunction with several other classes instead of a Loader.

Loader is used by the UI controller to load data. In addition, the Loader can be retained during configuration changes, such as when you rotate the phone screen during loading. This sounds familiar!

A common use of loaders, especially CursorLoaders, is to observe the contents of a database and keep the data in sync with the UI. With the CursorLoader, if one of the values in the database changes, the Loader automatically triggers a data reload and updates the UI.

The ViewModel can be used in conjunction with other architectural components, LiveData and Room, to replace the Loader. The ViewModel ensures that data is not lost after configuration changes. LiveData ensures that the UI and data are updated synchronously. Room ensures that LiveData is notified when your database is updated.

Since the Loader is implemented as a callback in the UI controller, the ViewModel has the added advantage of separating the UI controller from the data load. This reduces strong references between classes.

Some ways to load data using ViewModels, LiveData:

  • In this article, Ian Lake Outlines how to use ViewModel and LiveData instead of AsyncTaskLoader.
  • As the code gets more complex, you can consider doing the actual data loading in a separate class. The purpose of a ViewModel class is to hold data for the UI controller. Complex methods of loading, persisting, and managing data are beyond the traditional capabilities of the ViewModel. Guide to Android App Architecture recommends creating a repository class.

“The warehouse module handles the data operations. They provide a clean set of apis for the rest of the application. They know where to get the data and which API to call when the data is updated. You can think of them as coordinators between different data sources (persistence model, Web Service, cache, etc.).” — Guide to App Architecture

Conclusion and further study

In this article, I answered several questions about what a ViewModel class is and is not. The key points are:

  • The ViewModel is not a replacement for persistence – persist data as it changes as usual.
  • The ViewModel is notonSaveInstanceState()Because they save data during destruction related to configuration changes and not when the system kills application processes.
  • onSaveInstanceState()Does not apply to data that takes a long time to serialize/deserialize.
  • To save and restore UI state efficiently, you can use a mixture of persistence,onSaveInstanceState()And the ViewModel. Complex data is stored locally and then usedonSaveInstanceState()To hold the unique ID of that complex data. The ViewModel stores the data in memory after it is loaded.
  • In this scenario, the ViewModel retains data while the activity is spinning or entering the background, and is simply usedonSaveInstanceState()It’s not that easy.
  • Used in conjunction with ViewModel and LiveData instead of Loader. You can use Room instead of CursorLoader.
  • Create warehouse classes to support a scalable architecture for loading, caching, and synchronizing data.

Want more viewModel-related dry stuff? Please look at:

  • Instructions for adding the gradle dependencies
  • ViewModel documentation
  • Guided ViewModel practice with the Lifecycles Codelab
  • Helpful samples that include ViewModel [Architecture Components] [Architecture Blueprint using Lifecycle Components]
  • The Guide to App Architecture

Architectural components are created based on your feedback. If you have questions about the ViewModel or any architectural component, please check out our feedback page. Any questions about this series, please leave a comment.

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.