FlowオペレータshareInとstateInの使用上の注意事項

8544 ワード

Flow.shareInおよびFlow.stateInオペレータは、上流のコールドデータストリームからの情報を複数の収集者にブロードキャストすることができるコールドストリームをホットストリームに変換することができる.この2つのオペレータは、通常、パフォーマンスを向上させるために使用されます.コレクタがない場合にバッファを追加します.あるいはいっそキャッシュメカニズムとして使用します.
に注意
:コールドフローは必要に応じて作成され、観察されたときにデータが送信されます.熱流は常に活発であり,観察されるかどうかにかかわらずデータを送信することができる.
ここでは、shareInオペレータとstateInオペレータの例を示します.特定のインスタンスに対して構成し、一般的なトラップを回避する方法を学びます.
最下位データ・ストリーム・プロダクション
私の前の記事で使用した例を引き続き使用します.下位データストリーム生産者を使用して位置更新を発行します.callbackFlowを用いて実現された冷流である.各新しいコレクターは、データ・ストリームの生産者コード・ブロックをトリガーし、FusedLocationProviderClientに新しいコールバックを追加します.
class LocationDataSource(
    private val locationClient: FusedLocationProviderClient
) {
    val locationsSource: Flow = callbackFlow {
        val callback = object : LocationCallback() {
            override fun onLocationResult(result: LocationResult?) {
                result ?: return
                try { offer(result.lastLocation) } catch(e: Exception) {}
            }
        }
        requestLocationUpdates(createLocationRequest(), callback, Looper.getMainLooper())
            .addOnFailureListener { e ->
                close(e) // in case of exception, close the Flow
            }
        //   Flow          
        awaitClose {
            removeLocationUpdates(callback)
        }
    }
}

異なる例でshareInとstateInを使用してlocationsSourceデータストリームを最適化する方法を見てみましょう.
ShareInかstateInか?
私たちが議論する最初の話題はshareInstateInの違いです.shareInオペレータはSharedFlowを返し、stateInStateFlowを返します.
注意:詳細はこちらStateFlowSharedFlowの詳細は、
デルのドキュメント .
StateFlowはSharedFlowの特殊な構成であり、最後に送信されたアイテムは新しいコレクターに再送信され、これらのアイテムはAny.equalsを使用してマージされます.詳細については、StateFlowドキュメントで確認できます.
両者の最も主要な違いは、StateFlowインタフェースで、valueプロパティを読み込むことで、最後に発行された値に同期してアクセスできることです.これはSharedFlowの使用方法ではありません.
パフォーマンスの向上
これらのAPIは、必要に応じて同じデータ・ストリームの新しいインスタンスを作成するのではなく、すべての収集者が観察する同じデータ・ストリーム・インスタンスを共有することで、パフォーマンスを向上させることができます.
次の例では、LocationRepositoryは、LocationDataSourceによって暴露されたlocationsSourceデータストリームを消費し、shareInオペレータを使用して、ユーザ位置情報に関心のある収集者ごとに同じデータストリームインスタンスからデータを収集する.ここでは、locationsSourceのデータ・ストリーム・インスタンスのみが作成され、すべてのコレクターによって共有されます.
class LocationRepository(
    private val locationDataSource: LocationDataSource,
    private val externalScope: CoroutineScope
) {
    val locations: Flow = 
        locationDataSource.locationsSource.shareIn(externalScope, WhileSubscribed())
}

WhileSubscribed共有ポリシーは、収集者がいないときに上流データストリームをキャンセルするために使用される.これにより、プログラムが位置更新に興味を持っていないときにリソースの浪費を避けることができます.
Androidアプリで注意!ほとんどの場合、
WhileSubscribed(5000)は、最後の収集者が消えた後、上流データストリームのアクティブな状態を5秒間維持する.これにより、構成の変更など、特定の状況下で上流データストリームの再起動を回避できます.このテクニックは、上流データストリームの作成コストが高い場合や、ViewModelでこれらのオペレータを使用する場合に特に役立ちます.
バッファイベント
次の例では、私たちのニーズが変わりました.バックグラウンドからフロントに戻ると、最後の10の場所が画面に表示されるように、リスニング位置の更新を維持する必要があります.
class LocationRepository(
    private val locationDataSource: LocationDataSource,
    private val externalScope: CoroutineScope
) {
    val locations: Flow = 
        locationDataSource.locationsSource
            .shareIn(externalScope, SharingStarted.Eagerly, replay = 10)
}

