Feeds and reactive state API¶
virtual_collection is the list for many rows: a feed, a catalogue, a message
history. On Android it is a RecyclerView, so only the rows on the screen
exist and they are reused as you scroll; ten thousand items cost about the same
as twenty. state and lifecycle are the two small pieces most feeds need
around it.
virtual_collection¶
virtual_collection(
items,
template={...},
layout="list",
columns=2,
item_height=None,
buffer=2,
on_end_reached=None,
on_refresh=None,
prefetch=4,
screen=home,
)
| Argument | Default | Meaning |
|---|---|---|
items |
[] |
the rows, a list of dictionaries |
template |
which item field fills each slot: title, subtitle, image, meta and badge |
|
row |
a function that builds each row from components, instead of template |
|
on_click |
called with the item of the tapped row | |
layout |
"list" |
"list" or "grid" |
columns |
2 |
the number of columns of a grid |
item_height |
88 for a list, 224 for a grid | one height for every row (at least 56), or "auto" so that each row takes what it needs |
buffer |
2 |
rows kept ready just outside the screen, from 1 to 8 |
on_end_reached |
asks for the next page when the end comes near | |
prefetch |
4 |
how many rows before the end on_end_reached runs, from 0 to 100 |
on_refresh |
turns on pull to refresh |
A template fills each slot from the item's fields with {field}, and a slot
can mix fields and text:
tracks = virtual_collection(
songs,
template={
"image": "{cover}",
"title": "{name}",
"subtitle": "{artist} ยท {album}",
"meta": "{duration}",
},
on_click=open_track,
screen=home,
)
Rows built with row= take the height of what they hold. Rows that swipe and
rows that can be dragged into a new order (swipe_right, swipe_left,
reorder and on_reorder) are explained in Components and
layouts.
Methods¶
| Method | Result |
|---|---|
set_items(items, has_more=True) |
replace data and finish refresh |
append_items(items, has_more=True) |
add a page without resetting position |
prepend_items(items) |
add records before the visible anchor |
update_item(id, changes, key="id", optimistic=False) |
patch one keyed row |
remove_item(id, key="id", optimistic=False) |
remove one keyed row |
merge_items(items, key="id") |
update matches and append new keys |
commit(mutation_id=None) |
accept an optimistic snapshot |
rollback(mutation_id=None) |
restore a snapshot |
finish_load(has_more=True) |
release the paging latch |
refresh() |
run the guarded refresh callback |
Paging. on_end_reached runs once and then waits. It runs again only
after append_items() or finish_load(), so a slow network cannot ask for the
same page five times. has_more=False tells the list there is nothing left to
load.
Keys. update_item, remove_item and merge_items find rows by a field,
"id" unless key= names another. The row changes in place, and the list
keeps its position.
Optimistic changes. With optimistic=, a change shows at once and can be
undone if the server says no. optimistic=True names the change after the
row's id; a string gives it a name of your own.
def like_post(post_id):
mutation = "like-" + post_id
feed.update_item(post_id, {"liked": True}, optimistic=mutation)
https.post(
"https://api.example.com/posts/" + post_id + "/like",
on_response=lambda success, body: settle(success, mutation),
)
def settle(success, mutation):
if success:
feed.commit(mutation)
else:
feed.rollback(mutation)
state¶
A value that updates the components bound to it whenever it changes.
| Method | Result |
|---|---|
get() |
the current value |
set(value) |
a new value; every binding updates |
increment(amount=1), decrement(amount=1) |
add or take away |
toggle() |
True becomes False and back |
bind(component, template="{value}") |
writes the value into the component, through the template |
bind_visibility(component, when=True) |
shows the component while the value is when, and hides it otherwise |
basket = state(0)
empty = state(True)
total = label("", id="total", screen=shop)
hint = label("Your basket is empty", id="hint", screen=shop)
basket.bind(total, "{value} items in the basket")
empty.bind_visibility(hint)
def add_one():
basket.increment()
empty.set(False)
button("Add", command=add_one, screen=shop)
Give bind_visibility a state that holds True or False.
lifecycle¶
| Event | When it runs |
|---|---|
on_mount |
once, when the screen is built and its components exist |
on_resume |
each time the screen comes to the front, the first time included |
on_pause |
each time it leaves the front: another screen opens, or the app goes to the background |
on_destroy |
once, when the screen is closed |
Each one takes a function by its name. Load the first page in on_mount, open
connections in on_resume, stop what costs battery in on_pause, and close
connections in on_destroy. On Android they are the Activity's own
onCreate, onResume, onPause and onDestroy.
See API-backed feed and Production Feeds.