Android TDD Series: The Challenges
Six months ago I embarked on a journey to write my first Android app. I'm still on that journey, and though I haven't released yet (soon!) I've learned a number of hard lessons that I'd like to share with those who are on the same path in the hopes that it will make their own journey a little easier.
Before I get to those lessons though I want to talk about some of the challenges I faced when I first approached Android development. First among them is the fact that...
Android was written by people who were attempting to anticipate the varied needs of future app developers, and it's quite obvious that some needs were prioritized over others. It's also quite obvious that these people (naturally) brought their own biases and schools of thought into the project. As such, things like testable, maintainable designs and the application of well-understood principles such as Dependency Injection took a backseat to performance.
Reading the Android documentation on managing your memory usage is like reading a diatribe against every good practice you've ever used. Thinking of decomposing your code into smaller, more focused, more maintainable classes? Don't do that, think of the overhead! Want to pull in a known library that meets your needs? Heavens no, who knows what that library is doing. You should just write your own! Google even goes so far as to suggest in their documentation that you shouldn't design apps to use the aspects of the framework they themselves provided. All of these things we do to make our code easier to maintain and test (much less useful!) are flat-out discouraged in the official docs and maddeningly apparent in the implementation.
Admittedly these are some easy potshots to take at Google's expense, and they certainly bring up some interesting data points in regards to their design recommendations. However, I'm of the opinion that the state of the art for Android has advanced far beyond the point that these recommendations make any sense any more. This is true in just about every respect; the hardware has improved in leaps and bounds in the space of just a few short years, the ecosystem around Android has grown tremendously, the tooling has been steadily improving, and a thriving community provides an endless number of useful libraries.
Yes, we should eat our vegetables, brush our teeth, and keep performance in mind, but this in no way means we should simply roll over and forget all of the lessons we learned outside of mobile arena. Gone are the days in which it was acceptable to write monster methods simply to save a few bytes instead of writing new classes and methods. Gone are the days in which it was acceptable to new-up every dependency inline and call it good enough. These days there is simply no excuse to not apply the same level of craftsmanship to Android applications as we would to any other project, which leads me to my next point...
These are just a few of the challenges I've encountered while developing my first Android app, and I've outlined them here to give other developers an idea of what to expect when they enter this arena. Over the course of the next several months I'll be writing an Android TDD Series, in which I'll try to provide concrete examples of how to test-drive various pieces of functionality in an example app. I hope you'll join me here, and I look forward to your feedback. Thanks and happy app dev'ing!
Before I get to those lessons though I want to talk about some of the challenges I faced when I first approached Android development. First among them is the fact that...
You're Developing Against A Moving Target
Mobile is hot right now, and Android is constantly being updated to keep pace. When I first started working on the my app KitKat was the latest and greatest. Lollipop was released a month later, and at the time of this writing it already has close to 10% of the Android market share. The build tools and support libraries are also constantly being updated, as well as the Gradle plugin and the most popular Android IDE, Android Studio. As such, the API and your development environment can shift quickly, and library developers have to scramble to keep up. Information that was accurate a couple of months ago can now be completely out of date. Libraries that were compatible last week may suddenly stop working. It's a fast-paced environment, and if you're going to be an Android developer you should be prepared to keep up. This means following the latest Android news, keeping an eye on third-party tools, and being ready to update on a moment's notice.
As if hitting a moving target wasn't hard enough....
As if hitting a moving target wasn't hard enough....
The Framework Is Not Designed For Maintainability
Android was written by people who were attempting to anticipate the varied needs of future app developers, and it's quite obvious that some needs were prioritized over others. It's also quite obvious that these people (naturally) brought their own biases and schools of thought into the project. As such, things like testable, maintainable designs and the application of well-understood principles such as Dependency Injection took a backseat to performance.
Reading the Android documentation on managing your memory usage is like reading a diatribe against every good practice you've ever used. Thinking of decomposing your code into smaller, more focused, more maintainable classes? Don't do that, think of the overhead! Want to pull in a known library that meets your needs? Heavens no, who knows what that library is doing. You should just write your own! Google even goes so far as to suggest in their documentation that you shouldn't design apps to use the aspects of the framework they themselves provided. All of these things we do to make our code easier to maintain and test (much less useful!) are flat-out discouraged in the official docs and maddeningly apparent in the implementation.
Admittedly these are some easy potshots to take at Google's expense, and they certainly bring up some interesting data points in regards to their design recommendations. However, I'm of the opinion that the state of the art for Android has advanced far beyond the point that these recommendations make any sense any more. This is true in just about every respect; the hardware has improved in leaps and bounds in the space of just a few short years, the ecosystem around Android has grown tremendously, the tooling has been steadily improving, and a thriving community provides an endless number of useful libraries.
Yes, we should eat our vegetables, brush our teeth, and keep performance in mind, but this in no way means we should simply roll over and forget all of the lessons we learned outside of mobile arena. Gone are the days in which it was acceptable to write monster methods simply to save a few bytes instead of writing new classes and methods. Gone are the days in which it was acceptable to new-up every dependency inline and call it good enough. These days there is simply no excuse to not apply the same level of craftsmanship to Android applications as we would to any other project, which leads me to my next point...
TDD Is Hard (But Not Impossible)
There's no two ways about it, Test-Driven Development in Android is daunting. Coupling the issues with maintainability I mentioned above with the fact that out of the box there are little to no testing tools available makes getting started with TDD a difficult proposition. Luckily, the Android community has risen to this challenge, and Google has responded to the need for test support with updates to the build tools and other improvements. Testing tools such as Robolectric used in conjunction with dependency injection frameworks such as Dagger and a healthy dose of mocks (via Mockito or similar) go a long way towards making testing a much less frustrating experience. Functional testing is now simplified as well, as frameworks such as Espresso and Robotium provide easier and more reliable UI interactions than the standard ActivityInstrumentationTestCase2 style tests.
Even with these tools though testing is still not easy if you're not careful to design your app to be testable. It is all too easy to simply succumb to the framework if you don't take the time to become intimately familiar with its ins and outs, and to learn when you can and should ignore a design recommendation. Inline, anonymous listener classes? As Dikembe Mutombo would say, "Not in my house!" Business logic embedded in a framework class? Nope! It takes diligence and a critical eye for every design decision if you're going to test-drive your code in Android. Be ready to go off the beaten path. By the way....
If the Path Doesn't Exist, You Might Have to Build It
As I said earlier, Android is a moving target, and library developers have to scramble to keep up. Often the pace of advancement is quick enough that the support you're looking for might simply not be there yet. I've already had to contribute to more than one project in order to remove roadblocks for development, and chances are good that if you're reading this as an aspiring Android developer you might have to as well. If you're using Robolectric be ready to extend Shadow classes when it becomes necessary. Also, don't be afraid of cloning a project and pulling down the source to research a bug in your third party libraries, or of figuring out a fix to contribute back.
These are just a few of the challenges I've encountered while developing my first Android app, and I've outlined them here to give other developers an idea of what to expect when they enter this arena. Over the course of the next several months I'll be writing an Android TDD Series, in which I'll try to provide concrete examples of how to test-drive various pieces of functionality in an example app. I hope you'll join me here, and I look forward to your feedback. Thanks and happy app dev'ing!