我正在尝试使用体系结构组件来实现应用程序。我基于此google示例项目: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/repository/UserRepository.kt
但是我遇到了从存储库中以LiveData身份获取的用户始终为null的问题,并且在观察的同时,我仅收到一次null值的通知。
UI
private fun getUserProfile() {
myAccountViewModel.getUserProfile().observe(this, Observer { resource ->
Toast.makeText(this, "User is: ${resource?.data?.id ?: 0}", Toast.LENGTH_LONG).show()
println("User profile: ${resource?.data}")
})
}
查看模型
class UserProfileViewModel
@Inject constructor(val repository: UserProfileRepository) : ViewModel() {
private var userProfile : LiveData<Resource<UserProfile>>
init {
userProfile = repository.loadUserProfile()
}
fun getUserProfile() : LiveData<Resource<UserProfile>> {
return userProfile
}
}
存储库
fun loadUserProfile() : LiveData<Resource<UserProfile>> {
return object : CachedResource<UserProfile, UserProfile>(appExecutors) {
override fun saveCallResult(item: UserProfile) {
dao.insert(item)
}
override fun shouldFetch(data: UserProfile?): Boolean {
// always fetch new user profile from web
return true
}
override fun loadFromDb(): LiveData<UserProfile> {
// load user profile based on identifier
// it is stored with Token in Shared Preferences
val userId = authRepository.getAuthToken().value?._id
if (userId != null) {
return dao.load(userId)
}
return MutableLiveData<UserProfile>()
}
override fun createCall(): LiveData<Response<UserProfile>> {
return api.getUserProfile()
}
}.asLiveData()
}
DAO
@Dao
interface UserProfileDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(userProfile: UserProfile)
@Query("SELECT * FROM user_profile WHERE id = :id")
fun load(id: String) : LiveData<UserProfile>
@Query("SELECT * FROM user_profile")
fun loadAll() : LiveData<List<UserProfile>>
}
CachedResource的实现-基于来自的NetworkBoundResource Google Sample Project
abstract class CachedResource<EntityType, ResponseType>
@MainThread constructor(private val appExecutors: AppExecutors){
// Called to save the result of the API response into the database
@WorkerThread
protected abstract fun saveCallResult(item: ResponseType)
// Called with the data in the database to decide whether it should be
// fetched from the network.
@MainThread
protected abstract fun shouldFetch(data: EntityType?): Boolean
// Called to get the cached data from the database
@MainThread
protected abstract fun loadFromDb(): LiveData<EntityType>
// Called to create the API call.
@MainThread
protected abstract fun createCall(): LiveData<Response<ResponseType>>
@WorkerThread
protected open fun processResponse(response: Response<ResponseType>) = response.data
// Called when the fetch fails. The child class may want to reset components
// like rate limiter.
protected open fun onFetchFailed() {}
// returns a LiveData that represents the resource, implemented
// in the base class.
fun asLiveData() = result as LiveData<Resource<ResponseType>>
// IMPLEMENTATION
private val result = MediatorLiveData<Resource<EntityType>>()
init {
result.value = Resource.loading(null)
@Suppress("LeakingThis")
val dbSource = loadFromDb()
result.addSource(dbSource) { data ->
result.removeSource(dbSource)
// data loaded from database
if (shouldFetch(data)) {
fetchFromNetwork(dbSource)
} else {
result.addSource(dbSource) { newData ->
setValue(Resource.success(newData))
}
}
}
}
@MainThread
private fun setValue(newValue: Resource<EntityType>) {
if (result.value != newValue) {
result.value = newValue
}
}
private fun fetchFromNetwork(dbSource: LiveData<EntityType>) {
val apiSource = createCall()
// we re-attach dbSource as a new source, it will dispatch its latest value quickly
result.addSource(dbSource) { newData ->
setValue(Resource.loading(newData))
}
result.addSource(apiSource) { response ->
result.removeSource(apiSource)
result.removeSource(dbSource)
when(response?.status) {
Response.Status.SUCCESS -> {
appExecutors.diskIO().execute {
val data = processResponse(response)
if (data != null) {
saveCallResult(data)
appExecutors.mainThread().execute {
// we specially request a new live data,
// otherwise we will get immediately last cached value,
// which may not be updated with latest results received from network.
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
}
}
Response.Status.EMPTY -> {
appExecutors.mainThread().execute {
// reload from disk whatever we had
result.addSource(loadFromDb()) { newData ->
setValue(Resource.success(newData))
}
}
}
Response.Status.ERROR -> {
onFetchFailed()
result.addSource(dbSource) { newData ->
setValue(Resource.error(response.error, newData))
}
}
}
}
}
}
我已经测试了此架构,并且可以正确地从API加载数据,然后实时数据和视图模型也可以工作。也保存数据Room Database也可以正常工作。我已经从应用程序数据库目录下载了.db文件,其中包含正确的记录。
我不知道为什么当有新数据被提取并保存到Room时,LiveData observable为什么不通知我的观察者,在Resource.loading()情况下,它也不会返回缓存的数据。
感谢您的帮助