[Medium Wan Wen] ViewModel and LiveData: Mode + Inverse Mode

Original author: Jose Alcérreca

Original address: ViewModels and LiveData: Patterns + AntiPatterns

Translator: Bingxin said

Typical interaction of entities in an app built with Architecture Components

View and ViewModel

Assign responsibilities

Ideally, the ViewModel should know nothing about the Android world. This improves testability, memory leak safety, and facilitates modularization.
The usual practice is to ensure that your ViewModel does not import any android.*, android.arch.* (Translator’s Note: Now you should add another Except for >androidx.lifecycle).
This is the same for Presenter (MVP).

? Do not let ViewModel and Presenter touch the classes in the Android framework

Conditional statements, loops and general logic should be placed in the ViewModel or other layers of the application To execute, not in Activity and Fragment.
View usually does not perform unit testing unless you use Robolectric, so the less code the better.
View only needs to know how to display data and send user events to ViewModel/Presenter. This is called Passive View mode.

? Let the logic in Activity/Fragment be as concise as possible

View reference in ViewModel

h3>

ViewModel and Activity/Fragment
have different scopes. When the Viewmodel enters the alive state and is running, the activity may be in any state of the life cycle state.
Activitie and Fragment can be destroyed and recreated without the ViewModel perception.

ViewModels persist configuration changes

Pass View( Activity/Fragment) is a big adventure. Suppose the ViewModel requests the network and returns the data later.
If the reference to the View has been destroyed at this time, or has become an invisible Activity. This will cause memory leaks and even crashes.

? Avoid holding references to View in ViewModel

The recommended way to communicate between ViewModel and View is observer mode, using LiveData or other classes Observable objects in the library.

Observer Mode

Share pictures

A very convenient way to design the presentation layer in Android is to let the View observe and subscribe to the ViewModel (changes in).
Since the ViewModel doesn’t know anything about Android, it doesn’t know how frequently Android kills the View.
This has the following benefits:

  1. The ViewModel remains unchanged when the configuration changes, so there is no need to re-request resources (database or network) when the device rotates.
  2. When the time-consuming task is finished, the observable data in the ViewModel is updated. It does not matter whether this data is observed or not. Trying to update a View that does not exist will not cause a null pointer exception.
  3. ViewModel does not hold a reference to View, which reduces the risk of memory leaks.
private void subscribeToModel() {// Observe product data viewModel.getObservableProduct().observe(this, new Observer() {@Override public void onChanged (@Nullable Product product) {mTitle.setText(product.title);} });}

? Let the UI observe the changes in the data instead of pushing the data to the UI

Fat ViewModel

No matter what makes you choose layering, this is always a good idea. If your ViewModel has a lot of code and assumes too much responsibility, then:

  • Remove part of the logic to the same scope as the ViewModel. This part will communicate with other parts of the application and update the LiveData held by the ViewModel.
  • Using Clean Architecture, add a domain layer. This is a testable and easy to maintain architecture. There are examples of Clean Architecture in Architecture Blueprints.

? Distribution responsibility, add domain layer if necessary

use data warehouse

As mentioned in the Application Architecture Guide, most apps have multiple data sources:

  1. Remote: network or cloud
  2. Local: database or file
  3. Memory cache

It is a good idea to have a data layer in your application, which is completely isolated from your view layer. The algorithm to keep the cache and database synchronized with the network is not simple. It is recommended to use a separate Repository class as a single entry point to deal with this complexity.

If you have multiple different data models, consider using multiple Repository repositories.

? Add a data warehouse as a single entry point for your data.

Processing data status

Consider the following scenario: You are observing a LiveData exposed by the ViewModel, which contains the list that needs to be displayed item. So how does View distinguish between loaded data, network errors and empty collections?

  • You can expose a LiveData through ViewModel, MyDataState can contain the data being loaded, it has been loaded, Information such as an error has occurred.

  • You can wrap the data in a class with status and other metadata (such as error messages). Look at the Resource class in the example.

? Use wrapper class or another LiveData to expose data state information

Save activity state

When the activity is destroyed or the process is killed making the activity invisible, the information needed to recreate the screen is called the activity state. Screen rotation is the most obvious example. If the state is stored in the ViewModel, it is safe.

However, you may need to restore the state when the ViewModel does not exist, for example when the operating system kills your process due to resource constraints.

In order to effectively save and restore the UI state, use the combination of onSaveInstanceState() and ViewModel.

For details, see: ViewModels: Persistence, onSaveInstanceState(), Restoring UI
State and Loaders.

Event

Event refers to an event that occurs only once. ViewModel exposes data, but what about Event? For example, navigation events or displaying Snackbar messages are actions that should only be executed once.

LiveData saves and restores data, which is not completely consistent with the concept of Event. Take a look at a ViewModel with the following fields:

LiveData snackbarMessage = new MutableLiveData<>();

Activity starts to observe it, when the ViewModel ends a You need to update its value during operation:

snackbarMessage.setValue("Item saved!");

Activity receives the value and displays SnackBar. Obviously it should be like this.

However, if the user rotates the phone, a new Activity is created and starts to observe. When the observation of LiveData starts, the new Activity will immediately receive the old value, causing the message to be displayed again.

Rather than using libraries or extensions of architectural components to solve this problem, it is better to treat it as a design problem. We recommend that you treat the event as part of the state.

Design events as part of the state. For more details, please read LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)

