LiveData是什么?
- 如果你不知道,请点这里。
LiveData怎么用?
- 如果你不知道,请点这里。
LiveData有什么不足?
- 问得好!来看一段代码:
MainActivity的代码:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mainViewModel =
ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MainViewModel::class.java)
mainViewModel.userListLiveData.observe(this, Observer { list ->
//处理接收成功的数据
})
mainViewModel.errorLiveData.observe(this, Observer { throwable ->
//处理接收失败的错误
})
mainViewModel.loadingLiveData.observe(this, Observer { isLoading ->
//处理接收的等待动画
})
}
}
ViewModel的代码:
class MainViewModel : ViewModel() {
val userListLiveData = MutableLiveData<List<User>>()
val errorLiveData = MutableLiveData<Throwable>()
val loadingLiveData = MutableLiveData<Boolean>()
fun findUserByDepartment(departmentId: String) {
viewModelScope.launch {
try {
loadingLiveData.postValue(true)
val user = UserRepository.findUserByDepartment(departmentId)
loadingLiveData.postValue(false)
userListLiveData.postValue(user)
} catch (throwable: Throwable) {
errorLiveData.postValue(throwable)
}
}
}
}
- 以上代码的问题:如果需要在界面上分别展示加载时、加载后、加载成功以及加载失败的效果的话,我们需要定义很多个LiveData才能实现,这是非常混乱且不美观的。
我的解决方案
- 自定义一个LiveData,可以同时处理加载中、加载成功和加载失败的界面。
- 对Kotlin协程、RxJava都可以很好的支持。
- 完美支持JetPack中的ROOM + Paging分页方案。
- 不再需要频繁的切换线程。
1.引入依赖
①在你的项目的根目录下的build.gradle文件中添加以下代码,如果已存在则忽略
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
②在你的子模块的build.gradle文件中添加以下依赖:
implementation 'com.gitee.numeron.stateful:livedata:1.0.0'
2.使用方法
以上的代码就可以写作:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mainViewModel =
ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MainViewModel::class.java)
mainViewModel.userListLiveData.observe(this, Observer { stateful ->
stateful.onSuccess { list ->
//处理接收成功的数据
}.onFailure { message, throwable ->
//处理接收失败的错误
}.onLoading { message, progress ->
//处理接收的等待动画
}
})
}
}
class MainViewModel : ViewModel() {
val userListLiveData = StatefulLiveData<List<User>>()
fun findUserByDepartment(departmentId: String) {
viewModelScope.launch {
try {
userListLiveData.postLoading("正在加载该部门下的用户列表...")
val userList = UserRepository.findUserByDepartment(departmentId)
userListLiveData.postSuccess(userList)
} catch (throwable: Throwable) {
userListLiveData.postFailure(throwable)
}
}
}
}
使用了StatefulLiveData之后,声明的LiveData只有一个了,但是可以同时处理多种状态下的数据。
并且我们不再需要在ViewModel中切换线程了,只需要先开一个协程(线程)后,在里面处理数据,然后把数据扔给StatefulLiveData,告诉它,我们现在处于处理数据过程中的哪个阶段,最后在View层观察StatefulLiveData,分别处理各种状态下的界面就完事儿了。
3.异常处理
为了更简单、方便的使StatefulLivedData,这个库中还提供了一个异常处理工具,把它们利用起来后,MainViewModel中的代码可以写成以下形式:
class MainViewModel : ViewModel() {
val userListLiveData = StatefulLiveData<List<User>>()
fun findUserByDepartment(departmentId: String) {
viewModelScope.launch(StatefulExceptionHandler(userListLiveData)) {
userListLiveData.postLoading("正在加载该部门下的用户列表...")
val userList = UserRepository.findUserByDepartment(departmentId)
userListLiveData.postSuccess(userList)
}
}
}
不需要再手动把代码用try/catch包裹起来,StatefulExceptionHandler会在发生异常后,把异常提交到StatefulLiveData中,我们也就可以把代码写得更整齐一些了。当然,你可以仿着StatefulExceptionHandler自己定义一个异常处理器,然后在开启协程的时候,把它作为协程上下文传进去就可以了。
4.传递进度
当下载一个大文件、或者获取的数据很大的时候,可以引入一个进度条来告知用户我们的进度处理到哪里了。这种情况下,我们可以通过StatefulLiveData的postLoading方法传递一个Float值给View层。
class MainViewModel : ViewModel() {
val userListLiveData = StatefulLiveData<List<User>>()
fun findAllUser() {
viewModelScope.launch(StatefulExceptionHandler(userListLiveData)) {
//通知View层等待,当前进度为0
userListLiveData.postLoading("正在加载全部用户列表...", 0f)
//获取所有部门
val departmentList = UserRepository.allDepartment()
departmentList
//获取每个部门下的用户
.mapIndexed { index, department ->
//计算进度,并提交到View层
val progress = (index + 1) / departmentList.size.toFloat()
userListLiveData.postLoading("正在加载全部用户列表...", progress)
//获取部门下所有用户
UserRepository.findUserByDepartment(department.id)
}
//将List<List<User>>转换为List<User>
.flatten()
//将List<User>提交到StatefulLiveData中
.let(userListLiveData::postSuccess)
//发送一条消息到View层
userListLiveData.postMessage("所有用户加载成功!")
}
}
}
跟上面介绍的一样,StatefulLiveData中有一个postLoading的重载方法,它接收一个String和Float类型的参数,String类型的参数用于向View层发送一句话,Float类型的参数用于告知View层当前的进度。
在成功获取到数据之后,我们还多了一行调用postMessage方法的代码,它的功能马上开始介绍。
5.状态监听
同时,为了更清晰的处理数据不同的状态,提供了一个StatefulObserver,是Observer的实现类,用于处理StatefulLiveData接收到的各种状态。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mainViewModel =
ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MainViewModel::class.java)
mainViewModel.userListLiveData.observe(this, StatefulObserver(UserListStatefulCallback()))
}
private inner class UserListStatefulCallback : StatefulCallback<List<User>> {
override fun onSuccess(value: List<User>) {
//处理接收成功的数据
}
override fun onLoading(message: String, progress: Float) {
//处理等待动画或进度
}
override fun onFailure(message: String, cause: Throwable) {
//处理接收失败的错误
}
override fun onMessage(message: String) {
//通过postMessage传递的消息
}
}
}
StatefulObserver接收一个StatefulCallback的参数,StatefulCallback在实例化时需要一个与StatefulLiveData一样的泛型参数,共有4个需要实现的方法,分别是
- onSuccess,用于处理接收成功的数据;
- onLoading,处理等待动画或进度;
- onFailure,处理接收失败的错误;
- onMessage,通过postMessage传递的消息;
其中,onMessage方法是可选的,根据业务需求选择是否实现它。这4个方法都会在主线程上运行,这是LiveData原本就有的功能。
6.Paging分页
如果你在用ROOM + Paging的分页功能的话,你肯定没少写这样的代码:
class MainViewModel : ViewModel() {
val userPagedListLiveData = UserRepository.userSourceFactory().toLiveData(Config(20))
}
只需要一行代码,就可以声明一个LiveData<PagedList>实例,实在是太方便了,考虑到StatefulLiveData管理数据状态也非常方便,是否可以让它们一起使用呢?
当然!只需要一后面加一句代码就可以实现:
val userPagedListLiveData = UserRepository.userSourceFactory().toLiveData(Config(20)).toStateful()
请留意,虽然这里没提示,但是Android Studio中会提示你,userPagedListLiveData的类型已经是StatefulLiveData<PagedList>了。只需要在一个LiveData的后面加上.toStateful(),LiveData立马变成拥有状态的StatefulLiveData,兄弟们,请把评论打在方便上。
结语
以上,就是我在工作中抽取出来的StatefulLiveData的使用方法,还有一些其它已经抽取出来的组件,请点击我的github了解详情。