You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose导航返回失效求助:发送OTP后无法返回上页

Jetpack Compose OTP验证页返回按钮失效问题修复

问题描述

Jetpack Compose项目中,发送OTP请求成功后跳转至OTP验证页面,但点击返回按钮无法回到手机号输入页面,返回功能完全失效。

相关代码

ContinueWithPhoneView代码

@Composable
fun ContinueWithPhoneView(
    navController: NavController,
    authenticationDataViewModel: AuthenticationDataViewModel
) {
    val viewModel = hiltViewModel<ContinueWithPhoneViewModel>()
    val event by viewModel.events.observeAsState(null)
    val phoneNumber = remember { mutableStateOf("") }
    val errorMessage = remember { mutableStateOf("") }
    val (gesturesEnabled, toggleGesturesEnabled) = remember { mutableStateOf(false) }
    val scope = rememberCoroutineScope()
    val isLoading = remember { mutableStateOf(false) }
    val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
    Surface(
        modifier = Modifier.fillMaxSize(),
        ) {
        BottomDrawer(
            gesturesEnabled = gesturesEnabled,
            drawerState = drawerState,
            drawerShape = BottomSheetShape,
            drawerElevation = 8.dp,
            drawerContent = {
                ErrorBottomView(message = errorMessage.value) {
                    scope.launch { drawerState.close() }
                }
            },
            content = {
                Column(
                    horizontalAlignment = Alignment.CenterHorizontally,
                ) {
                    Column {
                        NavigationBar(topAppBarText = "Continue With Phone") {
                            navController.popBackStack()
                        }
                    }
                    Spacer(modifier = Modifier
                        .weight(1f))

                    Column(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(start = 16.dp, end = 16.dp),
                    ) {
                        OutlinedTextField(
                            keyboardOptions = KeyboardOptions(
                                capitalization = KeyboardCapitalization.None,
                                autoCorrect = false,
                                keyboardType = KeyboardType.Phone,
                            ),
                            modifier = Modifier
                                .navigationBarsWithImePadding()
                                .fillMaxWidth(),
                            value = phoneNumber.value,
                            onValueChange = { phoneNumber.value = it },
                            label = {
                                Text(
                                    text = "Phone Number"
                                )
                            },
                            colors = TextFieldDefaults.outlinedTextFieldColors(
                                focusedBorderColor = MaterialTheme.colors.onSecondary, // Set the focused border color here
                                focusedLabelColor = MaterialTheme.colors.onSecondary
                            )
                        )
                        Spacer(modifier = Modifier.padding(8.dp))
                        Text(modifier = Modifier, style = MaterialTheme.typography.caption, text = "By continuing, you agree to get SMS messages, WhatsApp, Telegram or calls, including automated means, from Dispatch and its affiliates to the number provided")
                    }
                    Spacer(modifier = Modifier
                        .weight(1f))
                    Column(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(start = 16.dp, end = 16.dp),
                    ) {
                        NavigationButton(buttonText = "Continue with Phone", isLoading = isLoading.value, onBackPressed = {
                            navController.popBackStack()
                        }) {
                            viewModel.sendOtp(phoneNumber = phoneNumber.value)
                        }
                    }

                    Spacer(modifier = Modifier.padding(6.dp))
                }
            }
        )
        LaunchedEffect(event) {
            when (event) {
                is Result.Loading -> {
                    isLoading.value = true
                }
                is Result.Error -> {
                    val exception = (event as Result.Error).exception
                    scope.launch {
                        errorMessage.value = exception
                        isLoading.value = false
                        drawerState.open()
                    }
                }
                is Result.Success -> {
                    val logInRequest = LogInRequest(phoneNumber = phoneNumber.value, password = "")
                    isLoading.value = false
                    navController.currentBackStackEntry?.arguments?.putParcelable("logInRequest", logInRequest)
                    navController.navigate(Screen.VerifyPhone.route)
                }
                else -> {}
            }
        }
    }
}

ViewModel代码

