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

Jetpack Compose中如何向TabItem的RegistrationScreen传递state与onEvent

如何将ViewModel的State和Event传递给TabItem中的RegistrationScreen

我正在开发一款带注册、登录Tab的移动端应用,注册数据存储在Room中。已经实现了Tab、UserState、UserViewModel等组件,但不清楚如何把创建用户所需的state和onEvent参数传递给作为TabItem的RegistrationScreen()。试过全局变量和直接传参,但没找到正确的实现方式。


原代码文件

MainActivity.kt

class MainActivity : ComponentActivity() {

    private val db by lazy {
        Room.databaseBuilder(
            applicationContext,
            UserDatabase::class.java,
            "users.db"
        ).allowMainThreadQueries().build()
    }
    private val viewModel by viewModels<UserViewModel>(
        factoryProducer = {
            object : ViewModelProvider.Factory {
                override fun <T : ViewModel> create(modelClass: Class<T>): T {
                    return UserViewModel(db.dao) as T
                }
            }
        }
    )

    @ExperimentalPagerApi
    @ExperimentalMaterialApi
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val state by viewModel.state.collectAsState()
            MainScreen(state = state, onEvent = viewModel::onEvent)
        }
    }
}

TabItem.kt

typealias ComposableFun = @Composable () -> Unit

sealed class TabItem(var icon: Int, var title: String, var screen: ComposableFun) {
    object Regisration : TabItem(R.drawable.ic_music, "Registration", { RegistrationScreen() })
    object Login : TabItem(R.drawable.ic_movie, "Login", { LoginScreen() })
}

ContentScreen.kt

@Composable
fun RegistrationScreen(
//    state: UserState,
//    onEvent: (UserEvent) -> Unit,
) {
    Box(modifier = Modifier.fillMaxSize(),
    ) {
        Column(
            modifier = Modifier
                .align(Alignment.TopStart)
                .padding(20.dp, top = 40.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {

            val phone = remember { mutableStateOf("") }
            val username = remember { mutableStateOf(TextFieldValue()) }
            val password = remember { mutableStateOf(TextFieldValue()) }
            val confirmPassword = remember { mutableStateOf(TextFieldValue()) }

            Spacer(modifier = Modifier.height(20.dp))
            TextField(
                label = { Text(text = "Phone number") },
                value = phone.value,
                onValueChange = { phone.value = it },
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone))


            Spacer(modifier = Modifier.height(20.dp))
            TextField(
                label = { Text(text = "Username") },
                value = username.value,
                onValueChange = { username.value = it })

            Spacer(modifier = Modifier.height(20.dp))
            TextField(
                label = { Text(text = "Password") },
                value = password.value,
                visualTransformation = PasswordVisualTransformation(),
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
                onValueChange = { password.value = it })

            Spacer(modifier = Modifier.height(20.dp))
            TextField(
                label = { Text(text = "Confirm Password") },
                value = confirmPassword.value,
                visualTransformation = PasswordVisualTransformation(),
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
                onValueChange = { confirmPassword.value = it })

            Spacer(modifier = Modifier.height(20.dp))
            Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
                var openDialog = remember {
                    mutableStateOf(false)
                }
                Button(
                    onClick = {
                        if (password.value != confirmPassword.value) {
                            openDialog.value = true
                        } else {
//                            state.name = username.value.toString()
//                            UserEvent.SetName(name = username.value.toString())
//                            UserEvent.SetPassword(password = password.value.toString())
//                            UserEvent.SetPhoneNumber(phoneNumber = phone.value)
//                            UserEvent.SaveUser
//                            onEvent(UserEvent.SaveUser)
                        }
                    },
                    shape = RoundedCornerShape(50.dp),
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(50.dp)
                ) {
                    Text(text = "Continue")
                }
                if (openDialog.value) {
                    AlertDialog(
                        onDismissRequest = {
                            openDialog.value = false
                        },
                        title = { Text(text = "Error") },
                        text = { Text("Password mismatch") },
                        buttons = {
                        }
                    )
                }
            }
        }
    }
}

UserViewModel.kt

class UserViewModel(
    private val dao: UserDao
): ViewModel() {
    private val _state = MutableStateFlow(UserState())
    private val _users =
        dao.getUsers().stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
    val state = combine(_state, _users) {state, users ->
        state.copy(
            users = users
        )
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), UserState())

    fun onEvent(event: UserEvent) {
        when(event) {
            is UserEvent.DeleteUser -> {
                viewModelScope.launch {
                    dao.deleteUser(event.user)
                }
            }
            UserEvent.HideDialog -> {
                _state.update { it.copy(
                    isAddingUser = false
                ) }
            }
            UserEvent.SaveUser -> {
                val name = state.value.name
                val password = state.value.password
                val phoneNumber = state.value.phoneNumber
                if (name.isBlank() || password.isBlank() || phoneNumber.isBlank()) {
                    return
                }
                val user = User(
                    name = name,
                    password = password,
                    phoneNumber = phoneNumber
                )
                viewModelScope.launch {
                    dao.upsertUser(user)
                }
                _state.update { it.copy(
                    isAddingUser = false,
                    name = "",
                    password = "",
                    phoneNumber = "",
                ) }
            }
            is UserEvent.SetName -> {
                _state.update { it.copy(
                    name = event.name
                ) }
            }
            is UserEvent.SetPassword ->
                _state.update { it.copy(
                    password = event.password
                ) }
            is UserEvent.SetPhoneNumber -> {
                _state.update { it.copy(
                    phoneNumber = event.phoneNumber
                ) }
            }
            UserEvent.ShowDialog -> {
                _state.update { it.copy(
                    isAddingUser = true
                ) }
            }
        }
    }
}

解决方案

核心问题是原TabItem定义的是无参Composable,无法接收外部参数。我们需要修改TabItem的定义,让它能接收state和onEvent,再在MainScreen中传递这些参数。

1. 修改TabItem.kt的定义

将无参的Composable类型改为带参数的类型,让每个TabScreen能接收所需参数:

typealias TabScreen = @Composable (state: UserState, onEvent: (UserEvent) -> Unit) -> Unit

sealed class TabItem(var icon: Int, var title: String, var screen: TabScreen) {
    object Registration : TabItem(R.drawable.ic_music, "Registration", { state, onEvent -> 
        RegistrationScreen(state = state, onEvent = onEvent) 
    })
    object Login : TabItem(R.drawable.ic_movie, "Login", { state, onEvent -> 
        LoginScreen(state = state, onEvent = onEvent) 
    })
}

2. 完善RegistrationScreen.kt的参数与逻辑

取消参数注释,替换本地状态为ViewModel的State,通过事件更新状态:

@Composable
fun RegistrationScreen(
    state: UserState,
    onEvent: (UserEvent) -> Unit,
) {
    Box(modifier = Modifier.fillMaxSize()) {
        Column(
            modifier = Modifier
                .align(Alignment.TopStart)
                .padding(20.dp, top = 40.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Spacer(modifier = Modifier.height(20.dp))
            TextField(
                label = { Text(text = "Phone number") },
                value = state.phoneNumber,
                onValueChange = { onEvent(UserEvent.SetPhoneNumber(it)) },
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone)
            )

            Spacer(modifier = Modifier.height(20.dp))
            TextField(
                label = { Text(text = "Username") },
                value = state.name,
                onValueChange = { onEvent(UserEvent.SetName(it)) }
            )

            Spacer(modifier = Modifier.height(20.dp))
            TextField(
                label = { Text(text = "Password") },
                value = state.password,
                visualTransformation = PasswordVisualTransformation(),
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
                onValueChange = { onEvent(UserEvent.SetPassword(it)) }
            )

            Spacer(modifier = Modifier.height(20.dp))
            var confirmPassword by remember { mutableStateOf("") }
            TextField(
                label = { Text(text = "Confirm Password") },
                value = confirmPassword,
                visualTransformation = PasswordVisualTransformation(),
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
                onValueChange = { confirmPassword = it }
            )

            Spacer(modifier = Modifier.height(20.dp))
            Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
                var openDialog by remember { mutableStateOf(false) }
                Button(
                    onClick = {
                        if (state.password != confirmPassword) {
                            openDialog = true
                        } else {
                            onEvent(UserEvent.SaveUser)
                        }
                    },
                    shape = RoundedCornerShape(50.dp),
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(50.dp)
                ) {
                    Text(text = "Continue")
                }
                if (openDialog) {
                    AlertDialog(
                        onDismissRequest = { openDialog = false },
                        title = { Text(text = "Error") },
                        text = { Text("Password mismatch") },
                        buttons = {
                            Button(onClick = { openDialog = false }) {
                                Text("OK")
                            }
                        }
                    )
                }
            }
        }
    }
}

3. 在MainScreen中传递参数给TabScreen

假设MainScreen是Tab容器组件,在渲染每个Tab时传递state和onEvent:

@ExperimentalPagerApi
@ExperimentalMaterialApi
@Composable
fun MainScreen(state: UserState, onEvent: (UserEvent) -> Unit) {
    val tabs = listOf(TabItem.Registration, TabItem.Login)
    val pagerState = rememberPagerState()
    val coroutineScope = rememberCoroutineScope()

    Column(modifier = Modifier.fillMaxSize()) {
        // Tab栏
        TabRow(selectedTabIndex = pagerState.currentPage) {
            tabs.forEachIndexed { index, tabItem ->
                Tab(
                    icon = { Icon(painterResource(id = tabItem.icon), contentDescription = tabItem.title) },
                    text = { Text(tabItem.title) },
                    selected = pagerState.currentPage == index,
                    onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } }
                )
            }
        }
        // 内容区域
        HorizontalPager(count = tabs.size, state = pagerState) { page ->
            tabs[page].screen(state = state, onEvent = onEvent)
        }
    }
}

4. MainActivity.kt保持不变

原MainActivity代码已正确获取ViewModel的State和事件处理函数,并传递给MainScreen,无需修改。


方案说明

  • 避免全局变量:通过参数传递是Compose状态提升的最佳实践,避免状态管理混乱
  • 单一职责:ViewModel负责状态管理与业务逻辑,Screen仅负责UI渲染与事件触发
  • 可测试性:参数传递的方式更便于单元测试和UI测试

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 13:58:09