23

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.

Javier Delgado
  • 2,343
  • 2
  • 17
  • 31
  • 1
    Unfortunately, I've hit the same problem. The only workaround I came up with was to make the Fragment wait a bit before loading its contents, but that's far from ideal. I wish there was a better way to solve this... – Mauker Jan 15 '20 at 05:33
  • Can you public code and xml? – Cuong Nguyen Jan 17 '20 at 04:38
  • I dont know how it was in 2019, but using the Navigation version 2.4.1 you can overwrite `onDrawerClosed` and put the fragment navigation logic using `NavController` class and `.navigate(..)` method. All as per my knowledge. – Michael Zur Feb 20 '22 at 16:37

2 Answers2

7

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.

UrosKekovic
  • 940
  • 7
  • 10
  • What if it's also possible to navigate to the fragment outside from the drawer? I would have to call the `configureFragment()` "by hand" right? – Mauker Jan 17 '20 at 13:19
  • 1
    No, this solution will handle it. BackStackListener will register every fragment change in host fragment, but it will only call configureFragment() if drawer was closed when fragment was changed - indicating that we navigated to that fragment outside the drawer. – UrosKekovic Jan 17 '20 at 13:27
  • 1
    Ah! You're right. Amazing :) Good thinking. I'll test it today. – Mauker Jan 17 '20 at 13:35
  • 1
    I've tested your solution, and man, that's a huge improvement in performance, thank you! As for the potential bug, a flag might solve the issue, but I'm also unsure if it can really happen. – Mauker Jan 19 '20 at 04:24
  • I found a problem with BackStackListener, there is a case when it wont register fragment change. When i pop up backstack when navigating so the destination fragment will be only fragment on backstack, BackStackListener wont be called. I will edit my answer to handle this :) – UrosKekovic Jan 20 '20 at 09:48
  • As for your edit, shouldn't we reset the variable once the fragment has been detached? – Mauker Jan 24 '20 at 13:46
  • 1
    @Mauker it has to be done, I missed it. I will update it now, thank you. – UrosKekovic Jan 27 '20 at 08:45
  • Great solutions – Patriotic Mar 16 '20 at 16:49
  • When you have tapped on outside the drawer then drawer closes. But I wanted the drawer to be closed smoothly on item selected. I have solved this by adding shouldConfigureUI = true in onViewCreated and shouldConfigureUI = false after configuring UI. – Patriotic Mar 18 '20 at 08:12
  • Unfortunately, this clever approach did not help me. Drawer still lagged and I have not noticed any performance improvement. Looks like in my case initializing RecyclerView adapter and populating it with data and configuring UI are not as "heavy" operations as creating and inflating a View. – Michael Zur Feb 19 '22 at 21:35
  • Try experimenting, disable displaying and loading data for some performance test, is your layout loading some heavy images etc? Maybe you could even add layout elements after the drawer is closed and initially only display root layout and maybe progress bar. @MichaelZur – UrosKekovic Feb 21 '22 at 15:14
  • The heaviest in my layout is a nested Google Maps fragment as . I thought the map loading starts afer I run `.getMapAsync()`, but turns out no. So, indeed, I might add it dynamically using your method. However, overwriting `onDrawerClose` works for me now. I might consider migrating to your method later again and then I will follow up on performance. @UrosKekovic – Michael Zur Feb 21 '22 at 15:42
0

Avoid the lag caused while changing Fragment / Activity onNavigationItemSelected- Android

Navigation Drawer is the most common option used in the applications, when we have more than five options then we go towards the navigation menu.

I have seen in many applications that when we change the option from the navigation menu, we observe that it lags, some people on StackOverflow recommended that use Handler like the below code:

private void openDrawerActivity(final Class className) {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                ProjectUtils.genericIntent(NewDrawer.this, className, null, false);
            }
        }, 200);
    }

But in the above code, it’s still not smooth & I thought why we add handler may be there is another solution after so much R&D, what I figure it out that we need to change the fragment/activity when the drawer is going to be close. let’s see with the implementation.

For more details with solution kindly go through https://android.jlelse.eu/avoid-the-lag-caused-while-changing-fragment-activity-onnavigationitemselected-android-28bcb2528ad8. It really help and useful.

Hope you find better solutions in it!

Kalpesh Rupani
  • 991
  • 4
  • 12