ViewModel’s leakage

Benefits Conveniently connect the UI layer and other layers of the application, and responsive programming works very efficiently in Android. LiveData is the key component of this pattern. Both your Activity and Fragment will observe LiveData instances.

How LiveData communicates with other components is up to you, so be aware of memory leaks and boundary conditions. As shown in the figure below, the presentation layer uses observer mode, and the data layer uses callbacks.

Observer pattern in the UI and callbacks in the data layer

< p>When the user exits the application, the View is no longer visible, so the ViewModel does not need to be observed. If the data warehouse Repository is in singleton mode and has the same scope as the application, then the data warehouse Repository will not be destroyed until the application process is killed. This only happens when the system resources are insufficient or the user manually kills the application. If the data warehouse Repository holds a reference to the ViewModel’s callback, then the ViewModel will have a memory leak.

The activity is nished but the ViewModel is still around

If the ViewModel is very lightweight, or if the operation is guaranteed to end soon, this kind of leakage is not a big problem. However, this is not always the case. Ideally, as long as it is not observed by the View, the ViewModel should be released.

Share picture

You can choose the following methods To achieve the goal:

  • Through ViewModel.onCLeared() notify the data warehouse to release the callback of the ViewModel
  • Use in the data warehouse Repository Weak references, or Event Bu (both are easily misused and even considered harmful).
  • By using LiveData in View and ViewModel, process communication between data warehouse and ViewModel

? Consider boundary conditions, memory leaks and consumption How time tasks affect the instances in the architecture.

? Do not save state or data-related core logic in the ViewModel. Every call in the ViewModel may be the last operation.

LiveData in the data warehouse

In order to avoid ViewModel leakage and callback hell, the data warehouse should be observed like this:

Share a picture

When the ViewModel is cleared, or the life of the View At the end of the cycle, the subscription will also be cleared:

share picture

< p>If you try this way, you will encounter a problem: If you don’t access the LifeCycleOwner object, what if you subscribe to the data warehouse through the ViewModel? Using Transformations can easily solve this problem. Transformations.switchMap allows you to create a new LiveData based on the changes of a LiveData instance. It also allows you to pass the life cycle information of the observer through the call chain:

LiveData repo = Transformations.switchMap(repoIdLiveData, repoId -> {if (repoId.isEmpty() ) {return AbsentLiveData.create();} return repository.loadRepo(repoId); });

In this example, when an update is triggered, this function is called and the result is distributed downstream. If an Activity observes repo, then the same LifecycleOwner will be applied to the call to repository.loadRepo(repoId).

Whenever you need a LifeCycle object inside the ViewModel, Transformation is a good solution.

inherited LiveData

The most common use of LiveData in ViewModel is MutableLiveData, and use it as < code>LiveData is exposed to the outside to ensure immutability to the observer.

If you need more features, inheriting LiveData will let you know the active observers. This is useful for monitoring location or sensor services.

public class MyLiveData extends LiveData {public MyLiveData(Context context) {// Initialize service} @Override protected void onActive() {// Start listening} @Override protected void onInactive() {// Stop listening }}

When not to inherit LiveData

You can also use onActive()< /code> to start the service to load data. But unless you have a good reason to explain that you don't need to wait for LiveData to be observed. These general design patterns are as follows:

  • Add the start() method to ViewModel and call it as soon as possible. [See Blueprints example]
  • Set an attribute that triggers loading [See GithubBrowerExample]

You don’t need to inherit LiveData often. Let Activity and Fragment tell the ViewModel when to start loading data.

Division Line

That's it for the translation. In fact, this article has been lying in my favorites for a long time.
Google recently rewrote the Plaid application, using a series of the latest technology stacks, AAC, MVVM, Kotlin, coroutines, etc. This is also a set of technology stacks that I like very much. The Wanandroid application was open sourced based on this before. See Zhenxiang for details! Kotlin+MVVM+LiveData+ coroutine build Wanandroid! .

At that time, based on a superficial understanding of MVVM, I wrote a set of MVVM architecture that I thought to be MVVM. After reading some articles about the architecture and the Plaid source code, I found some misunderstandings of my own MVVM. In the follow-up, the Wanandroid application will be reasonably modified, and some explanations will be made in conjunction with the knowledge points mentioned in the translation above. Welcome Star!

The article is first published on WeChat official account: Bingxinshuo, focusing on sharing original knowledge of Java and Android, and LeetCode problem solving.

For more latest original articles, scan the code and follow me!

Share a picture

WordPress database error: [Table 'yf99682.wp_s6mz6tyggq_comments' doesn't exist]
SELECT SQL_CALC_FOUND_ROWS wp_s6mz6tyggq_comments.comment_ID FROM wp_s6mz6tyggq_comments WHERE ( comment_approved = '1' ) AND comment_post_ID = 3596 ORDER BY wp_s6mz6tyggq_comments.comment_date_gmt ASC, wp_s6mz6tyggq_comments.comment_ID ASC

Leave a Comment

Your email address will not be published.