查看大量代码行来完成基本任务:
首先是进口:
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.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.ui.tooling.preview.Preview
然后
TextField
:
@Composable
fun EmailTextField() {
var email by rememberSaveable { mutableStateOf("") }
TextField(
value = email,
onValueChange = { email = it },
label = { Text("Enter email") }
)
}
@Composable
fun PasswordTextField() {
var password by rememberSaveable { mutableStateOf("") }
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Enter password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
)
}
最后是
App
本身:
@OptIn(ExperimentalResourceApi::class)
@Composable
@Preview
fun App() {
MaterialTheme {
var showContent by remember { mutableStateOf(false) }
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
EmailTextField()
PasswordTextField()
Button(onClick = { showContent = !showContent }) {
Text("Auth")
}
}
}
}
尝试获取
email
和 password
,将其传递给执行 HTTP 请求的函数,并根据输出导航到欢迎屏幕或显示错误。
官方 compose 示例项目显示了 14 个文件来解决此问题(它甚至不执行 HTTP 请求/响应):https://github.com/android/compose-samples/tree/309d4b9/Jetsurvey /app/src/main/java/com/example/compose/jetsurvey/signinsignup
是否有一种简单的方法来获取电子邮件+密码并导航到表单提交的下一步?
是否有一种简单的方法来获取电子邮件+密码并导航到表单提交的下一步?
您链接的存储库所做的不仅仅是一次登录。它包含两个不同的登录和注册屏幕,以及一个允许在登录/注册之间进行选择的欢迎页面。
此外,该存储库旨在作为仅使用 Compose 库构建更复杂表单的基础。如果您的表单始终只有两个
TextField
,那么您可以简化整个过程。
我们可以先总结一下哪些文件在做什么:
WelcomeScreen.kt
+ WelcomeViewModel.kt
SignInScreen.kt
+ SignInViewModel.kt
SignUpScreen.kt
+ WelcomeViewModel.kt
SignInSignUpScreen.kt
EmailState.kt
PasswordState.kt
TextFieldState.kt
WelcomeRoute.kt
SignInRoute.kt
SignUpRoute.kt
UserRepository.kt
现在让我们分析一下降低复杂性的潜力:
UI 定义:没有潜力
Jetpack Compose 中的常见设计是每个主屏幕都有自己的 ViewModel。
表单验证:潜力巨大
如果您的
TextField
只会出现一种错误状态(密码或电子邮件错误),那么您可以将该逻辑移至 ViewModel 类。
Wrapper Composables:潜力巨大
这些 Composables 的目的只是生成带有
UserRepository
实例的 ViewModel。这个逻辑可以向上移动到NavHost
,或者在使用像 Hilt 或 Koin 这样的依赖注入时完全删除。
存储库:没有潜力
这是必需的。
因此,当您通常以简单性为目标来实现这一点时,您最终会得到大约 8 个文件,这可能更容易接受。如果您只实现一个登录页面,您最终会得到三个文件:
LoginScreen.kt
LoginViewModel.kt
UserRepository.kt
关于您提到的代码冗长主题:
Jetpack Compose 将 UI 和布局定义统一为单个可组合项。虽然代码乍一看可能很冗长,但请记住,与经典的 Android 视图框架相比,您将逻辑和布局定义合并到一个位置,最终总共会减少行数。
不过,我确实同意,在 Compose 中将可组合项拆分为更小的单元尤其重要。
最后,登录屏幕的简单实现如下所示:
@Composable
LoginComposable(loginViewModel: LoginViewModel = viewModel(), onNavigateToContent: () -> Unit) {
var email by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
LaunchedEffect(loginViewModel.successfulLogin) {
if(loginViewModel.successfulLogin) {
onNavigateToContent()
}
}
Column(modifier = Modifier.fillMaxSize()) {
TextField(
value = email,
onValueChange = { email = it },
label = { Text("Enter email") },
isError = loginViewModel.emailError,
supportingText = if(loginViewModel.invalidEmail) {
Text(text = "Login failed")
}
)
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Enter password") },
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
isError = loginViewModel.passwordError,
supportingText = if(loginViewModel.invalidPassword) {
Text(text = "Login failed")
}
)
}
}
ViewModel 看起来像这样(userRepository 由依赖注入框架注入):
class LoginViewModel(
private val userRepository: UserRepository // using dependency injection
) : ViewModel() {
var successfulLogin by mutableStateOf(false)
var emailError by mutableStateOf(false)
var passwordError = mutableStateOf(false)
fun login(username: String, password: String) {
usernameError = false
passwordError = false
viewModelScope.launch {
try {
userRepository.login(username, password) // call networking suspend function
successfulLogin = true
} catch (e: Exception) {
// TODO add more sophisticated error handling here
usernameError = true
passwordError = true
}
}
}
}
这应该会给你一个简单实现的想法。