Library module navigation in Android Applications
Learn about the library module navigation in Android apps. This post is an overview of the library module navigation in Android applications.
Header Photo by Alexander Andrews on Unsplash
When it comes to building android applications, there’s no doubt that we’ll need to include some form of navigation to move between the different parts of our app. With modularisation becoming more and more popular when it comes to android development, navigation becomes a big part of this process.
At Buffer we’ve begun creating a lot of shared code between our applications – some of these are utilities, widgets and even features (note, these are not yet dynamic feature modules, instead they are library modules). When it comes to features, often these will need to navigate to another part of the app – however, because these library modules are not aware of the base android app module they are unable to satisfy the navigational requirements in our app.
In this post we are going to explore a solution that not only solves this problem for us, but helps us to separate the navigational concern from the rest of our application, providing us with numerous benefits in the process.
In the example we can see that we can’t easily navigate to some of the screens in our application, purely because we do not have a reference to them. Whilst we are forced to solve this initial problem here, this gets us thinking about navigation in general within our app. With single module applications we would normally handle the navigation by calling startActivity() , followed by passing in an Intent that has a direct reference to the Activity class which we were navigating to. However, this approach brings the question of whether or not our activities should have the knowledge of where it is they are navigating to? If anything, this leaks another concern & responsibility into the activity. As well as this, the class that is launching that activity now has a direct reference to it, which adds to the concepts that our launching class is aware of and being directly tied to that destination – which is something that a library (in most cases) cannot do. Finally, when it comes to the testing of our class, navigation also becomes a concern of these tests too – which reinforces the argument of there being another responsibility that is a part of our activity.
As it is becoming more common now to introduce modularisation to the mix, this is likely to become a common problem faced amongst applications. Because of this and the above issues, it may make sense in some cases to split out the navigation of our app to be handled by some classes outside of the ones which may be currently performing the navigation. Not only does this make navigation possible from these internal library modules, but it helps to separate the concerns of our navigation and it makes it far easier to test the implementations of navigation within our apps.
With all of this in mind, how can we achieve the above when it comes to navigation within our android apps? Let’s begin by taking a look at an approach that meets all of the above requirements.
Because we’re focusing in this post on library modules, let us begin by taking a look at a simple library module we have within our applications. Both of our apps (Publish and Reply) share the same on-boarding screens and because of this, we make use of a shared library that we import as a gradle dependency. This dependency shows a couple of on-boarding steps that the user can swipe through, but the important part here are the two buttons that are displayed to the user. These buttons allow the user to either Navigate to the Sign-Up or Sign-In screens – these are not part of the on-boarding library as currently these following screens are verify different for each of the apps. As previously mentioned in this post, the on-boarding library does not have (and cannot have) a reference to the base app module due to it being a library module. So as it is, it cannot navigate to the activities inside of the base app module – and because it is being reused for multiple apps, the paths to the desired activities will be completely different so this is not something that should be hard coded.
With this in mind, the Onboarding library is going to need to provide an interface which states the navigational requirements that are going to need to be satisfied. This allows the onboarding module to define and enforce these requirements without having any knowledge around the actual details of these actions.
From here, the application module using that onboarding library can implement the interface and satisfy the navigational requirements. So for example, the activity launching the screens of the onboarding library may implement that interface and when the methods are triggered, handle the navigation around sign-up and sign-in.
Whilst in the process of implementing this however, it got us thinking about the responsibility of navigation. Our activities and fragments are already handling other things, we could make an improvement here by removing this responsibility from these components and handling the navigation elsewhere.
For this reason, we decided to introduce a Navigation module. The purpose of this module is to encapsulate all of the navigational logic of the application – allowing us to remove this knowledge from our activities / fragments and make it far easier to test the navigational aspects of our app.
With this approach, the Navigation module needs to have a reference to the modules that contain the interfaces defining the required navigation. Whilst this module may end up having a reference to multiple modules, this is fine as its not intended to be reusable and is also fulfilling its purpose – it also removes the need to have these dependency references from within our app module itself (in most cases).
Whilst the Navigation module implements the navigation, the Publish module here still needs a reference to navigation for two reasons. First of all, we don’t have Navigation handling its own dependency injection – navigation becomes a part of our Dagger Component and Module that we have configured within our Publish module. This allows us to provide the required injections inside of the onboarding library – whilst this isn’t ideal having this injection requirement here, seeing as it is an internal library this solution works well for us at this point in time.
Another reason why the Publish module needs a reference to this Navigation module is to account for other navigation requirements throughout the app. Moving forward we will not be restricting this to library modules only – as we move to decouple more features within our application, these will use navigation interfaces within their corresponding packages (even if not yet modularised) – so having this reference to the navigation module helps us to achieve that.
When it comes to this Onboarding module, as previously mentioned, it will define an interface (lets call it the OnboardingNavigator) that defines the the required navigation. This might look a little something like this:
interface OnboardingNavigator {
fun showSignUpForm(activity: Activity)
fun showSignInForm(activity: Activity)
We pass in the activity here so that the onboarding module can completely handle the navigation to the next destination. We do not want to navigate here using string declarations of activity paths, so some form of context is required here.
Within our Navigation module we’re going to want to provide an implementation of this interface – this allows us to implement the required functions and launch the required activities when those functions are triggered.
class OnboardingCoordinator @Inject constructor() : OnboardingNavigator {
[...]
---
*[Original source](https://buffer.com/resources/library-module-navigation-in-android-applications/)*