Skip to content

Calling dispose() on OverlayEntry throws an assertion error #102718

Closed

Description

@kaboc

An assertion error is thrown when dispose() is used after remove().

_overlayEntry?.remove();
_overlayEntry?.dispose();

I think this is because _markDirty() is called from remove() in the next frame when dispose() has already been executed.

if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((Duration duration) {
overlay._markDirty();
});
} else {

The error is avoided if dispose() is also delayed to the next frame manually like below, whether it is good or not.

_overlayEntry?.remove();
SchedulerBinding.instance.addPostFrameCallback((_) => _overlayEntry?.dispose());

I guess that Flutter devs probably think they should use dispose() when an OverlayEntry becomes no longer necessary as they usually do when they finish using a ChangeNotifier. The unexpected error has been confusing me. Should dispose() not used? (Is it enough to use only remove()?) If so, I think it should be documented.

Steps to Reproduce

  1. Execute flutter run on the code sample below.
  2. Press the FAB to show an overlay.
  3. Press the FAB again to hide the overlay.

Expected results:

No error.

Actual results:

The last step above throws an error, showing A OverlayEntry was used after being disposed.

Full output
======== Exception caught by widgets library =======================================================
The following assertion was thrown while finalizing the widget tree:
A OverlayEntry was used after being disposed.

Once you have called dispose() on a OverlayEntry, 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:114:9)
#1      ChangeNotifier._debugAssertNotDisposed (package:flutter/src/foundation/change_notifier.dart:120:6)
#2      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:288:12)
#3      OverlayEntry._updateMounted (package:flutter/src/widgets/overlay.dart:130:5)
#4      _OverlayEntryWidgetState.dispose (package:flutter/src/widgets/overlay.dart:200:18)
#5      StatefulElement.unmount (package:flutter/src/widgets/framework.dart:4983:11)
#6      _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1926:13)
#7      ListIterable.forEach (dart:_internal/iterable.dart:39:13)
#8      _InactiveElements._unmountAll (package:flutter/src/widgets/framework.dart:1935:25)
#9      BuildOwner.lockState (package:flutter/src/widgets/framework.dart:2519:15)
#10     BuildOwner.finalizeTree (package:flutter/src/widgets/framework.dart:2932:7)
#11     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:884:19)
#12     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:363:5)
#13     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1144:15)
#14     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1081:9)
#15     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:995:5)
#19     _invoke (dart:ui/hooks.dart:151:10)
#20     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:308:5)
#21     _drawFrame (dart:ui/hooks.dart:115:31)
(elided 3 frames from dart:async)
====================================================================================================
Code sample
import 'package:flutter/material.dart';

void main() => runApp(const App());

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  OverlayEntry? _overlayEntry;

  @override
  void dispose() {
    _hideOverlay();
    super.dispose();
  }

  void _hideOverlay() {
    _overlayEntry?.remove();
    _overlayEntry?.dispose();
    setState(() => _overlayEntry = null);
  }

  void _showOverlay() {
    final entry = OverlayEntry(
      builder: (_) => const Center(child: Text('Overlay')),
    );
    Overlay.of(context)!.insert(entry);
    setState(() => _overlayEntry = entry);
  }

  @override
  Widget build(BuildContext context) {
    final isShown = _overlayEntry != null;

    return Scaffold(
      floatingActionButton: isShown
          ? FloatingActionButton(
              child: const Icon(Icons.remove),
              onPressed: _hideOverlay,
            )
          : FloatingActionButton(
              child: const Icon(Icons.add),
              onPressed: _showOverlay,
            ),
    );
  }
}
Logs
PS D:\bug> flutter analyze
Analyzing bug...                                                        
No issues found! (ran in 1.4s)
PS D:\bug> flutter doctor   
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.10.5, on Microsoft Windows [Version 10.0.22000.613], locale ja-JP)
[√] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
[X] Chrome - develop for the web (Cannot find Chrome executable at .\Google\Chrome\Application\chrome.exe)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.
[√] Visual Studio - develop for Windows (Visual Studio Community 2022 17.1.3)
[!] Android Studio (not installed)
[√] IntelliJ IDEA Ultimate Edition (version 2022.1)
[√] VS Code (version 1.66.2)
[√] Connected device (2 available)
[√] HTTP Host Availability

! Doctor found issues in 2 categories.

Activity

  1. darshankawar commented on Apr 28, 2022

    @darshankawar
    Member

    @kaboc
    I see two things here.

    1. Calling remove() before dispose() which probably is causing the error you are seeing.
    2. As the error indicates Once you have called dispose() on a OverlayEntry, it can no longer be used., per remove() method:
    /// This method removes this overlay entry from the overlay immediately. The
      /// UI will be updated in the same frame if this method is called before the
      /// overlay rebuild in this frame; otherwise, the UI will be updated in the
      /// next frame. This means that it is safe to call during builds, but	also
      /// that if you do call this after the overlay rebuild, the UI will not update
      /// until	the next frame (i.e. many milliseconds later).
    

    After calling remove(), the UI will not update until next frame, but then calling dispose() which permanently removes the object from tree.

    Although not exactly similar, but can you try something like referred here and see updating it accordingly in your use case helps ? rrousselGit/provider#168 (comment)

  2. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Apr 28, 2022
  3. kaboc commented on Apr 28, 2022

    @kaboc
    ContributorAuthor

    @darshankawar
    Thanks, but I already know the reason why the error occurs. I filed the issue because it seemed (almost) like a bug that just calling dispose() in an ordinary way caused an error, and also because the documentation could be improved. I mean, this is not a request for help but a bug report. It is error-prone that remove() and dispose() can be executed in reverse order.

  4. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Apr 28, 2022
  5. darshankawar commented on Apr 28, 2022

    @darshankawar
    Member

    Thanks for the feedback. I see that the usage of remove() and dispose() could be documented to avoid the scenario, or maybe a better error message to indicate usage of both.

    console error log
    
    ======== Exception caught by widgets library =======================================================
    The following assertion was thrown while finalizing the widget tree:
    A OverlayEntry was used after being disposed.
    
    Once you have called dispose() on a OverlayEntry, 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:125:9)
    #1      ChangeNotifier._debugAssertNotDisposed (package:flutter/src/foundation/change_notifier.dart:131:6)
    #2      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:304:12)
    #3      OverlayEntry._updateMounted (package:flutter/src/widgets/overlay.dart:130:5)
    #4      _OverlayEntryWidgetState.dispose (package:flutter/src/widgets/overlay.dart:200:18)
    #5      StatefulElement.unmount (package:flutter/src/widgets/framework.dart:5029:11)
    #6      _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1911:13)
    #7      ListIterable.forEach (dart:_internal/iterable.dart:39:13)
    #8      _InactiveElements._unmountAll (package:flutter/src/widgets/framework.dart:1920:25)
    #9      BuildOwner.lockState (package:flutter/src/widgets/framework.dart:2513:15)
    #10     BuildOwner.finalizeTree (package:flutter/src/widgets/framework.dart:2928:7)
    #11     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:868:19)
    #12     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:370:5)
    #13     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1146:15)
    #14     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1083:9)
    #15     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:997:5)
    #16     _invoke (dart:ui/hooks.dart:148:13)
    #17     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:318:5)
    #18     _drawFrame (dart:ui/hooks.dart:115:31)
    ====================================================================================================
    
    

    Keeping it open and labeling to get further insights from the team on correct usage of both methods and their order and whether the same need to be documented as well or not.

    stable, master flutter doctor -v
    
    [✓] Flutter (Channel stable, 2.10.5, on macOS 12.2.1 21D62 darwin-x64, locale
        en-GB)
        • Flutter version 2.10.5 at /Users/dhs/documents/fluttersdk/flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision 5464c5bac7 (12 hours ago), 2022-04-18 09:55:37 -0700
        • Engine revision 57d3bac3dd
        • Dart version 2.16.2
        • DevTools version 2.9.2
    
    [!] Xcode - develop for iOS and macOS (Xcode 12.3)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        ! Flutter recommends a minimum Xcode version of 13.
          Download the latest version or update via the Mac App Store.
        • CocoaPods version 1.11.2
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] VS Code (version 1.62.0)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.21.0
    
    [✓] Connected device (5 available)
        • SM G975F (mobile)       • RZ8M802WY0X • android-arm64   • Android 11 (API 30)
        • Darshan's iphone (mobile)  • 21150b119064aecc249dfcfe05e259197461ce23 •
          ios            • iOS 14.4.1 18D61
        • iPhone 12 Pro Max (mobile) • A5473606-0213-4FD8-BA16-553433949729     •
          ios            • com.apple.CoreSimulator.SimRuntime.iOS-14-3 (simulator)
        • macOS (desktop)            • macos                                    •
          darwin-x64     • Mac OS X 10.15.4 19E2269 darwin-x64
        • Chrome (web)               • chrome                                   •
          web-javascript • Google Chrome 98.0.4758.80
    
    [✓] HTTP Host Availability
        • All required HTTP hosts are available
    
    ! Doctor found issues in 1 category.
    
    [✓] Flutter (Channel master, 2.13.0-0.0.pre.791, on macOS 12.2.1 21D62
        darwin-x64, locale en-GB)
        • Flutter version 2.13.0-0.0.pre.791 at
          /Users/dhs/documents/fluttersdk/flutter
        • Upstream repository https://github.com/flutter/flutter.git
        • Framework revision d873157287 (87 minutes ago), 2022-04-27 23:14:06 -0400
        • Engine revision 220613fc0b
        • Dart version 2.18.0 (build 2.18.0-65.0.dev)
        • DevTools version 2.12.2
        
    [!] Xcode - develop for iOS and macOS (Xcode 12.3)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        ! Flutter recommends a minimum Xcode version of 13.
          Download the latest version or update via the Mac App Store.
        • CocoaPods version 1.11.2
    
    [✓] Chrome - develop for the web
        • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
    
    [✓] VS Code (version 1.62.0)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.21.0
    
    [✓] Connected device (5 available)
        • SM G975F (mobile)       • RZ8M802WY0X • android-arm64   • Android 11 (API 30)
        • Darshan's iphone (mobile)  • 21150b119064aecc249dfcfe05e259197461ce23 •
          ios            • iOS 14.4.1 18D61
        • iPhone 12 Pro Max (mobile) • A5473606-0213-4FD8-BA16-553433949729     •
          ios            • com.apple.CoreSimulator.SimRuntime.iOS-14-3 (simulator)
        • macOS (desktop)            • macos                                    •
          darwin-x64     • Mac OS X 10.15.4 19E2269 darwin-x64
        • Chrome (web)               • chrome                                   •
          web-javascript • Google Chrome 98.0.4758.80
    
    [✓] HTTP Host Availability
        • All required HTTP hosts are available
    
    ! Doctor found issues in 1 category.
    
    
    
  6. added
    frameworkflutter/packages/flutter repository. See also f: labels.
    p: material_uimaterial_ui package in flutter/packages
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    a: error messageError messages from the Flutter framework
    a: qualityA truly polished experience
    and removed
    in triagePresently being triaged by the triage team
    on Apr 28, 2022
  7. added
    r: fixedIssue is closed as already fixed in a newer version
    on May 2, 2022
  8. github-actions commented on May 16, 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.

  9. locked as resolved and limited conversation to collaborators on May 16, 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: error messageError messages from the Flutter frameworka: qualityA truly polished experiencefound in release: 2.10Found to occur in 2.10found in release: 2.13Found to occur in 2.13frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: material_uimaterial_ui package in flutter/packagesr: 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