数据库更改时,UI不更新

问题描述

我正在使用Android体系结构组件编写应用程序,该应用程序最初是基于著名的文章,但是现在已经过时且不准确,因此基于其他文档,文章和视频,我使用最新的组件来构建一些东西。结果是一个非常简单的架构,只需要很少的代码。

想法是该应用程序从其表为空开始,然后从Firestore数据库读取以获取其数据,将数据存储在本地SqlLite DB中(使用Room)并显示更新的数据。每当在Firestore上更新数据时,都应该在SqlLite中更新数据并更新UI。

但是,我的UI(现在仅是一个文本框)仅在应用程序启动时更新,而在修改数据库之后也不会更新。

PorteroDao

package com.sarcobjects.portero.db

import androidx.room.*
import com.sarcobjects.portero.entities.Portero
import com.sarcobjects.portero.entities.PorteroWithLevelsAndUnits

@Dao
abstract class PorteroDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    abstract suspend fun insert(portero: Portero): Long

    @Transaction
    @Query("SELECT * FROM Portero WHERE porteroId == :porteroId")
    abstract suspend fun getPortero(porteroId: Long): PorteroWithLevelsAndUnits
}

PorteroRepository

package com.sarcobjects.portero.repository

import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.EventListener
import com.google.firebase.firestore.FirebaseFirestore
import com.sarcobjects.portero.db.PorteroDao
import com.sarcobjects.portero.entities.Portero
import com.sarcobjects.portero.entities.PorteroWithLevelsAndUnits
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import timber.log.Timber.d
import timber.log.Timber.w
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class PorteroRepository @Inject constructor(
    private val porteroDao: PorteroDao,private val firestore: FirebaseFirestore
) {

    @ExperimentalCoroutinesApi
    suspend fun getPortero(porteroId: Long): PorteroWithLevelsAndUnits {
        GlobalScope.launch {refreshPortero(porteroId)}
        val portero = porteroDao.getPortero(porteroId)
        d("Retrieved portero: $portero")
        return portero
    }

    @ExperimentalCoroutinesApi
    private suspend fun refreshPortero(porteroId: Long) {
        d("Refreshing")
        //retrieve from firestore
        retrieveFromFirestore(porteroId)
            .collect { portero ->
                d("Retrieved and collected: $portero")
                porteroDao.insert(portero)
            }
    }

    @ExperimentalCoroutinesApi
    private fun retrieveFromFirestore(porteroId: Long): Flow<Portero> = callbackFlow {
        val callback = EventListener<DocumentSnapshot> { document,e ->
            if (e != null) {
                w(e,"Listen from Firestore failed.")
                close(e)
            }
            d("Read successfully from Firestore")
            if (document != null && document.exists()) {
                //Convert to objects
                val portero = document.toObject(Portero::class.java)
                d("New Portero: ${portero.toString()}")
                offer(portero!!)
            } else {
                d("Portero not found for porteroId: $porteroId")
            }
        }
        val addSnapshotListener = firestore.collection("portero").document(porteroId.toString())
            .addSnapshotListener(callback)
        awaitClose { addSnapshotListener.remove()}
    }
}

ButtonsViewModel

package com.sarcobjects.portero.ui.buttons

import androidx.hilt.Assisted
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.LiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import com.sarcobjects.portero.entities.PorteroWithLevelsAndUnits
import com.sarcobjects.portero.repository.PorteroRepository
import timber.log.Timber.d


class ButtonsViewModel @ViewModelInject
constructor(@Assisted savedStateHandle: SavedStateHandle,porteroRepository: PorteroRepository) : ViewModel() {

    private val porteroId: Long = savedStateHandle["porteroId"] ?: 0
    val portero: LiveData<PorteroWithLevelsAndUnits> = liveData {
        val data = porteroRepository.getPortero(porteroId)
        d("Creating LiveData with: $data")
        emit(data)
    }
}

ButtonsFragment

package com.sarcobjects.portero.ui.buttons

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import com.sarcobjects.portero.R
import com.sarcobjects.portero.entities.PorteroWithLevelsAndUnits
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.buttons_fragment.*
import timber.log.Timber.d

@AndroidEntryPoint
class ButtonsFragment : Fragment() {

    companion object {
        fun newInstance() = ButtonsFragment()
    }

    private val viewModel: ButtonsViewModel by viewModels (
    )

    override fun onCreateView(
        inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?
    ): View {
        return inflater.inflate(R.layout.buttons_fragment,container,false)
    }

    override fun onViewCreated(view: View,savedInstanceState: Bundle?) {
        super.onViewCreated(view,savedInstanceState)
        viewModel.portero.observe(viewLifecycleOwner,Observer<PorteroWithLevelsAndUnits> {porteroWLAU ->
            d("Observing portero: $porteroWLAU")
            message.text = porteroWLAU?.portero?.name ?: "Portero not found."
        })

    }
}

所有依赖项注入似乎都可以(没有NPE),我什至检查了Fragment和ViewModel本身的ViewModel实例是否相同,并且通过Room的持久性是正确的。当我更新Firestore时,新数据实际上已保存到SqlLite中。另外,logcat中没有异常或错误。 但是用户界面未更新。

解决方法

因此,尽管设法不同,我设法找到了一种使这项工作有效的方法。我的想法是每当我写SqlLite时,就让Room触发liveData重新加载,但我从未设法使其生效,但我仍然不知道为什么。

我最后所做的是:

由Firestore中的更新触发从存储库返回流:

    @ExperimentalCoroutinesApi
    fun getPorteroFlow(porteroId: Long): Flow<Portero> = retrieveFromFirestore(porteroId)

    @ExperimentalCoroutinesApi
    private fun retrieveFromFirestore(porteroId: Long): Flow<Portero> = callbackFlow {
        val callback = EventListener<DocumentSnapshot> { document,e ->
            if (e != null) {
                w(e,"Listen from Firestore failed.")
                return@EventListener
            }
            d("Read successfully from Firestore")
            if (document != null && document.exists()) {
                //Convert to objects
                val portero = document.toObject(Portero::class.java)
                d("New Portero: ${portero.toString()}")
                GlobalScope.launch {
                    d("Saved new portero: $portero")
                    porteroDao.insert(portero!!)
                }
                offer(portero!!)
            } else {
                d("Portero not found for porteroId: $porteroId")
            }
        }
        val addSnapshotListener = firestore.collection("portero").document(porteroId.toString()) //.get()
            .addSnapshotListener(callback)
        awaitClose { addSnapshotListener.remove()}
    }

在ViewModel中将流转换为liveData:

    private val porteroId: Long = savedStateHandle["porteroId"] ?: 0
    @ExperimentalCoroutinesApi
    val portero = porteroRepository.getPorteroFlow(porteroId)
        .onStart { porteroRepository.getPortero(porteroId) }
        .asLiveData()
}

(onStart用于在应用启动时从SqlLite读取数据,以防没有互联网且Firestore无法访问的情况。)

这完美无瑕且非常快速,一旦我在Firestore控制台中更新数据,就可以在设备中看到UI更新。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...