Java, Kotlin or Dart? One App, three implementations.

Part 1: Java

Java, Kotlin or Dart? Developers are spoilt for choice when it comes to writing for Android. In this series we will compare these technologies by looking at three different implementations of the same application.

This first installment introduces the application and its Java implementation. There will be a particular focus on testing, as this is an area in which there have been huge improvements over the last couple of years.

IndoFlash

Many years ago, when I was studying Indonesian, I wrote a flash-card application to help with learning lists of words. This original version was written in Swing, and I was able to run it on my laptop during my daily commute.

Later on, after buying my first smart phone, I re-wrote the application so that it would work on Android. This version, called IndoFlash, is freely available on the Play store.

IndoFlash comes pre-loaded with lists of Indonesian-English word pairs. The user is presented with a word and needs to think of the translation, or ask for help, then move on to the next word. After finishing a list, they can repeat it, or go to another list. There is a special “Favourites” list for those words that the user is currently having trouble with. There are 57 lists, arranged into “chapters” of about ten word lists each. The word pairs in the list can be viewed in a fixed order or randomly, and the user can elect to view the either the English or the Indonesian word first. It’s a simple but very effective tool for acquiring vocabulary.

Android Programming: the old days

My first experience with programming for Android was quite mixed. 
The platform was well documented, the application life-cycle was pretty straightforward, and I was a proficient Java programmer, so getting started was not too hard. However, writing tests was painful. My previous experience with user interfaces had been with Swing, in which it is pretty easy to write automated tests. (In fact, I had even co-authored a book about this: Swing Extreme Testing.) With Android, it seemed easy to write tests of the non-interactive parts of the software, but UI testing was another matter.

The tests were slow and flaky. The fact that it was even possible to do automated testing was great, and it was exciting to see them working on various emulated phones, and I did eventually get a suite of about 20 whole application tests running, but their execution took more than seven minutes and there was a lot of “magic code” needed in order that the tests would work one after another.

The Java implementation

IndoFlash is pretty simple. It has three distinct screens, which are: WordListDisplay, for showing the currently active list of words; WordListSelecter, for choosing a new list of words from the current chapter; and ChapterSelecter, for choosing a new collection of word lists. These all extend Activity.

Apart from these, there is a package of simple classes for reading an XML configuration file, and a package containing Wordand WordList, which model vocabulary. The main class is IndoFlash, an Application that coordinates the activities and holds the application state.

Android programming in 2017

The implementation described above is, apart from a bit of re-organisation into different packages, largely the same as the original from 2014. When it comes to the tests, however, there are a few changes to take advantage of the Espresso testing framework, which was introduced fairly recently.

Tests for Android either run as local unit tests, or as instrumented tests on an emulator. IndoFlash has local unit tests for Word, WordList and the XML handling classes. These are just standard JUnit tests. For example, here is a test that a WordList can be deserialised:

@Test
public void read() throws Exception {
String data = "satu=one\ndua=two\n\ntiga=three";
Reader reader = new StringReader(data);
WordList deserialised = WordList.read(reader);
assertEquals(deserialised.words(), wl123().words());
}

The Activity subclasses have unit tests that run on the emulator and use the Espresso framework. This is controlled through JUnit 4 rules, for example:

@RunWith(AndroidJUnit4.class)
public class WordListDisplayTest {
@Rule
public ActivityTestRule<WordListDisplay> activityRule = ...

Finally, there are integration tests of the entire application. These verify the application against specific requirements, such as being able to add a word to the list of favourites. These are instrumented tests that run on the emulator, and it was these tests that caused me so much pain in 2014. The new implementations use JUnit annotations, just like the Activity unit tests.

The instrumented unit tests and the integration tests are written using the “UI Proxy” design pattern.

UI Proxies

In any kind of user-interface testing, it’s a great idea to write test proxies for the main components. These so-called UI Proxies (they are called “Page Objects” in web programming) have methods corresponding to the main user interactions and methods for checking the state of the component. If the methods are well named, the tests become largely self-documenting. For example, here is the integration test that checks that a user can add a word to the Favourites list:

ui.checkCurrentWordIs("you");
ui.addToOrRemoveFromFavourites();
ui.showDefinitionOfCurrentWord();
ui.checkTranslationIs("anda");
ui.activateNextButton();
ui.checkCurrentWordIs("what");
showFavourites();
ui.checkCurrentWordIs("you");
ui.checkTranslationIsEmpty();
ui.showDefinitionOfCurrentWord();
ui.checkTranslationIs("anda");

In this code, ui is a test proxy for WordListDisplay, the main Activity.

UI Proxies are great because they:

  • make tests easy to read,
  • can be used for both unit and function tests, and
  • provide an abstraction layer so that if the implementation changes, the tests can be re-used.

The last point was particularly helpful in refactoring the tests to use the Espresso framework.

Testing with Espresso

The Espresso framework specifically addresses the problems of test flakiness and slowness that previously made Android testing a bit painful. Espresso uses matchers to find components, then performs actions on them or makes assertions about their state. This is all done without actually keeping object references to the components, which avoids the cleanup and concurrency problems that cause test flakiness.

Here’s an Espresso assertion that the text field with id id() contains the text expected:

onView(withId(id())).check(matches(withText(expected)));

And here’s Espresso code for clicking the item in a list that contains the text itemText:

onData(new ContainsMatcher(itemText)).perform(click());

The Espresso documentation includes a cheat sheet that gives a good overview of what is possible, and of course Stack Overflow provides a lot of great examples.

The IndoFlash UI Proxies are wrappers for low-level Espresso code like the samples shown above. As an example, the call

ui.checkCurrentWordIs("you");

translates to:

onView(withId(R.id.word_view)).check(matches(withText("anda")));

There is a proxy for each of the Activitys, which in turn use proxies for common UI components such as buttons and lists.

The unit tests for the Activitys use the corresponding proxy classes. Here is a unit test that checks the initial state of WordListDisplay:

@Test
public void testOnCreate() {
WordListDisplayProxy wld = new WordListDisplayProxy();
wld.checkWordViewIsShowing();
wld.checkCurrentWordIs("you");
wld.checkTranslationIsEmpty();
wld.checkFavouritesButtonIsShowing();
wld.checkNextButtonIsShowing();
wld.checkWordListsButtonIsShowing();
}

The original 20-odd integration tests have been preserved, with just a bit of re-factoring. These run in about 50 seconds, which is a huge improvement over the 7 minutes previously required. Admittedly, this is partly due my having a faster computer now, but all of the pauses and other flake-avoidance code has gone.

Summary

Android programming in Java has improved over recent years by the introduction of the Espresso framework. By wrapping the Espresso code in UI Proxy classes, we can have expressive tests that are quick to run and reliable.

Bagus sekali!

Resources

You can get the code for the Java implementation of IndoFlash from GitHub. Or you may just want to download the app and learn a bit of Indonesian.