@HiltViewModel
class ContinueWithPhoneViewModel @Inject constructor(
    private val repository: AccountRepositoryProviding,
    @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher,
): ViewModel() {
    private val _events = MutableLiveData<Result<Boolean>>()
    val events: LiveData<Result<Boolean>> = _events

    private var job: Job? = null

    fun sendOtp(phoneNumber: String) {
        job?.cancel()
//        Timber.tag(javaClass.name)
//            .d("inputted phone number %s", phoneNumber)
        _events.value = Result.Loading

        val handler = CoroutineExceptionHandler { _, throwable ->
            val message = when (throwable) {
                is HttpException -> throwable.toErrorMessage()
                else -> "An Error Occurred."
            }
            Log.d("ContinueWithPhoneViewModel", "ERR :: ${throwable} \n")
//            Timber.tag(javaClass.name)
//                .e(throwable)
            _events.value = Result.Error(message)
        }
        job = viewModelScope.launch(handler) {
            val resp = repository.sendOtp(phoneNumber = phoneNumber)
            Timber.tag(javaClass.name)
                .d("Response ::: %s", resp)
            _events.value = Result.Success(resp.data ?: false)
        }
    }
}

导航代码

@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    val authenticationDataViewModel: AuthenticationDataViewModel = viewModel()
    NavHost(
        navController = navController,
        startDestination = Screen.Landing.route
    ) {

        composable(Screen.Landing.route) {
            LandingPageView(navController = navController)
        }

        composable(Screen.ContinueWithPhone.route) {
            ContinueWithPhoneView(navController = navController, authenticationDataViewModel = authenticationDataViewModel)
        }

        composable(route = Screen.VerifyPhone.route) {
            navController.previousBackStackEntry?.arguments?.getParcelable<LogInRequest>("logInRequest")?.let { request -> VerifyPhoneView(navController, request) }
        }

        composable(route = Screen.Base.route) {
            TabBarView()
        }
    }
}

问题原因

  1. 参数传递方式错误:将LogInRequest存入当前页面的arguments,并依赖previousBackStackEntry获取参数,这种方式会破坏导航栈的正常状态,导致返回逻辑失效。
  2. 导航栈管理不当:跳转时未确保ContinueWithPhone页面保留在栈中,且参数传递的异常可能引发栈结构紊乱。

修复方案

1. 修改跳转时的参数传递逻辑

在ContinueWithPhoneView的LaunchedEffect中,直接通过bundle传递参数给VerifyPhone页面,移除错误的currentBackStackEntry参数设置:

// 替换原Success分支的代码
is Result.Success -> {
    val logInRequest = LogInRequest(phoneNumber = phoneNumber.value, password = "")
    isLoading.value = false
    // 使用bundle传递参数
    val bundle = bundleOf("logInRequest" to logInRequest)
    navController.navigate(Screen.VerifyPhone.route, bundle)
}

2. 正确接收VerifyPhone页面的参数

修改AppNavigation中VerifyPhone页面的注册代码,从当前页面的backStackEntry获取参数,不再依赖previousBackStackEntry:

composable(route = Screen.VerifyPhone.route) { backStackEntry ->
    val request = backStackEntry.arguments?.getParcelable<LogInRequest>("logInRequest")
    if (request != null) {
        VerifyPhoneView(navController, request)
    } else {
        // 参数为空时自动返回上一页
        LaunchedEffect(Unit) {
            navController.popBackStack()
        }
    }
}

3. 确认VerifyPhone页面的返回逻辑

确保VerifyPhoneView中的返回按钮正确调用popBackStack():

// 示例VerifyPhoneView中的返回导航栏
NavigationBar(topAppBarText = "Verify OTP") {
    navController.popBackStack()
}

4. 验证导航栈状态(可选)

添加日志打印导航栈,确认页面栈结构是否正常:

// 在ContinueWithPhone跳转前打印
Log.d("NavStack", "Before navigate: ${navController.backStack.map { it.destination.route }}")
// 在VerifyPhone页面初始化时打印
LaunchedEffect(Unit) {
    Log.d("NavStack", "VerifyPhone stack: ${navController.backStack.map { it.destination.route }}")
}

内容的提问来源于stack exchange,提问作者King

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 20:54:59