preface
In the last article, I shared with you my understanding of Android architecture and the evolution of architecture from an ideological level. After reading the book, many friends clapped their hands and said they still wanted to hear my views on Jetpack architecture. In the spirit of helping people to the end, I will try to explain the meaning of Jetpack architecture and the problems it solves in essence.
I also have a full open source project based on Jetpack MVVM that has been reconfigured along the same principles as the previous article and is currently hosted on Github, hoping to help you out as well. Making the address
Knowledge base: Need to be rightLifcycle, LiveData, ViewModel, DataBinding
Have a basic understanding
directory
- With Lifecycle, there is no need to worry about Lifecycle synchronization
- 1.1 Why lifecycle binding?
- 1.2 What issues does Lifecycle solve?
- 2. LiveData does not only apply observer mode
- 2.1 What are the advantages of the observer model?
- 2.2 What other extensions have been made to LiveData based on observer mode?
- 2.3 LiveData + Lifecycle implementation 1 + 1 > 2
- 3. ViewModel and LiveData are a match made in heaven
- 3.1 How to gracefully implement communication between Fragments?
- 3.2 What are the benefits of having a ViewModel as a VM/Presenter?
- 4. Disarm your DataBinding misconceptions
- 4.1 What are the benefits of using DataBinding?
- 4.2 Why do many people say DataBinding is difficult to debug?
- 5. What does Jetpack have to do with MVVM?
- 5.1 What is MVVM
- 5.2 Jetpack just makes MVVM simpler and more secure
With Lifecycle, there is no need to worry about Lifecycle synchronization
1.1 Why lifecycle binding?
The most important concept for activities/fragments is life cycle management, where we developers need to do different things in different life cycle callbacks. For example, onCreate does some initialization, onResume does some recovery, etc., etc., so all of these operations are simple and you can write them directly.
However, there are some components that rely heavily on the Activity/Fragment life cycle and can cause security issues if they are not properly written, such as the following example:
There is a video playing interface, we need to pause the playing when jumping to another interface, return to play again, and reset the playing after exiting. General idea:
#class PlayerActivity onCreate(){ player.init() } onResume(){ player.resume() } onPause(){ player.pause() } onDestroy(){ player.release() }Copy the code
If you read my last post, you can easily see that this violates the inversion of control. People are not machines and can easily make mistakes or forget to write, especially player.release(), which can cause memory leaks. The first step:
interface ObserverLifecycle{
onCreate()
...
onDestroy()
}
Copy the code
Start by defining an observer interface that contains the Activity/Fragment main lifecycle methods
The second step:
class BaseActivity{ val observers = mutableList<ObserverLifecycle>() onCreate(){ observers.forEach{ observer.onCreate() }}... onDestroy(){ observers.forEach{ observer.onDestroy() } } }Copy the code
Observe the lifecycle in BaseActivity and notify observers about it, one at a time
Step 3:
class VideoPlayer : ObserverLifecycle{
onCreate(){
init()
}
...
onDestroy(){
release()
}
}
class PlayerActivity : BaseActivity{
observers.add(videoPlayer)
}
Copy the code
The player implements the ObserverLifecycle interface and invokes the corresponding method at each opportunity. PlayerActivity can implement lifecycle synchronization simply by registering videoPlayer with Observers.
In fact, not only videoPlayer, but also any component that is dependent on the Activity lifecycle, can implement automatic lifecycle management, and avoid misoperations, simply by implementing ObserverLifecycle interfaces that are finally registered with observers
1.2 What issues does Lifecycle solve?
Since synchronization of the lifecycle is so important, Google is certainly not blind to it, and while a custom ObserverLifecycle can solve this problem, not everyone can think of one. Therefore, Google has developed a standardized Lifecycle management tool, enabling developers to naturally think of Lifecycle when they encounter Lifecycle problems, just as they think of Activities when they want to create a new interface on an Android phone.
At the same time, Lifecycle is built into both activities and fragments. It is very simple to use Lifecycle.
class VideoPlayer : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate(){
init()
}
..
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy(){
release()
}
}
class PlayerActivity : BaseActivity{
lifecycle.addObserver(videoPlayer)
}
Copy the code
This is a two-step process without us doing our own lifecycle distribution to the Observer (videoPlayer).
2. LiveData does not only apply observer mode
2.1 What are the advantages of the observer model?
Observer is a common and practical behavior pattern, which has the characteristics of strong expansibility and low coupling.
Lifecycle synchronous design in 1.1 is a standard observer pattern, ObserverLifecycle can be used as an observer, The PlayerActivity, as the observed, actively notifies the observer of changes to the PlayerActivity’s life cycle.
At the same time, observers can extend as they please without changing the code structure. For example, PlayerActivity is an MVP framework where Presenter implements ObserverLifecycle as an observer and then registers with the Observed. In this way, the Presenter can also monitor the Activity lifecycle, and the code structure does not change, conforming to the open closed principle (closed for extension development modifications).
2.2 What other extensions have been made to LiveData based on observer mode?
LiveData conforms to the standard observer mode, so it has the characteristics of strong scalability and low coupling. Meanwhile, it is also a container for storing data. When the container data changes, the observer will be triggered, that is, data-driven.
Data-driven is a very important concept in the field of front-end development. Before we say data-driven, let’s first think about the question: Why change data? The obvious answer is simply to make the data user aware, and LiveData can elegantly implement this process by combining change and notification into one step to save trouble and improve security.
According to the characteristics of LiveData, it is very suitable for data-driven UI. Here is an example to briefly describe it:
Val LiveData = MutableLiveData<String>() LiveData? .observe(this, Observer {value-> textView.text = value}) // This step will change the liveData value and trigger textView to re-render liveData.value = "android".Copy the code
It may seem mundane or even natural, but it does solve our front-end development pain point. Previously, data and UI had to be modified individually by us developers, which was hard to keep in mind when faced with dozens of views. After liveData is introduced, changing the data automatically triggers UI rendering, combining the two steps into one step, greatly reducing the probability of errors. I described data-driven UI in detail in the previous article, if you are interested, you can refer back to it.
2.3 LiveData + Lifecycle implementation 1 + 1 > 2
Lifecycle will allow LiveData to receive notifications only when they are visible. If onStop() is executed within an Activity, LiveData will not receive notifications. For example: ActivityA and ActivityB share the same LiveData with the pseudocode below
class ActivityA{ liveData? .observe(this, Observer { value-> textView.text = value }) } class ActivityB{ liveData? .observe(this, Observer { value-> textView.text = value }) }Copy the code
When ActivityA starts ActivityB and changes the liveData value multiple times, you don’t want the Observer to be notified multiple times to redraw the textView.
This will be resolved with the introduction of Lifecycle and liveData binding with Lifecycle(this in the example) will only take the latest value of liveData when returned to ActivityA and will be notified to avoid performance issues caused by unnecessary operations
3. ViewModel and LiveData are a match made in heaven
3.1 Jetpack ViewModel is not equivalent to MVVM ViewModel
The Jetpack ViewModel is often compared to the MVVM ViewModel, but the two are not on the same level at all. The MVVM ViewModel is a role in the MVVM architecture. Jetpack ViewModel is an actual framework for state hosting, with corresponding scope to follow the Activity/Fragment life cycle, but this feature works just as well as the MVVM ViewModel, separating the data layer from the view layer and doing data hosting.
So the conclusion is that Jetpack ViewModel can act as an MVVM ViewModel but they are not equivalent
3.2 How to gracefully implement communication between Fragments?
ViewModel is officially defined as a scoped state hosting framework, which can be used to co-exist with activities/fragments by specifying scope. In order to take its state hosting to the extreme, Google even opened a separate backdoor for ViewModel. The Activity does not destroy the corresponding ViewModel when switching between vertical and horizontal screens, so that the horizontal and vertical screens can share the same ViewModel to ensure data consistency.
Since it is a state-hosted framework, the first task of ViewModel is to ensure that the latest state is always distributed to the view layer, which makes me think of LiveData, data hosting and distribution to LiveData, and ViewModel focuses on hosting LiveData to ensure that it is not lost. It’s a match made in heaven.
With ViewModel plus LiveData, fragments can communicate more elegantly. For example, in the music player of my open source project (which belongs to the architecture of single Activity and multiple fragments), both the playing page and the home page contain basic music information, as shown in the picture below:
The most elegant way to synchronize playback information between two fragments in real time is to host the playback state inActivity
Under the scopeViewModel
theLiveData
, and then do status monitoring respectively, so that only if one party changes, the other party can be notified immediately, simple and safe, the specific details can be checked in my open source project.
3.3 What are the benefits of having a ViewModel as a VM/Presenter?
ViewModel can completely avoid this problem. The internal viewModelScope is an extension function of the coroutine. The viewModelScope Lifecycle follows the ViewModel’s corresponding Lifecycle (Activity/Fragment). When the page is destroyed, the viewModelScope coroutine scope is terminated, so time-consuming operations are placed directly in the viewModelScope immediately
In addition, the onClear method of ViewModel will be called when the interface is destroyed. You can do some operations to release resources in this method to further reduce the risk of memory leakage
4. Disarm your DataBinding misconceptions
4.1 What is the use of DataBinding?
The biggest advantage and only function of DataBinding is the data UI two-way binding, UI and data modification on either side of the other side of the automatic synchronization, such benefits are actually similar to LiveData, data and UI synchronization operations, to ensure data and UI consistency. The fact that LiveData, DataBinding, and DiffUtil are all designed to address data and UI consistency is a sign of how serious Google is about this, so it’s important to keep up with the authorities
Tips:
ObservableField in DataBinding is basically the same as LiveData, but ObservableField has a de-duplication effect.
4.2 Why do many people say DataBinding is difficult to debug?
I often hear people say that DataBinding doesn’t work because it is difficult to debug business logic in XML. Not that I agree with the idea of writing business logic in XML, but I think you have to solve problems, and if there are obstacles in the way, you have to remove them rather than run away from them.
Such as {vm. IsShow? View.visible: view.gone} where is the business logic not written in XML? I described this very clearly in the Data Mapper section of the last article. Taking a model of the back-end Data transformation cost (this process writes all the data-related logic), the local model corresponds to the design diagram one by one, which not only insulates the view from the back segment, but also solves the problem of writing business logic in XML.
5. What does Jetpack have to do with MVVM?
5.1 What is MVVM
MVVM is actually an architectural pattern focused on interface development in the front-end domain, which is divided into View, ViewModel and Repository modules (strictly divided according to a single design principle).
View(View layer):
We specialize in view rendering and UI logic processingThe Repository (remote) :
Represents a remote Repository, fetching the required data from RepositoryViewModel:
Data retrieved by Repository needs to be temporarily stored in the ViewModel and mapped to the view layer
Layering is important, but at the heart of MVVM is data-driven UI through ViewModel and bi-directional binding to address data /UI consistency issues. That’s all MVVM is, don’t make it too complicated
How to choose between bidirectional binding and one-way driver?
When facing a View such as TextView, one-way drive is fully enough. After all, in our cognition, there is no need to change the corresponding data through the text displayed by TextView. At this time, one-way drive can ensure the consistency of data and UI. Bidirectional binding is usually used in interactive views, such as EditText, where the content is changed by user input. In this case, bidirectional binding is required to ensure the consistency of data and UI. Whether it’s bidirectional binding or unidirectional driving, as long as the data and UI are consistent, it’s MVVM compliant
In fact, as I said briefly in my last article, good architecture should not be limited to one pattern (MVC/MVP/MVVM), but should be continuously improved based on the actual situation of your project. If you often develop the same interface with your colleagues, try to encapsulate every piece of business logic in a use case. This will most likely solve Git conflicts. Blah, blah, blah, anything that actually improves development efficiency and project stability is a good architecture.
5.2 Jetpack just makes MVVM simpler and more secure
Jetpack is Android’s official framework for standardizing development, Lifecycle will allow developers not to worry too much about Lifecycle issues. DataBinding will allow data UI to be bidirectbound. LiveData will remove the dependency between ViewModel and Activity….
After all, Jetpack is a development framework that makes MVVM simpler and safer.
Tips: The introduction of Jetpack in my company's projects has seen a noticeable improvement in project stability.
From what has been discussed above
- Lifecycle addresses the Lifecycle synchronization issue
- LiveData implements true state-driven behavior
- Viewmodels can make Fragment communication more elegant
- DataBinding makes bidirectional binding possible
- Jetpack just makes MVVM simpler and more secure