5

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?

Mauker
  • 11,237
  • 7
  • 58
  • 76

1 Answers1

9

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,
    }

}
aminography
  • 21,986
  • 13
  • 70
  • 74
  • That's indeed really interesting! I'll implement it and test it. Thanks! – Mauker Sep 05 '18 at 12:21
  • @Mauker: If it works for you, please accept the answer dude ;) – aminography Sep 05 '18 at 12:26
  • Of course! Gimme a few and I'll be back. Thanks again. – Mauker Sep 05 '18 at 12:27
  • It solved like 90% of the problem. Here's what I noticed: On the first time I tap on the navigation bar, it still lags, but after the second time it works wonders. I believe that this can be solved if I change how I'm creating the view. But since it works on the "second tap", I'll mark it as solved. Brilliant solution. – Mauker Sep 05 '18 at 13:08
  • Thank you man. I think you should revise your codes related to fragment views and try to make them lighter by using RecyclerView, maybe ViewStub, etc... – aminography Sep 05 '18 at 13:23
  • Exactly. I'll make those changes on the view. Thanks again! – Mauker Sep 05 '18 at 13:26
  • Sorry to bother again, but I found this: https://thomasclowes.com/android-fragments-and-memory/ And I'm facing a memory issue since I'm adding 5 fragments to my container, the memory usage spikes. – Mauker Sep 06 '18 at 13:01
  • Anybody with Java version of this? – Anurag Bhalekar Oct 02 '20 at 19:19