Android - ViewModel, LiveData, Room and Retrofit and coroutines put together in kotlin

Viewed 896

I am learning Android and I am stuck with making all those components work together. I am talking about ViewModel, LiveData, Room, Retrofit and coroutines.

So, what I want to achieve is the following:

When the application starts up, I want to check whether the user is logged in or not. If it is not logged I do nothing, if he is logged in , i log his name in the main activity. The user can authenticate with another api call. Then, when the user is updated, the main activity should reflect the change to user. I partially followed the google codelab here https://developer.android.com/jetpack/guide , but it's incomplete and I get lost in it.

So following are the code blocks:

UserDao.kt

Database queries using Room DAO

@Dao
interface UserDao {

    @Query("SELECT * FROM user WHERE id = :userId")
    fun get(userId: Long): LiveData<UserEntity?>

    @Insert(onConflict = REPLACE)
    suspend fun insert(userEntity: UserEntity)

    @Query("SELECT COUNT(*) FROM user WHERE last_update >= :timeout")
    suspend fun hasUser(timeout: Long): Boolean
}

UserService.kt

This is a simply api call using Retrofit

interface UserService {

    @GET("users/current-user")
    suspend fun getUser(): Response<User>
}

UserRepository.kt

main activity should call get on load and refresh the user (call the api and retrieve user id), save the user id the in the savedStateHandler, and retrieve the user from the database where it's just been saved. But at the very first startup, savedStateHandler is empty. When I get the user from the api, I store it in the database, but the userId variable passed to userDao.get(userId) is still null so nothing is loaded from the database. Where and when should I save the id of the user in the savedStateHandler? I have no reference of it in the Repository. Also when the application starts up the call is not fired. How can I fire it from the activity? Not sure where to set the user in the mutable live data variable in order to trigger the observer in the activity.

suspend fun insert(userEntity: UserEntity) {
    userDao.insert(userEntity)
}

suspend fun get(userId: Long): LiveData<UserEntity?> {
    refreshUser()
    return userDao.get(userId)
}

private suspend fun refreshUser() = withContext(Dispatchers.IO) {
    launch {
        // Check if user data was fetched recently.
        val userExists = userDao.hasUser(FRESH_TIMEOUT)

        if (!userExists) {
            // Refreshes the data.
            val response = userService.getUser()
            try {
                if (response.isSuccessful) {
                    Log.i("user", "Success ${response.code()} - ${response.message()}")
                    // Updates the database. The LiveData object automatically
                    // refreshes, so we don't need to do anything else here.
                    val body: User? = response.body()
                    val user = UserEntity(
                        body!!.id,
                        body.email,
                        body.name.firstName,
                        body.name.lastName,
                        body.telephone,
                        body.dateOfBirth,
                        System.currentTimeMillis() / 1000
                    )
                    userDao.insert(user)
                } else {
                    Log.w(
                        "user",
                        "Not successful ${response.code()} - ${response.message()}"
                    )
                }
            } catch (e: HttpException) {
                Log.e("user", "Exception ${response.code()} - ${response.message()}")
            }
        }
    }
}

UserViewModel.kt

class UserViewModel @ViewModelInject constructor(
      private val repository: UserRepository,
      @Assisted private val savedStateHandle: SavedStateHandle) : ViewModel() {

    fun insert(userEntity: UserEntity) = viewModelScope.launch {
        repository.insert(userEntity)
    }

    private val userId: Long? = savedStateHandle["uid"]

    val user: LiveData<UserEntity?>
        get() = liveData { repository.get(userId) }
}

MainActivity.kt

    viewModel.user.observe(this) { user ->
        Log.i("USER IS LOGGED", user.toString())
        Toast.makeText(this, "HELLO ${user?.email}!!", Toast.LENGTH_SHORT).show()
    }
1 Answers
private val userId: Long? = savedStateHandle["uid"]

val user: LiveData<UserEntity?>
   get() = liveData { repository.get(userId) }

Should be

private val userId: MutableLiveData<Long> = savedStateHandle.getLiveData("uid")

val user: LiveData<UserEntity?>
    get() = userId.switchMap { userId -> liveData { repository.get(userId) } }
Related