In my app, a Text composable calls the roomViewModel to display the energyState, but it shows null. I want the observer (the Text composable), to display the value of the "energy" column associated with the email the user inputs when the app starts. Here is my roomViewModel, which calls the repository, which calls the dao
class RoomViewModel(private val repository: Repository) : ViewModel() {
var email = ""
val energyState: StateFlow<Int?> = flowOf( email)
.flatMapLatest {
if (it == null) flowOf(null)
else repository.getEnergy(it)
}
.stateIn(scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = null,
)
fun upsertInfo(info: Info) {
viewModelScope.launch {
repository.upsertInfo(info)
}
}
fun updateEnergy(energy: Int, email: String) {
viewModelScope.launch {
repository.updateEnergy(energy, email)
//energyState = energy // Update energyState directly
}
}
Here is the repository
class Repository(private val db: InfoDatabase) {
suspend fun upsertInfo(info: Info) {
db.dao.upsertInfo(info)
}
suspend fun updateEnergy(energy: Int, email: String) {
db.dao.updateEnergy(energy, email)
}
fun getEnergy(email: String): Flow<Int> {
val energy = db.dao.getEnergy(email)
return energy
}
And here is the dao
@Query("SELECT energy FROM info WHERE email = :email LIMIT 1")
fun getEnergy(email: String): Flow<Int>
I think that the energyState value is set to null because I don't have an email when the roomViewModel is initialized.
My signInViewModel is responsable to handle the logic with the signing in process when I get the email from the user by a login screen, a register screen or a previously logged user. It assigns the value of a firebaseAuth class currentUser email to the signinViewModel email mutableStateFlow variable and it keeps track of the authState of the firebase class.
class SignInViewModel(private val aut: FirebaseAuth): ViewModel(){
private val _authState = MutableLiveData<AuthState>()
val authState: LiveData<AuthState> = _authState
private val _email = MutableStateFlow<String?>("Lavoe")
val email = _email.asStateFlow()
init{
checkAutStatus()
}
fun onSignInResult(result:SignInResult){
_state.update{it.copy(
isSignInSuccessful = result.data != null,
signInError = result.errorMessage
)}
}
fun checkAutStatus(){
val currentUser = aut.currentUser
if(currentUser == null) {
_authState.value = AuthState.Unauthenticated
}else{
_email.value = currentUser.email
_authState.value = AuthState.Authenticated
}
}
fun login(email: String,password: String){
if(email.isEmpty() || password.isEmpty()){
_authState.value = AuthState.Error("Email or password can't be empty")
return
}
_authState.value = AuthState.Loading
aut.signInWithEmailAndPassword(email, password)
.addOnCompleteListener{task ->
if (task.isSuccessful){
_authState.value = AuthState.Authenticated
}else{
_authState.value = AuthState.Error(task.exception?.message?:"Something went wrong")
}
}
}
If my authentification state is "Authenticated" i assign to my roomViewModel the value of signInViewModel.email.value
LaunchedEffect(authState.value) {
Log.i("tag", "authState in signInwithEmailScreen = ${authState}")
when (authState.value){
is AuthState.Authenticated -> {
navController.navigate(route = "Intro_Screen")
roomViewModel.email = signInViewModel.email.value ?: ""
If not, the user enters his email and presses a button, which receives the email from the outlineTextField, like this
Button(onClick = {
roomViewModel.email = email
My screen pattern goes like this MainActivity (roomDatabase initialized) -> ChooseSignInMethod -> LoginScreen (insert email) or RegisterScreen (insert email) or is already athenticated -> game screen (shows energyState value).
The problem I face is that I can't retrieve the energy value from my Room Database. It only shows null
It used to work great before I needed the email (I just showed the first user)
I tried to fiddle with the data types and different functions to retrieve the data but nothing worked.
It isn't quite clear what the individual view models actually contain, how they are related and what they need to do, so I will keep this answer more general.
Usually you want to use view model properties to expose your repository flows as StateFlows. When a repository flow needs a parameter, like the email of your repository's getEnergy
function, you have basically two options:
Provide email during object creation of the view model. Then you can access it in the declaration of your StateFlow property:
class MyViewModel(
private val repository: Repository,
email: String,
) : ViewModel() {
val energyState: StateFlow<Int?> = repository.getEnergy(email)
.stateIn(...)
}
This way the email is fixed and can never be changed for this view model instance.
Keep email variable by storing it in a Flow itself. Then you can base your repository flow onto the email flow like this:
class MyViewModel(
private val repository: Repository,
) : ViewModel() {
private val email = MutableStateFlow<String?>(null)
val energyState: StateFlow<Int?> = email
.flatMapLatest {
if (it == null) flowOf(null)
else repository.getEnergy(it)
}
.stateIn(...)
}
Whenever the email is changed the energyState
is updated accordingly.
Now, first you need to decide where the single source of truth for email should be. Then you need to decide how that should be provided to your various view models. And then you can use the above options to retrieve the energy
flow for that email.