协程之Flow

Flow是什么

官方文档给予了一句话简单的介绍:

Flow — cold asynchronous stream with flow builder and comprehensive operator set (filter, map, etc);

翻译一下:

具有流构建器和综合运算符集(过滤器、映射等)的冷异步流;


为了便于理解我们先简单看一下它的结构:



他的Rxjava类似都是上游产生数据,操作符,下游接受数据这种基于数据流驱动的结构。为了便于理解和上手,我做了一些flow和rxjava的使用类比。但需要注意flow是协程中的库,只能用于协程环境。


再熟悉一下Rxjava的定义:

RxJava:a library for composing asynchronous and event-based programs using observable sequences for the Java VM

翻译:RxJava是一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库

总结:RxJava是一个 基于事件流、实现异步等操作的库


[if !supportLists]二.[endif]初识flow

2.1简单创建

第一步:创建一个flow

val flow1 = flow {emit(1)}

第二步:收集数据流

flow1.collect {print(it)}

类比Rxjava

第一步:创建一个被观察者

val observable = Observable.create { emitter ->

            emitter.onNext(1)

        }

第二步:观察者订阅被观察者

observable.subscribe(Consumer {

            print(it)

        });

2.2其他创建方式

第一种

[if !supportLists]1. [endif] flowOf(1,2,3,4,5)

[if !supportLists]2. [endif]        .onEach {

[if !supportLists]3. [endif]            delay(100)

[if !supportLists]4. [endif]        }

[if !supportLists]5. [endif]        .collect{

[if !supportLists]6. [endif]            println(it)

[if !supportLists]7. [endif]        }

类比rxjava

val observable: Observable<*> = Observable.just("A", "B", "C")


第二种

listOf(1, 2, 3, 4, 5).asFlow()

类比rxjava

val observable1: Observable = Observable.fromIterable(words)


2.3线程切换

[if !supportLists]1. [endif] flow {

[if !supportLists]2. [endif]        for (i in 1..5) {

[if !supportLists]3. [endif]            delay(100)

[if !supportLists]4. [endif]            emit(i)

[if !supportLists]5. [endif]        }

[if !supportLists]6. [endif]    }.map {

[if !supportLists]7. [endif]        it * it

[if !supportLists]8. [endif]    }.flowOn(Dispatchers.IO)

[if !supportLists]9. [endif]   .collect {

[if !supportLists]10. [endif]            println(it)

[if !supportLists]11. [endif]    }


类比rxjava

[if !supportLists]1. [endif]Observable.just("A", "B", "C")

[if !supportLists]2. [endif]            .subscribeOn(Schedulers.io())

[if !supportLists]3. [endif]            .observeOn(AndroidSchedulers.mainThread())

[if !supportLists]4. [endif]            .subscribe(object : Observer {

[if !supportLists]5. [endif]            override fun onSubscribe(d: Disposable) {

[if !supportLists]6. [endif]            }

[if !supportLists]7. [endif]            override fun onNext(t: Any) {

[if !supportLists]8. [endif]                print(t)

[if !supportLists]9. [endif]            }

[if !supportLists]10. [endif]            override fun onError(e: Throwable) {

[if !supportLists]11. [endif]            }

[if !supportLists]12. [endif]            override fun onComplete() {

[if !supportLists]13. [endif]            }

[if !supportLists]14. [endif]        });


这里面值得注意的一个点是

flow builder 和 map 操作符都会受到 flowOn 的影响。

而collect() 指定哪个线程,则需要看整个 flow 处于哪个 CoroutineScope 下。


2.4取消

这里和rxjava有些不同

如果 flow 是在一个挂起函数内被挂起了,那么 flow 是可以被取消的,否则不能取消。也就是说flow的取消,要搭配协程的取消

[if !supportLists]1. [endif]lifecycleScope.launch {

[if !supportLists]2. [endif]            withTimeoutOrNull(2500) {

[if !supportLists]3. [endif]                flow {

[if !supportLists]4. [endif]                    for (i in 1..5) {

[if !supportLists]5. [endif]                        delay(1000)

[if !supportLists]6. [endif]                        emit(i)

[if !supportLists]7. [endif]                    }

[if !supportLists]8. [endif]                }.collect {

[if !supportLists]9. [endif]                    println(it)

[if !supportLists]10. [endif]                }

[if !supportLists]11. [endif]            }

[if !supportLists]12. [endif]            println("Done")

[if !supportLists]13. [endif]        }


rxjava的取消

[if !supportLists]1. [endif]Observable.interval(0,1000, TimeUnit.MILLISECONDS)

[if !supportLists]2. [endif]            .observeOn(AndroidSchedulers.mainThread())

[if !supportLists]3. [endif]            .subscribe(object : Observer {

[if !supportLists]4. [endif]                var d : Disposable? = null

[if !supportLists]5. [endif]                override fun onSubscribe(d: Disposable) {

[if !supportLists]6. [endif]                    this.d = d

[if !supportLists]7. [endif]                }

[if !supportLists]8. [endif]                override fun onNext(t: Long) {

[if !supportLists]9. [endif]                    println(t)

[if !supportLists]10. [endif]                    if(t>10){

[if !supportLists]11. [endif]                        d?.dispose()

[if !supportLists]12. [endif]                    }

[if !supportLists]13. [endif]                }

[if !supportLists]14. [endif]                override fun onError(e: Throwable) {

[if !supportLists]15. [endif]                }

[if !supportLists]16. [endif]                override fun onComplete() {

[if !supportLists]17. [endif]                }

[if !supportLists]18. [endif]            })


2.5生命周期状态回调

[if !supportLists]1. [endif]lifecycleScope.launch {

[if !supportLists]2. [endif]            val time = measureTimeMillis {

[if !supportLists]3. [endif]                flow {

[if !supportLists]4. [endif]                    for (i in 0..3) {

[if !supportLists]5. [endif]                        emit(i.toString())

[if !supportLists]6. [endif]                    }

[if !supportLists]7. [endif]                    throw Exception("Test")

[if !supportLists]8. [endif]                }.onStart {

[if !supportLists]9. [endif]                    Log.d("xys", "Start Flow in ${Thread.currentThread().name}")

[if !supportLists]10. [endif]                }.onEach {

[if !supportLists]11. [endif]                    Log.d("xys", "emit value---$it")

[if !supportLists]12. [endif]                }.onCompletion {

[if !supportLists]13. [endif]                    Log.d("xys", "Flow Complete")

[if !supportLists]14. [endif]                }.collect {

[if !supportLists]15. [endif]                    Log.d("xys", "Result---$it")

[if !supportLists]16. [endif]                }

[if !supportLists]17. [endif]            }

[if !supportLists]18. [endif]            Log.d("xys", "Time---$time")

[if !supportLists]19. [endif]        }

[if !supportLists]五.[endif]重试

[if !supportLists]1. [endif]lifecycleScope.launch {

[if !supportLists]2. [endif]            flow {

[if !supportLists]3. [endif]                for (i in 0..3) {

[if !supportLists]4. [endif]                    emit(i.toString())

[if !supportLists]5. [endif]                }

[if !supportLists]6. [endif]            }.retryWhen { _, retryCount ->

[if !supportLists]7. [endif]                retryCount <= 3

[if !supportLists]8. [endif]            }.onStart {

[if !supportLists]9. [endif]                Log.d("xys", "Start Flow in ${Thread.currentThread().name}")

[if !supportLists]10. [endif]            }.onEach {

[if !supportLists]11. [endif]                Log.d("xys", "emit value---$it")

[if !supportLists]12. [endif]            }.onCompletion {

[if !supportLists]13. [endif]                Log.d("xys", "Flow Complete")

[if !supportLists]14. [endif]            }.collect {

[if !supportLists]15. [endif]                Log.d("xys", "Result---$it")

[if !supportLists]16. [endif]            }

[if !supportLists]17. [endif]        }


