/RxMVVM

A basic framework that integrates MVVM and Redux.

Primary LanguageKotlinApache License 2.0Apache-2.0

RxMVVM

License

A basic framework that integrates both MVVM and Redux.

1、引入

implementation "com.github.ITGungnir:RxMVVM:$rxmvvm_version"

2、使用MVVM

1)State的使用

State用于保存当前页面中的所有必要保存的状态,是一个data class,需要实现my.itgungnir.rxmvvm.core.mvvm.State接口。

data class AppState1(
    val randomNum: Int = 0,
    val error: Throwable? = null
) : State

2)BaseViewModel的使用

BaseViewModelMVVMVM层的基类,提供方法给V层调用,并提供给V层一个监听器,监听数据变化。

class AppViewModel1 : BaseViewModel<AppState1>(initialState = AppState1()) {

    @SuppressLint("CheckResult")
    fun generateRandomNumber() {
        Single.just((1..100).random())
            .subscribe({
                setState {
                    copy(
                        randomNum = it,
                        error = null
                    )
                }
            }, {
                setState {
                    copy(
                        error = Throwable(message = "生成随机数失败!")
                    )
                }
            })
    }
}

3)在Activity中使用

新版本的RxMvvm中不再封装BaseActivityBaseFragmentLazyFragment,因此只需要继承AppCompatActivityFragment即可。 在Activity中通过buildActivityViewModel()方法绑定VM,从而可以调用VM层的方法或监听数据变化。

class AppActivity1 : AppCompatActivity() {

    private val viewModel by lazy {
        buildActivityViewModel(
            activity = this,
            viewModelClass = AppViewModel1::class.java
        )
    }

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

        initComponent()
        observeVM()
    }

    private fun initComponent() {
        button.setOnClickListener {
            viewModel.generateRandomNumber()
        }
    }

    private fun observeVM() {

        viewModel.pick(AppState1::randomNum)
            .observe(this, Observer { randomNum ->
                randomNum?.a?.let {
                    number.text = it.toString()
                }
            })

        viewModel.pick(AppState1::error)
            .observe(this, Observer { error ->
                error?.a?.message?.let {
                    Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
                }
            })
    }
}

4)在Fragment中使用

Fragment的使用与Activity的使用相似,它的VM绑定方法有两种,即buildActivityViewModel()buildFragmentViewModel()。 前者可以与其他Fragment共享同一个VM,而后者则只是使用自己的VM

private val innerViewModel by lazy {
    buildFragmentViewModel(
        fragment = this,
        viewModelClass = ChildViewModel::class.java
    )
}
private val outerViewModel by lazy {
    buildActivityViewModel(
        activity = activity!!,
        viewModelClass = AppViewModel4::class.java
    )
}

5)Fragment懒加载的使用

新版本的RxMvvm不再提供LazyFragmentAPI,因为Google已经废弃了setUserVisibleHint()方法,并提供了新的setMaxLifecycle()方法,其使用方法分为以下两步。

第一步,在创建FragmentPagerAdapterFragmentStatePagerAdapter时,调用两个方法的构造函数,代码如下:

adapter = object : FragmentPagerAdapter(supportFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
    override fun getItem(position: Int): Fragment = FragChild.newInstance(position)
    override fun getCount(): Int = pageCount
}

第二步,在子FragmentonResume()方法中进行页面的初始化:

class FragChild : Fragment() {

    private var isInitialized = false

    override fun onResume() {
        super.onResume()
        if (!isInitialized) {
            // perform lazy loading
            isInitialized = true
        }
    }
}

6)ViewModel传参

想要通过ViewModel传参,需要通过ViewModelProvider.Factory创建ViewModel的实例。新版本的RxMvvm提供的buildActivityViewModel()buildFragmentViewModel()方法中新加了factory参数, 但需要先在具体的ViewModel类中创建工厂:

class AppViewModel7 constructor() : BaseViewModel<AppState7>(initialState = AppState7()) {

    constructor(initialData: Int) : this() {
        setState {
            copy(
                data = initialData
            )
        }
    }

    class Factory(private val initialData: Int) : ViewModelProvider.NewInstanceFactory() {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T = AppViewModel7(initialData = initialData) as T
    }

