Skip to content

Instantly share code, notes, and snippets.

@GregKluska
Last active August 11, 2020 23:14
Show Gist options
  • Select an option

  • Save GregKluska/4b91b8b44b6236c94c24fea8bb3d43eb to your computer and use it in GitHub Desktop.

Select an option

Save GregKluska/4b91b8b44b6236c94c24fea8bb3d43eb to your computer and use it in GitHub Desktop.
import android.app.Activity
import android.content.Context
import androidx.annotation.IdRes
import androidx.annotation.NavigationRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.gregkluska.restaurantmvvm.util.BottomNavController.OnNavigationReselectedListener
/**
* Class credit: Allan Veloso, Mitch Tabian
* I took the concept from Allan Veloso and made alterations to fit our needs.
* https://stackoverflow.com/questions/50577356/android-jetpack-navigation-bottomnavigationview-with-youtube-or-instagram-like#_=_
* @property navigationBackStack: Backstack for the bottom navigation
*/
class BottomNavController(
val context: Context,
@IdRes val containerId: Int,
@IdRes val appStartDestinationId: Int,
val graphChangeListener: OnNavigationGraphChanged?,
val navGraphProvider: NavGraphProvider
) {
private val TAG: String = "AppDebug"
private val navigationBackStack = BackStack.of(appStartDestinationId)
lateinit var activity: Activity
lateinit var fragmentManager: FragmentManager
lateinit var navItemChangeListener: OnNavigationItemChanged
init {
if (context is Activity) {
activity = context
fragmentManager = (activity as FragmentActivity).supportFragmentManager
}
}
fun onNavigationItemSelected(itemId: Int = navigationBackStack.last()): Boolean {
// Replace fragment representing a navigation item
val fragment = fragmentManager.findFragmentByTag(itemId.toString())
?: NavHostFragment.create(navGraphProvider.getNavGraphId(itemId))
fragmentManager.beginTransaction()
.replace(containerId, fragment, itemId.toString())
.addToBackStack(null)
.commit()
// Add to back stack
navigationBackStack.moveLast(itemId)
// Update checked icon
navItemChangeListener.onItemChanged(itemId)
// communicate with Activity
graphChangeListener?.onGraphChange()
return true
}
fun onBackPressed() {
val childFragmentManager = fragmentManager.findFragmentById(containerId)!!
.childFragmentManager
when {
// We should always try to go back on the child fragment manager stack before going to
// the navigation stack. It's important to use the child fragment manager instead of the
// NavController because if the user change tabs super fast commit of the
// supportFragmentManager may mess up with the NavController child fragment manager back
// stack
childFragmentManager.popBackStackImmediate() -> {
}
// Fragment back stack is empty so try to go back on the navigation stack
navigationBackStack.size > 1 -> {
// Remove last item from back stack
navigationBackStack.removeLast()
// Update the container with new fragment
onNavigationItemSelected()
}
// If the stack has only one and it's not the navigation home we should
// ensure that the application always leave from startDestination
navigationBackStack.last() != appStartDestinationId -> {
navigationBackStack.removeLast()
navigationBackStack.add(0, appStartDestinationId)
onNavigationItemSelected()
}
// Navigation stack is empty, so finish the activity
else -> activity.finish()
}
}
private class BackStack : ArrayList<Int>() {
companion object {
fun of(vararg elements: Int): BackStack {
val b = BackStack()
b.addAll(elements.toTypedArray())
return b
}
}
fun removeLast() = removeAt(size - 1)
fun moveLast(item: Int) {
remove(item) // if present, remove
add(item) // add to end of list
}
}
// For setting the checked icon in the bottom nav
interface OnNavigationItemChanged {
fun onItemChanged(itemId: Int)
}
// Get id of each graph
// ex: R.navigation.nav_blog
// ex: R.navigation.nav_create_blog
interface NavGraphProvider {
@NavigationRes
fun getNavGraphId(itemId: Int): Int
}
// Execute when Navigation Graph changes.
// ex: Select a new item on the bottom navigation
// ex: Home -> Account
interface OnNavigationGraphChanged{
fun onGraphChange()
}
interface OnNavigationReselectedListener{
fun onReselectNavItem(navController: NavController, fragment: Fragment)
}
fun setOnItemNavigationChanged(listener: (itemId: Int) -> Unit) {
this.navItemChangeListener = object : OnNavigationItemChanged {
override fun onItemChanged(itemId: Int) {
listener.invoke(itemId)
}
}
}
}
// Convenience extension to set up the navigation
fun BottomNavigationView.setUpNavigation(
bottomNavController: BottomNavController,
onReselectListener: OnNavigationReselectedListener
) {
setOnNavigationItemSelectedListener {
bottomNavController.onNavigationItemSelected(it.itemId)
}
setOnNavigationItemReselectedListener {
bottomNavController
.fragmentManager
.findFragmentById(bottomNavController.containerId)!!
.childFragmentManager
.fragments[0]?.let { fragment ->
onReselectListener.onReselectNavItem(
bottomNavController.activity.findNavController(bottomNavController.containerId),
fragment
)
}
}
bottomNavController.setOnItemNavigationChanged { itemId ->
menu.findItem(itemId).isChecked = true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment