Britt Barak
Clean, Easy & New- How To Architect Your App: Part 1
About a week ago at Google I/O, Google’s Android team announced on a new mindset: it’s time to think about app’s architecture and best practices!
In a world where an app goes through many changes, new features, bug fixes etc.. it is so important to have a structure that supports this hectic lifestyle.
Google did an amazing work in helping us, developers, understand what is a recommended way to architect your app, so it can be stable, adaptive to changes, debuggable and testable.
New architecture components were introduced to support the suggested architecture ideas, which can be quite helpful for any Android developer, even if you’re not planning on a major refactor for your existing codebase.
There are so many new ideas and tools around! So where should one start catching up??
In the next few blog posts, I’ll introduce, as simply as possible, a way to think about your app’s structure and build it using the new architecture components and suggested ideas.
Show me the recipe!
To show these ideas in practice, I’ll demonstrate it with an app for jelly beans recipes! Not many people know that instead of just eating jelly beans separately, you can eat them according to a recipe and enjoy the taste of a super cool dessert!
What does our app have:
A list of jelly beans- each has a flavor and a color.
We can add a new bean:
We also have a list of recipes. Each recipe has a name, and can be combined by a different amount of each Jelly Bean flavor.
Clicking on a recipe displays more details about it:
Now, let’s build this thing
At the the very basic, the way I like to think of my app structure is as such:
On one end:
Data Layer —
is our actual data structures. It is as objective as possible, in the sense that it has no idea what will this data be used for, how (and even if ) will it be displayed, or who will use it.
On the other end:
Presentation / UI Layer —
is our views, how things end up on the screen. Our RecyclerView, ViewPagers, TextViews etc…. are all here. It knows how to take some data in the form of some view models, and present them on the screen. It doesn’t care where was this data originated from or how was it represented by the server, the database or even maybe statically by the app. It merely takes objects that it knows how to present — and present them.
Between them:
Domain layer —
the data models which are on our data layer, differ from the way the Presentation Layer needs them. Therefore, we need someone to be a mediator, and prepare the data to be presented for a certain use case, a certain scenario.
Add that bean!
For a minimal example, let’s consider the Add New Jelly Bean screen:
We give the Jelly Bean a name, and a color by using the R, G and B SeekBars. When we change the color with a SeekBar, the bean image on the top changes its color to the chosen one.
As mentioned before: we want the UI to be as “stupid” as possible. Meaning, to simply take a value and present it: take the flavor string and present it, take the color and present it…
To accomplish that we’ll create an object that contains the data in the easiest way for the UI to present it. This objects serves the UI, so it’s called a View Model.
The layout here is simple, but as the app gets more complex and there is more data we want to present, often from different data sources, it helps to have one object that has all the data that is needed for presentation.
For this screen, the viewModel is:
public class JellyBeanViewModel extends ViewModel {
String flavor;
int r;
int g;
int b;
The activity will have something like:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_bean);
this.viewModel = new JellyBeanViewModel();
setUi();
}
As the user changes one of the SeekBars, we update the corresponding R/G/B value:
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
switch (seekBar.getId()) {
case R.id.seekbar_r:
viewModel.setR(progress);
break;
case R.id.seekbar_g:
viewModel.setG(progress);
break;
case R.id.seekbar_b:
viewModel.setB(progress);
break;
}
}
The UI should represent the ViewModel state.
As user input changes the viewModel state (changing an R/G/B value), the UI should change accordingly (change the bean image’s color).
In other words I want to have the UI to “listen” or “observe” to the ViewModel’s changes, and update accordingly.
A great way to accomplish it is by using LiveData, which is an object that wraps some value (another object) and notifies about changes of this value.
In order to be notified about the changes, there should be an object that observes them (Observer), on a given lifecycle. That way, the observer is notified only when active, meaning only when the lifecycle is Started or Resumed. When the lifecycle ends, no updates are desired and the memory will be released on our behalf, to avoid leaks.
LiveData is an Abstract class that you can extend. However, a simple implementation for an object with a value that can be easily set and observed, is MutableLiveData class.
So, instead of treating the colors as ints, I’ll wrap them with LiveData objects.
public class JellyBeanViewModel extends ViewModel {
String flavor;
LiveData<Integer> r = new MutableLiveData();
LiveData<Integer> g = new MutableLiveData();
LiveData<Integer> b = new MutableLiveData();
Then, on my activity I’ll create an observer object, that will update the UI, in this case- color the bean image.
Then, I’ll observe the R/G/B values, so that when they change the observer will do its thing (change the UI).
Observer<Integer> colorChangedObserver =
integer -> beanIv.setColorFilter(viewModel.getColor());
viewModel.getR().observe(this, colorChangedObserver);
viewModel.getG().observe(this, colorChangedObserver);
viewModel.getB().observe(this, colorChangedObserver);
Passing “this” as a parameter on the observe() method, assures that this observer is tied to “this” lifecycle, and will be removed as the lifecycle ends, to prevent memory leaks.
Persist it!
If we edit the jelly bean and then a configuration change occurs (i.e. screen rotation) → the activity is destroyed and recreated → onCreate() is called again → another viewModel is created from scratch → we’d lose the changes we made.
To easily handle that and persist our ViewModel we use ViewModelProviders.
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_bean);
this.viewModel = ViewModelProviders.of(this).get(JellyBeanViewModel.class);
setUi();
}
Notice that ViewModelProviders is tied to a scope, which is the parameter given on the .of() method, that can be an activity or a fragment. Therefore, if you want to share a viewModel between fragments of the same activity, a good idea would be to use ViewModelProviders.of(getActivity()), so you can persist the same viewModel.
For a fragment, ViewModelProviders.of(getActivity()) and ViewModelProviders.of(this) will yield a different result.
Well, so far we saw an intro to the new architecture and the app I’ll be using to demonstrate it. We focused on implementing the Presentation Layer for a simple screen.
Next post we’ll dive into the Data and Domain Layers to complete the picture.
Join me there :)
❤️️ XOXO ❤️️