[if !supportLists]六.[endif]异常捕获

[if !supportLists]1. [endif]lifecycleScope.launch {

[if !supportLists]2. [endif]            flow {

[if !supportLists]3. [endif]                for (i in 0..3) {

[if !supportLists]4. [endif]                    emit(i.toString())

[if !supportLists]5. [endif]                }

[if !supportLists]6. [endif]                throw Exception("Test")

[if !supportLists]7. [endif]            }.retryWhen { _, retryCount ->

[if !supportLists]8. [endif]                retryCount <= 3

[if !supportLists]9. [endif]            }.onStart {

[if !supportLists]10. [endif]                Log.d("xys", "Start Flow in ${Thread.currentThread().name}")

[if !supportLists]11. [endif]            }.onEach {

[if !supportLists]12. [endif]                Log.d("xys", "emit value---$it")

[if !supportLists]13. [endif]            }.onCompletion {

[if !supportLists]14. [endif]                Log.d("xys", "Flow Complete")

[if !supportLists]15. [endif]            }.catch { error ->

[if !supportLists]16. [endif]                Log.d("xys", "Flow Error $error")

[if !supportLists]17. [endif]            }.collect {

[if !supportLists]18. [endif]                Log.d("xys", "Result---$it")

[if !supportLists]19. [endif]            }

[if !supportLists]20. [endif]        }

也可以写在onCompletion中

[if !supportLists]1. [endif]lifecycleScope.launch {

[if !supportLists]2. [endif]            flow {

[if !supportLists]3. [endif]                for (i in 0..3) {

[if !supportLists]4. [endif]                    emit(i.toString())

[if !supportLists]5. [endif]                }

[if !supportLists]6. [endif]                throw Exception("Test")

[if !supportLists]7. [endif]            }.retryWhen { _, retryCount ->

[if !supportLists]8. [endif]                retryCount <= 3

[if !supportLists]9. [endif]            }.onStart {

[if !supportLists]10. [endif]                Log.d("xys", "Start Flow in ${Thread.currentThread().name}")

[if !supportLists]11. [endif]            }.onEach {

[if !supportLists]12. [endif]                Log.d("xys", "emit value---$it")

[if !supportLists]13. [endif]            }.onCompletion {e->

[if !supportLists]14. [endif]                if(e!=null){

[if !supportLists]15. [endif]                    Log.d("xys", "Flow Exception ${e}")

[if !supportLists]16. [endif]                }else{

[if !supportLists]17. [endif]                    Log.d("xys", "Flow Complete")

[if !supportLists]18. [endif]                }

[if !supportLists]19. [endif]            }.collect {

[if !supportLists]20. [endif]                Log.d("xys", "Result---$it")

[if !supportLists]21. [endif]            }

[if !supportLists]22. [endif]        }












在我们了解了一下简单的常用用法之后,flow对于我们已经变得没有那么陌生了,我们来真正重新认识一下flow


重识Flow

3.1什么是冷流热流

冷流当数据被订阅的时候,发布者才开始执行发射数据流的代码。并且当有多个订阅者的时候,每一个订阅者何发布者都是一对一的关系,每个订阅者都会收到发布者完整的数据。热流无论有没有订阅者订阅,事件始终都会发生。当热流有多个订阅者时,发布者跟订阅者是一对多的关系,热流可以与多个订阅者共享信息。

示例:

[if !supportLists]1. [endif]fun simpleFlow2() = flow {

[if !supportLists]2. [endif]        println("Flow started")

[if !supportLists]3. [endif]        for (i in 1..3) {

[if !supportLists]4. [endif]            delay(1000)

[if !supportLists]5. [endif]            emit(i)

[if !supportLists]6. [endif]        }

[if !supportLists]7. [endif]

[if !supportLists]8. [endif]

[if !supportLists]9. [endif]    }

