Vladimir
08/05/2024, 5:45 PMLaunchedEffect doesn't re-render when the mutable state changes inside the ViewModelVladimir
08/05/2024, 5:47 PMHome screen when the user enters their credentials and presses the login button inside the Login composable.
The LaunchedEffect with this redirect logic is located in the main MyApp composable wrapper.
MyApp composable wrapper that is run from within `MainActivity`:
@Composable
fun MyApp(sessionViewModel: SessionViewModel = hiltViewModel()) {
val navController = rememberNavController()
MyAppNavHost(navController)
val sessionStatus by sessionViewModel.sessionStatus
LaunchedEffect(sessionStatus) {
println("RERENDERED") // gets printed only once
if (!sessionStatus) {
sessionViewModel.checkSession()
}
if (sessionStatus) {
navController.navigate(Home) {
popUpTo(Login) { inclusive = true }
}
} else {
navController.navigate(route = Login) {
popUpTo(Home) { inclusive = true }
}
}
}
}
Session ViewModel that contains the state variables and the function to update them:
@HiltViewModel
class SessionViewModel @Inject constructor(private val sessionManager: SessionManager) : ViewModel() {
private val _sessionStatus : MutableState<Boolean> = mutableStateOf(false)
var sessionStatus: MutableState<Boolean> = _sessionStatus
fun createSession(credentials: Credentials) {
viewModelScope.launch {
_sessionStatus.value = sessionCreated.isSuccess
_isAuthenticating.value = false
}
}
}
The LoginScreen composable contains the input fields and a button that, when pressed, triggers createSession in the viewmodel. The login itself is successful, however the LaunchedEffect inside MyApp does not trigger a rerender despite the _sessionStatus value gets updated inside the session viewmodel.
What am I doing wrong? Could it be that it is because the button press originates inside a LoginScreen composable and not inside the MyApp composable and the latter cannot detect the sessionStatus change that this press triggers in the viewmodel?Vladimir
08/05/2024, 5:54 PMSessionViewModel instance that is tied to the LoginScreen and therefore not visible MyApp?
fun NavGraphBuilder.loginScreenGraph() {
composable<Login> {
val sessionViewModel: SessionViewModel = hiltViewModel()
LoginScreen(onLogin = { credentials -> sessionViewModel.createSession(credentials) }, isAuthenticating = sessionViewModel.isAuthenticating.value)
}
}
@Serializable
object LoginIan Lake
08/05/2024, 5:56 PMIan Lake
08/05/2024, 5:57 PMLocalViewModelStoreOwner.current at the MyApp level, you can pass that down to your screen and pass that to hiltViewModelZach Klippenstein (he/him) [MOD]
08/05/2024, 5:58 PMIan Lake
08/05/2024, 5:59 PMVladimir
08/05/2024, 5:59 PMVladimir
08/05/2024, 6:00 PMvar sessionStatus -> val sessionStatusIan Lake
08/05/2024, 6:01 PMVladimir
08/05/2024, 6:02 PMVladimir
08/05/2024, 6:02 PMVladimir
08/05/2024, 6:02 PMIan Lake
08/05/2024, 6:04 PMval owner = LocalViewModelStoreOwner.current, fun NavGraphBuilder.loginScreenGraph(owner: ViewModelStoreOwner), and hiltViewModel(owner)Ian Lake
08/05/2024, 6:04 PMIan Lake
08/05/2024, 6:06 PMVladimir
08/05/2024, 6:07 PMIan Lake
08/05/2024, 6:08 PMVladimir
08/05/2024, 6:08 PM@Composable
fun MyAppNavHost(navController: NavHostController) {
NavHost(navController = navController, startDestination = Login) {
homeScreenGraph()
loginScreenGraph()
}
}Vladimir
08/05/2024, 6:09 PMMyAppNavHost function and pass it down?Ian Lake
08/05/2024, 6:10 PMLocalViewModelStoreOwner, so either spot is fine for creating the variableVladimir
08/05/2024, 6:12 PMIan Lake
08/05/2024, 6:14 PM!! or checkNotNull() around it let the compiler know you know it will always be availableVladimir
08/05/2024, 6:18 PMsince they are both outside the NavHost, will have the sameLocalViewModelStoreOwner
Ian Lake
08/05/2024, 6:19 PMVladimir
08/05/2024, 6:19 PMval owner = LocalViewModelStoreOwner.current!! the conventional way to go about it? Or is there a way to actually check for null and somehow troubleshoot it?Vladimir
08/05/2024, 6:20 PMIan Lake
08/05/2024, 6:20 PMcheckNotNull() optionVladimir
08/05/2024, 6:21 PM