Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigation, Saving fragment state #530

Closed
ibrahimAlii opened this issue Jan 4, 2019 · 93 comments
Closed

Navigation, Saving fragment state #530

ibrahimAlii opened this issue Jan 4, 2019 · 93 comments

Comments

@ibrahimAlii
Copy link

ibrahimAlii commented Jan 4, 2019

Hi, I'm was trying to save fragment state, Is there a suggested way to use while using Navigation component?

It's like open new fragment without lose the previous fragment states.

@ghost
Copy link

ghost commented Jan 6, 2019

Do you really need to save fragment state while using Navigation?

@Slim1991
Copy link

Yes, I do. It's like using FragmentTransaction.show()/hide()

@lymons
Copy link

lymons commented Jan 16, 2019

Yes, I do. It's like using FragmentTransaction.show()/hide()

You are right.
Using FragmentTransaction.add()/show()/hide() by override the method navigate() of FragmentNavigator

@Slim1991
Copy link

You are right.
Using FragmentTransaction.add()/show()/hide() by override the method navigate() of FragmentNavigator

Can u tell more detail about it? I can imagine about it, but for working, not yet :D

@ghost
Copy link

ghost commented Jan 16, 2019

Stop imagining things! haha

@lymons
Copy link

lymons commented Jan 24, 2019

You are right.
Using FragmentTransaction.add()/show()/hide() by override the method navigate() of FragmentNavigator

Can u tell more detail about it? I can imagine about it, but for working, not yet :D

you can see this repo.
https://github.com/STAR-ZERO/navigation-keep-fragment-sample

@Slim1991
Copy link

Slim1991 commented Jan 28, 2019

You are right.
Using FragmentTransaction.add()/show()/hide() by override the method navigate() of FragmentNavigator

Can u tell more detail about it? I can imagine about it, but for working, not yet :D

you can see this repo.
https://github.com/STAR-ZERO/navigation-keep-fragment-sample

In the case of that repo, fragments are not destroyed when navigating but they are recreate view all the time by onCreateView(). I want when I pop fragments from backstack, they are not recreate view like I do with the FragmentTransaction

@lymons
Copy link

lymons commented Jan 28, 2019

You are right.
Using FragmentTransaction.add()/show()/hide() by override the method navigate() of FragmentNavigator

Can u tell more detail about it? I can imagine about it, but for working, not yet :D

you can see this repo.
https://github.com/STAR-ZERO/navigation-keep-fragment-sample

In the case of that repo, fragments are not destroyed when navigating but they are recreate view all the time by onCreateView(). I want when I pop fragments from backstack, they are not recreate view like I do with the FragmentTransaction

The reason fragments are recreated is using attach/detach in navigate method of KeepStateNavigator class, so you should know that the correct is use show/hide instead of attach/detach.

@Slim1991
Copy link

You are right.
Using FragmentTransaction.add()/show()/hide() by override the method navigate() of FragmentNavigator

Can u tell more detail about it? I can imagine about it, but for working, not yet :D

you can see this repo.
https://github.com/STAR-ZERO/navigation-keep-fragment-sample

In the case of that repo, fragments are not destroyed when navigating but they are recreate view all the time by onCreateView(). I want when I pop fragments from backstack, they are not recreate view like I do with the FragmentTransaction

The reason fragments are recreated is using attach/detach in navigate method of KeepStateNavigator class, so you should know that the correct is use show/hide instead of attach/detach.

yeah, yeah, I know. haha. But the point is I haven't succeed yet when I replace attach/detach by show/hide in the KeepStateNavigator class :D

@dishantkawatra
Copy link

Any One solved the issue please let me know

@ghost
Copy link

ghost commented Feb 18, 2019

Did you guys manage to solve this?

@linhphan0108
Copy link

keep waiting lol

@donniesky
Copy link

keep waiting too !!

@ianhanniballake
Copy link
Contributor

Please file feature requests on the Navigation library on the issue tracker with your use case.

@Osaigbovo
Copy link

Still waiting...

@ibrahimAlii
Copy link
Author

Still waiting...

https://issuetracker.google.com/issues/127932815

@liveHarshit
Copy link

You can use: https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample

@JonathanImperato
Copy link

Following...+1

@Osaigbovo
Copy link

The fix is buggy and not good enough. The legion is waiting Ian...

