Android is one of the most challenging niches in high-level software development. Yet, many developers and managers miss this fact, which can lead to underestimation of projects’ scope and timelines. Therefore, in this article, I’ll list the main sources of Android’s complexity, so you could account for them.
The order of the sections corresponds to the contribution of each factor to the overall complexity, starting with the most prominent ones. I draw on my subjective experience here, so your mileage might vary.
Multiplicity of Complex Lifecycles
Backend devs have it very simple: the framework spins up a “handler” when new request arrives, the handler processes the request and generates the response, the framework sends the response back and then destroys the handler. There are various variations on this theme, but, overall, that’s the lifecycle that backend devs deal with most of the time. There can also be components that outlive individual requests, but they are also very straightforward to implement and manage.
Frontend devs need to deal with more complex lifecycles than backend devs. React components, for example, deal with construction, mounting, rendering, updating and can also have “effects”. Orchestrating these lifecycles is a real challenge.
Now enter Android, with its many components that have unique lifecycles: Activity, Fragment, ViewModel, Service, BroadcastReceiver, etc. Some of these lifecycles are unbelievably complex. For example, here is a diagram of the Activity and Fragment lifecycles. Take a look, it’s hilarious, and keep in mind that this diagram is still missing some methods and doesn’t account for the differences between different Android versions. On top of that, some lifecycles are inter-related, thus making Android devs even more miserable.
All in all, developing Android applications requires a deep understanding of many complex lifecycles and their inter-dependencies. Android devs get a “Lifecycles Stockholm Syndrome” at some point and take this complexity for granted, but if non-Android dev starts learning Android, they are usually very surprised by all that. I mean, what other framework would call for an 8-hours course just about lifecycles?
Inconvenient Distribution Channels
When there is a bug in a backend code, it can be fixed very quickly. Developers will implement the fix, testers will verify it, and a new version can be deployed immediately. That newly deployed version can then sit idly behind a load balancer until the switch is flipped on, at which point all new requests will be routed to it. If it’s the new version that introduced the bug, then the switch can be flipped back and the old version will resume processing the requests until the problem is fixed.
A similar approach can work with web frontends. In fact, it’s even simpler with frontends because they usually don’t store any persistent state that can get corrupted.
When there is a bug in an Android application, you don’t have any rollback mechanism. The only way to resolve the problem is to deploy a new version to users’ devices. The problem is that, unlike with the backend and frontend servers, you don’t have any control over these devices (except for remotely managed devices). So, the best you can do is to put a new version out there and hope that the affected users will update their apps.
Unfortunately, even “putting a new version out there” isn’t as simple as it might sound in some cases. If your app is distributed through Google Play, then you’ll face additional delays. Google Play deserves a section on its own, though, so we’ll discuss it later.
Google Play is the most popular distribution channel for Android apps. It’s pretty much the only option for consumer-oriented products. Dealing with Google Play can be tricky, however.
The first complication is the sheer amount of time it can take for them to approve and roll out an app update. These delays become especially painful when you need to fix a critical bug in the app.
Another problem is that they have their standards and requirements, some of which are kept in secret, while others can be unclear. So, if you receive a warning from Google Play, you (or your company) won’t always know exactly what’s the problem. Your communication is very likely to be answered by bots, and good luck trying to reach a human being at Google to clarify the situation. This state of affairs is especially unfortunate for indie and small developers who depend on Google Play for their entire income.
Fragmentation of Devices and Operating System Versions
Backend devs are free to choose their platform and operating system. Modern cloud providers let them control these parameters when they spin up servers. Furthermore, a tool like Docker can abstract out these details completely.
Frontend developers have to support multiple browsers and browser versions, so they deal with compatibility issues. However, in practice, their compatibility matrix isn’t huge, especially in light of the fact that most mainstream browsers use Chromium under the hood.
Android ecosystem suffers from a bad compatibility problem. There are many different Android devices out there, running different versions of Android OS. So, pretty much all mature applications are riddled with compatibility code. Furthermore, since OEMs can modify many aspects of Android before they flush it onto their devices, what works on one device isn’t guaranteed to work on another. The problems can range from minor stuff like incorrect representation of colors, to major issues like undocumented aspects of power management and mysterious crashes.
We call Android’s compatibility problem “fragmentation”, and it’s a major source of pain for many developers.
CPU, Memory and Battery Optimizations
As I mentioned previously, there are many different Android devices out there. Some of them are lower-end, budget devices, while others can be just very old. Therefore, Android apps can execute in a very resource-constrained environments. Furthermore, in addition to powering mobile phones and tablets, Android can be found in many TVs, set top TV boxes, payment terminals, and other types of special devices that usually have lower specs.
Some Android projects have the luxury of targeting just the richer countries, or specific types of high-end devices. However, in general, Android applications require much better optimizations than even their iOS counterparts. And, of course, unlike backend or frontend servers, you can’t just add resources to your application by visiting you cloud provider’s website.
Most backend developers don’t deal with internet connectivity loss. If the server becomes unavailable, there isn’t much they can do about it from within the application. The situation can be trickier if they can’t reach another server that their app depends on, but, even then, returning an error response to the client will usually suffice.
Frontend devs don’t have to deal with connectivity loss either. Users are pretty accustomed to seeing the generic browser’s error page in that case.
Android developers must think about connectivity loss all the time. No internet connection is not an exceptional condition on mobile devices, but an expected state. Therefore, Android application must handle connectivity loss gracefully. This aspect often leads to many bugs during development and in production, and proper handling of this state can introduce a surprising amount of complexity into the source code.
Offline work is when the application remains functional even if there is no internet connection. Well, assuming that the app requires the internet to begin with, of course.
Most applications don’t support offline work. However, there are many categories where users simply expect the app to work even when offline. Imagine, for example, that your favorite messenger would drop messages just because you’re not connected to the internet. That would be outrageous, right?
Offline work is a very challenging feature to implement. Even the simplest of cases, like backing up your notes to the cloud when internet is available, can introduce a surprising amount of complexity into the project. More complex cases, like supporting offline collaboration (which requires conflicts resolution), are very hard to get right.
You can get surprisingly far in backend development without dealing with manual concurrency. Some frameworks automatically invoke individual request handlers on standalone threads, so you can just execute all the required logic synchronously. Other frameworks, like the popular Node.js, abstract out concurrency almost completely and promote “single threaded” paradigm.
Frontend developers also don’t need manual concurrency much. They use promises (or async/await, which is a wrapper around promises).
Unfortunately, you can’t write any non-trivial Android app without dealing with concurrency. The moment you’ll want to send your first network request, you’ll need a background thread. Sure, you can use a higher-level concurrency framework like Coroutines for that, but, arguably, the learning curve of these frameworks will be even steeper than using a bare Thread class.
Inferior Development and Debug Tools
There are web frontend dev tools built into all major browsers: built-in network traffic inspector, storage inspector, style editor, etc. My favorite tool is UI inspector, which allows you to click on any UI element and see how it’s declared, what properties it has and the hierarchy of CSS applied to it. These features make fixing UI bugs a breeze.
These tools are beyond anything Android developers can dream about. For example, when debugging UI issues, Android devs have to build a debuggable version of the app and attach LayoutInspector to it. It takes a lot of time, and even then LayoutInspector is nothing compared to the web dev UI inspector that I described earlier. Now we have Jetpack Compose, which boasts a preview feature, but the preview is very slow, often fails and is nowhere close to the web dev UI inspector in terms of the features.
On top of these inconveniences, network traffic inspection in Android is harder, automated testing is more challenging, visibility into production code is minimal and more.
In part, this discrepancy in tooling can probably be attributed to the difference in programming languages, platforms, artifacts and distribution model. Still, case in point, Android tooling is inferior compared to web frontend. We probably can’t directly compare Android tooling to backend tooling, because backends don’t have UI and backend artifacts don’t leave your own servers, but I’ll still claim that backend tooling is more convenient and more mature.
Google Dev Ecosystem Lock In
Android developers “live” in Google’s dev ecosystem. There are pros and cons to that, but I find the overall balance to be negative.
The positive is that we get pretty much all of our tools from a single authority. There is the current “best practice” and that’s what you should use.
Unfortunately, that’s also the problem because you basically have to take what Google provides. The “best practices” change quickly and, often, for hardly justifiable reasons. Furthermore, Google’s standards of quality and maturity are pretty low in the context of Android dev experience. So, after each reinvention of the wheel, there is a period of instability. And don’t get me started about Google’s official issue tracker – I stopped submitting tickets to it because it’s just a black hole that sucks a lot of community effort for very little output.
As an Android dev, I constantly chase new trends. I’d prefer to spare time and just stick to the old, good, mature and time tested tools, but, given Google’s influence on the ecosystem, it’s impossible. Sooner or later, they push the latest wheel reinvention into the masses, which forces me to learn and adapt to it.
The combination of fundamental challenges, like dealing with connectivity loss and distribution mechanics, and accidental challenges, like going through cycles of Google’s reinvent the wheel -> deprecate -> reinvent the wheel, makes Android development very challenging. On the positive side, after dealing with these challenges, Android developers are well-prepared to handle any other development tasks because they are tough as boots.
In all seriousness, I like being an Android developer. It’s a very broad niche, with many different types of systems, that encompasses the largest userbase on the planet. It doesn’t get boring, for sure.
As usual, thank you for reading and you can leave your comments and questions below. Let me know if I missed any additional factors.