I believe I need to detach Firestore listeners but I can't see how to do this without passing the context to my repository. Below is one example:
Fragment
This method is called in onViewCreated
and simply updates private var sitesList = ArrayList<SiteObject>()
which is used to set list in my recyclerview:
private fun setupObservers() {
Log.d(TAG, "setupObservers()")
businessViewModel.listenToSites().observe(viewLifecycleOwner, Observer { allSites ->
if (sitesList != allSites && allSites != null) {
sitesList.clear()
sitesList.addAll(allSites)
sitesAdapter.setList(sitesList)
sitesAdapter.notifyDataSetChanged()
}
})
}
ViewModel
fun listenToSites(): LiveData<ArrayList<SiteObject>> {
Log.d(TAG, "listenToSites()")
return businessRepository.listenToSites()
}
Repository
This is where the Firestore addSnapshotListener
is created:
fun listenToSites(): LiveData<ArrayList<SiteObject>> {
Log.d(TAG, "listenToSites(), firebaseUser = ${firebaseUser?.uid}")
val userId = firebaseUser?.uid
firestore.collection(SITES).whereEqualTo("users.${userId}", true)
.addSnapshotListener(MetadataChanges.INCLUDE, EventListener { snapshots, e ->
if (e != null) {
Log.w(TAG, "listenToSites(), listen error:", e)
return@EventListener
}
val allSites = ArrayList<SiteObject>()
if (snapshots != null) {
for (document in snapshots) {
val site = document.toObject(SiteObject::class.java)
site.siteID = document.id
allSites.add(site)
}
for (docChange in snapshots.documentChanges) {
when (docChange) {
DocumentChange.Type.ADDED -> Log.d(TAG, "New site: ${docChange.document.data}")
DocumentChange.Type.MODIFIED -> Log.d(TAG, "Modified site: ${docChange.document.data}")
DocumentChange.Type.REMOVED -> Log.d(TAG, "Removed site: ${docChange.document.data}")
}
}
val source = if (snapshots.metadata.isFromCache){
"local cache"
}
else {
"server"
}
Log.d(TAG, "Sites data fetched from $source")
}
sitesMutableLiveData.value = allSites
})
return sitesMutableLiveData
}
So I guess I could pass the Fragment context by passing it up the chain as an argument like this:
businessViewModel.listenToSites(context)
Passing this through the VM, Repository to the snapshotListener as below:
addSnapshotListener(context, MetadataChanges.INCLUDE, EventListener....
But was unsure if this is the recommended way to do it or how best to capture the context in the Fragment?
Quick bonus question out of interest; does the snapshots.documentChanges
code create additional listeners? I'm just using while I debug, but thought it could..
Your repository layer should never include a single line of Android-related code. Your ViewModel is a common ground for the Android and the repo layer code. You can make ViewModel lifecycle aware by implementing LifeCycleObserver
interface.
class YourViewModel : LifeCycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_STOP) //Annotate it with any lifecycle event of your choice.
fun aMethodThatRemovesObserver() {
//Invoked on onStop()
}
}
In your activity:
onCreate() {
lifecycle.addObserver(yourViewModel)
}
No need to worry about removing ViewModel observer manually, it will be gone in uncleared
state of your ViewModel. Just keep in mind that the repository layer should remain free of Android-related code. If you need a context, you can use AndroidViewModel
(scoped to application). Sometimes it's not possible to seperate out context, in that case, I would just provide it with an applicationContext but not an Activity context.