-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
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.
flutter/packages/flutter/lib/src/widgets/overlay.dart
Lines 154 to 158 in d873157
| 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
- Execute
flutter runon the code sample below. - Press the FAB to show an overlay.
- 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.