Following @owen.campbell announcement:
Here’s a whistle-stop tour of a feature that we’ve added to the library.
example app:
- todos with
indexed_db
storage - counter
- server fetch
- submission form
(To see an individual example in isolation, set the example as the startup form)
Motivation:
State management is challenging, particularly when apps get large.
An Example:
# create an atom that holds state
from anvil_labs.atomic import atom, action, selector
@atom
class CountAtom:
value = 0
@selector
def get_count(self):
return self.value
@action
def update_count(self, increment):
self.value += increment
count_atom = CountAtom()
# the form to display the count
from anvil_labs.atomic import render
from ..atoms.count import count_atom
class Count(CountTemplate):
def __init__(self):
self.display_count()
@render
def display_count(self):
# I get called any time the get_count return value changes
self.count_lbl.text = count_atom.get_count()
def neg_btn_click(self, **event_args):
count_atom.update_count(-1)
def pos_btn_click(self, **event_args):
count_atom.update_count(1)
Discussion
In this example, whenever a button is clicked,
- the button event handler calls an
action
on the atom, - which updates the state of the
atom
, - which then updates any
selectors
that depend on that state change, - and finally, any
render
methods that depend on those updates are re-rendered. - …repeat.
Action → State change → Re-compute selectors → Call render methods
This approach leads to separation of concerns:
A form’s job is to:
- display the UI for the state; and
- hook up component event handlers to actions.
An atom’s job is to:
- handle the state
One benefit is that atoms are typically global objects, and any form can depend on them. i.e. no need to pass state up and down the parent form hierarchy.
You also don’t need to trigger updates of components - it happens automatically.
Limitations
- It’s only been tested on toy examples from the clone link above.
- So - not ready for production!
- Currently, we don’t support Row object updates as part of the render cycle.
- Documentation hasn’t been written yet
bindings and writebacks
Anvil’s data bindings and writebacks don’t play nicely with this library, so we’ve added our own.
We could write the above example as:
from anvil_labs.atomic import bind
class Count(CountTemplate):
def __init__(self):
bind(self.count_lbl, "text", count_atom.get_count)
# or bind it to an attribute of an atom
bind(self.count_lbl, "text", count_atom, "value")
writebacks work in a similar way:
# call signature:
writeback(component, prop, atom, atom_prop, events)
writeback(component, prop, selector, action, events)
# e.g.
writeback(self.check_box, "checked", self.item, "completed", events=["change"])
Final thoughts
If you (want to) try this as a dependency, please share your experience/problems/suggestions in the anvil-labs discussion page so that we can improve it.