# 一、 Flow 與 Channel 的相互轉換

## 1.1 Flow 轉換爲 Channel

### 1.1.1 ChannelFlow

``````@InternalCoroutinesApi
public abstract class ChannelFlow<T>(
// upstream context
@JvmField public val context: CoroutineContext,
// buffer capacity between upstream and downstream context
@JvmField public val capacity: Int,
// buffer overflow strategy
@JvmField public val onBufferOverflow: BufferOverflow
) : FusibleFlow<T> {
...

public open fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> =
scope.produce(context, produceCapacity, onBufferOverflow, start = CoroutineStart.ATOMIC, block = collectToFun)

...

}
``````

ChannelFlow 是一個抽象類，並且被標記爲內部 Api，不應該在外部代碼直接使用。

### 1.1.2 produceIn —— 將 Flow 轉換爲單播式 Channel

produceIn()轉換創建了一個produce 協程來 collect 原Flow，因此該produce協程應該在恰當時候被關閉或者取消。轉換後的 Channel 擁有處理背壓的能力。其基本使用方式如下：

``````fun main() = runBlocking {
val flow = flow<Int> {
repeat(5) {
delay(500)
emit(it)
}
}

val produceIn = flow.produceIn(this)
for (ele in produceIn) {
println(ele)
}
}
``````

``````0
1
2
3
4
``````

``````@FlowPreview
public fun <T> Flow<T>.produceIn(scope: CoroutineScope): ReceiveChannel<T> = asChannelFlow().produceImpl(scope)
``````

``````public fun <T> Flow<T>.broadcastIn(
scope: CoroutineScope,
start: CoroutineStart = CoroutineStart.LAZY
// Backwards compatibility with operator fusing
val channelFlow = asChannelFlow()
val capacity = when (channelFlow.onBufferOverflow) {
BufferOverflow.SUSPEND -> channelFlow.produceCapacity
BufferOverflow.DROP_OLDEST -> Channel.CONFLATED
BufferOverflow.DROP_LATEST ->
throw IllegalArgumentException("Broadcast channel does not support BufferOverflow.DROP_LATEST")
}
return scope.broadcast(channelFlow.context, capacity = capacity, start = start) {
collect { value ->
send(value)
}
}
}
``````

## 1.2 Channel 轉換爲 Flow

### 1.2.1 consumeAsFlow/receiveAsFlow —— 將單播式 Channel 轉換爲 Flow

``````fun main() = runBlocking<Unit> {
val testChannel = Channel<String>()

launch {
testFlow.collect {
println(it)
}
}

delay(100)
testChannel.send("hello")
delay(100)
testChannel.send("coroutine")
delay(100)

testChannel.close() // 注意只有 Channel 關閉了，協程才能結束
}
``````

``````public fun <T> ReceiveChannel<T>.consumeAsFlow(): Flow<T> = ChannelAsFlow(this, consume = true)

private class ChannelAsFlow<T>(
private val consume: Boolean,
context: CoroutineContext = EmptyCoroutineContext,
capacity: Int = Channel.OPTIONAL_CHANNEL,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
) : ChannelFlow<T>(context, capacity, onBufferOverflow) {}
``````

`consumeAsFlow``receiveAsFlow` 都是調用 `ChannelAsFlow` 將 Channel 轉換成了 ChannelFlow，所以轉換結果是熱流。但它們傳遞的第二個參數 `consume` 不一樣。兩者區別如下：

• 使用 `consumeAsFlow()` 轉換成的 Flow 只能有一個收集器收集，如果有多個收集器收集，將會拋出如下異常：
``````Exception in thread "main" java.lang.IllegalStateException: ReceiveChannel.consumeAsFlow can be collected just once
``````
• 使用 `receiveAsFlow()` 轉換成的 Flow 可以有多個收集器收集，但是保證每個元素只能被一個收集器收集到，即單播式。

### 1.2.2 asFlow —— 將廣播式 BroadcastChannel 轉換爲 Flow

BroadcastChannel 調用 `asFlow()` 方法即可將其轉換爲 Flow。

# 二、SharedIn —— 將冷數據流轉換爲熱數據流

``````public fun <T> Flow<T>.shareIn(
scope: CoroutineScope,
started: SharingStarted,
replay: Int = 0
): SharedFlow<T> {
...
}
``````

• CoroutineScope 用於共享數據流的 CoroutineScope。此作用域函數的生命週期應長於任何使用方，以使共享數據流在足夠長的時間內保持活躍狀態
• replay 每個新收集器的數據項數量
• started “啓動” 方式

``````public fun interface SharingStarted {
public companion object {
// 立即啓動，並且永遠不會自動停止
- public val Eagerly: SharingStarted = StartedEagerly()

// 第一個訂閱者註冊後啓動，並且永遠不會自動停止
- public val Lazily: SharingStarted = StartedLazily()

// 第一個訂閱者註冊後啓動，最後一個訂閱者取消註冊後停止
- public fun WhileSubscribed(
stopTimeoutMillis: Long = 0,
replayExpirationMillis: Long = Long.MAX_VALUE
): SharingStarted =
StartedWhileSubscribed(stopTimeoutMillis, replayExpirationMillis)
}
}
``````

# 三、callbackFlow —— 將基於回調的 API 轉換爲數據流

Kotlin 協程和 Flow 可以完美解決異步調用、線程切換的問題。設計接口時，可以類似 Rxjava 那樣，避免使用回調。比如 Room 在內的很多庫已經支持將協程用於數據流操作。對於那些還不支持的庫，也可以將任何基於回調的 API 轉換爲協程。

`callbackFlow` 是一個數據流構建器，可以將基於回調的 API 轉換爲數據流。

## 3.1 callbackFlow 的使用

``````interface Result<T>  {
fun onSuccess(t: T)
fun onFail(msg: String)
}

fun getApi(res: Result<String>) {
res.onSuccess("hello")
}.start()
}
``````

getApi() 是一個基於回調設計的接口。如何使用 callbackFlow 轉換爲 Flow 呢？

``````fun getApi(): Flow<String> = callbackFlow {
val res = object: Result<String> {
override fun onSuccess(t: String) {
trySend(t)
close(Exception("completion"))
}

override fun onFail(msg: String) {
}
}
getApi(res)

// 一定要調用骨氣函數 awaitClose， 保證流一直運行。在`awaitClose` 中移除 API 訂閱，防止任務泄漏。
awaitClose {
println("close")
}
}

// 新的 Api 使用方式
fun main() = runBlocking<Unit> {
getApi().flowOn(Dispatchers.IO)
.catch {
println("getApi fail, cause: \${it.message}")
}.onCompletion {
println("onCompletion")
}.collect {
}
}
``````

## 3.2 callbackFlow 實戰

Android 開發中有一個常見的場景：輸入關鍵字進行查詢。比如有個 EditText，輸入文字後，基於輸入的文字進行網絡請求或者數據庫查詢。

``````fun <T>query(keyWord: String): Flow<T> {
return flow {
//...
}
}
``````

``````fun textChangeFlow(editText: EditText): Flow<String> = callbackFlow {
val watcher = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {

}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}

override fun afterTextChanged(s: Editable?) {
s?.let {
trySend(s.toString())
}

}
}
awaitClose {
editText.removeTextChangedListener(watcher)
}
}
``````

``````scope.launch{
textChangeFlow(editText)
.debounce(300) // 防抖處理
.flatMapLatest { keyWord ->  // 只對最新的值進行搜索
flow {
<String>query(keyWord)
}
}.collect {
// ... 處理最終結果
}
}
``````