次のようなクラスと 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 関数についてはこちらも参考にされたい。
/以上