Architecting Android APP for the aSYNC

Gaurav Bhola
AndroidPub
Published in
9 min readMar 3, 2018

Why this article?

So, this article tries to address some of the architectural issues which arise while doing async operations especially while fetching something from the network (a Network Resource) in an android app and what kind of async approaches can be used to solve those. This article will ensure that the following assumptions are met:

  1. You are good when your app gets big. More screens, more data, more network resources.
  2. Business logic remains intact even if the UI changes frequently i.e. the UI should be independent of the Business Logic and async processing.
  3. Various classes of the code are well separated and responsible for different things. (Separation of concerns)
  4. The code is easy to maintain (Ease of use)
  5. The code is fairly unit testable

We will be discussing 4 approaches

  1. AsyncTask
  2. Custom Executors coupled with EventBus
  3. Custom Executors coupled with LiveData — MVVM using Google’s arch components
  4. Background Service

Prerequisites

  1. You are familiar with EventBus
  2. You are familiar with Observables
  3. You are familiar with Retrofit

Before starting an app → Act Early!

  • The decisions you are going to take will effect the app until its death
  • Which patterns? MVC, MVP, MVVM etc
  • Architect for the user experience
  • Don’t think request-response, think Sync
  • Decouple, when it makes sense

Lets Begin

So, lets begin and build a Contacts App which has 2 screens, one for showing the contacts and the other one for adding a contact.
AddContactsActivity and ViewContactsActivity are the required screens.

Yikes!

If you will observe, these 2 activities host fragments and not the content directly. Its a good practise to do so because it gives future developers the flexibility to move that fragment to some other screen(Activity) if need be as per our assumption 2..

So, the actual stuff happens in the AddContactsFragment and the ViewContactsFragment

Approach 1: AsyncTask

Code: (github)(branch: async-task)
The AsyncTask used in ViewContactsFragment looks something like this:

private static class FetchContactsTask extends AsyncTask<Void, Void, List<Contact>> {
WeakReference<ViewContactsFragment> mFragmentInstance;
ContactsManager mContactsManager;

public FetchContactsTask(ContactsManager manager, ViewContactsFragment fragment) {
super();
mContactsManager = manager;
mFragmentInstance = new WeakReference<ViewContactsFragment>(fragment);
}

@Override
protected void onPreExecute() {
super.onPreExecute();
if (mFragmentInstance.get() == null) {
// Fragment not present, skip
return;
}
mFragmentInstance.get().showLoading();
}

@Override
protected void onPostExecute(List<Contact> contacts) {
super.onPostExecute(contacts);
if (mFragmentInstance.get() == null) {
// Fragment not present, skip
return;
}
if (contacts == null) {
mFragmentInstance.get().showError("Some error!");
} else {
mFragmentInstance.get().showData(contacts);
}
}

@Override
protected List<Contact> doInBackground(Void... voids) {
List<Contact> contacts = mContactsManager.fetchContacts();
return contacts;
}
}

So, its a static inner class with a weak reference to the parent fragment to avoid memory leak. Looks simple and looks the way it should be done.
A similar task is present in AddContactsFragment as well.
We also have a ContactsManager class that is responsible for providing data by any means possible (network, disk or whatever). Looks like this:

@Singleton
public class ContactsManager {
// Considering it to be same as of now
final int mAccountId = 1;
private ApiService mApiService;

@Inject
ContactsManager(ApiService apiService) {
mApiService = apiService;
}
@WorkerThread
public List<Contact> fetchContacts() {
// Calls the retrofit api and returns the response
}
@WorkerThread
public AddContactsResponse addContact(final Contact contact) {
// Calls the retrofit api and returns the response
}
}

ContactsManager is responsible for getting the data from the network using retrofit and cache the results, if required.
Now lets see how better this design satisfies our assumptions

  1. More screens: Which means more fragments, we will end up defining more AsyncTasks in each fragment/activity.
    If there are multiple screens which care for same AsyncTask then we might need to implement Observer pattern in AsyncTask where several fragments/activities can subscribe for events.
    Hence, we definitely need to have a publisher-subscriber mechanism for async operations (or for network resource state update).
  2. Business Logic: It lies with in the ContactsManager class
  3. Separation of concerns: The AsyncTask knows so much about the fragment(UI). We should rather have a design where the AsyncTask is decoupled from the UI which can be easily done by making the fragment implement a custom interface.
  4. Maintenance: The code is simple and hence easily maintainable even by new developers
  5. UnitTestable: Slightly tricky to test the AsyncTask

