当前位置:网站首页>MVVM has become history, and Google has fully turned to MVI

MVVM has become history, and Google has fully turned to MVI

2022-06-23 05:07:00 Be a happy yard farmer

Preface

I wrote some introductions some time ago MVI Architecture article , But there is no best architecture for software development , Only the most appropriate architecture , At the same time, it is well known that ,Google What is recommended is MVVM framework . I believe many people will have questions , Why don't I use the official recommendation MVVM, And use what you said MVI Architecture? ?

But I check these days Android Application Architecture Guide , Discover that the best practices recommended by Google have become One way data flow + Centralized state management , Isn't that what MVI Architecture ? look Google Has started to recommend MVI Architecture , It is also necessary for us to start to understand Android The latest version of the Application Architecture Guide ~

Overall framework

Two architectural principles

Android There are two main principles for the architecture design of

Separation of concerns

The most important principle to follow is to separate concerns . A common mistake is in a Activity or Fragment Write all the code in . These interface based classes should contain only the logic that handles interface and operating system interactions . In general ,Activity or Fragment The code in should be as lean as possible , Try to migrate the business logic to other layers

Through the data-driven interface

Another important principle is that you should use a data-driven interface ( The best is the persistence model ). The data model is independent of the interface elements and other components in the application .

This means that they are not related to the life cycle of the interface and application components , But it will still be destroyed when the operating system decides to remove the application's process from memory .

Data model and interface elements , Life cycle decoupling , Therefore, it is convenient to reuse , At the same time, it is easy to test , More stable and reliable .

Recommended application architecture

Based on the common architectural principles mentioned in the previous section , Each application should have at least two layers :

  • Interface layer - Display application data on the screen .
  • The data layer - Provide the required application data .

You can add an additional one named “ Domain layer ” Architecture layer of , To simplify and reuse the interaction between the interface layer and the data layer

As shown above , The dependencies between the layers are unidirectional , Domain layer , The data layer does not depend on the interface layer

Interface layer

The function of the interface is to display application data on the screen , And respond to user clicks . Whenever the data changes , Whether it's because of user interaction ( For example, pressing a button ), Or because of external input ( For example, network response ), The interface should be updated , To reflect these changes .

however , The format of application data obtained from the data layer is usually different from UI Format of data to be displayed , So we need to transform the data layer data into the state of the page

Therefore, the interface layer is generally divided into two parts , namely UI Layer and State Holder,State Holder The role of is usually played by ViewModel To undertake

The role of the data layer is to store and manage application data , And provide access to application data , Therefore, the interface layer must perform the following steps :

  1. Get application data , And convert it to UI Can be easily presented UI State.
  2. subscribe UI State, Refresh when the page state changes UI
  3. Receive user input events , And handle it according to the corresponding events , To refresh UI State
  4. Repeat paragraph as necessary 1-3 Step .

It is mainly a one-way data flow , As shown in the figure below :

Therefore, the interface layer mainly needs to do the following work :

  1. How to define UI State.
  2. How to use one-way data flow (UDF), As providing and managing UI State The way .
  3. How to expose and update UI State
  4. How to subscribe UI State

How to define UI State

If we want to implement a news list interface , How do we define UI State Well ? We encapsulate all the states required by the interface in one data class in .

And previous MVVM One of the main differences between patterns is also here , It is usually a State Corresponding to one LiveData, and MVI Architecture emphasizes UI State Centralized management of

data class NewsUiState(
    val isSignedIn: Boolean = false,
    val isPremium: Boolean = false,
    val newsItems: List<NewsItemUiState> = listOf(),
    val userMessages: List<Message> = listOf()
)

data class NewsItemUiState(
    val title: String,
    val body: String,
    val bookmarked: Boolean = false,
    ...
)

In the example above UI State The definition is immutable . The main advantage of this is , Immutable objects ensure that the state of the application is immediately available . thus ,UI Can focus on a single role : Read UI State And update its... Accordingly UI Elements . therefore , Do not directly in UI Revision in China UI State. Violation of this principle will lead to multiple trusted sources of the same information , This leads to the problem of inconsistent data .

for example , From the above UI State Of NewsItemUiState Object bookmarked Mark in Activity Class has been updated , Then the tag will compete with the data tier , This leads to the problem of multiple data sources .

UI State Advantages and disadvantages of centralized management

