Monday, May 25, 2015

What To Expect When You're Expecting...

(...To Move Your App to Android Studio)


Introduction

I recently migrated a decently sized Android project that was originally built in Eclipse with ADT into Android Studio. I've been developing in Java for nearly 14yrs now and Eclipse has been my weapon of choice for ~10 of those years (just... don't ask about the other four). Inasmuch, all the tinkering and whatnot I've done in Android for the past 4-5yrs has been in Eclipse, too. Between all the buzz about Android Studio and the irrefutable, stone cold fact that all the cool kids use IntelliJ, I had been looking forward to changing IDEs for some time to see how much greener the grass is on the other side. As fate would have it, business-related happenings at my current place of employment made the new "build flavors" configuration option (by way of Studio's Gradle plug-in) a veritable must-have, so thus began my journey to the other side.

While I'm satisfied with the variant-based solution we eventually arrived at in switching over to Studio, there were some unexpected hurdles to overcome during the transition in order to begin implementing that solution. The following is an account of those hurdles and what I did to get over them.

Some information about my setup:
  • Android Studio 1.1.0 (though I think this write-up still holds for the 1.2.x branch)
  • Gradle Plug-in 1.1.2
  • Robolectric 3.0-rc2
  • OS X Yosemite
  • The migration itself was performed using Studio's "Import Project" wizard
  • Pre-Lollipop build

MultiDex

As you well know, your source code morphs a few times before it's actually ready to be run on an Android enabled device. Assuming Java as your language of choice, that lifecycle looks a little something like this:

  1. Firstly, your .java files are compiled into your normal VM-spec compliant .class files. 
  2. Since Android devices run a different variant of VM, named Dalvik (the default runtime for pre-Lollipop devices), the aforementioned .class files are then converted into Dalvik EXecutable files (.dex)
  3. Lastly, those .dex files (along with your compiled manifest, resources, etc) are packaged into an installable package (.apk) file.
Through this process, those of you with large projects or perhaps projects that have runtime dependencies on large third-party libraries, may have frustratingly come to learn that the Dalvik specification limits the number of methods that can be referenced within a single .dex file to 65,536.  (Why? <shrug>).  Should you breach this limit you'll be met with an aptly descriptive build error that looks like something along these lines:

Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536

What was really odd in my case was that before the transition to Studio I wasn't hitting this limit. It was only after the migration that my app wouldn't build as a result of hitting this 65k method limit. My (lazy schmuck) theory as to why this happened was that the import wizard may have converted some of the .jar files in my libs/ folder into Gradle managed dependencies, and in that process they maybe got swapped for later versions of those same libraries... which were larger? I know, I know. At the time I just wanted to get things working again, so rather than investing time in figuring out what changed I went about resolving the problem the build system was reporting.

While the recommended solution to this problem is to reduce the number of method references in your application, sometimes this just isn't possible (such as in the case of third-party libraries). Therefore Google's Android Team has produced a solution wherein your build's supplementary .dex files are referenced by the support library (for pre-Lollipop builds), or just natively supports handling multiple .dex files (for builds done against Lollipop or later).

In my case (pre-Lollipop with a custom android.os.Application class), enabling multidex handling involved two steps:

1) Updating the app's build.gradle file to set the multiDexEnabled flag, and importing the multidex support library as a dependency:

android {
  defaultConfig {
    multiDexEnabled = true
  }
}
dependencies {
  compile 'com.android.support:multidex:1.0.0'
}

2) Overriding Application.attachBaseContext() in my custom Application class:

@Override 
protected void attachBaseContext(Context base) { 
    super.attachBaseContext(base); 
    MultiDex.install(this); 
}

The extra emphasis on there being two steps comes from a mistake I made early on. I initially didn't realize the change to my Application class needed to be done. This resulted in the app building, but constantly crashing at runtime due to ClassNotFoundExceptions. Oblivious to what was going on, I unarchived my .apk file and noticed a few .dex files:
  • classes.dex
  • classes1.dex
  • ...
Strangely enough, it was within classes1.dex that the classes identified in the ClassNotFoundExceptions were hiding. Tugging on this thread led me on a few Google searches that helped bring me to the realization of the mistake I had made, which, hopefully now you won't.


Robolectric

...is now a first class citizen in the development cycle (as of Studio 1.2.x)! Rejoice ye responsible developer!

That's not to say we're completely problem free though.


Test Runner Configuration

Robolectric doesn't seem to resolve the application ID correctly when testing against different build flavors. However, creating a dummy BuildConfig with a hard-coded application ID (reflective of the main source tree) seems to be enough to get by in lieu of a real fix. 

My hacky little custom BuildConfig looks something like this:

public class TestBuildConfig {
    public static final boolean DEBUG = BuildConfig.DEBUG;
    public static final String APPLICATION_ID = "your.main.package.name";
    public static final String BUILD_TYPE = BuildConfig.BUILD_TYPE;
    public static final String FLAVOR = BuildConfig.FLAVOR;
    public static final int VERSION_CODE = BuildConfig.VERSION_CODE;
    public static final String VERSION_NAME = BuildConfig.VERSION_NAME;
}

Just refer to this class in your test's configuration annotation, like so:

@Config(constants=TestBuildConfig.class)

...among whatever other configuration options you intend to pass along.

To be fair, I'm not sure if this is a byproduct of my using a release candidate of Robolectric... although in my defense this seems to be a rather glaring problem for a second release candidate. But hey, we're all friends here.


Test Directories

