我正在使用 Android Architecture Components 编写一个应用程序,最初基于著名的文章,但是现在已经过时且不准确,因此基于其他文档、文章和视频,我使用最新的组件构建了一些东西,结果在一个非常简单的架构,代码很少。
这个想法是应用程序从其表为空开始,然后从 Firestore 数据库中读取以获取其数据,将数据存储在本地 SqlLite 数据库中(使用 Room)并显示更新的数据。每当在 Firestore 上更新数据时,都应该在 SqlLite 中更新数据并更新 UI。
但是,我的 UI(现在只是一个文本框)仅在应用程序启动时更新,并且在数据库被修改后永远不会更新。
波特罗道
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
}
Portero存储库
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()}
}
}
按钮视图模型
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)
}
}
按钮片段
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 中没有异常或错误。但是UI没有更新。