-
Notifications
You must be signed in to change notification settings - Fork 600
feat: Page.post_frame_callback
#5842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've reviewed this pull request using the Sourcery rules engine
Deploying flet-docs with
|
| Latest commit: |
e56d7e1
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://75f1353f.flet-docs.pages.dev |
| Branch Preview URL: | https://post-frame-callback.flet-docs.pages.dev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a post_frame_callback mechanism to solve timing issues with implicit animations that need to wait for the first frame to render before state changes can be animated. It also fixes a critical bug in the container control's animation event triggering.
- Adds
Page.post_frame_callback()method for scheduling callbacks after the first frame renders - Implements
on_first_framelifecycle event in both Python and Dart - Fixes missing comma in container.dart's
triggerEventcall that would have caused a syntax error
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| sdk/python/packages/flet/src/flet/controls/page.py | Adds on_first_frame event property and implements post_frame_callback() with callback queuing and execution logic |
| packages/flet/lib/src/controls/page.dart | Implements _scheduleFirstFrameNotification() to emit first_frame event using Flutter's addPostFrameCallback |
| packages/flet/lib/src/controls/container.dart | Fixes syntax error in triggerEvent call by adding missing comma between arguments |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if self.__first_frame_fired: | ||
| self.__run_first_frame_callback(callback) | ||
| else: | ||
| self.__first_frame_callbacks.append(callback) | ||
|
|
||
| def __handle_first_frame_event(self): | ||
| """Drain and execute callbacks when the Flutter client signals first frame.""" | ||
| if self.__first_frame_fired: | ||
| return | ||
|
|
||
| self.__first_frame_fired = True | ||
| callbacks = self.__first_frame_callbacks[:] | ||
| self.__first_frame_callbacks.clear() | ||
| for cb in callbacks: | ||
| self.__run_first_frame_callback(cb) |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition: The __first_frame_callbacks list and __first_frame_fired flag are accessed without synchronization. If post_frame_callback() is called from a different thread while __handle_first_frame_event() is executing, the callback could be:
- Added to the list after it's been copied but before it's cleared (line 573-574), causing it to be lost
- Checked against
__first_frame_firedbetween lines 569-572, seeing False, then the flag gets set to True before appending (line 565), causing the callback to never execute
Consider using a lock (e.g., self._lock if available, or a new dedicated lock) to protect access to both __first_frame_fired and __first_frame_callbacks.
| if inspect.isawaitable(result): | ||
| await result | ||
| except Exception: | ||
| logger.exception("Error running post_frame_callback callback") |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message in the exception handler is redundant and unclear. It says "Error running post_frame_callback callback" where "post_frame_callback" and "callback" are repeated. Consider simplifying to "Error running post-frame callback" or "Error in post_frame_callback".
| logger.exception("Error running post_frame_callback callback") | |
| logger.exception("Error running post-frame callback") |
|
IMHO, proposed solution feels a bit hacky and very specific to a given case. Why not just animate explicitly: import asyncio
import logging
import flet as ft
logging.basicConfig(level=logging.DEBUG)
START_BGCOLOR = ft.Colors.YELLOW
END_BGCOLOR = ft.Colors.BLUE
class Profile(ft.Column):
def __init__(self):
super().__init__()
self.avatar = ft.Container(
shape=ft.BoxShape.CIRCLE,
width=100,
height=100,
bgcolor=START_BGCOLOR,
animate=ft.Animation(duration=ft.Duration(milliseconds=1000)),
# on_animation_end=self.change_bgcolor,
)
self.controls = [self.avatar]
def did_mount(self):
super().did_mount()
async def shimmer():
while True:
await asyncio.sleep(1) # yield so the first frame can paint
self.change_bgcolor()
self.page.run_task(shimmer)
def change_bgcolor(self):
print("change_bgcolor")
if self.avatar.bgcolor == START_BGCOLOR:
self.avatar.bgcolor = END_BGCOLOR
else:
self.avatar.bgcolor = START_BGCOLOR
self.update()
def main(page):
page.add(Profile())
ft.run(main)or use just added Shimmer control? |
Fix #5796
In the below code, when
change_bgcolor()runs insidedid_mount, the page is still in its very first diff cycle. The color bgcolor flip gets merged into the same initial patch that delivers the widget tree to Flutter, so the client receives only one state (blue) and there’s no previous frame to interpolate from (implicit animations only work after the first frame because they need an “old” value to animate away from). As a result, theContaineractually starts with a bluebgcolor, andon_animation_endnever fires, and the avatar stays 'frozen'.Code
Note: Try it on the first commit of this PR, which fixes a bug.
A wayaround this, is to set a small time delay/sleep, sufficient enough to allow the first frame of the control to painted first (yellow
bgcolor), and only after it, request a color change.Code
Note: Try it on the first commit of this PR, which fixes a bug.
This PR introduces a better way to handle such scenarios, in which one will only have to pass a callback to the
Page.post_frame_callbackmethod, and it will run at the best time possible.Code
Summary by Sourcery
Add lifecycle support for running page callbacks after the first rendered frame to better support implicit animations and other post-layout work.
New Features:
Bug Fixes:
Enhancements:
Documentation: