Routing¶
Routing is split between a declarative registry and an imperative router. The registry describes each route; the router executes that description.
Registry responsibility¶
ui/route_registry.py is the source of truth for available
screens. ROUTE_REGISTRY keeps route-specific decisions out of buttons,
drawer code, and router conditionals.
A RouteDef connects a path to:
- a Shell or Deep route kind,
- a screen build factory,
- body wrapper and layout behavior,
- optional Shell chrome (
chrome=SHELL_CHROME[path]on existing Shell entries; runtime visibility still comes fromSHELL_CHROMEby path), - optional drawer metadata,
- optional fallback behavior.
For example, the Settings definition connects one path with its factory, wrapper, resize strategy, chrome, and drawer entry:
RouteDef(
path=SETTINGS_ROUTE,
kind=RouteKind.SHELL,
body_wrapper=BodyWrapperKind.BOTTOM_INSET_SHELL,
layout_kind=LayoutKind.SHELL,
chrome=SHELL_CHROME[SETTINGS_ROUTE],
build_factory=_build_settings_controls,
shell_layout_control=SettingsControl,
drawer_label="Settings",
drawer_icon="SETTINGS",
)
Drawer items are derived from definitions that provide a label and icon. A new drawer route therefore does not require a separate path-to-drawer mapping.
Router responsibility¶
ui/router.py:
- responds to Flet route and view-pop events,
- resolves the matching
RouteDef, - calls the route's build factory,
- applies the selected wrapper and Shell chrome from
SHELL_CHROME[path], - builds an
ft.View, - replaces or extends the view stack,
- dispatches responsive layout updates.
The router owns these invariants centrally. A screen should not decide how to
detach shared chrome, normalize unknown paths, or manipulate page.views.
Do not change a live view's route or replace the views underneath it — that
desyncs Android system Back from the Python stack, and Home can appear without
chrome.
Factory result and fallback¶
A factory receives the current ft.Page and decoded route parameters. It
returns raw controls when the route can be built, or None when required
input is missing.
Conceptually, the router handles the result like this:
controls = route_def.build_factory(page, params)
if controls is None:
fallback = route_def.fallback_path or HOME_ROUTE
return build_route_view(fallback)
wrapped_controls = apply_body_wrapper(controls, route_def.body_wrapper)
return build_view(wrapped_controls, route_def)
The helper names in this example describe the flow rather than form a public
API. The real implementation keeps wrapper and view construction private to
ui/router.py.
Why screens use navigation helpers¶
Screens should import navigate_to(), push_view(), go_back(), and
go_search() from ui/navigation.py, not internal router
functions. The helper module provides a small stable boundary and lets the
router change its stack, normalization, or update details without changing
every screen.
For a practical registration checklist, see Adding a screen. For the route map and Shell versus Deep roles, see Routing and screens. For symbol-level details, see the Route registry API, Router API, and Navigation API.