Search code examples
androidandroid-fragmentskotlinmaterial-designbottomnavigationview

BottomNavigationView lags on fragment transaction


The problem

I'm using the BottomNavigationView from the Android Design Support Library on one of my Activities, alongside with Fragments for each navigation item.

Each time I select an item on the bar, I do a fragment transaction, like the snippet below (some parts of the code was removed for brevity):

private var fragmentToSet: Fragment? = null

private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->

    fragmentToSet = when (item.itemId) {
        // Choose fragment based on selection
        // ...
    }

// ...

supportFragmentManager.beginTransaction()
                .replace(R.id.container, fragmentToSet)
                .commit()
}

The problem is... The bottom bar animation gets super laggy, and only finishes after the fragment is fully loaded and displayed on the screen.

This issue is not exactly new since it can also happen while using the Navigation Menu, but at least it's possible to solve it by using the DrawerLayout.DrawerListener and do the actual Fragment transaction only after the drawer is closed.

What I've tried so far

I tried to "cache" the fragments, holding their reference to avoid recreating the objects every time (e.g. MyFragment.newInstance()), but that didn't work.

I also tried to use handlers, which kinda solved the problem, but it might lead me to an exception in some cases. Something like the snippet below:

handler.postDelayed({changeFragment(fragmentToSet!!)}, 200)

Is there a way to solve this issue without using handlers (or other async calls), on a similar fashion to this solution while using the Navigation Menu?


Solution

  • I handled this situation by hiding and showing fragments using fragment manager. I wrote a sample code to deal with it as below.

    class MainActivity : BaseActivity() {
    
        private val homeFragment = HomeFragment.newInstance()
        private val categoryFragment = CategoryFragment.newInstance()
        private val searchFragment = SearchFragment.newInstance()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
            navigation.menu.findItem(R.id.navigation_home).isChecked = true
    
            supportFragmentManager.beginTransaction()
                    .add(R.id.containerFrameLayout, homeFragment)
                    .add(R.id.containerFrameLayout, categoryFragment)
                    .add(R.id.containerFrameLayout, searchFragment)
                    .commit()
            setTabStateFragment(TabState.HOME).commit()
        }
    
        override fun onBackPressed() {
            if (supportFragmentManager.backStackEntryCount > 0 || !homeFragment.isHidden) {
                super.onBackPressed()
            } else {
                setTabStateFragment(TabState.HOME).commit()
                navigation.menu.findItem(R.id.navigation_home).isChecked = true
            }
        }
    
        private fun setTabStateFragment(state: TabState): FragmentTransaction {
            supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
            val transaction = supportFragmentManager.beginTransaction()
            transaction.setCustomAnimations(R.anim.fragment_enter, R.anim.fragment_exit)
            when (state) {
                TabState.HOME -> {
                    transaction.show(homeFragment)
                    transaction.hide(categoryFragment)
                    transaction.hide(searchFragment)
                }
                TabState.CATEGORY -> {
                    transaction.hide(homeFragment)
                    transaction.show(categoryFragment)
                    transaction.hide(searchFragment)
                }
                TabState.SEARCH -> {
                    transaction.hide(homeFragment)
                    transaction.hide(categoryFragment)
                    transaction.show(searchFragment)
                }
            }
            return transaction
        }
    
        private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
            when (item.itemId) {
                R.id.navigation_home -> {
                    setTabStateFragment(TabState.HOME).commit()
                    return@OnNavigationItemSelectedListener true
                }
                R.id.navigation_category -> {
                    setTabStateFragment(TabState.CATEGORY).commit()
                    return@OnNavigationItemSelectedListener true
                }
                R.id.navigation_search -> {
                    setTabStateFragment(TabState.SEARCH).commit()
                    return@OnNavigationItemSelectedListener true
                }
            }
            false
        }
    
        internal enum class TabState {
            HOME,
            CATEGORY,
            SEARCH,
        }
    
    }