    fun changeData() {
        var newData: Int = (Math.random() * 1000 + 10).toInt()
        while (newData == getState().data) {
            newData = (Math.random() * 1000 + 10).toInt()
        }
        setState {
            copy(
                data = newData
            )
        }
    }
}

Activity中创建ViewModel的代码如下:

private val viewModel by lazy {
    buildActivityViewModel(
        activity = this,
        viewModelClass = AppViewModel7::class.java,
        factory = AppViewModel7.Factory(9999)
    )
}

3、使用Redux

Redux是一种前端的全局状态管理框架,它不仅可以存储全局的状态信息,还可以在系统各个组件中监听全局的状态的变化。

参考: Redux入门一Redux入门二Redux入门三

本项目中的Redux部分旨在提供一个轻量级的全局事件总线功能。

使用Redux时需要自定义Redux子类、AppStateActionReducerMiddleware

1)创建State

State中可以存储应用中的全局状态,在变量前面加上@DoPersist注解,可以将这个变量的值持久化到SharedPreferences中,如果不加这个注解,则不会做持久化操作。

data class AppState(
    val result: Int = 0,
    val loginFail: Unit? = null,
    @DoPersist val username: String = ""
)

2)创建Action

每个Action表示一个动作,需要实现Action接口。

data class GetResult(val result: Int) : Action

3)创建Reducer

Reducer用来处理Action,将Action中的数据更新到State中。自定义Reducer需要实现Reducer接口。

class MyReducer : Reducer<AppState> {

    override fun reduce(state: AppState, action: Action): AppState = when (action) {
        is GetResult ->
            state.copy(result = action.result)
        else ->
            state
    }
}

4)创建Middleware

Middleware用于将一个Action转换成另一个Action,需要实现Middleware接口。

class PlusMiddleware : Middleware<AppState> {

    override fun apply(state: AppState, action: Action, dispatch: (Action) -> Unit): Action = when (action) {
        is ChangeNum -> {
            Observable.just(action.newNum + 1)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    dispatch(MultiTwo(it))
                }
            NullAction
        }
        else ->
            action
    }
}

5)创建Redux子类

Redux子类需要继承BaseRedux类,并配置好其中的初始化数据,包括ReducerMiddleware等。

class MyRedux(context: Application) : BaseRedux<AppState>(
    context = context,
    initialState = AppState(),
    reducer = MyReducer(),
    middlewareList = listOf(PlusMiddleware(), MultipleMiddleware())
) {

    companion object {

        lateinit var instance: MyRedux

        fun init(context: Application) {
            instance = MyRedux(context)
        }
    }

    override fun deserializeToCurrState(json: String): AppState? =
        Gson().fromJson(json, AppState::class.java)
}

注意: BaseRedux的子类需要重写deserializeToCurrState方法,这个方法用于提供Json字符串向全局状态的映射规则。

6)初始化Redux

建议在Application类中初始化Redux,初始化时需要传入上下文。

MyRedux.init(this)

7)使用Redux

Redux的使用包括发送Action和监听State两个步骤。

// 发送Action
MyRedux.instance.dispatch(ChangeNum(currNum), listOf(PlusMiddleware(), MultipleMiddleware()))
// 监听State
MyRedux.instance.pick(AppState::result).observe(this, Observer {
    if (currNum == 1) {
        tvResult.text = "($currNum + 1) * 2 = 4"
    } else {
        tvResult.text = "($currNum + 1) * 2 = ${it.a}"
    }
    currNum++
})

如果本次dispatch的事件不需要中间件处理,则可以不传这个参数:

MyRedux.instance.dispatch(Logout)

发送Action的过程可以同步完成,也可以异步完成:

Single.just(ChangeNum(number))
    .subscribeOn(Schedulers.io())
//    .observeOn(AndroidSchedulers.mainThread())
    .observeOn(Schedulers.io())
    .subscribe({
        MyRedux.instance.dispatch(it)
    }, {
        println("------>>error: ${it.message}")
    })

除此之外,也可以通过currState()方法获取到当前全局状态对象:

println("------>>${MyRedux.instance.currState().result}")

Change Logs

v1.6.6

  • 优化Gradle依赖方式

v1.6.4

  • ViewModel中支持从非主线程中推送数据

v1.6.3

  • ViewModel支持传入参数

License

Copyright 2019 ITGungnir

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.