Select 文の戻り値の型を Flow にしても、 DB のデータが更新された際に最新のデータが受け取れない問題について
Jetpack Compose を使用して、 Android アプリの開発を行っている際に、 DB のデータ更新の通知を受け取れない問題につてい悩まされたため、解決方法を共有します。
問題の概要
Android でローカルのデータベースにデータを登録する場合は、 Room ライブラリを使用するのが一般的かと思います。 Room ライブラリを使用する際は、 Dao の Select 文で、 LiveData 型を返すか、あるいは、 Flow 型を返すようにすることで、テーブルのデータが更新された際に、通知を受け取れるようになっています。
しかし、今回、戻り値の型を Flow 型で定義しても、通知を受け取ることができませんでした。
問題の直接的な原因
この問題の直接的な原因は、画面が表示されても、ライフサイクルが ON_CREATED の状態から先に進まなかった ( ON_STARTED 以上の状態にならなかった) 、ことに起因しています。
一般的に、 Jetpack Compose を使用して画面を作成する場合は、以下のように、 UI 状態を監視します。これにより、画面が ON_STARTED 以上の状態になっているときにだけ、上流から流れてくるデータを監視します。
// Composable 関数内
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
今回の問題では、画面が表示されても、画面のライフサイクルが ON_STARTED 以上にならなかったため、上流から流れてくるデータの監視ができませんでした。
問題の根本的な原因
では、なぜ、画面のライフサイクルが ON_STARTED 以上にならなかったのでしょうか?
その前に、画面のライフサイクルオーナーが何かについて、少し触れたいと思います。
画面のライフサイクルオーナーは、少し前までは、 Activity や Fragment であることが一般的でした。しかし、 Jetpack Compose が導入されてからは、一つの画面が、一つの Activity や Fragment に紐づいているとは限らず、画面単位でのライフサイクルオーナーが存在しないようにも思えます。そこで登場するのが、 Navigation コンポーネントの NavBackStackEntry です。 Navigation コンポーネントで画面遷移を管理することで、画面ごとに NavBackStackEntry がライフサイクルオーナーになってくれます。
今回の問題に話を戻すと、この NavBackStackEntry が ON_STARTED 以上の状態になってくれなかったことで、今回の問題が発生しました。
NavBackStackEntry のライフサイクルを管理しているのは、 Navigation コンポーネントでしょう。画面が表示された際に、 Navigation コンポーネントが、なぜ NavBackStackEntry の状態を ON_STARTED 以上の状態にしないのかは、ライブラリの作りの問題なので、正直、私にはわかりません。
そして、 ON_STARTED 以上の状態にならないというバグ? or 仕様?が、いつも発生するのではなく、どうやら、以下のように launchSingleTop を指定したり、 popUp している際に、この不可解な挙動になるようです。
navController.navigate(List) {
popUpTo(List)
launchSingleTop = true
}
普通に、 navigate() 関数で純粋な画面遷移をする場合には、今回の問題は発生しないようです。
解決方法
上記の通り、 launchSingleTop などのパラメータを指定した画面遷移は、今回の問題につながるということが分かったので、私の場合は、純粋に前の画面に戻りたかっただけなので、
navController.navigateUp()
とすることで、この問題が解決しました。
ポップアップ以外で、画面遷移したい場合は、 navigate() 関数で純粋な画面遷移をすれば、この問題は解決するのではないかなと予想します。
(感想)Navigation コンポーネントは癖がある
今回の問題は、 Navigation コンポーネントで、特定の画面遷移をする際に、画面のライフサイクルが正しく反映されないという、仕様と言うには厳しいバグ?に起因するものでした。
少し前にも、 Navigation コンポーネントの挙動に疑問を感じることがありました。それは、「型安全なデスティネーションで、前の画面に戻ろうとする際に、パラメータを渡そうとしても、以前のパラメータが残ってしまっていて、渡せない」という挙動でした。
特定のユースケースでは、想定通りに動かない場合もあるので、 Navigation コンポーネントは癖が強いなという印象です。
Discussion