Analyse der Mvi-Architektur

  • In diesem Artikel wird einfach Kotlin + Coroutine + Flow + Channel verwendet, um einen Pseudo-Anmeldeanforderungsfall (Zweig dev_20220804_mvi) zu schreiben , um die Mvi-Architektur zu verstehen.
  • Bevor Sie Mvi verstehen, wird empfohlen, zuerst Mvvm zu verstehen. Sie können sich auf Mvc, Mvp und Mvvm beziehen

1. Code-Link

  • Wenn Sie zunächst nur das Konzept verstehen, erhalten Sie ein abstraktes Gefühl. Durch die Analyse der Logik des Codes und der dem Code entsprechenden Klasse können wir es zusammen mit dem Konzept von Mvi verstehen und es wird viel klarer.
  • Der Fall enthält insgesamt 4 Klassen: MainActivity, DemoViewModel, DemoIntent, DemoUiState. Fügen Sie sie zuerst ein.

1.1.Hauptaktivität

import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
    
    

    private lateinit var textView: TextView

    private val mDemoViewModel: DemoViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        textView = findViewById(R.id.textView)

        lifecycleScope.launch {
    
    
            //3.View层设置监听(VM层根据意图执行相应的逻辑后会主动触发该回调)
            mDemoViewModel.mUiState.collect {
    
     uiState ->
                when (uiState) {
    
    
                    is DemoUiState.loginSuccess -> {
    
    
                        textView.setText(uiState.success)
                    }

                    is DemoUiState.loginFail -> {
    
    
                        textView.setText("登录失败")
                    }

                    is DemoUiState.beforeLogin -> {
    
    
                        textView.setText(uiState.bengin)
                    }
                }
            }
        }
    }

    //1.点击按钮模拟网络请求
    fun login(view: View) {
    
    
        lifecycleScope.launch {
    
    
            //1.1.通过管道将意图传递给ViewModel层
            mDemoViewModel.mChannel.send(DemoIntent.LoginIntent)
        }
    }

    override fun onDestroy() {
    
    
        super.onDestroy()
        //Channel是一种协程资源['热'流],跨越不同的协程进行通信
        //在使用完若不去关闭,会造成不必要的浪费
        mDemoViewModel.mChannel.close()
    }
}

1.2.DemoViewModel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

/**
 * 意图类
 * 可以将意图类写在对应的ViewModel类中,不需要分开出去
 *      好处:避免粒度分的过细,从而增加项目的复杂度
 */
sealed class DemoIntent {
    
    
    //意图的个数根据实际的需求进行添加
    object LoginIntent : DemoIntent()
}

class DemoViewModel : ViewModel() {
    
    

    val mChannel = Channel<DemoIntent>()

    private val mDemoUiState = MutableStateFlow<DemoUiState>(DemoUiState.beforeLogin("准备登录"))
    //使用flow来监听
    val mUiState: StateFlow<DemoUiState> = mDemoUiState

    init {
    
    
        handleIntentInfo()
    }

    private fun handleIntentInfo() {
    
    
        viewModelScope.launch {
    
    
            mChannel.consumeEach{
    
    //consumeEach:channel(管道)读数据推荐方案[直接使用receive是很容易出问题的]
                //2.接收用户传输过来的意图
                when(it){
    
    
                    //这里为什么要经过一个意图呢?而不是有通信即可,目的是为了便于管理,视图是为了V层跟VM层更好地解耦 以及代码扩展
                    is DemoIntent.LoginIntent -> getLoginData()
                }
            }
        }
    }

    private fun getLoginData() {
    
    
        viewModelScope.launch {
    
    
            requestLogin.flowOn(Dispatchers.Default)
                .catch {
    
     exception ->
                    mDemoUiState.value = DemoUiState.loginFail(exception)
                }.collect {
    
     loginInfo ->
                    mDemoUiState.value = DemoUiState.loginSuccess(loginInfo)
                }
        }
    }

    private val requestLogin: Flow<String> = flow {
    
    
        val loginInfo = "登录成功"       //模拟请求登录接口
        emit(loginInfo)
    }
}

1.3.DemoUiState

sealed class DemoUiState {
    
    
    //为了降低复杂度,方法的参数都写定(实际项目中括号内部的参数建议封装成泛型)
    data class beforeLogin(var bengin: String):DemoUiState()
    data class loginSuccess(var success: String):DemoUiState()
    data class loginFail(var exception: Throwable):DemoUiState()
}
  • Als nächstes kombinieren wir Konzepte und Codes, um Mvi zu verstehen

Zwei. Mvi

2.1. Einfaches Verständnis von Mvi aus der Sicht von Android-Entwicklern

  • Wenn der Benutzer unsere App betreibt und einen Anmeldevorgang durchführen möchte, kapselt die Codeebene den Anmeldevorgang des Benutzers mit einer Absicht (entsprechend dem i von Mvi) und übergibt diese Absicht dann an das ViewModel (Teil von M Anschließend wird das ViewModel ausgeführt. Nach Abschluss der entsprechenden Logik wird die View-Ebene über einen Rückruf benachrichtigt (es gibt viele Möglichkeiten, dies zu implementieren, Livedata wird häufig verwendet und in diesem Fall wird Flow verwendet).
  • Im obigen Szenario beginnt die Benutzeroperation auf der V-Ebene, und das endgültige Feedback erfolgt ebenfalls auf der Ansichtsebene, und der Datenfluss ist unidirektional.

2.2. Der Unterschied zwischen Mvi und Mvvm

  • Es gibt ein weiteres i (dem Fall entsprechende DemoIntent-Klasse) und eine Datenbindung weniger. Andere sind der Meinung, dass es keinen Unterschied gibt (was die DemoUiState-Klasse betrifft, kann es auch ein entsprechendes Paket in Mvvm geben);

2.3. Vergleichen Sie die Codelogik

  • Ausgehend von der Anmeldemethode von MainActivity wird, wenn der Benutzer sich anmeldet, ein DemoIntent.LoginIntent-Parameter durch die Pipeline der Coroutine (eine Ressource der Coroutine, nicht der Schwerpunkt dieses Artikels, verstehen Sie es nur) übergeben, wo der DemoIntent Die Klasse ist eine Absichtsklasse, LoginIntent Es handelt sich um eine bestimmte Absicht, und wie viele Absichten in DemoIntent definiert werden müssen, hängt von den spezifischen Anforderungen ab. Packen Sie die Anmeldeanforderungen in eine Absicht und übergeben Sie sie an die ViewModel-Ebene. Die ViewModel-Ebene empfängt sie. Behandeln Sie entsprechend die von der View-Ebene übergebene Absicht in der handleIntentInfo-Methode der DemoViewModel-Klasse. Wir müssen nur die When-Anweisung davon analysieren Methode, um zu bestimmen, ob sie der Absicht von DemoIntent entspricht, bestimmt die Ausführungslogik. Wenn DemoIntent.LoginIntent den Login-(Pseudo-)Netzwerkanforderungsaufruf () an die Methode getLoginData ausführt, ändern Sie den Wert von mDemoUiState.value nach der Anforderung der Schnittstelle und lösen Sie dadurch aus Einstellung von mDemoViewModel.mUiState in der MainActivity-Klasse Listen, und dann führt die View-Ebene je nach unterschiedlichen Bedingungen unterschiedliche Logik aus;

3. Zusammenfassung

  • In diesem Artikel wird der Unterschied zwischen Mvi und Mvvm im spezifischen Code beschrieben. Die M-Schicht von Mvi kann der Kombination der VM-Schicht und der M-Schicht von Mvvm entsprechen und enthält zwei Teile von Absichts- und UI-Statusänderungen. Die Verwendungsebene ist einfacher, aber ich persönlich habe das Gefühl, dass die M-Schicht von Mvi etwas aufgebläht ist als die von Mvvm, und dieser aufgeblähte Punkt kann aus den Vorteilen von Mvvm gelernt werden, um flexible Änderungen an Mvi vorzunehmen.
  • In Bezug auf das Studium von Mvi empfehle ich persönlich einen Artikel: MVVM Advanced Edition: Erfahren Sie mehr über die MVI-Architektur ~

Guess you like

Origin blog.csdn.net/itTalmud/article/details/126166742