Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Bug fixes

* Fix a system/edge-swipe back gesture exiting the whole host app instead of navigating back when it lands on an embedded `FletApp` (an app rendered inside another Flet app — e.g. a gallery host running example apps in-process). The embedded app's `WidgetsApp` (`MaterialApp`/`CupertinoApp`) ran the default `NavigationNotification` handler, which reported `SystemNavigator.setFrameworkHandlesBack(false)` for a nested app that couldn't pop (typically a single-view example) and swallowed the notification, so the OS finished the whole activity on back and the host never got to report that it could pop. An embedded page now lets that notification bubble to the host (which re-reports `canHandlePop`) and chains a `ChildBackButtonDispatcher` to the host Router, so a system back propagates to the host and pops the view that embeds it by @FeodorFitsner.
* Fix `page.window.maximized = True` intermittently reverting to unmaximized right after startup on macOS, when set in the same patch as `page.title` (e.g. `page.title = "My App"; page.window.maximized = True` in `main()`) by @davidlawson.
* Fix `flet build` picking a non-decodable icon/splash image when several files share a base name, producing a machine-dependent `NoDecoderForImageFormatException` from `flutter_launcher_icons`. When an app's `assets` held, say, both `icon.png` and `icon.svg`, `find_platform_image` selected the first match from `glob.glob(...)` — whose order is filesystem-dependent — so the same app could pick `icon.png` on one machine and `icon.svg` on another (SVG is vector and can't be decoded by the raster icon/splash generators), turning a working build into a crash purely based on directory listing order. Candidates are now filtered to formats the generators can actually decode (`.svg` is dropped everywhere; `.icns` stays macOS-only and `.ico` Windows-only) and ranked so a raster image (`.png` first) always wins, making the choice deterministic across machines. When the only supplied image is an SVG (no raster sibling), it's skipped with a build-log warning and the default Flet icon is used instead of crashing by @FeodorFitsner.
* Fix modal controls (`AlertDialog`, `CupertinoAlertDialog`, `BottomSheet`, `CupertinoBottomSheet`) crashing to a black screen with "setState()/markNeedsBuild() called during build" when they close in the same frame that another route or overlay opens — e.g. dismissing a bottom sheet and showing a `SnackBar` from one handler. The close path popped the route synchronously during `build`, so the exit animation notified a listener that was mid-build. Each modal now tracks its own `ModalRoute` and closes it in a post-frame callback, popping *that* route (never the topmost one); `View`'s confirm-pop pops its own route too, so a modal dismissed in the same tick as a view pop can no longer dismiss the wrong one by @FeodorFitsner.
Expand Down
1 change: 1 addition & 0 deletions packages/flet/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## 0.86.3

* Fix a system/edge-swipe back gesture exiting the whole host app instead of navigating back when it lands on an embedded `FletApp` (an app rendered inside another Flet app — e.g. a gallery host running example apps in-process). The embedded app's `WidgetsApp` (`MaterialApp`/`CupertinoApp`) ran the default `NavigationNotification` handler, which reported `SystemNavigator.setFrameworkHandlesBack(false)` for a nested app that couldn't pop (typically a single-view example) and swallowed the notification, so the OS finished the whole activity on back and the host never got to report that it could pop. An embedded page now lets that notification bubble to the host (which re-reports `canHandlePop`) and chains a `ChildBackButtonDispatcher` to the host Router, so a system back propagates to the host and pops the view that embeds it.
* Fix modal controls (`AlertDialog`, `CupertinoAlertDialog`, `BottomSheet`, `CupertinoBottomSheet`) throwing "setState()/markNeedsBuild() called during build" and blanking the screen when closed in the same frame another route/overlay opens (e.g. a `SnackBar`). Each modal now tracks its own `ModalRoute` and closes it via a post-frame `closeModalRoute()` that pops that specific route; `View`'s confirm-pop pops its own route too, removing the wrong-route race between a dismissing modal and a view pop.

## 0.86.2
Expand Down
48 changes: 47 additions & 1 deletion packages/flet/lib/src/controls/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
final Map<int, MultiView> _multiViews = <int, MultiView>{};
bool _registeredFromMultiViews = false;

// True when this page is a nested/embedded app (hosted inside another Flet
// app via FletApp, so its backend has a controlId). Embedded pages chain
// their system back handling to the host through [_childBackButtonDispatcher].
bool _isEmbedded = false;

// For an embedded page, a child of the host Router's back button dispatcher.
// Without it, MaterialApp.router/CupertinoApp.router would install their own
// RootBackButtonDispatcher, and a system/edge-swipe back that the embedded
// app can't handle (e.g. it has a single view) would call SystemNavigator.pop()
// and exit the whole host app instead of propagating to the host, which would
// pop the view embedding this app.
ChildBackButtonDispatcher? _childBackButtonDispatcher;

@override
void initState() {
debugPrint("Page.initState: ${widget.control.id}");
Expand All @@ -84,7 +97,8 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {

_routeParser = RouteParser();
final backend = FletBackend.of(context);
final isEmbedded = backend.controlId != null;
_isEmbedded = backend.controlId != null;
final isEmbedded = _isEmbedded;
final defaultRouteName =
WidgetsBinding.instance.platformDispatcher.defaultRouteName;
final pendingInitial = isEmbedded
Expand Down Expand Up @@ -136,6 +150,18 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
super.didChangeDependencies();
_ensureServiceRegistries();
_loadFontsIfNeeded(FletBackend.of(context));

// When embedded inside another Flet app, hook this page's system back
// handling into the host Router so an unhandled back propagates to the host
// (which pops the embedding view) instead of exiting the whole app.
if (_isEmbedded && _childBackButtonDispatcher == null) {
final parentBackButtonDispatcher =
Router.maybeOf(context)?.backButtonDispatcher;
if (parentBackButtonDispatcher != null) {
_childBackButtonDispatcher =
parentBackButtonDispatcher.createChildBackButtonDispatcher();
}
}
}

@override
Expand Down Expand Up @@ -583,11 +609,26 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
var showSemanticsDebugger =
control.getBool("show_semantics_debugger", false)!;

// Claim back-button priority for the embedded page so its Router (and, on a
// fall-through, the host Router) receives system back events. Re-asserted on
// every build, matching Flutter's nested-Router guidance.
_childBackButtonDispatcher?.takePriority();

// For an embedded page, do NOT let WidgetsApp's default NavigationNotification
// handler run: it would call SystemNavigator.setFrameworkHandlesBack(false)
// (this nested app's single view can't pop) and stop the notification, so the
// OS finishes the whole activity on a system/edge-swipe back. Returning false
// lets the notification bubble to the host app, whose Navigator re-reports
// canHandlePop=true when the host can pop the view embedding this app.
final NotificationListenerCallback<NavigationNotification>?
onNavigationNotification = _isEmbedded ? (_) => false : null;

Widget? app = widgetsDesign == PageDesign.cupertino
? home != null
? CupertinoApp(
debugShowCheckedModeBanner: false,
showSemanticsDebugger: showSemanticsDebugger,
onNavigationNotification: onNavigationNotification,
title: windowTitle,
theme: cupertinoTheme,
builder: scaffoldMessengerBuilder,
Expand All @@ -599,9 +640,11 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
: CupertinoApp.router(
debugShowCheckedModeBanner: false,
showSemanticsDebugger: showSemanticsDebugger,
onNavigationNotification: onNavigationNotification,
routerDelegate: _routerDelegate,
routeInformationParser: _routeParser,
routeInformationProvider: _routeInformationProvider,
backButtonDispatcher: _childBackButtonDispatcher,
title: windowTitle,
theme: cupertinoTheme,
builder: scaffoldMessengerBuilder,
Expand All @@ -613,6 +656,7 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
? MaterialApp(
debugShowCheckedModeBanner: false,
showSemanticsDebugger: showSemanticsDebugger,
onNavigationNotification: onNavigationNotification,
title: windowTitle,
theme: lightTheme,
darkTheme: darkTheme,
Expand All @@ -626,9 +670,11 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
: MaterialApp.router(
debugShowCheckedModeBanner: false,
showSemanticsDebugger: showSemanticsDebugger,
onNavigationNotification: onNavigationNotification,
routerDelegate: _routerDelegate,
routeInformationParser: _routeParser,
routeInformationProvider: _routeInformationProvider,
backButtonDispatcher: _childBackButtonDispatcher,
title: windowTitle,
theme: lightTheme,
darkTheme: darkTheme,
Expand Down
Loading