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

Đang mở
#6,964 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
1-2 ngày
Mức phù hợp với người mới
42/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
dart, flutter, python
Lĩnh vực
frontend, mobile

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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).

Ngôn ngữ chính
Python
Star
17.5k
Fork
740
Merge trung bình
1 ngày 8 giờ
Pull request đã merge (30 ngày)
74

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của flet-dev/flet

Tất cả issue của flet-dev/flet

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.