Flow って、生きてるのか、死んでるのか、何が流れているのか、とか、慣れるまでは分かりづらいですね!
【Kotlin】ぼくらは Flow の マーブルダイアグラム を見るのか。
各タイミングでログを吐かせて確認する
Flow の中間オペレーターである onStart() や onEach() などを利用してログを吐かせて流れを確認します。
onStart()
collect を開始する前に、与えられたアクションを呼び出す Flow を返します。
onEach()
上流側 Flow からのそれぞれの値が下流へ emit される前に指定されたアクションを実行します。
onCompletion()
Flow が完了(またはキャンセル)された後に指定されたアクションを呼び出します。
Flow を返し、アクションの原因のパラメーターとして、キャンセル、例外、または失敗を渡します。
onEmpty()
Flow が要素を emit せずに完了したとき、指定されたアクションを実行します。
onSubscription()
この SharedFlow が collect を開始した(サブスクリプション)後、与えられたアクションを呼び出す Flow を返します。
まとめて整理して、エクステンションにしておきます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fun <T> Flow<T>.log(label: String, tag: String = "###"): Flow<T> {
val prefix = "$tag [$label] ${this.javaClass.simpleName}"
return this
.onStart {
Timber.d("$prefix onStart()")
}
.onEach { value ->
Timber.d("$prefix onEach() value = $value")
}
.onCompletion { cause ->
Timber.d("$prefix onCompletion() cause = $cause")
}
.onEmpty {
Timber.d("$prefix onEmpty()")
}
}
■ 実際にやってみる
よくある flatMapLatest を使った RecyclerView で利用するアイテムのリスト呼び出しで実際にやってみます。
private val pokemonFetchingIndex: MutableStateFlow< Int> = MutableStateFlow ( 0 )
private val pokemonListFlow = pokemonFetchingIndex
. log ( "INDEX" )
. flatMapLatest { page ->
mainRepository. fetchPokemonList (
page = page,
onStart = { isLoading = true } ,
onComplete = { isLoading = false } ,
onError = { toastMessage = it }
) . log ( "LIST" )
}
@MainThread
fun fetchNextPokemonList ( ) {
if ( ! isLoading) {
pokemonFetchingIndex. value++
}
}
pokedex:MainViewModel.kt#L47-L54
以下のようにログが出力され、ホットな Flow である[INDEX]が変化するたびに、flatMapLatest ブロック内の Flow [LIST]が onStart → onEach → onCompletion と流れていく様子が分かります。
19:54:52.779 D: ### [INDEX] StateFlowImpl onStart()
19:54:52.781 D: ### [INDEX] StateFlowImpl onEach() value = 0
19:54:52.788 D: ### [LIST] ChannelFlowOperatorImpl onStart()
19:54:53.354 D: ### [LIST] ChannelFlowOperatorImpl onEach() value = [Pokemon(page=0, name=bulbasaur, url=https://pokeapi.co/api/v2/pokemon/1/), Pokemon(page=0, name=ivysaur, url=https://pokeapi.co/api/v2/pokemon/2/), Pokemon(page=0, name=venusaur, url=https://pokeapi.co/api/v2/pokemon/3/), Pokemon(page=0, name=charmander, url=https://pokeapi.co/api/v2/pokemon/4/), Pokemon(page=0, name=charmeleon, url=https://pokeapi.co/api/v2/pokemon/5/), Pokemon(page=0, name=charizard, url=https://pokeapi.co/api/v2/pokemon/6/), Pokemon(page=0, name=squirtle, url=https://pokeapi.co/api/v2/pokemon/7/), Pokemon(page=0, name=wartortle, url=https://pokeapi.co/api/v2/pokemon/8/), Pokemon(page=0, name=blastoise, url=https://pokeapi.co/api/v2/pokemon/9/), Pokemon(page=0, name=caterpie, url=https://pokeapi.co/api/v2/pokemon/10/), Pokemon(page=0, name=metapod, url=https://pokeapi.co/api/v2/pokemon/11/), Pokemon(page=0, name=butterfree, url=https://pokeapi.co/api/v2/pokemon/12/), Pokemon(page=0, name=weedle, url=https://pokeapi.co/api/v2/pokemon/13/), Pokemon(page=0, name=kakuna, url=https://pokeapi.co/api/v2/pokemon/14/), Pokemon(page=0, name=beedrill, url=https://pokeapi.co/api/v2/pokemon/15/), Pokemon(page=0, name=pidgey, url=https://pokeapi.co/api/v2/pokemon/16/), Pokemon(page=0, name=pidgeotto, url=https://pokeapi.co/api/v2/pokemon/17/), Pokemon(page=0, name=pidgeot, url=https://pokeapi.co/api/v2/pokemon/18/), Pokemon(page=0, name=rattata, url=https://pokeapi.co/api/v2/pokemon/19/), Pokemon(page=0, name=raticate, url=https://pokeapi.co/api/v2/pokemon/20/)]
19:54:53.362 D: ### [LIST] ChannelFlowOperatorImpl onCompletion() cause = null
19:54:54.553 D: ### [INDEX] StateFlowImpl onEach() value = 1
19:54:54.556 D: ### [LIST] ChannelFlowOperatorImpl onStart()
19:54:54.855 D: ### [LIST] ChannelFlowOperatorImpl onEach() value = [Pokemon(page=0, name=bulbasaur, url=https://pokeapi.co/api/v2/pokemon/1/), Pokemon(page=0, name=ivysaur, url=https://pokeapi.co/api/v2/pokemon/2/), Pokemon(page=0, name=venusaur, url=https://pokeapi.co/api/v2/pokemon/3/), Pokemon(page=0, name=charmander, url=https://pokeapi.co/api/v2/pokemon/4/), Pokemon(page=0, name=charmeleon, url=https://pokeapi.co/api/v2/pokemon/5/), Pokemon(page=0, name=charizard, url=https://pokeapi.co/api/v2/pokemon/6/), Pokemon(page=0, name=squirtle, url=https://pokeapi.co/api/v2/pokemon/7/), Pokemon(page=0, name=wartortle, url=https://pokeapi.co/api/v2/pokemon/8/), Pokemon(page=0, name=blastoise, url=https://pokeapi.co/api/v2/pokemon/9/), Pokemon(page=0, name=caterpie, url=https://pokeapi.co/api/v2/pokemon/10/), Pokemon(page=0, name=metapod, url=https://pokeapi.co/api/v2/pokemon/11/), Pokemon(page=0, name=butterfree, url=https://pokeapi.co/api/v2/pokemon/12/), Pokemon(page=0, name=weedle, url=https://pokeapi.co/api/v2/pokemon/13/), Pokemon(page=0, name=kakuna, url=https://pokeapi.co/api/v2/pokemon/14/), Pokemon(page=0, name=beedrill, url=https://pokeapi.co/api/v2/pokemon/15/), Pokemon(page=0, name=pidgey, url=https://pokeapi.co/api/v2/pokemon/16/), Pokemon(page=0, name=pidgeotto, url=https://pokeapi.co/api/v2/pokemon/17/), Pokemon(page=0, name=pidgeot, url=https://pokeapi.co/api/v2/pokemon/18/), Pokemon(page=0, name=rattata, url=https://pokeapi.co/api/v2/pokemon/19/), Pokemon(page=0, name=raticate, url=https://pokeapi.co/api/v2/pokemon/20/), Pokemon(page=1, name=spearow, url=https://pokeapi.co/api/v2/pokemon/21/), Pokemon(page=1, name=fearow, url=https://pokeapi.co/api/v2/pokemon/22/), Pokemon(page=1, name=ekans, url=https://pokeapi.co/api/v2/pokemon/23/), Pokemon(page=1, name=arbok, url=https://pokeapi.co/api/v2/pokemon/24/), Pokemon(page=1, name=pikachu, url=https://pokeapi.co/api/v2/pokemon/25/), Pokemon(page=1, name=raichu, url=https://pokeapi.co/api/v2/pokemon/26/), Pokemon(page=1, name=sandshrew, url=https://pokeapi.co/api/v2/pokemon/27/), Pokemon(page=1, name=sandslash, url=https://pokeapi.co/api/v2/pokemon/28/), Pokemon(page=1, name=nidoran-f, url=https://pokeapi.co/api/v2/pokemon/29/), Pokemon(page=1, name=nidorina, url=https://pokeapi.co/api/v2/pokemon/30/), Pokemon(page=1, name=nidoqueen, url=https://pokeapi.co/api/v2/pokemon/31/), Pokemon(page=1, name=nidoran-m, url=https://pokeapi.co/api/v2/pokemon/32/), Pokemon(page=1, name=nidorino, url=https://pokeapi.co/api/v2/pokemon/33/), Pokemon(page=1, name=nidoking, url=https://pokeapi.co/api/v2/pokemon/34/), Pokemon(page=1, name=clefairy, url=https://pokeapi.co/api/v2/pokemon/35/), Pokemon(page=1, name=clefable, url=https://pokeapi.co/api/v2/pokemon/36/), Pokemon(page=1, name=vulpix, url=https://pokeapi.co/api/v2/pokemon/37/), Pokemon(page=1, name=ninetales, url=https://pokeapi.co/api/v2/pokemon/38/), Pokemon(page=1, name=jigglypuff, url=https://pokeapi.co/api/v2/pokemon/39/), Pokemon(page=1, name=wigglytuff, url=https://pokeapi.co/api/v2/pokemon/40/)]
19:54:54.872 D: ### [LIST] ChannelFlowOperatorImpl onCompletion() cause = null
【MVVM】 Kotlin Flow で使える5つの利用パターン
Playing with Kotlin Flows. Nowadays we are listening to words… | by Davide Cerbo | Medium
skydoves/Pokedex: Pokedex demonstrates modern Android development with Hilt, Material Motion, Coroutines, Flow, Jetpack (Room, ViewModel) based on MVVM architecture.