Google announced some awesome tools during #io17. They’re called Architecture Components, I’m going to assume you already know the basics about each, if not you can find videos and official documentation at the end of this article.
After studying these new elements I was able to write up a simple demo project, where you can check (especially if you don’t have any prior experience) how awesome reactive programming can be. For that reason and regarding my previous MVP project, I decided to open source the code and write a blog post about my experience.
MVVM + Observables + Repositories + Architecture Components 😨?
Don’t be overwhelmed, it’s simpler than it first looks, this is my interpretation:
TL;DR: Data source → Repository → ViewModel → View. There’s a nice diagram at the end of this section with a simplified event/data flow. Also the project is available on GitHub.
The repository serves as the communication bridge between the data and the rest of the app. Building a Foo feature, the
MARKDOWN_HASHe36173c5776358c6bcdf9f2d530a2559MARKDOWN_HASH abstracts the data source(s) from the
MARKDOWN_HASHd506aea54ee5ce4f4a014c87a68dd967MARKDOWN_HASH, while providing the necessary Observables to stream data to the latter.
On the other hand, the ViewModel should bridge the Repository and the View, this is also where your Business Logic lives, much like a Presenter in MVP or a Controller in MVC, but with some key differences, we’re extending the framework’s own
MARKDOWN_HASH80203151d7ec749a02e690c07abb9ebaMARKDOWN_HASH class, which has some internal lifecycle awareness, making our development a little less painful, but more on that later.
MARKDOWN_HASH9c984d88861a055c8ee7e055747b15c9MARKDOWN_HASH will hold a reference to the corresponding
MARKDOWN_HASHd506aea54ee5ce4f4a014c87a68dd967MARKDOWN_HASH instance, getting this is as simple as calling:
viewModel = ViewModelProviders.of(this).get(FooViewModel.class);
Note: ViewModel objects are scoped to the
MARKDOWN_HASH44dbe144d735628d631841914bc9f730MARKDOWN_HASHpassed to the
MARKDOWN_HASH63bb242e54757490b7c0f4707a1e2728MARKDOWN_HASHwhen getting the ViewModel. The ViewModel stays in memory until the
MARKDOWN_HASH44dbe144d735628d631841914bc9f730MARKDOWN_HASHit’s scoped to go away permanently—in the case of an activity, when it finishes; in the case of a fragment, when it’s detached.
The next and final step is to subscribe to the ViewModel’s exposed data observables, display the data coming in, and report the user actions back to the ViewModel.
That’s it, no need to have huge amounts of callbacks, keeping your views (as always) logic free, but now less verbose, therefore easier to maintain 👍.
Using this structure enables our code to be more reusable/modular, when compared to other patterns.
With the new android architecture components writing tests 👈 and separation of concerns is easy as 🍰.
LiveData<T>: Is a data holder class that keeps a value and allows this value to be observed. Unlike a regular observable, LiveData respects the lifecycle of app components, such that the Observer can specify a Lifecycle in which it should observe.
Android’s ViewModel gives us a lifecycle aware component, without the need of holding a reference to a View (e.g Activity, Fragment), more susceptible to leaks. However it will take ownership of the data, therefore we must focus on exposing the right data and work on the Business Logic.
Note: Since the ViewModel outlives specific activity and fragment instantiations, it should never reference a View, or any class that may hold a reference to the activity context. If the ViewModel needs the
MARKDOWN_HASH7a92e391052e06caa85477d38e0f8d85MARKDOWN_HASHcontext (for example, to find a system service), it can extend the
MARKDOWN_HASH3a159549952eaf4e5e4e36c5fdac9206MARKDOWN_HASHclass and have a constructor that receives the
MARKDOWN_HASH7a92e391052e06caa85477d38e0f8d85MARKDOWN_HASHin the constructor (since
Uses SMS-Token service or the web to request a more precise ETA of a public-transport (Bus, Boat etc.) to a specific station, currently implementing for Lisbon 🇵🇹, but the idea is to scale up to any other cities around the world that provide the same kind of service(s). Most of the complex stuff will happen on the backend anyway — yay 🙌 Cloud Functions for Firebase. Writing a well cared reactive android client codebase should be enough to adapt.
Currently written in Java, but will most likely become Kotlin only on new classes/features.
The Data and Ui are in separated parts, imho it’s a crucial way to keep yourself from doing spaghetti code. Using the observer pattern has its advantages since we can simply open channels streaming data and then listen. Sounds awesome (and it is) but, if you’re not careful, you’ll start losing control of your own code 😣.
I’ve placed each ViewModel class inside of its
MARKDOWN_HASH83130ee33ee8963c515b2474b10169b7MARKDOWN_HASH package, but it would be correct to have these on a separate viewmodel package at root-level along with repository and ui. I’m still experimenting to see if makes sense if so, I’ll change it. Feel free to leave your opinion down below 👍.
There’s also a repository for each context, this will further help to archive the app’s complete abstraction from the data-source(s). And these can also be used by different ViewModels since the sole responsibility is to provide (specific) data.
At the moment the web-service responses are mocked, with MVVM or any other battle-tested pattern, anything outside of the scope of this article is virtually meaningless, that’s the beauty of a good software architecture.
Note: As mentioned above, the open-sourced code isn’t 100% implemented, there is a List and Detail screens (with a list of Stations belonging to the specific bus, demoing relation), this is purely to show Room’s implementation. I might update the project in the future, but I think there is enough there to understand the AACs.
Bye bye Presenter. Hello ViewModel.
Let’s start looking at our
As you can see the
MARKDOWN_HASH65e2e6e67ba45d93706efbbca3daf02eMARKDOWN_HASH has no idea to whom might subscribe to the emitted data. It is also completely abstracted from the data sources, it simply asks the
MARKDOWN_HASHdbe5040739b4a4193968187808f03591MARKDOWN_HASH for the data and doesn’t really care of it’s internal implementations, it could come from a remote webservice or a simple local database (this is oddly satisfying to know we’re respecting the Single responsibility principle).
So now looking at our
Take this code with a grain of salt, it might not be 100% correct, and it’s quite complex, for example it could make sense to have a separate class to do Local Database (Room) operations.
The repository returns LiveData that is directly linked to the database, keeping it as the single source of truth. As mentioned before, we’re using the observable pattern so we’re sure every change gets propagated up the stream, preventing re-queries to the database.
Also notice how the responsibilities are, once again, delegated to the respective classes. The repository only gets the
MARKDOWN_HASHae74e238efe8f966af0c6f329eef205aMARKDOWN_HASH, the only way to get database access, and the remote repository, the entity that deals with all the webservice request/response handling logic.
Finally our simple
MARKDOWN_HASH1024416658c75751a4404b7dcda59879MARKDOWN_HASH → the LifecycleOwner
Also, notice I’m not extending the documentation referred LifecycleActivity. This was actually a test, and it works! Most on-going projects will probably have a big difficulty to extend a new class (LifecycleActivity/LifecycleFragment), so this solution seems to work, just copy the
MARKDOWN_HASH4c9c653a3454c5aa185a7a229fbb854fMARKDOWN_HASH (gist’s line 5) and the
For more examples look at the GitHub repository, there’s also Room code there, I didn’t dwell much on it here. Imo it is a whole other chapter on itself.
For any other details code related you can always ask here or hit me up on Twitter. I don’t think the focus of this article should be the LoC but the implementation of these new awesome tools, so I didn’t show a lot of code, there are other articles here on medium who focused more on that.
I highly recommend you to watch the resources posted bellow 👇!
- Architecture Components — Introduction (Google I/O ’17) — IO17 Youtube video
- Architecture Components — Solving the Lifecycle Problem (Google I/O ’17) — IO17 Youtube video
- Architecture Components — Persistence and Offline (Google I/O ’17) — IO17 Youtube video
- Android Architecture Components official page
- Architecture Components: Improve Your App’s Design — 5 minute Youtube video by Lyla from Google
- Room Persistence Library — Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
If you know someone who might extract value please share, I’ll be looking for your opinion and suggestions in the comments, feedback is always welcome.