@wahdatjan
Copy link

I use bottom navigation which has three tabs A B and C
when i go from tab A to Tab B then i revisited to tab A it recreates the tab A
i want to save fragment state dont want to recreate the fragment A.

@SogoGolf
Copy link

same. this is crazy, cmon google! way way easier on iOS.

@zedlabs
Copy link

zedlabs commented Aug 15, 2019

the only probable solution right now is to not use Navigation unfortunately

@JonathanImperato
Copy link

JonathanImperato commented Aug 15, 2019

Currently the only working solution I found is to store the different fragment states in a shared viewmodel. Fortunately the navigation components helps us in this way with a custom Library built method called navGraphViewModel.

@Robotecom
Copy link

could you share a piece of code of your working solution @JonathanImperato ?

@JonathanImperato
Copy link

JonathanImperato commented Aug 21, 2019

could you share a piece of code of your working solution @JonathanImperato ?

Look at this.
Just use it like a normal ViewModel that is global to every fragment available in the navigation graph.

@StevenWT
Copy link

I want to save fragments state. Fragments according to official used the replace() method that doesn't save fragment state, but we expect to use the add()/hide() methods shows fragments to save fragment state. My solution is to not use the official implementation implementation "androidx.navigation:navigation-fragment:$nav_version", directly put the library's code in your project, and then modify the replace() method to hide()/add() method.

  • androidx.navigation.fragment.FragmentNavigator.java
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        
//      ft.replace(mContainerId, frag);

//      change to  

        if(mFragmentManager.getFragments().size()>0){
            ft.hide(mFragmentManager.getFragments().get(mFragmentManager.getFragments().size()-1));
            ft.add(mContainerId, frag);
        }else {
            ft.replace(mContainerId, frag);
        }
        
}

I have a demo you can run.
fragment-demo

@iRYO400
Copy link

iRYO400 commented Sep 12, 2019

/**
 * Manages the various graphs needed for a [BottomNavigationView].
 *
 * This sample is a workaround until the Navigation Component supports multiple back stacks.
 */

https://github.com/googlesamples/android-architecture-components/blob/master/NavigationAdvancedSample/app/src/main/java/com/example/android/navigationadvancedsample/NavigationExtensions.kt

Sounds not cool :/

@iRYO400
Copy link

iRYO400 commented Sep 12, 2019

What about NavigationView, nothing is here

@Disx
Copy link

Disx commented May 8, 2020

как так? непонятно.... :/

@lexasok
Copy link

lexasok commented May 8, 2020

Why this is locked? Seriously using Navigation Component is more painful than I handle the fragment transaction myself

Ye bro))

@lexasok
Copy link

lexasok commented May 8, 2020

как так? непонятно.... :/

Реальная дитч, ваще удобности не заметил, только что сама сетапится с некоторыми компонентами, но управлять не возможно епта))

@DDihanov
Copy link

Return transitions of shared elements work fine for fragments on the back stack, as seen in the GithubBrowserSample. If you've ever found a case where you need to keep the View around because you're losing state, you've just covered up a solvable problem and you should actually just fix the root cause of your state loss.

That's entirely separate from multiple back stacks and saving the state of fragments not on the back stack, which is, of course, being worked on.

You've never had huge recycler views with hundreds of items it seems. Re-inflating everything is expensive, and the recycled view pool is kept in the recycler view which gets destroyed.

@markHervagault
Copy link

Anyone have a real solution?

@BismeetSingh
Copy link

BismeetSingh commented Jun 2, 2020

This is still buggy. I am going back to my dear Fragment Manager and Transactions.

@aminPial
Copy link

aminPial commented Jun 8, 2020

If someone is working with Bottom Navigation, and try to save state(well in fact just load all fragments on create of host activity and hide/show) then, you can follow this:

https://github.com/bukunmialuko/BottomNavViewWithFragments

We are using this in production. Here are some issues with this,

  1. It loads all fragment + their actions when the host activity starts (which might make the activity loading time a bit slow, if many works is being done on UI thread, so if you use this then keep your action codes in non UI thread and update with livedata may be.)
  2. If you have a dynamic content based fragment, say for example a timeline feed that fetches data from an API, unless you don't have livedata thingy in your codebase then this solution doesn't work, As it doesn't support recreation. (It depends on your requirement, but you can customize it!)

@abdurahmanadilovic
Copy link

Return transitions of shared elements work fine for fragments on the back stack, as seen in the GithubBrowserSample. If you've ever found a case where you need to keep the View around because you're losing state, you've just covered up a solvable problem and you should actually just fix the root cause of your state loss.

That's entirely separate from multiple back stacks and saving the state of fragments not on the back stack, which is, of course, being worked on.

@ianhanniballake I would like to keep a view state + its view model in scenarios where there is an infinite scroll recycler view that opens up a details view, it makes much more sense to just push a new fragment, when a user wants to see details view, without losing recycler view's state (scroll position). Manually storing the recyclerview's state is tricky because for any navigation that is not a details view I don't want to keep the recyclerview's state, but for details navigation I do.

@Slim1991
Copy link

Slim1991 commented Jun 9, 2020

After 1.5 years, this still can not be done. LOL
I still have to use FragmentTransaction for complex lifecycle's applications. The FT is easier to control but cost more time for development than the navigation's idea

@congdanh1608
Copy link

waiting...

@lucasmontano
Copy link

lucasmontano commented Jun 17, 2020

Please file feature requests on the Navigation library on the issue tracker with your use case.

https://issuetracker.google.com/issues/127932815

⚠️ Attention: don't comment here your frustrations (I'm also looking forward to having this issue closed for real). Instead, do what @ianhanniballake recommended 👍

🔥 They won’t fix this issue because it's an Intended Behavior. Another improvement ticket was created and accepted: https://issuetracker.google.com/issues/80029773

@woraphol-j
Copy link

I suggest that google developers add this limitation at the beginning of their navigation component document to warn those who want to use this library (in a hope that it will make their life easier) to realize this behaviour because it is a real showstopper for most people (come on, this use case where a user navigate off the long-list page and come back is extremely common). I know there is an ugly workaround for this (saving state) but it is obviously inefficient and hard.

The thing is, If I had known this limitation, I would have surely not invested a lot of my time implementing my navigation based on this library because ,as it stands, sorry to say this but it is useless and it is more trouble that it's worth.

Google should have hired the developers with good mindset who intend to do things that makes other developer's lives easier, especially considering this is the Androidx library whose intention is to encourage developers to stay on Android development (let's face it, Android development is hard, people are moving away, and that's why your team was formed in the first place to make Android development more attractive and here's what happens...).

Sorry to be a bit offensive but I just spoke my mind and I hope it gets heard.

@thiagoluis88
Copy link

Same problem here, with version 2.3.0 that I just updated today. Anyone with some workaround to bypass this?

@jorgevalbuena56
Copy link

I used findNavController().popBackStack() to show my previous fragment which is a list and it keeps the state intact.

@mecoFarid
Copy link

Do you really need to save fragment state while using Navigation?

Do you like seeing your screen flick when you navigate back as a user?! No, right. So yeah there gotta be a better solution than just reinflating the whole mess.

@r020477888
Copy link

r020477888 commented Aug 25, 2020

When your project has been developed to close to completion, and then you find that it is so difficult to implement such a simple requirement, it is a disaster

@thiagoluis88
Copy link

I have deleted all Navigation Graph library from my project and I did as always: Using Activities.
Now my project is perfect with no problems!

@joseAugustoCR
Copy link

I regret the day I tried the navigation component. Still have about 5 big projects running with navigation, which I am reluctant to refactor, but you give me no choice. After 1.5 year and a lot a bugs and workarounds, I'm going back to activities...

@vapstor
Copy link

vapstor commented Oct 6, 2020

that kotlin workaround file (until navigation 2.4.0) has bugs when implemented on java codes, at runtime.
the fragments overlap itself:
https://imgur.com/a/DcsKqPr

why this is closed?

@P1NG2WIN
Copy link

I regret the day I tried the navigation component. Still have about 5 big projects running with navigation, which I am reluctant to refactor, but you give me no choice. After 1.5 year and a lot a bugs and workarounds, I'm going back to activities...

i would recommend you to use single activity pattern and cicerony library for navigation. You can watch about single activity in cicerony's author presentations

@sixangle
Copy link

sixangle commented Jan 7, 2021

still waiting... A favorable way should be that when doing the navigation, if I pass a boolean-type parameter, for example "resuse", the fragment should save its state (by default not save).

@muradavud
Copy link

I found a very easy way which can make only your home fragment constant through whole activity using back press. But it only works if you set up your navigation to have a home fragment, and every other fragment goes to home fragment on the back press. It works well for GoogleMaps, because it takes a second to load them which makes the whole thing ugly.
Basically you just define android:OnClick in menu XML (https://developer.android.com/guide/topics/resources/menu-resource) and then put onBackPressed() in the function that you defined there.
It is a very ugly solution and will only work for if you don't put something else on the stack while pressing the home button, but maybe this will be useful for someone.

@yassirlaaouissi
Copy link

Hey everyone that is still encountering problems. I have found a workaround which is most likely a classic duct-tape fix. Fiddled around with some .show and switching within the navigation buttons I have located in my project.

Have a look at https://github.com/yassirlaaouissi/IKPMD/blob/master/app/src/main/java/com/example/ikpmd_periode2/MainActivity.java

A small overview of what I did:

   BottomNavigationView botnav = (BottomNavigationView) findViewById(R.id.nav_view);
        GridLayout mainGrid = (GridLayout) findViewById(R.id.mainGrid);
        botnav.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.navigation_home:
                        System.out.println("Yes a mattie");
                        fm.beginTransaction().replace(R.id.nav_host_fragment, e, "1").show(e).commit();
                        mainGrid.setClickable(true);
                        mainGrid.setVisibility(View.VISIBLE);

                        return true;
                    case R.id.navigation_dashboard:
                        System.out.println("Yes a mattie");
                        fm.beginTransaction().replace(R.id.nav_host_fragment, b, "2").show(b).commit();
                        mainGrid.setClickable(false);
                        mainGrid.setVisibility(View.INVISIBLE);

                        return true;
                    case R.id.navigation_graph:
                        System.out.println("Yes a mattie");
                        fm.beginTransaction().replace(R.id.nav_host_fragment, f, "3").show(f).commit();
                        mainGrid.setClickable(false);
                        mainGrid.setVisibility(View.INVISIBLE);


                        return true;
                }
                return false;
            }
        });

    }

@kashpur
Copy link

kashpur commented Jan 25, 2021

You can override FragmentFactory class like this:

public class CustomFragmentFactory extends FragmentFactory {

    private Map<Class, Fragment> fragments = new HashMap<>();

    @NonNull
    @Override
    public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
        try {
            Class<? extends Fragment> cls = loadFragmentClass(classLoader, className);
            if (fragments.containsKey(cls)) {
                return fragments.get(cls);
            }
            Fragment fragment = cls.getConstructor().newInstance();
            if (fragment instanceof SingleFragment) {
                fragments.put(cls, fragment);
            }
            return fragment;
        } catch (java.lang.InstantiationException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (IllegalAccessException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (NoSuchMethodException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": could not find Fragment constructor", e);
        } catch (InvocationTargetException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": calling Fragment constructor caused an exception", e);
        }
    }
}

And apply factory in main activity:

getSupportFragmentManager().setFragmentFactory(new CustomFragmentFactory());

You can implements your own interface SingleFragment to fragments to adding only its to HashMap and other will be work like before.

@Xing1P
Copy link

Xing1P commented Feb 9, 2021

I have deleted all Navigation Graph library from my project and I did as always: Using Activities.
Now my project is perfect with no problems!

Okay you win bro

@Daphne-CoffeeIT
Copy link

Daphne-CoffeeIT commented Feb 24, 2021

Any updates on this, preferably from the Android team? The hacks and workarounds aren't working for me and I'm thinking of reverting everything. But it's such a pain to do that and use 'old navigation' again when this solution is so elegant and perfect, except for the fact that Fragment states aren't saved.

Still a fan of @cumtsx idea of adding a Boolean type attribute like reuse or saveState on the Fragment element in a navigation XML, which is false by default, but saves the state of the Fragment when set to true.

@andrewpros
Copy link

The new navigation library is a mess, they have destroyed the standard way the things were working, you expect to load a fragment, you expect if u go to another fragment and go back, to see the prev fragment as it was when u left it, NOT being created again from scratch every single time u change to another fragment or at least have easy control of it.

But not with the new nav arch, there is always an issue and workaround for everything you would want to do or another library you are proposed to use cuz google want u to.

