Introduction

First of all, I welcome you to my blog. My name is Josip Šalković. I’m 24 years old and have years of experience developing native Android apps. I have worked on various projects from different social spheres, from those used by millions of people around the world to local ones. I am most known for the development of the HŽPP Planer mobile application, which is listed in the BEST 50 Croatian mobile applications by IT professionals. Here I will post the things I am facing and the problems I am solving while developing Android projects, so maybe help someone or list them in a similar way to implement the solution. So let’s go! 🙂

About navigation

Navigation is one of the key things in mobile software development. Navigation components allow users to navigate through different parts of the application and options such as launching a new screen, returning to the previous one, starting certain actions etc. All of the above actions are taken care of by the developers when writing the code and developing the entire application flow. Poorly programmed navigation components can lead to a disastrous user experience. Although this is unlikely today because there are extensive tests being performed on the public release of an application, but imagine a situation such as when you want to reply to someone’s mail and expect the application to send that message back to that person, and under the strange circumstances it will forward that same message to you instead of the answer. How would you feel and what would it cause for you? Certainly not a delight 🙂

There are several standard ways of navigating the Android platform between screens that you have certainly seen and used before in applications, even though you probably didn’t pay attention and don’t know their name. So the most commonly used components are:

Android Navigation Components

Bottom Navigation View

Navigation Drawer

Options Menus

  1. BottomNavigationView – as its name implies, the navigation component at the bottom of the screen. When you click on a particular item in a component, it becomes highlighted and the content that is specific to that part is loaded. It is found in most popular applications like LinkedIn, Google Maps, YouTube, Instagram, Viber, Facebook Messenger, Wolt and Twitter etc.
  2. NavigationDrawer – a navigation component that is initially hidden from the user and in native Android applications is opened by clicking the menu button in the upper left corner (three vertically aligned lines) or by dragging the finger from the left edge of the screen towards the middle. The usual behavior is to click on an item to close a component and an action occurs. Usually, this is the launch of a new screen or the dynamic replacement of an existing one. We can find it in applications like Gmail, Microsoft Outlook, Uber, Bolt, etc.
  3. OptionsMenus – a navigation component that is ubiquitous throughout the application flow, and is typically located in the upper right corner of the screen in the form of three vertically arranged dots. Clicking on a component opens a drop-down menu with options for the user to select, most often one-time actions such as adding an item to favorites, refreshing the screen, etc.

Dynamic UI using Fragments

As users navigate between different parts of the application, the Activities in charge of that part of the application are triggered. Android OS maintains the Activity stack on its own and we as developers have no influence on that. When building a dynamic UI with different modules that must be added to an existing part of the app that is already displayed to the user, the Activities are powerless.

For the reasons stated above, Google has been seeking to raise the awareness of developers in our projects over the long term that we use Fragments more instead Activities. Fragments are components that can be easily added to existing Activities and do different jobs. They always belong to a specific Activity that hosts them, they are determined by their Lifecycle, they have their own resources and most importantly, they can be reused. We can think of fragments as some sub activity.

A great lecture on a similar topic by the Single Activity model with Fragments was delivered by Ian Lake at last year’s Android Dev Summit. The lecture discusses why you should switch to this model, what are the benefits and when to use it best. You can watch the video at the link below:

Problems with Fragments navigation

Just as Activities have their stack, so do Fragments have their own. The main difference with Fragments is that we are the developers in charge of maintaining the stack, while in Activity this is Android OS. This means that all actions such as adding, deleting, and other supported actions for Fragments, we developers must maintain properly.

Poor maintenance and manipulation of the Fragment stack can lead to confusing application behavior, and thus to poor UX. It is especially difficult for beginners in the Android world when they first encounter Fragments and Fragment Manager and do not understand what they need to do to properly add / delete a Fragment from a container.

Android Navigation Component and problem solution

To top it off, Google unveiled the Navigation Component, a library within Android Jetpack, at last year’s Android Dev Summit, making it easier for us developers to properly implement navigation from a simple click to more complex stuff. The Library is in charge of:

  • implementing Fragment transactions
  • maintaining a Fragment back stack
  • adding animations during transactions
  • prolonging arguments when creating a Fragment

Looking at it from this aspect, it offers everything that I had to program on my own. Although I try to have as few libraries as possible in my projects, as they pull in new dependencies, I take things that Google places more seriously and I always like to try and see how this can help me develop my project.

