MvRx + Epoxy —— 基本使用

前言

最近开发项目的时候框架选择了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 -> {}
            }
        }
    }

}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值