Skip to content

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

  1. Have two routes on the navigator stack with the same tag.

  2. Have an AnimatedBuilder as the child to the Hero widget in the lower route and pass a ValueNotifier as the animation parameter.

  3. Use Navigator.popUntil to 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

  1. added
    frameworkflutter/packages/flutter repository. See also f: labels.
    on Jul 3, 2019
  2. creativecreatorormaybenot commented on Jul 3, 2019

    @creativecreatorormaybenot
    ContributorAuthor

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

  3. LongCatIsLooong commented on Jul 18, 2019

    @LongCatIsLooong
    Contributor

    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 /page pushed onto / was disposed before the hero in flight was, after calling popUntil. 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 the Hero is dependent on the route.

    Hopefully fixing #36220 would also fix this.

  4. creativecreatorormaybenot commented on Aug 29, 2019

    @creativecreatorormaybenot
    ContributorAuthor

    @LongCatIsLooong
    Do you know what I can do when I have a State (StatefulWidget) that has a Ticker mixin (needs to be in there to stop the ticker in lower routes when there is a Hero transition) and I am using this State as the vsync for an AnimationController in the child widget of a Hero?

    Hierarchy illustrated:

    TickingStatefulWidget with TickerProviderStateMixin
        |
        v
       Hero
        |
        v
    OtherWidget <- contains AnimationController that uses vsync: State object of the top TickingStatefulWidget
    

    Open 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 above
    

    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:

    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 called TickingStatefulWidget here.

    Let me explain: The Hero transition keeps the OtherWidget alive while the route that has the TickingStatefulWidget is already being popped - so I have no way to dispose the AnimationController before because the OtherWidget is still alive in the Hero transition.

    Is it really bad to leak tickers? Otherwise, this is not so urgent, just very annoying.

  5. LongCatIsLooong commented on Aug 29, 2019

    @LongCatIsLooong
    Contributor

    Just an out-of-context potential workaround, but in your case is it possible, by any chance, to move the TickerProvider to a different Element node, so that it becomes an ancestor of the animation that listens to it (e.g., move the TickerProvider into the a custom shuttle builder of the Hero), as opposed to having a Ticker and its listener on different sub trees?

    As for long term solutions, I have a draft PR that aims to allow the Hero to live in the Element tree that created it (instead of the OverlayEntry that hosts the Hero). If the Hero and the ticker that controls the Hero lives in the same tree I would guess it should work just like any other tickers without a problem.

  6. creativecreatorormaybenot commented on Oct 3, 2019

    @creativecreatorormaybenot
    ContributorAuthor

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

  7. removed their assignment
    on Jun 19, 2020
  8. darshankawar commented on Sep 9, 2020

    @darshankawar
    Member

    Issue 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)
    
  9. exaby73 commented on Nov 16, 2022

    @exaby73
    Member

    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.

  10. added
    r: fixedIssue is closed as already fixed in a newer version
    on Nov 16, 2022
  11. github-actions commented on Nov 30, 2022

    @github-actions

    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 -v and a minimal reproduction of the issue.

  12. locked as resolved and limited conversation to collaborators on Nov 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    a: animationAnimation APIsf: routesNavigator, Router, and related APIs.found in release: 1.20Found to occur in 1.20frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onr: fixedIssue is closed as already fixed in a newer version

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions