はじめに
Androidアプリを自作している時に、つまづいたところを備忘や整理の意味を込めて記事に残します。今回は 「StateFlow×JetpackCompose」です
実現したかったこと
私の作成しているアプリで、アカウントの削除機能を実装していました。
![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F3760174%2Fe4be5152-67e3-7980-d7a8-87ddcd5faa8f.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=58b5f82e609bd60223f0fd67dcba5c3b)
- アカウント削除ボタンを押すと、DBからユーザ情報を削除
- 削除が成功したら、完了ポップアップを出力する
当初の実現方式
概要図(以降に参考コードも載せますが、長いので、読み飛ばしてOK)
-
画面A(アカウント削除ボタン画面)
//省略 modifier = Modifier .padding(top = 32.dp) .clickable { DeleteViewModel.reAuthForDel(context,activityResultLauncher) } ) { Text( text = "アカウント削除", color = Color.Red, fontSize = 24.sp, modifier = Modifier
-
ViewModel(ユーザ情報の削除と状態管理用)
@HiltViewModel class DeleteViewModel @Inject constructor( private val db: FirebaseFirestore, private val storage: FirebaseStorage, ) : ViewModel() { val user = FirebaseAuth.getInstance().currentUser private var _resultFlag = MutableStateFlow(false) val resultFlag: StateFlow<Boolean> = _resultFlag fun chengeResultFlag(flag: Boolean) { _resultFlag.value = flag } //Google再認証 fun reAuthForDel( context: Context, activityResultLauncher: ActivityResultLauncher<Intent>, ) { //省略 } //ユーザ情報削除 fun allUserInfoDeleAft( navController: NavController, idToken:String? ) { if (idToken !==null) { //省略 user.reauthenticate(googleProvider).addOnSuccessListener { Log.d(TAG,"reauth is success") viewModelScope.launch { //user情報の削除を実施 val result: Boolean = allUserInfoDel() if (result) { //成功したらFlagをtrueに _resultFlag.value = true navcontoroler.navigate("LogIn") } else { } } }.addOnFailureListener {Exception -> } } } } //削除用のfunction suspend fun allUserInfoDel(): Boolean { }
-
画面B(LogInのUI部分)
@Composable fun LogInView( loginViewModel: LoginViewModel = viewModel(), currentUserViewModel: CurrentUserViewModel = viewModel(), GoogleAuthViewModel: GoogleAuthViewModel = viewModel(), navController: NavController, DeleteViewModel: DeleteViewModel = viewModel() ) { val deleteFlag by DeleteViewModel.resultFlag.collectAsState() //省略 //削除成功後、画面遷移→ポップアップ if (deleteFlag) { Log.d("DeleteFlag", "deleteFlag: $deleteFlag") AlertDialog( onDismissRequest = { DeleteViewModel.chengeResultFlag(false) }, title = { Text("削除完了のお知らせ") }, text = { Text(text = "ユーザアカウントが削除されました。") }, confirmButton = { Button(onClick = { DeleteViewModel.chengeResultFlag(false) }) { Text(text = "OK") } }) }
困ったこと
概要図の再掲ですが、画面BでviewModelが定義しているStateFlowの状態を拾えず「削除完了」のポップアップが表示できませんでした。
原因調査の結果
調査の結果、「アクティブではない画面B」に「StateFlowの収集」を任せていることが原因だとわかりました。
ざっくりとした理解にはなってしまいますが、「ComposeUI」「Navigation」で画面遷移を管理している場合は、以下のような各composeで定義されている画面が管理されており、画面AからBに遷移する際は、画像内のリストが入れ替わるはずです。
また、アクティブな画面においては、composeが再描画できるような状態(Compose は自動的に UI を更新できる状態)になっていますが、画面B〜Dのようにアクティブでない画面はそうではありません。
そのため、アクティブではない画面B(アプリのログイン画面)でアカウントの削除結果を元にしたStateFlowの値を監視するということはできなかったのです。
改善
なので、以下のように現在アクティブである「アカウント削除の画面A」でStateFlowを監視し、ポップアップで「削除完了!」と表示させ、その後に画面Bに遷移させることで、composeUIがStateFlowを監視しない問題を回避することができました。
以下に改修後のコードを参考までに載せておきます
-
画面A(アカウント削除ボタン画面)
//省略 val deleteFlag by DeleteViewModel.resultFlag.collectAsState() modifier = Modifier .padding(top = 32.dp) .clickable { DeleteViewModel.reAuthForDel(context,activityResultLauncher) } ) { Text( text = "アカウント削除", color = Color.Red, fontSize = 24.sp, modifier = Modifier) if (deleteFlag) { Log.d("DeleteFlag", "deleteFlag: $deleteFlag") AlertDialog( onDismissRequest = { DeleteViewModel.chengeResultFlag(false) }, title = { Text("削除完了のお知らせ") }, text = { Text(text = "ユーザアカウントが削除されました。") }, confirmButton = { Button(onClick = { DeleteViewModel.chengeResultFlag(false) navController.navigate("LogInView") }) { Text(text = "OK") } }) }
-
ViewModel(ユーザ情報の削除と状態管理用)
#navigatioをコメントアウト。それ以外は変更なし@HiltViewModel class DeleteViewModel @Inject constructor( private val db: FirebaseFirestore, private val storage: FirebaseStorage, ) : ViewModel() { val user = FirebaseAuth.getInstance().currentUser private var _resultFlag = MutableStateFlow(false) val resultFlag: StateFlow<Boolean> = _resultFlag //ユーザ情報削除 fun allUserInfoDeleAft( navController: NavController, idToken:String? ) { if (idToken !==null) { //省略 user.reauthenticate(googleProvider).addOnSuccessListener { Log.d(TAG,"reauth is success") viewModelScope.launch { //user情報の削除を実施 val result: Boolean = allUserInfoDel() if (result) { //成功したらFlagをtrueに _resultFlag.value = true //画面遷移は画面A内で実装するためコメントアウト //navcontoroler.navigate("LogIn") } else { } } }.addOnFailureListener {Exception -> } } } } //削除用のfunction suspend fun allUserInfoDel(): Boolean { }
- 画面B
- StateFlowを監視するロジックを削除
- alertDialogを削除(画面Aにて実装)
おわりに
誰かの役に立てば嬉しいです。
誤り等あれば、ご指摘ください。