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

Jetpack Compose简易表单:如何简化邮箱密码提交与页面跳转?

简化Jetpack Compose表单:获取输入、处理请求与页面跳转

用TextField和提交Button创建简单表单时,发现基础任务需要大量代码,包括一堆导入、拆分的输入组件,还没法直接获取输入值来调用HTTP请求,官方示例甚至要14个文件还没处理请求响应。想找更简便的方式,在提交时拿到邮箱密码,处理请求后跳转页面或显示错误。

核心优化思路:状态提升+集中逻辑处理

把分散在子组件的状态和逻辑集中到父组件,避免冗余,同时直接获取输入值处理请求。


1. 简化表单代码,直接获取输入值

不用拆分独立的EmailTextField和PasswordTextField,把状态放在父组件中,直接管理输入值:

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.material3.*
import kotlinx.coroutines.launch

@Composable
fun LoginScreen(
    onLoginSuccess: () -> Unit,
    onShowError: (String) -> Unit
) {
    MaterialTheme {
        // 集中管理表单状态
        var email by rememberSaveable { mutableStateOf("") }
        var password by rememberSaveable { mutableStateOf("") }
        var isLoading by remember { mutableStateOf(false) }
        val coroutineScope = rememberCoroutineScope()

        Column(
            Modifier.fillMaxWidth(),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            // 邮箱输入框
            TextField(
                value = email,
                onValueChange = { email = it },
                label = { Text("Enter email") },
                enabled = !isLoading, // 加载时禁用输入
                modifier = Modifier.fillMaxWidth(0.8f)
            )

            // 密码输入框
            TextField(
                value = password,
                onValueChange = { password = it },
                label = { Text("Enter password") },
                visualTransformation = PasswordVisualTransformation(),
                keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
                enabled = !isLoading,
                modifier = Modifier.fillMaxWidth(0.8f)
            )

            // 提交按钮
            Button(
                onClick = {
                    coroutineScope.launch {
                        isLoading = true
                        // 执行登录请求
                        val loginResult = performLogin(email, password)
                        isLoading = false

                        // 根据结果处理跳转或错误
                        if (loginResult.isSuccess) {
                            onLoginSuccess()
                        } else {
                            onShowError(loginResult.exceptionOrNull()?.message ?: "登录失败")
                        }
                    }
                },
                enabled = email.isNotBlank() && password.isNotBlank() && !isLoading,
                modifier = Modifier.padding(top = 16.dp)
            ) {
                if (isLoading) {
                    CircularProgressIndicator(modifier = Modifier.size(20.dp), strokeWidth = 2.dp)
                } else {
                    Text("登录")
                }
            }
        }
    }
}

// 模拟HTTP登录请求(替换为实际API调用)
suspend fun performLogin(email: String, password: String): Result<Unit> {
    return if (email == "test@example.com" && password == "123456") {
        Result.success(Unit)
    } else {
        Result.failure(IllegalArgumentException("邮箱或密码错误"))
    }
}

2. 页面跳转与错误提示

使用Compose Navigation组件处理页面跳转,用Snackbar显示错误信息:

import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.padding

@Composable
fun AppNavHost() {
    val navController = rememberNavController()
    val snackbarHostState = remember { SnackbarHostState() }

    Scaffold(
        snackbarHost = { SnackbarHost(snackbarHostState) },
        content = { paddingValues ->
            NavHost(
                navController = navController,
                startDestination = "login",
                modifier = Modifier.padding(paddingValues)
            ) {
                // 登录页面
                composable("login") {
                    LoginScreen(
                        onLoginSuccess = { navController.navigate("welcome") },
                        onShowError = { errorMsg ->
                            rememberCoroutineScope().launch {
                                snackbarHostState.showSnackbar(errorMsg)
                            }
                        }
                    )
                }

                // 欢迎页面
                composable("welcome") {
                    Column(
                        Modifier.fillMaxWidth(),
                        horizontalAlignment = Alignment.CenterHorizontally,
                        verticalArrangement = Arrangement.Center
                    ) {
                        Text("欢迎登录!", style = MaterialTheme.typography.headlineMedium)
                    }
                }
            }
        }
    )
}

3. 可选:封装表单状态(多字段场景)

如果表单字段较多,用数据类封装状态,减少重复代码:

data class LoginFormState(
    val email: String = "",
    val password: String = "",
    val isLoading: Boolean = false
)

@Composable
fun LoginScreen(
    onLoginSuccess: () -> Unit,
    onShowError: (String) -> Unit
) {
    var formState by rememberSaveable { mutableStateOf(LoginFormState()) }
    val coroutineScope = rememberCoroutineScope()

    MaterialTheme {
        Column(
            Modifier.fillMaxWidth(),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            TextField(
                value = formState.email,
                onValueChange = { formState = formState.copy(email = it) },
                label = { Text("Enter email") },
                enabled = !formState.isLoading
            )

            // 密码输入框同理,修改formState.copy(password = it)

            Button(
                onClick = {
                    coroutineScope.launch {
                        formState = formState.copy(isLoading = true)
                        val result = performLogin(formState.email, formState.password)
                        formState = formState.copy(isLoading = false)

                        if (result.isSuccess) onLoginSuccess()
                        else onShowError(result.exceptionOrNull()?.message ?: "登录失败")
                    }
                },
                enabled = formState.email.isNotBlank() && formState.password.isNotBlank() && !formState.isLoading
            ) {
                if (formState.isLoading) CircularProgressIndicator(modifier = Modifier.size(20.dp))
                else Text("登录")
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 16:55:00