How to better structure your Android application

Edoardo Tognoni
7 min readDec 13, 2015

--

After a few months working in busuu I’ve learnt that structuring your code is probably at the first position of things to do when dealing with a long life product. busuu is a learning language platform and community, and for those who haven’t heard about it, go and download it. With this article I’m trying to explain how we structure our code and how this is helping a lot when implementing new features and bugfixing.

Our code is based on The Clean Architecture with some little changes to better adapt it to our needs. The core principle we keep in mind when developing is: Android’s evil! In other words, the less you depend on the Android framework, the better. For this, all the Java modules are easily and fully unit testable with JUnit/Mockito while, as you may already know, Android dependent objects are not so easy to test. Let’s start talking concretely.

We have split our app in different layers (gradle modules):

  1. UI (Android module)
  2. Presentation (Java module)
  3. Domain (Java module)
  4. Repository (Java module)
  5. Data source (Java module)

This is the dependency tree of these modules:

Gradle modules dependency tree

I’m going to try to explain the roles of these layers with a concrete example of an use case inside our app: Loading a course. This is the first thing the app does when it’s opened.

UI module

This is the Android app itself (the module that you actually compile and run). It’s made of all the Activities,Fragments,Adapters and all of the things that depend on the Android framework. There’s absolutely no logic involved here. All the app logic is delegated to the other layers. The role of the UI layer is just to show data to the user.

public class CourseFragment extends ContentFragment implements CourseView {@Inject 
CoursePresenter mCoursePresenter; //Presentation layer
@Override
public void onStart() {
super.onStart();
...
mCoursePresenter.onUiReady(mCourseLanguage);
}
@Override
public void showCourse(Course course) {
mLessonsAdapter.setCourse(course);
mLessonsAdapter.notifyDataSetChanged();
}

As someone may already notice, this is just the normal MVP pattern. When onStart is called, we call the method onUiReady of the presenter passing as an argument the course language we want to load. When the course will be ready then, the method showCourse (that belongs to the interface CourseView) is called by the presenter to actually populate the adapter.

Presentation module

The role of this module is:

  1. Make some little logic if needed
  2. Launch other threads (Interactions) to load data
  3. Get the result back from the lower layer (make some little logic if needed)
  4. Present the data back to the UI module.
public class CoursePresenter {public void onUiReady(Language courseLanguage) {
mView.showLoadingCourse();
//Pass data down to the domain layer
mLoadCourseInteraction.setCourseLanguage(courseLanguage);
mInteractionExecutor.execute(mLoadCourseInteraction);
}
@Subscribe
public void onCourseLoadingFinished(FinishedEvent loadCourseFinishedEvent) {
mView.hideLoading();
if (loadCourseFinishedEvent.getError() != null) {
mView.showErrorLoadingCourse();
}
else {
final Course course = loadCourseFinishedEvent.getCourse();
mView.showCourse(course);
}
}

In the method onUiReady we pass the data down to the domain layer and we wait for the result on onCourseLoadingFinished. To get the result back we use a Bus that we register/unregister inside the presenter. It will be the lower layer to fire these events up.

Domain layer

This layer is the “thread divider”. All the code executed from this layer runs in an another thread different from the UI one. We call every component in this layer an Interaction. You can think an Interaction as a Java Runnable where there’s one method (execute()) that does all the work. The role of this layer is to call all the methods that could potentially block the UI such as:

  • Loading/Saving from/into the Database
  • API calls

Also, this layer is responsible of catching all the exceptions that could raise from these calls, and making sure that all the data is consistent and ready to be displayed.

This module is a pure implementation of the Command pattern. There’s the Invoker (see mInteractionExecutor in the presenter) and the Commands (see mLoadCourseInteraction in the presenter). We don’t use any Loader or AsyncTask because this is a Java module.

In the example, as the name suggests, we need to load the Course object. NOTICE: We still don’t know wether we need to get the object from the database, API or any other data source.

public class LoadCourseInteraction implements Interaction {@Override 
public void execute() {
final FinishedEvent event = new FinishedEvent();
try {
Language courseLanguage = mCourseLanguage;
if (courseLanguage == null) {
courseLanguage = mUserRepository.loadLastLearningLanguage();
}
final Course course =
mCourseRepository.loadCourse(courseLanguage);
event.setCourse(course);
mUserRepository.saveLastLearningLanguage(courseLanguage);
}
catch (CantLoadCourseException e) {
event.setError(e);
}
mEventBus.post(event);
}

The behavior is quite simple:

  • Course language validation
  • Load the course
  • Store the course language as the last opened by the user

Error handling:

  • What happens if I can’t load the Course? I set an error so the presenter knows that something went wrong
  • What happens if the course language from the presenter is null? Let’s try to see if I can get it from the last saved language

Repository

The repository layer is responsible of deciding where to get the data from (Database or API in this case). This is the only gradle module that has no dependencies! For this, it needs to hold all the entities (Java beans objects), all the custom exceptions, and all the interfaces of the Data module. Get back to the graph at the beginning: It’s the Data source module that has a dependency on this module, so this layer must use interfaces to interact with the lower layer. Some examples of Repositories could be:

  • CourseRepository: Loading/Saving course related objects
  • UserRepository: Loading/Saving user related data
  • ProgressRepository: Loading/Saving the progress in the course of the user

Let’s see the loadCourse method from the CourseRepository:

public Course loadCourse(Language language) throws CantLoadCourseException {
try {
Course course = mCourseDbDataSource.loadCourse(language);
if (course == null) {
course = mCourseApiDataSource.loadCourse(language);
mCourseDbDataSource.persistCourse(course);
}
return course;
}
catch (ApiException | DatabaseException e) {
throw new CantLoadCourseException(e);
}
}

At first it tries to load the course from the DB. If there’s no course, we ask it to the API. When done, we store it in the DB. If anything goes wrong, the method throws an exception which will be caught by the domain layer.

Data source layer

The data source layer provides all the implementations of the interfaces used in the Repository layer. Again, this is a Java module, no Android involved here. An example of a Data source could be:

  • Database
  • API
  • SharedPreferences
  • Assets
  • SD

Clearly the SharedPreferences are Android dependent. In this case, we work again with interfaces. We delegate every action to an Interface and the implementation will be provided through Dependency Injection.

There’s another important type of entity in this layer: Mappers

The mappers are responsible of “translating” one object into another. For example we can have the LanguageApiDomainMapper object that changes the Language enumeration into a String understandable by the backend APIs. Or in the other way round a String into a Language enumeration. Why do I need this? Because working with Enumerations is way easier compared to Strings. This rule also applies for all of the other objects. Let’s get to the code:

I’m taking the ApiCourseDataSourceImpl object for this example. This is the data source we use to communicate with the backend:

public class ApiCourseDataSourceImpl 
implements CourseApiDataSource {
public Course loadCourse(Language courseLanguage) throws ApiException { final String apiCourseLanguage =
mLanguageMapper.upperToLowerLayer(courseLanguage);
try {
final ApiCourse apiCourse =
mService.loadCourse(apiCourseLanguage); //API call
.... // Map the apiCourse to the course object
return course;
} catch (Throwable t) {
throw new ApiException(t);
}
}

This is the mapper for Language:

public class LanguageApiDomainMapper 
implements Mapper<Language, String> {
@Override
public String upperToLowerLayer(Language language) {
return String.valueOf(language);
}
@Override
public Language lowerToUpperLayer(String apiLanguage) {
try {
return Language.valueOf(apiLanguage);
} catch (IllegalArgumentException | NullPointerException e) {
return null;
}
}
}

Further improvements

Right in these days we noticed that we’d better have other Mappers in the Presentation layer that translate Domain object to UI objects. Why? Because if we need to handle configuration changes we better have these objects as Parcelable. So (when needed), the presenter before passing the object to the UI, it translates it to a UI object. Again, Parcelable means Android dependent while the Presentation layer is a Java module. Solution: Interfaces! The presenter has a class field object of the type <Object>UiDomainMapper that looks like this:

public interface ExerciseUiDomainMapper {   UIExercise lowerToUpperLayer(Exercise exercise);}

The UIExercise is a normal Java interface. The mapper interface and the UIExercise are declared in the presentation layer. The actual implementation of both of them is inside the UI module and it’s provided to the presenter through Dependency Injection.

Dependency Injection

To make this structure working and flexible we use Dependency Injection with Dagger to provide all the objects and interfaces’ implementations. The dependencies are injected in the UI and all the Dagger modules are living in the Android module. We create our ObjectGraph starting from a basic graph that contains all the Interactions,Repositories,Data sources. We create a scoped graph to provide the Presenter implementations. With this, Dagger is able to satisfy all the dependencies down to the Data source module. Here’s the example for the CoursePresentationModule.

@Module( 
injects = CourseFragment.class,
complete = false
)
public class CoursePresentationModule {
private final CourseView mView; public CoursePresentationModule(CourseView view) {
mView = view;
}
@Provides
public CoursePresenter providePresenter(EventBus bus, InteractionExecutor executor, LoadCourseInteraction loadInteraction
{
return new CoursePresenter(mView, executor, loadInteraction,
bus);
}
}

The LoadCourseInteraction, EventBus, InteractionExecutor objects are provided in the basic graph that lives in the Application object.

Conclusions

This is a good structure but it’s not the solution to all the problems. This works well in our case, and may be not in others. Also, this is not perfect. The codebase is increasing, but it’s easily unit testable. Anyway, the point is that whatever your app is doing, your code should follow a clean structure that’s divided into layers with different roles. I hope this helps anyone, and if you have any comments/suggestions or needs declarations, I’m all ears.

Thanks to @Christian Garcia and @Jarek Ankowski for reviewing and comments.

--

--