Handling life cycle events, maintaining view state, and persisting data are common challenges on Android. When I started developing applications in 2009, we were dealing with the same issues that we deal with today. Today I want to talk about clean architecture, by taking a look at the new Android architecture components, and how we might be able to use those to improve on already existing clean app design.
Before you start writing your code, having an idea of how you might want to lay it out, and how you might want to separate things can be a useful tool as you’re approaching a new project.
A key to clean architecture is the idea of having layers to your application, and clear separation between your UI, versus your logic, versus your data. Once you have these layers and you have a plan, this will allow you to layout your classes in a cohesive way so that each thing can do one thing and do it well with regards to the single responsibility principle.
Clean architecture allows us to have a certain degree of flexibility with our code base. It makes it easier to evolve and change it over time and help with writing automated tests.
“Classic” App Design
However you started off with Android, it all began with an Activity, the basic construction of an Android app.
An Activity contains a lifecycle - this is the contract of how it’s going to communicate with the operating system. Activity by its nature is also a context which gives us access to system services, e.g. the layout manager, location manager, package manager.
Get more development news like this
An Activity by itself does not imply a user interface. Suppose there is an EditText and a Button located in our Activity, and we need to fetch data from a web service, then cache or persist that data. We use AsyncTask to complete this. Then, one day, Fragments were introduced.
Fragments rolled out as part of tablet support for Android as these reusable components that can help us manage chunks of UI for different form factors, orientation changes.
While there are advantages that came with fragments, there was also a good deal of complexity in that. Now, there were two lifecycles to manage.
What are some of the challenges with this?
Firstly, it’s difficult to modify behavior. If you want to change a component, chances are you’re going to have to change the way how everything around it also works. If the user rotates their device, you will have to manage the state.
Finally, in terms of testing, if you want to test any components in isolation, it will be so tightly coupled to everything else that you will have to build up the entire application to test one piece of it.
Clean Architecture Patterns
Over the past few years, clean architecture has risen in popularity in the Android community. There are a number of different common patterns that people have been experimenting with: model view controller, model view presenter, model view ViewModel. These are not new concepts, in fact, they are older than Android itself.
Android Architecture Components
A lifecycle has Observers; it also has Events, and States. This is a simplification of what we had to manage before when it came to the activity and the fragment lifecycle. There are fewer states we have to be concerned with.
We are also allowed to do things like query the lifecycle, as well as have some implicit awareness of what state we are in.
ViewModel is a tool that helps you persist your view state through configuration changes and other lifecycle events. It can also be useful in managing an asynchronous data fetch, which gets that logic out of your activities. You can also share data between UI controllers.
ViewModels don’t necessarily lock you into using MVVM architecture. You can use them alongside a presenter if you have more complex logic for your display. A ViewModel should not hold a reference back to the original activity or view context.
ViewModels persist through all of the changes until your activity is finally finished, and that is when it’s cleared out of scope.
LiveData is this wrapper for our data. This allows us to observe changes in it. It works as a bridge between the lifecycle owner and the observer.
LiveData can be valuable in preventing crashes and memory leaks that come from attempting to perform an operation on your UI at the wrong time.
Room is an SQLite abstraction layer. This can eliminate the boilerplate you need to deal with database code, and also gives you compile-time validation of SQL.
Room works by providing some data access objects that then get you to your entities, which are exposed to the rest of the architecture.
We can set up Rooms that it returns us the LiveData instances that we can then take advantage of inside of our ViewModels and subscribe to from our UI.
Taking Advantage of the New Components
Let’s come back to our clean architecture example. As a good first step, we can make our Activity lifecycle-aware, and have it broadcast to the other components inside of the app about its current state.
There are a couple ways to do this. One is you can have it implement the lifecycle activity base class. The other option is to implement the lifecycle owner interface to expose that lifecycle of your activity.
To use the ViewModel, we can simply swap it in for the presenter. This is one option for how to adopt this component and puts it on the non-Android side of our architecture.
But say you have a lot of logic already built up in your presenter, you might not want to completely get rid of it. The other option is to use a specific type of the ViewModel called the Android ViewModel which gives you access to the application context.
I would caution against using the Android ViewModel, because now we’re introducing an Android dependency into the non-Android side of our application, which is going to have implications for testing.
Where do we go from here?
The emergence of clean architecture on Android as a theme and a trend has been driven by the community. The introduction of the official architecture components library is a nod to the community.
It’s an exciting time to be involved in Android development. We have clean design patterns, and tools like dependency injection are all valuable assets that can help you make your applications more flexible, more maintainable, and more testable than ever before.
Q & A
Q: How would you use that inside of a ViewModel that shouldn’t hold onto a context? Chuck: If it can function based on application context, you could use the Android ViewModel, which gives you the ability to access the application context. If you need an activity or a view context specifically for that component, you might need to abstract it behind some interface, but you’re going to have to do TLC to manage that so that context gets refreshed each time your activity goes through the lifecycle changes.
Q: if you have your activity lifecycle events, should the presenter be subscribing to those, or should the activity be broadcasting those to the presenter? Chuck: If we’re taking advantage of the new lifecycle components, the activity is going to broadcast those, and then the presenter is going to pick up on those, either through the ViewModel or it itself can become lifecycle-aware, and it’s going to receive those. In the example that I was illustrating, your activity is your controller. That’s just an interface that we put the activity behind, which becomes useful during testing because then you can just have some dummy implementation of that interface that then your presenter gets tested against. But when you’re running a real application, it’s much more of the activity/controller broadcasting out the events, and then the presenter listening for them, or the ViewModel.
Q: How would you persist the presenter? Chuck: It is a tough choice. You have two options. You can either scope your presenter to the lifecycle of the activity, so it gets destroyed and recreated along the activity with each rotation. That keeps your presenter a little bit cleaner, and that it doesn’t have to worry about maintaining state. But the challenge there is you still need to maintain that state somewhere, which is where if you use a ViewModel, you could just put the current state in the ViewModel. The old school way of doing it would be to put it in the bundle. The other way of managing that is you can make your presenter outlive your, outlive your activity lifecycle events, and persist through all those until the activity is finished. But then it’s behaving the same way the ViewModel component is, and so you might as well just use that.
Q: are there Google examples for using this with other patterns MVP; and are you forced to use MVVM? Chuck: for the first point, I am not sure; for the second point, definitely not. The fact of the components are mix and match, and you can use each one individually, it gives us the flexibility to use any architecture that makes us happy. There are some examples available in the Android blueprints that use MVP, or not necessarily following the MVVM pattern.
About the content
This talk was delivered live in July 2017 at 360 AnDev. The video was recorded, produced, and transcribed by Realm, and is published here with the permission of the conference organizers.