Migrating to the new navigation system

In my last article I mentioned that we migrated to the new Android’s navigation system that is a part of Android Jetpack components. This time I want to share some implementation details. Usually in my posts I don’t really like to share the actual code, but only concepts, since there are plenty of implementation examples for the particular concept, and I don’t want to repeat what the others already wrote, I rather prefer to explain the reasoning behind the one or another decision. But while migrating to the new navigation system our team has faced a few challenges, that was not possible to resolve just by reading available documentation and copying code examples, therefore I think some implementation details we came up with might help someone who is striving to master the new navigation system.

Problem

After all our Fragment and Activity analysis we came to conclusion that we should have several activities In our application which would serve as hosts for set of Fragments. And the features we used to group by the fragments were:

  • common master layout
  • common UI logic
  • common use case

So we started to explore the documentation and look for code examples, that would help us to understand how to implement the navigation from one navigation graph to the other graph hosted in different activities. And very quickly we realized that the documentation and examples available on the Internet are heavily focused around a single Activity application and navigation between the fragments hosted in one navigation graph. Although official Android documentation mentions that the new navigation system supports Activities as destinations. The biggest challenge was to find a way how to assign the start destination to navigation graph at runtime depending on various parameters. In our app user should be able to create a sport event and later edit it or delete it. All fragments related to sport event we decided to put in one navigation graph. But the start destination in this graph depends on whether user wants to create a new sport event, edit previously created sport event or see other user’s sport event. So we could not assign the start destination at design time in navigation graph but we had to do it at runtime. Since we could not find enough information how to do it, we started to consider other solutions.

Solution

Spending hours on reading documentation and exploring examples we started to think maybe we should use only one Activity. And put all UI logic from the other Activities in this one and show/hide the parts in UI which are needed/not needed for active fragment. But very soon it became clear that this universal hosting activity is getting very big and it is hard to read it and we have to add additional logic for hiding and showing views in Activity. The hosting activity became something opposite we were striving for – smaller and more manageable classes. So we dropped this idea and started to consider moving the views from Activities to Fragments, that Fragments would contain all the necessary views and UI logic and hosting Activity would be left only with NavHostFragment. That would mean that views like BottomNavigationView and Toolbar now would be located in several Fragments. We could ofcourse use include tag but that still would not remove the need to repeat the same include tags in several fragments. So we didn’t really like this idea either, since it looked like we are violating the DRY (Don’t Repeat Yourself) principle. And we got back to the idea about several activities. This time in order to find a solution we started to dig in to the navigation components source code, and after awhile we came up with the following implementation that allows assign a start destination to a navigation graph at runtime:


private void initNavGraph() {
    NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
            .findFragmentById(R.id.sport_activity_nav_fragment);
    NavInflater inflater = navHostFragment.getNavController().getNavInflater();
    NavGraph graph = inflater.inflate(R.navigation.nav_sport_event);
    String sportEventId = getIntent().getStringExtra(EXTRA_SPORT_EVENT_ID);
    long calendarEventId = getIntent().getLongExtra(CalendarContract.Events._ID, -1);
    if (calendarEventId == -1 && sportEventId == null) {
        graph.setStartDestination(R.id.editMySportEventFragment);
    } else {
        graph.setStartDestination(R.id.bookSportEventFragment2);
    }
    this.mNavController = navHostFragment.getNavController();
    graph.addDefaultArguments(getIntent().getExtras());
    this.mNavController.setGraph(graph);
}

The above method creates new navigation graph and sets the needed start destination, and passes arguments we set in Intent when started the hosting Activity. In such way we can pass needed arguments further to the start destination. And finally we set this new navigation graph with defined start destination and arguments in navigation controller.