Skip to content

Instantly share code, notes, and snippets.

@samuel22gj
Last active March 14, 2023 03:10
Show Gist options
  • Save samuel22gj/aa18f5bdb63b93087b2f41407460a81a to your computer and use it in GitHub Desktop.
Save samuel22gj/aa18f5bdb63b93087b2f41407460a81a to your computer and use it in GitHub Desktop.
/**
* A mediator to link a fragment container with a BottomNavigationView.
*
* The mediator will synchronize the fragment container's content
* with the selected item when a item of BottomNavigationView is selected.
*/
class FragmentBottomNavigationMediator(
private val fragmentManager: FragmentManager,
@IdRes private val containerViewId: Int,
private val bottomNavigationView: BottomNavigationView,
private val accountDelegate: AccountDelegate,
private val i13nDelegate: I13nDelegate
) {
private val itemMap: MutableMap<Int, Item> = mutableMapOf()
var currentFragment: Fragment? = null
private set
fun addItems(vararg items: Item) {
items.forEach { itemMap[it.menuId] = it }
}
fun attach() {
bottomNavigationView.setOnNavigationItemSelectedListener { menuItem: MenuItem ->
val item = itemMap[menuItem.itemId]
item ?: return@setOnNavigationItemSelectedListener false
if (item.isLoginRequired && !accountDelegate.isLogin()) {
accountDelegate.requestLogin { bottomNavigationView.selectedItemId = item.menuId }
return@setOnNavigationItemSelectedListener false
}
fragmentManager.commit(allowStateLoss = true) {
currentFragment?.let { hide(it) }
val fragmentInStack = fragmentManager.findFragmentByTag(item.fragmentTag)
if (fragmentInStack is Fragment) {
currentFragment = fragmentInStack
show(fragmentInStack)
} else {
val fragment = item.fragmentInitializer.invoke()
currentFragment = fragment
add(containerViewId, fragment, item.fragmentTag)
}
}
i13nDelegate.logItemSelected(menuItem.itemId)
return@setOnNavigationItemSelectedListener true
}
bottomNavigationView.setOnNavigationItemReselectedListener { menuItem: MenuItem ->
val item = itemMap[menuItem.itemId]
item ?: return@setOnNavigationItemReselectedListener
if (currentFragment == null) {
fragmentManager.commit(allowStateLoss = true) {
val fragment = item.fragmentInitializer.invoke()
currentFragment = fragment
add(containerViewId, fragment, item.fragmentTag)
}
return@setOnNavigationItemReselectedListener
}
// If there are child fragments in the stack, pop all of them out, otherwise scroll itself to top.
currentFragment?.run {
if (childFragmentManager.backStackEntryCount > 0) {
do {
childFragmentManager.popBackStackImmediate()
} while (childFragmentManager.backStackEntryCount > 0)
} else {
scrollToTop()
}
}
i13nDelegate.logItemSelected(menuItem.itemId)
}
}
fun detach() {
bottomNavigationView.setOnNavigationItemSelectedListener(null)
bottomNavigationView.setOnNavigationItemReselectedListener(null)
itemMap.clear()
currentFragment = null
fragmentManager.commit(allowStateLoss = true) {
fragmentManager.fragments.forEach { remove(it) }
}
}
fun onLogout() {
val item = itemMap[bottomNavigationView.selectedItemId]
item ?: return
// If the current tab is login-required, switch to first non-login-required tab.
if (item.isLoginRequired) {
val firstNonLoginRequiredItem = itemMap.values.asSequence()
.firstOrNull { !it.isLoginRequired && fragmentManager.findFragmentByTag(it.fragmentTag) != null }
if (firstNonLoginRequiredItem != null) {
bottomNavigationView.selectedItemId = firstNonLoginRequiredItem.menuId
}
}
// Remove all login-required tab in container
fragmentManager.commit(allowStateLoss = true) {
itemMap.values.asSequence()
.filter { it.isLoginRequired }
.map { fragmentManager.findFragmentByTag(it.fragmentTag) }
.filterNotNull()
.forEach { remove(it) }
}
}
class Item(
val menuId: Int,
val fragmentTag: String,
val isLoginRequired: Boolean,
val eventName: String,
val fragmentInitializer: () -> Fragment
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other === null || javaClass != other.javaClass) return false
val item = other as Item
return Objects.equals(menuId, item.menuId)
}
override fun hashCode(): Int {
return Objects.hash(menuId)
}
}
interface AccountDelegate {
fun isLogin(): Boolean
fun requestLogin(onSuccess: () -> Unit)
}
interface I13nDelegate {
fun logItemSelected(@IdRes menuId: Int)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment