Zhuinden / simple-stack

[ACTIVE] Simple Stack, a backstack library / navigation framework for simpler navigation and state management (for fragments, views, or whatevers).
Apache License 2.0
1.36k stars 76 forks source link

setHistory with force "recreation" even if Key already exists in backstack #246

Closed uvaysss closed 2 years ago

uvaysss commented 2 years ago

I've read the tutorial and have browsed other issues, but didn't find information how to implement such behavior. Or I just missed it.

I need to setHistory() with a certain Key, but it is already in backstack, so now it just behaves like I would call goBack().

I could just override getFragmentTag() by the Key and set an irrelavant value with default value like 0, and when I need to recreated this Key I would set Random.nextInt() just to distinguish the 2 Key's, but it's a hack.

Am I missing something and there is some reasonable way to get this behavior?

Zhuinden commented 2 years ago

Heyo, well that's a tricky one to answer because there is no way for me to tell if a key "should be different" if it is the same. So currently the only way to "destroy" something is to replace the key with a different key.

Are you sure changes cannot be propagated via for example using a MutableLiveData/BehaviorRelay/etc in a scoped service then using switchMap/switchMap to update the property inside it?

uvaysss commented 2 years ago

Well, the screen I'm returning back to is a multistack screen, the one from your latest sample. I was trying to avoid such changes, but looks like their is no other way to do it.

I've decided to add backstack.jumpToRoot() in FragmentStackHostFragment so that by going this way back, all stacks will have only the initial key in the stack. But it didn't work the way I wanted, I guess it's because the StateChanger is attach only on 1 stack at a time. So Added attach and detach logic for the stacks that aren't currently active.

if (stackHost.isActiveForBack) {
    stackHost.backstack.jumpToRoot()
} else {
    stackHost.backstack.reattachStateChanger()
    stackHost.backstack.jumpToRoot()
    stackHost.backstack.detachStateChanger()
}

But I'va faced some other issues. It's not really in scope of this issue, but maybe you could give me a hint.

On this multistack screen I need to change the tab and go to a specific screen by an action. I implemented it also through FragmentStackHostFragment calling there setHistory(), but I'm getting some weird behavior. After the setHistory() when I navigate back in this localStack from that specific screen, the initial screen of this local stack disappears.

What I mean by disappears. In the StateChanger for start and stop I'm using show and hide and when this initial screen of the localStack disappears it is not visible, but the ui is clickable. And also should mention that this initial screen key is always the same, it never changes, it has no arguments (except placeholder).

Zhuinden commented 2 years ago

There is technically one way to destroy/recreate the history, which is to, well, set a new history that does not contain the keys. For example, using backstack.setHistory(History.of(SomeResultScreen()), StateChange.REPLACE) and then backstack.setHistory(History.of(MainScreen()), StateChange.REPLACE) would work.

However, instead of that, I personally have been using https://github.com/Zhuinden/live-event (or https://github.com/Zhuinden/event-emitter) to communicate one-off events to the previous screen, which is how I changed the tab on the main screen to whatever I wanted (when the screen came back in front).

but I'm getting some weird behavior. After the setHistory() when I navigate back in this localStack from that specific screen, the initial screen of this local stack disappears.

What I mean by disappears. In the StateChanger for start and stop I'm using show and hide and when this initial screen of the localStack disappears it is not visible, but the ui is clickable. And also should mention that this initial screen key is always the same, it never changes, it has no arguments (except placeholder).

At first glance, the only time I've seen fragments "disappear" like this is when the FragmentManager used for child fragments was NOT actually the childFragmentManager even though it should have been.

Zhuinden commented 2 years ago

Has any of the above input helped?

uvaysss commented 2 years ago

Sorry for the delay, I was trying to figure out what I might doing wrong.

About the case with "disappearing". I'm using the library for this navigation also, so there cannot be some issue with the wrong fragment manager. But it looks like it happens when I use show/hide, because I didn't catch this behavior with attach/detach. But that's not an option for me. Also, I did look into the multistack sample again and realised that you are using there for the key a simple class , not data class. And if I do not ovveride the hashCode() it works with no problem with show/hide.

