Search code examples
androidandroid-roomandroid-jetpack-composeandroid-livedata

Android Room - Return Row from SELECT Query


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.


Solution

  • 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.