With the newly introduced “architectural components”, the way Android applications are designed will change drastically.
If I had been using Loaders all this time, I’d be filled with confusion. “How do ViewModels and LiveData fit into my application?”
But I’ve been lucky. I had the opportunity to use Realm instead. I could simplify most of my problems just by “listening to changes in the database”, and everything was always up to date. Any changes made to Realm from any thread reflected on the UI thread without additional work beyond keeping a reference to a
RealmResults, and adding a
RealmChangeListener to it.
With the introduction of Room, a lot of benefits that I took for granted with Realm are now available for everyone else — but on top of SQLite.
With Room, your typical reactive data access layer would look like this:
As you can see, all DAO methods return a
LiveData<T> that you can subscribe to for changes.
Subscription to changes isn’t a new concept at all. Realm had received a lot of criticism for “being all over the codebase”, but in reality, no one ever stopped anyone from creating their own Realm-based DAOs to collect the queries in one place.
In fact, it would look something like this:
— — — — — — — — — — — — — — — — — — —
Once we have that, we can observe the changes like so:
And while traditionally, you’d have the
RealmResults (and maybe even the
Realm instance) managed by the activity/fragment’s lifecycle callbacks directly, this is partly what
ViewModel is responsible for (the other half being a scoped data holder for Activities and Fragments — which can store subscoped Dagger components, for example).
If we leverage the new lifecycle integration and wrap Realm in a ViewModel, we can even re-use this code above, and just use this view model instead:
There’s an interesting thing to note here based on this example.
RealmResults<T>are the same thing.
In fact, even previously when anyone asked, I told them: if they use Clean Architecture with Realm, and their data layer returns a
List<T> instead of something like a
MutableLiveList<T> that would mutate in place and provide the means to add/remove a change listener, then they were using Realm wrong, and they were making their life far more complicated.
Still, a key difference is that SQLite will eagerly evaluate your query results, while Realm provides lazy-loaded ones. As Realm’s results are lazy-loaded, this kind of “scoped caching” provided by ViewModel wouldn’t even be necessary, but it’s nice to see a code that provides reactivity and can abstract Realm away properly from the Fragment itself, doesn’t it? :)
Now, there’s a very common architectural decision I see all over the place, but I never truly undestood.
People often create a
Repository class that is responsible for “selecting data from an in-memory cache, the local data source, or the remote data source” — I think this comes from Fernando Cejas’s “Architecting Android: The Evolution” , but it might even originate from Dan Lew’s “Loading Data From Multiple Sources”.
Typically this results in a
concat operation that has a sort of algebraic beauty to it:
// from Dan Lew's article
Single<Data> memory = ...;
Single<Data> disk = ...;
Single<Data> network = ...;
Single<Data> source = Single
.concat(memory, disk, network)
.first(); // <-- return only 1!
In the new world where you can observe changes in the database, you don’t only ask for 1 element. You ask for an Observable. It can be 1…* elements.
If you just retrieve a single element from your data sources and otherwise don’t listen for changes made to the database, then where’s the reactivity? Where’s the REACTIVEness of your Rx-based solution?
As such, I do NOT understand this data layer design. I drew a graph in Paint to illustrate the point (also available here):
As you can see, the presenter does not subscribe for changes. How would any background operation that changes the data set reflect in the presenter? There is no way to listen.
Instead, a possible approach would be to start a one-off operation that retrieves the data from remote if necessary, but otherwise listen only for changes in the local data source.
Previously, your caching could be done via Jake Wharton’s RxReplayingShare, which would return the latest value upon subscription (which already replaced the in-memory cache as a data source).
Now this is handled by ViewModel (scoping of data) and LiveData (holding previous value, and allowing subscriptions — similarly to
BehaviorRelay), so caching is no longer the responsibility of the Observable chain:
This way, we have created scoped data and listening for changes using Architecture Components, while we’ve kept reactivity in our data layer (instead of requesting the data only once, and then having no means of update if the data has changed).
I personally welcome the new change brought to Android applications using ViewModel and LiveData. After more than a year of having used Realm, this simplicity introduced by reactivity feels just like home — whether it’s Realm or Room. :)
The concept is the same, and thankfully it’s much better, much easier, and much more stable than either loaders, or concating singles.