stay MVVM We usually have multiple data streams , That is, a State Corresponding to one LiveData, and MVI Is a single data stream . What are the advantages and disadvantages of each ?

The main advantage of a single data stream is convenience , Reduce template code , To add a state, just give data class Add an attribute , Can effectively reduce ViewModel And View The cost of communication

meanwhile UI State Centralized management can easily achieve something like MediatorLiveData The effect of , For example, it may only be when the user is logged in and is a subscriber of a paid news service , You need to display the bookmark button . You can define UI State

data class NewsUiState(
    val isSignedIn: Boolean = false,
    val isPremium: Boolean = false,
    val newsItems: List<NewsItemUiState> = listOf()
){
 val canBookmarkNews: Boolean get() = isSignedIn && isPremium
}

As shown above , The visibility of bookmarks is a derivative of the other two properties , When the other two properties change ,canBookmarkNews It will also change automatically , When we need to implement the visible and hidden logic of Bookmarks , Just subscribe canBookmarkNews that will do , This makes it easy to achieve something like MediatorLiveData The effect of , But far more than MediatorLiveData Be simple

Of course ,UI State There are also some problems with centralized management :

  • Unrelated data types :UI Some of the required states may be completely independent of each other . In such cases , The cost of tying these different states together may outweigh their advantages , Especially when the update frequency of one state is higher than that of other States .
  • UiState diffingUiState The more fields in the object , The more likely the data flow is to be sent out because one of the fields is updated . Because the view does not diffing Mechanism to know whether the data streams sent out continuously are the same , So each issue will cause the view to update . Of course , We can LiveData or Flow Use distinctUntilChanged() And so on , To solve this problem

Use one-way data flow management UI State

Mentioned above , In order to ensure UI Status cannot be modified in ,UI State The elements in are immutable , So how to update UI State Well ?

We usually use ViewModel As UI State The container of , So update in response to user input UI State Mainly divided into the following steps :

  1. ViewModel Will store and publish UI State.UI State Is a ViewModel Converted application data .
  2. UI Layer orientation ViewModel Send user event notification .
  3. ViewModel Will handle user actions and update UI State.
  4. The updated status will be fed back to UI To render .
  5. The system will repeat the above operation for all events that cause the state change .

for instance , If the user needs to bookmark the news list , Then you need to pass the event to ViewModel, then ViewModel to update UI State( There may be data layer updates in the middle ),UI Layer subscription UI State Subscription response refresh , To complete the page refresh , As shown in the figure below :

Why use one-way data flow ?

One way data flow can implement the separation of concerns principle , It can put the state change source location 、 The conversion position and the final use position are separated .

This separation allows UI Only play the role indicated by its name : Through observation UI State Change to display page information , And pass user input to ViewModel To achieve status refresh .

let me put it another way , One way data flow helps achieve the following :

  1. Data consistency . The interface has only one trusted source .
  2. ...Testability . The source of state is independent , Therefore, it can be tested independently of the interface .
  3. Maintainability . The change of state follows a well-defined pattern , That is, state change is the result of the joint action of user events and their data pull sources .

Exposure and update UI State

Well defined UI State And determine how to manage the corresponding status , The next step is to send the provided status to the interface . We can use LiveData perhaps StateFlow take UI State Convert to a data stream and expose to UI layer

In order to ensure that you cannot UI Modification status in , We should define a variable StateFlow With an immutable StateFlow, As shown below :

class NewsViewModel(...) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    ...

}

thus ,UI The layer can subscribe to status , and ViewModel You can also modify the status , Take the case where an asynchronous operation is required , have access to viewModelScope Start the coroutines , And the status can be updated when the operation is completed .

class NewsViewModel(
    private val repository: NewsRepository,
    ...
) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    private var fetchJob: Job? = null

    fun fetchArticles(category: String) {
        fetchJob?.cancel()
        fetchJob = viewModelScope.launch {
            try {
                val newsItems = repository.newsItemsForCategory(category)
                _uiState.update {
                    it.copy(newsItems = newsItems)
                }
            } catch (ioe: IOException) {
                // Handle the error and notify the notify the UI when appropriate.
                _uiState.update {
                    val messages = getMessagesFromThrowable(ioe)
                    it.copy(userMessages = messages)
                 }
            }
        }
    }
}

In the example above ,NewsViewModel Class will try to make a network request , And then update UI State, then UI Layers can react appropriately to it

subscribe UI State

subscribe UI State It's simple , Only need UI Layer view and refresh UI that will do

class NewsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

UI State Local refresh

because MVI Under the framework of UI State Centralized management of , So updating a property will result in UI State Update , So how to implement local refresh in this case ?

We can use distinctUntilChanged Realization ,distinctUntilChanged The refresh will only be called back after the value has changed , This is equivalent to an anti shake property , So we can implement local refresh , Use as follows

class NewsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Bind the visibility of the progressBar to the state
                // of isFetchingArticles.
                viewModel.uiState
                    .map { it.isFetchingArticles }
                    .distinctUntilChanged()
                    .collect { progressBar.isVisible = it }
            }
        }
    }
}

Of course, we can also encapsulate it , to Flow perhaps LiveData Add an extension function , Make it support listening properties , Use as follows

class MainActivity : AppCompatActivity() {
 private fun initViewModel() {
        viewModel.viewStates.run {
            // monitor newsList
            observeState([email protected], MainViewState::newsList) {
                newsRvAdapter.submitList(it)
            }
            // Monitor network status 
            observeState([email protected], MainViewState::fetchStatus) {
                //..
            }
        }
    }
}

About MVI The architecture supports attribute listening , More detailed content is visible :MVI Architecture better practices : Support LiveData Property listening

Domain layer

The domain layer is an optional layer between the interface layer and the data layer .

The domain layer is responsible for encapsulating complex business logic , Or by multiple ViewModel Reusable simple business logic . This layer is optional , Because not all applications have such requirements . therefore , You should use this layer only when you need it .

The domain layer has the following advantages :

  1. Avoid code duplication .
  2. Improve readability of classes that use domain layer classes .
  3. Improve application testability .
  4. Enables you to divide responsibilities , To avoid large classes .

I feel that for the common APP, Domain layer doesn't seem necessary , about ViewModel The logic of repetition , Use util Generally speaking, it is enough

Maybe the domain layer is suitable for very large projects , You can choose according to your own needs , For more information about the domain layer, see :https://developer.android.com/jetpack/guide/domain-layer

The data layer

The data layer is mainly responsible for the logic of acquiring and processing data , The data layer consists of multiple layers Repository form , Each of them Repository Can contain zero to more Data Source. You should create a for each different type of data that the application processes Repository class . for example , You can create... For movie related data MoviesRepository class , Or create... For payment related data PaymentsRepository class . Of course, for convenience , For those with only one data source Repository, You can also write the data source code in the Repository, When there are multiple data sources in the future, split them

The data layer is the same as before MVVM The data layer under the architecture makes no difference , I won't go into that , Detailed information about the data layer can be found in :https://developer.android.com/jetpack/guide/data-layer

summary

Compared with the Old Architecture Guide , The new version mainly adds the domain layer and modifies the interface layer , The domain layer is optional , Each of you should use... According to your own project needs .

The interface layer starts from MVVM Architecture becomes MVI framework , Emphasized the importance of data One way data flow And Centralized management of status . comparison MVVM framework ,MVI The architecture has the following advantages

  1. Emphasize the one-way flow of data , It's easy to track and trace state changes , In data consistency , ...Testability , Maintainability has certain advantages
  2. Emphasize on UI State Centralized management of , Just subscribe to one ViewState You can get all the status of the page , relative MVVM Reduced a lot of template code
  3. To add a state, you only need to add an attribute , To reduce the ViewModel And View Communication cost of layer , Focus business logic on ViewModel in ,View The layer only needs to subscribe to the status and then refresh

Of course, there is no best architecture in software development , Only the most appropriate architecture , You can choose the appropriate architecture for the project according to the situation , Actually in my opinion Google Recommended in the guide MVI Instead of MVVM, Probably for the sake of unification Android And Compose The architecture of . Because in Compose There is no bidirectional data binding in , Only one-way data flow , therefore MVI Is the best fit for Compose The architecture of .

Of course, if your project doesn't use DataBinding, Maybe you can start to try MVI, Don't use DataBinding Of MVVM The schema is switched to MVI The cost is not high , Switching is also relatively simple , In the ease of use , Data consistency , ...Testability , Maintainability and other aspects have certain advantages , You can also seamlessly switch to Compose.

原网站

版权声明
本文为[Be a happy yard farmer]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/01/202201151636093723.html