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

Jetpack Compose中如何用共享ViewModel共享TextField手机号

问题解答

完全可以实现,基于Hilt + Jetpack Compose导航的共享ViewModel机制就能做到,改动量很小。

实现思路

你现在的核心问题是手机号输入状态存在RegisterScreen内部的remember变量里,页面跳转后该状态会随Composition销毁,两个独立业务的ViewModel拿不到同一份输入值。单独抽离一个共享ViewModel存储跨页面的手机号状态,让注册页、验证码页持有同一个SharedViewModel实例即可解决。

具体实现步骤

  1. 首先定义共享ViewModel
@HiltViewModel
class SharedViewModel @Inject constructor() : ViewModel() {
    private val _phoneNumber = MutableStateFlow("")
    val phoneNumber: StateFlow<String> = _phoneNumber.asStateFlow()

    fun updatePhoneNumber(number: String) {
        // 可在此处提前增加手机号格式校验逻辑
        _phoneNumber.value = number
    }
}

注意不要把这个ViewModel绑定到Application全局作用域,否则退出注册流程后数据会残留,绑定到注册流程对应的导航图作用域即可,流程退出时ViewModel会自动回收。

  1. 修改注册页代码,把手机号状态从页面内部移到共享ViewModel
    把原来页面内remember的手机号状态替换成从SharedViewModel收集的状态,输入变化时直接更新到共享VM:
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun RegisterScreen(
    navController: NavController,
    model: RegisterViewModel,
    sharedVm: SharedViewModel = hiltViewModel() // 注入共享VM实例
) {
    val focusManager = LocalFocusManager.current
    // 收集共享VM里的手机号状态
    val phoneNumber by sharedVm.phoneNumber.collectAsStateWithLifecycle()

    OutlinedTextField(
        value = phoneNumber,
        colors = TextFieldDefaults.textFieldColors(
            backgroundColor = white,
            focusedIndicatorColor = Grey,
            unfocusedIndicatorColor = Grey,
            focusedLabelColor = Grey,
            unfocusedLabelColor = Grey,
            cursorColor = custom,
            textColor = custom,
        ),
        onValueChange = { sharedVm.updatePhoneNumber(it) }, // 输入更新直接同步到共享VM
        label = { Text(text = "Phone Number") },
        keyboardActions = KeyboardActions(onNext = {
            focusManager.moveFocus(FocusDirection.Down)
        }),
        placeholder = { Text(text = "Phone Number") },
        singleLine = true,
        modifier = Modifier.fillMaxWidth(0.8f)
    )

    Button(
        modifier = Modifier
            .width(205.dp)
            .height(35.dp),
        onClick = {
            focus.clearFocus(force = true)
            model.onSignUp(phoneNumber) // 直接取共享VM的手机号传给写入Firestore的逻辑
            navController.navigate("otp")
        },
        colors = ButtonDefaults.buttonColors(
            backgroundColor = custom
        ),
        shape = RoundedCornerShape(30)
    ) {
        Text(
            text = "Next",
            style = TextStyle(
                fontSize = 11.sp,
                color = white,
            ))
    }
}
  1. 修改验证码页代码,从同一个SharedViewModel实例读取之前输入的手机号
    验证码页注入同一个SharedViewModel,就能直接拿到用户在注册页输入的手机号,不需要通过导航参数传递:
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun PhoneVerifyScreen(
    navController: NavController,
    modelAuthentication: AuthenticationViewModel,
    onClick: (mobileNum: String, otp: String) -> Unit,
    sharedVm: SharedViewModel = hiltViewModel() // 同一个导航栈下拿到的是和注册页相同的实例
) {
    val focusManager = LocalFocusManager.current
    val phoneNumberOTP = remember { mutableStateOf("") }
    // 读取共享VM里存的手机号
    val phoneNumber by sharedVm.phoneNumber.collectAsStateWithLifecycle()

    val context = LocalContext.current
    LaunchedEffect(Unit) {
        println("found activity? ${context.findActivity()}")
        val activity = context.findActivity() ?: return@LaunchedEffect
        modelAuthentication.setActivity(activity)
        // 可在此处直接触发发送验证码逻辑,不用等用户额外操作
        // modelAuthentication.sendVerificationCode(phoneNumber)
    }


    Column(
        Modifier.fillMaxSize()
            .clickable(
                indication = null,
                interactionSource = remember { MutableInteractionSource() } 
            ) {keyboardController?.hide()},
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        OutlinedTextField(
            value = phoneNumberOTP.value,
            colors = TextFieldDefaults.textFieldColors(
                backgroundColor = white,
                focusedIndicatorColor = Grey,
                unfocusedIndicatorColor = Grey,
                focusedLabelColor = Grey,
                unfocusedLabelColor = Grey,
                cursorColor = custom,
                textColor = custom,
            ),
            onValueChange = { phoneNumberOTP.value = it },
            label = { Text(text = "Verify code") },
            placeholder = { Text(text = "Verify code") },
            singleLine = true,
            modifier = Modifier.fillMaxWidth(0.8f),
        )

        CompositionLocalProvider(LocalRippleTheme provides NoRippleTheme) {
            Button(
                modifier = Modifier
                    .width(285.dp)
                    .height(55.dp),
                onClick = {
                    // 把共享VM里的手机号和用户输入的验证码一起传给验证逻辑
                    modelAuthentication.otpVerification(
                        phoneNumber = phoneNumber,
                        otp = phoneNumberOTP.value
                    )
                    navController.navigate("profileScreen")
                },
                colors = ButtonDefaults.buttonColors(
                    backgroundColor = custom2
                ),
                shape = RoundedCornerShape(60),
           
            ) {
                Text(
                    text = "Next",
                    style = TextStyle(
                        fontSize = 18.sp,
                        color = white,
                    ))
            }
        }
    }
}

补充说明

  • 如果给注册流程做了嵌套导航图,只需要在导航图的作用域内获取SharedViewModel,就能保证整个注册流程(注册页->验证码页->资料完善页)拿到的是同一个实例,退出注册流程后ViewModel会自动销毁,不会残留数据。
  • 不需要修改现有AuthViewModel和RegisterViewModel的构造逻辑,手机号直接在Composable层从共享VM取出后传给对应业务方法即可,业务VM之间完全解耦。
  • 相比通过导航路由参数传递手机号,用共享VM的方式不需要处理参数序列化、长度限制的问题,后续如果要加其他跨页面的注册表单字段(比如昵称、密码),直接在SharedViewModel里加对应状态即可,拓展性更强。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 09:09:20