|
| 1 | +# Migration from robocorp-flet to Official Flet |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The `rpaframework-assistant` package was migrated from `robocorp-flet==0.4.2.3` (an outdated fork of flet 0.4.2) to the official `flet>=0.82.0,<1.0.0` package. This was necessary because the fork was no longer maintained and fell far behind upstream flet, which has undergone major breaking changes — most notably the [v0.80 "1.0 Alpha" overhaul](https://flet.dev/blog/flet-v0-80-release-announcement) that merged `flet_core` into `flet` and redesigned many APIs. |
| 6 | + |
| 7 | +## Reason for Migration |
| 8 | + |
| 9 | +- **`robocorp-flet` is unmaintained** — pinned to a fork of flet 0.4.2 with no further updates. |
| 10 | +- **Security and compatibility** — staying on an outdated fork blocks Python version upgrades and misses upstream fixes. |
| 11 | +- **`flet_core` was removed** — flet 0.80+ merged `flet_core` into `flet`, making the old import paths (`from flet_core import ...`) invalid. |
| 12 | + |
| 13 | +## Python Version Requirement |
| 14 | + |
| 15 | +`requires-python` was bumped from `>=3.9.1` to `>=3.10` because flet 0.82+ requires Python 3.10 at minimum. |
| 16 | + |
| 17 | +## Major Changes by File |
| 18 | + |
| 19 | +### `background_flet.py` — Full Rewrite |
| 20 | + |
| 21 | +**Why:** The old code relied on private flet internals that were completely removed: |
| 22 | +- `ft.__connect_internal_sync` — internal connection setup |
| 23 | +- `ft.open_flet_view` / `ft.close_flet_view` — subprocess-based window management |
| 24 | +- `flet_core.page.Connection` — internal connection class |
| 25 | + |
| 26 | +**New approach:** |
| 27 | +- Runs `ft.app(target=...)` in a daemon `threading.Thread` |
| 28 | +- Patches `signal.signal` to no-op in the thread (signal handlers can't be registered outside the main thread) |
| 29 | +- Uses `threading.Event` for close detection (replaces `subprocess.Popen.poll()`) |
| 30 | +- `close_flet_view()` calls `page.window.destroy()` via `asyncio.run_coroutine_threadsafe` instead of subprocess termination |
| 31 | +- `start_flet_view()` has a 30-second timeout on page readiness to prevent indefinite hangs if flet fails to start |
| 32 | +- `poll()` returns `None` (still open) or `0` (closed), preserving the existing interface |
| 33 | + |
| 34 | +**Reference:** [Flet Page Window events](https://flet.dev/docs/controls/page#window) |
| 35 | + |
| 36 | +### `flet_client.py` — Window API and Service Changes |
| 37 | + |
| 38 | +**Window properties renamed** (flet 0.80+ change): |
| 39 | + |
| 40 | +| Old (0.4.x) | New (0.82+) | |
| 41 | +|---|---| |
| 42 | +| `page.window_height` | `page.window.height` | |
| 43 | +| `page.window_width` | `page.window.width` | |
| 44 | +| `page.window_always_on_top` | `page.window.always_on_top` | |
| 45 | +| `page.window_center()` | `page.window.center()` | |
| 46 | +| `page.window_left` | `page.window.left` | |
| 47 | +| `page.window_top` | `page.window.top` | |
| 48 | +| `page.window_destroy()` | `page.window.destroy()` | |
| 49 | + |
| 50 | +**Reference:** [Flet Window properties migration](https://flet.dev/docs/controls/page#window) |
| 51 | + |
| 52 | +**FilePicker moved from overlay to services:** |
| 53 | +`FilePicker` is now a `Service` subclass. Invisible elements that are services are added to `page.services` instead of `page.overlay`. |
| 54 | + |
| 55 | +**Reference:** [Flet Services](https://flet.dev/docs/controls/filepicker) |
| 56 | + |
| 57 | +**Dropdown `on_change` renamed to `on_select`:** |
| 58 | +The `Dropdown` control no longer has `on_change`. The equivalent event handler is `on_select`. Other controls (`TextField`, `Checkbox`, `Slider`, `RadioGroup`) still use `on_change`. |
| 59 | + |
| 60 | +**Thread-safe `page.update()` via `flet_update()`:** |
| 61 | +In flet 0.82+, calling `page.update()` from a thread other than the flet event loop thread corrupts flet's internal state and prevents `window.destroy()` from closing the app. The `flet_update()` method now detects whether it's being called from the flet thread or not, and routes cross-thread calls through `loop.call_soon_threadsafe(page.update)`. This is critical for button callbacks, which are queued as `pending_operation` and executed on the main thread. |
| 62 | + |
| 63 | +**AppBar.actions defaults to None:** |
| 64 | +In the old flet, `AppBar.actions` was initialized as an empty list. In 0.82+, it defaults to `None` and must be initialized before appending. |
| 65 | + |
| 66 | +### `library.py` — API Renames |
| 67 | + |
| 68 | +**Renamed modules:** |
| 69 | + |
| 70 | +| Old | New | |
| 71 | +|---|---| |
| 72 | +| `from flet_core import ...` | `from flet import ...` | |
| 73 | +| `from flet_core.control_event import ControlEvent` | `from flet import ControlEvent` | |
| 74 | +| `from flet_core.dropdown import Option` | `from flet import DropdownOption` | |
| 75 | +| `colors` (module) | `Colors` (enum) | |
| 76 | +| `icons` (module) | `Icons` (enum) | |
| 77 | + |
| 78 | +**Icon constructor:** |
| 79 | +`flet.Icon(name=...)` changed to `flet.Icon(icon=...)`. |
| 80 | + |
| 81 | +**Icon names must use `Icons` enum:** |
| 82 | +In flet 0.82+, passing icon names as lowercase strings (e.g. `"check_circle_rounded"`) no longer renders correctly — the icon appears as a blank white area. String names must be converted to the `Icons` enum via `getattr(Icons, icon.upper(), icon)`. The `add_icon` method already used `Icons` enum constants; `add_flet_icon` was updated to convert user-provided strings. |
| 83 | + |
| 84 | +**FilePicker API change:** |
| 85 | +The old callback-based `FilePicker(on_result=callback)` pattern was replaced. In flet 0.82+, `pick_files()` is async and returns a list of `FilePickerFile` directly. The `FilePickerResultEvent` class was removed. The click handler must be `async def` and `await` the `pick_files()` call. |
| 86 | + |
| 87 | +**FilePicker service registration:** |
| 88 | +`page.services.append()` is a plain list that doesn't mount controls. Must use `page._services.register_service()` to properly register services like FilePicker so they get a page reference. |
| 89 | + |
| 90 | +**`page.launch_url()` is now async:** |
| 91 | +In flet 0.82+, `page.launch_url()` is a coroutine. Event handlers that call it must wrap it with `asyncio.ensure_future()` to schedule it on the event loop. |
| 92 | + |
| 93 | +**Reference:** [Flet FilePicker](https://flet.dev/docs/controls/filepicker) |
| 94 | + |
| 95 | +### `types.py` — Alignment Constants Removed |
| 96 | + |
| 97 | +The convenience constants `alignment.center`, `alignment.top_left`, etc. were removed. Replaced with explicit `Alignment(x, y)` values: |
| 98 | + |
| 99 | +| Old constant | New value | |
| 100 | +|---|---| |
| 101 | +| `alignment.top_left` | `Alignment(-1, -1)` | |
| 102 | +| `alignment.top_center` | `Alignment(0, -1)` | |
| 103 | +| `alignment.top_right` | `Alignment(1, -1)` | |
| 104 | +| `alignment.center_left` | `Alignment(-1, 0)` | |
| 105 | +| `alignment.center` | `Alignment(0, 0)` | |
| 106 | +| `alignment.center_right` | `Alignment(1, 0)` | |
| 107 | +| `alignment.bottom_left` | `Alignment(-1, 1)` | |
| 108 | +| `alignment.bottom_center` | `Alignment(0, 1)` | |
| 109 | +| `alignment.bottom_right` | `Alignment(1, 1)` | |
| 110 | + |
| 111 | +**Reference:** [Flet Alignment](https://flet.dev/docs/controls/container#alignment) |
| 112 | + |
| 113 | +### `library.py` — State Cleanup Between Dialogs |
| 114 | + |
| 115 | +After each `run_dialog` / `ask_user` call, the following state is now cleared to prevent leaks between sequential dialogs: |
| 116 | +- `results` — user input values |
| 117 | +- `date_inputs` — names of date fields (for string→date conversion) |
| 118 | +- `_required_fields` — names of required fields |
| 119 | +- `_open_layouting` — layout stack (Row/Column/Stack/Container/AppBar) |
| 120 | +- `validation_errors` — validation error messages |
| 121 | + |
| 122 | +**Date input empty value handling:** |
| 123 | +`_get_results()` now returns `None` for empty date fields instead of crashing with `ValueError: Invalid isoformat string: ''`. |
| 124 | + |
| 125 | +### `callback_runner.py` — Import Path Only |
| 126 | + |
| 127 | +Changed `from flet_core import ControlEvent` to `from flet import ControlEvent`. No logic changes. |
| 128 | + |
| 129 | +## Files Modified |
| 130 | + |
| 131 | +| File | Change scope | |
| 132 | +|---|---| |
| 133 | +| `packages/assistant/pyproject.toml` | Dependency + Python version | |
| 134 | +| `packages/assistant/src/RPA/Assistant/background_flet.py` | Full rewrite, startup timeout | |
| 135 | +| `packages/assistant/src/RPA/Assistant/callback_runner.py` | Import path | |
| 136 | +| `packages/assistant/src/RPA/Assistant/flet_client.py` | Window API, services, Dropdown event, thread-safe update | |
| 137 | +| `packages/assistant/src/RPA/Assistant/library.py` | Renames, FilePicker (async), Icon enum, launch_url async, state cleanup, date input empty handling | |
| 138 | +| `packages/assistant/src/RPA/Assistant/types.py` | Import path, alignment constants | |
| 139 | + |
| 140 | +## Future Considerations |
| 141 | + |
| 142 | +### Improve window close error handling |
| 143 | + |
| 144 | +`close_flet_view()` in `background_flet.py` uses a broad `except Exception: pass` that silently swallows all errors from `window.destroy()`. This can mask real failures and make the dialog appear closed when the native window is still open. A future improvement would be to log the error instead of silently ignoring it, and ensure `_closed_event` is only set after the close operation actually succeeds or a real close/disconnect event is observed. Note that `close_flet_view` also serves as an `atexit` handler, so error propagation must be handled carefully. |
| 145 | + |
| 146 | +### Replace private flet service internals with public API |
| 147 | + |
| 148 | +`flet_client.py` uses `page._services.register_service()` and `page._services.unregister_services()` — private flet internals that may change without notice. This was necessary because the public `page.services.append()` does not properly mount controls (the `FilePicker` fails with "Control must be added to the page first"). If a future flet version provides a public service registration API, these calls should be migrated. Until then, this is documented tech debt. |
| 149 | + |
| 150 | +### Expose additional flet layout properties |
| 151 | + |
| 152 | +The Assistant library keywords expose only a subset of the underlying flet control properties. The following are available in flet 0.82+ and would be useful additions: |
| 153 | + |
| 154 | +**Quick wins — high impact, easy to add:** |
| 155 | + |
| 156 | +| Keyword | Property | Description | |
| 157 | +|---|---|---| |
| 158 | +| `open_row` | [`alignment`](https://flet.dev/docs/controls/row#alignment) | Position children along main axis (START, END, CENTER, SPACE_BETWEEN) | |
| 159 | +| `open_row` | [`vertical_alignment`](https://flet.dev/docs/controls/row#vertical_alignment) | Align items vertically within the row | |
| 160 | +| `open_row` | [`spacing`](https://flet.dev/docs/controls/row#spacing) | Gap between children (default 10) | |
| 161 | +| `open_column` | [`horizontal_alignment`](https://flet.dev/docs/controls/column#horizontal_alignment) | Left/center/right align content | |
| 162 | +| `open_column` | [`alignment`](https://flet.dev/docs/controls/column#alignment) | Vertical distribution of children | |
| 163 | +| `open_column` | [`spacing`](https://flet.dev/docs/controls/column#spacing) | Gap between children | |
| 164 | +| `open_container` | [`alignment`](https://flet.dev/docs/controls/container#alignment) | Position content within container | |
| 165 | +| `open_navbar` | [`bgcolor`](https://flet.dev/docs/controls/appbar#bgcolor) | Navbar background color | |
| 166 | + |
| 167 | +**Medium priority:** |
| 168 | + |
| 169 | +| Keyword | Property | Description | |
| 170 | +|---|---|---| |
| 171 | +| `open_row` / `open_column` | [`wrap`](https://flet.dev/docs/controls/row#wrap) | Wrap children to next line when out of space | |
| 172 | +| `open_container` | [`border_radius`](https://flet.dev/docs/controls/container#border_radius) | Rounded corners | |
| 173 | +| `open_container` | [`border`](https://flet.dev/docs/controls/container#border) | Border styling (color, width) | |
| 174 | +| `open_container` | [`shadow`](https://flet.dev/docs/controls/container#shadow) | Drop shadow effect | |
| 175 | +| `open_navbar` | [`center_title`](https://flet.dev/docs/controls/appbar#center_title) | Center the title text | |
| 176 | +| `open_navbar` | [`leading`](https://flet.dev/docs/controls/appbar#leading) | Back button or menu icon | |
| 177 | +| `open_stack` | [`alignment`](https://flet.dev/docs/controls/stack#alignment) | Default position for non-positioned children | |
| 178 | +| `add_submit_buttons` | [`icon`](https://flet.dev/docs/controls/elevatedbutton#icon) | Icons on buttons | |
| 179 | + |
| 180 | +**Advanced:** |
| 181 | + |
| 182 | +| Keyword | Property | Description | |
| 183 | +|---|---|---| |
| 184 | +| `open_container` | [`gradient`](https://flet.dev/docs/controls/container#gradient), [`image`](https://flet.dev/docs/controls/container#image) | Background gradient or image | |
| 185 | +| `open_stack` | [`fit`](https://flet.dev/docs/controls/stack#fit), [`clip_behavior`](https://flet.dev/docs/controls/stack#clip_behavior) | Child sizing strategy, overflow clipping | |
| 186 | +| `add_submit_buttons` | [`style`](https://flet.dev/docs/controls/elevatedbutton#style) | Per-button colors, elevation, shape | |
| 187 | + |
| 188 | +## External References |
| 189 | + |
| 190 | +- [Flet 0.80 Release Announcement](https://flet.dev/blog/flet-v0-80-release-announcement) — breaking changes overview |
| 191 | +- [Flet Documentation](https://flet.dev/docs/) — official docs |
| 192 | +- [Flet Page / Window API](https://flet.dev/docs/controls/page#window) — window property changes |
| 193 | +- [Flet FilePicker](https://flet.dev/docs/controls/filepicker) — new FilePicker API |
| 194 | +- [Flet PyPI](https://pypi.org/project/flet/) — package versions |
0 commit comments