New features
- Client actions: an
actionproperty 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,TextButtonandTextSpannow acceptaction- a singleClientActionor a list of them - which the client performs inside the original gesture, before youron_clickhandler 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.urlis unchanged and keeps working exactly as before by @FeodorFitsner. - App icon and desktop integration for
flet build linux. Linux was the one platformflet buildshipped without an icon —flutter_launcher_iconshas no Linux generator, soassets/icon.pngwas silently ignored and built apps ran with a generic icon. The resolved icon (icon_linux.png, falling back toicon.pngor the default Flet icon) is now bundled asdata/app_icon.pngand 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,StartupWMClassset) plus the icon atshare/icons/hicolor/<size>/apps/<bundle_id>.png. The entry's application categories default toUtilityand are configurable with--linux-categoriesor[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-idsigns every bundled binary with your Developer ID certificate — hardened runtime, entitlements, secure timestamp — then notarizes and staples the app for direct distribution, whileapp-storeproduces a sandboxed app with your provisioning profile embedded, packaged into an installer-signed.pkgready 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-authextension with aLocalAuthenticationservice for on-device biometric and credential authentication on Android, iOS, macOS, and Windows via thelocal_authFlutter 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_DETACHon 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 inmatplotlib'sft2fontlooking up a pybind11 type-caster map that had just been destructed, butnumpy,Pillowand Flutter's own Skia statics are torn down by the same pass. Both exit paths were affected: closing the window on desktop, andsys.exit()on every native platform - the latter is the worse of the two, because Flet'ssys.exitposts the exit code to the Dart side and returns, so the interpreter is still fully alive (running on intoPy_Finalize()) when Dart tears the process down. The desktop runners now_exit(TerminateProcesson Windows, since_exit/ExitProcessstill runDLL_PROCESS_DETACHand would not help), and thesys.exitpath routes through a newserious_python_hard_exitindart_bridge1.9.0, reached viaserious_python4.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: Pythonatexithandlers,__del__finalizers and unflushed buffered writes are not guaranteed to run on exit - persist what matters before exiting rather than relying on shutdown cleanup.SharedPreferenceswrites are unaffected and were verified to survive both paths. See How a built app terminates by @FeodorFitsner. flet run -vnow turns on the framework's own logging, and-vvraises it to debug. The flag was accepted and documented as "enable verbose output", but nothing mapped it to a log level, so everylogger.infoin Flet's web transport — web root, assets directory, session and upload activity — was unreachable and the command printed nothing at all.flet runpasses the level to the app through the newFLET_LOG_LEVELvariable; an app that configures logging itself keeps the setup it chose. The web root now also namesFLET_WEB_PATHwhen that is where it came from, matching what the desktop client already reports forFLET_VIEW_PATH.flet runitself now configures logging too, not just the app it starts, so the desktop client resolution — which ofbuild/<platform>,FLET_VIEW_PATHor the cached client won — is visible as well (#6835) by @ndonkoHenri.FLET_WEB_PATHnow works with aflet build weboutput, so an app with third-party extensions can be served byflet run --webandft.run(export_asgi_app=True). Those commands serve the prebuilt web client shipped in theflet-webpackage, whose extension list is fixed when Flet is released — so a third-party control renders asUnknown control: <Type>there, however the app was built. PointingFLET_WEB_PATHat a built client should have solved that, but a built page bakesflet.pyodide = trueinto 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 — mirroringflet runon desktop, which already prefers a client from a previousflet 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 apostMessagetransfer list from the packet's own buffer, but the JavaScriptjsSendit calls accepted only two parameters, so the third was dropped and every frame was copied.jsSendnow takes the transfer list and hands it topostMessage, 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 webno longer compiles and ships a dart2wasm build that the page can never load. Flutter emits that output for theskwasmrenderer only, while the generatedflutter_bootstrap.jspinsflutterConfig.rendererto whateverweb_rendererresolved to — so under the defaultcanvaskitthe loader skipped it on every page load, leavingmain.dart.wasmandmain.dart.mjs(~7.3 MB) in the output as files nothing requests.--wasmis now passed only when the resolved renderer isautoorskwasm, the two cases where the loader can actually select it;--no-wasmand[tool.flet.web] wasm = falseare 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.pngin 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 --webno longer logsassets_dir does not exist: ...for an app that simply has no assets directory.assets_dirdefaults 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 throughFLET_ASSETS_DIR, or passed straight toFletStaticFileswhen 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 whatflet buildshipped: the defaulticon.pngframed 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 everyflet build apkproduced 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 createno longer shipsassets/splash_android.png, becauseicon.pngnow fits the splash circle on its own and the extra file silently won the fallback chain — replacing onlyicon.pnggave you your own launcher icon but kept the Flet logo on the splash; and the default PWAtheme_colormoved from#0175C2(Flutter's stock blue, which matched no Flet brand colour) to#FF005F, still overridable with--pwa-theme-coloror[tool.flet.web].pwa_theme_color(#6816) by @FeodorFitsner. flet buildgenerates app icons and splash screens itself, replacingflutter_launcher_iconsandflutter_native_splash. Both Dart tools are gone from generated projects, along with the twopubspec.yamlconfig blocks that fed them; generation now runs through a newflet-platform-assetspackage (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 withoverwrite_if_exists=Trueafter 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 thatLaunchImage{,@2x,@3x}.pngstayed 1x1 placeholders andContents.jsonstayed light-only (the correctly generatedLaunchImageDark*files were left orphaned, referenced by nothing), andlaunch_background.xmlstayed stock white — so iOS showed no launch image at all and Android below 12 showed a plain white screen; only Android 12+ worked, becausevalues-v31/styles.xmlhappens to be the one file the template does not ship. Alongside the fix, several long-standing output defects go away:web/favicon.pngis 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.icocarries 16/32/48/256 instead of a single 256 entry;apple-touch-icon-192.pngis generated at last, thoughindex.htmlhas 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.webpsource 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 realflutter_native_splashbuild, so existing apps keep the on-screen size they have today by @FeodorFitsner.flet createnow ships a full-bleedassets/icon.png, andflet build linuxgets 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.icoentry and Linux icon on empty space — those three surfaces apply no mask at all. It is now edge-to-edge, andflet buildcomputes 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.pngnow 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-bleedicon.pngand 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 — andapple-touch-icon, which becomes an iOS home-screen icon, is framed exactly like the native one, while the favicon and the plainIcon-*.pngkeep 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. Supplyingicon_ios.png,icon_macos.pngoricon_android.pngopts that platform out completely and uses your file exactly as given.flet buildalso 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_colorfalls through to[tool.flet.splash] colorinstead 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 frommanifest.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_backgroundsets the colour behind your icon wherever transparency cannot survive.assets/icon.pngis 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_backgroundoverrides[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_backgroundstill overrides it. An unparsable colour warns and falls back rather than failing the build by @FeodorFitsner.flet build linuxships a proper icon theme, andflet runon Linux finally has a window icon.flutter_launcher_iconsnever had a Linux generator — requested since 2022 — soflet build linuxinstalled a single file, often a 1024px image, intohicolor/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.FilePickergained anon_resultevent, called when files are selected through aPickFilesaction. APickFilesaction opens the dialog on the client before your code sees the click, so the selection cannot be returned to the caller the waypick_files()returns it - it arrives here instead. The picked files stay associated with theFilePicker, so they can be passed straight toupload()by @FeodorFitsner.flet build ipanow 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'sPROVISIONING_PROFILE_SPECIFIERdoes (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 ipanow 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.xcarchiveand explains that Xcode exports an.ipaonly for a signed app. A failed export is also caught properly:flutter build ipaexits 0 when Xcode's export step fails, and the existing check for it inspected captured output — which is empty whenever-vis used, so verbose builds reported the failure as success. The check now looks for the.ipaitself (#6796) by @ndonkoHenri.- Web builds and
flet publishnow read theFLET_WEB_RENDERER,FLET_WEB_ROUTE_URL_STRATEGY, andFLET_WEB_NO_CDNenvironment 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 webandflet publishno 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 fromgstatic.comand Flet pointspyodideUrlat jsdelivr, so both copies were dead weight the browser never requested — yetflutter build webalways emitscanvaskit/(~37 MB), andensure_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 pullingchromium/canvaskit.{js,wasm}from gstatic and the full Pyodide runtime from jsdelivr, with no request to a localcanvaskit/orpyodide/path.--no-cdn(or[tool.flet.web] cdn = false) still bundles everything and is unchanged.flet buildalso clears apyodide/left in the reused Flutter project by an earlier--no-cdnbuild, so switching modes doesn't silently keep shipping it by @FeodorFitsner.flet.canvasKitBaseUrlandflet.fontFallbackBaseUrlare now honored whenever they are set, instead of only whenflet.noCdnis true. Previouslyflutter_bootstrap.jsapplied both insideif (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 setnoCdnfor 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 tonullin CDN mode and are pinned byflet build/patch_index.pyonly when bundling by @FeodorFitsner.flet_web.fastapi.app()now honors theFLET_ASSETS_DIRenvironment variable, as it already did forFLET_UPLOAD_DIRand asft.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_pythonto 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 incrementalhtml.parser.HTMLParserparsing and the same inxml.etree.ElementTreeXPath index predicates; 3.12.14 additionally bundles libexpat 2.8.3 forCVE-2026-72522, which 3.13.15 and 3.14.7 shipped a week too early to carry.serious_python4.6.0 tracks the same python-build release, keepingPYTHON_BUILD_RELEASE_DATEin sync with itspythonReleaseDateas the pin requires (#6810) by @FeodorFitsner. - Android host apps now use
FlutterFragmentActivityand AppCompatLaunchTheme/NormalThemeso biometric authentication dialogs work on API 24–27. Abiometriccross-platform permission bundle addsNSFaceIDUsageDescriptionfor 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_bgcolorbecomesicon_background,icon_dark_bgcolorbecomesicon_dark_background, andandroid_12_fitbecomesicon_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 toicon.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 explicitsplash.pngis 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. InputBorderis a class hierarchy instead of an enum, so enum-shaped usage no longer works:InputBorderis not iterable, its members have no.valueor.name, andInputBorder.OUTLINE is InputBorder.OUTLINEis nowFalsebecause 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 explicitsidenow 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 itsborder_radius;DropdownM2's open menu is shaped by the newmenu_border_radiusrather than by the field's radius; and onCupertinoTextField,NoInputBorder()now actually removes the border while an outline without asidekeeps 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 ofNone: the eightPaintstyle properties,RoundedRectangleBorder.radius,Button.autofocus,Text.no_wrap,GridView.clip_behavior,Semantics.container,ExpansionPanelList.spacing,TextField.fit_parent_size,Page.show_semantics_debugger, the threeCupertinoAppBar.automatic*flags,Path.Rect.border_radiusandcanvas.Text.max_width. Rendering is unchanged, and properties a widget resolves at runtime from the theme, the platform or its own state keepNone(#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, andDragTargetEvent.offset(deprecated in0.85.0). UseDragTargetEvent.local_positionfor target-relative coordinates orDragTargetEvent.global_positionfor global coordinates (#6693) by @ndonkoHenri. - Remove
Video.show_controls(deprecated in0.85.0). SetVideo.controlstoNoneto hide controls (#6693) by @ndonkoHenri. - Remove
Video.playlist_add()andVideo.playlist_remove()(deprecated in0.85.0). MutateVideo.playlistdirectly with list methods such asappend()andpop()(#6693) by @ndonkoHenri. - Remove
FletApp.show_app_startup_screenandFletApp.app_startup_screen_message(deprecated in0.86.0). UseFletApp.boot_screen_optionsinstead, e.g.boot_screen_options={'spinner_size': 30}orboot_screen_options={'startup_message': '...'}(#6693) by @ndonkoHenri. - Remove the
--clear-cacheflag offlet buildandflet debug(deprecated in0.86.0). Use theflet cleancommand instead (#6693) by @ndonkoHenri. - Remove
Page.go()(deprecated in0.80.0). UsePage.push_route()instead (#6693) by @ndonkoHenri. - Remove the
Page.url_launcher,Page.browser_context_menu,Page.shared_preferences,Page.clipboard, andPage.storage_pathsservice accessors (deprecated in0.80.0). Instantiate the corresponding service classes directly:UrlLauncher(),BrowserContextMenu(),SharedPreferences(),Clipboard(),StoragePaths()(#6693) by @ndonkoHenri. - Remove the
ConstrainedControlbase class (deprecated in0.80.0). Inherit fromLayoutControlinstead (#6693) by @ndonkoHenri. - Remove
ElevatedButton(deprecated in0.80.0). UseButtoninstead (#6693) by @ndonkoHenri. - Remove the deprecated non-underscored
Colorsaliases (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()andapp_async()(deprecated in0.80.0). Userun()andrun_async()instead (#6693) by @ndonkoHenri. - Remove the
targetparameter ofrun()andrun_async()(deprecated alias formain). Passmaininstead (#6693) by @ndonkoHenri. - Remove
Page.launch_url(),Page.can_launch_url(), andPage.close_in_app_web_view()(deprecated in0.80.0). UseUrlLauncher().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"(orControlState.DEFAULT) instead (#6693) by @ndonkoHenri. - Remove
flet.utils.cleanup_path(). It existed only to strip rivalflutter/dartdirectories out of thePATHgiven 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.UNDERLINEandInputBorder.NONEare deprecated in favor ofOutlineInputBorder(),UnderlineInputBorder()andNoInputBorder(). Each still resolves, returning the equivalent instance, and they are scheduled for removal in1.3.0. See the InputBorder class hierarchy guide (#6773) by @ndonkoHenri.- The
border_radius,border_width,border_color,focused_border_widthandfocused_border_colorproperties ofTextField,Dropdown,DropdownM2andCupertinoTextFieldare deprecated in favor ofborder, which accepts anInputBorderor aControlStatedictionary. They keep working and are scheduled for removal in1.3.0; where both are set,borderwins. See the InputBorder class hierarchy guide (#6773) by @ndonkoHenri. DropdownM2.border_radiusis deprecated in favor ofmenu_border_radiusfor the open menu, orborderfor the input field. It is scheduled for removal in1.3.0. See the InputBorder class hierarchy guide (#6773) by @ndonkoHenri.
Changed
DropdownM2is no longer deprecated and remains a supported control; its0.84.0deprecation in favor ofDropdownhas been reverted (#6693) by @ndonkoHenri.
Bug fixes
- Fix
flet build windowsfailing withPermissionErrorwhen removing previous build output containing read-only files, such as Git pack/index files (#6808, #6817) by @eminsk. - Fix a
flet build webapp showing a blank page forever when its Python program fails to start. The Pyodide worker reports a startup failure by rejecting the connection, whichFletBackend.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 raisesFletAppStartupExceptionfor 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 withconsole.lograther thanconsole.error, invisible in a console filtered to Errors, and an unguardedflet_js.send()surfaced a misleadingTypeErrorinstead 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, raisedRuntimeError: 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 --webcrashing withModuleNotFoundError: No module named 'flet_desktop'before the app started. The web branch installedflet-web, but the next line importedflet_desktopunconditionally - and that is only an optional extra offlet, so a plainpip install fletor a headless CI image never got past it.--web,--iosand--androidall serve the app over the web server and none opens a native window, so they now ensureflet-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 withIndexError: 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.BLANKnot actually opening a new tab per link on the web. Its value was"blank"whileSELF,PARENTandTOPall carry the leading underscore the HTML spec defines, so the value reachedwindow.open()as an ordinary window name rather than the reserved_blankkeyword: the first such link opened a tab calledblankand every later one reused that same tab instead of opening its own. TheLaunchMode.externalApplicationupgrade thatopenWebBrowser()applies to_blanknever fired either, so on non-web platformsBLANKdid 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, andClipboard.set(),Clipboard.set_image()and theSharemethods 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 madeFilePickerin particular look broken sincesave_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'sactionproperty 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 atft.PickFiles(#3710) by @FeodorFitsner. - Fix
flet build --description(andproject.description/tool.poetry.descriptionfrompyproject.toml) never reaching the built app. The value was passed to the build template under a key no file consumed (description) while every template readsproject_description, so it always rendered as its empty default — a web app's<meta name="description">and PWAmanifest.jsondescription have been silently blank since the option was introduced. Both now receive it, as does the new Linux desktop entry'sComment=, and the value is escaped per format, so a description containing quotes, newlines or backslashes can no longer produce an unparsablepubspec.yaml/manifest.jsonor a desktop entry the desktop environment discards. The option is now documented under Description (#2269) by @ndonkoHenri. - Fix Linux apps packaged with
flet packappearing in the taskbar as "flet", grouped together with every other Flet app and unable to carry an icon.flet packruns the shared prebuilt client binary, and the Linux desktop keys a window's identity on its X11WM_CLASSor Waylandapp_id— both of which GTK derives from the client'sargv[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 newFLET_APP_IDenvironment variable that the PyInstaller runtime hook sets to--bundle-idwhen 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 versionedmy-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, soflet packnow writes one next to the binary — withStartupWMClassalready matching the app's identity, which is the part that is impossible to guess — plus the icon itself when--iconis 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 twocpcommands (#5422, #6800) by @ndonkoHenri. - Fix Windows apps packaged with
flet packshowing 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 (itsrthooks.datmanifest was missing from theflet-cliwheel, 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 cachedflet.exe. The wheel now ships the manifest, andflet_desktopstampsSystem.AppUserModel.ID/RelaunchCommand/RelaunchDisplayNameResource/RelaunchIconResourceon the client window right after launch (newflet_desktop.win_taskbarmodule, 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
FletAppweb 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 openedwss://gateway/wsinstead ofwss://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 (
pageshowwithpersisted); 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 aflet build web(Pyodide) app, whose state lives in the page, an unnecessary reload would destroy it. Applied to both the hosted web client and theflet buildweb template by @FeodorFitsner. - Fix
flet create --template extensiongenerating an example app whosepyproject.tomlcannot be parsed on Windows. The[tool.flet.dev_packages]and[tool.uv.sources]entries that point back at the extension package interpolated the host'sos.sepinto 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 whichpathlibanduvaccept on Windows just as they do elsewhere (#5507, #6775) by @ndonkoHenri. - Fix
flet publish's documented[tool.flet.web].route_url_strategyandFLET_WEB_ROUTE_URL_STRATEGYfallbacks being unreachable: the--route-url-strategyoption's argparse default of"path"made the CLI value always win. The default now applies at the end of the resolution chain, as inflet build, by @ndonkoHenri. - Fix the generated
macos/Runner/*.entitlementsfiles being rejected bycodesignwithAMFIUnserializeXML: syntax errorwhen used directly for re-signing: the template emitted boolean values as self-closing tags with a space (<true />), which Xcode andplutilaccept but codesign's stricter AMFI plist parser does not. The templates now emit<true/>, andflet build's own signing step additionally normalizes any entitlements file throughplistlibbefore use, so plist formatting can never break signing (#6702) by @ndonkoHenri. - Fix
CupertinoBottomSheetpainting an opaque rectangular background behind custom content, which obscured rounded corners and transparent padding. ItsMaterialwrapper is now transparent while preserving normal text styling (#4761, #6780) by @ndonkoHenri. - Fix
WebViewfailing to open a local page on Android withnet::ERR_ACCESS_DENIED, so thatWebView(url="file:///…/index.html")andload_request("file://…")now work as they already did on iOS and macOS. Every URL went throughwebview_flutter'sloadRequest(), a bareWebView.loadUrl()on Android;loadFile()is the only entry point that callsWebSettings.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 toloadFile()viaUri.toFilePath(), so percent-encoded paths such as iOS'sApplication%20Supportdecode correctly and sibling assets (<script src="lib.js">, stylesheets, images) resolve.load_html(base_url="file://…")maps toloadDataWithBaseURL, which does not enable file access either, so it now grants it explicitly first — throughwebview_flutter_android, imported behind adart.library.ioconditional so web builds are unaffected. Remote URLs are unchanged (#4627, #6787) by @ndonkoHenri. - Fix
WebViewbeing unable to run any JavaScript on Android:initStatenever calledsetJavaScriptMode, and Android'sWebSettings.javaScriptEnableddefaults tofalse— 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.unrestrictedis 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>printingflet test <dir>as a next step — a command that does not run.flet testtakes the target platform as its first positional and the app path as its second, so argparse rejected the directory withinvalid choice: '<dir>'; the hint had been copied from theflet run <dir>line just above it, which works only becauseflet runhas a single leading positional. The next steps nowcdinto the new project and runflet runandflet testbare — which is also the only placeflet testfindspytest, since that comes from the generated app's ownflet[test]dev dependency rather than fromflet-cli. Paths in commands the CLI suggests are shell-quoted as well, soflet create '$demo'no longer printscd $demo, and the--separator hintflet runoffers for an unrecognized argument no longer suggestsflet run my app.py -- --wev, which would runmy.flet testnow also checks thatpytestis 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
Observableinuse_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.ComponentOwnedand its subclasses —Hook, the concrete hooks, andObservableSubscription— are lifecycle objects tracked in lists and matched by identity within/list.remove, but they are dataclasses whose declared fields areInitVars 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_subscriptiontherefore 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. EveryComponentOwnedsubclass now setseq=Falseand compares by identity;@dataclassregenerates__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__toNone(#6776, #6777) by @chiliec. - Fix
flet build web --no-cdnstill loading Pyodide from jsdelivr. The web template chose between the bundled runtime and the CDN with{% if cookiecutter.no_cdn == "True" %}, butflet buildpassesno_cdnthrough cookiecutter'sextra_contextas a Pythonbool, which Jinja never renders to a string before comparing —True == "True"isFalse, so the CDN branch was taken in both modes and--no-cdnbuilds downloaded, cached and shipped ~15 MB of Pyodide that the browser then ignored. OnlypyodideUrlwas affected:flet.noCdnitself 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 otherno_cdntest (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-cdnbuilds, so a sub-path deployment (flet build web --base-url myapp) requested/pyodide/pyodide.mjsand got a 404 while the file sat at/myapp/pyodide/pyodide.mjs. It now renders relative to the configured base URL, ascanvasKitBaseUrldoes. Builds without--base-urlrender exactly the same URL as before by @FeodorFitsner. - Fix integration tests failing to start when the host Python environment carries IDE configuration:
flutter testexited with code 79 and "No tests were found" while theflet_appfixture failed during setup.FletTestApplaunched the Flutter test process with the host environment inherited wholesale, and the interpreter embedded in the app under test readsPYTHONPATH/PYTHONHOMEat initialization - so the debugger andsitecustomizepaths PyCharm injects landed on the packaged app'ssys.pathand killed it before it could connect toRemoteTester. 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 withPYTHONPATH,PYTHONHOMEandPYTHONEXECUTABLEremoved andPYTHONNOUSERSITE=1set - user site-packages is opt-out, so a host~/.local/lib/pythonX.Y/site-packagesmatching the embedded interpreter's version leaks in the same way.PATH, and everyFLET_*andSERIOUS_PYTHON_*variable the native build phase needs, are untouched (#6747) by @PythBuster. - Fix ink ripples and hover highlights not covering the whole
Containerwhen bothink=Trueandanimateare set, and itspaddingbeing applied twice. In that combinationpaddingandalignmentwere passed to the outerAnimatedContainerand to the innerContainerthat wraps the content inside theInkWell, so apadding=10container was laid out with 20 on each side. The duplicatedalignmentwas the more visible half: it made theMaterial/InkWellshrink-wrap to the content, so splashes and the hover overlay stopped short of the container's edges whilebgcoloron 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 anAnimatedContainerwhenanimateis 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=Truehaving almost no effect onRadioandCupertinoRadio: the radio could still be selected and kept its enabled colors; only the label grayed out. Broken since the migration to Flutter's nativeRadioGroupwidget (#5651), whose API needs an explicitenabled: falsethat Flet never passed. A disabled radio now ignores clicks and renders grayed out (fill_color'sControlState.DISABLEDvalue applies too), disabling a wholeRadioGroupcascades 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
InteractiveViewerboth zooming it and scrolling the enclosing scrollable, so zooming an image inside a scrollingColumnmoved the page out from under the pointer. Flutter'sInteractiveViewerapplies pointer signals directly from its own listener and never claims them through thePointerSignalResolver, unlikeScrollable, 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: withscale_enabled=False, and - unlike a bare FlutterInteractiveViewer- when the transform comes out unchanged because the zoom is already atmin_scale/max_scaleor 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
yieldin 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 theyieldlook like it did nothing. Ayieldruns the pending update, but that only queues a patch on the connection's send queue: the socket transport'ssend_messageis aput_nowaitdrained 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 theyield, 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_dirbeing silently ignored when a Flet app is mounted on FastAPI, so a customfavicon.pngnever replaced the default one and the app's own images could not be loaded.FletStaticFilesrequired an absolute path and setassets_dirtoNoneotherwise, 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 anassets_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()andflet run --web -aresolve one against the script directory, andft.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 matchingupload_dirin the same module, which already resolved this way throughos.path.realpath(). Anassets_dirthat 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 aControlis 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
Riveanimations failing on iOS in apps built withflet build ipawithRive failed to load: Invalid argument(s): Failed to lookup symbol 'loadRiveFile': dlsym(RTLD_DEFAULT, loadRiveFile): symbol not found.rive_nativecompiles its native runtime into the app executable and reaches its entry points at runtime throughdlsym(RTLD_DEFAULT, ...), but the Runner target leftSTRIP_STYLEat its default ofall, 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 everyRivecontrol rendered as an error box. It only ever showed up in an.ipa: no strip runs forflutter 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 setSTRIP_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 devicesandflet emulatorsfailing on the first run with/usr/bin/env: 'bash': No such file or directorywhen adartorflutterexecutable is installed into a systembindirectory. While provisioning its own Flutter SDK, Flet rebuilt thePATHhanded to every Flutter subprocess by deleting each directory that contained a file namedflutterordart(or their.bat/.cmdvariants) - not just the SDK's own entry. A/usr/bin/dart, as installed by Arch'sextra/dartand the AURflutter-binpackage or by a hand-made symlink, therefore removed/usr/bin- and on a merged-/usrdistribution/binwith it, since the check follows symlinks - from the child environment, leaving Flutter's#!/usr/bin/env bashlauncher script with no shell to run. Even wherebashsurvived, losing a systembindirectory stripped thegit,unzipandjavathat Flutter's ownshared.shand the Gradle build require. The managed SDK is now only prepended toPATH, never subtracted from it, which is sufficient for it to win:PATHlookup is first-match, Flet invokesflutter/dartby absolute path anyway, and the launcher resolvesFLUTTER_ROOTfrom its own script location rather than fromPATH. 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.interpretersandInterpreterPoolExecutorfor 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-interpreterQueue, 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