ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Context free Android navigation

Josef Raska
ProAndroidDev
Published in
4 min readMar 24, 2020

--

Pic by ElisaDGM with ❤

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.

Full example here together with Deep link navigation implementation.

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 or FragmentLifecycleCallback 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:

Full implementation with example app and tests here.

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:

Full version here.

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!

--

--

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Josef Raska

Key to being a good engineer is largely to use your judgment and avoid problems that would require a good engineer to solve them.

Responses (3)

Write a response