告别切换线程,个人认为最优的LiveData用法

4,048 阅读5分钟

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了解详情。