The ecosystem of native Android development is very dynamic. Well, at least I experienced it as such for the past five years, while I’ve been heavily involved in all things Android. During this period, Google released new sets of recommendations, libraries and frameworks every 2-3 years, and I invested much time to review these changes and sort out the sheep from the goats. I’m sure that many Android developers can relate to this experience.
However, this past year has been absolutely crazy even by Android ecosystem standards. Stuff got added, stuff got deprecated or removed, docs changed, new official guidelines were introduced, etc. When I come to think about it, I can’t draw a complete, detailed picture of Android development landscape in my mind anymore.
Therefore, I decided to set some time aside for a research and then write this post. That’s my attempt to summarize what’s going on in Android ecosystem and make some predictions about the future of native development. I will structure my thoughts into sections dedicated to different topics, in no particular order, but I’ll try to keep the most controversial opinions for the end.
I hope that this article will be interesting and useful to you, but keep in mind that I surely missed many important points and all my personal biases went into it.
It’s kind of crazy to think about it, but AndroidX preview was announced just one and a half years ago. It became stable about a year ago, and at that point Google also stopped further development of the legacy support libraries. [the moment I wrote this sentence I recalled that I asked this question about the motivation behind support libraries on StackOverflow back when I was totally “green” Android developer]
The usage of the term “stable” to describe AndroidX is a bit ironic, though, because nothing about this set of artifacts is stable. Google constantly adds new libraries and frameworks under
androidx. namespace, and many “old” APIs (which are barely one year old at this point) evolve at a very fast pace.
To this day I migrated two applications to AndroidX. It went alright and I don’t remember many surprises. Jetifier, the tool that redirects transitive dependencies on support libraries to their AndroidX counterparts, also worked surprisingly well. However, even though the applications weren’t big, it wasn’t a “one-click migration” either.
I’m also involved in one project which hasn’t migrated to AndroidX yet (and it’s not even on the roadmap). No problems there. Looks like this is also a viable course of actions in some cases.
All in all, I’d say that new Android apps should definitely use AndroidX artifacts. I’d also recommend to plan for migration to AndroidX in existing project, even if you can’t see clear benefits right now. You’ll most probably need to migrate at some point anyway, so it’s better to do it on your own terms, instead of migrating in a rush when you’ll need some new AndroidX library in six months.
After discussing AndroidX I must mention Jetpack, of course. As far as I remember, Jetpack started as an umbrella for “architecture components”, but expanded to incorporate most (or even all) APIs from AndroidX. Therefore, as of today, I don’t see any meaningful distinction between AndroidX and Jetpack. Except for, surprisingly, marketing and PR.
When you look at Jetpack’s website, it does’t look like a resource for technical documentation. It looks like a landing page of an early-stage SaaS startup.
Take for example these “testimonials”:
Or this “trusted by” section:
Given such a focus on marketing and PR, I won’t be surprised if Jetpack files for a standalone IPO in 2020.
Seriously, though, there is something deeply concerning about this attempt to “sell” APIs to developers in your own ecosystem. Like, why the hell would anyone want to actually advertise ViewModel in search?!
All in all, since Jetpack just aggregates stuff from AndroidX, what I wrote earlier about AndroidX is applicable to Jetpack in large part as well. Below I’ll also discuss some of these APIs individually.
Perfoming work when the application is not in the foreground has been one of the most dynamic use cases in Android. You could leverage regular “started” Services (as opposed to “bound” Services) for that before the introduction of doze, SyncAdapter, GCMNetworkManager, FirebaseJobDispatcher, JobScheduler and, lately, WorkManager. These are just Google’s own APIs that I can recall from the top of my head. There were also a bunch of third-party solutions like Android-Job.
However, Google announced recently that they are going to “unify background task scheduling” around WorkManager API. This sounds great, but, for some reason, I can’t get rid of deja vu feeling when I hear that…
Anyways, whether unified or not, WorkManager doesn’t solve the most serious issue with background work: reliability. I won’t go into explanations here, but remember that if you ever need to implement background work in your app, start by reading all the information on dontkillmyapp.com. In addition, read and star this issue in Google’s issue tracker.
TL; DR; background work story in Android is a mess and the fragmentation makes it very nuanced and unreliable.
In the past, I’ve been advocating for background syncs of data and other types of background work whenever possible. I probably was one of the last fans of SyncAdapter. However, today, given the reliability issues, I advocate for the opposite: avoid background work whenever possible. If your PM insists on this feature, show them the above links. Explain them that background work can take hundreds of hours of effort to implement, and still bring more troubles than benefits.
Sometimes background work requirement will be unavoidable, but in most cases you can do without it. It might be the optimal choice, even if at the cost of some inconvenience to users.
There are no surprises in the world of SQLite ORMs – Room dominates the landscape. Starting with version 2.2.0, Room finally supports incremental annotation processing. Keep in mind, though, that your architecture shouldn’t care which kind of ORM you use. Therefore, when it comes to Room, “architecture component” is just a marketing term, not a technical role.
The main contender for the crown of ORMs in Android is SQLDelight. This library is much older than Room, but, as far as I understand, it had been pretty much rewritten during the past year or so. Unfortunately, it’s only targeting Kotlin now. On the other hand, SQLDelight supports Kotlin multiplatform. Therefore, as Kotlin adoption rates increase, I expect the adoption of SQLDelight to increase as well.
By the way, there are mirrors of bare SQLite consturcts in AndroidX namespace. I’m not sure what to make of it, but if you use bare SQLite in your apps, then maybe it’s worth exploring this topic.
In addition, let’s not forget non-SQL databases for Android like Realm, Parse, Firebase, ObjectBox and others (some of them still use SQLite under the hood). If I’m not mistaken, most of them (or even all of them) come with automatic data synchronization capabilities. There was a period of time when these solutions were relatively popular, but, as far as I can tell, they aren’t anymore. That said, I wouldn’t discount non-SQL databases with auto-syncs right away.
Last year I wrote very complex Android app that integrated with Parse Server. The app had full offline support, server-side localization, user-specified system and content languages, very complex multimedia and more. I used Parse SDK for Android and, Except for minor WTFs, the experience was surprisingly good. Maybe that’s not the best solution if your company already employs a bunch of backend folks, or you need to implement lots of server-side logic, but it might be a game changer for startups and individuals who perform just CRUD operations on backend.
Just one warning: if you’re going to take Database-as-a-Service solution (e.g. Firebase), make sure that you understand its long-term costs and implications.
Much more “interesting” development happened in the context of external storage.
If your app targets API 29 or above, the standard access to files on phone’s external storage won’t work anymore, with few notable exceptions. Instead, you’ll need to use SAF framework which (supposedly) allows users a more granular access management. Unfortunately, SAF works in completely different manner, so a major refactoring might be required in some apps.
Google wanted to roll this requirement out for all apps starting with Android 10, but it generated such an outcry from the community that they decided to postpone this feature. Therefore, you can still work in “legacy” mode even if your app targets API 29 by setting a special flag in your build files. However, it looks like scoped storage access will be enforced in the next version of Android for all apps, regardless of their target API level.
I haven’t worked with scoped storage until now, but from the many discussions I read on the internet, it looks like implementing it might be a challenging task. Therefore, if your app uses external storage in “legacy” mode and none of the exceptions apply to it, you better start refactoring and testing it right now.
Couple of weeks ago a new framework was added to AndroidX family. Its commit message says:
New library meant to replace SharedPreferences. The name is not final, this is just for implementation review and to support the design doc (feel free to request the design doc privately)[…]
It’s nothing to worry about right now, but looks like in the longer term SharedPreferences will be deprecated in favor of this new approach.
The main difference between SharedPreferences and this new framework is that the latter is asynchronous by default. In other words, instead of just getting the value of a specific key, you’ll need to implement a callback that will be notified with the value at some later time.
If you’re curious about the motivation behind this move to async notifications, you can read this StackOverflow answer. Reddit user Tolriq shared their numbers concerning the probability of hitting this bug here. In their specific app it affects
1 / 10,000 / SESSIONS_PER_USER_PER_MONTH fraction of user sessions. That’s maybe not that bad in general purpose applications, but might be a deal breaker when high reliability is required. For example, in cars with Android Auto, application hang and subsequent crash can distract the driver, which can lead to very unfortunate consequences.
In the land of dependency injection, the biggest news is the effective deprecation of Dagger-Android. This statement immediately requires two clarifications. First of all, I say “effective” deprecation as opposed to “formal” deprecation because it hasn’t been officially deprecated yet. Second, Dagger-Android is not the entire Dagger 2 framework, but just relatively recent addition to it. I wrote a very detailed artilce on this subject, so I won’t repeat myself here.
As for other dependency injection frameworks, I don’t see them as real contenders to Dagger. Koin, for example, might be nice, but I don’t believe that it’ll get much traction. In fact, I believe that it got a bit of initial adoption due to just two main reasons. The first one is Dagger’s awful documentation which makes it very difficult to approach. Koin is light years ahead of Dagger in this context. The second reason is the fact that Koin is written in Kotlin, so it enjoyed a bit of a ride on Kotlin’s hype waive. This wave is pretty much died by now, so the excitement around Koin will probably decrease going forward.
What I do think might happen, is a very slow appearance of Pure Dependency Injection (aka. manual dependnecy injection).
Now, Google claims that “manual dependency injection cost grows exponentially as your app gets bigger”. In my opinion, it only shows that they neither understand what “exponential” means, nor did they actually “measure” anything. This statement is plain wrong and I’d expect Google to not mislead the community in such a manner.
The truth is that Pure Dependnecy Injection is actually relatively common in backends (especially with microservices, where you don’t want to add dependency on the framework in each service) and it works alright. However, reflection is also a valid option in backends. Therefore, they don’t usually need to resolve to compile-time code generation if they want to use dependnecy injection frameworks.
The situation is different in Android, though. We can’t use reflective DI frameworks, so we use Dagger. Well, in fact we can use reflective frameworks and for most projects it’s alright, but “OMG performance”. Now, I’m not saying that using reflective frameworks is safe, but it’s definitely not black and white decision. In any case, Dagger is de-facto standard in Android and we all use it. However, despite the nice green graph for Dagger’s cost in the aforementioned Google’s presentation, its cost actually grows the fastest with time. There are three factors here: 1) the more code you have, the more time it takes for annotation processing to run during build 2) the more developers you have, the more builds they’ll execute and 3) all developers need to learn Dagger, which takes a lot of time.
In other words, while Dagger indeed allows you to write less code, it consumes much more effort on bigger projects due to its effect on build times and required education.
On bigger projects build times become real issue and constitute major productivity bottleneck. Therefore, despite the fact that Dagger indeed provides very nice set of features to make DI simpler (once you know how to use it, of course), I believe that we’ll see a growing interest in Pure Dependency Injection.
One of the main reasons developers adopt DataBidning is to eliminate
findViewById() calls. To be honest, I never had problems with these Views lookups, but they’re indeed just boilerplate and I wouldn’t mind to get rid of them. However, slight annoyances with
findViewById() calls did not justify DataBinding usage, in my opinion. The good news are that very soon we will be able to remove these calls without DataBinding using another new feature: ViewBinding.
In fact, I never believed in DataBinding. It felt as too complex solution for the problems it (supposedly) addressed. In addition, DataBinding allowed developers to put logic inside XML layouts. Experienced developers don’t use this approach because it sounds like a sure recipe for maintainability hell, but the sole fact that DataBinding opened this door is yet another drawback of this framework.
Back in November 2016, when DataBinding was at the top of its hype-waive, I made the following prediction in this StackOverflow answer:
However, there is one prediction I can make with a high degree of confidence: Usage of the Data Binding library will not become an industry standard. I’m confident to say that because the Data Binding library (in its current implementation) provides short-term productivity gains and some kind of architectural guideline, but it will make the code non-maintainable in the long run. Once long-term effects of this library will surface – it will be abandoned.
Now, I don’t have any numbers concerning DataBinding’s adoption, but it’s quite evident that it didn’t become industry standard. I’ve never seen a professional project that used it myself, and I rarely meet developers who use it in their applications. In my estimation, once ViewBinding will mature and become widely adopted, DataBinding will fall in popularity even more and become “legacy” framework.
Preserving State on Configuration Changes
Since the introduction of ViewModel “architecture component”, the story of configuration changes in Android apps became a shitshow. I know that it’s a harsh statement, but, in fact, it’s the most moderate expression I can think of to describe the situation.
Fortunately for me, Gabor Varadi (aka. Zhuinden) already summarized the first act of this shitshow in this post on Reddit, so I don’t need to do it myself. TL; DR;
onRetainCustomNonConfigurationInstance() was deprecated in favor of ViewModel. Twice.
Interestingly, at the end of that post, Gabor made some tongue-in-the-cheek predictions:
And you know what? Retained Fragments are getting deprecated now! Gabor, wellcome to “Cassandra club”.
In my opinion, deprecation of retained Fragments is actually a good idea. See, the only reason Fragments have
onDetach callbacks is to support this use case. With deprecation of retained Fragments, these methods can be deprecated too and Fragment’s lifecycle will be simplified. If you used my approach to Fragment’s lifecycle, this deprecation shouldn’t bother you at all because I long recommended to avoid retained Fragments and forget about
However, while there are some good reasons to deprecate retained Fragments, deprecation of
onRetainCustomNonConfigurationInstance() is bullshit. And this time it’s not my words, but Jake Wharton’s (you can read his opinions as the top comment under the aforementioned Gabor’s post on Reddit). I sometimes disagree with Jake, but this time I couldn’t say it any better myself. There is simply not a single reason to deprecate this method. Especially given the fact that ViewModel by itself uses the same mechanism under the hood.
What should we make of these deprecations? I can see only one explanation: Google is determined to force all Android projects to migrate to ViewModel, regardless of its technical merits. They are willing to deprecate all existing alternatives to achieve their goal, even if these alternatives are actually superior to ViewModel itself.
Sounds a bit conspiratory, right? I agree. But, fortunately, there is a simple test for this theory.
See, while I don’t like the deprecation of retain-non-config-state mechanism, it doesn’t affect me in any way because I don’t use it. In fact, absolute majority of applications don’t need it. And they don’t need ViewModel either. All you need to properly handle config changes is
onSaveInstanceState(Bundle) callback. It’s much simpler and better approach because it also handles save & restore flow (aka. process death). So, as long as I can save the state in this manner, I’m alright. And I’m not the only one in this boat. Despite Google’s heavy marketing and PR, many experienced developers realize that ViewModel is unneeded complication and there are better ways to both architect your app and preserve state on config changes.
Therefore, if Google indeed has ulterior motives and wants to force all projects to use ViewModel, they’ll need to deprecate
onSaveInstanceState(Bundle) as well. This sounds crazy, I know, but it’s actually good because if such crazy prediction will come true, you’ll know that the underlying theory was correct.
However, given Android’s memory management mechanism, Google can’t just deprecate
onSaveInstanceState(Bundle) without providing a reliable alternative. “Luckily”, they already work on saved state module for ViewModel.
I guess that in one-two years we’ll know whether this theory has any merit.
All in all, as I said in the beginning of this section, since the release of ViewModel, configuration changes story in Android became a shitshow. More than two years ago, when I wrote the post titled Android ViewModel Architecture Component Considered Harmful, I predicted that ViewModels will be a waste and will further require save & restore hacks at some point. All my predictions came true, but, unfortunately, the reality turned out to be even worse than that.
The most important development in the domain of concurrency APIs in Android is, of course, deprecation of AsyncTask. I already wrote a very detailed article on this subject with specific recommendations, so I won’t discuss this matter again here.
Now I’m going to say something that will alienate many readers. Please, try to not take it personally.
Another popular framework for multithreading in Android, RxJava, quickly becomes “legacy”. It’s quite evident from this StackOverflow Trends graph:
Now, many developers challenge this statement, saying that this data is not representative and that there are other ways to interpret this graph. That’s probably correct, and I’m not data scientist myself. However, I don’t see any other explanation for the very distinct maximum in this graph, and the fact that RxJava’s curve has the same slope as AsyncTask’s one.
Therefore, if you haven’t invested time into RxJava yet and your project doesn’t use it, then I recommend avoiding it. In fact, this was my recommendation all along, but today it’s also supported by data.
If your project already uses Rx, don’t panic. There is no need to refactor anything immediately. Especially if you’re a single developer, or the turnover of staff is slow. However, keep in mind that, going forward, it’ll be more and more difficult to find developers experienced in Rx. Therefore, extensive usage of Rx in projects will probably require more time for new developers to ramp up on it. Eventually, projects that use Rx extensively will be considered “not cool” (like projects with AsyncTask and Loaders today).
I understand that this discussion of RxJava cuts close to the bone for many developers. They invested weeks into learning RxJava, maybe even convinced their teammates to use it in projects, and now I’m saying that it’s “legacy”. How dare I? Once again, all I can say is that it’s not personal. I simply analyze the situation and make predictions based on what I see. I might be wrong too. But if I’m right, I’m just the messenger, so don’t shoot me.
In Kotlin world we have Coroutines. I implemented some non-trivial use cases using Coroutines recently and found this framework to be quite nuanced, complex and relatively immature. Even found a bug.
There is a widespread claim that Coroutines make concurrency simpler. I never bought it because I know that concurrency is fundamentally complex, but now, after I got some hands-on experience, I can say with confidence that Coroutines aren’t all rainbows and unicorns. In my opinion, coroutines actually add complexity, so I recommend to approach them carefully.
On the other hand, looks like Coroutines are going to be Kotlin’s default concurrency primitive. Therefore, I do think that you need to invest time and learn to use them if you write Kotlin code.
There is also Flow framework which, as far as I understand, adds stream processing operators on top of Coroutines. It became stable just couple of months ago, so I can’t say anything about it right now.
Now let’s discuss the state of Kotlin in Android. From the past experience I know that this is very sensitive topic and, regardless of my efforts to be objective, shit hits the fan very quickly. However, I think it would be professionally dishonest on my side to summarize the state of native Android development and skip Kotlin. Therefore, I’ll just ask you once again to not take anything I say personally.
The most important fact you need to know about Kotlin in Android is that it can severely increase your build times.
In this post you can read about my own informal benchmarking of build times with Kotlin. The results were +18% for clean builds and +8% for incremental builds.
Then Uber published their own research in collaboration with JetBrains. Their results draw much more negative picture. Based on these results, it looks like if you don’t use annotation processors in your application, then introduction of Kotlin can potentially multiply your build times by a factor of four! If you do use annotation processors, then Kotlin will add 50%-100% to your build times.
Uber’s results are consistent with the metrics obtained after migration of OkHttp to Kotlin: x4 increase in compilation time.
If you’re absolutely astonished by these numbers, then don’t worry – it’s not your fault and you’re not alone. This subject, although of utmost importance, isn’t being discussed widely and I get an impression that Google tries to sweep it under the rug. When I asked one developer, who is closely familiar with the matter, after a very interesting discussion, whether I can quote them on this topic, they said “I prefer not; it’s a delicate matter”.
In addition to slow build times in general, support for incremental annotation processing in Kotlin was non-existent until this week. For comparison, it has been available for Java since about 10 months ago.
Two years ago I wrote this post to warn the community about potential dangers of early Kotlin adoption. I got a lot of heat for this article (as you can see from the comments) and became “Kotlin hater in chief” for a long time.
However, if you read that article today and keep the aforementioned metrics in mind, you’ll realize that I actually underestimated the problems. Build times are one of the worst productivity killers on bigger Android projects and the fact that even today, more than two years after “official adoption”, Kotlin is still so much inferior to Java, speaks for itself. Whatever other benefits Kotlin brought, all of them have probably been negated manifold by longer build times.
That said, we should not ignore the fact that Google pushes the ecosystem to Kotlin and its adoption steadily increases.
I, personally, didn’t choose Kotlin for new projects that I started so far. It was too immature and I don’t believe that my clients should pay for my education, or, alternatively, that I should waste my own time on Kotlin. However, starting now, I’ll give Kotlin a serious consideration for new projects. I already tried it on several pet projects and it looks like its evolution finally slows down. Again, I don’t agree with developers who say that you must use Kotlin on new projects. It’s still a trade-off. However, Kotlin became a real contender in my opinion.
As for whether you should migrate existing projects to Kotlin or not, I can’t give any general recommendations. It’s a multi-variable problem that needs to be discussed on case-by-case basis. However, if you do decide to start a migration (or already started), this post that contains some warnings might be useful to you.
Allow me to describe my activities in context of Android development:
I started three new applications in the past two years. I always strive to have at least one client where I do hands-on development. I jump into existing projects and analyze the long-term effects of earlier technical decisions. I write this blog. I produce advanced courses on Android development. I spend too much time arguing about Android related topics on the internet.
However, despite all the aforementioned activities, today I feel like I can’t keep up with changes in Android ecosystem.
If that’s what I feel, I’m really sorry for less experienced Android developers who need to navigate their way. And I don’t even want to imagine how it feels to learn Android development from scratch today. By the time you get comfortable with the framework and the tools, many of them will be outdated or deprecated. It’s probably the worst time to join this, otherwise amazing, community. Google is very proud about their “inclusivity”, but looks like it doesn’t apply to less experienced developers.
I, personally, think that Google’s actions in context of Android framework lead to enormous waste of human potential. It takes hours just to read about all these changes, let alone actually implement them. I’d prefer to spend this time more productively, creating value, instead of chasing my own tail.
In this post I tried to summarize the important info about the current state of native Android development. I also made some predictions about the future. Now, I’m sure that this post isn’t perfect: it probably contains some inacurracies and I surely missed additional important points. Feel free to correct me in the comments below. But please keep in mind that nothing in this article is personal. I know that I brought up some very controversial points, but that’s what I believe to be true.
I also referenced some of my older posts in several places. I did it not to show off and say “look, I was correct!”, but for you to be able to read my predictions from the past and compare them with what actually happened. When I wrote these articles, they read just as crazy as this one reads today. But the predictions I made turned out to be quite accurate.
Well, whom do I kid? Sure thing I also want to say “look, I was correct!”. Given the huge professional risk I take by publishing these controversial predictions, it’s a huge relief to know that I didn’t mislead my readers. Even though sometimes I’d prefer to be absolutely wrong and Google to turn out to be a real partner. As of today, that’s not the case.
As usual, thanks for reading. You can leave your comments and questions below.