Android - ViewModel、LiveData、Room 和 Retrofit 以及协程放在 kotlin 中

Posted

技术标签:

【中文标题】Android - ViewModel、LiveData、Room 和 Retrofit 以及协程放在 kotlin 中【英文标题】:Android - ViewModel, LiveData, Room and Retrofit and coroutines put together in kotlin 【发布时间】:2021-04-01 10:43:13 【问题描述】:

我正在学习 android,我坚持让所有这些组件一起工作。我说的是 ViewModel、LiveData、Room、Retrofit 和协程。

所以,我想要实现的目标如下:

当应用程序启动时,我想检查用户是否已登录。如果没有登录,我什么也不做,如果他登录了,我在主要活动中记录他的名字。用户可以通过另一个 api 调用进行身份验证。然后,当用户更新时,主要活动应该反映用户的变化。 我在这里 https://developer.android.com/jetpack/guide 部分遵循了谷歌代码实验室,但它不完整,我迷失了。

以下是代码块:

UserDao.kt

使用 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

这是一个使用 Retrofit 的简单 api 调用

interface UserService 

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

UserRepository.kt

主要活动应该在加载时调用get并刷新用户(调用api并检索用户ID),将用户ID保存在saveStateHandler中,并从刚刚保存的数据库中检索用户。但是在第一次启动时,savedStateHandler 是空的。当我从 api 获取用户时,我将其存储在数据库中,但传递给 userDao.get(userId)userId 变量仍然为空,因此不会从数据库中加载任何内容。我应该在何时何地将用户的 ID 保存在 savedStateHandler 中?我在存储库中没有引用它。 此外,当应用程序启动时,不会触发调用。如何从活动中触发它? 不确定在可变实时数据变量中的何处设置用户以触发活动中的观察者。

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()
    

【问题讨论】:

看来你总是想得到“当前”用户,我得到的正确吗? refreshUser() 不接受任何参数,因此 IMO 也不应该 UserRepository.get()。你可以让refreshUser返回插入的idprivate suspend fun refreshUser(): Long? = withContext(Dispatchers.IO) ,然后用它从DAO中提取UserEntity 【参考方案1】:
private val userId: Long? = savedStateHandle["uid"]

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

应该是

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

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

【讨论】:

以上是关于Android - ViewModel、LiveData、Room 和 Retrofit 以及协程放在 kotlin 中的主要内容,如果未能解决你的问题,请参考以下文章

Android Jetpack架构组件(三)—ViewModel

Android ViewModel组件详解

Android 架构组件 之 ViewModel 学习

Android Jetpack组件 - ViewModel,LiveData使用以及原理

Android 面试之—— ViewModel 总结篇

Android ViewModel的使用详解