(Original) Use of Flow data flow

Preface

This article mainly introduces some basic usage methods of Flow.
It also introduces how to use Flow to request network data.
Let’s get started!

What is Flow

Flow translated means "flow".
For example, in nature, common water flows
from high to low.
In the computer world, the so-called "flow"
actually refers to the flow of data, that is, the flow of data
from high to low. The process of raw data, processing, and final use.
For example, getting a json, converting it into a bean
, and then screening and filtering
to get the final data to be used .
This process is called the flow of data
, as shown below:
Insert image description here
In order to process this process
We can use the tool Flow

Use of Flow flow

Simple to use

To use Flow, you first need to import coroutine related tool classes:

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7-mpp-dev-11'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7-mpp-dev-11'

The simplest Flow usage:

suspend fun flow1() {
    
    
    flow<Int> {
    
    
        (0..4).forEach {
    
    
            emit(it)//生产者发送数据
        }
    }.collect {
    
    
        Log.d("flow1", "it:$it")//消费者处理数据
    }
}

Let's analyze this code:
1: (0...4) is a list of 0-4, this is the original data
2: emit actually sends the original data out
3: collect is used to collect the sent original data, and internally Printed the received data
4: The code block wrapped by the flow { } function is responsible for sending data. This function returns a Flow object.
Note: Flow flow is a "cold flow",
which means that the method body in the flow will not be generated until collect is called. When called,
if there is no collect method, no matter how it is emitted, the data will not be sent.

stream operator

Flow provides us with a series of APIs for data operations,
which we call "operators"
. They are generally divided into "flow builders", "intermediate operators" and "terminal operators".
Flow builders are generally used to build Flow. The intermediate operators of stream objects
only pre-define some operations on the stream,
such as filtering, conversion, etc.
, and do not actively trigger action execution.
The terminal operators are the final processing of the stream
. For example, collect is the terminal operator.
Here are some operators.

Stream builder

flowof

You can emit the variable length parameters in flowOf one by one

flowOf(1, 2, 5, 4).collect {
    
    
        println(it)
}

asFlow

flowOf can convert a collection into flow emission

suspend fun asFlowM(){
    
    
    listOf(1,2,9,0,8).asFlow().collect{
    
    
        println(it)
    }
}

intermediate operator

map

We can perform some transition operations in the map.
For example, in this example, the data sent by the producer is *9, and then transmitted to the consumer
. It is worth mentioning that we can perform asynchronous operations in the map
. Note that this map and It doesn't matter if it's a collection, don't be misled

suspend fun mapM(){
    
    
    (1..9).asFlow().map {
    
    
        it*9
    }.collect{
    
    
        println(it)
    }
}

transform

transform mainly emphasizes type conversion

(1..3).asFlow() // 一个请求流
        //transform中的泛型<Int,String> 表示将Int类型转换为String后,继续发射
        .transform<Int, String> {
    
     request ->
            emit("transform Int to String $request")
        }
        .collect {
    
     response -> println(response) }

take

The length limit operator take can limit the amount of data we want to consume, see the code

(1..9).asFlow().take(3).collect {
    
    
        println(it)
}

conflate

When the producer transmits data faster than the consumer, the consumer can only get the latest data transmitted by the producer.

suspend fun conflate(){
    
    
    flow<Int> {
    
    
        (1..9).forEach {
    
    
            delay(100)
            emit(it)
        }
    }.conflate().collect {
    
    
        delay(300)
        println(it)
    }
}

For example, the above code, because of the existence of conflate, the output is as follows:

1
3
6
9

If no conflate exists, the output is as follows:

1
2
3
4
5
6
7
8
9

Comparing the two, it is obvious that the example of using conflate has ignored a lot of data that cannot be processed immediately.

collectLast

The meaning of this operator: If the producer data has been transmitted and the consumer has not finished processing the previous data, then it will directly stop processing the previous data and directly process the latest data.


suspend fun collectLastM(){
    
    
    flow<Int> {
    
    
        (1..9).forEach {
    
    
            delay(100)
            emit(it)
        }
    }.collectLatest {
    
    
        delay(800)
        println(it)
    }
}

For example, the output of this example is 9

zip

The zip operator can merge two streams into one stream, and then process and combine the data emitted by the two streams in the zip method before sending it to the consumer.
If the lengths of the two streams are inconsistent, the shorter stream will be processed:
1. The length of the two streams is the same, both are 3

suspend fun zipM(){
    
    
    val flow1 = (1..3).asFlow()
    val flow2 = flowOf("李白","杜甫","安安安安卓")
    flow1.zip(flow2){
    
    a,b->
        "$a : $b"
    }.collect {
    
    
        println(it)
    }
}

Output:

1 : 李白
2 : 杜甫
3 : 安安安安卓

Let’s change the above code and change the length of flow1 to 5

val flow1 = (1..5).asFlow()

View the output:

1 : 李白
2 : 杜甫
3 : 安安安安卓

So to verify our initial conclusion, two streams with different lengths are zip-merged, and the data length output by the consumer is the length of the shorter stream.

combine

We are clear about the shortcomings of zip in the previous section, that is, when the lengths of the two streams are not equal, the later part of the longer stream cannot be output.

Then combine is used to solve the shortcomings of zip (it's hard to say it's a shortcoming, it's just that the application scenarios are different, you can think of it as a shortcoming)


suspend fun combineM(){
    
    
    val flowA = (1..5).asFlow()
    val flowB = flowOf("李白","杜甫","安安安安卓")
    flowA.combine(flowB){
    
    a,b->
        "$a : $b"
    }.collect {
    
    
        println(it)
    }
}

Output log:

1 : 李白
2 : 李白
2 : 杜甫
3 : 杜甫
3 : 安安安安卓
4 : 安安安安卓
5 : 安安安安卓

Our two streams, the length of the numeric stream is 5 and the length of the string stream is 3.

Simple logical analysis of the effect achieved:

flow发射1,flow2发射 ”李白“ ,打印:1 : 李白
flow发射2,flow2未发射数据  ,打印:2 : 李白
flow未发射,flow2发射 ”杜甫“ ,2 : 杜甫
flow发射3,flow2未发射 ,打印:3 : 杜甫
flow未发射,flow2发射 ”安安安安卓“ ,打印:3 : 安安安安卓
flow发射4,flow2发射完成  ,打印:4 : 安安安安卓
flow发射5,flow2发射完成  ,打印:5 : 安安安安卓

onCompletion

Use onCompletion to send a value when the stream is completed.

 flowOf(1, 23, 5, 3, 4).onCompletion {
    
    
        println("流操作完成")
        emit(12344)//这里不返回值也没关系
    }.collect {
    
    
        println(it)
    }

Output:

1
23
5
3
4
流操作完成
12344

terminal operator

toList

The data will be consumed into a List list

suspend fun toList():List<Int> {
    
    
   return (1..9).asFlow().filter {
    
     it % 2 == 0 }.toList()
}

toSet

同toList

frist

Get the first element

suspend fun firstM(): Int {
    
    
    return (2..9).asFlow().filter {
    
     it % 2 == 1 }.first()
}

reduce

The lambda expression of reduce will provide the operation formula for calculation.

In the lambda expression of reduce, the current value to be consumed and the previously calculated value can be calculated to obtain a new value and return it. The final value is returned after all values ​​are consumed.

suspend fun reduceM():Int {
    
    
    return (1..9).asFlow().reduce {
    
     accumulator, value ->
        println("$accumulator : $value")
        accumulator + value
    }
}

buffer

The buffer can cache producer data and will not be blocked by consumers.

suspend fun bufferM() {
    
    
    val startMillis = System.currentTimeMillis()
    flow<Int> {
    
    
        (1..3).forEach {
    
    
            delay(300)
            emit(it)
        }
    }.buffer(4)
        .collect {
    
    
            delay(400)
            println(it)
            println("时间已经过了${
      
      System.currentTimeMillis() - startMillis}")
        }
}

Code execution print log:

1
时间已经过了745
2
时间已经过了1148
3
时间已经过了1552

If we do not use buffer, then the total duration should be 2100ms. If
we use buffer, the total duration is: 1552=300+400*3
. Therefore, when using buffer, the producer can transmit data concurrently without being blocked by the consumer.

Flow exception

Use try/catch to wrap the stream.
We can use try/catch to collect stream exceptions, but this method is not recommended.
Use the catch operator of flow to process the stream. It
is more elegant to use the catch operator of flow to handle exceptions.
However, catch also has shortcomings. Only producer exceptions can be caught, but consumer exceptions cannot be caught.


suspend fun trycatch() {
    
    
    flow<Int> {
    
    
        (1..3).forEach {
    
    
            if (it == 2) {
    
    //故意抛出一个异常
                throw NullPointerException("强行空指针,嘿嘿嘿嘿")
            }
            emit(it)
        }
    }.catch {
    
    e->
        e.printStackTrace()
        emit(-1)//异常的情况下发射一个-1
    }.collect{
    
    
        println(it)
    }
}

How to handle consumer exceptions.
Try throwing an exception in the consumer to see if it can be caught.

 flow<Int> {
    
    
        for (i in 1..3) {
    
    

            emit(i)
        }
    }.catch {
    
    
        emit(-1)
    }.collect {
    
    
        if(it==2){
    
    //在消费者中抛出数据
            throw IllegalArgumentException("数据不合法")
        }
        println(it)
    }

Output:

1
Exception in thread "main" java.lang.IllegalArgumentException: 数据不合法
	at HahaKt$consumerCatch$$inlined$collect$1.emit(Collect.kt:138)

Put the exception code in onEach to catch the exception

suspend fun consumerCatch() {
    
    
    flow<Int> {
    
    
        for (i in 1..3) {
    
    

            emit(i)
        }
    }.onEach {
    
    
        if (it == 2) {
    
    //与上面的不同,在消费之前先用onEach处理一下
            throw IllegalArgumentException("数据不合法")
        }
    }.catch {
    
    
        emit(-1)
    }.collect {
    
    
        println(it)
    }
}

Output:

1
-1

Flow requests network data

The above describes how Flow processes data.
We can use it with Retrofit in our projects
. For example, we first define a request with the return type as Flow data flow.

    @POST(ServerName.BS_API + "test/url")
	fun getMessage(): Flow<List<Bean>>

Use viewModelScope and viewmodel life cycle binding
and then filter the returned data to get the final required data:

        viewModelScope.launch {
    
    
            repository.getMessage()
                ?.filter {
    
    
                    it.isNotEmpty()
                }
                ?.collect {
    
    
                    //得到数据
                }
        }

In this way, we can use Flow to perform various complex processing on the returned data.
Of course, these are still basic usages. You can make more encapsulations based on actual business conditions.

Relevant information

Detailed explanation of Kotlin Flow Kotlin
Flow, where will you flow?
The official flow address
uses Kotlin Flow to build a data flow "pipeline"

Guess you like

Origin blog.csdn.net/Android_xiong_st/article/details/128571004