次のようなクラスと suspend 関数があるとする。
class MyData
/**
* [MyData] オブジェクトを非同期でロードする。
*/
suspend fun loadMyData(): MyData {
// 省略
}
ここで新たに MyClass
クラスを実装したい。
MyClass
クラスには MyData
を値とする StateFlow
をプロパティとして持たせ、
MyClass
インスタンス生成時に loadMyData
関数を呼んで
その返値でこのプロパティを初期化したい。
単純に実装するとこうなる。
⚠️改善の余地あり
class MyClass(scope: CoroutineScope) {
val myData: StateFlow<MyData?>
get() = _myData
private val _myData = MutableStateFlow<MyData?>(null)
init {
scope.launch {
loadMyData()
}
}
}
しかしこれだと _myData
が MutableStateFlow
なので
「myData
が一度だけ初期化される」ことを保証するためには
MyClass
クラス全体をみる必要がある。
そこで次のようにする。
class MyClass(scope: CoroutineScope) {
val myData: StateFlow<MyData?> =
flow {
emit(
loadMyData()
)
}
.stateIn(
scope,
// stateIn 実行に flow の購読が開始されるようにする。
SharingStarted.Eagerly,
initialValue = null,
)
}
これであれば myData
プロパティが一度だけ初期化されることが、プロパティの定義だけで保証される。
なお loadMyData
が例外をスローすることがある場合(MyClass
だけでは復帰処理などができない場合)は
StateFlow
の値を Result
型にするとよいだろう。
class MyClass(scope: CoroutineScope) {
val myData: StateFlow<Result<MyData>?> =
flow {
emit(
runCatching {
loadMyData()
}
)
}
.stateIn(
scope,
// stateIn 実行に flow の購読が開始されるようにする。
SharingStarted.Eagerly,
initialValue = null,
)
}
Result
型および runCatching
関数についてはこちらも参考にされたい。
/以上