bug: Android Back with a dialog open over a View also pops the View

未关闭
#6,964 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
1-2 天
新手友好度
42/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
dart, flutter, python
领域
frontend, mobile

调研方向

Start in _handleSystemPopRoute() in packages/flet/lib/src/controls/page.dart, which runs before popRoute() in packages/flet/lib/src/routing/router_delegate.dart. Check whether the Navigator's top route is a pageless dialog before the can_pop and on_confirm_pop checks. Done when a widget test with two Views and an AlertDialog over the second, given one handlePopRoute(), closes only the dialog and sends no view_pop, and a second call pops the View.

由索引模型根据 Issue 内容生成。

描述

Duplicate Check
  • I have searched the opened issues and there are no duplicates (#3353 is about on_view_pop firing on a desktop dialog close, a different path)
Describe the bug

On Android, with two or more Views and a dialog open over the top View (an AlertDialog, a DatePicker, a TimePicker, a BottomSheet or a PopupMenuButton's menu), one Back key press (or a committed Back gesture) closes the dialog and pops the View under it. Material and Flutter's own Navigator close only the topmost route: the dialog.

Cause

PageControl._handleSystemPopRoute() (packages/flet/lib/src/controls/page.dart, added in #5915) is the popRouteHandler of SimpleRouterDelegate (packages/flet/lib/src/routing/router_delegate.dart), so it runs on every system Back before PopNavigatorRouterDelegateMixin.popRoute()'s Navigator.maybePop(). With views.length > 1, when the top View has can_pop true and no on_confirm_pop handler, it calls _markViewAsPopped() (the View leaves the Navigator's pages at once and view_pop is sent) and returns true, so the Navigator is never asked. It does not check for pageless routes above that View's page: dialogs, pickers, sheets and menus are pushed on the same Navigator (useRootNavigator: false in alert_dialog.dart, date_picker.dart, time_picker.dart), and when the View's page is removed the routes attached to it go with it.

The Android Back gesture ends there too: with a dialog on top the page route is not current, so PredictiveBackPageTransitionsBuilder's detector declines the gesture (_isEnabled is route.isCurrent && popGestureEnabled) and the commit falls back to handlePopRoute() → popRoute().

A View that has can_pop=False or an on_confirm_pop handler is not affected: _handleSystemPopRoute() returns null and maybePop() pops the dialog.

Code sample
import flet as ft


def main(page: ft.Page):
    def build_views():
        page.views.clear()
        page.views.append(
            ft.View(
                route="/",
                appbar=ft.AppBar(title=ft.Text("Home")),
                controls=[
                    ft.Button("Open details", on_click=lambda: page.navigate("/details"))
                ],
            )
        )
        if page.route == "/details":
            page.views.append(
                ft.View(
                    route="/details",
                    appbar=ft.AppBar(title=ft.Text("Details")),
                    controls=[
                        ft.Button(
                            "Open dialog",
                            on_click=lambda: page.show_dialog(
                                ft.AlertDialog(
                                    title=ft.Text("Back should close only this"),
                                    on_dismiss=lambda e: print("dialog dismissed"),
                                )
                            ),
                        ),
                        ft.Button(
                            "Open date picker",
                            on_click=lambda: page.show_dialog(ft.DatePicker()),
                        ),
                    ],
                )
            )
        page.update()

    def on_view_pop(e: ft.ViewPopEvent):
        print("view_pop", e.route)
        page.views.pop()
        page.navigate(page.views[-1].route)

    page.on_route_change = lambda e: build_views()
    page.on_view_pop = on_view_pop
    build_views()


ft.run(main)
To reproduce
  1. flet build apk (or flet debug android) and run it on an Android device or emulator.
  2. Tap Open details, then Open dialog (or Open date picker).
  3. Press Back once (adb shell input keyevent KEYCODE_BACK).
Expected behavior

The dialog closes and Details stays. A second Back pops Details.

Actual behavior

The dialog closes and Details is popped too: the app shows Home, and the log has view_pop /details with the dismiss.

Seen with the code above, built with flet build apk at 15854a0ac on an Android 14 emulator (API 34, gesture navigation): after one Back with the AlertDialog open, the screen showed Home and the log had view_pop /details then dialog dismissed; with the DatePicker open, Home and view_pop /details. The Back gesture (an edge swipe) did the same in the app where this was found.

Suggested fix

Let the Navigator close a pageless route first: in _handleSystemPopRoute(), return null when the Navigator's top route is not a page (so super.popRoute() → maybePop() pops it), before the can_pop / confirm_pop checks:

    final navigator = _navigatorKey.currentState;
    if (navigator != null && !_isTopRoutePageBased(navigator)) {
      // A dialog, picker, sheet or menu is open over the top view:
      // Navigator.maybePop() closes it, and the view stays.
      return null;
    }
  bool _isTopRoutePageBased(NavigatorState navigator) {
    Route<dynamic>? top;
    navigator.popUntil((route) {
      top = route;
      return true; // stops at the top route: nothing is popped
    });
    return top?.settings is Page;
  }

A test: two Views, an AlertDialog shown over the second, tester.binding.handlePopRoute() once: the dialog is gone, both Views remain and no view_pop was sent; a second handlePopRoute() sends view_pop for the second View.

Operating system

Android

Flet version

1.1 from the flet-1.1.0 branch at 15854a0ac; the same code is on main.

Regression

Likely since #5915 (December 2025), which added popRouteHandler: before it, popRoute() went straight to Navigator.maybePop().

Additional details

Found in an app that works around it by giving every pushed View an on_confirm_pop that calls confirm_pop(True): with a handler, _handleSystemPopRoute() returns null, maybePop() closes the dialog first, and the View's own pop still goes through onDidRemovePage → view_pop (the handler itself is never called while can_pop is true). With the fix above, that workaround is no longer needed.


Written by Claude Code (AI agent).

主要语言
Python
星标
17.5k
派生
740
平均合并
1 天 8 小时
30 天内合并 PR
74

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

flet-dev/flet 的其他 Issue

查看 flet-dev/flet 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。