Add declarative UI model supports? #4473
Replies: 6 comments
-
I know |
Beta Was this translation helpful? Give feedback.
-
It resets scrolling position because the entire list is being re-created every time an item is add/updated/deleted. At the moment Flet uses control's hash code to determine if the control was added or deleted within its holding collection. As you are re-creating all the elements of the collection it's considered all new and all its elements go to a Flet client again. For proper support of reactive approach we need to use a different algorithm to check if a control in a collection was changed. What could it be: some "dirty" flag set by a user program or maybe "deep" hash of collection item (in Python it's easy to get dict representation of object attributes)? Your thoughts? |
Beta Was this translation helpful? Give feedback.
-
Can we just leverage flutter's performant rebuild system? # maybe immutable assumption?
@dataclass
class MyStateA(State):
z: tuple[int]
@dataclass
class MyStateB(State):
x: float
y: MyStateA
class MyUserControl(UserControl):
def retrieve_state(self):
return MyStateB(x=1.0, y=MyStateA(z=(1,2)))
def new_build(self, abstract_state: MyStateB):
# leaves of abstract_state are replaced by special proxies with indices
# The tree is flattened and enumerated to generate indices
# Control handle it's data properly when data is proxy
# leaves proxies classes can be created in `State`'s __init_subclass__ hook dynamically
# Or we can follow weakref.proxy, use .__class__ attribute to pass `isinstance` check
# Or we can just ignore type constraint since python is dynamically typed
return SomeControl(x=abstract_state.x, z=abstract_state.y.z) List is hard to handle properly, but it is the core of declarative UI, I need to think more about it... |
Beta Was this translation helpful? Give feedback.
-
What do you mean by "on-demand data transfer"? Right now Flet sends diffs. Per my tests sending everything every time is very slow and ineffective. I mean it's "alright" for dozens and hundreds of elements, but not alright for thousands. Holding state inside control is a good idea, but the same question: what would be a good diffing approach? Also, frameworks like Judo separate "data" and "template", so merging occurs on a client side. That gives you the same effectiveness as JS/Flutter frontend fetching JSON data. |
Beta Was this translation helpful? Give feedback.
-
What would be the purpose of retrieve_state exactly? Is the idea that Flet would poll for changes in the return value of retrieve_state and re-run new_build every time its return value is altered?
Not that Im familiar with any of the underlying code you have rn, but as you are supposedly generating JSON structures from the Control Python class instances, why not just check for differences in the resulting JSON data (comparing the previous return value with the latest) and only build UI controls that have no exact match in the previous return value (and for the sake of simplicity, you probably also want to rebuild all the children of the control that has changed) & delete any widgets that existed previously but are now missing? To identify each control in the json data, start from the top parent, see if its attributes are the same as before (excepting the list of child controls), if it is, dont re-build it, then move on to its child(ren) and do the same for them/it (and continue recursively). Once you hit a control that either didnt exist previously under the same parent or of which attributes has changed, "build" it and all the children and append it to the "old" parent that was unchanged. Then do the same process all over again but look for controls that existed in the previous return value of ^ I really hope that makes sense. But this should solve your example of say scrollbars resetting - unless some property on the widget owning the scrollbar is modified that is 😜 Another idea would be to let the user set an ID attribute on the control - if the new return value has a control that matches an ID in the previous, Flet will try to update the attributes on the existing control to match that of the newly created one instead of rebuilding it entirely (again, Idk if this would actually work with your codebase, but I would be imagining that you would literally just loop over every attribute on the flutter widget representing the control and set the values until they all match). This could be an "Optional" ID that if not set, Flet will fall back to the method described above. So when the user creates a control inside the ^^ Reading all this myself I must admit my suggestions sound kinda hacky 🤣But I think it could work really well & not require all too much effort to put together (again, if it plays decently well with the way Flets internals function rn) |
Beta Was this translation helpful? Give feedback.
-
@skeledrew tried something similar in his Flet CLI(fletil): https://pypi.org/project/fletil/0.3.0/ |
Beta Was this translation helpful? Give feedback.
-
If UI and business logic share same data and/or data are lists, imperative UI model is really verbose.
If I use declarative UI model, following code is enough. But if I use imperative UI model, I need to implement proper setters for all its properties(and repeat rebuild code in setters) and hold reference to
ListView
orListView.controls
. Thus I need to properly NAME them.Currently I use following code to emulate declarative UI model:
But it has a problem: rebuilding will reset scrolling position of
ListView
.Beta Was this translation helpful? Give feedback.
All reactions