概述
ViewModel+Flow的绝佳实例和封装
0.前言
在最近的Android开发中,主流推送慢慢从Java→Kotlin,LifeData→Flow.
那里面各有各的好处,也都是为了解决不同应用场景给出不同的方案。
但是在使用官方推荐的ViewModel+Flow
时,会有很多需要解决的细节,里面也会牵涉到比较多的知识点。所以这个文章就是将自己学习到的东西进行总结.
此文章会涉及到的知识点会比较多包括:
- kotlin扩展函数
- kotlin高阶函数
- kotlin内联函数
- Flow的使用
1.Flow的简易使用
以下例子乍一看并没有任何毛病,但是官方完全不建议你这么使用.
当然,这里还涉及到了用ViewModel去创建协程域.
感兴趣你也可以看回来我的文章
Android kotlin协程浅析笔记_矿坑中的野猫的博客-CSDN博客_android kotlin协程
class kotlinFlow(application: Application) :AndroidViewModel(application){
//热数据flow
val uiState =MutableStateFlow("")
}
class FragmentDemon : Fragment() {
kotlinflow.viewModelScope.launch {
kotlinflow.uiState.collect {
Log.d(TAG, "onCreate: StateFlow"+it)
}
}
}
2.案例的分析
我先分析以下上面的案例出现的问题:
- 不安全
- 重复性高
- 灵活度不强
首先,我们先来将为什么不安全
.其实看文档我们就知道
简单的来讲,就是FLow在Activity处于后台时,Collect是不会停止的.如果此时更新UI,程序会发生崩溃.
最简单的解决办法是加上repeatOnLifecycle
的监听. 还有一种是*addRepeatingJob
.*注意这两种是增加不同的监听,前者重点是挂起,后期是取消。在协程中挂起和取消是不同的!
kotlinflow.viewModelScope.launch{
//在Start的时候收集,Stop的时候就自动挂起了..
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){
kotlinflow.uiState.collect{
Log.d(TAG, "onCreate: StateFlow"+it)
}
}
}
再来讲重复性高
,其实看到上面的代码我们就知道了,如果每一种UIStatue都需要进行收集并且添加监听,那么中间一大串的监听以及collect全都是重复性代码,所以这里我们需要去进行封装整合.
最后灵活性不强
,其实也是作者最近接触到的概念,如果一个数据并不是每次都从网络/持久性数据进行获取,而是从自定义缓存中进行获取
。 那么该如何设计ViewModel能让数据进行选择呢?哪一层适合做这样的一件事。 答案就是在ViewModel中增加一个仓库层
.在仓库层中完成这件事.
3.Nice的实例和封装
先把代码亮出来,其它再BB.
ViewModel
class NiceUseCoroutineAndFlow(application: Application) : AndroidViewModel(application) {
val useFunctionCreate by lazy{
createFlow(RepoSearchResponse(), viewModelScope = viewModelScope, action = {
it.value = DemoRepository.getGitHubData()
})
}
val NormalCreate by lazy{
createFlow(1)
}
//----------------使用仓库层------------------------//
//Repo中的Example,作为仓库层的统一封装入口
//仓库层的主要工作是判断应该走本地还是走网络并且将获得的数据返回给调用方.
//若没有本地的则去网络层进行获取
object DemoRepository {
//其它的网络接口库
private val gitHubService = GitHubService.create()
private var hasCache = false;
suspend fun getGitHubData(): RepoSearchResponse {
if(hasCache){
return RepoSearchResponse()
}else {
return retrofitUseDemo.createGithubApi().searchRepos("Android", 0, 20)
}
}
}
//----------------使用仓库层------------------------//
}
class FragmentOther : Fragment() {
fun Function2(){
NiceViewModel._useFunctionCreate.launchAndCollectIn(viewLifecycleOwner,action= {
Log.d(TAG, "You _useFunctionCreate that !!!!!!!!Function2: ${it.total}")
})
NiceViewModel.NormalCreate.launchAndCollectIn(viewLifecycleOwner, action = {
Log.d(TAG, "Function2: What happen $it")
})
}
}
扩展函数
package com.kotlin.learning
import androidx.lifecycle.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
//-----------------Flow扩展内联函数--------------------------------//
/**
*解决写重复代码
*并且最早在 View处于 STARTED状态时从数据流收集数据,并在
*生命周期进入 STOPPED状态时 STOPPED(停止)收集操作。
*它会在生命周期再次进入 STARTED状态时自动开始进行数据收集操作。
*/
//扩展函数.
inline fun <T> Flow<T>.launchAndCollectInx(
owner: LifecycleOwner,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
crossinline action: suspend CoroutineScope.(T) -> Unit
) {
owner.lifecycleScope.launch{
//在生命周期到达该状态时,自动创建并启动新的协程,低于该状态是被自动取消
owner.addRepeatingJob(minActiveState){
collect{
action(it)
}
}
}
}
//扩展函数,加上repeatOnlifecycle
//这个在于协程会被自动挂起,在处理事务时无法被直接取消,所以建议用这个挂起
inline fun <T> Flow<T>.launchAndCollectIn(
owner: LifecycleOwner,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
crossinline action: suspend CoroutineScope.(T) -> Unit
) {
owner.lifecycleScope.launch{
owner.lifecycle.repeatOnLifecycle(minActiveState){
collect{
action(it)
}
}
}
}
//-----------------Flow扩展内联函数--------------------------------//
fun NormalCoroutineScope():CoroutineScope{
val job =Job()
val coroutineScope =CoroutineScope(job)
return coroutineScope;
}
/**
* data->初始数据
* dispatcher - >调度器
* action -->需要初始赋值的函数
*/
inline fun <reified T>createFlow(data:T,
dispatcher: CoroutineDispatcher = Dispatchers.Default,
viewModelScope:CoroutineScope =NormalCoroutineScope(),
// 在内联函数中必须添加 noinline ->用作于不被内联函数影响,生成一个匿名内部类
//or crossinline 保证了函数只能return当前这个lambda
//你看到的CoroutineScope.()其实是具体的具象, 它可以是任何对象。比如 View.(Int)->Unit
//这里需要思考一下,为什么这么做.添加了CoroutineScope,因为它是协程,所以添加这个协程的作用域
//该高阶函数需要一个参数,然后可以传到外面去
crossinline action:suspend CoroutineScope.(MutableStateFlow<T>)->Unit={}): MutableStateFlow<T> {
returnMutableStateFlow(data).also{
viewModelScope.launch(dispatcher){
//回调返回使其可以做任意操作
action(it)
}
}
}
我先把在这里使用这种方式完成的功能告诉你们:
1.ViewModel中可以异步的
请求数据创建Flow,并且按需创建,在数据被使用时才会占用内存
.
2.默认生命周期大于STARTED
数据才被collect,在STOP
时自动挂起.
3.通过仓库层去选择你要通过缓存获取数据/去请求网络数据
而你只需要简单的CreateFlow,和launchAndCollectIn…简直太幸福了.
4.案例解析.
先从简到难一步步解析这个案例如果实现这些不同的功能点以及如何进行思考的.
- 被使用时才占用内存
比较熟悉的朋友应该都不用将了,by lazy
.通过懒加载
进行数据的添加.为什么要这么做?数据不用就没必要占用运行时的内存,也是做了小小的优化.非常合理.
- 异步请求数据
看到createFlow这个内联函数,默认我们是通过job自定义创建协程域,可以通过协程去异步请求数据
.
为什么要这么做?如果请求数据作为网络请求,在请求量大的情况下,利用协程的异步功能可以很好的提高请求数据的速度.
- 处于 STARTED状态时收集数据,生命周期进入 STOPPED状态时 STOPPED(停止)收集操作
我们利用了LifecycleOwner中的lifecycle去addRepeatingJob
。这是官方推荐使用的,至于原理是什么
我暂时没时间看,你们可以自己继续扩展一下. 为什么要这么做?安全!安全!还是tmd安全!谁知道你要在收集数据中做什么呢.~ 上面就说过,如果更新UI,不添加这个可能会造成程序崩溃.
5.内联函数和扩展函数的解析
其实整体思路并不会很难,只是涉及到的知识点会比较多。这里我觉得比较重要的是整体的内联函数的写法.
我们先来看createFlow函数的写法。
我暂且将注释去掉,并且解释一下这里的整体写法.
inline
:内联函数.为什么用内联函数,不用普通函数,因为内联函数可以解决函数调用的效率问题.
reified
:可直接通过泛型拿到泛型的类型
看第四个参数:crossinline action:suspend CoroutineScope.(MutableStateFlow<T>)->Unit={}): MutableStateFlow<T>
crossinline
:保证了函数只能返回当前的lambda
suspend
:保证高阶函数可以是为协程域函数
CoroutineScope
:为具体的具象,可以为任何对象.在这里的作用为添加这个是协程的作用域
参考文章:https://www.jianshu.com/p/629e388685c5
/**
* data->初始数据
* dispatcher - >调度器
* action -->需要初始赋值的函数
*/
inline fun <reified T>createFlow(data:T,
dispatcher: CoroutineDispatcher = Dispatchers.Default,
viewModelScope:CoroutineScope =NormalCoroutineScope(),
crossinline action:suspend CoroutineScope.(MutableStateFlow<T>)->Unit={}): MutableStateFlow<T> {
returnMutableStateFlow(data).also{
viewModelScope.launch(dispatcher){
action(it)
}
}
}
这样一个内联函数可以去创建MutableStateFlow而不用再去写重复的代码.而默认参数保证了不会导致出太多错误.
再看launchAndCollectIn
扩展函数.这个函数其实和上面的函数很像,所以我们只需要关注owner: LifecycleOwner。
这个参数,需要注意的是.Activity中,这个参数在Activty中可以使用this.但是在Fragment中需要使用的为viewLifecycleOwner.
因为在Fragement中,Fragment和Fragment中的View不一致,需要让Flow去感知的是FragmentView的生命周期而不是Fragment的,否则可能会导致程序崩溃
参考文章:https://blog.csdn.net/Jason_Lee155/article/details/117655264
inline fun <T> Flow<T>.launchAndCollectIn(
owner: LifecycleOwner,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
crossinline action: suspend CoroutineScope.(T) -> Unit
) {
owner.lifecycleScope.launch{
owner.lifecycle.repeatOnLifecycle(minActiveState){
collect{
action(it)
}
}
}
}
6.总结
使用了内联去调用并且参数为函数.Kotlin的语法和使用,讲道理真的是太香了…这是我从未见过的操作。里面官网推荐的写法会有一些坑需要我们自己去排除并且修改。这样才会让我们的程序更加的健壮。所以,文档要好好看啊…协程用起来当然是非常舒服的,但是里面的东西也需要我们慢慢的去学习。好了,很久不写文章了,但是曾经把这个实例写完之后运行了,我都不知道自己是怎么做到的…里面还是有挺多知识我还没有把握的完全。所以需要我自己去记录下来.~
Android 学的东西真的是…不少啊0_0…好了,有空的话,下次再见了.
参考文章:
你不知道的Kotlin高阶函数应用
Kotlin的inline noinline crossinline笔记
Fragment中使用viewLifecycleOwner/getActivity/this_Jason_Lee155的博客-CSDN博客
Flow | Android Developers
使用更为安全的方式收集 Android UI 数据流_River_ly的博客-CSDN博客
最后
以上就是害羞母鸡为你收集整理的ViewModel+Flow的绝佳实例和封装ViewModel+Flow的绝佳实例和封装的全部内容,希望文章能够帮你解决ViewModel+Flow的绝佳实例和封装ViewModel+Flow的绝佳实例和封装所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复