Context free Android navigation
You may be familiar with this situation: You want a ViewModel without Android code, but some dependencies need an Activity to navigate. Or perhaps you just want to use a class that requires a Context, poisoning code by pulling the Android framework in. We can avoid this.

One of the challenges in the Android world is accessing user interfaces from the application logic. We are taught to keep the logic out of UI code, however we also need to use that UI for things like navigation, permissions or simply interacting with users from a background service.
Let’s have a look into the example of navigation and discuss how to expand the solution to solve this problem.
ViewModel navigation
Having a ViewModel
or layers below independent from the Android framework can have benefits like testability, leak safety, modularity or even just better build times.
Navigation breaks this. We simply need a Context or a reference to the FragmentManager
to interact with the system when our logic tells us to navigate. Event-based solutions can be used, but this means the navigation logic has to be within the Android code and we increase the coupling of our Activity
to its ViewModel
.
Navigator pattern
It would be great to navigate to any screen, without anything other than the parameters, from any thread. The Navigator pattern could be just a simple interface like the following.
Having such an interface within our ViewModels can be very convenient, but we need to come up with a good implementation.
Where does the Context come from?
There has to be an activity Context
, FragmentManager
or some other class to interact with the system UI. This could be the Activity which is the one at the top of the app activity stack. ActivityLifecycleCallbacks
can be a handy, decorative way to get it.
Use
ActivityLifecycleCallbacks
orFragmentLifecycleCallback
to prevent activities and fragments poisoning all your business logic code.
A possible implementation may look like the following:
Our Navigator
implementation could look like:
This might be enough for a basic implementation and it should keep us happy in most situations, however I believe some readers may already see the problem with the ?.let
part. There are moments within the Activity
lifecycle, when there can be no Activity
at the top and our navigation will fail, doing nothing.
Another challenge is that the final implementation should allow us to use the Navigator
from any thread at any moment. Taking into account that we may perform UI operations as part of navigation, this means executing navigation on the main thread.
Async Navigator implementation
To ensure that the methods requiring Activity are executed as soon as possible, we need some asynchronous interface for our TopActivityProvider.
The simplest way could be passing a method with an Activity as a parameter, leading to the code:
fun onTopActivity(action: (Activity) -> Unit)
We also need to ensure immediate execution when we are already on a main thread to prevent any delays and maintain thread safety. A brief implementation could look like:
We introduce pendingActions
, which are just methods that are saved when there is no current activity or we call onTopActivity from a thread other than the UI one. The runOnUiThread
method is used for switching to the main thread.
The resulting Navigator implementation can be very simple now:
This gives clients of our Navigator
interface the freedom of calling the navigation methods from anywhere and we can enjoy code like this:
This approach is also valid when using Jetpack Navigation where obtained instances can be used for findNavController calls.
We show here an implementation for Activity-based navigation, but a very similar solution can be implemented for fragments.
TopActivityProvider has way more usages
This was an example of how to use ActivityLifecycleCallbacks
to break the Android framework dependency for navigation interfaces. We can do way more than that.
We may have a use case where we need a reference to the UI and having a simple way to execute actions on top of anything currently visible to the user can improve decoupling of our logic from our UI code.
Having TopActivityProvider
can allow you to keep Android specific code as an implementation detail and focus only on your application logic in pure Kotlin code, improving testability, reducing coupling and also improving build times by avoiding the Android framework.
What pattern do you use for navigation? Do you have a Context
in your ViewModel
? Let me know in the comments.
Happy coding!