Skip to content

Commit 0fceb06

Browse files
committed
feat: Add pop_until_with_result to Page.
Add the Page.pop_until_with_result() method and ViewPopResultEvent to support popping multiple views and returning a result to the destination view — the Flet equivalent of Flutter's Navigator.popUntilWithResult. Fixes #6326
1 parent 48e1ee7 commit 0fceb06

5 files changed

Lines changed: 405 additions & 10 deletions

File tree

client/pubspec.lock

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,10 @@ packages:
157157
dependency: transitive
158158
description:
159159
name: characters
160-
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
160+
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
161161
url: "https://pub.dev"
162162
source: hosted
163-
version: "1.4.1"
163+
version: "1.4.0"
164164
charcode:
165165
dependency: transitive
166166
description:
@@ -359,7 +359,7 @@ packages:
359359
path: "../packages/flet"
360360
relative: true
361361
source: path
362-
version: "0.83.0"
362+
version: "0.82.2"
363363
flet_ads:
364364
dependency: "direct main"
365365
description:
@@ -911,18 +911,18 @@ packages:
911911
dependency: transitive
912912
description:
913913
name: matcher
914-
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
914+
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
915915
url: "https://pub.dev"
916916
source: hosted
917-
version: "0.12.18"
917+
version: "0.12.17"
918918
material_color_utilities:
919919
dependency: transitive
920920
description:
921921
name: material_color_utilities
922-
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
922+
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
923923
url: "https://pub.dev"
924924
source: hosted
925-
version: "0.13.0"
925+
version: "0.11.1"
926926
media_kit:
927927
dependency: transitive
928928
description:
@@ -1628,10 +1628,10 @@ packages:
16281628
dependency: transitive
16291629
description:
16301630
name: test_api
1631-
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
1631+
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
16321632
url: "https://pub.dev"
16331633
source: hosted
1634-
version: "0.7.9"
1634+
version: "0.7.7"
16351635
torch_light:
16361636
dependency: transitive
16371637
description:
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import asyncio
2+
3+
import flet as ft
4+
5+
6+
def main(page: ft.Page):
7+
page.title = "Routes Example"
8+
9+
result_text = ft.Text("No result yet", size=18)
10+
11+
def route_change():
12+
page.views.clear()
13+
14+
# Home View (/)
15+
page.views.append(
16+
ft.View(
17+
route="/",
18+
controls=[
19+
ft.AppBar(title=ft.Text("Home"), bgcolor=ft.Colors.SURFACE_BRIGHT),
20+
result_text,
21+
ft.Button(
22+
"Start flow",
23+
on_click=lambda _: asyncio.create_task(
24+
page.push_route("/step1")
25+
),
26+
),
27+
],
28+
)
29+
)
30+
31+
if page.route == "/step1" or page.route == "/step2" or page.route == "/step3":
32+
page.views.append(
33+
ft.View(
34+
route="/step1",
35+
controls=[
36+
ft.AppBar(
37+
title=ft.Text("Step 1"),
38+
bgcolor=ft.Colors.SURFACE_BRIGHT,
39+
),
40+
ft.Text("Step 1 of the flow"),
41+
ft.Button(
42+
"Go to Step 2",
43+
on_click=lambda _: asyncio.create_task(
44+
page.push_route("/step2")
45+
),
46+
),
47+
],
48+
)
49+
)
50+
51+
if page.route == "/step2" or page.route == "/step3":
52+
page.views.append(
53+
ft.View(
54+
route="/step2",
55+
controls=[
56+
ft.AppBar(
57+
title=ft.Text("Step 2"),
58+
bgcolor=ft.Colors.SURFACE_BRIGHT,
59+
),
60+
ft.Text("Step 2 of the flow"),
61+
ft.Button(
62+
"Go to Step 3",
63+
on_click=lambda _: asyncio.create_task(
64+
page.push_route("/step3")
65+
),
66+
),
67+
],
68+
)
69+
)
70+
71+
if page.route == "/step3":
72+
page.views.append(
73+
ft.View(
74+
route="/step3",
75+
controls=[
76+
ft.AppBar(
77+
title=ft.Text("Step 3 (Final)"),
78+
bgcolor=ft.Colors.SURFACE_BRIGHT,
79+
),
80+
ft.Text("Flow complete!"),
81+
ft.Button(
82+
"Finish and go Home",
83+
on_click=lambda _: asyncio.create_task(
84+
page.pop_until_with_result(
85+
"/", result="Flow completed!"
86+
)
87+
),
88+
),
89+
],
90+
)
91+
)
92+
93+
page.update()
94+
95+
def on_pop_result(e: ft.ViewPopResultEvent):
96+
result_text.value = f"Result: {e.result}"
97+
page.show_dialog(ft.SnackBar(ft.Text(f"Result: {e.result}")))
98+
page.update()
99+
100+
async def view_pop(e: ft.ViewPopEvent):
101+
if e.view is not None:
102+
page.views.remove(e.view)
103+
top_view = page.views[-1]
104+
await page.push_route(top_view.route)
105+
106+
page.on_route_change = route_change
107+
page.on_view_pop = view_pop
108+
page.on_view_pop_result = on_pop_result
109+
110+
route_change()
111+
112+
113+
if __name__ == "__main__":
114+
ft.run(main)

