上一篇博客写了《MvRx的基本使用》,简单介绍了以下MvRx的优缺点和基本使用。这篇博客主要分享下MvRx的一些简单使用技巧。
博客中的项目地址:https://github.com/RDSunhy/MvRxSample
简单封装
根据官方的WiKi先封装一些基类,方便后面的使用,首先封装的就是ViewModel,代码如下:
BaseViewModel
abstract class BaseViewModel<S : MvRxState>(initialState: S) :
BaseMvRxViewModel<S>(initialState, debugMode = true)
BaseViewModel代码很简单,debugMode 是控制日志输出,可以在BaseViewModel中进行控制,子类集成BaseViewModel,debugMode需要改变时不用在每个子类里进行改变了。
BaseFragment
abstract class BaseFragment : BaseMvRxFragment(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
protected fun navigateTo(@IdRes actionId: Int, arg: Serializable? = null) {
/**注意!要传递的数据类型必须是可序列化的,也就是必须实现Serializable接口**/
val bundle = arg?.let { Bundle().apply { putSerializable(MvRx.KEY_ARG, it) } }
findNavController().navigate(actionId, bundle)
}
}
BaseFragment的封装也很简单,上面代码仅仅添加了一个 navigateTo()方法,里面的两行代码,分别是处理MvRx中的数据传递、以及Navigation的跳转,这个方法在我们跳转页面传递数据时会用到。
数据传递
MvRx的一大优势就在于页面间的数据传递非常简单,Fragment和Fragment之间的数据传递常常让我们头疼,来看一下MvRx是如何传递数据的,效果图:
主要由两个Fragment构成,FirstFragment输入数据,单击按钮将输入框中的数据传递到SecondFragment中,SecondFragment接受数据并且展示。
代码如下:
传递数据的实体类Person 。注意:一定要实现Serializable
data class Person(
val name: String,
val age: String,
val sex: String
):Serializable
FirstFragment代码:
data class FirstState(
val name: String = "--"
) : MvRxState
class FirstViewModel(firstState: FirstState, private val apiService: ApiService) :
BaseViewModel<FirstState>(firstState) {
init {
logStateChanges()
}
/**
* MvRx提供的依赖注入,通过MvRxViewModelFactory中的create,可以给
* viewModel中的构造参数实现依赖注入 下面代码实例化了一个retrofit的请求类
* 代码在Http包中
*/
companion object : MvRxViewModelFactory<FirstViewModel, FirstState> {
override fun create(
viewModelContext: ViewModelContext,
state: FirstState
): FirstViewModel {
val service: ApiService by lazy {
HttpUtils.retrofit.create(ApiService::class.java)
}
return FirstViewModel(state, service)
}
}
}
class FirstFragment : BaseFragment(){
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_first,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bnIntent.setOnClickListener{
if(etName.text.toString().isNotEmpty()
||etAge.text.toString().isNotEmpty()
||etSex.text.toString().isNotEmpty()){
var person = Person(
etName.text.toString(),
etAge.text.toString(),
etSex.text.toString()
)
//传递数据
navigateTo(R.id.action_firstFragment_to_secondFragment,person)
}else{
Toast.makeText(context,"请输入要传递的数据",Toast.LENGTH_SHORT)
}
}
}
override fun invalidate() {
}
}
传递数据时很简单,利用我们在BaseFragment中的navigateTo()指定跳转的Fragment (Fragment跳转我采用的是Navigation中的action,Deom中有对应代码就不贴出来了),重点是如何接受数据,下面贴出SecondFragment代码,代码中提供了两种接受传递数据的方法,代码中有大量注释:
SecondFragment
/**
* 方法二:
* 通过State的构造器也可以接受传递的数据
* 同时,如果你的state中的属性需要初始值(例如:分页加载的页数),都可以在构造器中赋值
*/
data class SecondState(
val name: String = "",
val state_person: Person? = null
) : MvRxState {
constructor(person: Person) : this(
name = "Sunhy",//给state中的属性赋初始值
state_person = person
)
}
class SecondViewModel(secondState: SecondState, private val apiService: ApiService) :
BaseViewModel<SecondState>(secondState) {
init {
logStateChanges()
}
companion object : MvRxViewModelFactory<SecondViewModel, SecondState> {
override fun create(
viewModelContext: ViewModelContext,
state: SecondState
): SecondViewModel {
val service: ApiService by lazy {
HttpUtils.retrofit.create(ApiService::class.java)
}
return SecondViewModel(state, service)
}
}
}
class SecondFragment : BaseFragment(){
/**
* 方法一:
* 接受传递过来的数据 只需要指定类型 从 args()中取出
* 在BaseFragment中 我们把数据存在了 MvRx.KEY_ARG 里
* @see [BaseFragment]
*/
val person :Person by args()
val secondViewModel by fragmentViewModel(SecondViewModel::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_second,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun invalidate() {
/**
* 通过 args() 取到的 person 数据
*/
if (person != null){
tvName.text = person.name
tvAge.text = person.age
tvSex.text = person.sex
}
/**
* 通过 State 构造器 取到的 state_person 数据
*/
withState(secondViewModel){
if(it.state_person != null){
tvName2.text = it.state_person.name
tvAge2.text = it.state_person.age
tvSex2.text = it.state_person.sex
}
/**
* 通过 State 构造器 赋初始值
*/
tvName3.text = it.name
}
}
}
上面的代码中共有两种接受数据的方式:
1.通过val person :Person by args()
只需要指定数据类型,从args()中取需要的数据即可
2.通过state的构造器去赋值,同时也可以给其他属性赋予初始值
监听
监听,同样也是MvRx的一大优势,本质上就是观察者模式,通过viewModel我们可以在任何地方监听State中的属性变化,也就是说当State中的属性发生变化时,就会回调,执行对应的代码
这一点和我们使用retrofit + rxjava进行网络请求时非常的相似,rxjava会在请求成功或者失败时回调不同的方法,处理对应代码。
MvRx中的监听分为两种,一种是监听普通的State属性,另一种就是监听Async<T>
包裹的网络请求接受实体类。前者通过selectSubscribe
监听并且可以监听多个(最多四个),后者通过asyncSubscribe
监听提供了onSuccess和onFail回调
效果图:
代码中有大量注释,代码如下:
SubscribeFragment
data class SubscribeState(
val name: String = "Shy",
val age: Int = 21,
val articleData: Async<ArticleData> = Uninitialized
) : MvRxState
class SubscribeViewModel(state: SubscribeState, private val apiService: ApiService) :
BaseViewModel<SubscribeState>(state) {
init {
logStateChanges()
}
fun changeName(newName: String){
withState { state ->
setState { copy(name = newName) }
}
}
fun changeAge(newAge: Int){
withState {
setState { copy(age = newAge) }
}
}
fun getArticleData(){
withState {
/**
* Loading 表示 正在请求中 防止重复请求
*/
if(it.articleData is Loading) return@withState
Api.api.getArticleList()
.execute { data ->
copy(articleData = data)
}
}
}
companion object : MvRxViewModelFactory<SubscribeViewModel, SubscribeState> {
override fun create(
viewModelContext: ViewModelContext,
state: SubscribeState
): SubscribeViewModel{
val service: ApiService by lazy {
HttpUtils.retrofit.create(ApiService::class.java)
}
return SubscribeViewModel(state, service)
}
}
}
class SubscribeFragment : BaseFragment(){
val subscribeViewModel by fragmentViewModel(SubscribeViewModel::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_subscribe,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
/**
* 通过两个按钮的单击事件 触发 State 的改变
*/
bnChangeName.setOnClickListener {
if(tvName.text == "Shy"){
subscribeViewModel.changeName("Sunhy")
}else{
subscribeViewModel.changeName("Shy")
}
}
bnChangeAge.setOnClickListener {
if(tvAge.text == "21"){
subscribeViewModel.changeAge(99)
}else{
subscribeViewModel.changeAge(21)
}
}
bnRequest.setOnClickListener {
subscribeViewModel.getArticleData()
}
/**
* 通过两种监听 监听 State"普通"属性 和 State中Async<T>属性
*
* selectSubscribe 监听"普通
*/
subscribeViewModel.selectSubscribe(SubscribeState::name, SubscribeState::age,
subscriber = { name, age ->
Toast.makeText(context,"name:-> ${name},age:-> ${age}",Toast.LENGTH_SHORT).show()
})
/**
* asyncSubscribe 监听网络请求接受bean的成功或者失败
*/
subscribeViewModel.asyncSubscribe(SubscribeState::articleData,
onSuccess = {
//请求成功
Toast.makeText(context,"请求成功",Toast.LENGTH_SHORT).show()
},
onFail = {
//请求失败
Toast.makeText(context,"请求失败->${it}",Toast.LENGTH_SHORT).show()
})
}
override fun invalidate() {
withState(subscribeViewModel){
tvName.text = it.name
tvAge.text = it.age.toString()
when(it.articleData){
is Success -> {
/**
* 因为 Async 是在 Observable 上封装了一层 所以 需要 invoke() 之后 获取到的才是 实体类 (也就是响应数据)
*/
tvData.text = it.articleData.invoke().data.toString()
}
is Fail -> {
tvData.text = "请求失败"
Log.e("网络请求失败","网络请求失败")
}
is Loading -> {
tvData.text = "请求中..."
Log.e("网络请求中","网络请求中")
}
else -> {}
}
}
}
}