Skip to content

Commit ca0346f

Browse files
committed
Refactor Flet Python declarative API and improve observables
Refactors the Flet Python declarative API to use new hooks and component patterns, replacing StateView with use_state and @ft.component, and updates all examples accordingly. Introduces a new unwrap_component utility, improves Observable and observable collection handling, and adds new public API exports (on_mounted, on_unmounted, on_updated, unwrap_component). Refactors context and session management, updates event and control handling, and fixes various docstrings and type annotations. Removes deprecated code, cleans up hooks, and adds new routing and progress bar examples.
1 parent a95ceb1 commit ca0346f

33 files changed

Lines changed: 611 additions & 419 deletions

packages/flet/lib/src/controls/page.dart

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
164164
pixelRatio: parseDouble(args["pixel_ratio"], _dpr)!);
165165
final data = await image.toByteData(format: ui.ImageByteFormat.png);
166166
return data!.buffer.asUint8List();
167+
case "push_route":
168+
_routeState.route = args["route"];
167169
default:
168170
throw Exception("Unknown Page method: $name");
169171
}
@@ -184,9 +186,7 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
184186
_multiViews[view.viewId] = MultiView(
185187
viewId: view.viewId, flutterView: view, initialData: initialData);
186188
if (triggerAddViewEvent) {
187-
widget.control.backend.triggerControlEventById(
188-
widget.control.id,
189-
"multi_view_add",
189+
widget.control.triggerEventWithoutSubscribers("multi_view_add",
190190
{"view_id": view.viewId, "initial_data": initialData});
191191
}
192192
changed = true;
@@ -197,8 +197,8 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
197197
.any((view) => view.viewId == viewId)) {
198198
_multiViews.remove(viewId);
199199
if (triggerAddViewEvent) {
200-
widget.control.backend.triggerControlEventById(
201-
widget.control.id, "multi_view_remove", viewId);
200+
widget.control
201+
.triggerEventWithoutSubscribers("multi_view_remove", viewId);
202202
}
203203
changed = true;
204204
}
@@ -218,7 +218,8 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
218218
}
219219

220220
void _handleAppLifecycleTransition(String state) {
221-
widget.control.triggerEvent("app_lifecycle_state_change", {"state": state});
221+
widget.control.triggerEventWithoutSubscribers(
222+
"app_lifecycle_state_change", {"state": state});
222223
}
223224

224225
bool _handleKeyDown(KeyEvent e) {
@@ -238,7 +239,7 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
238239
LogicalKeyboardKey.shiftLeft,
239240
LogicalKeyboardKey.shiftRight
240241
].contains(k)) {
241-
widget.control.triggerEvent(
242+
widget.control.triggerEventWithoutSubscribers(
242243
"keyboard_event",
243244
KeyboardEvent(
244245
key: k.keyLabel,
@@ -292,15 +293,6 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
292293
// clear hrefs index
293294
FletBackend.of(context).globalKeys.clear();
294295

295-
// page route
296-
var route = widget.control.getString("route");
297-
if (route != null && _routeState.route != route) {
298-
// update route
299-
WidgetsBinding.instance.addPostFrameCallback((_) {
300-
_routeState.route = route;
301-
});
302-
}
303-
304296
if (!widget.control.backend.multiView) {
305297
// single page mode
306298
return _buildApp(widget.control, null);
@@ -529,7 +521,7 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
529521
pages: pages,
530522
onDidRemovePage: (page) {
531523
if (page.key != null) {
532-
widget.control.triggerEvent(
524+
widget.control.triggerEventWithoutSubscribers(
533525
"view_pop", {"route": (page.key as ValueKey).value});
534526
}
535527
});

packages/flet/lib/src/flet_backend.dart

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -187,22 +187,22 @@ class FletBackend extends ChangeNotifier {
187187
Message(
188188
action: MessageAction.registerClient,
189189
payload: RegisterClientRequestBody(
190-
sessionId: SessionStore.getSessionId(pageUri.toString()),
190+
sessionId: SessionStore.getSessionId(),
191191
pageName: getWebPageName(pageUri),
192192
page: {
193-
'route': page.get("route"),
194-
'pwa': page.get("pwa"),
195-
'web': page.get("web"),
196-
'debug': page.get("debug"),
197-
'wasm': page.get("wasm"),
198-
'test': page.get("test"),
199-
'multi_view': page.get("multi_view"),
200-
'platform_brightness': page.get("platform_brightness"),
201-
'width': page.get("width"),
202-
'height': page.get("height"),
203-
'platform': page.get("platform"),
204-
'window': page.child("window")!.toMap(),
205-
'media': page.get("media"),
193+
"route": page.get("route"),
194+
"pwa": page.get("pwa"),
195+
"web": page.get("web"),
196+
"debug": page.get("debug"),
197+
"wasm": page.get("wasm"),
198+
"test": page.get("test"),
199+
"multi_view": page.get("multi_view"),
200+
"platform_brightness": page.get("platform_brightness"),
201+
"width": page.get("width"),
202+
"height": page.get("height"),
203+
"platform": page.get("platform"),
204+
"window": page.child("window")!.toMap(),
205+
"media": page.get("media"),
206206
}).toMap()),
207207
unbuffered: true);
208208
}
@@ -211,7 +211,7 @@ class FletBackend extends ChangeNotifier {
211211
if (resp.error?.isEmpty ?? true) {
212212
// all good!
213213
// store session ID in a cookie
214-
SessionStore.setSessionId(pageUri.toString(), resp.sessionId);
214+
SessionStore.setSessionId(resp.sessionId);
215215
isLoading = false;
216216
_reconnectDelayMs = 0;
217217
error = "";
@@ -337,7 +337,8 @@ class FletBackend extends ChangeNotifier {
337337
debugPrint("Platform brightness updated: $newBrightness");
338338
platformBrightness = newBrightness;
339339
updateControl(page.id, {"platform_brightness": newBrightness.name});
340-
triggerControlEvent(page, "platform_brightness_change", newBrightness.name);
340+
triggerControlEventById(
341+
page.id, "platform_brightness_change", newBrightness.name);
341342
notifyListeners();
342343
}
343344

packages/flet/lib/src/models/control.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,17 @@ class Control extends ChangeNotifier {
149149
return backend.triggerControlEvent(this, eventName, data);
150150
}
151151

152+
/// Triggers a control event without checking for subscribers.
153+
///
154+
/// This method directly triggers the event for the control identified by its
155+
/// [id] without verifying if there are any subscribers for the event.
156+
///
157+
/// - [eventName]: The name of the event to trigger.
158+
/// - [data]: Optional data to pass along with the event.
159+
void triggerEventWithoutSubscribers(String eventName, [dynamic data]) {
160+
return backend.triggerControlEventById(id, eventName, data);
161+
}
162+
152163
/// Updates the properties of this control.
153164
///
154165
/// The [props] map contains key-value pairs where the key is the property

packages/flet/lib/src/utils/session_store_non_web.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import 'package:flutter/foundation.dart';
33
class SessionStore {
44
static String? sessionId;
55

6-
static String? getSessionId(String pageUrl) {
6+
static String? getSessionId() {
77
return get("sessionId");
88
}
99

10-
static setSessionId(String pageUrl, String? value) {
10+
static setSessionId(String? value) {
1111
set("sessionId", value ?? "");
1212
}
1313

packages/flet/lib/src/utils/session_store_web.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import 'package:web/web.dart' as web;
44
const String _sessionIdKey = "_flet_session_id";
55

66
class SessionStore {
7-
static String? getSessionId(String pageUrl) {
8-
return get("$pageUrl$_sessionIdKey");
7+
static String? getSessionId() {
8+
return get(_sessionIdKey);
99
}
1010

11-
static setSessionId(String pageUrl, String? value) {
12-
set("$pageUrl$_sessionIdKey", value ?? "");
11+
static setSessionId(String? value) {
12+
set(_sessionIdKey, value ?? "");
1313
}
1414

1515
static String? get(String name) {

sdk/python/examples/apps/declarative/counter.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,32 @@
44

55

66
@dataclass
7+
@ft.observable
78
class AppState:
89
count: int
910

1011
def increment(self):
1112
self.count += 1
1213

1314

14-
def main(page: ft.Page):
15-
state = AppState(count=0)
15+
@ft.component
16+
def App():
17+
state, _ = ft.use_state(AppState(count=0))
1618

17-
page.floating_action_button = ft.FloatingActionButton(
18-
icon=ft.Icons.ADD, on_click=state.increment
19-
)
20-
page.add(
21-
ft.StateView(
22-
state,
23-
lambda state: ft.SafeArea(
19+
return ft.View(
20+
floating_action_button=ft.FloatingActionButton(
21+
icon=ft.Icons.ADD, on_click=state.increment
22+
),
23+
controls=[
24+
ft.SafeArea(
2425
ft.Container(
2526
ft.Text(value=f"{state.count}", size=50),
2627
alignment=ft.Alignment.CENTER,
2728
),
2829
expand=True,
29-
),
30-
expand=True,
31-
)
30+
)
31+
],
3232
)
3333

3434

35-
ft.run(main)
35+
ft.run(lambda page: page.render_views(App))

sdk/python/examples/apps/declarative/edit-form.py

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66

77
@dataclass
8+
@ft.observable
89
class Form:
910
first_name: str = ""
1011
last_name: str = ""
@@ -15,7 +16,7 @@ def set_first_name(self, value):
1516
def set_last_name(self, value):
1617
self.last_name = value
1718

18-
async def submit(self, e: ft.ControlEvent):
19+
async def submit(self, e: ft.Event[ft.Button]):
1920
e.page.show_dialog(
2021
ft.AlertDialog(
2122
title="Hello",
@@ -28,37 +29,31 @@ async def reset(self):
2829
self.last_name = ""
2930

3031

31-
def main(page: ft.Page):
32-
form = Form()
33-
34-
page.add(
35-
ft.StateView(
36-
form,
37-
lambda state: ft.Column(
38-
cast(
39-
list[ft.Control],
40-
[
41-
ft.TextField(
42-
label="First name",
43-
value=form.first_name,
44-
on_change=lambda e: form.set_first_name(e.control.value),
45-
),
46-
ft.TextField(
47-
label="Last name",
48-
value=form.last_name,
49-
on_change=lambda e: form.set_last_name(e.control.value),
50-
),
51-
ft.Row(
52-
[
53-
ft.FilledButton("Submit", on_click=form.submit),
54-
ft.FilledTonalButton("Reset", on_click=form.reset),
55-
]
56-
),
57-
],
58-
)
59-
),
60-
)
61-
)
32+
@ft.component
33+
def App():
34+
form, _ = ft.use_state(Form())
35+
36+
return [
37+
ft.TextField(
38+
label="First name",
39+
value=form.first_name,
40+
on_change=lambda e: form.set_first_name(e.control.value),
41+
),
42+
ft.TextField(
43+
label="Last name",
44+
value=form.last_name,
45+
on_change=lambda e: form.set_last_name(e.control.value),
46+
),
47+
ft.Row(
48+
cast(
49+
list[ft.Control],
50+
[
51+
ft.FilledButton("Submit", on_click=form.submit),
52+
ft.FilledTonalButton("Reset", on_click=form.reset),
53+
],
54+
)
55+
),
56+
]
6257

6358

64-
ft.run(main)
59+
ft.run(lambda page: page.render(App))
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import asyncio
2+
from dataclasses import dataclass
3+
4+
import flet as ft
5+
6+
7+
@dataclass
8+
@ft.observable
9+
class AppState:
10+
counter: float
11+
12+
async def start_counter(self):
13+
self.counter = 0
14+
for _ in range(0, 10):
15+
self.counter += 0.1
16+
await asyncio.sleep(0.5)
17+
18+
19+
@ft.component
20+
def App():
21+
state, _ = ft.use_state(AppState(counter=0))
22+
23+
return [
24+
ft.ProgressBar(state.counter),
25+
ft.Button("Run!", on_click=state.start_counter),
26+
]
27+
28+
29+
ft.run(lambda page: page.render(App))

sdk/python/examples/apps/declarative/progress-with-yield.py

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)