Composable is recomposing endlessly after flow collect - android

My composable is recomposing endlessly after flow collect and navigating to a new screen.
I can't understand why.
I'm using Firebase for Auth with Email and Password.
I had to put some Log.i to test my function and my composable, and yes, my Main composable (SignUp) is recomposing endlessly after navigating.
ViewModel
// Firebase auth
private val _signUpState = mutableStateOf<Resources<Any>>(Resources.success(false))
val signUpState: State<Resources<Any>> = _signUpState
fun firebaseSignUp(email: String, password: String) {
job = viewModelScope.launch(Dispatchers.IO) {
firebaseAuth.firebaseSignUp(email = email, password = password).collect {
_signUpState.value = it
Log.i("balito", "polipop")
}
}
}
fun stop() {
job?.cancel()
}
SignUp
#Composable
fun SignUp(
navController: NavController,
signUpViewModel: SignUpViewModel = hiltViewModel()
) {
val localFocusManager = LocalFocusManager.current
Log.i("salut", "salut toi")
Column(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
.padding(16.dp)
.background(color = PrimaryColor)
) {
BackButton(navController = navController)
Spacer(modifier = Modifier.height(30.dp))
Text(
text = stringResource(id = R.string.sinscrire),
fontFamily = visby,
fontWeight = FontWeight.SemiBold,
fontSize = 28.sp,
color = Color.White
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = stringResource(R.string.prenez_votre_sante_en_main),
fontFamily = visby,
fontWeight = FontWeight.SemiBold,
fontSize = 20.sp,
color = Grey
)
Spacer(modifier = Modifier.height(20.dp))
Email(signUpViewModel = signUpViewModel, localFocusManager = localFocusManager)
Spacer(modifier = Modifier.height(16.dp))
Password(signUpViewModel = signUpViewModel, localFocusManager = localFocusManager)
Spacer(modifier = Modifier.height(30.dp))
Button(value = stringResource(R.string.continuer), type = Type.Valid.name) {
localFocusManager.clearFocus()
signUpViewModel.firebaseSignUp(signUpViewModel.emailInput.value, signUpViewModel.passwordInput.value)
}
Spacer(modifier = Modifier.height(16.dp))
Button(value = stringResource(R.string.inscription_avec_google), type = Type.Other.name) {
}
Spacer(modifier = Modifier.weight(1f))
Box(
modifier = Modifier
.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
ClickableTextInfo(stringResource(id = R.string.deja_un_compte_se_connecter), onClick = {})
}
}
Response(navController = navController, signUpViewModel = signUpViewModel)
DisposableEffect(key1 = signUpViewModel.signUpState.value == Resources.success(true)) {
onDispose {
signUpViewModel.stop()
Log.i("fin", "fin")
}
}
}
#Composable
private fun Response(
navController: NavController,
signUpViewModel: SignUpViewModel
) {
when (val response = signUpViewModel.signUpState.value) {
is Resources.Loading<*> -> {
//WaitingLoaderProgress(loading = true)
}
is Resources.Success<*> -> {
response.data.also {
Log.i("lolipop", "lolipopi")
if (it == true) {
navController.navigate(Screen.SignUpConfirmation.route)
}
}
}
is Resources.Failure<*> -> {
// response.throwable.also {
// Log.d(TAG, it)
// }
}
}
}

During navigation transition recomposition happens multiple times because of animations, and you call navController.navigate on each recomposition.
You should not cause side effects or change the state directly from the composable builder, because this will be performed on each recomposition, which is not expected in cases like animation.
Instead you should use side effects. In your case, LaunchedEffect should be used.
if (response.data) {
LaunchedEffect(Unit) {
Log.i("lolipop", "lolipopi")
navController.navigate(Screen.SignUpConfirmation.route)
}
}

Related

How to Updating jetpack compose with MutableLiveData.observeAsState() more often

fun Conversation(mainViewModel: MainViewModel) {
var stat = mainViewModel.getTweet().observeAsState()
val listState = rememberScrollState()
stat?.let {
if( stat.value == null){
Log.d("DAD","DASSA")
// null 값이면 로딩 에니메이션 뜨게 만들면 좋을듯>????
}
else{
Log.d("DAD","됐다")
var author = it.value!!.first
var messages = it.value!!.second
LazyColumn(modifier = Modifier.verticalScroll(listState).height(100.dp)) {
items(messages) { message ->
MessageCard(author, message)
}
}
}
}
}
In this composable, it seems like the composable "DID NOT" update value of mutableLivedata( mainViewModel.getTweet()). but I confuse about below code
fun InfoCard(mainViewModel: MainViewModel){
val Name = mainViewModel.getData().observeAsState()
Name.value?.let { it ->
Row(modifier = Modifier
.fillMaxWidth()
.background(shape = RoundedCornerShape(6.dp), color = Color.LightGray)
.padding(30.dp)
.height(40.dp),
verticalAlignment = Alignment.CenterVertically
) {
Spacer(modifier = Modifier.height(15.dp))
Text(it.get(0), modifier = Modifier.width(200.dp), textAlign = TextAlign.Center)
Spacer(modifier = Modifier.width(30.dp))
Text(text = it.get(1))
}
}
}
it also composable and use value of mutableLivedata( mainViewModel.getData())
But IT UPDATE VERY WELL..
what is a difference and How can I update first composable..
Self solution for someone like me.
Change
val Name = mainViewModel.getData().observeAsState()
to
val Name by mainViewModel.getData().observeAsState()

