Simply copy from Loading data from multiple sources with RxJava
Suppose I have some Data
- that I query from the network. I could simply hit the network each time I need the data, but caching the data on disk and in memory would be much more efficient.
- More specifically, I want a setup that:
Occasionally performs queries from the network for fresh data.
Retrieves data as quickly as possible otherwise (by caching network results).
I'd like to present an implementation of this setup usingRxJava.
Observable<Data> for each source (network, disk and memory), we can construct a simple solution using two operators,
concat() takes multiple Observables and concatenates their sequences. first() emits only the first item from a sequence. Therefore, if you use
concat().first(), it retrieves the first item emitted by multiple sources.
Let's see it in action:
// Our sources (left as an exercise for the reader) Observable<Data> memory = ...; Observable<Data> disk = ...; Observable<Data> network = ...; // Retrieve the first source with data Observable<Data> source = Observable .concat(memory, disk, network) .first();
The key to this pattern is that
concat() only subscribes to each child Observable when it needs to. There's no unnecessary querying of slower sources if data is cached,
sincefirst() will stop the sequence early. In other words, if memory returns a result, then we won't bother going to disk or network. Conversely, if neither memory nor disk have data, it'll make a new network request.
Note that the order of the source
concat() matters, since it's checking them one-by-one.
Unfortunately, now our data-saving code is working a little toowell! It's always returning the same data, no matter how out-of-date it is. Remember, we'd like to go back to the server occasionally for fresh data.
The solution is in
first(), which can also perform filtering. Just set it up to reject data that isn't worthy:
Observable<Data> source = Observable .concat(memory, diskWithCache, networkWithSave) .first(data -> data.isUpToDate());
Now we'll only emit the first item that qualifies as up-to-date. Thus, if one of our sources has stale
Data, we'll continue on to the next one until we find fresh
first() vs. takeFirst()
As an alternative to using
first() for this pattern, you could also use [
The difference between the two calls is that
first() will throw a NoSuchElementException if none of the sources emits valid data, whereas
takeFirst() will simply complete without exception.
Which you use depends on whether you need to explicitly handle a lack of data or not.