Duplicate Check
Describe the bug
When a layout container rebuilds its subtree with a different root widget type between two builds (for example, ResponsiveRowControl.build() returns either a Row or a Wrap depending on whether the resolved col values for the current breakpoint fit within columns), Flutter's element reconciliation cannot reuse the old Elements: since the runtimeType at that slot changed, the whole subtree below it is unmounted and a new one is inflated from scratch, regardless of the keys used by descendant widgets.
How a layout control chooses to rearrange its children (Row vs Wrap, or any other conditional layout change) is its own logic and design choice.
The point here is the side effect: any descendant control that keeps expensive or native state in its Flutter State (an open media player, a WebView, animation/scroll state, etc.) gets that state fully disposed and recreated whenever this kind of type-changing rebuild happens above it.
Concretely, with flet_video's VideoControl, _teardown() (disposing the media_kit Player/VideoController) followed by _setup() (creating a new Player and reopening the playlist) runs on every such rebuild, producing a visible black-screen flash and playback restart.
This isn't unique to ResponsiveRow — it's a generic consequence of Flutter's reconciliation rules, so it likely shows up anywhere in Flet where a control conditionally changes the widget type it returns from build() (not just Row/Wrap — any conditional branching that changes the top-level widget type).
Code sample
Code
import flet as ft
import flet_video as ftv
async def main(page: ft.Page):
media = "https://user-images.githubusercontent.com/28951144/229373720-14d69157-1a56-4a78-a2f4-d7a134d7c3e9.mp4"
page.add(
ft.ResponsiveRow(
controls=[
ftv.Video(
expand=True,
autoplay=True,
playlist=[ftv.VideoMedia(media)],
aspect_ratio=16/9,
col={ft.ResponsiveRowBreakpoint.MD: 6},
),
ft.Container(
expand=True,
height=150,
bgcolor=ft.Colors.GREEN_ACCENT_100,
col={ft.ResponsiveRowBreakpoint.MD: 6},
),
]
)
)
if __name__ == "__main__":
ft.run(main)
To reproduce
- Run the sample above (requires
flet and flet-video).
- Resize the window/browser so its width repeatedly crosses the MD breakpoint, so the two children switch back and forth between sitting side by side and stacking on top of each other.
- Observe that each time this switch happens, the Video control goes black for a moment and restarts playback from the beginning of the currently open media, instead of continuing to play uninterrupted.
Expected behavior
A way to opt a specific control into preserving its Flutter State across a rebuild that only changes layout, without changing the control itself.
Screenshots / Videos
Captures
[Upload media here]
Operating System
Windows
Operating system details
11
Flet version
0.85.3
Regression
No, it isn't
Suggestions
Flutter's GlobalKey lets an existing Element (and its State) be reused/reparented into a new location in the tree within the same build pass, even when the immediate ancestor's widget type changed — which is what's needed here.
This probably shouldn't apply to every control by default. A per-control opt-in flag (e.g. preserve_state on Control) could work: when set, Flet would build/reuse a GlobalKey for that control's widget — something like GlobalObjectKey(control.id), cached rather than recreated on every build.
The cache for these keys could live on flet.View (ViewControl), since navigating away from a View and disposing it is an expected point for state to reset, so the cache wouldn't need to survive across views — only across rebuilds within the same View.
Logs
Logs
Additional details
No response
Duplicate Check
Describe the bug
When a layout container rebuilds its subtree with a different root widget type between two builds (for example,
ResponsiveRowControl.build()returns either aRowor aWrapdepending on whether the resolvedcolvalues for the current breakpoint fit withincolumns), Flutter's element reconciliation cannot reuse the old Elements: since the runtimeType at that slot changed, the whole subtree below it is unmounted and a new one is inflated from scratch, regardless of the keys used by descendant widgets.How a layout control chooses to rearrange its children (Row vs Wrap, or any other conditional layout change) is its own logic and design choice.
The point here is the side effect: any descendant control that keeps expensive or native state in its Flutter
State(an open media player, a WebView, animation/scroll state, etc.) gets that state fully disposed and recreated whenever this kind of type-changing rebuild happens above it.Concretely, with
flet_video'sVideoControl,_teardown()(disposing the media_kitPlayer/VideoController) followed by_setup()(creating a newPlayerand reopening the playlist) runs on every such rebuild, producing a visible black-screen flash and playback restart.This isn't unique to ResponsiveRow — it's a generic consequence of Flutter's reconciliation rules, so it likely shows up anywhere in Flet where a control conditionally changes the widget type it returns from build() (not just Row/Wrap — any conditional branching that changes the top-level widget type).
Code sample
Code
To reproduce
fletandflet-video).Expected behavior
A way to opt a specific control into preserving its Flutter State across a rebuild that only changes layout, without changing the control itself.
Screenshots / Videos
Captures
[Upload media here]
Operating System
Windows
Operating system details
11
Flet version
0.85.3
Regression
No, it isn't
Suggestions
Flutter's
GlobalKeylets an existing Element (and its State) be reused/reparented into a new location in the tree within the same build pass, even when the immediate ancestor's widget type changed — which is what's needed here.This probably shouldn't apply to every control by default. A per-control opt-in flag (e.g.
preserve_stateonControl) could work: when set, Flet would build/reuse aGlobalKeyfor that control's widget — something likeGlobalObjectKey(control.id), cached rather than recreated on every build.The cache for these keys could live on
flet.View(ViewControl), since navigating away from a View and disposing it is an expected point for state to reset, so the cache wouldn't need to survive across views — only across rebuilds within the same View.Logs
Logs
[Paste your logs here]Additional details
No response