Skip to content

View.can_pop and View.on_confirm_pop #5284

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 7, 2025
Merged

View.can_pop and View.on_confirm_pop #5284

merged 2 commits into from
May 7, 2025

Conversation

FeodorFitsner
Copy link
Contributor

@FeodorFitsner FeodorFitsner commented May 7, 2025

Close #5280

Summary by Sourcery

Add support for customizing view navigation behavior with can_pop and on_confirm_pop properties

New Features:

  • Introduce can_pop property to control whether a view can be popped
  • Add on_confirm_pop event handler to provide custom pop confirmation logic

Enhancements:

  • Implement platform-specific navigation handling for root and nested views
  • Add method to programmatically confirm or cancel view pop navigation

@FeodorFitsner FeodorFitsner merged commit fcc712f into main May 7, 2025
1 of 2 checks passed
@FeodorFitsner FeodorFitsner deleted the 0-28-0-view-pop branch May 7, 2025 17:29
@SIRGPrice
Copy link
Contributor

Hi @FeodorFitsner , I updated some code to 0.28.3 this morning and this navigation changes and everything in general worked very nice.

I have a suggestion for a feature associated with the pop of the views. In android flet 0.27.6, on_view_pop was called directly when native android button pressed, making possible a view exit logic, like a confirmation back, save or exit dialog.

Now back button event pop the view first and then on_view_pop is called wich (I can be wrong) leaves no options to any logics. It could be possible to associate can_pop with other event called on_pop, for example, that gives the opportunity to page.views.pop() yourself or do other things instead?

Thank you for everything.

@FeodorFitsner
Copy link
Contributor Author

FeodorFitsner commented May 21, 2025

Got it. In 0.28.3 we added a few hooks to View control that allows you to control its "popping" :) logic.
There is a new View.can_pop bool property which is True by default and allows for any view to be removed with no questions asked. However, if you set View.can_pop to False then you can add View.on_confirm_pop event handler where you can open an alert dialog ton confirm or do other checks and then call View.confirm_pop(True|False) method to either allow or prevent view from removal. In its simplest form, setting page.views[0].canPop = False for root view prevents closing app with hardware back button on Android. Let me know if you have any questions. I'm adding this to the docs.

@SIRGPrice
Copy link
Contributor

I undertand, It seems I was wrong with interpretation of the new pop mechanics. I tryed the method you mention when defining the View 's:

  • page x = View(can_pop=False, on_confirm_pop = lambda _: function(args))

But the can_pop = False disable the pop event so on_confirm_pop never is called and function(args) never executed.

The hardware back button dont do nothing.

This has sense for you?

Thanks.

@FeodorFitsner
Copy link
Contributor Author

Make sure you have flet-desktop installed of 0.28.3 version (check with pip list).

I'm using this example to test with 0.28.3 and I can see "on_confirm_pop" printed when clicking "Back" on "Store" screen:

import flet as ft


def main(page: ft.Page):
    page.title = "Routes Example"

    def route_change(e):
        page.views.clear()

        def confirm_route_pop(e):
            print("on_confirm_pop")
            e.control.confirm_pop(True)

        page.views.append(
            ft.View(
                "/",
                [
                    ft.AppBar(title=ft.Text("Flet app"), bgcolor=ft.Colors.YELLOW),
                    ft.Button("Visit Store", on_click=lambda _: page.go("/store")),
                ],
                can_pop=False,
                on_confirm_pop=confirm_route_pop,
            )
        )
        if page.route == "/store":
            page.views.append(
                ft.View(
                    "/store",
                    [
                        ft.AppBar(title=ft.Text("Store"), bgcolor=ft.Colors.YELLOW),
                        ft.Button("Go Home", on_click=lambda _: page.go("/")),
                    ],
                    can_pop=False,
                    on_confirm_pop=confirm_route_pop,
                )
            )
        page.update()

    def view_pop(e):
        page.views.pop()
        top_view = page.views[-1]
        page.go(top_view.route)

    page.on_route_change = route_change
    page.on_view_pop = view_pop

    page.go(page.route)


ft.app(main)

@SIRGPrice
Copy link
Contributor

Absolutely, it works perfectly on desktop. But when I do the next secuence of things:

  • Build the apk of this example or other combinations and try it in Android Studio Emulator or in a real mobile android device.

  • With the View 's property can_pop set to False.

  • With the View 's event on_confirm_pop set to: lambda_ : function(args)

  • And you press the android sistem back button to go back.

Then the button does nothing because the property can_pop = False is disabling the pop event, not on_confirm_pop or page.on_view_pop event is generated.

If can_pop=True pressing android sistem button generates pop event that pops a view directly, without the oportunity to have logic between.

In the 2 cases android back sistem button give no oportunity to display pop logic.

Thats only on android once apk done.

Thanks Feodor.

@FeodorFitsner
Copy link
Contributor Author

OK, I just bumped Flet version in Flet build template. Delete "build" folder of your app and rebuild.

@SIRGPrice
Copy link
Contributor

