ActivityLifecycleCallbacks — a blind spot in public API

Vladimir Genovich
ЮMoney
Published in
5 min readDec 31, 2019

Ever since I was a child, I liked to read instructions and manuals. Even now, as an adult, I’m still amazed at how carelessly people treat them, thinking they already know everything yet only using one or two modes and functions out of dozens! How many of you use the Keep Warm feature of your microwave ovens? It’s available in almost every model now.

Recently, I decided to read the documentation for various Android framework classes, skimming through the main ones such as View, Activity, Fragment, Application. My interest was piqued by the Application.registerActivityLifecycleCallbacks() method and the ActivityLifecycleCallbacks interface. The Internet didn’t have any better usage examples other than logging of the Activity life cycle. I started experimenting with it myself, and now Yandex.Money actively uses it for solving a whole range of tasks related to outside management of the Activity objects.

What is ActivityLifecycleCallbacks?

Take a look at this interface. This is what it looked like when it appeared in API 14:

Starting with API 29, a few more methods were added:

Maybe people pay so little attention to this interface because it only debuted in Android 4.0 ICS, which is a shame as it provides a very interesting tool that allows managing all Activity objects from the outside. More on that later, first, we’ll take a closer look at the methods.

Each method displays a similar Activity lifecycle method, and you can call it when the method is triggered by any Activity in the app. That is, if the app starts with MainActivity, then the first call we receive will be ActivityLifecycleCallback.onActivityCreated(MainActivity, null).

Great, but how does it work?

No magic here: Activity report on their state by themselves. Here’s a piece of code from Activity.onCreate():

This looks like as if we did BaseActivity ourselves, except our friends from Android not only did it for us, but also obliged everyone to use it. Really good of them, isn’t it? In API 29, these methods work in almost the same way, but their Pre and Post copies are called before and after specific methods. The process is probably now controlled by ActivityManager, but this is just my guess since I didn’t go into the source deep enough to find out.

How to make ActivityLifecycleCallbacks work?

Like all callbacks, first it needs to be registered. We register all ActivityLifecycleCallbacks in Application.onCreate(), and this gives us information about all Activity objects and the managing rights.

Short aside: from API 29 on, ActivityLifecycleCallbacks can also be registered from within Activity. This will be a local callback that only works for this Activity. That’s it. You can find all this by simply searching for “ActivityLifecycleCallbacks”. The results will include many examples of logging the Activity lifecycle, but does that actually interest you? Activity has a lot of public methods (about 400), and all of them can be used for a lot of interesting and useful things.

What can I do with this?
Whatever you want. Dynamically change the theme in all Activity objects in the app? Sure: the setTheme() method is public, which means you can call it from ActivityLifecycleCallback:

Try this ONLY at home
Some Activity objects from the connected libraries can use their custom themes. Therefore, check the package or any other attribute determining that the theme of this Activity can be safely changed. Here’s how we check the package (in Kotlin-way ツ):

Not working? You may have forgotten to register ThemeCallback in Application or Application in AndroidManifest.

Want another interesting example? You can show messages for any Activity in the app.

Try this ONLY at home
Of course, we don’t show messages on every screen: our users would definitely not appreciate this, though sometimes it can be useful to show something like this on some specific screens.

Here’s another case: what if we need to run Activity? Simple: Activity.startActivity() and go. What if we need to wait for the result after calling Activity.startActivityForResult()? I’ve got a recipe:

In the example, we simply dropped Fragment that starts Activity and gets the result, then delegates the processing to us.

Let’s complicate the examples. Up until now, we only used methods provided in Activity. What if we add our own? Suppose we want to send analytics about an opened screen. Let’s remember that our screens have their own names. How do we solve this? Simple! Create a Screen interface that can provide the screen name:

Now we implement it in the required Activity:

After that we set off the special ActivityLifecycleCallback on such Activity objects:

See? We just check the interface and, if it’s been implemented, send analytics.

Let’s go over it once again. What if we need to process some more parameters? Extend the interface:

Implement:

Send:

Still, too easy. Everything we did so far was to bring us to the actual interesting topic: native dependency injection. Yes, we have Dagger, Koin, Guice, Kodein, and others, but they’re redundant for small projects. I’ve got a solution, though… Guess what?

Let’s say we have a tool, something like this:

Hide it with interface (as any grown-up programmers would do):

Now, a bit of street magic from ActivityLifecycleCallbacks: we create an interface for injecting this dependency, implement it in the required Activity objects, then find and inject the CoolToolImpl implementation using ActivityLifecycleCallbacks.

Don’t forget to test InjectingLifecycleCallbacks in your Application, now, launch it. Everything’s working!
Don’t forget to test:

This approach wouldn’t scale well on large projects, so I’m not going to take away anybody’s DI frameworks. It’d be way better to combine efforts and work in synergy. Let’s see the example of Dagger2. If you have some basic Activity in the project that does something like `AndroidInjection.inject(this)`, then it’s time to throw it away. Instead, do the following:

  1. inject DispatchingAndroidInjector to Application using the instruction;
  2. create ActivityLifecycleCallbacks, that calls DispatchingAndroidInjector.maybeInject() for each Activity;
  3. register ActivityLifecycleCallbacks in Application.

Same effect can be achieved with other DI frameworks. Try some and post your results in the comments.

Let’s summarize

ActivityLifecycleCallbacks is an underrated, powerful tool. Try one of these examples, and let them help you in your projects the same way they help Yandex.Money make our apps better.

--

--