Skip to content

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

@kaboc

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    a: error messageError messages from the Flutter frameworka: qualityA truly polished experiencef: material designflutter/packages/flutter/material repository.found 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 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