Abacus Updates

Taking Ownership of the Abacus Android App

Abacus-Andoid-App

When I was hired at Abacus, my job was to take ownership of the Android application. I was thrown in the metaphorical deep end, tasked with some pretty large and exciting changes to how the application would work. We wanted to completely makeover the compose expense workflow, as well as implement the ability for admins and managers to approve expenses on Android.

This post covers my journey of taking ownership of the Abacus Android app, and hopefully helps other Android developers in similar situations.

Setting the stage: the state of the Abacus Android app

The app was originally written by Josh, our CTO, and extended over time by other engineers at Abacus. Considering that none of the engineers specialized in developing for Android, the app wasn’t in bad shape. I went through the app code, and noted a few good things and a few things that I wanted to improve.

What I liked:

  • It had a very flat package directory, which made it easy to navigate.
  • It used 1:1 Activity-Fragment pairs everywhere.
  • It used Volley to do network requests, which is better than HttpURLConnection.

Things I wanted to change:

  • No Entity/Model layer
  • No tests
  • No JSON Object mapper, only JSONObject and JSONArray

After talking with Josh and the other engineers who had worked on it, familiarizing myself with Abacus, and reviewing the app, I decided to jump head first into the code.

Make a model/entity layer

Wanting to learn more about the capabilities of the Abacus app, the first thing I looked for was a model or entity layer, but there weren’t any to be found. As I sifted through the code, I saw that the app mainly used JSONObject and JSONArray from the org.json[2] library (the one that comes default with Android) directly, as opposed to mapping them to POJOs [0]. While org.json has it’s uses, I prefer solutions like Gson[1] or Jackson [2], which allow you to map JSON to classes.

As an example, let’s say you had a JSON structure describing an entity that looks like this:

In order to read the “is_remote” key in the “metadata” object using org.json, you’d have to write something like

While it’s not completely terrible, it has some flaws:

  • The JSON keys appear as strings everywhere.
  • If the JSON structure changes, it’s hard to know where in the code base the keys are used (not checked at compile-time)
  • Checked exceptions, which leads to a lot of boilerplate try-catch blocks.

On the other hand, using a JSON-Object mapper like Gson or Jackson helps with some of these flaws. If you write a POJO and directly map the JSON string to the class, it means you get compile time checking everywhere that entity is used.

That would look something like this:

(If you’re curious about these libraries, I posted the links below)

With the Abacus application, I began by writing an entity class for each object the API returns, digging through the Abacus Node.js backend source code to understand what the different fields meant and how they related to each other. After that, I included Gson, configured it for our needs (Gson has tons of awesome configuration options), and integrated it with Volley, the HTTP request library the app used. As I went about this process, I realized that if I planned to make a lot of changes across the app’s code, I couldn’t confidently say that I didn’t break anything without tests.

Testing 1, 2, 3

Before I got too deep into making changes throughout the app, I decided the safest way to confirm I don’t break anything was to build some tests. There are a lot of resources when it comes to testing android(articles, github repositories, etc) but the gist is: it’s hard. There is no silver bullet, but you should do it even if it’s not perfect. It helps prevent you from breaking features when you want to refactor the code, and gives you more confidence than coding blind. Before I changed anything, I decided to start with an End-to-End test, and try to write smaller unit tests whenever possible.

It sounds self-evident, but it’s difficult to write unit tests for an Android app if the code wasn’t written with unit testing in mind. Specifically, unit testing Android activities and fragments is possible, but unless the app was written with strong separation of concerns, it’s daunting to jump into unit tests, even with a framework like Robolectric. End-to-End tests, on the other hand, can be written (mostly) without changing any of the app code. This meant that once I started making changes across the code base, I could be reasonably certain that those changes didn’t break core user flows across the app.

I used Espresso [5] for my end-to-end tests. I decided to go with an approach where the app communicates with the server that runs locally, as opposed to an approach which involves mock responses from a fake server or stubbing. The benefit is that there are drastically fewer differences between the testing environment and the production environment. The app talks to a server, makes network requests, and serializes and deserializes requests and responses like it would in the production environment. The test runs through the full stack of the app, as opposed to orchestrating different layers of the app. The cost of this approach is that it greatly increases the dependencies that the tests need to run, and makes it more difficult to get started running tests. You need to have an up-to-date copy of the API running locally and seeded with some test data. If your test

There are always tradeoffs with tests, but I choose to because it gives me more peace of mind.

Mindfully refactor as you go

With tests in place and a well built entity layer, I began implementing my first feature at Abacus! As I began implementation of the feature, I started running into blocks of code which were using the old style JSONObject. I really wanted to refactor those blocks to start using the entity classes that I had added to the code base, as well as start to incorporate more of the libraries that I included. While these blocks of refactoring ultimately improved the state of the code, I found myself much less productive during these stretches. The context switches between implementing the feature and reworking the code to use entities and the new libraries were extremely draining.

I found that depth-first refactoring was not sustainable. It was a rabbit hole and exhausting. I decided to change my process and instead of refactoring as I touched blocks of code, I started by implementing the feature using the original conventions of the app. I would then commit that code with a test to make sure that it was working properly. Now that I had the test in place, I could refactor the code I just committed, and once the refactor was done I could be sure that I hadn’t broken anything. Although this iterative process required self-constraint, I was much happier when I stopped myself from refactoring the world and wrote down the next steps I wanted to take instead.

Putting your own spin on things

In the first few weeks, I was hesitant to change the app too much. I didn’t want to add more complexity to the app without fully understanding how it was architected or why it was built that way. I was also new to Abacus and was navigating our engineering process, and wanted to maintain the Abacus culture as I started touching the code. But once I became comfortable with the app code, I decided to start switching to the tools that I’m more comfortable with which are well vetted by the Android community. Things became easier to change and test once I started using tools like Dagger[6], Retrofit[7], Gson, and others.

Taking ownership means owning and deciding what is best for the app’s future. You can’t do that successfully without thinking about the architecture and managing legacy code. I read several articles and posts about the different architectures which are becoming popular on Android and I strongly recommend checking out the recent trends for yourself. While it might not be the best idea to migrate the entire app to the CLEAN[8][9] architecture, understanding it may help inform you on how best to proceed with implementing new features.

I hope my experiences help you figure out where to start when taking ownership of an Android app. Feel free to drop me a message at prashanth@abacus.com if you have any questions!

[0] https://en.wikipedia.org/wiki/Plain_Old_Java_Object

[1] Gson: https://github.com/google/gson

[2] Jackson: https://github.com/FasterXML/jackson

[3] org.JSON: http://www.json.org/java/

[4] Volley: http://developer.android.com/intl/es/training/volley/index.html

[5] Espresso: http://developer.android.com/intl/es/training/testing/ui-testing/espresso-testing.html

[6] Dagger 2: https://github.com/google/dagger

[7] Retrofit: http://square.github.io/retrofit/

[8] CLEAN Architecture: https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

[9] Android CLEAN Architecture: http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/

[10] Android Weekly: http://androidweekly.net/

Related Posts