I'm using Android Room to build an offline database. I'm able to perform simple INSERT or DELETE queries but i'm having hard time figuring out how to perform a SELECT with parameters that returns a single row. This is my code right now:
users_details table:
@Entity(tableName = "user_details")
data class User(
@PrimaryKey(autoGenerate = true)
val userId: Int,
var email : String,
val userName: String = "NoName",
)
Dao:
@Dao
interface UserDao {
@Query("SELECT * FROM user_details WHERE email =:email LIMIT 1")
suspend fun findByEmail(email: String) : User
}
Repository:
class UserRepository(private val userDao: UserDao) {
suspend fun findUserByEmail(email: String): User {
return userDao.findByEmail(email)
}
}
ViewModel:
class UserViewModel(application: Application) : AndroidViewModel(application) {
val _user = MutableLiveData<User>()
val user: LiveData<User>
get() = _user
fun findUserByEmail(email: String) {
viewModelScope.launch {
val userByEmail = repository.findUserByEmail(email)
_user.value = userByEmail
Log.d("ViewModel", _user.value.toString()) <--HERE LIVEDATA RESULTS UPDATED AND USER RESULTS LOADED FROM ROOM TABLE
}
}
}
UI Compose
@Composable
fun Screen(navController: NavController, UserViewModel : UserViewModel) {
//CODE FOR GOOGLE-SIGNIN - CODE OUTPUT is "username" and "useremail" in a user object
//Support variable for observing LiveData in UserViewModel
val lifecycleOwner = LocalLifecycleOwner.current
//Manage next Navigation element based on/if the user is already recorded in Room DB
user?.let {
LaunchedEffect(key1 = true) {
navController.popBackStack()
//Check if email used to log-in is already in Database
val isUserInDatabase = mUserViewModel.readAllData.value?.
find { user -> user.email == it.email }
//If it doesn't exist in DB go to FirstLoginScreen to create user
if (isUserInDatabase == null) {
navController.navigate("firstlogin")
}
//If it exists load user details and continue to MainScreen
else {
mUserViewModel.findUserByEmail(it.email!!)
mUserViewModel.user.observe(lifecycleOwner) {
navController.navigate("main")
}
}
}
}
I already read many similar posts but no one has been able to let me fully understand what is wrong.
I tried to use LiveData<User>
too, but i'm surely doing it wrong, because i always get null data even if data in table exists (checked with Log
)
EDIT: Thanks to @CommonsWare comments i have been able to understand that the main issue was related to use these calls in the mainThread. I have been able to avoid crashes implementing viewModelScope.launch()
. Unfortunately i am not still able to get the result i want. Code above is updated.
I found a solution. The issue was caused by the positioning of line:
navController.popBackStack()
The right position for this line is just before navController.navigate("screen")
, like this:
user?.let {
LaunchedEffect(key1 = true) {
//navController.popBackStack() //<- IN THIS POSITION CODE DOESN'T WORK!!!!
//Check if email used to log-in is already in Database
val isUserInDatabase = mUserViewModel.readAllData.value?.
find { user -> user.email == it.email }
//If it doesn't exist in DB go to FirstLoginScreen to create user
if (isUserInDatabase == null) {
navController.popBackStack() //<---HERE IS GOOD!
navController.navigate("firstlogin")
}
//If it exists load user details and continue to MainScreen
else {
mUserViewModel.findUserByEmail(it.email!!)
mUserViewModel.user.observe(lifecycleOwner) {
navController.popBackStack() //<---HERE IS GOOD!
navController.navigate("main")
}
}
}
I still don't understand why the old code doesn't work. In particular i don't understand why the code below popBackStack()
method runs normally (i.e. the mUserViewModel.findUserByEmail(it.email!!)
is called) but i don't get LiveData
updates. I hope expert community members have an explanation.