2

I'd like to use the same LiveData with different sources. One from an API call which is an observable and one from a database which is a LiveData. I'd like to be able to do something like this:

private LiveData<List<Items>> items = new MutableLiveData<>();

// this one comes from an API and it's an observable
public void onApiItemsSelected(String name) {
 Disposable disposable = repository.getItemsFromApi(name)
 .observeOn(AndroidSchedulers.mainThread())
 .subscribe(itemsList -> {
     items.setValue(itemsList);
 });

 compositeDisposable.add(disposable);
}

// this one comes from the database and it's a livedata that I want to transform first
public void onDatabaseItemsSelected() {
 items = Transformations.map(repository.getItemsFromdatabase(), itemsList -> {
    List<Items> finalItemsList = new ArrayList<>();

    for (Item item : itemsList) {
      finalItemsList.add(itemsList.toSomething());
    }

    return finalItemsList;
 });
}

The problem is that a Transformation.map always returns a LiveData and in order to make a items.setValue(itemsList) items need to be a MutableLiveData. I tried with MediatorLiveData but it's calling the two sources and mixing everything. That's not what I need here. I need one source OR the other. Is it possible?

unadivadantan
  • 363
  • 4
  • 14
  • I am not sure what you trying to do. You want to combine those results or you want to access the latest result (like whichever is the latest database or api)? – Henrique Vasconcellos May 14 '20 at 18:48
  • If the user clicks on a button that calls onApiItemsSelected I want to display the values from the API and if he clicks on onDatabaseItemsSelected I want to display the values from the database. I don't want them to be combined. I want to display one or the other. They are the same object that will be displayed on the same list. Only the source is different. – unadivadantan May 14 '20 at 19:30
  • I will try to post a code to help. – Henrique Vasconcellos May 14 '20 at 19:43
  • Why not always save the data from API to DB, and only show data from DB? – EpicPandaForce May 31 '20 at 20:04

2 Answers2

1

I think you could delegate this responsibility to your repository.

See for example this open source project and related article with more info.

Basically, the repository handles all the complexity of deciding which source to get the data from (see the JobRepository). It exposes a rx Observable to the ViewModel (see the JobsViewModel). Then all the ViewModel has to do is update the LiveData:

private val presentation: MutableLiveData<Presentation> = MutableLiveData()

fun bind() {
        jobRepository.getJobs(true)
            .subscribeOn(Schedulers.io())
            .subscribe { resource: Resource<List<JobWithRelations>> ->
                when (resource) {
                    // commenting some parts for brevity ...
                    is Resource.ResourceFound -> {
                        presentation.postValue(Presentation(getApplication(), resource.data!!))
                    }
                    // ...
                }
            }.addTo(compositeDisposable)
    }

Managing multiple sources of data is complex. Among the many aspects involved in architecting this, things to keep in mind include:

  • In-memory caching expiration strategies
  • Policies for db caching, for example, when users are offline
  • Throttling and retry of API calls
  • Policies for updating the in-memory cache or db when new data comes from the network

One library that could help you is the Dropbox Store. With it, you could build your data sources like in the example below from their documentation:

StoreBuilder
    .from(
        fetcher = nonFlowValueFetcher { api.fetchSubreddit(it, "10").data.children.map(::toPosts) },
        sourceOfTruth = SourceOfTrue.from(
            reader = db.postDao()::loadPosts,
            writer = db.postDao()::insertPosts,
            delete = db.postDao()::clearFeed,
            deleteAll = db.postDao()::clearAllFeeds
        )
    ).build()

Then to get the data in your ViewModel:

private val presentation: MutableLiveData<Presentation> = MutableLiveData()

lifecycleScope.launchWhenStarted {
  store.stream(StoreRequest.cached(key = key, refresh=true)).collect { response ->
    when(response) {
        // commenting some parts for brevity ...
        is StoreResponse.Data -> {
            presentation.postValue(response.value)
        }
        // ...
    }
  }
}
Ricardo
  • 7,785
  • 8
  • 40
  • 60
0

I am not sure it is the best practice but I think will do the trick.


public void onDatabaseItemsSelected() {
items.value = repository.getItemsFromdatabase().value;
};

The syntax may not be accurate as I am know little about java, I do mainly kotlin. But the thing is: when the user clicks Api, make your onApiItemsSelected() run, that will set the value of items, when the user clicks database, make onDatabaseItemsSelected() run, so it will replace items value for the result of repository.getItemsFromDatabase()

Henrique Vasconcellos
  • 1,144
  • 1
  • 8
  • 13
  • This only works if you are lucky, which is probably never (it definitely would not work if the `getItemsFromDatabase` is a LiveData from a Room DAO). – EpicPandaForce May 31 '20 at 20:02