ViewModel+Flow的绝佳实例和封装

50 篇文章 0 订阅
37 篇文章 3 订阅
本文探讨了从Java迁移到Kotlin和LifeData+Flow在Android开发中的应用,重点讲解了ViewModel+Flow的使用细节,涉及的知识点包括kotlin扩展函数、高阶函数、内联函数等,以及如何解决数据流安全性和重复性问题,以及利用仓库层进行数据选择。
摘要由CSDN通过智能技术生成
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.案例的分析

我先分析以下上面的案例出现的问题:

  1. 不安全
  2. 重复性高
  3. 灵活度不强

首先,我们先来将为什么不安全.其实看文档我们就知道
在这里插入图片描述

简单的来讲,就是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的语法和使用,讲道理真的是太香了…这是我从未见过的操作。里面官网推荐的写法会有一些坑需要我们自己去排除并且修改。这样才会让我们的程序更加的健壮。所以,文档要好好看啊…协程用起来当然是非常舒服的,但是里面的东西也需要我们慢慢的去学习。好了,很久不写文章了,但是曾经把这个实例写完之后运行了,我都不知道自己是怎么做到的…里面还是有挺多知识我还没有把握的完全。所以需要我自己去记录下来.~
 

参考文章:

你不知道的Kotlin高阶函数应用

Kotlin的inline noinline crossinline笔记

Fragment中使用viewLifecycleOwner/getActivity/this_Jason_Lee155的博客-CSDN博客

Flow | Android Developers

使用更为安全的方式收集 Android UI 数据流_River_ly的博客-CSDN博客

————————————————
版权声明:本文为CSDN博主「矿坑中的野猫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_33902817/article/details/124924020

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Java 中,ViewModel 是一种用于管理 UI 组件数据的架构组件。ViewModel 与 Activity 或 Fragment 等 UI 组件相关联,并存储 UI 组件需要的数据,以便在旋转屏幕或配置更改等情况下保持数据。 以下是一个简单的 ViewModel 实例,它管理一个整数变量: ``` import androidx.lifecycle.ViewModel; public class MyViewModel extends ViewModel { private int count = 0; public void increment() { count++; } public int getCount() { return count; } } ``` 此 ViewModel 包含一个名为 `count` 的整数变量,以及两个公共方法:`increment` 和 `getCount`。`increment` 方法用于递增 `count` 变量的值,`getCount` 方法返回当前 `count` 变量的值。 在 Activity 或 Fragment 中使用此 ViewModel: ``` public class MyActivity extends AppCompatActivity { private MyViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 获取 MyViewModel 实例 viewModel = new ViewModelProvider(this).get(MyViewModel.class); // 使用 ViewModel 中的数据更新 UI updateUI(); // 设置一个按钮点击事件,当用户点击按钮时递增 count 变量 Button incrementButton = findViewById(R.id.increment_button); incrementButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { viewModel.increment(); updateUI(); } }); } private void updateUI() { // 更新 UI 中的文本视图,显示当前 count 变量的值 TextView countTextView = findViewById(R.id.count_text_view); countTextView.setText(String.valueOf(viewModel.getCount())); } } ``` 在 `onCreate` 方法中,我们获取 `MyViewModel` 实例,并使用 `updateUI` 方法更新 UI 中的文本视图,以显示 `count` 变量的值。我们还设置了一个按钮点击事件,当用户点击按钮时,我们递增 `count` 变量并再次调用 `updateUI` 方法,以便在 UI 中更新 `count` 变量的值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值