Is there a reason why you didn't use there data class?

Zhuinden commented 2 years ago

You use show/hide like this right?

https://github.com/Zhuinden/simple-stack/blob/2f2419a8c0cc0d959f34a425cf95e5c0a284eb29/samples/shared-element-samples/simple-stack-example-sharedelement-fragment/src/main/java/com/example/fragmenttransitions/core/navigation/SharedElementFragmentStateChanger.java#L42-L55

Is there a reason why you didn't use there data class?

I used non-data class in sample because the defaults use key.getClass().getName(); as the fragment tag, and that's because if you forget the data class using key.toString() otherwise then you get very shady process death bugs. So this is a safer default.

In my own projects I never forget data class so I use data class SomeKey(private val noArgsPlaceHolder: String = ""): FragmentKey() and then redefine getFragmentTag() to use toString()

uvaysss commented 2 years ago

You use show/hide like this right?

Yes.

I used non-data class in sample because the defaults use key.getClass().getName(); as the fragment tag, and that's because if you forget the data class using key.toString() otherwise then you get very shady process death bugs. So this is a safer default. In my own projects I never forget data class so I use data class SomeKey(private val noArgsPlaceHolder: String = ""): FragmentKey() and then redefine getFragmentTag() to use toString()

Yes, I think I did catch something like that.

I was wondering, what are you using in your project? show/hide or attach/detach?

Zhuinden commented 2 years ago

I've used both depending on requirements, haven't ever seen disappearing fragments when the fragment tag and the fragment manager were OK. 🤔

I haven't figured out what could be happening here yet.

uvaysss commented 2 years ago

I think I got it. The problem is here the animation.

To be sure, I will try invalidate caches and restart, because this gradle build cache sometimes makes me go crazy!

uvaysss commented 2 years ago

Yep, the animation is the problem.

I disabled completely the animation and now it's working fine with show/hide. Before I was using fade animation for all kind transitions, I guess thats why the fragment was there, but invisible.

Zhuinden commented 2 years ago

Cross-fade should not break, even with show-hide 🤔

uvaysss commented 2 years ago

If ONLY slide animation is used then it works fine too.

Zhuinden commented 2 years ago

Oh, interesting. Are you using setTransition(TRANSIT_FADE), or setCustomAnimations for the crossfade?

If you use the other variant, can you try the other variant to see if it helps? Sounds like fragmentManager is conflicting with itself. I wonder if this is a Fragment 2.3.x bug...

uvaysss commented 2 years ago

Looks likes I was wrong about the type of animation, that isn't the problem. Also I don't see any difference between setTransition() and setCustomAnimations().

It depends on which combination of animations I use onForwardNavigation(), onBackwardNavigation(), onReplaceNavigation(). The default animation of DefaultFragmentStateChanger works correct. But If I mix some animations then I get weird behavior.

For example, if I use fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) for onForwardNavigation() and onBackwardNavigation(), but for onReplaceNavigation() use fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) then on the root fragment of a stack where previously I did get a disappeared screen, I do get I a little bit scaled screen, like it did stop on some step of animation when it was doing the "open" animation.

uvaysss commented 2 years ago

But if I use for all cases of navigation only TRANSIT_FRAGMENT_FADE or TRANSIT_FRAGMENT_OPEN than it works correct.

Zhuinden commented 2 years ago

I'm totally out of ideas, mostly because I vaguely remember having some issues with FragmentManager alpha transitions (FragmentTransaction bug, nothing to do with Simple-Stack) and flickering, and having to manually say view.setVisibility(View.VISIBLE) at some point, but this used to be fixed as it was back in support-v27.

uvaysss commented 2 years ago

I think we can close the issue, because the main problem I had was handled. And the part about the animation issue, well, I was planing to remove the animation anyway. But still is a strange behavior.

Thank you for such quick response and help!