Bootstrap

基于Kotlin实现的MVI,用最少的代码实现完整框架

文章目录

概要

MVI(Model-View-Intent)框架是一种软件架构模式,尤其在移动应用开发中较为流行,它被设计来简化应用程序的复杂性和可维护性。MVI模式是对传统的MVVM(Model-View-ViewModel)模式的一种演进,强调了数据的单向流动和状态管理的重要性,这使得它在处理复杂的用户界面交互和状态变化时更加有效。

整体架构流程

技术名词解释

Model:
Model代表了应用程序的状态,通常是一个不可变的数据结构。
它可以包含业务逻辑或数据层的处理,但主要职责是存储和更新状态。
Model通常是响应Intent而产生新的状态。
View:
View负责展示Model中的数据,它是用户界面的表示。
View不直接与Model通信,而是通过Intent来表达用户的操作。
View通常会订阅Model的变化,并根据Model的状态更新UI。
Intent:
Intent是用户操作或者外部事件的抽象表示,比如按钮点击、网络请求完成等。
Intent触发Model的更新,从而导致View的重新渲染。
Intent可以看作是用户输入和系统输出之间的桥梁。

技术细节

帮助各位理解MVI

假设你正在玩一个电子游戏,游戏里有一个角色(可以把它想象成Model),一个屏幕(View),以及你的操作(Intent)。
Model(模型):这就像游戏里的角色,它包含了所有关于角色的信息,比如生命值、位置、装备等。每当游戏世界中发生一些事情,比如你打败了一个怪兽,Model就会更新,比如增加经验值或掉落物品。
View(视图):这是你看到的游戏画面,也就是屏幕上的显示。它不会自己决定要显示什么,而是根据Model里的信息来更新画面。比如,如果Model里的生命值减少了,View就会显示角色的生命条变短。
Intent(意图):这代表你的操作,比如按下的按键或触摸屏上的滑动。当你按下攻击键,这个动作就是Intent,它告诉游戏(Model)去做某些事情,比如让角色挥剑。

整体流程

1.封装BaseViewModel

open class BaseViewModel<I,S>(initState:S):ViewModel() {
	val intent  by lazy {
		Channel<I>(Channel.UNLIMITED)
	}
	val state  by lazy {
		MutableStateFlow<S>(initState)
	}
	val service:ApiService by lazy {
		service()
	}
}

2.封装BaseView

@Suppress("UNCHECKED_CAST")
abstract class BaseView<B:ViewDataBinding,VM:BaseViewModel<*,*>>:AppCompatActivity() {
	private val types by lazy {
		(javaClass.genericSuperclass as ParameterizedType).actualTypeArguments
	}
	val binding:B by lazy {
		val clazz = types[0] as Class<B>
		clazz.getMethod("inflate", LayoutInflater::class.java).invoke(null,layoutInflater) as B
	}
	val viewModel:VM by lazy {
		val clazz = types[1] as Class<VM>
		ViewModelProvider(this)[clazz]
	}
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(binding.root)
		initData()
		initView()
	}
	
	abstract fun initView()
	
	abstract fun initData()
}

3.封装完成,实际应用,获取意图

interface LoginIntent {
	data class login(val password:String,val username:String):LoginIntent
}

.4.获取状态

interface LoginState {
	data class LoginSuccess(val user:Res<LoginEntity>):LoginState
	data class LoginFail(val error:String):LoginState
	data object default:LoginState
}

5.实际运用之ViewModel

class LoginViewModel :BaseViewModel<LoginIntent,LoginState>(initState = LoginState.default) {
	init {
		viewModelScope.launch {
			intent.consumeAsFlow().collect{
				when(it){
					is LoginIntent.login->Login(it.password,it.username)
				}
			}
		}
	}
	
	
	
	private  fun Login(password: String, username: String) {
		viewModelScope.launch(Dispatchers.IO) {
			service.Login(password,username).collect{
				if(it.code == 0){
					state.value = LoginState.LoginSuccess(it)
				}else{
					state.value =(LoginState.LoginFail(it.msg))
				}
			}
		}
	}
}

这里的service是网络请求

6.实际应用之VIew

class LoginActivity : BaseView<ActivityLoginBinding, LoginViewModel>() {
	val map by lazy {
		mutableMapOf<String, String>()
	}
	
	override fun initView() {
		binding.user = map
		binding.btn.setOnClickListener {
			viewModel.intent.trySend(
				LoginIntent.login(
					map["password"] ?: "",
					map["username"] ?: ""
				)
			)
		}
	}
	
	override fun initData() {
		lifecycleScope.launch {
			viewModel.state.collect {
				when (it) {
					is LoginState.LoginSuccess -> {
						Toast.makeText(this@LoginActivity, "登录成功", Toast.LENGTH_SHORT).show()
						ARouter.getInstance().build("/login/main").navigation()
					}
					
					is LoginState.LoginFail -> {
						Toast.makeText(this@LoginActivity, "${it.error}", Toast.LENGTH_SHORT).show()
						Log.d("+++", "${it.error}")
					}
				}
			}
		}
		
	}
	
}

这里用的双向绑定

                                                           ~~~到此结束~~~

;