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() } } }
问题原因
- 参数传递方式错误:将
LogInRequest存入当前页面的arguments,并依赖previousBackStackEntry获取参数,这种方式会破坏导航栈的正常状态,导致返回逻辑失效。 - 导航栈管理不当:跳转时未确保
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
相关产品推荐
相关产品推荐