Issues with this like state, using the same fragments in nav or arguments. There is even not a single sample about creating navigation programmatically, they except devs that have apps with like couple of dozens menu items to do it manually or what, the documentation is lacking a lot. U can do everything, but not with the help of google.

Is this some kind of hidden forced android app optimization so they use less memory at the cost for recreating everything again and again??? As this is how it looks, complete madness.

Looks like it is way way better to just stick with managing everything yourself with custom code, it will take you longer at first, but after you have a working solution just as it was before this atrocious new component it will just work as expected and u will have 100% control over it instead of wasting time looking for some workarounds.

@ianhanniballake
Copy link
Contributor

This issue was mentioned in the Compose reddit AMA, but it appears that comment never made it back here, so I'll include the reply in full here to the question "must I really re-inflate the view EVERY time I go back to my fragment having to manually keep the state of everything in check, or can I just retain the view and save all the hassle? Is retaining fragment views okay, is it REALLY a memory leak? Why must the nav component limit developer options by not allowing them to retain the view and use the simple hide()/show()(instead of replace()) methods of the Fragment API instead of having to re-inflate the view every time(inflations are a heavy operation).":

First of all: The architecture-components-samples issue tracker is for issues with the samples, not with the libraries themselves. For that, you must use the issue tracker.

Fragments give you all of the tools necessary to keep your state. You are responsible for keeping your state across configuration changes and process death and recreation. If you’re already handling those two cases, then you won’t lose any state when using Navigation as is. If you find a case where you are handling those two cases but are still losing your state when on the Fragment back stack, please file an issue against Fragments. This has nothing to do with Navigation and everything to do with actually saving your state correctly - something you must do anyways. So far, no one has filed such a state loss bug since I asked for one back in March 2019 (please file a new bug if you do have a case though!).

So as long as we’re not talking about saving state, let’s talk about the leaking side of things. When a Fragment reaches onDestroyView(), the entire Fragment system drops all references to the View you created in onCreateView(), only holding onto the Bundle of saved state needed to restore your View back to the same state when/if it is recreated. At this point, if you are holding onto a reference to that View, you are 100%, absolutely consuming more memory than you need to. Holding onto memory when it isn’t needed is exactly the definition of a memory leak.

That being said, there’s plenty of cache implementation specifically designed to avoid repeatedly doing heavyweight operations by using more memory than the minimum needed. Picasso / your image loading framework of choice most certainly has an in memory cache for Bitmaps to avoid reloading from disk/network. RecyclerView has a RecycledViewPool to avoid unnecessary view inflations. Gmail displays emails in a WebView and has a cache of WebView instances that it allocates to each Fragment as needed precisely because they are expensive to create.

In all of those cases, it may be that the only thing that is holding onto that reference in memory is the cache. Is that a leak? Clearly, no. You’re holding onto those references in a responsible way for a legitimate purpose. If you want to write your own responsible cache of only heavyweight views that have a distinct destruction lifetime and cache limit to prevent an OutOfMemoryException that is entirely independent from Fragments and Navigation, go for it. That is exactly the type of system you should build for trading off memory usage and performance.

So to summarize:

  1. It is never the right approach when it comes to saving and restoring state properly. Holding onto a reference to your View after onDestroyView() always, always results in additional memory usage that would otherwise be freed.
  1. Using the Fragment back stack and replace() has nothing to do with inflation and everything to do with going through the proper Lifecycle changes. If your inflation is a heavy operation, you should treat it like any other performance optimization: add benchmarking, targeted optimizations, caching, etc.

I'll add to this that since then, implementation work on multiple back stacks has begun:

Implementation work on multiple back stacks (to support it at both the Fragment and Navigation level) has started. Feel free to read the update on the official issue and star the issue to receive updates. https://issuetracker.google.com/issues/80029773#comment94

TL/DR: the blocking work of the rework of the internals of FragmentManager (https://medium.com/androiddevelopers/fragments-rebuilding-the-internals-61913f8bf48e) is wrapping up with Fragment 1.3, the API design work for multiple back stacks is in a good spot, and we know what needs to be done to get this out the door.

This multiple back stack work is exactly what is needed to save and restore the state of fragments when swapping between bottom navigation items, for instance.

@android android locked as off-topic and limited conversation to collaborators Mar 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests