github flet-dev/flet v1.0.0

3 hours ago

New features

  • Client actions: an action property that performs gesture-gated work without a round trip to Python. Browsers only let a page open a file picker, write to the clipboard, show a share sheet or open a new tab while they are still handling the user's click or key press. Sending that click to your Python code and acting on the reply takes longer than the permission lasts, so on iOS Safari those operations were silently ignored while Android and desktop browsers let them through - which made a browser rule look like a Flet bug. Button, Container, IconButton, ListTile, CupertinoButton, CupertinoListTile, FloatingActionButton, OutlinedButton, TextButton and TextSpan now accept action - a single ClientAction or a list of them - which the client performs inside the original gesture, before your on_click handler is even notified: ft.Button("Open", action=ft.OpenUrl("https://flet.dev", target=ft.UrlTarget.BLANK)). Because an action runs before your code sees the click, its arguments have to be known in advance; to act on a value computed at click time, set it on the control ahead of the click. url is unchanged and keeps working exactly as before by @FeodorFitsner.
  • App icon and desktop integration for flet build linux. Linux was the one platform flet build shipped without an icon — flutter_launcher_icons has no Linux generator, so assets/icon.png was silently ignored and built apps ran with a generic icon. The resolved icon (icon_linux.png, falling back to icon.png or the default Flet icon) is now bundled as data/app_icon.png and set as the window icon on startup, which taskbars and window switchers pick up on X11 and XWayland. Because Wayland has no window-icon protocol — desktops resolve icons from an installed desktop entry matching the app id — the bundle also ships a ready-to-install freedesktop tree: share/applications/<bundle_id>.desktop (name from --product, comment from --description, StartupWMClass set) plus the icon at share/icons/hicolor/<size>/apps/<bundle_id>.png. The entry's application categories default to Utility and are configurable with --linux-categories or [tool.flet.linux].categories, and the runner now sets its program name to the bundle ID (matching upstream Flutter's runner template) so the running app maps to that entry on both Wayland and X11. See the new App icon docs (#2269) by @ndonkoHenri.
  • macOS code signing, notarization, and Mac App Store builds in flet build macos. Select a distribution lane with --macos-distribution (or [tool.flet.macos.signing].distribution): developer-id signs every bundled binary with your Developer ID certificate — hardened runtime, entitlements, secure timestamp — then notarizes and staples the app for direct distribution, while app-store produces a sandboxed app with your provisioning profile embedded, packaged into an installer-signed .pkg ready for App Store Connect and TestFlight. Signing identities are auto-discovered from the keychain when not explicitly configured (via CLI options, pyproject.toml — including per-lane [tool.flet.macos.signing.<lane>] subtables — or environment variables), and the whole configuration is validated before the build starts, so a typo'd identity, expired certificate, or missing store prerequisite fails in seconds instead of after the full build. See the new Code signing, Notarization, and Mac App Store docs (#2347, #4543, #6702) by @ndonkoHenri.
  • Add flet-local-auth extension with a LocalAuthentication service for on-device biometric and credential authentication on Android, iOS, macOS, and Windows via the local_auth Flutter plugin. Linux and Web are not supported (#3192, #6823).

Improvements

  • Built apps no longer segfault on exit, and now terminate immediately without running process teardown. An app whose Python code was still running when the process ended could crash with EXC_BAD_ACCESS - reported by the OS as an application crash even though the app had finished its work. Your Python code runs on its own thread alongside Flutter, and a normal process exit runs __cxa_finalize (DLL_PROCESS_DETACH on Windows), destroying the C++ statics inside every loaded C extension module while that thread is still executing inside one of them; the reported case died in matplotlib's ft2font looking up a pybind11 type-caster map that had just been destructed, but numpy, Pillow and Flutter's own Skia statics are torn down by the same pass. Both exit paths were affected: closing the window on desktop, and sys.exit() on every native platform - the latter is the worse of the two, because Flet's sys.exit posts the exit code to the Dart side and returns, so the interpreter is still fully alive (running on into Py_Finalize()) when Dart tears the process down. The desktop runners now _exit (TerminateProcess on Windows, since _exit/ExitProcess still run DLL_PROCESS_DETACH and would not help), and the sys.exit path routes through a new serious_python_hard_exit in dart_bridge 1.9.0, reached via serious_python 4.7.0 with the bundled python-build snapshot re-pinned to 20260908 (no CPython or Pyodide versions change). Exit codes are preserved. The trade-off is now a documented contract: Python atexit handlers, __del__ finalizers and unflushed buffered writes are not guaranteed to run on exit - persist what matters before exiting rather than relying on shutdown cleanup. SharedPreferences writes are unaffected and were verified to survive both paths. See How a built app terminates by @FeodorFitsner.
  • flet run -v now turns on the framework's own logging, and -vv raises it to debug. The flag was accepted and documented as "enable verbose output", but nothing mapped it to a log level, so every logger.info in Flet's web transport — web root, assets directory, session and upload activity — was unreachable and the command printed nothing at all. flet run passes the level to the app through the new FLET_LOG_LEVEL variable; an app that configures logging itself keeps the setup it chose. The web root now also names FLET_WEB_PATH when that is where it came from, matching what the desktop client already reports for FLET_VIEW_PATH. flet run itself now configures logging too, not just the app it starts, so the desktop client resolution — which of build/<platform>, FLET_VIEW_PATH or the cached client won — is visible as well (#6835) by @ndonkoHenri.
  • FLET_WEB_PATH now works with a flet build web output, so an app with third-party extensions can be served by flet run --web and ft.run(export_asgi_app=True). Those commands serve the prebuilt web client shipped in the flet-web package, whose extension list is fixed when Flet is released — so a third-party control renders as Unknown control: <Type> there, however the app was built. Pointing FLET_WEB_PATH at a built client should have solved that, but a built page bakes flet.pyodide = true into its config and carried no injection point, so it kept trying to start Python in the browser and never connected. The build template now carries the injection marker, and the runtime config states the mode explicitly instead of relying on the page's own default — mirroring flet run on desktop, which already prefers a client from a previous flet build (#6835) by @ndonkoHenri.
  • Bulk byte traffic from a web app's DataChannels is no longer structured-cloned on its way to the Python worker. The Dart side has always built a postMessage transfer list from the packet's own buffer, but the JavaScript jsSend it calls accepted only two parameters, so the third was dropped and every frame was copied. jsSend now takes the transfer list and hands it to postMessage, making the hand-off zero-copy as intended; each packet is freshly allocated per send and never read back, so detaching the buffer is safe (#6829) by @ndonkoHenri.
  • flet build web no longer compiles and ships a dart2wasm build that the page can never load. Flutter emits that output for the skwasm renderer only, while the generated flutter_bootstrap.js pins flutterConfig.renderer to whatever web_renderer resolved to — so under the default canvaskit the loader skipped it on every page load, leaving main.dart.wasm and main.dart.mjs (~7.3 MB) in the output as files nothing requests. --wasm is now passed only when the resolved renderer is auto or skwasm, the two cases where the loader can actually select it; --no-wasm and [tool.flet.web] wasm = false are unchanged (#6828) by @ndonkoHenri.
  • The Flet web client now shows a static loading logo instead of the breathing and zoom animation. Dynamic websites can still replace it by supplying icons/loading-animation.png in the assets directory. The logo disappears on Flutter's first frame; [tool.flet.boot_screen] configures the subsequent startup screen (#6824) by @FeodorFitsner.
  • flet run --web no longer logs assets_dir does not exist: ... for an app that simply has no assets directory. assets_dir defaults to "assets" whether or not the app has one, so the resolved path was handed downstream regardless — the desktop view ignored it silently while the web server complained, which is why the same app warned only with --web, about a directory the user never asked for. A resolved path that does not exist is now dropped at the source, making both views behave the same. A path set explicitly through FLET_ASSETS_DIR, or passed straight to FletStaticFiles when mounting on FastAPI, is always deliberate and still reports a missing directory by @FeodorFitsner.
  • New Flet logo, and a fix for Android launcher icons that were silently being clipped. Every icon in the repo is now derived from a single master by a committed generator (.github/scripts/generate_brand_assets.py) instead of being maintained by hand across three pipelines. That surfaced a real defect in what flet build shipped: the default icon.png framed the mark at 72.9% of the canvas, outside the 66.7% that Android guarantees is visible in an adaptive icon's foreground layer — so every flet build apk produced a launcher icon cropped under circular masks, and the Android 12 splash, which clips to a circle of the same ratio, lost its edges too. The default is now framed at 60% and both render whole. Two knock-on changes worth knowing about: flet create no longer ships assets/splash_android.png, because icon.png now fits the splash circle on its own and the extra file silently won the fallback chain — replacing only icon.png gave you your own launcher icon but kept the Flet logo on the splash; and the default PWA theme_color moved from #0175C2 (Flutter's stock blue, which matched no Flet brand colour) to #FF005F, still overridable with --pwa-theme-color or [tool.flet.web].pwa_theme_color (#6816) by @FeodorFitsner.
  • flet build generates app icons and splash screens itself, replacing flutter_launcher_icons and flutter_native_splash. Both Dart tools are gone from generated projects, along with the two pubspec.yaml config blocks that fed them; generation now runs through a new flet-platform-assets package (Pillow only, no other dependencies), and everything whose content is fixed at render time ships as a template file so the generator writes only pixels. This fixes a splash that was already broken, not just a quality gap: cookiecutter re-renders the project with overwrite_if_exists=True after the Dart tools run, restoring every file the template ships, while the change-detection stamp still recorded the earlier run — so generation was skipped on every later build and the reverted files stayed reverted. The result was that LaunchImage{,@2x,@3x}.png stayed 1x1 placeholders and Contents.json stayed light-only (the correctly generated LaunchImageDark* files were left orphaned, referenced by nothing), and launch_background.xml stayed stock white — so iOS showed no launch image at all and Android below 12 showed a plain white screen; only Android 12+ worked, because values-v31/styles.xml happens to be the one file the template does not ship. Alongside the fix, several long-standing output defects go away: web/favicon.png is 32x32 instead of a hardcoded 16x16; macOS icons get Apple's inset squircle and drop shadow instead of a plain full-bleed resize; maskable web icons are opaque and actually differ from the normal ones instead of being byte-identical copies; the Windows .ico carries 16/32/48/256 instead of a single 256 entry; apple-touch-icon-192.png is generated at last, though index.html has always linked to it; the Android 12 splash icon is fitted to the circle the platform crops it to, on the canvas the platform specifies, instead of being passed through for the user to pad by hand; web splash images are always .png, so a .webp source no longer produces a <picture> srcset pointing at files that were never written; and every downscale is done with premultiplied alpha, which removes the dark halo that any logo with soft transparent edges used to pick up. Splash bitmap sizes are unchanged and asserted against a real flutter_native_splash build, so existing apps keep the on-screen size they have today by @FeodorFitsner.
  • flet create now ships a full-bleed assets/icon.png, and flet build linux gets the default icon it was missing. The template icon framed the mark at 60% of its canvas, which spent about 40% of every favicon, Windows .ico entry and Linux icon on empty space — those three surfaces apply no mask at all. It is now edge-to-edge, and flet build computes the margin iOS, macOS and Android need, so a new app's favicon fills its 32 pixels instead of two thirds of them. The build template also ships a Linux icon theme for the first time: an app that supplies no icon of its own already fell back to the Flet logo on iOS, macOS, Windows, web and Android, but produced a Linux bundle with no icon at all. The Flet client's own Linux icons are framed tight for the same reason by @FeodorFitsner.
  • One assets/icon.png now works everywhere: Flet computes each platform's margin instead of asking you to pick one. No single framing can satisfy every platform - web, Windows and Linux apply no mask and want the artwork edge to edge, while iOS, macOS and Android each mask the edges away and need margin - so an icon padded to survive Android wasted about 40% of every favicon, and a full-bleed one lost 24% of itself to Android's circular mask. Supply a full-bleed icon.png and it is used as-is on the unmasked platforms, then shrunk to suit each of the other three: 60% of the canvas for iOS, 68% for macOS (which Flet then insets into the squircle tile, landing at 55%), and for Android to 85% of the launcher's mask radius, measured radially because a circle clips artwork that passes an axis test. The web needs all three cases at once, so its maskable icons are framed to the safe zone the spec defines — a circle 80% of the icon's width — and apple-touch-icon, which becomes an iOS home-screen icon, is framed exactly like the native one, while the favicon and the plain Icon-*.png keep every pixel because nothing masks them. Framing only ever shrinks, so an icon you already padded is untouched, and it is skipped entirely for an opaque source, which is a finished icon rather than a glyph on a canvas and would otherwise be ringed with background colour. Supplying icon_ios.png, icon_macos.png or icon_android.png opts that platform out completely and uses your file exactly as given. flet build also warns when a source is smaller than the largest icon it is about to make — 1024px for iOS and macOS, less elsewhere — since anything below that is enlarged and looks soft, most visibly in an App Store listing by @FeodorFitsner.
  • An installed PWA's launch background now follows your splash colour. [tool.flet.web] pwa_background_color falls through to [tool.flet.splash] color instead of defaulting to white on its own. A browser paints the in-page splash from the page, but an installed PWA paints its launch screen from manifest.json, so the two were driven by different keys — colouring your splash left a white flash in front of it on the home screen by @FeodorFitsner.
  • [tool.flet] icon_background sets the colour behind your icon wherever transparency cannot survive. assets/icon.png is meant to be transparent, but three surfaces reject an alpha channel: iOS, because the App Store checks for one; the macOS tile, which has to be opaque to read as a tile at all; and the maskable web icons, which render with black corners on some Android launchers otherwise. All three were hardcoded to white while Android's equivalent (adaptive_icon_background) was configurable, so an app with a dark brand had no way to avoid a white square on Apple platforms. The new key resolves platform-specific over global — [tool.flet.macos] icon_background overrides [tool.flet] icon_background — matching how the splash colours already resolve, and defaults to white so nothing changes for anyone who does not set it. Android's adaptive-icon background falls through to it as well, so one colour covers every platform; [tool.flet.android] adaptive_icon_background still overrides it. An unparsable colour warns and falls back rather than failing the build by @FeodorFitsner.
  • flet build linux ships a proper icon theme, and flet run on Linux finally has a window icon. flutter_launcher_icons never had a Linux generator — requested since 2022 — so flet build linux installed a single file, often a 1024px image, into hicolor/256x256/; a directory that claims one size while holding another is rescaled wrongly by the icon cache, and every small panel size was downscaled from it on the fly. Each of the eight hicolor sizes is now rendered at the size its directory declares. Separately, the Flet client itself (flet run) shipped no Linux icon at all and its runner had no icon reference, so it has always shown a generic placeholder; it now carries the same generated icon set and sets its program name to the application id, so desktop environments map the running app to its desktop entry by @FeodorFitsner.
  • FilePicker gained an on_result event, called when files are selected through a PickFiles action. A PickFiles action opens the dialog on the client before your code sees the click, so the selection cannot be returned to the caller the way pick_files() returns it - it arrives here instead. The picked files stay associated with the FilePicker, so they can be passed straight to upload() by @FeodorFitsner.
  • flet build ipa now validates the configured provisioning profile before the build starts, instead of letting Xcode fail at the signing step minutes later with "No profile for team 'X' matching 'Y' found" — a message that cannot say what is installed. The profile is resolved the same way Xcode's PROVISIONING_PROFILE_SPECIFIER does (by name or UUID, across both directories Xcode reads), and is additionally checked for expiry, team match, and bundle-id coverage. A name that matches nothing now fails in seconds, listing the installed profiles with their teams and UUIDs so a typo — or a profile that was downloaded but never installed — is immediately obvious (#5100, #6796) by @ndonkoHenri.
  • flet build ipa now reports the artifact it actually produced. An unsigned build yields only an .xcarchive, yet the command announced "Successfully built your .ipa bundle" and pointed at an output directory holding no .ipa; it now names the .xcarchive and explains that Xcode exports an .ipa only for a signed app. A failed export is also caught properly: flutter build ipa exits 0 when Xcode's export step fails, and the existing check for it inspected captured output — which is empty whenever -v is used, so verbose builds reported the failure as success. The check now looks for the .ipa itself (#6796) by @ndonkoHenri.
  • Web builds and flet publish now read the FLET_WEB_RENDERER, FLET_WEB_ROUTE_URL_STRATEGY, and FLET_WEB_NO_CDN environment variables as fallbacks behind the CLI options and [tool.flet.web] pyproject keys, matching the [env: ...] notation the options already advertised, by @ndonkoHenri.
  • flet build web and flet publish no longer bundle CanvasKit and Pyodide when CDN mode is on (the default), taking a minimal web build from 71 MB to 19 MB. In CDN mode Flutter loads CanvasKit from gstatic.com and Flet points pyodideUrl at jsdelivr, so both copies were dead weight the browser never requested — yet flutter build web always emits canvaskit/ (~37 MB), and ensure_pyodide() ran unconditionally in both commands, downloading and copying a further ~15 MB. Neither is fetched, so nothing about how a CDN-mode app loads changes; verified with a network log showing the built app pulling chromium/canvaskit.{js,wasm} from gstatic and the full Pyodide runtime from jsdelivr, with no request to a local canvaskit/ or pyodide/ path. --no-cdn (or [tool.flet.web] cdn = false) still bundles everything and is unchanged. flet build also clears a pyodide/ left in the reused Flutter project by an earlier --no-cdn build, so switching modes doesn't silently keep shipping it by @FeodorFitsner.
  • flet.canvasKitBaseUrl and flet.fontFallbackBaseUrl are now honored whenever they are set, instead of only when flet.noCdn is true. Previously flutter_bootstrap.js applied both inside if (flet.noCdn), so a host serving its own copy of the runtime — a CDN-restricted network, an air-gapped deployment, or a platform that mirrors the runtime on its own origin — had to also set noCdn for the assignment to take effect at all, and assigning the URL alone failed silently by booting off gstatic anyway. Where a runtime asset is fetched from is now independent of what the build bundled: both values default to null in CDN mode and are pinned by flet build/patch_index.py only when bundling by @FeodorFitsner.
  • flet_web.fastapi.app() now honors the FLET_ASSETS_DIR environment variable, as it already did for FLET_UPLOAD_DIR and as ft.run() does for both, so a deployment can point a mounted app at a different assets directory without editing code (#6792) by @ndonkoHenri.
  • Bumped serious_python to 4.6.0 and re-pinned the bundled python-build snapshot to 20260902, moving the bundled Python to 3.12.14 / 3.13.15 / 3.14.7 and Pyodide for 3.14 to 314.0.6. All three CPython micros are security releases — they fix a quadratic-complexity denial of service in incremental html.parser.HTMLParser parsing and the same in xml.etree.ElementTree XPath index predicates; 3.12.14 additionally bundles libexpat 2.8.3 for CVE-2026-72522, which 3.13.15 and 3.14.7 shipped a week too early to carry. serious_python 4.6.0 tracks the same python-build release, keeping PYTHON_BUILD_RELEASE_DATE in sync with its pythonReleaseDate as the pin requires (#6810) by @FeodorFitsner.
  • Android host apps now use FlutterFragmentActivity and AppCompat LaunchTheme / NormalTheme so biometric authentication dialogs work on API 24–27. A biometric cross-platform permission bundle adds NSFaceIDUsageDescription for iOS and macOS builds (#6823).

Breaking changes

  • Splash settings renamed for consistency, and the splash no longer changes size when you replace your icon. Three keys under [tool.flet.splash] describe the Android 12 splash icon and now say so: icon_bgcolor becomes icon_background, icon_dark_bgcolor becomes icon_dark_background, and android_12_fit becomes icon_fit. All three were undocumented, and the former names are still read so existing configuration keeps working. Every key under [tool.flet.splash] now also accepts a per-platform override at [tool.flet.<platform>.splash], which previously only the colours did. Separately, when no splash image is supplied the splash falls back to icon.png — and since an app icon fills its canvas by design, it was being drawn at full size, which made the splash artwork 67% larger than before. It is now framed for the splash, as it always effectively was when the default icon carried its own padding. An explicit splash.png is still used exactly as supplied, and opaque artwork is never resized on either path — including on the Android 12 splash icon, which previously shrank it into the middle of the circle instead of letting its colour bleed past, unlike the equivalent icon rule by @FeodorFitsner.
  • InputBorder is a class hierarchy instead of an enum, so enum-shaped usage no longer works: InputBorder is not iterable, its members have no .value or .name, and InputBorder.OUTLINE is InputBorder.OUTLINE is now False because each access returns a new instance — compare with ==. Assigning a border is unaffected; see the deprecations below. Rendering changes as well: a border with no explicit side now takes its color and weight from the Material theme per state instead of always painting black, so dark mode and custom themes work; an underline finally honors its border_radius; DropdownM2's open menu is shaped by the new menu_border_radius rather than by the field's radius; and on CupertinoTextField, NoInputBorder() now actually removes the border while an outline without a side keeps the native iOS one. See the InputBorder class hierarchy guide (#6773) by @ndonkoHenri.
  • Properties whose Flutter default is a fixed constant now declare that constant rather than Optional[...] = None, so reading one returns the value the control actually applies instead of None: the eight Paint style properties, RoundedRectangleBorder.radius, Button.autofocus, Text.no_wrap, GridView.clip_behavior, Semantics.container, ExpansionPanelList.spacing, TextField.fit_parent_size, Page.show_semantics_debugger, the three CupertinoAppBar.automatic* flags, Path.Rect.border_radius and canvas.Text.max_width. Rendering is unchanged, and properties a widget resolves at runtime from the theme, the platform or its own state keep None (#6773) by @ndonkoHenri.
  • iOS per-method signing settings in [tool.flet.ios.export_methods.<method>] (provisioning_profile, signing_certificate, export_options, team_id) now override the flat [tool.flet.ios] keys instead of being overridden by them. Previously a per-method value silently lost to the generic one, making the subtables useless whenever a flat key was also set; the flat key is now the shared fallback across methods — the same rule as the macOS [tool.flet.macos.signing.<lane>] subtables. See the iOS per-method signing precedence guide (#6702) by @ndonkoHenri.
  • Remove DragTargetEvent.x, DragTargetEvent.y, and DragTargetEvent.offset (deprecated in 0.85.0). Use DragTargetEvent.local_position for target-relative coordinates or DragTargetEvent.global_position for global coordinates (#6693) by @ndonkoHenri.
  • Remove Video.show_controls (deprecated in 0.85.0). Set Video.controls to None to hide controls (#6693) by @ndonkoHenri.
  • Remove Video.playlist_add() and Video.playlist_remove() (deprecated in 0.85.0). Mutate Video.playlist directly with list methods such as append() and pop() (#6693) by @ndonkoHenri.
  • Remove FletApp.show_app_startup_screen and FletApp.app_startup_screen_message (deprecated in 0.86.0). Use FletApp.boot_screen_options instead, e.g. boot_screen_options={'spinner_size': 30} or boot_screen_options={'startup_message': '...'} (#6693) by @ndonkoHenri.
  • Remove the --clear-cache flag of flet build and flet debug (deprecated in 0.86.0). Use the flet clean command instead (#6693) by @ndonkoHenri.
  • Remove Page.go() (deprecated in 0.80.0). Use Page.push_route() instead (#6693) by @ndonkoHenri.
  • Remove the Page.url_launcher, Page.browser_context_menu, Page.shared_preferences, Page.clipboard, and Page.storage_paths service accessors (deprecated in 0.80.0). Instantiate the corresponding service classes directly: UrlLauncher(), BrowserContextMenu(), SharedPreferences(), Clipboard(), StoragePaths() (#6693) by @ndonkoHenri.
  • Remove the ConstrainedControl base class (deprecated in 0.80.0). Inherit from LayoutControl instead (#6693) by @ndonkoHenri.
  • Remove ElevatedButton (deprecated in 0.80.0). Use Button instead (#6693) by @ndonkoHenri.
  • Remove the deprecated non-underscored Colors aliases (BLACK12, BLACK26, BLACK38, BLACK45, BLACK54, BLACK87, WHITE10, WHITE12, WHITE24, WHITE30, WHITE38, WHITE54, WHITE60, WHITE70). Use the underscored names instead (e.g. Colors.BLACK_12) (#6693) by @ndonkoHenri.
  • Remove app() and app_async() (deprecated in 0.80.0). Use run() and run_async() instead (#6693) by @ndonkoHenri.
  • Remove the target parameter of run() and run_async() (deprecated alias for main). Pass main instead (#6693) by @ndonkoHenri.
  • Remove Page.launch_url(), Page.can_launch_url(), and Page.close_in_app_web_view() (deprecated in 0.80.0). Use UrlLauncher().launch_url() / .can_launch_url() / .close_in_app_web_view() instead (#6693) by @ndonkoHenri.
  • Remove the legacy [tool.flet.app.boot_screen] / [tool.flet.app.startup_screen] build-config fallback. Use [tool.flet.boot_screen] with a named screen instead (#6693) by @ndonkoHenri.
  • Remove the Dart empty-string ("") widget-state key back-compat mapping. Use "default" (or ControlState.DEFAULT) instead (#6693) by @ndonkoHenri.
  • Remove flet.utils.cleanup_path(). It existed only to strip rival flutter/dart directories out of the PATH given to Flutter subprocesses, which is the cause of #5118 and no longer done; it has no remaining callers. (#6815) by @ndonkoHenri.

Deprecations

  • InputBorder.OUTLINE, InputBorder.UNDERLINE and InputBorder.NONE are deprecated in favor of OutlineInputBorder(), UnderlineInputBorder() and NoInputBorder(). Each still resolves, returning the equivalent instance, and they are scheduled for removal in 1.3.0. See the InputBorder class hierarchy guide (#6773) by @ndonkoHenri.
  • The border_radius, border_width, border_color, focused_border_width and focused_border_color properties of TextField, Dropdown, DropdownM2 and CupertinoTextField are deprecated in favor of border, which accepts an InputBorder or a ControlState dictionary. They keep working and are scheduled for removal in 1.3.0; where both are set, border wins. See the InputBorder class hierarchy guide (#6773) by @ndonkoHenri.
  • DropdownM2.border_radius is deprecated in favor of menu_border_radius for the open menu, or border for the input field. It is scheduled for removal in 1.3.0. See the InputBorder class hierarchy guide (#6773) by @ndonkoHenri.

Changed

  • DropdownM2 is no longer deprecated and remains a supported control; its 0.84.0 deprecation in favor of Dropdown has been reverted (#6693) by @ndonkoHenri.

Bug fixes

  • Fix flet build windows failing with PermissionError when removing previous build output containing read-only files, such as Git pack/index files (#6808, #6817) by @eminsk.
  • Fix a flet build web app showing a blank page forever when its Python program fails to start. The Pyodide worker reports a startup failure by rejecting the connection, which FletBackend.connect() answered by reconnecting — right for a server still coming up, useless here, since the same program fails identically every time. So the app sat on an empty boot screen while a fresh worker re-downloaded the Python runtime every 10 seconds, and the reconnect overwrote the captured error with "Loading..." before anything could show it. A transport now raises FletAppStartupException for a failure of the app itself, which settles on the error and renders it on the boot screen; an unreachable peer still reconnects as before, keeping its error for the give-up path. Two things that hid the cause are fixed with it: the fatal init error was logged with console.log rather than console.error, invisible in a console filtered to Errors, and an unguarded flet_js.send() surfaced a misleading TypeError instead of the Python traceback (#5876, #6827) by @ndonkoHenri.
  • Fix a control losing its events after its parent component re-rendered it unchanged: a click or hover on it was silently dropped (Control with ID … not found) or, when something still referenced the previous instance, raised RuntimeError: Control must be added to the page first. The frozen diff that reconciles a component's output skipped any child in a single-control or list property (Container.content, DataCell.content, DataColumn.label, DataTable.columns, ...) that compared equal to the previous render's — and control equality ignores the control id, so that is every rebuilt child with stable handlers (module-level functions, use_callback, cached lambdas) or a cached subtree. The new instance was left in the rendered tree with a fresh id and no parent, while the session index kept the old one, whose parent chain died with the old tree; the next event on it - a click, a hover - failed before reaching the handler, and the mounted-control recovery walk could not find it because the live instance carried an id the client had never seen. Such children are now reconciled like any other, which emits no patch operations but migrates the id, stamps the parent and refreshes the index. Per-render lambdas masked this in most apps, since they make every rebuilt control unequal by @davidlawson.
  • Fix flet run --web crashing with ModuleNotFoundError: No module named 'flet_desktop' before the app started. The web branch installed flet-web, but the next line imported flet_desktop unconditionally - and that is only an optional extra of flet, so a plain pip install flet or a headless CI image never got past it. --web, --ios and --android all serve the app over the web server and none opens a native window, so they now ensure flet-web, and the desktop teardown is imported lazily and runs only when a window was opened. A failure inside the desktop-view thread also no longer leaves the CLI stuck in its wait loop (#5826, #6822) by @ndonkoHenri.
  • Fix ft.use_dialog() crashing the session's updates scheduler with IndexError: list assignment index out of range, which silently stops an app from responding - the scheduler task dies, so every deferred update and effect after it is dropped. After swapping in a re-rendered dialog, the hook realigned the snapshot of what the client last saw by writing at the dialog's live list index; but that snapshot only refreshes when a dialog-list update is actually flushed, so entries appended or removed since then shift the two lists relative to each other. A component can render twice within a single scheduler batch - the first render appends the dialog and queues the list update, and before it drains, another update in the same batch diffs a parent whose rebuild re-renders the host - leaving the live list one entry longer than the snapshot. The snapshot entry is now found by identity, and when the dialog has not reached the client yet the queued list update carries it instead of a patch the client would drop as an unknown control (#6814) by @FeodorFitsner.
  • Fix UrlTarget.BLANK not actually opening a new tab per link on the web. Its value was "blank" while SELF, PARENT and TOP all carry the leading underscore the HTML spec defines, so the value reached window.open() as an ordinary window name rather than the reserved _blank keyword: the first such link opened a tab called blank and every later one reused that same tab instead of opening its own. The LaunchMode.externalApplication upgrade that openWebBrowser() applies to _blank never fired either, so on non-web platforms BLANK did not force an external browser as intended. The enum value is now "_blank" by @FeodorFitsner.
  • Fix FilePicker.pick_files() never opening a dialog in a web app on iOS, and Clipboard.set(), Clipboard.set_image() and the Share methods doing nothing there either. A browser opens a file picker, writes to the clipboard or shows a share sheet only while it is handling the user's click or key press, and a service method called from an event handler misses that window entirely: the click travels to Python, the handler runs, and the instruction travels back long after the permission has lapsed. WebKit enforces this and reports nothing, while Chrome and Firefox allow the same calls, so an app worked on Android and on the desktop and silently did nothing on an iPhone or iPad - which made a browser rule look like a Flet bug, and made FilePicker in particular look broken since save_file() kept working (it clicks a download link, which is not gated). These operations are now available as client actions - ft.PickFiles, ft.CopyToClipboard, ft.ShareText, ft.OpenUrl - assigned to a control's action property and performed by the client inside the original gesture. pick_files() also no longer hangs for its full one-hour timeout when a browser has already reported it will not open the dialog: it raises straight away, pointing at ft.PickFiles (#3710) by @FeodorFitsner.
  • Fix flet build --description (and project.description / tool.poetry.description from pyproject.toml) never reaching the built app. The value was passed to the build template under a key no file consumed (description) while every template reads project_description, so it always rendered as its empty default — a web app's <meta name="description"> and PWA manifest.json description have been silently blank since the option was introduced. Both now receive it, as does the new Linux desktop entry's Comment=, and the value is escaped per format, so a description containing quotes, newlines or backslashes can no longer produce an unparsable pubspec.yaml/manifest.json or a desktop entry the desktop environment discards. The option is now documented under Description (#2269) by @ndonkoHenri.
  • Fix Linux apps packaged with flet pack appearing in the taskbar as "flet", grouped together with every other Flet app and unable to carry an icon. flet pack runs the shared prebuilt client binary, and the Linux desktop keys a window's identity on its X11 WM_CLASS or Wayland app_id — both of which GTK derives from the client's argv[0], so every packed app inherited that binary's own name. The client is now launched under the app's own identity instead, taken from the new FLET_APP_ID environment variable that the PyInstaller runtime hook sets to --bundle-id when one is given and to the executable's name otherwise — so an executable named something a desktop entry should not be keyed on, such as a versioned my-app-1.2.3, can be given a stable identity; the binary is unchanged, so this needs no client rebuild and works with clients already cached. A Linux app's display name and icon come from an installed desktop entry rather than from the executable, so flet pack now writes one next to the binary — with StartupWMClass already matching the app's identity, which is the part that is impossible to guess — plus the icon itself when --icon is a .png, closing the other half of the report. Neither is installed for you, since that would change your application menu as a side effect of building; Linux taskbar identity shows the two cp commands (#5422, #6800) by @ndonkoHenri.
  • Fix Windows apps packaged with flet pack showing a second taskbar identity named "Flet description", whose right-click entry and pin launch a blank Flet client window instead of the app. Two defects stacked up: the PyInstaller runtime hook carrying the AppUserModelID fix from #6403 was never bundled into packed apps (its rthooks.dat manifest was missing from the flet-cli wheel, and PyInstaller skips a missing manifest silently), and a process-level AppUserModelID only fixes taskbar grouping anyway — the taskbar name, icon and pin target resolve through the shell's relaunch properties, which were never set, so they fell back to the cached flet.exe. The wheel now ships the manifest, and flet_desktop stamps System.AppUserModel.ID/RelaunchCommand/RelaunchDisplayNameResource/RelaunchIconResource on the client window right after launch (new flet_desktop.win_taskbar module, pure ctypes), driven by environment variables the runtime hook sets — and settable manually when packaging by other means, such as Nuitka. Apps started hidden (AppView.FLET_APP_HIDDEN) get their taskbar identity as well, and executable paths containing spaces or longer than 128 characters are supported (#6767, #6793) by @ndonkoHenri.
  • Fix flet pack-patched desktop clients and the vanilla client shadowing each other through the shared ~/.flet/client/ cache. The cache was keyed by flavor and version only, so a packaged app whose bundled client was patched with a custom icon and metadata could silently run whatever same-version client happened to be cached first — and vice versa. Bundled client archives are now content-fingerprinted and each distinct client gets its own cache directory. Client archives are built deterministically so rebuilds with unchanged content reuse the same cache entry, superseded entries unused for 30 days are garbage-collected (never touching a running app's client), and concurrent first-run extractions of the same archive no longer crash the losing process (#6793) by @ndonkoHenri.
  • Fix embedded FletApp web apps served from a path-prefixed URL never connecting: the embedded session derived its WebSocket path from the host document's endpoint configuration — which describes the host app and is shared by every app embedded on the page — so the URL's path prefix was lost before connecting (e.g. FletApp(url="https://gateway/device1/") behind a reverse proxy opened wss://gateway/ws instead of wss://gateway/device1/ws). Embedded apps now derive the endpoint from their own URL, matching what the io implementation already did; root apps are unchanged (#6794) by @jmvillalba.
  • Fix a Flet web app silently freezing in Safari after navigating away and back in the same tab. Safari restores the page from its back/forward cache with Flutter's frame scheduling broken: clicks still reached Python, the websocket reconnected, and server patches arrived and applied - but scheduled frames never rendered, so the screen stayed at its last painted state while the server kept processing events. The freeze looked intermittent because a restore can paint one synchronous catch-up frame before wedging on the very next update, so another Back/Forward appeared to briefly "fix" it. The page now reloads itself when a WebKit browser restores it from the back/forward cache (pageshow with persisted); for a hosted app the Flet session lives on the server and survives the reload, so state is preserved. The reload is gated to WebKit - every browser on iOS, Safari on macOS - because other engines restore correctly, and for a flet build web (Pyodide) app, whose state lives in the page, an unnecessary reload would destroy it. Applied to both the hosted web client and the flet build web template by @FeodorFitsner.
  • Fix flet create --template extension generating an example app whose pyproject.toml cannot be parsed on Windows. The [tool.flet.dev_packages] and [tool.uv.sources] entries that point back at the extension package interpolated the host's os.sep into a TOML basic string, so on Windows they rendered as "..\.." and "..\..\": the first is an invalid escape sequence, and in the second the trailing \" escapes the closing quote and leaves the string unterminated — the "unbalanced quotes" error reporters hit before they could build or run the generated example. Both paths are now written with forward slashes, which need no escaping in TOML and which pathlib and uv accept on Windows just as they do elsewhere (#5507, #6775) by @ndonkoHenri.
  • Fix flet publish's documented [tool.flet.web].route_url_strategy and FLET_WEB_ROUTE_URL_STRATEGY fallbacks being unreachable: the --route-url-strategy option's argparse default of "path" made the CLI value always win. The default now applies at the end of the resolution chain, as in flet build, by @ndonkoHenri.
  • Fix the generated macos/Runner/*.entitlements files being rejected by codesign with AMFIUnserializeXML: syntax error when used directly for re-signing: the template emitted boolean values as self-closing tags with a space (<true />), which Xcode and plutil accept but codesign's stricter AMFI plist parser does not. The templates now emit <true/>, and flet build's own signing step additionally normalizes any entitlements file through plistlib before use, so plist formatting can never break signing (#6702) by @ndonkoHenri.
  • Fix CupertinoBottomSheet painting an opaque rectangular background behind custom content, which obscured rounded corners and transparent padding. Its Material wrapper is now transparent while preserving normal text styling (#4761, #6780) by @ndonkoHenri.
  • Fix WebView failing to open a local page on Android with net::ERR_ACCESS_DENIED, so that WebView(url="file:///…/index.html") and load_request("file://…") now work as they already did on iOS and macOS. Every URL went through webview_flutter's loadRequest(), a bare WebView.loadUrl() on Android; loadFile() is the only entry point that calls WebSettings.setAllowFileAccess(true), which defaults to false whenever the app targets API 30 or above — as Flet builds do (targetSdk 36). Chromium rendered its "Webpage not available" error page for a file the app had just written itself, making a permissions problem look like a broken path. file: URLs are now routed to loadFile() via Uri.toFilePath(), so percent-encoded paths such as iOS's Application%20Support decode correctly and sibling assets (<script src="lib.js">, stylesheets, images) resolve. load_html(base_url="file://…") maps to loadDataWithBaseURL, which does not enable file access either, so it now grants it explicitly first — through webview_flutter_android, imported behind a dart.library.io conditional so web builds are unaffected. Remote URLs are unchanged (#4627, #6787) by @ndonkoHenri.
  • Fix WebView being unable to run any JavaScript on Android: initState never called setJavaScriptMode, and Android's WebSettings.javaScriptEnabled defaults to false — unlike WKWebView and the web platform — so the same app silently behaved differently depending on the platform it was built for, with no error to point at. JavaScriptMode.unrestricted is now applied before the first page load; set_javascript_mode() remains the way to change it and can still turn it off (flet-dev/flet-webview#14, #6787) by @ndonkoHenri.
  • Fix flet create <dir> printing flet test <dir> as a next step — a command that does not run. flet test takes the target platform as its first positional and the app path as its second, so argparse rejected the directory with invalid choice: '<dir>'; the hint had been copied from the flet run <dir> line just above it, which works only because flet run has a single leading positional. The next steps now cd into the new project and run flet run and flet test bare — which is also the only place flet test finds pytest, since that comes from the generated app's own flet[test] dev dependency rather than from flet-cli. Paths in commands the CLI suggests are shell-quoted as well, so flet create '$demo' no longer prints cd $demo, and the -- separator hint flet run offers for an unrecognized argument no longer suggests flet run my app.py -- --wev, which would run my. flet test now also checks that pytest is importable up front, instead of after spending minutes provisioning a Flutter test host (#6786) by @ndonkoHenri.
  • Fix observable subscriptions leaking from components that hold more than one Observable in use_state: unmounting left listeners attached to those observables, so later changes kept scheduling updates on an already-unmounted component for as long as it stayed reachable. ComponentOwned and its subclasses — Hook, the concrete hooks, and ObservableSubscription — are lifecycle objects tracked in lists and matched by identity with in/list.remove, but they are dataclasses whose declared fields are InitVars or per-hook payloads, so the generated __eq__ compared no fields at all (or equal payloads) and made distinct instances compare equal. Component._detach_observable_subscription therefore disposed the subscription it was handed but removed a different one from _state.observable_subscriptions, leaving the disposed instance tracked and the live one untracked — and _detach_observable_subscriptions() at unmount then never disposed it. A component with a single observable state was unaffected, because the wrong match and the right match coincide, which is why this only surfaced as a slow leak in apps with several observable states. Every ComponentOwned subclass now sets eq=False and compares by identity; @dataclass regenerates __eq__ on each class, so the flag is repeated per subclass rather than inherited. As a side effect these objects are hashable again — @dataclass(eq=True) had set their __hash__ to None (#6776, #6777) by @chiliec.
  • Fix flet build web --no-cdn still loading Pyodide from jsdelivr. The web template chose between the bundled runtime and the CDN with {% if cookiecutter.no_cdn == "True" %}, but flet build passes no_cdn through cookiecutter's extra_context as a Python bool, which Jinja never renders to a string before comparing — True == "True" is False, so the CDN branch was taken in both modes and --no-cdn builds downloaded, cached and shipped ~15 MB of Pyodide that the browser then ignored. Only pyodideUrl was affected: flet.noCdn itself is derived separately, via "{{ cookiecutter.no_cdn }}".toLowerCase() == "true", which does render correctly — which is why CanvasKit and the fallback fonts loaded locally as expected while Pyodide alone went to the CDN, making the failure look like a Pyodide-specific quirk rather than a template bug. The template's other no_cdn test (assets/FontManifest.json) already used plain truthiness; all conditions now do, and no string comparisons against "True" remain by @FeodorFitsner.
  • Fix the bundled Pyodide URL being origin-absolute in --no-cdn builds, so a sub-path deployment (flet build web --base-url myapp) requested /pyodide/pyodide.mjs and got a 404 while the file sat at /myapp/pyodide/pyodide.mjs. It now renders relative to the configured base URL, as canvasKitBaseUrl does. Builds without --base-url render exactly the same URL as before by @FeodorFitsner.
  • Fix integration tests failing to start when the host Python environment carries IDE configuration: flutter test exited with code 79 and "No tests were found" while the flet_app fixture failed during setup. FletTestApp launched the Flutter test process with the host environment inherited wholesale, and the interpreter embedded in the app under test reads PYTHONPATH/PYTHONHOME at initialization - so the debugger and sitecustomize paths PyCharm injects landed on the packaged app's sys.path and killed it before it could connect to RemoteTester. Since the failure happened inside the app rather than in the test process, it surfaced only as a Flutter exit code, which made it look like the tests themselves were missing. The Flutter subprocess now gets an explicit environment with PYTHONPATH, PYTHONHOME and PYTHONEXECUTABLE removed and PYTHONNOUSERSITE=1 set - user site-packages is opt-out, so a host ~/.local/lib/pythonX.Y/site-packages matching the embedded interpreter's version leaks in the same way. PATH, and every FLET_* and SERIOUS_PYTHON_* variable the native build phase needs, are untouched (#6747) by @PythBuster.
  • Fix ink ripples and hover highlights not covering the whole Container when both ink=True and animate are set, and its padding being applied twice. In that combination padding and alignment were passed to the outer AnimatedContainer and to the inner Container that wraps the content inside the InkWell, so a padding=10 container was laid out with 20 on each side. The duplicated alignment was the more visible half: it made the Material/InkWell shrink-wrap to the content, so splashes and the hover overlay stopped short of the container's edges while bgcolor on the same container still filled it - the two disagreed on where the control ended. Both properties now live only on the inner container, which becomes an AnimatedContainer when animate is set so that padding and alignment changes still animate; this is what the non-animated ink path already did. Inked, animated containers with padding will render tighter than before - by the padding they declare, instead of double (#6757) by @FeodorFitsner.
  • Fix disabled=True having almost no effect on Radio and CupertinoRadio: the radio could still be selected and kept its enabled colors; only the label grayed out. Broken since the migration to Flutter's native RadioGroup widget (#5651), whose API needs an explicit enabled: false that Flet never passed. A disabled radio now ignores clicks and renders grayed out (fill_color's ControlState.DISABLED value applies too), disabling a whole RadioGroup cascades to its radios, and the label grays out with the default text style as well (#6159, #6769) by @ndonkoHenri.
  • Fix a mouse wheel tick over an InteractiveViewer both zooming it and scrolling the enclosing scrollable, so zooming an image inside a scrolling Column moved the page out from under the pointer. Flutter's InteractiveViewer applies pointer signals directly from its own listener and never claims them through the PointerSignalResolver, unlike Scrollable, which does - with nothing claiming the event first, the ancestor scrollable handled it as well. The viewer now claims the signals it acts on, so the scrollable ignores them. Signals the viewer does not act on still reach the parent: with scale_enabled=False, and - unlike a bare Flutter InteractiveViewer - when the transform comes out unchanged because the zoom is already at min_scale/max_scale or a trackpad pan is clamped at the content boundary, which keeps a fitted, unzoomed image from becoming a region where a trackpad cannot scroll the page at all (#6755, #6761) by @7576457.
  • Fix a yield in a generator event handler not actually showing the intermediate UI state when the handler blocks right after it - the classic progress pattern (status.value = "Working..."; yield; time.sleep(2)) drew nothing until the whole handler had finished, making the yield look like it did nothing. A yield runs the pending update, but that only queues a patch on the connection's send queue: the socket transport's send_message is a put_nowait drained by a background send loop, and since the handler resumed without ever suspending, the event loop never got the turn it needed to hand that patch to the client. Blocking calls (time.sleep(), a synchronous HTTP request, a CPU-bound loop) are exactly what such handlers put after the yield, so the update the user asked for arrived only once the work was over. Each generator step - sync and async - now yields to the event loop after its update, so queued patches are flushed before the handler continues. The yield is scoped to the generator boundary, so ordinary handlers gain no new suspension point mid-dispatch; explicit .update() calls inside a generator are covered too, not just auto-update (#6764) by @PythBuster.
  • Fix a relative assets_dir being silently ignored when a Flet app is mounted on FastAPI, so a custom favicon.png never replaced the default one and the app's own images could not be loaded. FletStaticFiles required an absolute path and set assets_dir to None otherwise, after which every request fell through to the packaged web client - the app started and served normally, it simply behaved as though no assets directory had been configured, which is what made this look like a favicon-specific problem rather than a path one. The only signal was an assets_dir must be absolute path. line logged on a logger with no handler attached, so it surfaced as bare text in the middle of uvicorn's own output. This also left the FastAPI mount as the only entry point that rejected a relative path: ft.run() and flet run --web -a resolve one against the script directory, and ft.run(export_asgi_app=True) against the current working directory. Relative paths are now resolved against the current working directory, matching the ASGI export - the other case where a Flet app is served from a server process started externally - and matching upload_dir in the same module, which already resolved this way through os.path.realpath(). An assets_dir that does not exist is still dropped, but now logs a warning naming the resolved absolute path instead of an info-level message naming the unresolved one, so it is clear which directory was searched (#5077, #6792) by @ndonkoHenri.
  • Fix an intermittent type 'Null' is not a subtype of type 'Control' crash while a component re-renders. A component that re-renders is patched to a null body first and receives its new body in a follow-up message, so for the gap between the two it has no body at all - normally invisible, but if the client happens to draw a frame in between, the parent asks for its children and gets a null where a Control is required, taking down the widget tree. A component with no body now renders nothing for that frame and appears when its body arrives. The crash was timing-dependent, which is what made it look like flaky CI: it surfaced as a widget exception during route changes that rebuild a view's contents by @FeodorFitsner.
  • Fix Rive animations failing on iOS in apps built with flet build ipa with Rive failed to load: Invalid argument(s): Failed to lookup symbol 'loadRiveFile': dlsym(RTLD_DEFAULT, loadRiveFile): symbol not found. rive_native compiles its native runtime into the app executable and reaches its entry points at runtime through dlsym(RTLD_DEFAULT, ...), but the Runner target left STRIP_STYLE at its default of all, and the strip step Xcode runs when archiving removes every non-weak global symbol from the executable — the Rive code was in the binary, only its symbol names were gone, so each lookup failed and every Rive control rendered as an error box. It only ever showed up in an .ipa: no strip runs for flutter run --release, so the same app worked when launched from a development machine and broke in TestFlight or on the App Store. The Runner target's Release and Profile configurations now set STRIP_STYLE = non-global, which keeps global symbols and costs only symbol-table size — no code is added to the binary. This is not Rive-specific: any Flutter plugin that statically links FFI entry points and resolves them by name was affected the same way (#6812) by @FeodorFitsner.
  • Fix flet build, flet debug, flet test, flet devices and flet emulators failing on the first run with /usr/bin/env: 'bash': No such file or directory when a dart or flutter executable is installed into a system bin directory. While provisioning its own Flutter SDK, Flet rebuilt the PATH handed to every Flutter subprocess by deleting each directory that contained a file named flutter or dart (or their .bat/.cmd variants) - not just the SDK's own entry. A /usr/bin/dart, as installed by Arch's extra/dart and the AUR flutter-bin package or by a hand-made symlink, therefore removed /usr/bin - and on a merged-/usr distribution /bin with it, since the check follows symlinks - from the child environment, leaving Flutter's #!/usr/bin/env bash launcher script with no shell to run. Even where bash survived, losing a system bin directory stripped the git, unzip and java that Flutter's own shared.sh and the Gradle build require. The managed SDK is now only prepended to PATH, never subtracted from it, which is sufficient for it to win: PATH lookup is first-match, Flet invokes flutter/dart by absolute path anyway, and the launcher resolves FLUTTER_ROOT from its own script location rather than from PATH. The bug reproduced only until the SDK was installed - the next run skipped the provisioning step entirely and worked - which is why it looked intermittent. Fixes #5118. (#6815) by @ndonkoHenri.

Documentation

  • Add a Subinterpreters cookbook page on using Python 3.14's concurrent.interpreters and InterpreterPoolExecutor for true multi-core CPU parallelism inside a single Flet process — the in-process, mobile-capable counterpart to Multiprocessing, which can't spawn child processes on iOS/Android. Walks through three runnable examples — a parallel pool map, streaming progress over a shared cross-interpreter Queue, and a reused long-lived interpreter — with the rules and gotchas for each. Works on desktop and mobile with the bundled Python 3.14; not in static (Pyodide) web builds (#6782) by @ndonkoHenri.

Full Changelog: v0.86.5...v1.0.0

Don't miss a new flet release

NewReleases is sending notifications on new releases.