Kotlincompose,无法使用viewModel输入文本字段

lmvvr0a8  于 2023-03-30  发布在  Kotlin
关注(0)|答案(1)|浏览(174)

在学习了几年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)

                }
            }
        }
    }
}
x3naxklr

x3naxklr1#

你是否将ViewModel的null值传递到这个Composable中?它甚至是一个可空属性也没有意义。你应该像这样声明它

loginViewModel: LoginViewModel = viewModel()

所以它是不可空的,你甚至不需要把它传递进来。为了单元测试,把它作为一个函数参数仍然是一个很好的实践。
如果参数为null,所有这些?.调用都不做任何事情。这是一个信号,表明参数根本不应该为null。只有当代码期望某些参数有时为null并且没有它仍然有用时,才使用?.。

相关问题