Harder, Better, Faster, Stronger

A story of How I met great Android libraries

Olivier Goutay
AndroidPub
9 min readApr 3, 2017

--

We all dreamed of making our application the best ever. There are actually plenty of tools out there that allows to improve drastically reliability and performance, while setting some nice architecture patterns. And this is the story of How I met these libraries.

The beginning

Ok so, to understand the first need, I have to tell you what the problem was.

The Android application I was working on was using Robotium to run Instrumentation tests (this framework is based on AndroidTestCase). It was really handful for a period of time, but the tests execution time was growing too much compared to the base code.
In the end, the test suite was running in 1hour and 30 minutes. When you are doing Continuous Deployment / Continuous Integration (using Jenkins for instance), more than one hour to deploy is too much. There were also a few random errors in the tests (waiting for an Activity or a Fragment was not always accurate, resulting in false negatives).

I needed a better solution for instrumentation testing!

Espresso

When I saw that Espresso was now recommended by Google, I wanted to give it a try.
After the migration, the execution time was reduced by 50% and the tests were much more stable.

Why?
Well mostly because Espresso has some useful methods, one of them called waitForIdle(), that, like the name says, waits for your app to finish everything it has to do before continuing. I found this method to be really reliable, and way faster that what AndroidTestCase was proposing.
Plus Espresso has everything that we had in the past: easy access to Contexts, Intents, Activities, Fragments, Views etc…
(Black&White box testing)

Mockito

Ok so reducing the tests execution time by 50% was already neat! I knew it could be even better, so I continued my research.

While analyzing the test suite, I realized that most of the slow downs were due to waiting times for server responses.
To be honest, this analysis was simply done with some “Log” that were showing the execution time at multiple steps of the tests. Nothing crazy!

To come back to my problem, let’s say you have a complex screen, and your server API is REST style. This complex screen mights require 5 different network calls in order to display all the different sections. Let’s say these 5 calls are taking 2 seconds (your local server is not super fast), that means that every time you are going through this screen, you are losing 2 seconds of waiting time, right?

That’s when I figured out that there are two things that need to be validated in the tests:

  • Acceptance: Validating that the REST API is still working with the application Models (the first M in MVC and MVVM)
  • Instrumentation: Validating that the Views, Controllers and ViewModels are working fine with the Models (VC and VVM)

So why not separating both, and stop waiting for server responses all the time!

That’s where Mockito is really awesome: you can simply mock your Http client response directly in the app. The architecture that I went with was:

  • An “acceptance” package, not using Mockito. These tests will assess that all the requests and responses defined in the controllers were conform to the Models.
  • An “instrumentation” package, using Mockito. These tests will fake the responses from the Http client and will assess that the relationship between Models and Views works.

As Mockito is super fast, the waiting times are reduced to ~0 second.

Results Espresso + Mockito

So if we take the first execution time of 1hour and 30 minutes, it was already reduced by 50% with Espresso (something around 45 minutes). With Mockito, the test suite execution time reduced again by a little more than 50%, moving it to 22 minutes.

Before Espresso + Mockito
After Espresso + Mockito

Good thing to note, I found this test suite to be more reliable and I have even more confidence in it.
When implementing a new feature, I just have to make sure to implement one test for the API<->Model interface, and then do everything else in Mockito style.

Second Part

In the previous part, I explained why I needed to improve the test suite execution time.
There were also some problems in the application that were causing slow downs and memory leaks. I needed to fix them and improve the overall architecture of the app.

The first noticeable thing was Singletons.
The “Manager” layer of the application was using the Singleton pattern, but without making too much sense of the dependencies between these Managers. Plus these managers were storing Contexts, which is a big source of leak on Android.

So how to identify and fix these dependencies?

Dagger 2

Dagger was developed by Square, and then updated to version 2 in association with Google developers.

If you have a big application that needs an architecture revamp, I would say that Dagger is especially great at that task, since it’s gonna force you to establish a nice dependency graph.

One very interesting thing of Dagger is, if you have a circular dependency in your injections, your application is gonna crash. That’s actually the best indicator that something is wrong in your architecture. It’s also going to prevent future code to present the same type of problem.

Dagger Injection

Integrating Dagger in your app is done by two steps:

  • First you need to build a “Module”. This module is gonna be the place where your dependencies are gonna be constructed.
@Module
public class AppModule {
public AppModule() {
}
@Provides
@Singleton
AccountManager provideAccountManager() {
return new AccountManager();
}
}
  • Secondly you need to create the “Graph” of your injections. This graph is gonna be the link between your dependencies and the class you inject.
@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
//Activities
void inject(MainActivityActivity mainActivity);
...
//Fragments
...
}

Once you’ve done that, Dagger is gonna generate a DaggerAppComponent that you can build and use to inject classes (you use it to inject your dependencies).

AppComponent daggerComponent = DaggerAppComponent.builder()
.appModule(new AppModule())
.build();
[...]
//Later in a class we want to inject
daggerComponent.inject(this);

Notes:
You can have multiple Modules and Components, depending on your needs.
You can store the AppComponent into something you have easy access in your app (like the Application class, if you override it).

Dagger + Espresso + Mockito

Dagger introduces a new way of injecting your dependency into your app. Taking advantage of it to inject Mockito dependencies into the app and tests would be nice.

Let’s modify our AppModule a little:

@Module
public class AppModule {
/**
* Used for tests purpose by AbstractEspressoMockitoTest
*/
private boolean mMockManagers;
public AppModule(boolean mockManagers) {
this.mMockManagers = mockManagers;
}
@Provides
@Singleton
AccountManager provideAccountManager() {
if (mMockManagers) {
return Mockito.mock(AccountManager.class);
}
return new AccountManager();
}
}

As you can see now, our AppModule has a “mMockManagers” variable that is used to provide either a real AccountManager instance, or a Mockito instance of that object.

Let’s see our AbstractEspressoMockitoTest#beforeActivityLaunched() method:

/**
* Need to execute this in the
* {
@link IntentsTestRule#beforeActivityLaunched()} to mock
* {
@link DaggerAppComponent} dependencies
*/
protected void beforeActivityLaunched() {
Context context = getInstrumentation().getTargetContext();
AppComponent mockDaggerComponent = DaggerAppComponent.builder()
.appModule(new AppModule(true))
.build();

App.setDaggerComponent(mockDaggerComponent);
}

Note:
In your normal App class, your instantiate the AppComponent with a false boolean.

Access Dagger dependencies from Espresso

If you followed until now, we need to do one more thing: have access to our injected “Managers” in the Espresso test case.
That way, we can call Mockito.verify() on these for example.

One way to do that, is to declare an “AbstractTest” class, in our main app package, and make AbstractEspressoMockitoTest extend it.

public class AbstractTest {    @Inject
protected AccountManager mAccountManager;
/**
* Inject all the Managers/Dependencies you want to mock here
*/
public void beforeActivityLaunched() {
App.getDaggerComponent().inject(this);
}
/**
* Used to reset the Mockito injection
*/
public void tearDown() {
App.setDaggerComponent(null);
}
}

Our AbstractEspressoMockitoTest#beforeActivityLaunched() method now looks like:

/**
* Need to execute this in the
* {
@link IntentsTestRule#beforeActivityLaunched()} to mock
* {
@link DaggerAppComponent} dependencies
*/
protected void beforeActivityLaunched() {
Context context = getInstrumentation().getTargetContext();
AppComponent mockDaggerComponent = DaggerAppComponent.builder()
.appModule(new AppModule(true))
.build();

App.setDaggerComponent(mockDaggerComponent);

super.beforeActivityLaunched(context);
}

You now have access to your Mockito objects in your app classes and in your test classes, allowing you to use both of these two libraries.

Note:
Building for release does not need to have dependencies towards Mockito. In this case, you want to create an AppModule class for the debug flavor (containing Mockito references), and one for the release flavor (not containing Mockito references). Don’t forget to declare the Mockito dependency in your build.gradle as “debugCompile”.

LeakCanary

Once I fixed the dependency graph of our classes, fixing memory leaks was much easier.

One good thing to know is that, on Android, “static” variables are considered as StrongReference (if there was a StrongReference java class). That means that this variable is not gonna be collected by the Garbage Collector unless you set it to null.
Declaring “static” any class that contains a Context on Android is a big memory leak, since the Context indirectly keeps a reference to all the Views and Activities.

LeakCanary is here to help you simplify the process of looking for these memory leaks. If you used already Android Monitor to capture a Heap Dump, you know what I’m talking about. It’s working, but the it’s not the easiest tool to use.

An example of LeakCanary

Once you obtain a Leak Trace, you can then think about how to fix it. There are multiple ways of doing so:

  • Don’t store the object anymore, just pass it and use it locally in the method.
  • Using WeakReference<> to let an object be collected if it’s not used anymore.
  • etc…

Results

So in the end, is using Dagger and LeakCanary really worth it?

How to prove it? We could try to check for the Cold Start time of the application. It’s generally a good indicator of the health of your application, since a bunch of initializations / collections happen when the app starts:

Before Dagger
After Dagger
After LeakCanary

Explanations:
Fixing your app dependencies means that you are gonna instantiate classes when you really need them. So you have more memory to run other part of your application faster.
Fixing your app memory leaks behaves in the same way. Before you could not free some part of the memory the application was using.

No need for more right?
Well you can actually do more, using Android Monitor to check your application memory and CPU usages for example. That would be a little too long to explain here, so you will have to believe me, the application memory management was much better after all of these changes.

Thank you for reading :)

Github:

I wrote an example application demonstrating the use of Espresso, Mockito, Dagger and LeakCanary all together. It is available on Github (as the other app code is not public):
https://github.com/olivierg13/EspressoMockitoDaggerLeakCanary

Don’t hesitate to check it out :)

References

Espresso: https://developer.android.com/training/testing/ui-testing/espresso-testing.html

Mockito: https://developer.android.com/training/testing/unit-testing/local-unit-tests.html

Dagger (Square/Google): https://google.github.io/dagger/

LeakCanary(Square): https://github.com/square/leakcanary & https://realm.io/news/droidcon-ricau-memory-leaks-leakcanary/

--

--

Olivier Goutay
AndroidPub

Senior Software Engineer @ Solana Labs | Ex-Netflix | Ex-Mentor at Code2040