A basic framework that integrates both MVVM and Redux.
implementation "com.github.ITGungnir:RxMVVM:$rxmvvm_version"
State
用于保存当前页面中的所有必要保存的状态,是一个data class
,需要实现my.itgungnir.rxmvvm.core.mvvm.State
接口。
data class AppState1(
val randomNum: Int = 0,
val error: Throwable? = null
) : State
BaseViewModel
是MVVM
中VM
层的基类,提供方法给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 = "生成随机数失败!")
)
}
})
}
}
新版本的RxMvvm
中不再封装BaseActivity
、BaseFragment
和LazyFragment
,因此只需要继承AppCompatActivity
或Fragment
即可。
在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()
}
})
}
}
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
)
}
新版本的RxMvvm
不再提供LazyFragment
的API
,因为Google
已经废弃了setUserVisibleHint()
方法,并提供了新的setMaxLifecycle()
方法,其使用方法分为以下两步。
第一步,在创建FragmentPagerAdapter
或FragmentStatePagerAdapter
时,调用两个方法的构造函数,代码如下:
adapter = object : FragmentPagerAdapter(supportFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): Fragment = FragChild.newInstance(position)
override fun getCount(): Int = pageCount
}
第二步,在子Fragment
的onResume()
方法中进行页面的初始化:
class FragChild : Fragment() {
private var isInitialized = false
override fun onResume() {
super.onResume()
if (!isInitialized) {
// perform lazy loading
isInitialized = true
}
}
}
想要通过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)
)
}
Redux
是一种前端的全局状态管理框架,它不仅可以存储全局的状态信息,还可以在系统各个组件中监听全局的状态的变化。
参考: Redux入门一、 Redux入门二、 Redux入门三
本项目中的Redux
部分旨在提供一个轻量级的全局事件总线功能。
使用Redux
时需要自定义Redux
子类、AppState
、Action
、Reducer
和Middleware
。
State
中可以存储应用中的全局状态,在变量前面加上@DoPersist
注解,可以将这个变量的值持久化到SharedPreferences
中,如果不加这个注解,则不会做持久化操作。
data class AppState(
val result: Int = 0,
val loginFail: Unit? = null,
@DoPersist val username: String = ""
)
每个Action
表示一个动作,需要实现Action
接口。
data class GetResult(val result: Int) : Action
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
}
}
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
}
}
Redux
子类需要继承BaseRedux
类,并配置好其中的初始化数据,包括Reducer
、Middleware
等。
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字符串向全局状态的映射规则。
建议在Application
类中初始化Redux
,初始化时需要传入上下文。
MyRedux.init(this)
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}")
- 优化Gradle依赖方式
- ViewModel中支持从非主线程中推送数据
- ViewModel支持传入参数
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.