Approach 2 :Use Executors with a publisher/subscriber mechanism

Code: (github)(branch: master)

Why moving away from AsyncTask?

AsyncTasks internally use executors only. By default a serial executor is used by AsyncTask.
MoreScreens: More screens means there will/can be tasks which will happen concurrently and hence we will need to have AsyncTask run on an executor with multiple threads. Which can be done easily but then it appears, why even to use an AsyncTask. We can ditch it and use custom executors.
Separation of concerns: It makes more sense to offload the task creation from the UI (i.e fragments/activities) to the Manager classes handling the business logic. Hence, any kind of task (Runnable) creation for fetching a network resource will happen in ContactsManager.
This ensures that our UI need not to care about the complex asynchronous tasks which are running behind the scenes.

EventBus:

I decided upon using EventBus but you can use any other publisher-subscriber library that you find good.

NetworkResourceEvent:
Its a helper model class that encapsulates the state of a network resource. So, basically EventBus will keep posting instances of this class and the Fragments/Activities will be listening to these events.

A Separate EventBus for every network resource or a global EventBus ?

Separate EventBus:

You would have guessed it by now that we are moving towards Reactive way of doing things. Having separate EventBus instances sounds an overkill because its not made to cater to this kind of need.
As an alternative here, you could use Observables or LiveData from Google’s new arch components.

Yikes!
If you use Observable or LiveData, you might want to also use custom CallAdapter and CallAdapterFactory so that retrofit api service outputs Observable/LiveData instead of Call object.
A sample CallAdapter and CallAdapterFactory for LiveData can be found here:

LiveDataCallAdapter

LiveDataCallAdapterFactory

Global EventBus:

Global EventBus seems fairly easy to implement. Though, we need to take care about one thing. Lets say both our fragments are listening to ResourceEvents like this:

@Subscribe(threadMode = ThreadMode.MAIN)
public void onTaskEvent(ResourceEvent event) {
// We need to check if this event is for fetchContacts call or addContact call
switch (event.getStatus()) {
case ERROR:
showError(event.getMessage());
break;
case LOADING:
showLoading();
break;
case SUCCESS:
showData(event.getData());
break;
default:
}
}

We need to distinguish events by their type. In our case the events can either be related to fetchContacts task or addContact task.

  • One way of doing this is by having different Classes for different event types like I have done.
// In ViewContactsFragment
@Subscribe(threadMode = ThreadMode.MAIN)
public void onContactEvent(FetchContactsEvent event) {
// Do stuff
}
// In AddContactsFragment
@Subscribe(threadMode = ThreadMode.MAIN)
public void onContactEvent(AddContactEvent event) {
// Do stuff
}
  • Another better way of doing this is by having type field in ResourceEvent and set it every time we create a new event and check the same in onTaskEvent. — Its for you to implement :)

NetworkBoundTask

It does what our AsyncTask was doing before but it does it smartly.

All the data managers(like ContactsManager) in your app will make use of this class to start a new NetworkBoundTask.
This class is responsible for posting the ResourceEvent(s) which are handled in the UI. This class is actually inspired from NetworkBoundResource (from the google’s GithubBrowserSample).

Yikes!
If you will have a look at NetworkBoundResource. You will find an abstract method shouldFetch(T data). Basically this method provides the data that is received by loadFromDb() and lets the subclasses decide if the data needs to be re-fetched from the network. This is really helpful when we are thinking of SYNC. So, we can always decide if we need to use the cached data and/or fetch from the server and update the UI correspondingly.

AppExecutors

This class is responsible for keeping the executors for networkIO, diskIO and mainThread. The executors config can be whatever you feel is good for your app. For example

// Using dagger to create a singleton instance of AppExecutors
@Provides
@Singleton
AppExecutors provideAppExecutors() {
return new AppExecutors(
Executors.newFixedThreadPool(3),
Executors.newSingleThreadExecutor(),
new AppExecutors.MainThreadExecutor()
);
}

