53

I use bottomNavigationView and navigation component. Please tell me how I don't destroy the fragment after switching to another tab and return to the old one? For example I have three tabs - A, B, C. My start tab is A. After I navigate to B, then return A. When I return to tab A, I do not want it to be re-created. How do it? Thanks

RKRK
  • 1,284
  • 5
  • 14
  • 18
Nikitc
  • 795
  • 1
  • 6
  • 12

7 Answers7

40

As per the open issue, Navigation does not directly support multiple back stacks - i.e., saving the state of stack B when you go back to B from A or C since Fragments do not support multiple back stacks.

As per this comment:

The NavigationAdvancedSample is now available at https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample

This sample uses multiple NavHostFragments, one for each bottom navigation tab, to work around the current limitations of the Fragment API in supporting multiple back stacks.

We'll be proceeding with the Fragment API to support multiple back stacks and the Navigation API to plug into it once created, which will remove the need for anything like the NavigationExtensions.kt file. We'll continue to use this issue to track that work.

Therefore you can use the NavigationAdvancedSample approach in your app right now and star the issue so that you get updates for when the underlying issue is resolved and direct support is added to Navigation.

Community
  • 1
  • 1
ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • 110
    So a simple feature like keeping 3 screens in memory required a new 'advanced' sample? You have got to be kidding – Radu Aug 14 '19 at 10:27
  • 9
    @Radu - it isn't just the three Fragments, it is a whole back stack associated with each tab (and the state of every one of *those* Fragments). FragmentManager only stores the state of things directly on the back stack (i.e., you can hit the system back button to get back to them), which isn't the case for bottom nav, where you want users to be able to swap between tabs without losing state. – ianhanniballake Aug 14 '19 at 13:46
  • After all, maybe it is not that bad of an idea! my problem was that I head to redeclare fragments in each nav graphs. – Amin Keshavarzian Sep 03 '19 at 10:27
  • 1
    @AminKeshavarzian - you can also use an [`` tag](https://developer.android.com/guide/navigation/navigation-nested-graphs#include) to reference a separate graph instead of copy/pasting. – ianhanniballake Sep 03 '19 at 15:45
  • @ianhanniballake you are right, but I was referring to mostly a single fragment because it wouldn't make sense to include a large graph just to add a destination from it.the trick, however, was to use identical ids. – Amin Keshavarzian Sep 04 '19 at 09:59
  • i use Navigation Advanced Sample and that is not save instance of fragment something is behind of this sample for saving instance – Ali Khaki Sep 18 '19 at 13:18
  • @AliKhaki - it does indeed save and restore state correctly. You should test your Fragments in other cases, such as being put on the back stack or when rotating your device to ensure that your Fragment is written correctly to restore its state. – ianhanniballake Sep 18 '19 at 13:38
  • 1
    The one solution that I works me was implement ViewModel + LiveData in each fragment where needish – ClarkXP Nov 22 '19 at 16:12
  • they say this is coming in 2.3, were on 2.2 do i do the advance sample just to change it later? who knows – martinseal1987 Jan 17 '20 at 10:19
  • 1
    @ianhanniballake can you please let me know, how can I use it with the navigation view? – M.Usman Jan 29 '20 at 17:04
  • @ianhanniballake Using this solution doesn't generate SafeArgs and FragmentDirection bindings. Ca you please help how can this be fixed? – sud007 Feb 27 '20 at 14:38
  • 1
    This class from the sample that @ianhanniballake is talking about, does not work. All it does it maintain the back stack and the state when you press back, that's it. Clicking on the bottom nav item, re-creates the fragment again. I fail to understand 250 lines of code for I don't know what. – Udayaditya Barua Mar 18 '20 at 10:46
  • 1
    @Uday - the [`setupItemReselected()`](https://github.com/android/architecture-components-samples/blob/master/NavigationAdvancedSample/app/src/main/java/com/example/android/navigationadvancedsample/NavigationExtensions.kt#L181) method is where you'd want to customize the reselection behavior. You could just do nothing if that's what is appropriate for your app. – ianhanniballake Mar 18 '20 at 16:08
  • 1
    @ianhanniballake Hello sir, any updates on this matter or folk are still confined to the advanced nav sample approach? Many thanks. – binjiezhao May 22 '20 at 22:08
  • 17
    I almost gave up on navigation architecture components. Google should also mention the limitation of their new APIs that will make life of developer easier to decide which API is more feasible to use in longer run. – Zeeshan Shabbir Jun 29 '20 at 10:04
  • 2
    Why all the samples are now in Kotlin?. Its highly unreadable. – Amit Jayant Nov 26 '20 at 09:28
  • 1
    @ianhanniballake Hi Ian, does this solution still the most actual in context of 'navigation component'? – Дмитро Яковлєв Jan 19 '21 at 08:37
  • The latest nav library v: 2.4.0-alpha01 does support multiple back stacks out of the box [Multiple Back Stack](https://medium.com/androiddevelopers/navigation-multiple-back-stacks-6c67ba41952f). – Abbas Nov 14 '21 at 23:47
  • It took me days trying to save the state of the fragment until I saw your answer, thank you – Mohmmad Qunibi Jan 17 '22 at 15:42
  • this issue was fixed with navigation in Jetpack Compose – user924 Aug 11 '22 at 17:07
  • @ianhanniballake is this fixed now? How can save fragment states when moving from one fragment to another – amodkanthe Nov 05 '22 at 10:17
  • @amodkanthe - you'd want to follow the [guide to saving your fragment's state](https://developer.android.com/guide/fragments/saving-state). – ianhanniballake Nov 05 '22 at 19:08
  • not working with viewBinding. any solution ? – KamDroid Dec 07 '22 at 11:28
  • Can some one validate below for me? I would have bottom navigation, viewpager, tab layout(hide this). On bottom navigation item click set the viewpager selected item. Thats is binding.contentMain.bottomNavigation.setOnItemSelectedListener { item -> viewPager.currentItem = position } I am sure with this approach I can get the job done. But what can be wrong with this approach? – tanni tanna Apr 05 '23 at 18:22
  • 1
    Nav version 2.5.3: the only main graph startDestination target fragment is keeping in memory, other fragments are still have been recreated. Useless fix, Google is not ok here too. – Konstantin Konopko Apr 06 '23 at 21:32
7

In case you can deal with destroying fragment, but want to save ViewModel, you can scope it into the Navigation Graph:

private val viewModel: FavouritesViewModel by 
    navGraphViewModels(R.id.mobile_navigation) {
        viewModelFactory
    }

Read more here

EDIT

As @SpiralDev noted, using Hilt simplifies a bit:

private val viewModel: MainViewModel by 
    navGraphViewModels(R.id.mobile_navigation) {
         defaultViewModelProviderFactory     
    }
Dievskiy
  • 199
  • 3
  • 4
  • 2
    For DaggerHilt: `private val viewModel: MainViewModel by navGraphViewModels(R.id.my_nav) { defaultViewModelProviderFactory }` – SpiralDev Dec 02 '20 at 09:55
5

just use navigation component version 2.4.0-alpha01 or above

Herlian Zhang
  • 67
  • 1
  • 1
1

Update: Using last version of fragment navigation component, handle fragment states itself. see this sample

Old:

class BaseViewModel : ViewModel() {

    val bundleFromFragment = MutableLiveData<Bundle>()
}


class HomeViewModel : BaseViewModel () {

   ... HomeViewModel logic
}

inside home fragment (tab of bottom navigation)

private var viewModel: HomeViewModel by viewModels()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel.bundleFromFragment.observe(viewLifecycleOwner, Observer {
      
        val message = it.getString("ARGUMENT_MESSAGE", "")
       binding.edtName.text = message
    })
}

override fun onDestroyView() {
    super.onDestroyView()
    viewModel.bundleFromFragment.value = bundleOf(
        "ARGUMENT_MESSAGE" to binding.edtName.text.toString(),
        "SCROLL_POSITION" to binding.scrollable.scrollY
    )
}

You can do this pattern for all fragments inside bottom navigation

Erfan Eghterafi
  • 4,344
  • 1
  • 33
  • 44
-1

Update 2021 use version 2.4.0-alpha05 or above. don't use this answer or other etc.

Rasoul Miri
  • 11,234
  • 1
  • 68
  • 78
-4

This can be achieved using Fragment show/hide logic.

private val bottomFragmentMap = hashMapOf<Int, Fragment>()
bottomFragmentMap[0] = FragmentA.newInstance()
bottomFragmentMap[1] = FragmentB.newInstance()
bottomFragmentMap[2] = FragmentC.newInstance()
bottomFragmentMap[3] = FragmentD.newInstance()


private fun loadFragment(fragmentIndex: Int) {
    val fragmentTransaction = childFragmentManager.beginTransaction()

    val bottomFragment = bottomFragmentMap[fragmentIndex]!!

    // first time case. Add to container
    if (!bottomFragment.isAdded) {
        fragmentTransaction.add(R.id.container, bottomFragment)
    }

    // hide remaining fragments
    for ((key, value) in bottomFragmentMap) {
        if (key == fragmentIndex) {
            fragmentTransaction.show(value)
        } else if (value.isVisible) {
            fragmentTransaction.hide(value)
        }
    }
    fragmentTransaction.commit()
}
Vikas
  • 3
  • 4
-6

Declare fragment on the activity & create fragment instance on onCreate method, then pass the fragment instance in updateFragment method. Create as many fragment instances as required corresponding to bottom navigation listener item id.

Fragment fragmentA;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);

fragmentA = new Fragment();
updateFragment(fragmentA);
}

public void updateFragment(Fragment fragment) {
FragmentTransaction transaction = 
getSupportFragmentManager().beginTransaction();
transaction.add(R.id.layoutFragment, fragment);
transaction.commit();
}

Furthermore be sure you are using android.support.v4.app.Fragment and calling getSupportFragmentManager()

  • 5
    The OP said they were using the [Navigation Component](https://developer.android.com/guide/navigation). You don't directly do FragmentTransactions when you use Navigation. – ianhanniballake May 18 '19 at 05:27
  • Yes I understand but OP wants to switch tabs only. I guess this must be a better approach. – Atirek Pothiwala May 18 '19 at 05:30