Skip to content

Instantly share code, notes, and snippets.

@fonix232
Created September 27, 2019 11:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fonix232/14294caea86c4161478f5263a41fc50b to your computer and use it in GitHub Desktop.
Save fonix232/14294caea86c4161478f5263a41fc50b to your computer and use it in GitHub Desktop.
BottomNavigationView Conductor Adapter
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import androidx.annotation.NonNull
import androidx.core.view.contains
import androidx.core.view.get
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.Router
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.bottomnavigation.BottomNavigationView
abstract class BNVRouterPagerAdapter(
val host: Controller,
val bnv: BottomNavigationView,
val viewPager: ViewPager,
val pages: Map<Int, () -> Controller>
): PagerAdapter() {
companion object {
private const val KEY_SAVED_PAGES = "BottomBarRouterPagerAdapter.savedStates"
private const val KEY_MAX_PAGES_TO_STATE_SAVE = "BottomBarRouterPagerAdapter.maxPagesToStateSave"
private const val KEY_SAVE_PAGE_HISTORY = "BottomBarRouterPagerAdapter.savedPageHistory"
}
private var maxPagesToStateSave = Integer.MAX_VALUE
private val visibleRouters = SparseArray<Router>()
var currentPrimaryRouter: Router? = null
private set
var savedPageHistory = ArrayList<Int>()
private set
var savedPages: SparseArray<Bundle> = SparseArray()
private set
init {
require(bnv.menu.all { pages.containsKey(it.itemId) }) { "All menu items must have a matching page setup!" }
viewPager.adapter = this
bnv.setOnNavigationItemSelectedListener {
viewPager.currentItem = bnv.menu.indexOf(it)
true
}
}
/**
* Called when a router is instantiated. Here the router's root should be set if needed.
*
* @param router The router used for the page
* @param position The page position to be instantiated.
*/
fun configureRouter(@NonNull router: Router, position: Int) {
if(!router.hasRootController()) {
val page = pages[bnv.menu[position].itemId] ?: throw Exception("Page not found in initializers!")
router.setRoot(RouterTransaction.with(page.invoke()))
}
}
override fun getCount(): Int = bnv.menu.size()
override fun getPageTitle(position: Int): CharSequence? = bnv.menu[position].title
override fun instantiateItem(container: ViewGroup, position: Int): Any {
return host.getChildRouter(container, makeRouterName(container.id, getItemId(position))).apply {
if(!hasRootController()) {
val savedState = savedPages.get(position)
if(savedState != null) {
restoreInstanceState(savedState)
savedPages.remove(position)
savedPageHistory.remove(position)
}
rebindIfNeeded()
configureRouter(this, position)
if(this !== currentPrimaryRouter) {
backstack.forEach { it.controller().setOptionsMenuHidden(true) }
}
visibleRouters.put(position, this)
}
}
}
override fun destroyItem(container: ViewGroup, position: Int, router: Any) {
require(router is Router) { "Non-router object in router stack!" }
val savedState = Bundle()
router.saveInstanceState(savedState)
savedPages.put(position, savedState)
savedPageHistory.remove(position)
savedPageHistory.add(position)
ensurePagesSaved()
host.removeChildRouter(router)
visibleRouters.remove(position)
}
override fun setPrimaryItem(container: ViewGroup, position: Int, router: Any) {
require(router is Router) { "Non-router object in router stack!" }
if(router !== currentPrimaryRouter) {
currentPrimaryRouter?.backstack?.forEach { it.controller().setOptionsMenuHidden(true) }
router.backstack.forEach { it.controller().setOptionsMenuHidden(false) }
currentPrimaryRouter = router
}
}
override fun isViewFromObject(view: View, router: Any): Boolean {
require(router is Router) { "Non-router object in router stack!" }
for(transaction in router.backstack) if(transaction.controller().view == view) return true
return false
}
fun setMaxPagesToStateSave(maxPagesToStateSave: Int) {
require(maxPagesToStateSave >= 0) { "Only positive integers may be passed for maxPagesToStateSave." }
this.maxPagesToStateSave = maxPagesToStateSave
ensurePagesSaved()
}
override fun saveState(): Parcelable? {
return (super.saveState() as? Bundle? ?: Bundle()).apply {
putSparseParcelableArray(KEY_SAVED_PAGES, savedPages)
putInt(KEY_MAX_PAGES_TO_STATE_SAVE, maxPagesToStateSave)
putIntegerArrayList(KEY_SAVE_PAGE_HISTORY, savedPageHistory)
}
}
override fun restoreState(state: Parcelable?, loader: ClassLoader?) {
super.restoreState(state, loader)
(state as? Bundle?)?.apply {
savedPages = getSparseParcelableArray(KEY_SAVED_PAGES) ?: SparseArray()
maxPagesToStateSave = getInt(KEY_MAX_PAGES_TO_STATE_SAVE)
savedPageHistory = getIntegerArrayList(KEY_SAVE_PAGE_HISTORY) as ArrayList<Int>
}
}
fun getRouter(position: Int): Router = visibleRouters[position, null]
fun getItemId(position: Int): Long {
return bnv.menu.getItem(position).itemId.toLong()
}
private fun ensurePagesSaved() {
while (savedPages.size() > maxPagesToStateSave) {
val positionToRemove = savedPageHistory.removeAt(0)
savedPages.remove(positionToRemove)
}
}
private fun makeRouterName(viewId: Int, id: Long): String {
return "$viewId:$id"
}
}
fun Menu.all(predicate: (android.view.MenuItem) -> Boolean): Boolean {
if(size() == 0) return false
for(index in (0..size())) if(!predicate(get(index))) return false
return true
}
fun Menu.indexOf(item: android.view.MenuItem): Int {
require(size() > 0) { "Menu is empty!" }
require(contains(item)) { "Item is not part of menu!" }
for(index in (0..size())) if(get(index) == item) return index
throw Exception("Item is not part of menu!")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment