Repository navigation
Hero inserts widgets After Dispose #35555
Description
Activity
- addedframeworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.
on Jul 3, 2019 creativecreatorormaybenot commented
on Jul 3, 2019 ContributorAuthorMore actions@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.Reacted by Shi-Hao Hong- addeda: animationAnimation APIsAnimation APIsf: routesNavigator, Router, and related APIs.Navigator, Router, and related APIs.
on Jul 3, 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.
Reacted by creativecreatorormaybenotcreativecreatorormaybenot commented
on Aug 29, 2019 ContributorAuthorMore actions@LongCatIsLooong
Do you know what I can do when I have aState(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:
TickingStatefulWidget with TickerProviderStateMixin | v Hero | v OtherWidget <- contains AnimationController that uses vsync: State object of the top TickingStatefulWidgetOpen routes:
w/e | v The route that has the widgets illustrated above | v Route that has a Hero with the same tag as the Hero aboveNow, 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:The following exception was thrown while finalizing the widget tree: {STATE}#1e742(tickers: tracking 1 ticker) was disposed with an active Ticker. {STATE} created a Ticker via its TickerProviderStateMixin, but at the time dispose() was called on the mixin, that Ticker was still active. All Tickers must be disposed before calling super.dispose(). Tickers used by AnimationControllers should be disposed by calling dispose() on the AnimationController itself. Otherwise, the ticker will leak. The offending ticker was: _WidgetTicker(created by {STATE}e#1e742(tickers: tracking 1 ticker)) The stack trace when the _WidgetTicker was actually created was: #0 new Ticker.<anonymous closure> (package:flutter/src/scheduler/ticker.dart:64:40) #1 new Ticker (package:flutter/src/scheduler/ticker.dart:66:6) #2 new _WidgetTicker (package:flutter/src/widgets/ticker_provider.dart:225:80) #3 TickerProviderStateMixin.createTicker (package:flutter/src/widgets/ticker_provider.dart:161:34) #4 new AnimationController (package:flutter/src/animation/animation_controller.da #0 TickerProviderStateMixin.dispose.<anonymous closure> (package:flutter/src/widgets/ticker_provider.dart:178:13) #1 TickerProviderStateMixin.dispose (package:flutter/src/widgets/ticker_provider.dart:191:6) #2 {STATE}.dispose{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.
Reacted by Curtis ChangJust 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.Reacted by creativecreatorormaybenotcreativecreatorormaybenot commented
on Oct 3, 2019 ContributorAuthorMore actions@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.Reacted by LongCatIsLooongIssue replicable on latest stable (1.20.3).
log
Another exception was thrown: A ValueNotifier<dynamic> was used after being disposed. ════════ 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:108:9) #1 ChangeNotifier._debugAssertNotDisposed (package:flutter/src/foundation/change_notifier.dart:114:6) #2 ChangeNotifier.removeListener (package:flutter/src/foundation/change_notifier.dart:169:12) #3 _AnimatedState.dispose (package:flutter/src/widgets/transitions.dart:168:23) #4 StatefulElement.unmount (package:flutter/src/widgets/framework.dart:4773:12) ... ════════════════════════════════════════════════════════════════════════════════════════════════════flutter doctor
Flutter 1.20.3 • channel stable • https://github.com/flutter/flutter.git Framework • revision 216dee60c0 (2 days ago) • 2020-09-01 12:24:47 -0700 Engine • revision d1bc06f032 Tools • Dart 2.9.2 Running flutter doctor... Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 1.20.3, on Mac OS X 10.15.2 19C57, locale en-US) [!] Android toolchain - develop for Android devices (Android SDK version 29.0.3) ✗ Android license status unknown. Try re-installing or updating your Android SDK Manager. See https://developer.android.com/studio/#downloads or visit https://flutter.dev/docs/get-started/install/macos#android-setup for detailed instructions. [✓] Xcode - develop for iOS and macOS (Xcode 11.6) [✓] Android Studio (version 3.6) [✓] Connected device (1 available)- addedhas reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onfound in release: 1.20Found to occur in 1.20Found to occur in 1.20
on Sep 9, 2020 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.- addedr: fixedIssue is closed as already fixed in a newer versionIssue is closed as already fixed in a newer version
on Nov 16, 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.- locked as resolved and limited conversation to collaborators
on Nov 30, 2022
Explanation
Basically, when using
Navigator.popUntil, where two associatedHero's are in the top most route and the one below it, where both get popped by thepopUntilcall, theState.disposemethod will be called before theHeroinserts its child again, i.e. the route essentially gets popped "too fast" or theHerois 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
openand thenpushat least twice (and thenpopUntilto 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
ValueNotifierand overridingremoveListenerthat checksif (disposed) return;before callingsuper.removeListener, wheredisposedis aboolthat is set by overridingdispose, just to get rid of the error message.If you want to understand my fix firsthand, you can just change the
ValueNotifierin the Gist to aFixedValueNotifier.Flutter logs
Flutter doctor
Further notes
Please correct me if my assumption about the
Heroinserting 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.