Jetpack Compose - Search bar won't allow me to search and the cursor will not show up

I have a custom search bar will not allow me to place a cursor and type text into it. Before adding the viewmodel and state, I was able to have the TextField work and allow typing text into it.
I've tried using state variables within the composable instead of separating the logic into the view model and unfortunatly recieved the same result. I have a feeling it's something simple that I'm missing but can't quite find it.
Custom Search Bar:
#Composable
fun SearchBar(
modifier: Modifier,
viewModel: ToolSetListViewModel = hiltViewModel()
){
Surface (
modifier = modifier
.fillMaxWidth()
.height(74.dp)
.padding(20.dp, 15.dp, 20.dp, 0.dp),
elevation = 10.dp,
color = MaterialTheme.colors.primary,
shape = RoundedCornerShape(25)
){
TextField(
modifier = modifier
.fillMaxWidth(),
value = viewModel.searchText,
onValueChange = {
viewModel.onEvent(ToolSetListEvent.OnSearchToolSet(it))
},
placeholder = {
Text(
modifier = modifier
.alpha(ContentAlpha.medium),
text = "Search...",
color = White
)
},
textStyle = TextStyle(
fontSize = MaterialTheme.typography.subtitle1.fontSize,
),
singleLine = true,
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent,
cursorColor = White
)
)
}
Screen:
#Composable
fun ToolSetListScreen(
onNavigate: (UiEvent.Navigate) -> Unit,
viewModel: ToolSetListViewModel = hiltViewModel()
) {
val toolSets = viewModel.toolSets.collectAsState(initial = emptyList())
LaunchedEffect(key1 = true) {
viewModel.uiEvent.collect { event ->
when(event) {
is UiEvent.Navigate -> onNavigate(event)
}
}
}
Scaffold (
floatingActionButton = {
FloatingActionButton(onClick = {
viewModel.onEvent(ToolSetListEvent.OnAddToolSetClick)
}) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Add")
}
}
) {
SearchBar(
modifier = Modifier,
)
LazyColumn(
modifier = Modifier.fillMaxSize()
.padding(0.dp, 20.dp)
) {
items(toolSets.value) { toolset ->
ToolSetItem(
toolSet = toolset,
onEvent = viewModel::onEvent,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clickable {
viewModel.onEvent(ToolSetListEvent.OnToolSetClick(toolset))
}
)
}
}
}
}
ViewModel:
#HiltViewModel
class ToolSetListViewModel #Inject constructor(
private val repository: ToolSetRepository
): ViewModel() {
val toolSets = repository.getAllToolSets()
private val _uiEvent = Channel<UiEvent>()
val uiEvent = _uiEvent.receiveAsFlow()
var searchText by mutableStateOf("")
private set
fun onEvent(event: ToolSetListEvent) {
when(event) {
is ToolSetListEvent.OnToolSetClick -> {
sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TOOL_SET + "?PONumber=${event.toolSet.PONumber}"))
}
is ToolSetListEvent.OnDeleteToolSetClick -> {
viewModelScope.launch {
repository.deleteToolSet(event.toolset)
}
}
is ToolSetListEvent.OnAddToolSetClick -> {
sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TOOL_SET))
}
is ToolSetListEvent.OnSearchToolSet -> {
viewModelScope.launch {
if (event.searchText.isNotBlank()) {
searchText = event.searchText
repository.getToolSetByPO(event.searchText)
}
}
}
}
}
private fun sendUiEvent(event: UiEvent) {
viewModelScope.launch {
_uiEvent.send(event)
}
}
MainActivity:
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PunchesManagerTheme {
val navController = rememberNavController()
Scaffold (
content = {
Navigation(navController)
},
bottomBar = { BottomNavigationBar(navController = navController) },
)
}
}
}
}
#Composable
fun BottomNavigationBar(navController: NavHostController) {
BottomNavigation {
val backStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = backStackEntry?.destination?.route
NavBarItems.bottomNavItem.forEach { navItem ->
BottomNavigationItem(selected = currentRoute == navItem.route, onClick = {
navController.navigate(navItem.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = false
}
launchSingleTop = true
restoreState = true
}
},
icon = {
Icon(imageVector = navItem.icon,
contentDescription = navItem.name)
},
label = {
Text(text = navItem.name)
},
)
}
}
}
Any suggestions on what the issue may be?
I think I found the fix!
I adjusted the layout of my scaffoled in my ToolSetListScreen:
Scaffold (
content = {
SearchBar(
modifier = Modifier,
)
LazyColumn(
modifier = Modifier.fillMaxSize()
.padding(0.dp, 75.dp)
) {
items(toolSets.value) { toolset ->
ToolSetItem(
toolSet = toolset,
onEvent = viewModel::onEvent,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clickable {
viewModel.onEvent(ToolSetListEvent.OnToolSetClick(toolset))
}
)
}
}
},
floatingActionButton = {
FloatingActionButton(onClick = {
viewModel.onEvent(ToolSetListEvent.OnAddToolSetClick)
}) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Add")
}
}
)
}
and this seemed to work.

I don't understand why Jetpack Compose don't recompose when I change list into a mutableStateOf?

I try to create an quiz app with Jetpack Compose but I need some response to understand how Jetpack Compose recompose after some change into a mutableStateOf.
After choosing a response I call the function into my viewModel to change the state of response to change background color. But if I don't add this code
state = state.copy(
isLoading = true
)
The background color doesn't change, why ?
Here complete code of Composable question's screen :
#Composable
fun QuizzScreen(
viewModel: QuizzViewModel,
modifier: Modifier = Modifier
) {
val state = remember {
viewModel.state
}
Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Center
) {
if (state.isLoading) {
CircularProgressIndicator()
} else if (state.error != null) {
Text(
text = state.error,
color = MaterialTheme.colors.error
)
} else {
Column(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = Html.fromHtml(state.question, Html.FROM_HTML_MODE_LEGACY).toString(),
color = MaterialTheme.colors.onSurface,
fontSize = 32.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
)
Spacer(modifier = Modifier.height(48.dp))
for (response: Response in state.responses) {
Response(response) {
viewModel.checkAnwser(response)
}
}
}
}
}
}
#Composable
fun Response(
response: Response,
onClick: () -> Unit
) {
Spacer(modifier = Modifier.height(24.dp))
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = if (response.state == ResponseStatus.Init) MaterialTheme.colors.surface else if (response.state == ResponseStatus.Correct) Color.Green else Color.Red),
onClick = onClick
) {
Text(
text = Html.fromHtml(response.response, Html.FROM_HTML_MODE_LEGACY).toString(),
fontSize = 24.sp,
textAlign = TextAlign.Center,
color = Color.White,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
)
}
}
And now my ViewModel
class QuizzViewModel(
val repository: QuizzRepository
) : ViewModel() {
private val _isLoading = MutableStateFlow(true)
var isLoading = _isLoading.asStateFlow()
var state by mutableStateOf(QuizzState())
init {
viewModelScope.launch {
loadQuizz()
}
}
suspend fun loadQuizz() {
state = state.copy(isLoading = true)
when (val quizzLocal = repository.getLocalQuizz()) {
is Resource.Success -> {
val quizz: Quizz = quizzLocal.data!!
state = state.copy(
question = quizz.question,
responses = quizz.responses.map { response ->
Response(
response,
ResponseStatus.Init
)
},
correctAnswer = quizz.correctAnswer,
isLoading = false
)
}
is Resource.Error -> {
state = state.copy(
isLoading = false,
error = quizzLocal.message!!
)
}
else -> Unit
}
_isLoading.value = false
}
fun checkAnwser(response: Response) {
val correctAnwser = (response.response == state.correctAnswer)
state.responses.map {
if (!correctAnwser && it == response) {
it.state = ResponseStatus.Error
}
if (it.response == state.correctAnswer) {
it.state = ResponseStatus.Correct
}
}
// If i comment this,
// not recompose
state = state.copy(
isLoading = true
)
}
}
I think the problem is that you are caching ViewModel's state by using remember{viewModel.state}
#Composable
fun QuizzScreen(
viewModel: QuizzViewModel,
modifier: Modifier = Modifier
) {
val state = remember {
viewModel.state
}
Using remember{} caches the state to the default value which makes the state remain unchanged even with recomposition.
Instead use val state = viewModel.state

Using the value of observeAsState() in an if statement, causes major UI lag

I have a login page with two TextFields, one for the username and one for the password. When the submit button is clicked, I call my loginViewModel's login(username,passwd) method in order to asynchronously authenticate the user. Once the authentication result is received, I post the value to the ViewModel's mutableLiveData in order for the value to be observed in my Login composable through loginViewModel.wasLoginSuccessful.observeAsState(false).
The problem occurs with the if (isUserAuthenticated) onSuccessfulLogin() since when the authentication result is true, I successfully get "redirected" to the home page but everything is super laggy, and using the profiler I can see the Memory & CPU usage fluctuating a lot. If I remove the if statement and make it so that the onSuccessfullLogin() is called unconditionally, everything works just fine without any issues. I'm guessing that this is a side-effect through recomposition and/or a memory leak but I can't pinpoint it.
Login page:
#Composable
fun Login(navController: NavController) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally
) {
LoginContent() {
navController.navigate(Screens.DASHBOARD_MYEWAY.navRoute) {
popUpTo(0)
}
}
LoginFooter() {
navController.navigate(NavigationGraphs.ACTIVATION.route)
}
}
}
#Composable
private fun LoginContent(onSuccessfulLogin: () -> Unit) {
val loginViewModel: LoginViewModel = remember { getKoin().get() }
val isUserAuthenticated by loginViewModel.wasLoginSuccessful.observeAsState(false)
val username = remember { mutableStateOf("") }
val password = remember { mutableStateOf("") }
val scope = rememberCoroutineScope()
Column(
modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
TopBarPadding()
Spacer(modifier = Modifier.padding(24.fixedDp()))
Text(
text = LocalContext.current.getString(R.string.welcome_plural),
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = colorResource(id = R.color.Black_100),
textAlign = TextAlign.Start,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.padding(8.fixedDp()))
Text(
text = LocalContext.current.getString(R.string.please_enter_password_to_proceed),
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = colorResource(id = R.color.Black_100)
)
Spacer(modifier = Modifier.padding(30.fixedDp()))
InputField(
label = LocalContext.current.getString(R.string.username),
onTextChangeCallback = { username.value = it })
Spacer(modifier = Modifier.padding(26.fixedDp()))
InputField(
label = LocalContext.current.getString(R.string.password),
isPassword = true,
canReveal = true,
onTextChangeCallback = { password.value = it })
Spacer(modifier = Modifier.padding(32.fixedDp()))
MyButton(text = LocalContext.current.getString(R.string.login),
buttonType = MyButtonType.PRIMARY,
onClick = {
scope.launch {
loginViewModel.login(username.value, password.value)
}
})
Spacer(modifier = Modifier.padding(24.fixedDp()))
Link(text = LocalContext.current.getString(R.string.forgot_my_password))
}
if (isUserAuthenticated) onSuccessfulLogin()
}
#Composable
private fun LoginFooter(onClickEwayActivation: () -> Unit) {
Column(
modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = LocalContext.current.getString(R.string.you_have_no_username),
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = colorResource(id = R.color.Black_100)
)
Row {
Text(
text = LocalContext.current.getString(R.string.do_string),
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
color = colorResource(id = R.color.Black_100)
)
Link(
text = LocalContext.current.getString(R.string.my_eway_activation),
onClick = { onClickEwayActivation() })
}
}
Spacer(modifier = Modifier.padding(40.fixedDp()))
}
}
LoginViewModel:
class LoginViewModel(
private val authenticationUseCase: AuthenticationControllerUseCase
) : ViewModel() {
private val _loginResult = MutableLiveData(false)
val wasLoginSuccessful: LiveData<Boolean> = _loginResult
fun login(username: String, password: String) {
viewModelScope.launch(Dispatchers.IO) {
when (val response = authenticationUseCase.validateUser(username, password)) {
is ResponseResult.Success -> {
response.data?.userLoginId!!.saveStringThroughKeystore(PreferenceKeys.ACCOUNT_ID.key)
_loginResult.postValue(true)
}
is ResponseResult.Error -> {
_loginResult.postValue(false)
Log.d(
"USER_VALIDATION_ERROR",
"Network Call Failed With Exception: " + response.exception.message
)
}
}
}
}
}

How to resolve infinite loop causing by state change in android compose with firebase authentication

I have an application with sign in via email and password with firebase.
I'm using jetpack compose with MVVM and clean architecture.
When getting from firebase that login was done I'm getting true in the view model and then listening to this state change in the composable.
The problem is I'm always get in the when statement of the sign in state and it cause an infinite loop that always navigating to the next composable.
Copying here the specific line from the view:
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)
}
}
My View:
#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)
}
}
}
My View model:
#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
}
}
}
}
My Sign in function:
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))
}
}
Navigation is also a side effect, after each frame of the animation you will again navigate to your destination. Change your block to:
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)
}
}

Categories

Resources