👋

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