The Rise Of Android - How Android Became A Modern Software Platform
The Android ecosystem has improved tremendously over the last few years. Major components have been modernized, setting a high bar for other software platforms. Those improvements span all over the language, libraries, programming paradigms, tooling and app architecture. This article gives an overview about many recent improvements. We will see that major problems of Android are non-technical.
Warning: This article is about improvements for “native Android development”. However, I do not recommend native Android development anymore. Instead, I recommend to use cross-platform frameworks like https://capacitorjs.com/ or https://flutter.dev/.
A Modern Language
A few years ago, Java was the primary language of Android development. Although Java worked well, writing Java felt somewhat clumsy and bloated. With Kotlin, we now have a much more expressive and feature-rich language. There is no single feature that sets Kotlin ahead of Java. Instead, it is the combination of many improvements and features. Nevertheless, if I had to choose my favorite Kotlin feature, I would opt for the type inference and smart casting capabilities.
Coroutines
Coroutines enable asynchronous programming in a very clean and sequential fashion. They make asynchronous code look like sequential code, without having to use any special chaining-operators. Thereby, coroutines are a great addition to the Kotlin ecosystem. Although this concept has been around for a long time, it is a new concept for Android and for the JVM. Coroutines can be used for either single-threaded async code, or for parallel code. Single-threaded async is the safer option since we do not need to worry about race conditions between multiple threads. In the single-threaded async case, we typically replace traditional callbacks with coroutines and let the coroutines run on the UI thread. In the parallel case, we typically replace threads or asynctasks with coroutines and let the coroutines run on a pool of worker threads. Those two cases can be easily combined: For example, we can offload parts of a coroutine to a worker thread but then continue the coroutine on the UI thread once the heavy-weight work is done. Coroutines can be easily added to an existing code base, enabling a gradual transition to coroutines. In contrast to threads, coroutines are not only more lightweight, but also easier to manage and cleanup.
Better IDE Integration
A few years ago, Eclipse was the primary IDE for Android. Although Eclipse worked reasonably well, it was not as neatly integrated as Android Studio. Using Android Studio, it is possible to get productive very quickly and take advantage of many JetBrains productivity enhancements.
Guide to App Architecture
Several great libraries have been introduced throughout the last years. Building on those libraries, Google released a guide to app architecture. Naturally, you do not need to follow this guide since there exist a myriad of different app architectures. Nevertheless, this guide is a great architecture for CRUD applications with a backend data sync and a local persistence layer. We will discuss a few of those libraries in more detail.
SQL Made Better
Since many years, SQLite databases are commonly used for persisting local app data. More recently, SQLite has been improved greatly by the Room library. Now you might say that Room is just yet another object relational mapping library. However, the advanced safety checks of Room are a huge benefit that we do not get with raw SQL statements. Room checks many things at compile time, saving us from run-time failures of SQL statements. Another crucial feature of Room are auto-generated migration tests. With raw SQLite, doing database migrations in production felt like juggling with chain saws. Note that a broken database migration often leads to an immediate app crash, and there is no immediate way to revoke a broken Android app update. With Room, we can auto-generate tests to assert that the scheme after a migration matches exactly with a scheme that is specified in a new version of the database.
Test Driven Development
A few years ago, UI testing was not very common among Android developers. There have been testing frameworks like Appium, but those framework suffered from limitations because of their black-box design. In my view, the right approach for UI tests is not black-box testing but gray-box testing. Although we want UI tests to be as black-boxy as possible, there are situations where we want to dig deep into implementation details of the app under test. This need for gray-box testing has been acknowledged by the rise of the Espresso testing library. Compared to frameworks like Appium or XCUITest, a key differentiator of Espresso is that it runs inside the very same app that is tested. This means that Espresso tests can jump into arbitrary app code and perform arbitrary state modifications on the app under test. This gives us the flexibility to do literally everything with UI tests. If we are missing a specific Espresso function, then we can just implement it within a short time. Moreover, we can choose to either extend Espresso APIs or modify the state of the app under test without caring about Espresso APIs. These capabilities enable effective test driven development for Android.
Test Orchestrator
Test Orchestrator is an infrastructure library that sits on a lower level than Espresso. Test Orchestrator is very critical for the success of Android UI tests. Without something like Test Orchestrator, the outcome of previous tests influences the outcome of subsequent tests. For example, a single failing test can produce a failure of multiple subsequent tests. This situation is highly undesired because it makes a test suite more fragile. In the extreme case, a test suite might become so fragile that the developers start to ignore or abandon parts of the test suite. Test Orchestrator mitigates that problem by clearing the state of the app between test runs.
Painful Activity Lifecycle
Android’s activity lifecycle is notoriously cumbersome for developers. A few years ago, a common recommendation was to serialize intermediate UI state into a “bundle” that survives activity re-creations. This practice led to quite a lot of boilerplate code and additional sources for errors. Today, this situation is still not ideal, but it became better with the introduction of ViewModels and lifecycle-aware components.
The Negative: Random Play Store Bans
Major issues with Android are not technical but rather political and legal. Building for the Google Play Store is like building on sand or walking on thin ice. Specifically, there are automated bots that ban Google developer accounts for lifetime without any chance of recovery. Filing appeals against such lifetime bans is useless since those appeals will be answered by automated rejection mails. Communication with Google is non-existing unless you have special contacts or a huge public outreach. The reasons for such bans are various: One can be banned for intentional or unintentional policy violations. Moreover, one can be banned because some personal contact or fellow developer got banned. If a banned developer sends you a Gmail, then you might get banned too. This practice is known as “associated accounts ban”. Regardless of whether there exists a conceivable reason, Google won’t tell you any reason for a lifetime ban. Several case reports can be found [1]. Clearly, those bans are a high risk for every developer or business that is invested in the Google Play Store. There exist only limited strategies to reduce this risk: If your customers do not demand an Android app, then I suggest to develop a pure web application. If you are able to use an alternative distribution channel instead of Google Play, then I suggest to use this distribution channel. Finally, we can hope for stronger antitrust laws to counteract this kind of monopolistic behavior.
Disclaimer: In my work environment, nobody got banned from Google Play. However, the Google AdMob account of a fellow developer was terminated for no conceivable reason whatsoever. The procedure of AdMob bans is very similar to Google Play bans (i.e. irrevocable, only automated responses, no reasons or justifications provided).
[1] https://medium.com/@tokata/how-google-play-terminated-a-developer-for-no-reason-e4d760e9f472