advice
advice

Reputation: 5988

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.

Upvotes: 11

Views: 11106

Answers (2)

tynn
tynn

Reputation: 39853

Your implementation of contents includes several references to outer variables, which makes it hard to follow and keep track of the state. I'd just keep the references as local as possible and trust switchMap(liveData) to do a proper job. The following code should be doing just the same as yours:

val contents = Transformations.switchMap(database.group) { id ->
    val data = MediatorLiveData<Resource<List<MyItem>>()

    if (id == null) {
        data.value = Resource.init(null)
    } else {
        data.value = Resource.loading(null)
        data.addSource(database.getList(id)) {
            data.value = Resource.success(it)
        }
    }

    return liveData
}

In regards of getList(id) you might also want to handle the exception properly.

Upvotes: 1

SamiAzar
SamiAzar

Reputation: 1320

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

}

Upvotes: 4

Related Questions