android / architecture-components-samples

Samples for Android Architecture Components.

Home Page:https://d.android.com/arch

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Navigation, Saving fragment state

ibrahimAlii opened this issue · comments

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.

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

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

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

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

Stop imagining things! haha

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

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

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.

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

Any One solved the issue please let me know

Did you guys manage to solve this?

keep waiting lol

keep waiting too !!

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

Still waiting...

Following...+1

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

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.

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

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

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.

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

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.

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

/**
 * 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 :/

What about NavigationView, nothing is here

Did Google solve the retain instance problem?

We are still waiting! :D

We are still waiting! :D

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

Sound good, we can solve the problem this way.
But copy all related source codes then modify one line of codes is not a good behavior. I'm also trying to extends FragmentNavigator but can not change super()'s logic. Google should open it for us can customize

We are still waiting! :|

We are still waiting! :|

commented

We are still waiting! :|

i guess we are still waiting :)

We are still waiting! :|

We are still waiting! :|

We are still waiting! :|

I found a work around, the key is to create your own NavHostFragment and custom fragment navigator and set it inside your custom NavHostFragment.

i used code from from nav component library and made edit for adding and hiding fragments.

https://gist.github.com/farhanahmed95/a33aaabdb4fb602d515005c34e864ffe

Reuse the View can fix the problem in most cases:

abstract class DataBindingFragment<T : ViewDataBinding> : Fragment() {
    lateinit var binding: T
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        if (!::binding.isInitialized) {
            binding = onCreateDataBinding(
                inflater,
                container,
                savedInstanceState
            )
        }
        return binding.root
    }

    abstract fun onCreateDataBinding(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): T
}

Any solution or optimized work around??
We are still waiting !!

come on!!! I don't want to send the same request every time the user tapped the bottom navigation bar!!! solve it please! every time when we tap the bottom navigation view fragment item it creates the whole fragment again! what is the point??? you make us regret to use new components!!!

I update the navigation extensions class and the state problem has fixed:

https://gist.github.com/theozgurr/506895a4f0f820a6c60e97eb6026198b

anybody out there?! google!

commented

Why is this closed it's not fixed ffs? What a joke

Yeah... the navigation component has been my least favorite androidx library. I am guessing they are working on a complete revamp judging by the silence so far.

Should have never been moved out of beta given the limitations (no fragment reuse being among them).

I update the navigation extensions class and the state problem has fixed:

https://gist.github.com/theozgurr/506895a4f0f820a6c60e97eb6026198b

It is not working with <dialog tags, it is crashing :(

I update the navigation extensions class and the state problem has fixed:
https://gist.github.com/theozgurr/506895a4f0f820a6c60e97eb6026198b

It is not working with <dialog tags, it is crashing :(

did you mean dialog fragments?

We are still waiting!

I update the navigation extensions class and the state problem has fixed:
https://gist.github.com/theozgurr/506895a4f0f820a6c60e97eb6026198b

It is not working with <dialog tags, it is crashing :(

did you mean dialog fragments?

Yes, on dialog fragment
with <dialog tag in navigation

I update the navigation extensions class and the state problem has fixed:
https://gist.github.com/theozgurr/506895a4f0f820a6c60e97eb6026198b

It is not working with <dialog tags, it is crashing :(

did you mean dialog fragments?

Yes, on dialog fragment
with <dialog tag in navigation

I'm using dialog fragment in the one of the navigation component nav graphs as they are based on each fragment and it's working fine.
You have each separate navigation graph you can use dialog fragment in them. Maybe if you can share the crash logs I can help you.

Any solution ?

We are still wating!!

waiting yeah! @ianhanniballake just gave us a dump on this and closed the issue.
Posted a Advanced sample that just doesn't work at all!

What a disappointment for an android developer! I feel sad about this. Navigation Controller should have a way of managing the backstack with an option to use it! Either for complete activity level, or each navigation.xml level.

waiting...........................................

Why is this closed it's not fixed ffs? What a joke

we got poop in the reply and doors slammed shut!! It's SAD bud! And I didn't expect this when choose to keep my app's core development, solely on architecture components.

Are there any updates?

I update the navigation extensions class and the state problem has fixed:

https://gist.github.com/theozgurr/506895a4f0f820a6c60e97eb6026198b

Thank you! @theozgurr

Also, the return transition of shared element doesn't work properly since the view is recreated. I had to save the view in a variable, which is a really bad practice.

Also, the return transition of shared element doesn't work properly since the view is recreated. I had to save the view in a variable, which is a really bad practice.

Actually! +1

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.

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

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

This statement is true for all google made libraries, as a whole android is painful to develop. one have to always find workarounds and nothing is straight forward.

writing your own code for fragment transaction is better approach.

commented

Я тоже жду!

commented

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

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

Ye bro))

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

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

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.

Anyone have a real solution?

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

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!)

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.

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

waiting...

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

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.

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

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

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.

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

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!

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...

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?

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

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).

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.

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;
            }
        });

    }

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.

commented

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

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.

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.

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.