State and persistence¶
Choose storage from the value's lifetime, owner, and purpose. Do not move a value into a global registry merely because more than one function needs it.
flowchart TD
Value[Where should this value live?] --> Persist{Survive restart?}
Persist -->|Yes| Preferences[SharedPreferences]
Persist -->|No| Local{Owned by one screen?}
Local -->|Yes| Field[Screen control field]
Local -->|No| Route{Required to open a route?}
Route -->|Yes| Params[Route parameters]
Route -->|No| Runtime[AppSession only for app-wide runtime services]
BodyRegistry in ui/body_registry.py is a separate, narrowly
scoped exception for sharing Home and Import/Export tile bodies with
in-place search. It is not a general state store.
Learning-set content and progress do not belong in AppSession,
preferences, or BodyRegistry. They live as CSV files under application
storage and are accessed through data/ helpers (and AppData during a learn
session). See Data and storage.
Local screen state¶
Keep transient UI state on the control that owns it:
class MyForm(ft.Column):
def __init__(self):
super().__init__()
self.selected_kind: str | None = None
self.title_field = ft.TextField(label="Title")
This is appropriate for form values, selection, validation messages, and other data that should disappear with the screen.
Route parameters¶
Use route parameters for values required to construct a particular screen:
The build factory receives them as strings:
def _build_edit_set_controls(
page: ft.Page,
params: dict[str, str],
) -> list[ft.Control] | None:
file_name = params.get("file")
if not file_name:
return None
return [EditSetMenu(file_name)]
Route parameters make navigation reproducible and allow the factory to reject an incomplete route through its configured fallback.
Application runtime services¶
AppSession in ui/app_session.py owns process-wide UI objects
and interaction state that should exist once while the application is running.
Shared page and file picker¶
AppSession.set_page(page)
page = AppSession.get_page()
export_picker = AppSession.get_export_csv_picker()
tts_service = AppSession.get_tts_service()
await AppSession.speak("insult", "en")
The export picker is created lazily and reused rather than constructing a new
service for each export action. It holds the picker control only — not CSV
bytes or catalog rows. Import uses a separate FilePicker on
ImportExportControl. Validation and save helpers stay on CSVProcessor;
see Import and export.
The export picker is created lazily and reused rather than constructing a new
service for each export action. It holds the picker control only — not CSV
bytes or catalog rows. Import uses a separate FilePicker on
ImportExportControl. Validation and save helpers stay on CSVProcessor;
see Import and export.
TTS uses one TtsService for the whole session. The Audio player is
created on first speak (never with an empty src) and reused. A newer
speak takes over the slash-separated playlist; the current clip is not
paused or stopped. Repeating the same cached file skips remounting and the
load wait so the speaker button can replay immediately. Do not add Audio
controls to tiles or learn screens.
Temporary navigation lock¶
AppSession.disable_all_navigation_controls()
try:
await perform_protected_operation()
finally:
AppSession.enable_all_navigation_controls()
This coordinates the drawer, bottom-bar buttons, FAB, and home/export sort dropdowns while an operation must not be interrupted.
Do not put these values in AppSession:
- fields belonging to one form,
- learning-set or other domain data,
- imported DataFrames or catalog rows,
- values that should survive restart,
- arbitrary controls that are not application-wide services.
Persisted preferences¶
Use Flet SharedPreferences via ui/preferences.py for small
settings that should survive an application restart:
from learning_app.ui.preferences import get_shared_preferences
storage = get_shared_preferences()
await storage.set("theme_mode", ft.ThemeMode.DARK.value)
theme_mode = await storage.get("theme_mode")
Theme mode, background-shade, TTS, and learn-retry keys are the preference consumers today; see Theming, Text to speech, and Learning below. Keep large domain data and temporary controls out of this store.
Theming¶
Theme mode and content background are preference-backed and owned at
runtime by AppTheme in ui/app_theme.py. The Settings screen
(SettingsControl / BackgroundShadeSlider in
ui/screens/settings_control.py) is the UI that edits them. The same screen
also hosts Defaults / Restore defaults (resets the preference keys documented on that page and does not modify learning sets, their per-set overrides, demo install, or CSV progress), Text to speech (language, auto-speak, and word-list speakers; see
Text to speech), Learning (retry until correct; see
Learning), and Demo sets (install bundled samples
via install_demo_sets(); that path writes CSVs and files.csv, not
preferences — see
Demo sets). Startup
restores theme before the first route so Home does not flash the wrong look;
see Startup.
Preference keys¶
| Key | Role |
|---|---|
theme_mode |
"dark" or "light" (ThemeMode value) |
dark_theme_slider_value / light_theme_slider_value |
Shade index 1–4 as a string |
dark_theme_bgcolor / light_theme_bgcolor |
Resolved page/view background color |
Missing keys are seeded once in app.py (default dark mode, slider 2,
SURFACE bgcolor).
Runtime flow¶
- Startup sets
page.theme_mode, applies the matching bgcolor, thenAppTheme.load_from_preferences()andAppTheme.sync_from_page(page). - Settings toggles light/dark: writes
theme_mode, syncsAppTheme, and reapplies the bgcolor for that mode (including the saved slider shade). - The shade slider updates
AppThemeslider/color for the active mode, callsAppTheme.apply_to_page, and persists{mode}_theme_slider_value/{mode}_theme_bgcolor. - The router and layout hosts call
AppTheme.apply_to_page/current_bgcolor()when building or resizing views so stacked views stay consistent.
set_theme_from_bgcolor in ui/page_functions.py is a thin wrapper around
AppTheme.apply_bgcolor used during startup.
Content background vs Shell chrome¶
| Layer | Where it lives | User-editable? |
|---|---|---|
| Page / view bgcolor | Preferences + AppTheme |
Yes — Settings shade slider |
| AppBar, bottom bar, FAB, icon/title colors | Hardcoded colors map in app.py |
No — change the map in code |
Do not put Shell teal (or other chrome colors) into preferences unless you intentionally design a second prefs surface for them.
Related¶
- Screen:
/settingsin Routing and screens - Demo install: Demo sets, Demo sets API
- APIs: App theme, Preferences
Text to speech¶
TTS language, auto-speak on definitions Check, and word-list
speakers have app defaults owned at runtime by TtsPreferences in
ui/tts_preferences.py. Settings is the UI that edits those defaults. A set
can store its own values in files.csv; an empty cell keeps using the app
default. See
Per-set settings.
Under Text to speech, Word list speakers separates word-formation switches
from definition-card switches, warns that definition text may not match the
global TTS language, and shows always-visible sample cards under Word
formations and Definitions.
Definitions learn (WordDefinitionField) calls AppSession.speak with the
set's effective language; auto-speak uses the set's effective flag and does
not show error snackbars (the speaker button still does). Word-list cards
(WordContainer) show per-field speakers when the set's effective switch is
on.
Cached MP3s live under tts_cache/ and are pruned when set content is deleted
or rewritten — see Data and storage.
Preference keys¶
| Key | Role |
|---|---|
tts_language |
gTTS language code (en, pl, zh-CN, …) |
tts_auto_speak_definitions |
"true" or "false"; pronounce the word after Check |
tts_speakers_word_formations |
"true" or "false"; speakers on word-formation list fields |
tts_speakers_word |
"true" or "false"; speakers on definition-list word fields |
tts_speakers_definition |
"true" or "false"; speakers on definition-list definition fields |
Missing keys are seeded in TtsPreferences.load_from_preferences() (English,
auto-speak on, word-formation and word speakers on, definition speakers off).
Related¶
- Screen:
/settingsfor the app defaults,/set/settingsfor one set, in Routing and screens - Playback: App session
- APIs: TTS preferences, Set settings, TTS
Learning¶
The retype until correct switch and the two practice-queue size sliders
are the app defaults, owned at runtime by LearnPreferences in
ui/learn_preferences.py. Settings is the UI that edits those defaults. A
set can store retry_until_correct and queue_size on its catalog row; an
empty cell keeps the app default.
BaseWordField reads the set's effective retry flag during Check: a wrong
first answer still writes statistics once, then extra attempts stay on the
same card and do not call good_answer_at_current_row /
bad_answer_at_current_row. draw_index_group reads the set's effective
queue size for the next group. Queue rules remain on AppData — see
Learning algorithm.
Preference keys¶
| Key | Role |
|---|---|
learn_retry_until_correct |
"true" or "false"; stay on a card until Check is correct |
learn_word_formations_queue_size |
integer string; max practice-queue size for word formations (3–10, default 5) |
learn_definitions_queue_size |
integer string; max practice-queue size for definitions (4–15, default 10) |
Missing keys are seeded in LearnPreferences.load_from_preferences() (retry
on, word formations 5, definitions 10). A queue size that cannot be read as
an integer becomes that default. A stored integer outside the range is
clamped to the minimum or maximum.
Related¶
- Screen:
/settingsfor the app defaults,/set/settingsfor one set, in Routing and screens - Session UI: UI components
- API: Learn preferences, Set settings
Where BodyRegistry fits¶
BodyRegistry in ui/body_registry.py stores only the active
Home and Import/Export TilesContainer instances so in-place search can
filter the same controls the user was already viewing. It should not hold
preferences, route parameters, form state, or general domain data. See
Body registry.
Where set data fits¶
Catalog rows, set CSVs, and learning statistics belong on disk via
data/file_path_manager.py and data/app_data.py. Per-set learning and
speech overrides are cells on the catalog row (data/set_settings.py), not
preference keys. Resetting progress uses set_default_progress; deleting a
set uses delate_set. During a learn session, queue and answer transitions
live on AppData — not in AppSession. Retry and queue size for that
session are the set's effective values.
See Data and storage and
Learning algorithm.
For generated contracts, see the App session API, Preferences API, App theme API, TTS preferences API, TTS API, Learn preferences API, Set settings API, Body registry API, File path manager API, App data API, Constants API, and CSV processor API.