If you're using build flavors in your setup and you're wondering what the directory structure for flavor-specific tests is supposed to look like, here it is:

  -- src
    |-- flavor1
    |-- flavor2
    |-- main
    |-- test
    |-- testFlavor1
    `-- testFlavor2

assertj + assertj-android

If you're using assertj, don't bother trying to use the 2.x branch or later for testing your Android code. It uses a Path-based approach to making assertions, which isn't (yet?) compatible with Dalvik.

Stick with the latest release on the 1.x branch.

Dagger + ButterKnife

These amazingly work right out of the box with little-to-no configuration changes.

The one hiccup I experienced had to do with Studio's overzealous import wizard actually importing the generated code from these frameworks, thus resulting in subsequent builds failing due to classes already being defined when the frameworks attempt to re-generate them. The build system is likely to report an error along these lines:

Error:(7, 8) error: duplicate class: com.android.test.Foo$$ViewInjector


Annoying, yes, but thankfully simple to resolve. Traverse your source tree and delete all the classes with names that are suffixed with:
  • $$InjectAdapter
  • $$ModuleAdapter
  • $$ViewInjector

Saturday, January 3, 2015

Adventures in Code: Android MVP By Example

In this post, we'll take a look at an application of the MVP pattern in Android. Given that MVP is an architectural style as opposed to a rigid set of rules to be followed, it goes without saying that what follows is merely one schmuck's interpretation of that pattern, for better or for worse. Comments, feedback, and spirited debate are always welcome!

Note - this write-up assumes some working knowledge of Dagger. I found the documentation on the site to be really thorough, and having experience with Guice is a bit of a plus, but if you want a write-up on Dagger then let me know in the feedback.

Let's start by establishing what we're doing and why we're doing it before we get into the code.

What is MVP?

As mentioned earlier, MVP is an architectural pattern for designing software, that when used, is most commonly found in applications that have a user interface. The pattern assumes three logical components:

  1. Model (an interface defining what data will displayed by a View)
  2. View (an interface describing how data represented by a Model will be displayed)
  3. Presenter (an intermediary facilitating communication between the Model and View)
Simply illustrated...


A typical interaction might go something like this:

  1. A user interacts (click, touch, drag, fling, butt-dial, etc) with a screen element hosted by the View
  2. The View delegates this interaction to a Presenter 
  3. The Presenter interprets this interaction and in turns communicates with one or more associated Models
  4. If any results are forthcoming, they are then communicated back to the Presenter and then finally to the View


OK... But Why Use It?

A few reasons:
  • Separating the domain logic and the presentation logic into discrete components allows you to unit test the behavior of each in isolation, rather than having to test both in conjunction by doing instrumentation style testing on an emulator or a real device:
    • The UI (activities and fragments) can be tested with Presenter(s) that are test doubles (using a framework like Robolectric), and...
    • Presenters can be tested with Views that are test doubles (without any Android dependencies)
  • Having smaller, focused classes helps steer you away from code bloat within any one class 


Sold! Show Me How!

Let's assume for this example that we have an application that bootstraps itself by checking to see if the current installation has an account associated with it. For this, we'll design three components in line with the MVP style:

An interface for the Model...
... an interface for the View...
(It might be kind of hard to see at this point, but BootstrapView is shaping up to be a pretty passive component. For example, it knows how to show and hide its progress indicator... but it doesn't know when to do either!)

... and finally, an interface for the Presenter:
Now let's put some meat on those bones.

This being an Android application, we'll create an Activity named BootstrapActivity that implements our BootstrapView interface:
Let's turn our attention to the more noteworthy parts:
  • (Line 15) as you can see, BootstrapActivity is an extension of InjectingFragmentActivity. A full description of InjectingFragmentActivity goes beyond the scope of this post, but it should suffice to say that its onCreate() method is overridden to invoke Dagger to create a sub-graph of the application's dependencies and subsequently use that graph to inject `this` instance. As a result, we have an instance of a BootstrapPresenter after the call to super.onCreate() on line 22.
  • (Line 15) as previously mentioned, this class implements our BootstrapView that will later be used to drive the UI.
  • (Line 24) this is the jump off point for our domain logic. When the activity is created, the BootstrapPresenter is invoked to check for an existing account.
  • (Lines 34-54) the BootstrapPresenter instance will asynchronously invoke any of these methods depending where it is in it's invocation of checkForAccount().

Now let's look at an implementation of BootstrapPresenter:


Let's once again inspect the noteworthy stuff:
  • Take note that this class is a simple POJO and thus requires no special Android framework-y stuff in order to test
  • (Line 8) as expected, this class implements our BootstrapPresenter interface 
  • (Lines 10-18) the same instance of BootstrapView that invoked the call to checkForAccount() will be injected as an argument for constructing this instance of BootstrapPresenterImpl. Keeping this reference around his gives us a way to call back to the UI to perform the various actions exposed by the BootstrapView interface.
  • (Line 16) wrapping the reference to BootstrapView with a WeakReference here is critical to memory management in that it signals to the VM that the wrapped instance should not be retained on account of this referrer. In other words, if the only references to the wrapped instance are in the form of a WeakReference, then the garbage collector can feel free to reclaim the memory.
  • (Lines 22-24) while the WeakReference itself will not be null, its delegate may be if the previously described circumstances called for it to be garbage-collected. A simple null check will tell you everything you need to know.
  • Now we'll basically drive the behavior of BootstrapView:
    • (Line 27) instruct the BootstrapView to show the user that the application is currently doing work.
    • (Line 31) instruct the BootstrapView to stop doing whatever it was doing to show the user that the app was doing work.
    • (Lines 33-37) invoke BootstrapView to either take the user to the account registration or to whatever other entry point the application has defined for users that already have an account.
  • (Lines 41-43) this is obviously a dummy implementation for example purposes, but this type of behavior could be further decomposed into Services, Interactors, etc. that can likewise be injected into this instance and delegated to.
And there you have it. MVP on Android.