パラメータreplayの値を10に設定して、最後に発行された10個のアイテムをメモリに保持し、収集者がデータストリームを観察するたびに再送信します.内部データストリームが常にアクティブであることを維持し、位置更新を送信するために、共有ポリシーSharingStarted.Eagerlyを使用し、収集者がいなくても更新を常に傍受することができます.
データのキャッシュ
デルのニーズは再び変化し、今回はバックグラウンドで監視位置の更新を継続する必要はありません.ただし、最後に送信されたアイテムをキャッシュして、データが古い場合でも、現在の場所を取得するときに画面にデータを表示できるようにする必要があります.この場合、stateInオペレータを使用できます.
class LocationRepository(
    private val locationDataSource: LocationDataSource,
    private val externalScope: CoroutineScope
) {
    val locations: Flow = 
        locationDataSource.locationsSource.stateIn(externalScope, WhileSubscribed(), EmptyLocation)
}
Flow.stateInは、最後に送信されたアイテムをキャッシュし、新しいコレクターに再生することができる.
注意!各関数呼び出し時に新しいインスタンスを作成しないでください
関数呼び出しの戻りを呼び出すときにshareInまたはstateInを使用して新しいデータストリームを作成しないでください.これにより、関数呼び出しのたびに新しいSharedFlowまたはStateFlowが作成され、役割ドメインがキャンセルされるか、参照がないときにゴミが回収されるまでメモリに保持されます.
class UserRepository(
    private val userLocalDataSource: UserLocalDataSource,
    private val externalScope: CoroutineScope
) {
    //             shareIn   stateIn 
    //              SharedFlow   StateFlow,         。
    fun getUser(): Flow =
        userLocalDataSource.getUser()
            .shareIn(externalScope, WhileSubscribed())    

    //          shareIn   stateIn 
    val user: Flow = 
        userLocalDataSource.getUser().shareIn(externalScope, WhileSubscribed())
}

パラメータが必要なデータ・ストリームuserIdなどのパラメータを必要とするデータストリームは、shareInまたはstateInを単純に使用して共有することはできない.オープンソースプロジェクトであるGoogle I/OのAndroidアプリケーションioschedを例にとると、ソース中からユーザイベントを取得するデータストリームはcallbackFlowによって実現されていることがわかります.パラメータとしてuserIdを受信するため、shareInまたはstateInオペレータを単純に多重化することはできない.
class UserRepository(
    private val userEventsDataSource: FirestoreUserEventDataSource
) {
    //         Firestore         。
    //            `userId`,        
    //           shareIn   stateIn     .
    //             ,        SharedFlow   StateFlow
    fun getUserEvents(userId: String): Flow =
        userLocalDataSource.getObservableUserEvents(userId)
}

この例を最適化する方法は、アプリケーションのニーズに応じて異なります.
  • 複数のユーザーから同時にイベントを受信できますか?答えが肯定的である場合は、SharedFlowまたはStateFlowインスタンスにmapを作成し、subscriptionCountが0の場合、参照を削除して上流データストリームを終了する必要があります.
  • ユーザーが1人しか許可されておらず、コレクターが新しいユーザーを観察するために更新する必要がある場合は、すべてのコレクターで共通のSharedFlowまたはStateFlowにイベント更新を送信し、クラス内の変数として共通データストリームを送信できます.
  • shareInおよびstateInオペレータは、冷却フローとともに使用してパフォーマンスを向上させ、コレクタがない場合にバッファを追加したり、キャッシュメカニズムとして直接使用したりすることができます.関数呼び出しのたびに新しいデータ・ストリーム・インスタンスを作成しないでください.これにより、リソースの浪費や予想外の問題が発生します.