I tryed again with the new build template and Flet dont catch any event associated with sistem back button on mobile device when can_pop=False. So page.on_view_pop and on_confirm_pop are never being called.

I think could be a personal code issue, but it seems imposible to me to reach a combination that makes any sistem back button associated event handler being fired when can_pop = False when testing in device.

@SIRGPrice
Copy link
Contributor

Im going to try some hipersimplistic variants so I can give you more feed back about it.

Thanks.

@FeodorFitsner
Copy link
Contributor Author

What version of Flet do you have inside of your APK? It's in libpythonsitepackages.so (it's just renamed zip).

@SIRGPrice
Copy link
Contributor

SIRGPrice commented May 22, 2025

Fonunded in version.py inside libpythonsitepackages.so:

"""Provide the current Flet version."""

import os
import subprocess as sp
from pathlib import Path

import flet
from flet.utils import is_mobile, is_windows, which

will be replaced by CI
version = "0.28.3"

...

@FeodorFitsner
Copy link
Contributor Author

Can you do a simple code repro for me please?

@SIRGPrice
Copy link
Contributor

Sure!

from flet import *
import time

def main(page: Page):

    G = Colors.LIGHT_GREEN_ACCENT_400
    B = Colors.BLUE_400
    R = Colors.PINK_ACCENT_400
    D = Colors.DEEP_PURPLE_900

    # main page, not popable, all other view return here.

    view0 = View(
        route="/0",
        can_pop=False,
        controls=[SafeArea(expand=True, content=Container(
                height=page.height,
                width=page.width,
                bgcolor=R,
                content=Column([Container(Icon(Icons.MENU,color=D),on_click=lambda _: go(1)),
                                Container(Icon(Icons.HOME,color=D),on_click=lambda _: go(2))])
            ))
        ]
    )

    # secondary pages

    view1 = View(
        route="/1",
        can_pop=False,
        on_confirm_pop=lambda e: Pop(e),
        controls=[SafeArea(expand=True, content=Container(
                height=page.height,
                width=page.width,
                bgcolor=G,
                content=Container(Icon(Icons.MENU,color=D),on_click=lambda _: go(0))
            ))
        ]
    )

    view2 = View(
        route="/2",
        can_pop=False,
        on_confirm_pop=lambda e: Pop(e),
        controls=[SafeArea(expand=True, content=Container(
                height=page.height,
                width=page.width,
                bgcolor=B,
                content=Container(Icon(Icons.HOME,color=D),on_click=lambda _: go(0))
            ))
        ]
    )

    def go(view): # poping the old view and pushing the new one on top.

        print("go")

        if len(page.views) == 2: # max number of views that want to be acumulated in page.views.

            page.views.pop(0)
            page.views[0].confirm_pop = True

        if view == 0:
            new_view = view0
        elif view == 1:
            new_view = view1
        elif view == 2:
            new_view = view2

        page.views.append(new_view)
        page.update()

    def Pop(e):

        print("Pop")

        time.sleep(0.5) # pop logic

        e.control.confirm_pop = True # explicitly confirming the pop of the last view

        # also valid

        #page.views[-1].confirm_pop = True

    go(0) # starting the app with the first view

app(target=main, assets_dir="assets")

When I build this apk with the same toml and everything, it gives the same result as my real code, pushing sistem back button not firing on_confirm_pop.

@SIRGPrice
Copy link
Contributor

SIRGPrice commented May 26, 2025

Hi Feodor, was posible for you to reproduce it? Its the simplest example I can do.

I'm in no hurry, it's just in case you needed more information.

@FeodorFitsner FeodorFitsner self-assigned this May 27, 2025
@FeodorFitsner
Copy link
Contributor Author

Looking into that.

