在学习了几年python之后,我才刚刚开始使用Kotlincompose进入android,所以现在对我来说仍然很陌生。
我从登录/注册屏幕开始,我使用viewModel与可组合对象对话。然而,下面的代码不允许我在任何文本字段中键入内容。此外,即使在文本字段中没有任何内容的情况下单击“登录”按钮,理论上也应该向我发送一条吐司消息,所以我认为LoginViewModel的设置方式有问题,但是经过几天的尝试理解和解决,我发现它是一个很好的解决方案。由于我对Kotlin缺乏了解,我仍然被卡住了。
有没有人可以帮我看一下,看看你能不能告诉我哪里出错了?
我已经采取了代码片段,所以我不粘贴太多到这里。我认为这应该是足够的代码,以了解我有什么。
哦,也许应该提一下,我在ubuntu上运行android studio的电鳗版本,无论我在手机上还是在模拟器上运行应用程序,都会发生同样的问题。
import android.content.Context
import android.graphics.drawable.Icon
import android.media.Image
import android.os.Bundle
import android.view.Surface
import com.ftc.nomadicworker.R
import org.w3c.dom.Text
/*
LoginScreen.kt file
This holds the composables that make up the signin and signup screens
*/
@Composable
fun LoginScreen(
loginViewModel: LoginViewModel? = null,
onNavToHomePage: () -> Unit,
onNavToSignUpPage: () -> Unit,
) {
val loginUiState = loginViewModel?.loginUiState
println("LoginScreen: loginUiState: $loginUiState")
val isError = loginUiState?.loginError != null
val context = LocalContext.current
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "Logo",
modifier = Modifier.padding(16.dp)
)
Text(
text = "Login",
style = MaterialTheme.typography.h4,
fontWeight = FontWeight.Black,
color = MaterialTheme.colors.primary
)
if (isError) {
Text(
text = loginUiState?.loginError ?: "unknown error",
color = Color.Red,
)
}
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
value = loginUiState?.userName ?: "",
onValueChange = { loginViewModel?.onUserNameChange(it) },
leadingIcon = {
Icon(
imageVector = Icons.Default.Person,
contentDescription = null,
)
},
label = {
Text(text = "Email")
},
isError = isError
)
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
value = loginUiState?.password ?: "",
onValueChange = { loginViewModel?.onPasswordNameChange(it) },
leadingIcon = {
Icon(
imageVector = Icons.Default.Lock,
contentDescription = null,
)
},
label = {
Text(text = "Password")
},
visualTransformation = PasswordVisualTransformation(),
isError = isError
)
Button(onClick = { loginViewModel?.loginUser(context) }) {
Text(text = "Sign In")
}
Spacer(modifier = Modifier.size(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
Text(text = "Don't have an Account?")
Spacer(modifier = Modifier.size(8.dp))
TextButton(onClick = { onNavToSignUpPage.invoke() }) {
Text(text = "SignUp")
}
}
if (loginUiState?.isLoading == true) {
CircularProgressIndicator()
}
LaunchedEffect(key1 = loginViewModel?.hasUserBeenLoggedIn) {
if (loginViewModel?.hasUserBeenLoggedIn == true) {
onNavToHomePage.invoke()
}
}
}
}
....
/*
LoginViewModel.kt file
This is the associated viewModel for the above login screens.
*/
class LoginViewModel(
private val repository: AuthRepository = AuthRepository(),
) : ViewModel() {
val currentUser = repository.currentUser
val hasUserBeenLoggedIn: Boolean
get() = repository.hasUserBeenLoggedIn()
// These next two don't work as it tells me off for
// @Composable invocations can only happen from the context of a @Composable function
// Which i think is ok cause i dont need to remeber the login fields anyway ?
//val loginUiState = remember { mutableStateOf(LoginUiState()) }
//var loginUiState by remember { mutableStateOf(LoginUiState()) }
// This seems to not give a error
var loginUiState by mutableStateOf(LoginUiState())
private set
fun onUserNameChange(userName: String) {
println("onUserNameChange: $userName")
loginUiState = loginUiState.copy(userName = userName)
}
fun onPasswordNameChange(password: String) {
loginUiState = loginUiState.copy(password = password)
}
fun onUserNameChangeSignup(userName: String) {
loginUiState = loginUiState.copy(userNameSignUp = userName)
}
fun onPasswordChangeSignup(password: String) {
loginUiState = loginUiState.copy(passwordSignUp = password)
}
fun onConfirmPasswordChange(password: String) {
loginUiState = loginUiState.copy(confirmPasswordSignUp = password)
}
private fun validateLoginForm() =
loginUiState.userName.isNotBlank() &&
loginUiState.password.isNotBlank()
private fun validateSignupForm() =
loginUiState.userNameSignUp.isNotBlank() &&
loginUiState.passwordSignUp.isNotBlank() &&
loginUiState.confirmPasswordSignUp.isNotBlank()
fun createUser(context: Context) = viewModelScope.launch {
try {
if (!validateSignupForm()) {
throw IllegalArgumentException("Email and password can not be empty")
}
loginUiState = loginUiState.copy(isLoading = true)
if (loginUiState.passwordSignUp !=
loginUiState.confirmPasswordSignUp
) {
throw IllegalArgumentException(
"Password do not match"
)
}
loginUiState = loginUiState.copy(signUpError = null)
repository.createUser(
loginUiState.userNameSignUp,
loginUiState.passwordSignUp
) { isSuccessful ->
loginUiState = if (isSuccessful) {
Toast.makeText(
context,
"success Login",
Toast.LENGTH_SHORT
).show()
loginUiState.copy(isSuccessLogin = true)
} else {
Toast.makeText(
context,
"Failed Login",
Toast.LENGTH_SHORT
).show()
loginUiState.copy(isSuccessLogin = false)
}
}
} catch (e: Exception) {
loginUiState = loginUiState.copy(signUpError = e.localizedMessage)
e.printStackTrace()
} finally {
loginUiState = loginUiState.copy(isLoading = false)
}
}
fun loginUser(context: Context) = viewModelScope.launch {
try {
if (!validateLoginForm()) {
throw IllegalArgumentException("email and password can not be empty")
}
loginUiState = loginUiState.copy(isLoading = true)
loginUiState = loginUiState.copy(loginError = null)
repository.login(
loginUiState.userName,
loginUiState.password
) { isSuccessful ->
loginUiState = if (isSuccessful) {
Toast.makeText(
context,
"success Login",
Toast.LENGTH_SHORT
).show()
loginUiState.copy(isSuccessLogin = true)
} else {
Toast.makeText(
context,
"Failed Login",
Toast.LENGTH_SHORT
).show()
loginUiState.copy(isSuccessLogin = false)
}
}
} catch (e: Exception) {
loginUiState = loginUiState.copy(loginError = e.localizedMessage)
e.printStackTrace()
} finally {
loginUiState = loginUiState.copy(isLoading = false)
}
}
}
data class LoginUiState(
val userName: String = "",
val password: String = "",
val userNameSignUp: String = "",
val passwordSignUp: String = "",
val confirmPasswordSignUp: String = "",
val isLoading: Boolean = false,
val isSuccessLogin: Boolean = false,
val signUpError: String? = null,
val loginError: String? = null,
)
/* MainActivity.kt
*/
class MainActivity : ComponentActivity() {
//private val loginViewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAndroidAppTheme {
val loginViewModel = viewModel(modelClass = LoginViewModel::class.java)
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Navigation(loginViewModel = loginViewModel)
}
}
}
}
}
1条答案
按热度按时间x3naxklr1#
你是否将ViewModel的null值传递到这个Composable中?它甚至是一个可空属性也没有意义。你应该像这样声明它
所以它是不可空的,你甚至不需要把它传递进来。为了单元测试,把它作为一个函数参数仍然是一个很好的实践。
如果参数为null,所有这些?.调用都不做任何事情。这是一个信号,表明参数根本不应该为null。只有当代码期望某些参数有时为null并且没有它仍然有用时,才使用?.。