Repository navigation
Hero inserts widgets After Dispose #35555
Description
Explanation
Basically, when using Navigator.popUntil, where two associated Hero's are in the top most route and the one below it, where both get popped by the popUntil call, the State.dispose method will be called before the Hero inserts its child again, i.e. the route essentially gets popped "too fast" or the Hero is trying to insert into the tree when it should not.
Steps to reproduce
https://gist.github.com/creativecreatorormaybenot/505fb58446e41fac140c459039d7029a
If you want to reproduce the error with the Gist, make sure to tap open and then push at least twice (and then popUntil to produce the exception).
-
Have two routes on the navigator stack with the same tag.
-
Have an
AnimatedBuilderas the child to theHerowidget in the lower route and pass aValueNotifieras theanimationparameter. -
Use
Navigator.popUntilto pop both of the routes at the same time.
Temporary fix
I had to come up with a workaround, extending ValueNotifier and overriding removeListener that checks if (disposed) return; before calling super.removeListener, where disposed is a bool that is set by overriding dispose, just to get rid of the error message.
If you want to understand my fix firsthand, you can just change the ValueNotifier in the Gist to a FixedValueNotifier.
Flutter logs
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown while finalizing the widget tree:
A ValueNotifier<dynamic> was used after being disposed.
Once you have called dispose() on a ValueNotifier<dynamic>, it can no longer be used.
When the exception was thrown, this was the stack:
#0 ChangeNotifier._debugAssertNotDisposed.<anonymous closure> (package:flutter/src/foundation/change_notifier.dart:105:9)
#1 ChangeNotifier._debugAssertNotDisposed (package:flutter/src/foundation/change_notifier.dart:111:6)
#2 ChangeNotifier.removeListener (package:flutter/src/foundation/change_notifier.dart:166:12)
#3 _AnimatedState.dispose (package:flutter/src/widgets/transitions.dart:137:23)
#4 StatefulElement.unmount (package:flutter/src/widgets/framework.dart:4107:12)
...
Flutter doctor
Flutter: v1.7.12-pre.40
Further notes
Please correct me if my assumption about the Hero inserting at a wrong point in time is wrong here. I feel like I understood the gist of the problem given that my reproducible example is concise and I found a working fix. In that regard everything works as I would it expect it to now, just that this behavior is unwanted (I believe).
It took me many attempts over different Flutter versions to figure this out. I believe that it would be very helpful if the exception message could somehow point to user written code if that is possible.
Activity
creativecreatorormaybenot commentedon Jul 3, 2019
@shihaohong Looking at other open Hero issues, I saw that they had the f:routes and a:animation labels as well.
It seems like that would make sense to me, however, I do not know how the label system works.
LongCatIsLooong commentedon Jul 18, 2019
Hi @creativecreatorormaybenot thank you for the detailed bug report and the distilled repro! The workaround looks good.
The direct cause of this exception is that the first
/pagepushed onto/was disposed before the hero in flight was, after callingpopUntil. The route subtree and the (in flight) hero subtree were both marked for disposal, but they were disposed in the wrong order.Ideally we should guarantee in the framework that in such case the route will be disposed after the
Hero, because theHerois dependent on the route.Hopefully fixing #36220 would also fix this.
creativecreatorormaybenot commentedon Aug 29, 2019
@LongCatIsLooong
Do you know what I can do when I have a
State(StatefulWidget) that has aTickermixin (needs to be in there to stop the ticker in lower routes when there is aHerotransition) and I am using thisStateas thevsyncfor anAnimationControllerin the child widget of aHero?Hierarchy illustrated:
Open routes:
Now, when popping both of the upper routes (they are at the bottom of my illustration, but at the top of the stack) at once using
popUntil, I will necessarily get the following error:{STATE}is the state of what I calledTickingStatefulWidgethere.Let me explain: The
Herotransition keeps theOtherWidgetalive while the route that has theTickingStatefulWidgetis already being popped - so I have no way to dispose theAnimationControllerbefore because theOtherWidgetis still alive in theHerotransition.Is it really bad to leak tickers? Otherwise, this is not so urgent, just very annoying.
LongCatIsLooong commentedon Aug 29, 2019
Just an out-of-context potential workaround, but in your case is it possible, by any chance, to move the
TickerProviderto a differentElementnode, so that it becomes an ancestor of the animation that listens to it (e.g., move theTickerProviderinto the a custom shuttle builder of theHero), as opposed to having aTickerand its listener on different sub trees?As for long term solutions, I have a draft PR that aims to allow the
Heroto live in theElementtree that created it (instead of theOverlayEntrythat hosts theHero). If theHeroand the ticker that controls theHerolives in the same tree I would guess it should work just like any other tickers without a problem.creativecreatorormaybenot commentedon Oct 3, 2019
@LongCatIsLooong I think that it is too complicated to try fixing the issue by e.g. using a custom shuttle builder as #39104 will resolve this anyway as it seems.
I would assume that apart from the exception, there is no problem with having an active ticker while disposing.
darshankawar commentedon Sep 9, 2020
Issue replicable on latest stable (1.20.3).
log
flutter doctor
exaby73 commentedon Nov 16, 2022
I cannot reproduce this issue with
Master (3.6.0-3.0.pre.54). Closing this issue as fixed. If you disagree, please comment with a complete and minimal, reproducible example and I can reopen the issue. Thank you.github-actions commentedon Nov 30, 2022
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.