sdk/python/packages/flet/src/flet/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@
409409
PlatformBrightnessChangeEvent,
410410
RouteChangeEvent,
411411
ViewPopEvent,
412+
ViewPopResultEvent,
412413
)
413414
from flet.controls.painting import (
414415
Paint,
@@ -1072,6 +1073,7 @@
10721073
"VerticalDivider",
10731074
"View",
10741075
"ViewPopEvent",
1076+
"ViewPopResultEvent",
10751077
"VisualDensity",
10761078
"Wakelock",
10771079
"WebBrowserName",

sdk/python/packages/flet/src/flet/controls/page.py

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,33 @@ class ViewPopEvent(Event["Page"]):
214214
"""
215215

216216

217+
@dataclass
218+
class ViewPopResultEvent(Event["Page"]):
219+
"""
220+
Event payload delivered when [`Page.pop_until_with_result`]\
221+
[flet.Page.pop_until_with_result] completes navigation.
222+
223+
Carries the result value back to the destination view, analogous to
224+
Flutter's `Navigator.popUntilWithResult`.
225+
"""
226+
227+
route: str
228+
"""
229+
Route of the destination view that remained on the stack.
230+
"""
231+
232+
result: Any = None
233+
"""
234+
The result value passed from the caller of
235+
[`pop_until_with_result`][flet.Page.pop_until_with_result].
236+
"""
237+
238+
view: Optional[View] = None
239+
"""
240+
Matched [`View`][flet.View] instance for `route`, if found on the page.
241+
"""
242+
243+
217244
@dataclass
218245
class KeyboardEvent(Event["Page"]):
219246
"""
@@ -505,6 +532,14 @@ class Page(BasePage):
505532
Called when the user clicks automatic "Back" button in [`AppBar`][flet.] control.
506533
"""
507534

535+
on_view_pop_result: Optional[EventHandler[ViewPopResultEvent]] = None
536+
"""
537+
Called when [`pop_until_with_result`][flet.Page.pop_until_with_result] reaches
538+
the destination view.
539+
540+
The event carries the result value passed by the caller.
541+
"""
542+
508543
on_keyboard_event: Optional[EventHandler[KeyboardEvent]] = None
509544
"""
510545
Called when a keyboard key is pressed.
@@ -706,7 +741,7 @@ def before_event(self, e: ControlEvent):
706741
self.__last_route = e.route
707742
self.query()
708743

709-
elif isinstance(e, ViewPopEvent):
744+
elif isinstance(e, ViewPopEvent | ViewPopResultEvent):
710745
for v in unwrap_component(self.views):
711746
v = unwrap_component(v)
712747
if v.route == e.route:
@@ -896,6 +931,75 @@ async def view_pop(e):
896931
arguments={"route": new_route},
897932
)
898933

934+
async def pop_until_with_result(self, route: str, result: Any = None) -> None:
935+
"""
936+
Pops views from the navigation stack until a view with the given
937+
`route` is found, then delivers `result` via the
938+
[`on_view_pop_result`][flet.Page.on_view_pop_result] event.
939+
940+
This is the Flet equivalent of Flutter's `Navigator.popUntilWithResult`.
941+
942+
Example:
943+
```python
944+
import flet as ft
945+
946+
947+
def main(page: ft.Page):
948+
def on_pop_result(e: ft.ViewPopResultEvent):
949+
page.show_dialog(ft.SnackBar(ft.Text(f"Result: {e.result}")))
950+
951+
page.on_view_pop_result = on_pop_result
952+
953+
# ... later, from a deeply nested view:
954+
async def go_back(ev):
955+
await page.pop_until_with_result("/", result="Done!")
956+
```
957+
958+
Args:
959+
route: Target route to navigate back to. Must match the `route`
960+
of an existing [`View`][flet.View] in
961+
[`page.views`][flet.Page.views].
962+
result: Optional value delivered to
963+
[`on_view_pop_result`][flet.Page.on_view_pop_result] on the
964+
destination view.
965+
966+
Raises:
967+
ValueError: If no view with the given `route` exists in
968+
[`page.views`][flet.Page.views].
969+
"""
970+
views = unwrap_component(self.views)
971+
972+
# Find the target view (first match from bottom of the stack)
973+
target_idx = None
974+
for i, v in enumerate(views):
975+
v = unwrap_component(v)
976+
if v.route == route:
977+
target_idx = i
978+
break
979+
980+
if target_idx is None:
981+
raise ValueError(f"No view found with route '{route}' in page.views")
982+
983+
# Remove views above the target
984+
del self.views[target_idx + 1 :]
985+
986+
# Update browser URL
987+
await self.push_route(route)
988+
989+
# Fire on_view_pop_result for the destination view
990+
if self.on_view_pop_result:
991+
target_view = unwrap_component(views[target_idx])
992+
e = ViewPopResultEvent(
993+
name="view_pop_result",
994+
control=self,
995+
route=route,
996+
result=result,
997+
view=target_view,
998+
)
999+
await self._trigger_event("view_pop_result", event_data=None, e=e)
1000+
1001+
self.update()
1002+
8991003
def get_upload_url(self, file_name: str, expires: int) -> str:
9001004
"""
9011005
Generates presigned upload URL for built-in upload storage:

0 commit comments

Comments
 (0)