前言
最近开发项目的时候框架选择了MvRx,一个更适用于单Activity多Fragment应用的框架,熟悉React - Native开发的会很容易入手这个框架,思想基本是一样的。项目忙碌的过程已经过去了,现在我会抽时间写写使用MvRx的总结,分享在博客上,国内的MvRx教程确实非常少,不过github上的Wiki写的也很详细,认真读几遍会解决很多问题。
我在学习这个框架的时候就是通过官方Wiki,以及“珞泽珈群”大佬的一些列博客来入门的。大佬的博客关于原理的讲的很清楚。我写博客的意义更多的在于实践代码的分享,会通过一些列的Demo来分享MvRx的使用技巧。
框架优缺点
优点:
1.用大佬的话来说,实现了真响应式框架,UI层面只需要关心数据如何呈现。
2.框架封装的非常到位,基本能解决开发中的问题
3.页面跳转数据传递非常便捷
缺点:
1.学习成本大,首先要学习Kotlin
2.Fragment确实存在一些弊端,比如:Fragment切换数据保存等
3.国内的学习资料比较少,可能这个框架在国外比较流行
项目引入
MvRx项目地址:https://github.com/airbnb/MvRx
Epoxy项目地址:https://github.com/airbnb/epoxy
注:
1.MvRx项目必须使用kotlin语言开发!!!
2.MvRx更倾向于于单Activity多Fragment应用,所以我使用了navigation
引入步骤:
app/build.gradle 中加入如下代码:
//加载顶部的两行代码
...
apply plugin: 'kotlin-kapt'
apply plugin: 'com.jakewharton.butterknife'
...
//android {} 内增加
android{
...
//minSdkVersion 必须大于等于16
defaultConfig {
...
minSdkVersion 16
...
}
kotlinOptions {
jvmTarget = "1.8"
}
kapt {
correctErrorTypes = true
}
...
}
//添加依赖
dependencies {
...
//MvRx + Epoxy
implementation 'com.airbnb.android:mvrx:1.1.0'
implementation 'com.airbnb.android:epoxy:3.8.0'
kapt 'com.airbnb.android:epoxy-processor:3.8.0'
//navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.1.0'
...
}
项目的build.gradle中加入如下代码:
...
buildscript {
dependencies {
...
//butterknife
classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'
...
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
...
MvRx基本使用
MvRx将页面分为三部分 State,ViewModel,View
State :一个data class 里面包含的属性 对应着当前页面所需要的数据,State的改变只能在 ViewModel 中,当 State改变时,会立即刷新页面上的数据。这一点和React Native 中的 state 可以说是一模一样。State中定义的属性必须要有一个初始值。
ViewModel:viewModel 主要用于处理业务逻辑(网络请求、数据处理等)、监听 State 中的属性。viewModel中定义的方法必须是无返回值。
View:就是当前Fragment,MvRx真正实现了View层只需要负责数据展示,不处理任何业务逻辑。如:给TextView设置显示文本值时,是设置的 state 中的属性。当State 中的 属性改变时,会调用invalidate() 去更新页面的数据。
MvRx提供了很多封装,Asycn<T>
是一个非常常用封装,它实际上就是在Observable<T>
外包装了一层,提供了四种请求的状态:Uninitialized(未初始化)、Loading(请求中)、Success(请求成功)、Fail(请求失败)。通过invoke()就可以取到实体Bean,结合MvRx提供的监听,能够很方便的处理网络请求。
下面的一个非常简单的Demo来展示一下State、ViewModel、View三者的使用。三个按钮,前两个按钮对应着 text 文本的改变,最后一个网络请求按钮,展示了Async<T>
的使用,代码中有非常详细的注释,个人的理解可能有错误的地方,欢迎各位纠正
Demo地址:https://github.com/RDSunhy/MvRxSample(还在完善中,后续会把MvRx常用的一些技巧写在博客,代码更新到github)
效果图:
代码如下,一些个人的理解都写在注释中,有误解的地方请指正,这里就只贴出来Fragment中的代码,在你的Activity中一定要继承BaseMvRxActivity!!!
代码如下:
/**
* state 可以理解为 包含所有当前页面需要的数据的一个 data class
* state 中的 属性 必须赋予初始值
*
* Async<T> 是MvRx的封装 将网络请求的 Observable<T> 映射到 State 中的 Async<T>上 使网络请求处理变得很简单
* 分别有:Uninitialized(未初始化)
* Loading (请求中)
* Success (请求成功)
* Fail (请求失败)
*/
data class SampleState(
val name: String = "--",
val age: Int = 0,
val articleData: Async<ArticleData> = Uninitialized
) : MvRxState
class SampleViewModel(initialState: SampleState) : BaseMvRxViewModel<SampleState>(initialState, debugMode = true) {
/**
* init 方法 在ViewModel 初始化的时候会调用
*/
init {
/**
* logStateChanges 打印 State 变化日志 debugMode = true 时打印
*/
logStateChanges()
}
/**
* 改变state的方法必须在viewModel
* withState{}
*/
fun changeName(newName: String){
withState { state ->
/**
* 可以通过 withState 拿到 当前页面State 中的所有属性
* 如:state.age
*/
setState { copy(name = newName) }
}
}
fun changeAge(newAge: Int){
withState {
setState { copy(age = newAge) }
}
}
/**
* 网络请求 retrofit
*
* execute 是对 rxjava 的封装 直接返回一个Async<T>对象 里面包含了 请求状态 和 请求的数据
*/
fun getArticleData(){
withState {
/**
* Loading 表示 正在请求中 防止重复请求
*/
if(it.articleData is Loading) return@withState
Api.api.getArticleList()
.execute { data ->
copy(articleData = data)
}
}
}
}
class SampleFragment : BaseMvRxFragment(){
/**
* 获取当前页面的 viewModel 通过viewModel中的方法实现数据交互
* fragment中 只负责数据如何呈现 无需管理任何网络请求逻辑
*/
val sampleViewModel by fragmentViewModel(SampleViewModel::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_sample,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bnChangeName.setOnClickListener { sampleViewModel.changeName("Sunhy") }
bnChangeAge.setOnClickListener { sampleViewModel.changeAge(21) }
bnRequest.setOnClickListener { sampleViewModel.getArticleData() }
}
/**
* invalidate 当 监听的 viewModel 对应的 State 中的属性发生改变时 自动调用
*
* 在我个人理解 这个相当于 自定义view中的 重绘方法 不过这个是自动在调用
* 当你的State发生改变时 就会触发invalidate() 执行里面的逻辑
* 这一点 和 React-Native 中的 state 是一模一样的 都是一个思想 当数据改变时 UI视图也随之改变
*
*/
override fun invalidate() {
withState(sampleViewModel){state ->
/**
* 切记一点思想 在 fragment 中只需要关心 view 是如何呈现数据 不需要关心数据获取的逻辑
*
* 如: 一个TextView 需要展示姓名 姓名对应 State 中的一个属性 则 text == state.name
*/
tvName.text = state.name
tvAge.text = state.age.toString()
/**
* 网络请求 也同上 当 state 中的 Async<T> 对象 改变时 会自动触发 invalidate()
* 去刷新页面 就相当于 invalidate() 中的代码 重新执行一遍
*/
when(state.articleData){
is Success -> {
/**
* 因为 Async 是在 Observable 上封装了一层 所以 需要 invoke() 之后 获取到的才是 实体类 (也就是响应数据)
*/
tvData.text = state.articleData.invoke().data.toString()
}
is Fail -> {
Log.e("网络请求失败","网络请求失败")
}
is Loading -> {
Log.e("网络请求中","网络请求中")
}
else -> {}
}
}
}
}