[if !supportLists]10. [endif]

[if !supportLists]11. [endif]    fun testCold(){

[if !supportLists]12. [endif]

[if !supportLists]13. [endif]        viewModelScope.launch {

[if !supportLists]14. [endif]            val flow = simpleFlow2()

[if !supportLists]15. [endif]            println("Calling collect...")

[if !supportLists]16. [endif]            flow.collect { value -> println(value) }

[if !supportLists]17. [endif]

[if !supportLists]18. [endif]            println("Calling collect again...")

[if !supportLists]19. [endif]            flow.collect { value -> println(value) }

[if !supportLists]20. [endif]        }

[if !supportLists]21. [endif]    }

[if !supportLists]22. [endif]    val _events = MutableSharedFlow()

[if !supportLists]23. [endif]

[if !supportLists]24. [endif]    //这里会每隔1s发送一个数据

[if !supportLists]25. [endif]    suspend fun foo1(){

[if !supportLists]26. [endif]        Log.i(TAG, "foo: 开始发送数据")

[if !supportLists]27. [endif]        Log.i(TAG, "foo: 开始发送A")

[if !supportLists]28. [endif]        _events.emit("A")

[if !supportLists]29. [endif]        Log.i(TAG, "foo: 结束发送A")

[if !supportLists]30. [endif]        delay(1000)

[if !supportLists]31. [endif]        Log.i(TAG, "foo: 开始发送B")

[if !supportLists]32. [endif]        _events.emit("B")

[if !supportLists]33. [endif]        Log.i(TAG, "foo: 结束发送B")

[if !supportLists]34. [endif]        delay(1000)

[if !supportLists]35. [endif]        Log.i(TAG, "foo: 开始发送C")

[if !supportLists]36. [endif]        _events.emit("C")

[if !supportLists]37. [endif]        Log.i(TAG, "foo: 结束发送C")

[if !supportLists]38. [endif]        Log.i(TAG, "foo: 结束发送数据")

[if !supportLists]39. [endif]    }

[if !supportLists]40. [endif]

[if !supportLists]41. [endif]    fun testHot(){

[if !supportLists]42. [endif]        //先开启协程,创建出SharedFlow

[if !supportLists]43. [endif]        viewModelScope.launch {

[if !supportLists]44. [endif]            foo1()

[if !supportLists]45. [endif]        }

[if !supportLists]46. [endif]        //立马进行收集

[if !supportLists]47. [endif]        viewModelScope.launch(Dispatchers.IO) {

[if !supportLists]48. [endif]            _events.collect {

[if !supportLists]49. [endif]                Log.i(TAG, "initData: A开始收集 $it")

[if !supportLists]50. [endif]            }

[if !supportLists]51. [endif]        }

[if !supportLists]52. [endif]        //延迟2秒再进行收集

[if !supportLists]53. [endif]        viewModelScope.launch {

[if !supportLists]54. [endif]            delay(2000)

[if !supportLists]55. [endif]            _events.collect {

[if !supportLists]56. [endif]                Log.i(TAG, "initData: B开始收集 $it")

[if !supportLists]57. [endif]            }

[if !supportLists]58. [endif]        }

[if !supportLists]59. [endif]    }


从控制台可以观察到打印的信息:

foo: 开始发送数据

foo: 开始发送A

foo: 结束发送A

foo: 开始发送B

initData: A开始收集 B

foo: 结束发送B

foo: 开始发送C

initData: A开始收集 C

initData: B开始收集 C

foo: 结束发送C

foo: 结束发送数据


热流之一SharedFlow

构造函数:

public fun MutableSharedFlow(

    replay: Int = 0,

    extraBufferCapacity: Int = 0,

    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND

): MutableSharedFlow

replay:表示当有新的订阅者collect时,发送几个已经发送过的数据给它,默认是0,即默认新订阅者不会获取到订阅之前的数据。

-extraBufferCapacity:表示减去replay,这个Flow还可以缓存多少个数据,默认是0;

