1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kotlin】ComposeUIがStateFlowの値を取得できていなかったので、基礎から勉強してみた

Posted at

はじめに

Androidアプリを自作している時に、つまづいたところを備忘や整理の意味を込めて記事に残します。今回は 「StateFlow×JetpackCompose」です

実現したかったこと

私の作成しているアプリで、アカウントの削除機能を実装していました。

  • アカウント削除ボタンを押すと、DBからユーザ情報を削除
  • 削除が成功したら、完了ポップアップを出力する

当初の実現方式

概要図(以降に参考コードも載せますが、長いので、読み飛ばしてOK)
アカウント削除.jpg

  • 画面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の状態を拾えず「削除完了」のポップアップが表示できませんでした。

アカウント削除 (1).jpg

原因調査の結果

調査の結果、「アクティブではない画面B」に「StateFlowの収集」を任せていることが原因だとわかりました。

ざっくりとした理解にはなってしまいますが、「ComposeUI」「Navigation」で画面遷移を管理している場合は、以下のような各composeで定義されている画面が管理されており、画面AからBに遷移する際は、画像内のリストが入れ替わるはずです。
スクリーンショット 2025-01-05 11.14.30.png

また、アクティブな画面においては、composeが再描画できるような状態(Compose は自動的に UI を更新できる状態)になっていますが、画面B〜Dのようにアクティブでない画面はそうではありません。

そのため、アクティブではない画面B(アプリのログイン画面)でアカウントの削除結果を元にしたStateFlowの値を監視するということはできなかったのです。

改善

なので、以下のように現在アクティブである「アカウント削除の画面A」でStateFlowを監視し、ポップアップで「削除完了!」と表示させ、その後に画面Bに遷移させることで、composeUIがStateFlowを監視しない問題を回避することができました。

アカウント削除 (2).jpg

以下に改修後のコードを参考までに載せておきます

  • 画面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にて実装)

おわりに

誰かの役に立てば嬉しいです。
誤り等あれば、ご指摘ください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?