Using Thread pool with 3 threads for networkIO and a Thread Pool with a single thread for diskIO.

ContactsManager Updated

The code looks something like this:

public void fetchContacts() {
mAppExecutors.networkIO().execute(new NetworkBoundTask<List<Contact>, FetchContactsEvent>(mEventBus) {
@Override
public Call<List<Contact>> createCall() {
return mApiService.getContacts(mAccountId+"");
}

@Override
public FetchContactsEvent getNewEventWithCachedData() {
return new FetchContactsEvent();
}

@Override
public void saveCallResult(List<Contact> response) {
// Save in Db if required
}
});
}
public void addContact(final Contact contact) {
mAppExecutors.networkIO().execute(new NetworkBoundTask<AddContactsResponse, AddContactEvent>(mEventBus) {
@Override
public Call<AddContactsResponse> createCall() {
AddContactsRequest request = new AddContactsRequest();
request.setAccountId(mAccountId);
request.setEmail(contact.getEmail());
request.setName(contact.getName());
request.setPhone(contact.getPhone());
return mApiService.addContact(request);
}

@Override
public AddContactEvent getNewEventWithCachedData() {
return new AddContactEvent();
}

@Override
public void saveCallResult(AddContactsResponse response) {
// Save in Db if required
}
});
}

These 2 functions look quite interesting. Now we have developed a framework for fetching network resources from the internet. We can cache the results using saveCallResult and use the cached result in getNewEventWithCachedData() which get posted to the UI eventually.

Now lets see how better this design satisfies our assumptions

  1. More screens: Not an issue any more. Any number of screens can listen for any number of events for which they are interested in.
  2. Business Logic: It lies with in the ContactsManager class
  3. Separation of concerns: All the classes used here NetworkBoundResource, ContactsManager, ResourceEvent are responsible for one and only one thing.
  4. Maintenance: The code has become little complex and hence, the new developers might need some level of expertise to maintain it.
  5. UnitTestable: All of these classes are pretty much unit-testable. To look for the unit-testing please refer to the GithubBrowserSample’s tests.
    InstantAppExecutors, NetworkBoundResourceTest.

Approach 3: Custom Executors coupled with LiveData — MVVM using Google’s arch components

Well, our approach 2 is quite close to this approach. The only difference being the application flow logic and the UI are still controlled by the fragments and activities. So, here:

  • The Managers (ContactsManager) will become Repositories supplying dynamic data in the form of LiveData
  • We have ViewModels to filter out, transform and provide this dynamic data to the View (fragment/activity). Having that said, ViewModels also contain the application flow logic and the presentation logic.
  • Views (Activity/Fragment) are totally dumb and listen for the changes in ViewModel’s provided LiveData.
    Bonus: You don’t need to unregister for changes in LiveData because LiveData is lifecycle aware and it unregisters the View itself when View is destroyed.

GithubBrowserSample is a nice sample application for Approach 3.

In case this article receives nice response, i will surely implement this ContactsApp with arch components. Or I will be more than happy to accept a pull request from YOU.

Approach 4: A Background Service coupled with a HandlerThread or An Intent Service

We won’t go in much detail for this approach.

If you truly need the ability to run a background task in a way that is entirely decoupled from the UI, you should use an IntentService or a BackgroundService coupled with a HandlerThread.

Conceptually a background service is the most desirable place for long running tasks (generally more than 1 type of task) which are independent of the UI to be in.
If the tasks are to be handled sequentially, IntentService can be used.

The concern here is the added complexity of sending request and receiving response from the service since the maximum size of the bundle of data in an Intent is quite small.
The responses and events can be received using EventBus but sending the Request still boils down to creating an Intent.

End Note

All the approaches described in this article are not ‘one size fits all’ solutions. It very much depends on the kind of async operations you are going to have in your app. But for most general cases where your app communicates with a decent REST Api and fetches/updates some network resources, this is probably one of the good ways to go about architecting your application.

Resources

All of my hybrid knowledge comes from:

  1. Efficient Android Threading — Anders Goransson
  2. DevSummit Architecture demo — Yigit Boyar
  3. Google Sample — Android Arch Components
  4. Some years of writing android code

--

--