-onBufferOverflow:表示缓存策略,即缓冲区满了后,如何处理,默认是挂起。z

热流之二喜闻乐见的StateFlow

官方就是希望用这个来替代LiveData,那么它到底是如何来替代LiveData的呢?


1)、特性

StateFlow是SharedFlow的子类,根据前面说的那么它是个热流,可以被多个观察者观察,同时可以设置它消失的scope以及条件。StateFlow只更新最新的数据,也就是它是一个replay为0的SharedFlow。

StateFlow里面和LiveData很像,都有个value来保存其值,也可以通过这个属性来获取或者设置它的值。

2)、使用

使用MutableStateFlow就和LiveData一样,不过它需要一个默认值。

也可以使用stateIn函数把一个Flow转成StateFlow,直接看这个函数:

val result = userId.mapLatest { newUserId ->

    repository.observeItem(newUserId)

}.stateIn(

    scope = viewModelScope,

    started = WhileSubscribed(5000),

    initialValue = Result.Loading

)

从这个最基本的使用,我们可以看出以下信息:

它的范围是viewModelScope,当viewModelScope结束时,流会停止。

当观察者都被移除后,超过5s,为了不浪费性能,流会停止。

会有个默认值,也就是这个initivalValue。

3)、观察

从上面来看,确实很像LiveData,不过LiveData有个很重要的功能就是具有生命周期感知性能力,在UI处于活跃状态时才会更新数据,那这个StateFlow在收集时并没有传递lifecycleOwner,那如何达到一样的效果呢?

直接推荐最佳写法:

onCreateView(...) {

    viewLifecycleOwner.lifecycleScope.launch {

        viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) {

            myViewModel.myUiState.collect { ... }

        }

    }

}


解释一下为什么是这样:

如果我们直接使用launch的话,可能会在onStop执行时这时来个数据更新,我的View根本没法更新,所以会造成错误,这里要达到和LiveData一样的效果在界面活跃时进行更新,所以这里启动协程就需要使用launchWhenStarted。

这个看起来没啥问题,不过我们前面不是有个WhileSubscribed这个优化项呢,表示没有观察者时5s后停止上游数据发射,那现在这个则无法做到,这时就需要使用repeatOnLifecycle

这个repeatOnLifecycle的作用就是当满足特定状态时启动协程,并且在生命周期退出这个状态时停止协程。那这就可以比如我APP退到后台,这时如果超过5S,则不会再订阅,协程终止,这时上游发射逻辑也会停止,提高性能。当APP再切换到前台时,会执行onStart,这时又会有观察者,这时上游逻辑又会开始,

3.2 flow的操作符

通过之前与rxjava对比,已经熟悉了一些的操作符。接下来再看一下还有哪些常用的操作符。

3.2.1创建操作符

Flow,flowOf,asFlow

3.2.2末端操作符

是在流上用于启动流收集的挂起函数。collect是最基础的末端操作符,但是还有另外一些更方便使用的末端操作符:

Collect,collectIndexed,collectLatest

toCollection、toSet、toList

这些操作符用于将Flow转换为Collection、Set和List。

launchIn

在指定的协程作用域中直接执行Flow。

last、lastOrNull、first、firstOrNull

3.2.3状态操作符

状态操作符不做任何修改,只是在合适的节点返回状态。

onStart:在上游生产数据前调用

onCompletion:在流完成或者取消时调用

onEach:在上游每次emit前调用

onEmpty:流中未产生任何数据时调用

catch:对上游中的异常进行捕获

retry、retryWhen:在发生异常时进行重试,retryWhen中可以拿到异常和当前重试的次数

3.2.4转换操作符

map、mapLatest、mapNotNull

3.2.5过滤操作符

filter、filterInstance、filterNot、filterNotNull

3.2.6组合操作符

combine、combineTransform

Merge、zip

3.2.7其他

[if !supportLists]四.[endif]Flow的应用

