How to read a value from Room database via View Model, Repository and DAO?

Viewed 2526

I'm developing an Android app using Room database. I've already learnt to write values to database. As far as reading/fetching is concerned, I could get help only related to fetching whole list of values form the database (and populate them in a RecyclerView). However, I don't know how to fetch a single value from the database based on some criteria.

Following is my code:

User.kt

@Entity(tableName = TABLE_NAME)
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    @ColumnInfo(name = "name") val name: String,
    @ColumnInfo(name = "email") val email: String,
    @ColumnInfo(name = "password") val password: String
)

We get TABLE_NAME from UserDAO.

UserViewModel.kt

class UserViewModel(private val repository: UserRepository) : ViewModel() {

    val allUsers: LiveData<List<User>> = repository.allUsers.asLiveData()

    /**
     * Launching a new coroutine to insert the data in a non-blocking way
     */
    fun insert(user: User) = viewModelScope.launch {
        repository.insert(user)
    }
}

class UserViewModelFactory(private val repository: UserRepository) :
    ViewModelProvider.Factory {
    
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return UserViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

UserRepository.kt

class UserRepository(private val userDAO: UserDAO) {
      
     val allUsers: Flow<List<User>> = userDAO.getAll()

     @Suppress("RedundantSuspendModifier")
     @WorkerThread
     suspend fun insert(user: User) {
          userDAO.insert(user)
     }
}

UserDAO.kt

@Dao
interface UserDAO {

    @Query("SELECT * FROM " + TABLE_NAME)
    fun getAll(): Flow<List<User>>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(user: User)

    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)

    @Query("DELETE FROM " + TABLE_NAME)
    suspend fun deleteAll()


    companion object {
        const val TABLE_NAME: String = "user_table"
    }
}

UserDatabase.kt

@Database(entities = arrayOf(User::class), version = 1, exportSchema = false)
public abstract class UserDatabase : RoomDatabase() {

    abstract fun userDAO(): UserDAO

    private class UserDatabaseCallback(private val scope: CoroutineScope) :
        RoomDatabase.Callback() {
        
        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)
            INSTANCE?.let { database ->
                scope.launch {
                    populateUsers(database.userDAO())
                }
            }
        }

        suspend fun populateUsers(userDAO: UserDAO) {
            userDAO.deleteAll()

            var user = User(name = "Admin", email = "admin@example.com",
                password = "admin123")    
            userDAO.insert(user)
        }
    }

    companion object {
        /**
         * Singleton prevents multiple instances of database opening at the same time
         */
        @Volatile
        private var INSTANCE: UserDatabase? = null

        fun getDatabase(context: Context, scope: CoroutineScope): UserDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    UserDatabase::class.java,
                    "user_database"
                ).addCallback(UserDatabaseCallback(scope))
                    .build()
                INSTANCE = instance
                instance              // return instance
            }
        }
    }

MyApplication.kt

class MyApplication : Application() {

    val applicationScope = CoroutineScope(SupervisorJob())

    // Using by lazy so the database and the repository are only created
    // when they're needed rather than when the application starts
    val database by lazy { UserDatabase.getDatabase(this, applicationScope) }

    val userRepository by lazy { UserRepository(database.userDAO()) }
}

Now, I want to create a login activity and try to login a user based on email and password.

LoginActivity.kt

class LoginActivity : AppCompatActivity() {

    private val userViewModel: UserViewModel by viewModels {
        UserViewModelFactory((application as MyApplication).userRepository)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        btnLogin.setOnClickListener {
            val email=editEmail.text.toString()
            val password=editPassword.text.toString()
           
            /**
             * code to check whether user with this email and password exists or not
             */
        }
    }
}

I don't know how to fetch a user with matching email and password. Please help me with that.

2 Answers

You can add a method inside UserDao which will get the record of the user if available something like below

@Query("SELECT * FROM " + TABLE_NAME + " WHERE email = :email AND password = :password LIMIT 1")
fun isValidUser(email: String, password: String): Flow<User?>

Then in repository add a method to verify if acquired user is not null

 @WorkerThread
 suspend fun isValidUser(user: User): Flow<User?> {
      return userDAO.isValidUser(user.email, user.password)
 }

Then in ViewModel, add one more method like below

val userLiveData: LiveData<User?> = MutableLiveData()

/**
 * Launching a new coroutine to insert the data in a non-blocking way
 */
fun insert(user: User) = viewModelScope.launch {
    repository.insert(user)
}

fun validateUser(email: String, password: String)= viewModelScope.launch{
    repository.isValiduser(user(name = "",email = email, password = password 
        ).collect{
        userLiveData.postValue(it)
 }
    
}

Finally, in activity

btnLogin.setOnClickListener {
            val email=editEmail.text.toString()
            val password=editPassword.text.toString()
           
            userViewModel.validateUser(email, password)
        }

userViewModel.userLiveData.observe(this){
     if(it != null){
       //valid user
     }else{
        //invalid
     }
}

You have to create function in DAO to do that, similar as you created.

@Query("SELECT * FROM " + TABLE_NAME)
fun getAll(): Flow<List<User>>

See this example and learn more about SQL

Related