The Library is divided into three key points that are necessary for it to function properly, which are:

  1. Navigation GraphAn XML resource that contains all possible destinations to which a user can navigate and the actions they reach. Destinations are content areas in this library (mostly Fragments), while actions are logical paths that explain how users reach that destination. The destinations below are numbered 1 in the figure, while the shares are numbered 2.
  2. Navigation HostAn empty container that contains the Navigation Graph described earlier and knows what destinations a user can reach in that part of the application.
  3. Navigation Controllera controller that, in conjunction with the Navigation Host, correctly changes destinations as the user navigates through the application. You explicitly tell the controller which destination you want to display or specify the route that leads to that destination. Then the controller displays that destination in the Navigation Host.

Navigation Graph example

If you want to take a closer look at the Navigation Component and what it has to offer, I suggest you watch a video from this year’s Android Dev Summit at the link below:

So I also gave the navigation component a chance and tried it out in my project shortly after the introduction, even though I maintained the back stack on my own and there was no problem, but clean to see the library’s capabilities. I implemented the above three points and merged them and best of all, the component supports the BottomNavigationView I used so all I needed to do in the program part were the following two lines:

// associate navigation controller with navigation host
val navController = findNavController(this, R.id.nav_host)
// associate bottom navigation with controller
binding.bottomNav?.setupWithNavController(navController)

Then why don't I use the Navigation Component?

I started the app and it really worked. I thought methods I wrote for Fragment transactions like addFragment(), removeFragment(), replaceFragment(), detachFragment(), attachFragment(), hideFragment(), and showFragment() and maintaining the Fragment back stack are a thing of the past.

However, as I clicked on BottomNavigation a little longer, I realized that my Fragments were being recreated. It’s not exactly the best UX when a user clicks on a tab in a component again, so the content reloads instead of saving the state and re-displaying the next time it starts. Previously, this was not the case when I was doing transactions on my own. I immediately went to look at the source code and had something to see. The key thing is the NavController.navigate (action or id) method that the library invokes internally when the user clicks on a single tab in BottomNavigation by passing the tab tab ID to the method. What the component does in this method are the following two things:

  1. Popup all the Fragments from the back stack until you reach the desired destination
  2. Creating a new Fragment using the specified destination ID

Unfortunately, the component did not live up to my expectations with BottomNavigation because re-creating Fragments at every tab click was out of the question. Of course there are already different workarounds for this, but then I wonder why use the library if I have to write things further?

So I still stay with the old way and my proven methods that work well. If someone accidentally bothers with BottomNavigation’s navigation implementation and its Fragments are not recreating, below is the code snippet I use in my projects, so it might help. Notice one important thing, that is, when adding a Fragment, I make it the primary one and do not add it to the back stack. This means that the back stack is empty and I always have one active Fragment instance as the primary one and that is the tab that the user clicked. When the user clicks on another tab, the process is repeated ie the current Fragment is hidden if it exists (initially does not exist), the Fragment to be shown to the user is added or added if it is not in Fragment Manager and displayed or retrieved from the Fragment Manager if there is also I just show it. That’s it!

/**
 * Method exposed to nested classes to perform Fragment transaction in BottomNavigation.
 * It first check if we have currently active Fragment and hide it. 
 * Then check if given Fragment already exists in FragmentManager and if so, show it.
 * Else, add it to Fragment Manager.
 * @param containerViewId layout in which Fragment transaction will be executed
 * @param fragment which needs to be shown
 */
protected fun performFragmentTransaction(containerViewId: Int, fragment: BaseFragment?) {
    
    // get current primary Fragment from Fragment Manager
    val currentFragment = supportFragmentManager.primaryNavigationFragment as BaseFragment?

    // check if current Fragment isn't null and current fragment isn't same as given one
    if (currentFragment!=null && currentFragment.fragmentTag != fragment?.fragmentTag) {
        // if both conditions are true, hide current fragment
        hideFragment(currentFragment)
    }

    // check if given Fragment already exists in Fragment Manager
    val f = supportFragmentManager.findFragmentByTag(fragment?.fragmentTag) as BaseFragment?

    // if Fragment not exists, add it to Fragment Manager
    if (f == null) {
        addFragment(
            containerViewId = containerViewId,
            fragment = fragment,
            setPrimaryFragment = true,
            addToBackStack = false
        )
    }
    // if Fragment exist, just show it
    else {
        showFragment(f)
    }
}