Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Navigation Component has lag when navigating through NavigationDrawer

Tags:

I am updating my app to Navigation Architecture Components and I see that it has a lag replacing fragments which is visible in the NavigationDrawer that does not close smoothly.

Until now, I was following this approach:

https://vikrammnit.wordpress.com/2016/03/28/facing-navigation-drawer-item-onclick-lag/

So I navigate in onDrawerClosed instead than in onNavigationItemSelected to avoid the glitch.

This has been a very common issue, but it is back again. Using the Navigation Component, it is laggy again and I don't see a way to have it implemented in onDrawerClosed.

These are some older answers prior to Navigation Component

Navigation Drawer lag on Android

DrawerLayout's item click - When is the right time to replace fragment?

Thank you very much.

like image 300
Javier Delgado Avatar asked Jul 11 '19 13:07

Javier Delgado


People also ask

How to use DrawerLayout in Android?

To use a DrawerLayout, position your primary content view as the first child with width and height of match_parent and no layout_gravity> . Add drawers as child views after the main content view and set the layout_gravity appropriately. Drawers commonly use match_parent for height with a fixed width.

Can we use navigation component for activities?

Note: If your app uses multiple activities, each activity uses a separate navigation graph. To take full advantage of the Navigation component, your app should use multiple fragments in a single activity. However, activities can still benefit from the Navigation component.

What is navigation SafeArgs?

SafeArgs is a gradle plugin that allows you to enter information into the navigation graph about the arguments that you want to pass. It then generates code for you that handles the tedious bits of creating a Bundle for those arguments and pulling those arguments out of the Bundle on the other side.


1 Answers

I'm tackling this issue as I write this answer. After some testing, I concluded that code I'm executing in fragment right after its created (like initializing RecyclerView adapter and populating it with data, or configuring UI) is causing the drawer to lag as its all happening simultaneously.

Now the best idea I got is similar to some older solutions that rely on onDrawerClosed. We delay the execution of our code in fragment until the drawer has closed. The layout of the fragment will become visible before the drawer is closed, so it will still look fast and responsive.

Note that I'm also using navigation component.

First, we are going to create an interface and implement it fragments.

interface StartFragmentListener {
    fun configureFragment()
}

In activity setup DrawerListener like:

private fun configureDrawerStateListener(){
    psMainNavDrawerLayout.addDrawerListener(object: DrawerLayout.DrawerListener{
        override fun onDrawerStateChanged(newState: Int) {}
        override fun onDrawerSlide(drawerView: View, slideOffset: Float) {}
        override fun onDrawerOpened(drawerView: View) {}

        override fun onDrawerClosed(drawerView: View) {
            notifyDrawerClosed()
        }

    })
}

To notify a fragment that the drawer has been closed and it can do operations that cause lag:

private fun notifyDrawerClosed(){
    val currentFragment =
            supportFragmentManager.findFragmentById(R.id.psMainNavHostFragment)
                    ?.childFragmentManager?.primaryNavigationFragment

    if(currentFragment is StartFragmentListenr && currentFragment != null)
        currentFragment.configureFragment()

}

In case you are not navigating to the fragment from the drawer (for example pressing back button) you also need to notify fragment to do its things. We will implement FragmentLifecycleCallbacksListener:

private fun setupFragmentLifecycleCallbacksListener(){
    supportFragmentManager.findFragmentById(R.id.psMainNavHostFragment)
            ?.childFragmentManager?.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() {

        override fun onFragmentActivityCreated(fm: FragmentManager, f: Fragment, savedInstanceState: Bundle?) {
            super.onFragmentActivityCreated(fm, f, savedInstanceState)

            if (!psMainNavDrawerLayout.isDrawerOpen(GravityCompat.START)) {
                if (f is StartFragmentListener)
                    f.configureFragment()
            }

        }
    }, true)
}

In fragment:

class MyFragment: Fragment(), MyActivity.StartFragmentListener {
    private var shouldConfigureUI = true
    ...

    override fun onDetach() {
        super.onDetach()
        shouldConfigureUI = true
    }

    override fun configureFragment() {
        if(shouldConfigureUI){
            shouldConfigureUI = false
            //do your things here, like configuring UI, getting data from VM etc...
            configureUI()
        }
    }
} 

A similar solution could be implemented with a shared view model.

like image 86
UrosKekovic Avatar answered Sep 28 '22 03:09

UrosKekovic