LiveData with multiple sources of different types

Advice-Dog picture Advice-Dog · Apr 10, 2019 · Viewed 7.3k times · Source

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.

Answer

SamiAzar picture SamiAzar · Apr 16, 2019

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)
        }
   });

}