I’m here at the airport with some time to kill and I’d like to write a something about Android.
It’s really just a rant. I don’t expect anyone reading this to learn much from it. But it’s still an experience I’d like to share.
I’ve been working with Xamarin to make an iOS and Android version of the same app.
I’m not going to discuss problems related to the tool, it certainly has its quirks. Instead, I’m going to focus on the Android app model.
A quick note about my background. I’m a developer with 10+ years of experience but I’m relatively new to mobile app development. I’m an iPhone user, I’ve played a bit with iOS apps on my spare time and made some Cydia tweaks for jailbroken iPhones. Never done anything on Android. So, one might say I’m biased, it’s up to you to decide if that is the case.
The app I’ve developed has a somewhat complex “backend” but the “frontend” (UI) is actually quite simple. Several pages with lists or labels/buttons, some toolbars, a mapview … that’s pretty much it. Nothing like the jaw-dropping animation/transitions on some modern single-page apps.
The plan was to write a common backend and then write a platform-specific frontend in both iOS and Android. That’s the common pattern for Xamarin, unless you go for Xamarin.Forms.
Ok, ok, let’s get to the meat.
In Android, there are a few “components” that you can put together to make an app.
The most common component is the “Activity”…. and I’m not even sure how to describe what it is at this point.
Let’s start simple. It’s basically a full-screen view in your app that does a specific and indipendent (in theory, more on this later) task.
Now, the actual visual content is usually defined via AXML files. This rocks. I like it much more than iOS constraints-based model.
The first eyebrow-raiser moment is when you learn that a “configuration change” will cause the Activity to be destroyed and re-created. This happens in a number of circumstances, but the most common is when you rotate the device from portrait orientation to landscape or vice-versa.
The documentation says that the UI state is saved automatically for you (true, but only partially) and you’re supposed to save the internal state of your Activity object (basically the member variables). This sounds not intuitive but feasible at the beginning.
Here are the first problems:
- you need to keep track of WHICH fields you want to persist
- you have to use primitive types or types that implement the parcealable interface. This is a major PITA if you have common backend objects that are platform-independent and thus don’t and can’t implement it.
Another annoyance is that Activities can receive some input parameters and return some output values but they must be primitive/parcealable as well.
The idea is that in Android an Activity can start an Activity in another app (or a system activity). This is what happens when you want to compose an email for instance: the activity starts a new activity with the appropriate Intent and the default mail client will launch to let you write your email.
It’s an interesting and powerful concept, but it’s not what you do all the time. You usually start other activities in your own package, so forcing all the data to be parcealable makes the common use case unnecessarily complex.
The other issue with the Activities re-creation is that, if you use asynchronous programming (async methods in C# or AsyncTask in Java I believe), you’re basically making a certain method/lambda run when the task is complete. That code will run in the current context (the method’s object or lambda’s captured references) so, guess what, you rotate your device, the task completes and the code runs on the old activity instead of the one just created for the new orientation.
There are workarounds, which basically make you do the async from within another (persistent) object and callback into the current activity.
It’s a workaround. It’s feasible, but it’s not practical.
The app process
In other platforms, the app is effectively a process. When you launch the app, the process gets started. When you close the app, the process is terminated. On mobile platforms, the process is usually suspended while the app is not visible.
On iOS, there’s no real concept of closing the app from the user point of view: it’s just in the foreground or not. What actually happens it that when you open the app the first time the process is created and gets suspended/resumed from that point. If the OS needs memory, it may kill the process. Still, the app == process assumption holds.
On Android, no. Far from it. The process is just a container for Activities.
When you start the first Activity, the process is started. When you finish the last Activity, the process terminates.
This design choice IMHO is particularly bad because it means that app-level state shared between the activities, particularly in the form of Singletons, can’t be used. Not without hacks at least.
At first, you may think it’s okay. You believe this is always going to happen:
- The process starts
- The main Activity A is started and the singleton is somehow instantiated
- The Activity A does some work and sets some state in the singleton for future activities to use. For instance, let’s say the Activity authenticates the user and stores the auth token that needs to be used in web requests
- Activity B is started
- Activity B uses state in the singleton, which is expected to be valid at this point because Activity B is only started from Activity A.
For instance, let’s say it uses the auth token to make a web request.
- Activity B finishes
- Activity A finishes
- The process terminates.
All good right? Well, let me introduce the “activity stack restoration”…
The “activity stack” contains information about the running activities. If the OS decides to kill the container process due to low memory, the activity stack is saved. The user can go back to the app and at that point the OS creates ONLY the top activity in a new process. If the user goes “back”, the Activity will finish and the previous activity in the backstack will be created. It’s the same as when the app is running but the Activities are only created when necessary instead of being already loaded and “stopped”.
So, you can’t use a singleton to share some state because the following will happen, assuming the user was in Activity B:
- The process starts
- Activity B is started (because it was at the top of the stack). Activity A is NOT.
- Activity B tries to use the state in the singleton.
At this point, depending on your implementation, either the singleton hasn’t been instantiated at all or it is but it doesn’t contain the data, because Activity A has never put it there.
The activity state restoration only works fine if you do all of the following:
- correctly save and restore the Activity state
- don’t store any state outside of the Activity or manage to restore it as well
One might think an Activity is just the UI for the app. In this case, if you have some business logic elsewhere, you somehow have to restore its state as well when the OS restores the Activity.
Let me put it this way: it’s like a car running on he highway. The dashboard is the UI, the car is the entire app. You’re halfway your trip, then… zap, the car disappears. Then, Android says “It’s cool bro, here’s your dashboard, exactly how you left it (assuming you told me about all of its indicators). You’re good to continue your trip.”. What about the rest of the car? Where are we? What are the tyres pressure? What’s the steering wheel angle? What’s the licence plate number?
The dashboard would have to store all of that information. Which makes no sense whatsoever.
<plang=”en-GB”>On the other hand, if you store all the information in your Activity, it becomes basically a God-object… a standalone mini-app. A mini-app that must be able to save and restore its state at any point in a parcealable format. A mini-app that must be able to communicate with other mini-apps (why yes, communicating only via parcealable intents).
Activities can be independent from each other only to a certain extent. If they’re all in the same app it’s because they have somehow to “work together” to get something done. If they were completely independent, then they would be separate apps.
You can try some workarounds to avoid putting all of your logic into an Activity, all of which have some drawbacks because they are workarounds.
The “proper” way seems to be a clusterfuck.
Fragments to the rescue… lol J/K
Some people suggest using Fragments to solve part of these issues.
Fragments are… basically activities that can be embedded into other activities. Their lifecycle normally follows the one of containing activity. However – and this is the nice thing – they can also be set to survive configuration changes (setRetainInstance()). They also don’t need to actually contain a UI. In fact, non-UI fragments are the only case where setRetainInstance is recommended. For UI fragments, like with Activities, it’s recommended to let the framework destroy and recreate it so it can adapt and use different resources for the new screen orientation (or whatever).
Non-UI fragments set to retain the instance can be used to effectively store any kind of state without the need for parcealable stuff.
However, you’re using something that is generally supposed to be part of your UI as a data container, which already smells fishy. Then, it doesn’t help with async programming, unless you use that fragment to also contain the relative code. And then invoke callbacks on the Activity.
It’s like a shit-cake with a cherry on the top.
If the OS kills the process and tries to restore the activity later, the state contained in that non-UI fragment is gone anyway (unless you save/restore parcealable stuff, but then it’s the same as activity), so it’s not even a good cherry.
The quality of the documentation is usually mediocre. Some classes are well documented, others… not quite. It reminds me of PHP or Python. I haven’t spent too much time on those though, so I can’t really compare (Python wasn’t too bad last time I had a look).
What I can say though, is that it’s definitely worse than MSDN and iOS Developer docs.
If you need to read StackOverflow to understand how something works (and even there it’s not straightforward because the correct explanation might very well be not in the accepted answer) then you know something is wrong.
My feeling is that on Android it’s easy to do really basic stuff, but it gets exponentially more difficult when you try to get serious.
I’m sure a proper app can be built if designed from the beginning with Android’s app model in mind. The problem, IMHO, is that this model sucks and being so different from other platforms makes code sharing difficult.