[if !supportLists]1.[endif]使用flow实现倒计时

[if !supportLists]1. [endif]fun countDownCoroutines(

[if !supportLists]2. [endif]        total: Int,

[if !supportLists]3. [endif]        scope: CoroutineScope,

[if !supportLists]4. [endif]        onTick: (Int) -> Unit,

[if !supportLists]5. [endif]        onStart: (() -> Unit)? = null,

[if !supportLists]6. [endif]        onFinish: (() -> Unit)? = null,

[if !supportLists]7. [endif]    ): Job {

[if !supportLists]8. [endif]        return flow {

[if !supportLists]9. [endif]            for (i in total downTo 0) {

[if !supportLists]10. [endif]                emit(i)

[if !supportLists]11. [endif]                delay(1000)

[if !supportLists]12. [endif]            }

[if !supportLists]13. [endif]        }.flowOn(Dispatchers.Main)

[if !supportLists]14. [endif]            .onStart { onStart?.invoke() }

[if !supportLists]15. [endif]            .onCompletion { onFinish?.invoke() }

[if !supportLists]16. [endif]            .onEach { onTick.invoke(it) }

[if !supportLists]17. [endif]            .launchIn(scope)

[if !supportLists]18. [endif]    }



[if !supportLists]1. [endif]countDownCoroutines(5, lifecycleScope,

[if !supportLists]2. [endif]            onTick = { second ->

[if !supportLists]3. [endif]                tvSkip.text = "${second}s跳过"

[if !supportLists]4. [endif]            }, onStart = {

[if !supportLists]5. [endif]                // 倒计时开始

[if !supportLists]6. [endif]                if (show) {

[if !supportLists]7. [endif]                    tvSkip.visibility = View.VISIBLE

[if !supportLists]8. [endif]                }

[if !supportLists]9. [endif]            }, onFinish = {

[if !supportLists]10. [endif]                // 倒计时结束,重置状态

[if !supportLists]11. [endif]                if (show) {

[if !supportLists]12. [endif]                    enterHomePage()

[if !supportLists]13. [endif]                }

[if !supportLists]14. [endif]            })


[if !supportLists]2.[endif]flow做搜索限流

[if !supportLists]1. [endif]lifecycleScope.launchWhenCreated {

[if !supportLists]2. [endif]            _etState.

[if !supportLists]3. [endif]            sample(500).filter {

[if !supportLists]4. [endif]                it.isNotEmpty()

[if !supportLists]5. [endif]            }.collect {

[if !supportLists]6. [endif]                viewModel.searchArticles(it)

[if !supportLists]7. [endif]            }

[if !supportLists]8. [endif]        }


[if !supportLists]3.[endif]flow结合room

[if !supportLists]1. [endif]@Dao

[if !supportLists]2. [endif]interface UserDao {

[if !supportLists]3. [endif]

[if !supportLists]4. [endif]    //返回插入行 ID 的Insert DAO 方法永远不会返回 -1,因为即使存在冲突,此策略也将始终插入行

[if !supportLists]5. [endif]    @Insert(onConflict = OnConflictStrategy.REPLACE)

[if !supportLists]6. [endif]    suspend fun insert(user: User)

[if !supportLists]7. [endif]

[if !supportLists]8. [endif]    @Query("SELECT * FROM user")

[if !supportLists]9. [endif]    fun getAll(): Flow>

[if !supportLists]10. [endif]

[if !supportLists]11. [endif]}

[if !supportLists]1. [endif] fun getAll(): Flow> {

[if !supportLists]2. [endif]        return AppDatabase.getInstance(getApplication())

[if !supportLists]3. [endif]            .userDao()

[if !supportLists]4. [endif]            .getAll()

[if !supportLists]5. [endif]            .catch { e -> e.printStackTrace() }

[if !supportLists]6. [endif]            .flowOn(Dispatchers.IO) //切换上下文为IO异步

[if !supportLists]7. [endif]    }


