如何解决由 android 中的状态更改引起的无限循环与 firebase 身份验证组合
How to resolve infinite loop causing by state change in android compose with firebase authentication
我有一个通过电子邮件和密码登录 firebase 的应用程序。
我正在使用 Jetpack Compose 与 MVVM 和干净的架构。
当从 firebase 获取登录完成时,我在视图模型中得到 true,然后在可组合项中监听此状态变化。
问题是我总是进入登录状态的 when 语句,这会导致无限循环,总是导航到下一个可组合项。
将视图中的特定行复制到此处:
when (val response = viewModel.signInState.value) {
is Response.Loading -> LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
is Response.Success -> if (response.data) {
navController?.navigate(Screen.HomeScreen.route)
}
is Response.Error -> LaunchedEffect(Unit) {
scaffoldState.snackbarHostState.showSnackbar("Error signing out. ${response.message}", "", SnackbarDuration.Short)
}
}
我的看法:
@Composable
fun LoginScreen(
navController: NavController?,
viewModel: LoginViewModel = hiltViewModel()
) {
val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState = scaffoldState) {
Column (
modifier = Modifier
.fillMaxHeight()
.background(
Color.White
),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
){
var email = viewModel.email
var pass = viewModel.password
var passwordVisibility = viewModel.passwordVisibility
TitleView(title = "SIGN IN", topImage = Icons.Filled.Person, modifier = Modifier)
Spacer(modifier = Modifier.padding(20.dp))
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
value = email.value,
onValueChange = viewModel::setEmail,
label = { Text( "Email")},
placeholder = { Text("Password") }
)
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
value = pass.value,
onValueChange = viewModel::setPassword,
label = { Text( "Password")},
placeholder = { Text("Password") },
visualTransformation = if (passwordVisibility.value) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = {
val image = if (passwordVisibility.value)
Icons.Filled.Visibility
else Icons.Filled.VisibilityOff
IconButton(onClick = {
viewModel.setPasswordVisibility(!passwordVisibility.value)
}) {
Icon(imageVector = image, "")
}
}
)
Spacer(modifier = Modifier.padding(5.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.border(
width = 2.dp,
brush = SolidColor(Color.Yellow),
shape = RoundedCornerShape(size = 10.dp)
),
onClick = {
viewModel.signInWithEmailAndPassword()
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.White
)
) {
Text(
text = "SIGN IN",
textAlign = TextAlign.Center,
fontSize = 20.sp,
style = TextStyle(
fontWeight = FontWeight.Thin
)
)
}
Spacer(modifier = Modifier.padding(5.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.border(
width = 2.dp,
brush = SolidColor(Color.Yellow),
shape = RoundedCornerShape(size = 10.dp)
),
onClick = {
viewModel.forgotPassword()
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.White
)
) {
Text(
text = "FORGOT PASSWORD",
textAlign = TextAlign.Center,
fontSize = 20.sp,
style = TextStyle(
fontWeight = FontWeight.Thin
)
)
}
}
}
}
when (val response = viewModel.signInState.value) {
is Response.Loading -> LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
is Response.Success -> if (response.data) {
navController?.navigate(Screen.HomeScreen.route)
}
is Response.Error -> LaunchedEffect(Unit) {
scaffoldState.snackbarHostState.showSnackbar("Error signing out. ${response.message}", "", SnackbarDuration.Short)
}
}
when (val response = viewModel.forgotState.value) {
is Response.Loading -> LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
is Response.Success -> if (response.data) {
LaunchedEffect(Unit){
scaffoldState.snackbarHostState.showSnackbar("Please click the link in the verification email sent to you", "", SnackbarDuration.Short)
}
}
is Response.Error -> LaunchedEffect(Unit){
scaffoldState.snackbarHostState.showSnackbar("Error signing out. ${response.message}", "", SnackbarDuration.Short)
}
}
}
我的视图模型:
@HiltViewModel
class LoginViewModel @Inject constructor(
private val useCases: UseCases
) : ViewModel() {
private val _signInState = mutableStateOf<Response<Boolean>>(Response.Success(false))
val signInState: State<Response<Boolean>> = _signInState
private val _forgotState = mutableStateOf<Response<Boolean>>(Response.Success(false))
val forgotState: State<Response<Boolean>> = _forgotState
private val _email = mutableStateOf("")
val email = _email
fun setEmail(email: String) {
_email.value = email
}
private val _password = mutableStateOf("")
val password = _password
fun setPassword(password: String) {
_password.value = password
}
private val _passwordVisibility = mutableStateOf(false)
val passwordVisibility = _passwordVisibility
fun setPasswordVisibility(passwordVisibility: Boolean) {
_passwordVisibility.value = passwordVisibility
}
fun signInWithEmailAndPassword() {
viewModelScope.launch {
useCases.signInWithEmailAndPassword(_email.value, _password.value).collect { response ->
_signInState.value = response
}
}
}
fun forgotPassword() {
viewModelScope.launch {
useCases.forgotPassword(_email.value).collect { response ->
_forgotState.value = response
}
}
}
}
我的登录功能:
override suspend fun signInWithEmailAndPassword(
email: String,
password: String
) = flow {
try {
emit(Response.Loading)
val result = auth.signInWithEmailAndPassword(email, password).await()
if (result.user != null)
emit(Response.Success(true))
else
emit(Response.Success(false))
} catch (e: Exception) {
emit(Response.Error(e.message ?: ERROR_MESSAGE))
}
}
导航也是一个side effect,在动画的每一帧之后您将再次导航到您的目的地。将您的区块更改为:
when (val response = viewModel.signInState.value) {
is Response.Loading -> LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
is Response.Success -> if (response.data) LaunchedEffect(response.data){ navController?.navigate(Screen.HomeScreen.route) }
is Response.Error -> LaunchedEffect(Unit) {
scaffoldState.snackbarHostState.showSnackbar("Error signing out. ${response.message}", "", SnackbarDuration.Short)
}
}
我有一个通过电子邮件和密码登录 firebase 的应用程序。 我正在使用 Jetpack Compose 与 MVVM 和干净的架构。
当从 firebase 获取登录完成时,我在视图模型中得到 true,然后在可组合项中监听此状态变化。
问题是我总是进入登录状态的 when 语句,这会导致无限循环,总是导航到下一个可组合项。
将视图中的特定行复制到此处:
when (val response = viewModel.signInState.value) {
is Response.Loading -> LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
is Response.Success -> if (response.data) {
navController?.navigate(Screen.HomeScreen.route)
}
is Response.Error -> LaunchedEffect(Unit) {
scaffoldState.snackbarHostState.showSnackbar("Error signing out. ${response.message}", "", SnackbarDuration.Short)
}
}
我的看法:
@Composable
fun LoginScreen(
navController: NavController?,
viewModel: LoginViewModel = hiltViewModel()
) {
val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState = scaffoldState) {
Column (
modifier = Modifier
.fillMaxHeight()
.background(
Color.White
),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
){
var email = viewModel.email
var pass = viewModel.password
var passwordVisibility = viewModel.passwordVisibility
TitleView(title = "SIGN IN", topImage = Icons.Filled.Person, modifier = Modifier)
Spacer(modifier = Modifier.padding(20.dp))
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
value = email.value,
onValueChange = viewModel::setEmail,
label = { Text( "Email")},
placeholder = { Text("Password") }
)
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
value = pass.value,
onValueChange = viewModel::setPassword,
label = { Text( "Password")},
placeholder = { Text("Password") },
visualTransformation = if (passwordVisibility.value) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = {
val image = if (passwordVisibility.value)
Icons.Filled.Visibility
else Icons.Filled.VisibilityOff
IconButton(onClick = {
viewModel.setPasswordVisibility(!passwordVisibility.value)
}) {
Icon(imageVector = image, "")
}
}
)
Spacer(modifier = Modifier.padding(5.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.border(
width = 2.dp,
brush = SolidColor(Color.Yellow),
shape = RoundedCornerShape(size = 10.dp)
),
onClick = {
viewModel.signInWithEmailAndPassword()
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.White
)
) {
Text(
text = "SIGN IN",
textAlign = TextAlign.Center,
fontSize = 20.sp,
style = TextStyle(
fontWeight = FontWeight.Thin
)
)
}
Spacer(modifier = Modifier.padding(5.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.border(
width = 2.dp,
brush = SolidColor(Color.Yellow),
shape = RoundedCornerShape(size = 10.dp)
),
onClick = {
viewModel.forgotPassword()
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.White
)
) {
Text(
text = "FORGOT PASSWORD",
textAlign = TextAlign.Center,
fontSize = 20.sp,
style = TextStyle(
fontWeight = FontWeight.Thin
)
)
}
}
}
}
when (val response = viewModel.signInState.value) {
is Response.Loading -> LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
is Response.Success -> if (response.data) {
navController?.navigate(Screen.HomeScreen.route)
}
is Response.Error -> LaunchedEffect(Unit) {
scaffoldState.snackbarHostState.showSnackbar("Error signing out. ${response.message}", "", SnackbarDuration.Short)
}
}
when (val response = viewModel.forgotState.value) {
is Response.Loading -> LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
is Response.Success -> if (response.data) {
LaunchedEffect(Unit){
scaffoldState.snackbarHostState.showSnackbar("Please click the link in the verification email sent to you", "", SnackbarDuration.Short)
}
}
is Response.Error -> LaunchedEffect(Unit){
scaffoldState.snackbarHostState.showSnackbar("Error signing out. ${response.message}", "", SnackbarDuration.Short)
}
}
}
我的视图模型:
@HiltViewModel
class LoginViewModel @Inject constructor(
private val useCases: UseCases
) : ViewModel() {
private val _signInState = mutableStateOf<Response<Boolean>>(Response.Success(false))
val signInState: State<Response<Boolean>> = _signInState
private val _forgotState = mutableStateOf<Response<Boolean>>(Response.Success(false))
val forgotState: State<Response<Boolean>> = _forgotState
private val _email = mutableStateOf("")
val email = _email
fun setEmail(email: String) {
_email.value = email
}
private val _password = mutableStateOf("")
val password = _password
fun setPassword(password: String) {
_password.value = password
}
private val _passwordVisibility = mutableStateOf(false)
val passwordVisibility = _passwordVisibility
fun setPasswordVisibility(passwordVisibility: Boolean) {
_passwordVisibility.value = passwordVisibility
}
fun signInWithEmailAndPassword() {
viewModelScope.launch {
useCases.signInWithEmailAndPassword(_email.value, _password.value).collect { response ->
_signInState.value = response
}
}
}
fun forgotPassword() {
viewModelScope.launch {
useCases.forgotPassword(_email.value).collect { response ->
_forgotState.value = response
}
}
}
}
我的登录功能:
override suspend fun signInWithEmailAndPassword(
email: String,
password: String
) = flow {
try {
emit(Response.Loading)
val result = auth.signInWithEmailAndPassword(email, password).await()
if (result.user != null)
emit(Response.Success(true))
else
emit(Response.Success(false))
} catch (e: Exception) {
emit(Response.Error(e.message ?: ERROR_MESSAGE))
}
}
导航也是一个side effect,在动画的每一帧之后您将再次导航到您的目的地。将您的区块更改为:
when (val response = viewModel.signInState.value) {
is Response.Loading -> LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
is Response.Success -> if (response.data) LaunchedEffect(response.data){ navController?.navigate(Screen.HomeScreen.route) }
is Response.Error -> LaunchedEffect(Unit) {
scaffoldState.snackbarHostState.showSnackbar("Error signing out. ${response.message}", "", SnackbarDuration.Short)
}
}