Skip to content

This repository contains some tricks about Android Navigation Component. 3rd party libraries not used.

License

Notifications You must be signed in to change notification settings

furkanaskin/TrickyNavigationSample

Repository files navigation

TrickyNavigationSample

This repository contains some tricks about Android Navigation Component. 3rd party libraries not used.

Outputs

Default Bottom Behaviour Tricky Bottom Behaviour

Android Navigation Component default behavior hasn't got bottom navigation back stack. If you wanna add back stack to your bottomNavigationMenu it's easy and simple. Just add:

android:menuCategory="secondary"

to your all menu items.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/homeFragment"
        android:icon="@drawable/ic_home"
        android:menuCategory="secondary"
        android:title="Home" />

    <item
        android:id="@+id/searchFragment"
        android:icon="@drawable/ic_search"
        android:menuCategory="secondary"
        android:title="Search" />

    <item
        android:id="@+id/gamesFragment"
        android:icon="@drawable/ic_games"
        android:menuCategory="secondary"
        android:title="Games" />

    <item
        android:id="@+id/notificationsFragment"
        android:icon="@drawable/ic_notifications"
        android:menuCategory="secondary"
        android:title="Notifications" />
</menu>
menuCategory="secondary" Tricky "secondary"

With adding secondary to your items you gonna have backstack but if you want a stack like in Instagram or Youtube make the following changes:

First of all we need an extension for our navController.

fun NavController.popBackStackAllInstances(destination: Int, inclusive: Boolean): Boolean {
    var popped: Boolean
    while (true) {
        popped = popBackStack(destination, inclusive)
        if (!popped) {
            break
        }
    }
    return popped
}

Then extend your bottom tab fragments from BaseBottomTabFragment, don't forget to use utils functions when you try to navigate from tab starter fragments.

BaseBottomTabFragment has onBackPressedDispatcher for handling back press. Also it has isNavigated boolean, because if it's trying to navigate in same tab we don't need to addCallback.

open class BaseBottomTabFragment : Fragment() {
    var isNavigated = false

    fun navigateWithAction(action: NavDirections) {
        isNavigated = true
        findNavController().navigate(action)
    }

    fun navigate(resId: Int) {
        isNavigated = true
        findNavController().navigate(resId)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        if (!isNavigated)
            requireActivity().onBackPressedDispatcher.addCallback(this) {
                val navController = findNavController()
                if (navController.currentBackStackEntry?.destination?.id != null) {
                    findNavController().popBackStackAllInstances(
                        navController.currentBackStackEntry?.destination?.id!!,
                        true
                    )
                } else
                    navController.popBackStack()
            }
    }
}

With this trick we have a back stack like Instagram and Youtube but we forgot something. As you know if you setup your toolbar with navController, your back press behaviour works with navController and onBackPressedDispatcher just affects your activiy's back press. If you wanna get the same bottom behavior with your toolbar navigate button. Add to following code to your activity:

binding.toolbar.setNavigationOnClickListener {
    when (navController.currentDestination?.id) {
        R.id.searchFragment, R.id.gamesFragment, R.id.notificationsFragment -> {
            if (onBackPressedDispatcher.hasEnabledCallbacks())
                onBackPressedDispatcher.onBackPressed()
            else
                navController.navigateUp()
        }
        else -> navController.navigateUp()
    }
 }
Dynamic Label

As you know, if you setup your toolbar with navController, your toolbar titles handling from navController. Navcontroller uses your fragment labels as title. For making this dynamically we need to use Android Navigation Component - SafeArgs

Define your argument as string in nav_graph

<fragment
    android:id="@+id/dynamicTitleFragment"
    android:name="com.faskn.trickynavigationsample.fragments.DynamicTitleFragment"
    android:label="{title}"
    tools:layout="@layout/fragment_dynamic_title" >
    <argument
        android:name="title"
        app:argType="string"
        android:defaultValue="Title" />
</fragment>

don't forget to use your argument as label

android:label="{title}"

and then pass data between destinations

binding.buttonDynamicTitleNavigate.setOnClickListener {
    navigateWithAction(
        SearchFragmentDirections.actionSearchFragmentToDynamicTitleFragment(
            binding.editTextTitle.text.toString()
        )
    )
 }
Default Replace Tricky Replace

As you know, if you call findNavController().navigate() it replaces your current fragment with other and when you call back press maybe your state is not saved. The easiest solution is passing id to your all views. It works with ScrollView, Recyclerview, etc..

But the best solution is Event.kt or EventObserver.kt

Tricky Add

With Android Navigation Component you can't add any fragment to your container. But you just wanna add 3-4 fragment to your navController you can use this solution.

Navigation Component 2.1.0 supports <dialog> tag in navigation graph.

This trick uses BottomSheetDialogFragment as fullscreen and with isDraggable = false attribute. It works like add. But adding too many screens can cause performance issues. Be careful.