Search code examples
androidkotlingoogle-cloud-firestoreandroid-livedatamutablelivedata

LiveData with multiple sources of different types


I currently have a project that contains a list of MyItem, and using Firebase/LiveData. It's structured into groups, and each group has items.

I want to be able to update this list if any of the following happen:

  1. An item is updated (on the backend through Firebase)
  2. A filter is changed (a separate table on Firebase for each user)
  3. An item is bookmarked (a separate table on Firebase for each user)

To get the list of contents, I have a function like this to return LiveData that will update whenever an item is updated (#1).

database

getList(id: String): LiveData<List<MyItem>> {
    val data = MutableLiveData<List<MyItem>>()

    firestore
        .collection("groups")
        .document(id)
        .collection("items")
            .addSnapshotListener { snapshot, exception ->
                val items = snapshot?.toObjects(MyItem::class.java) ?: emptyList()

                // filter items 

                data.postValue(items)
        }

    return data
}

And within my ViewModel, I have logic for handling that case.

viewmodel

private val result = MediatorLiveData<Resource<List<MyItem>>>()

private var source: LiveData<List<MyItem>>? = null
val contents: LiveData<Resource<List<MyItem>>>
    get() {
        val group = database.group

        // if the selected group is changed.
        return Transformations.switchMap(group) { id ->
            // showing loading indicator
            result.value = Resource.loading(null)

            if (id != null) {
                // only 1 source for the current group
                source?.let {
                    result.removeSource(it)
                }

                source = database.getList(id).also {
                    result.addSource(it) {
                        result.value = Resource.success(it)
                    }
                }

                // how to add in source of filter changes?

            } else {
                result.value = Resource.init(null)
            }
            return@switchMap result
        }
    }

The logic is pretty complex, and hard to follow. Is there a better way to structure this to handle multiple different changes? What's the best way to store the user's current filters?

Thanks.


Solution

  • I don't know am I get your question correctly or not, but if you have a view that work with one list (something like MyItemList) and this list updated or changed by several situations, you must work with MediatorLiveData.

    I mean you must have three LiveData that each one responsible for a situation and one MediatorLiveData that notified if each one of them has changed.

    see below:

    database

    fun getListFromServer(id: String): LiveData<List<MyItem>> {
        val dataFromServer = MutableLiveData<List<MyItem>>()
    
        firestore
          .collection("groups")
          .document(id)
          .collection("items")
              .addSnapshotListener { snapshot, exception ->
                  val items = snapshot?.toObjects(MyItem::class.java) ?: emptyList()
                  dataFromServer.postValue(items)
          }
    
        return dataFromServer
    }
    
    fun getFilteredData(id: String): LiveData<FilterData> {
        return DAO.user.getFilteredData(id)
    }
    
    fun getBookmarkedList(id: String): LiveData<BookmarkData> {
        return DAO.user.getBookmarkedData(id)
    }
    

    And in the viewModel you have one MediatorLiveData that observe on these liveDatas till if any data has changed notify view.

    viewModel

    private val result = MediatorLiveData<<List<MyItem>>()
    
    fun observeOnData(id: String, owner: LifeCycleOwner, observer: Observer<List<MyItem>>) {
       result.observe(owner, observer);
    
       result.addSource(Database.getListFromServer(id), MyItemList -> {
            if(MyItemList != null)
                result.setValue(MyItemList)
       });
       result.addSource(Database.getFilteredData(id), filterData -> {
            if(filterData != null) {
                val myItemList = result.getValue()
                if (myItemList == null) return
    
                //here add logic for update myItemList depend On filterData
    
                result.setValue(myItemList)
            }
       });
       result.addSource(Database.getBookmarkedList(id), bookmarkData -> {
            if(MyItemList != null) {
                val myItemList = result.getValue()
                if (myItemList == null) return
    
                //here add logic for update myItemList depend On bookmarkData
    
                result.setValue(myItemList)
            }
       });
    
    }