FeodorFitsner added a commit that referenced this pull request Jun 19, 2025
commit 2a1facb
Author: Henning Wilmer <57907680+HenningCode@users.noreply.github.com>
Date:   Thu Jun 12 18:37:58 2025 +0200

    Change the type alias from a class to a real rename (#5258)

commit 8f83b45
Author: PythonPan <2292551082@qq.com>
Date:   Fri Jun 13 00:29:49 2025 +0800

    Fix  page.run_thread does not receive kwargs (#5318) (#5320)

commit bf09a2c
Author: Antón Fernández Pérez <134967595+SIRGPrice@users.noreply.github.com>
Date:   Wed Jun 11 21:16:41 2025 +0200

    Update remove_control_payload.dart (#5353)

    Android apk app randomly generates this error in logcat:

    E  [ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: type 'Null' is not a subtype of type 'String'
                                                                                                        #0      new List.from (dart:core-patch/array_patch.dart:30)
                                                                                                        #1      new RemoveControlPayload.fromJson (package:flet/src/protocol/remove_control_payload.dart:7)
                                                                                                        #2      appReducer (package:flet/src/reducers.dart:376)
                                                                                                        #3      Store._createReduceAndNotify.<anonymous closure> (package:redux/src/store.dart:235)
                                                                                                        #4      Store.dispatch (package:redux/src/store.dart:267)
                                                                                                        #5      FletServer._onMessage (package:flet/src/flet_server.dart:265)
                                                                                                        #6      FletTcpSocketServerProtocol._onMessage (package:flet/src/flet_server_protocol_tcp_socket.dart:125)
                                                                                                        #7      FletTcpSocketServerProtocol.connect.<anonymous closure> (package:flet/src/flet_server_protocol_tcp_socket.dart:94)
                                                                                                        #8      _RootZone.runUnaryGuarded (dart:async/zone.dart:1778)
                                                                                                        #9      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:381)
                                                                                                        #10     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:312)
                                                                                                        #11     _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:798)
                                                                                                        #12     _StreamController._add (dart:async/stream_controller.dart:663)
                                                                                                        #13     _StreamController.add (dart:async/stream_controller.dart:618)
                                                                                                        #14     _Socket._onData (dart:io-patch/socket_patch.dart:2904)
                                                                                                        #15     _RootZone.runUnaryGuarded (dart:async/zone.dart:1778)
                                                                                                        #16     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:381)
                                                                                                        #17     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:312)
                                                                                                        #18     _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:798)
                                                                                                        #19     _StreamController._add (dart:async/stream_controller.dart:663)
                                                                                                        #20     _StreamController.add (dart:async/stream_controller.dart:618)
                                                                                                        #21     new _RawSocket.<anonymous closure> (dart:io-patch/socket_patch.dart:2323)
                                                                                                        #22     _NativeSocket.issueReadEvent.issue (dart:io-patch/socket_patch.dart:1646)
                                                                                                        #23     _microtaskLoop (dart:async/schedule_microtask.dart:40)
                                                                                                        #24     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49)

commit 1c040ef
Author: Feodor Fitsner <feodor@appveyor.com>
Date:   Sun May 18 20:13:06 2025 -0400

    Preparing Flet v0.28.3 release (#5306)

    * Fix images displaying in web mode with non-empty page name

    Fix #5198

    * Fix page.on_view_pop event handling

    Fix #5302

    * Fix FilePicker.save_file() for Android and iOS

    Fix #5301

    * PubSub: allow multiple subscribers per session, per topic

    Close #5303

    * Bump version to 0.28.3, updated changelog

commit 69e4e10
Author: Feodor Fitsner <feodor@appveyor.com>
Date:   Sat May 10 13:49:30 2025 -0700

    Prepare Flet 0.28.2 (#5295)

    * Added missing imports into `__init__.py`

    * Changelog, bumped version to 0.28.2

    * fix WindowDragArea

    * update code in readme

    * Upated changelog

    ---------

    Co-authored-by: ndonkoHenri <robotcoder4@protonmail.com>

commit 7ce8e48
Author: Feodor Fitsner <feodor@appveyor.com>
Date:   Thu May 8 12:38:37 2025 -0700

    Update .appveyor.yml

commit deb38c7
Author: Feodor Fitsner <feodor@appveyor.com>
Date:   Thu May 8 09:11:14 2025 -0700

    v0.28.0 release notes (#5286)

    * v0.28.0 release notes

    * Fix "Fixed:"

commit fcc712f
Author: Feodor Fitsner <feodor@appveyor.com>
Date:   Wed May 7 10:29:03 2025 -0700

    `View.can_pop` and `View.on_confirm_pop` (#5284)

    * View.can_pop and View.on_confirm_pop

    * Dismissible and View: added 5 minutes timeout for confirm callbacks

commit c3d550a
Author: Owen McDonnell <7119543+OwenMcDonnell@users.noreply.github.com>
Date:   Sat May 3 16:41:10 2025 -0700

    Update CONTRIBUTING.md (#5268)

    note about activating .zprofile variables.

commit f4b83ae
Author: bl1nch <130155870+bl1nch@users.noreply.github.com>
Date:   Tue Apr 29 07:31:08 2025 +0600

    UTF-8 encoding for pyproject.toml (#5203)

commit e3f4d16
Author: Feodor Fitsner <feodor@appveyor.com>
Date:   Fri Apr 4 17:17:48 2025 -0700

    Update reorderable_list_view.dart

commit add9c61
Author: TheEthicalBoy <98978078+ndonkoHenri@users.noreply.github.com>
Date:   Sat Apr 5 02:10:23 2025 +0200

    feat: custom `ReorderableListView` drag handle listeners (#5051)

    * initial commit

    * `ReorderableListView`: mouse_cursor, show_default_drag_handles

    * generated files

    * flutter 3.29.0

commit 440009d
Author: TheEthicalBoy <98978078+ndonkoHenri@users.noreply.github.com>
Date:   Sat Apr 5 02:04:32 2025 +0200

    feat: expose events (`on_double_tap`, `on_pan_start`) in `WindowDragArea` (#5043)

    * improve events typing in gesture_detector.py

    * window.start_dragging

    * delete window_drag_area.dart

    * rework WindowDragArea inheriting from GestureDetector

    * export more utils
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Prevent platform back button from popping a route with pop confirmation event
2 participants