[if !supportLists]4.[endif]flow结合Retrofit

[if !supportLists]1. [endif] viewModelScope.launch {

[if !supportLists]2. [endif]            flow {

[if !supportLists]3. [endif]                //这里就是通过Retrofit从服务器拿到对应key过滤后的文章内容

[if !supportLists]4. [endif]                val map = mapOf("word" to key, "search_match_type" to 1.toString(), "page_size" to 30)

[if !supportLists]5. [endif]                val list = RetrofitClient.articleApi.getSearchResult(map as Map)

[if !supportLists]6. [endif]                //将对应数据发射出去

[if !supportLists]7. [endif]                emit(list)

[if !supportLists]8. [endif]            }.flowOn(Dispatchers.IO)

[if !supportLists]9. [endif]                .catch {

[if !supportLists]10. [endif]                        e -> e.printStackTrace()

[if !supportLists]11. [endif]                }

[if !supportLists]12. [endif]                .collect {

[if !supportLists]13. [endif]                    //这里收到对应数据,更新对应的LiveData数据

[if !supportLists]14. [endif]                    articles.setValue(it.article?.items)

[if !supportLists]15. [endif]                }

[if !supportLists]16. [endif]        }


代码已经托管到github

[email protected]:mm46468648/flowTest.git


补充小知识:

一.Navigation+BottomNavigationView的用法

[if !supportLists]1.[endif]创建nav_graph

[if !supportLists]2.[endif]创建bottom_nav_menu

[if !supportLists]3.[endif]布局

[if !supportLists]1. [endif]

[if !supportLists]2. [endif]        android:id="@+id/fragment"

[if !supportLists]3. [endif]        android:name="androidx.navigation.fragment.NavHostFragment"

[if !supportLists]4. [endif]        android:layout_width="match_parent"

[if !supportLists]5. [endif]        android:layout_height="match_parent"

[if !supportLists]6. [endif]        app:defaultNavHost="true"

[if !supportLists]7. [endif]        app:layout_constraintBottom_toBottomOf="parent"

[if !supportLists]8. [endif]        app:layout_constraintLeft_toLeftOf="parent"

[if !supportLists]9. [endif]        app:layout_constraintRight_toRightOf="parent"

[if !supportLists]10. [endif]        app:layout_constraintTop_toTopOf="parent"

[if !supportLists]11. [endif]        app:navGraph="@navigation/nav_graph" />

[if !supportLists]12. [endif]

[if !supportLists]13. [endif]

[if !supportLists]14. [endif]    

[if !supportLists]15. [endif]        android:id="@+id/bottom_nav_view"

[if !supportLists]16. [endif]        android:layout_width="match_parent"

[if !supportLists]17. [endif]        android:layout_height="wrap_content"

[if !supportLists]18. [endif]        app:layout_constraintBottom_toBottomOf="parent"

[if !supportLists]19. [endif]        app:layout_constraintLeft_toLeftOf="parent"

[if !supportLists]20. [endif]        app:layout_constraintRight_toRightOf="parent"

[if !supportLists]21. [endif]        app:menu="@menu/bottom_nav_menu" />

[if !supportLists]4.[endif]关联:

[if !supportLists]1. [endif] //fragment的容器视图,navHost的默认实现——NavHostFragment

[if !supportLists]2. [endif]val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment

[if !supportLists]3. [endif]

[if !supportLists]4. [endif] //管理应用导航的对象

[if !supportLists]5. [endif]val navController = navHostFragment.navController

[if !supportLists]6. [endif]

[if !supportLists]7. [endif]//fragment与BottomNavigationView的交互交给NavigationUI

[if !supportLists]8. [endif]bottom_nav_view.setupWithNavController(navController)

注意:

menu中的id和nav_garph中的id一定要对应,否则会找不到需要展示的fragment

参考:

https://juejin.cn/post/6898234461361307655/

你可能感兴趣的:(协程之Flow)