Building Android apps… the right way !

Don’t just write code, engineer it.

Hi again. In this post I thought of writing about how we can develop clean android applications which are stable, flexible to changes, and can stand the test of time. Have you ever encountered a situation where you make changes to one feature of your app and then soon realize that you broke some other feature? This was a common problem I faced when I first started building apps, but once I understood the power of application design I was no longer afraid of modifying tested code. The application design I want to talk about is one very similar to the MVC (Model-View-Controller) pattern, but with minor changes to fit into the android ecosystem.

To begin, we need to first split our application into three base layers: the presentation layer, the domain layer and the data layer.

Presentation Layer This will be the top most layer in our arhictecture and its main responsibility will be to interact with views, and receive user input. Android framework classes such as Activity and View will be accessed from this layer. Instrumentation tests are usually done on this layer.

Domain Layer This layer sits in between the presentation and data layer. It will be responsible for performing any domain related logic such as data validation, modification, etc. as the data passes from the presentation to the data layer or visa versa. This layer will be free of android framework classes, so it can be unit tested directly. There is no need for this code to be run on a device / emulator when being tested.

Data Layer This layer will the bottom most layer and will be responsible for providing data (either through an API endpoint or from application cache) to the presentation layer. So any network related, or database related code will be implemented in this layer.

Sample application

Hopefully you understand what we are trying to achieve by using these layers. As an example, think of a simple app with a single Activity. When that Activity is launched, it retrieves a set of users from an API and displays it in a ListView. So our presentation layer will include the Activity instance, with its onCreate(), onDestroy() callbacks implemented, as well as an additional callback which receives a set of Users. Pseudocode below.

public class MySampleActivity extends Activity {
     public void onCreate(){
// create the domain implementation and
// ask that domain instance for list of top 10 users
}
     public void onDestroy(){
// destroy domain implementation instances
// to avoid memory leaks
}
     public void onUsersReceived(List<User> users){
// refresh and update the list view
}
}

So that is all the code that goes into the activity and you can think of this as the presentation layer. As you can see, there is no validation done here. If the onUserReceived callback is fired, the list view will be updated, simple as that. Filtering, sorting, modifying, etc. User instances before displaying will be the responsibility of the domain layer. The only thing the presentation layer cares about is how to display the data when it arrives. I will explain to you the benefits of doing this further down this post.

Next comes our domain layer. Here we define our use cases. As you can see the pseudocode above, in the onCreate() method we request for the top 10 users. So this will be a use case in our domain layer. Say we want the highest ranked users, this would be another use case in our domain layer.

public class UserDomain{ 
    // Default constructor.
public UserDomain(){
// Create data layer implementation here
}
    // use case 1
public void getTop10Users(UserListCallback userListCallback){
// Ask the data layer for the top 10 users.
// Once the data arrives validate, modify, etc and then pass
// it to the presentation layer via the
// userListCallback.onUsersReceived() callback
}
    // use case 2
public void getHighestRankedUsers(UserListCallback
userListCallback){
// Ask the data layer for the highest ranked users.
// Once the data arrives validate, modify, etc and then pass
// it to the presentation layer via the
// userListCallback.onUsersReceived() callback
}
}

So for every user related use case in our app we could define an additional method in our UserDomain class and provide it a callback to send the data back to the presentation layer. Any data validation or modifications are done here before being passed onto the presentation layer.

Finally comes our data layer. Here we define methods which pass data to each of our use cases.

public class UserRepository {
    // User pojo
public static class User{
private int id;
private String username;

// setters and getters
}
    // Default constructor.
public UserRepository(){
// setup network configurations, database connections
}
    public List<User> getTop10Users(){
// get top 10 users from database or from network (note
// that network calls should go into a background thread)
// and return it to the caller (domain layer)
}
    public List<User> getHighestRankedUsers(){
// get top 10 users from database or from network (note
// that network calls should go into a background thread)
// and return it to the caller (domain layer)
}
}

So that will be the data layer for our user list. As you can see this layer is responsible for creating network calls and database connections. Its only purpose is to access these data sources for different use cases and return the data. There is no data validation or modification in this layer either. The reason for this is explained below.

Architecture benefits

I’ll start with the presentation layer. Remember I said that the presentation layer only reacts to the data it receives. It does not perform any functions on the actual data itself. By keeping the presentation layer clean of any domain logic we can easily switch out our presentation view. Say we want to switch out the ListView for the newer RecyclerView. The transition becomes simple because the only change we need to do is in how we display the data. There is no need to worry about any bugs in the domain logic (sorting, filtering, etc) which was applied to that data previously, since that is taken care of in the domain layer, and we did not touch that.

The same concept applies to the domain and data layers. If we needed to add an additional filtering step before displaying, all we need to do is add it to the relevant use case method in our domain class. There is no need to test the UI or data source all over again for this change. Need to change the network library? Swap out the old implementation with the new one in the data layer. The views and domain related logic will remain unchanged. By doing this testing becomes faster, and there will be fewer bugs along the way as well!

Bottom line

As you can see, this is one of the most basic applications of this clean architecture. The presentation layer solely consists of a single activity instance, and the domain and data layer each have a single implementation. Depending on the size and complexity of our application we can modify this architecture to make it more robust. For example, we could have different implementations, each one to handle view updates, view animations, user inputs and so on in the presentation layer. The same can be done with the domain and data layers as well depending on how stable we need our application to be. We could even add additional layers in between these three. The only thing you need to remember while doing this is not to over-engineer your app because this can lead to performance impacts and create a steeper learning curve to anyone else who starts working on your app.

I hope you enjoyed reading this post and have learnt something new in doing so. Next time I plan on writing about two libraries: RxJava and Retrofit. These libraries simplify the communication and greatly reduce the code complexity when passing data between our layers. I also plan on writing about how we can apply the State pattern to our presentation layer to achieve a more powerful way of transitioning between application view states. If you have any ideas on how we can improve this architecture please share your thoughts in the comments section below.