Everything that’s wrong with Android’s Activities
..and Fragments, for that matter.
Let’s just kick off and have a look at what an Activity is capable of:
1. The Activity is an entry point
The Activity is an entry point into your application that the user sees when they open your application. Upon clicking your application icon from a launcher app, an Activity instance is created and presented to the user.
2. The Activity hosts the window that interacts with the user.
By calling setContentView
in your Activity, you can inflate a layout that is bound to a Window
instance hosted by the Activity. This allows the user to see and interact with your application.
3. The Activity has a lifecycle
Android’s multitasking mechanism allows for applications to be put in the background temporarily, and navigated back to when the user sees fit. To be able to react to these events, the Activity provides a set of callbacks you can implement.
4. The Activity needs state restoration
Android’s multitasking mechanism combined with limited memory can cause applications to be killed. To prevent a bad user experience when the user loses a message he or she was typing due to this, the Activity allows its state to be saved to a bundle.
5. An Activity can have a result
Applications can start other applications to request a particular result. For example, the Contacts app can be launched to select a particular contact. When this is done, the result is sent back to the original application that started the request.
Now these are all very valid use cases. They make up the foundation of all applications, and allow for perfectly fine applications to be created.
But wait, there’s more!
6. The Activity is destroyed on configuration changes
When a device configuration changes, such as an orientation change or when the system local has changed, the Activity is destroyed and recreated with a new Resources
set for the new configuration.
7. The Activity handles navigation
When a user clicks an item in an item list Activity, the Activity can start a new Activity to show the item’s details using startActivity
.
8. The Activity handles permissions
When your application wants to read a user’s location info for example, you need to call Activity#requestPermissions
. Your Activity will receive the result in Activity#onRequestPermissionsResult
.
9. The Activity hosts an options menu
Earlier versions of Android had a dedicated ‘menu’ button that would popup a menu in the application. To populate this menu, you override Activity#onCreateOptionsMenu
; when a menu item has been selected, Activity#onOptionsItemSelected
will be called which you can override.
10. The Activity hosts an ActionBa.. Toolbar
When the options menu button disappeared from devices, the ActionBar
class appeared to be able to host this options menu. You can set a Toolbar
using Activity#setActionBar
.
11. The Activity has direct references to the View hierarchy
To show meaningful content to the user, you can use Activity#findViewById
to fetch a certain View
and populate it with data.
12. The Activity can handle key presses and touches
Just override Activity#onKeyDown
or any of its siblings.
13. The Activity can handle voice interaction
I’m not even sure what this is. Here’s a reference.
14. The Activity provides Activity-private preferences
Calling Activity#getPreferences
returns a SharedPreferences
instance private to the Activity.
The above is a subset of what android.app.Activity
can do, and I’m sure I have forgotten a few major use cases. On top of all this FragmentActivity
can also host Fragments and by the way, Fragments are designed to be able to do everything an Activity can do!
Now what’s wrong with all this?
All these points are perfectly valid features. You need new resources on configuration changes, you need to handle navigation, you need to handle permissions, etcetera.
However, having to resort to the Activity to do all these things severely violates any kind of single responsibility principle. The Activity is a God class and all of its subclasses are prone to become God classes themselves as well.
All of a sudden your screen logic needs to stop whatever it is doing and be recreated because someone rotated their device.
Handling complex navigational patterns suddenly resides in the screen as well because, well, the back stack.
Working with UI elements like options menus, toolbars, even key presses or touches are all view-related and have no business near any useful logic.
Then there are problems with what they can’t do. The most obvious one here is transitions between screens. With multi-Activity applications, it is very hard to have proper and fine grained control over your screen transition animations.
Inversion of control
With the latest trend of using single Activity apps, the Activity should have a very simple purpose: hosting the UI. Everything else, from callbacks to navigation and state restoration belong in different components. Unfortunately, that is not the way Android works, and you’ll have to deal with it.
That doesn’t mean that you have to bind all your logic to it though.
When writing software, your business logic is the most important thing in the world. Everything you (or your team) didn’t write yourself is out of your control, and you should protect yourself from it. Obviously this includes the Android framework.
Protecting yourself from third party software means that that third party software cannot break your business logic. If there’s a bug in the Android framework, or a third party library breaks, you shouldn’t have to alter your core logic to fix the problem.
The plugin model
The de facto standard for this is the plugin model. You let the GUI be a plugin to your application. The database is a plugin to your application. Then, when there is indeed a bug in the Android Framework, you still have to create a workaround. But instead of the Android Framework leaving its marks in your precious business logic, this will all be at the very boundaries of your system.
Note that the GUI is just that: the Graphical User Interface. Or in Android terms: everything that extends the View class. Navigation, screens, presenters and everything else is business logic that you want to have testable and far out of reach from the complex intricacies the Android Framework provides.
It’s time to take back control over our applications and shove the monolith classes that Activities and Fragments are aside until they’re absolutely necessary.
When you do this, you can write your core application logic in pure Java. Or Kotlin even. And what about Kotlin Native?