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.