Skip to content

Router

Source: ui/router.py

Route-change and resize handlers that build views, synchronize shell chrome, and apply route-specific layouts. Screen authors normally use Navigation API instead; see the Routing concept for the end-to-end flow and Startup and lifecycle for when handlers are attached in app.py.

Coordinate route views, shared chrome, and responsive layout state.

is_current_route

is_current_route(page: Page, route: str) -> bool

Return whether the page's current route path matches a path.

Query parameters on page.route are ignored.

Parameters:

Name Type Description Default
page Page

Page whose current route should be inspected.

required
route str

Canonical route path to compare.

required

Returns:

Type Description
bool

True when the current route path equals route.

Source code in src/learning_app/ui/router.py
def is_current_route(page: ft.Page, route: str) -> bool:
    """Return whether the page's current route path matches a path.

    Query parameters on ``page.route`` are ignored.

    Args:
        page: Page whose current route should be inspected.
        route: Canonical route path to compare.

    Returns:
        ``True`` when the current route path equals ``route``.
    """
    return route_path(page.route) == route

reset_to_route async

reset_to_route(page: Page, full_route: str)

Replace the view stack with one normalized route.

Unknown paths resolve to the home route. The operation rebuilds the target view, updates the browser/page route, reapplies the theme, and refreshes the page. When a deep-route factory falls back (for example a missing set file or a stale create-set edit URL whose CSV already exists), the fallback view route is pushed so the URL stays consistent.

Parameters:

Name Type Description Default
page Page

Page whose view stack should be replaced.

required
full_route str

Route path with optional encoded query parameters.

required
Source code in src/learning_app/ui/router.py
async def reset_to_route(page: ft.Page, full_route: str):
    """Replace the view stack with one normalized route.

    Unknown paths resolve to the home route. The operation rebuilds the target
    view, updates the browser/page route, reapplies the theme, and refreshes the
    page. When a deep-route factory falls back (for example a missing set file
    or a stale create-set edit URL whose CSV already exists), the fallback view
    route is pushed so the URL stays consistent.

    Args:
        page: Page whose view stack should be replaced.
        full_route: Route path with optional encoded query parameters.
    """
    from learning_app.ui.inplace_search import close_inplace_search

    close_inplace_search(page)
    requested_route = _normalize_full_route(full_route)

    LayoutMetricsStore.refresh(page)
    view = _build_route_view(requested_route, page)
    resolved_route = view.route or HOME_ROUTE
    fell_back = not routes_match(resolved_route, requested_route)

    _update_drawer_selection(resolved_route)
    _detach_shared_chrome_from_views(page)
    page.views[:] = [view]
    await page.push_route(resolved_route)
    AppTheme.apply_to_page(page)
    if fell_back and route_params(requested_route).get("file"):
        _notify_unavailable_set(page, requested_route)
    page.update()

reanchor_active_deep_view_on_home async

reanchor_active_deep_view_on_home(
    page: Page, edit_route: str
) -> None

Replace the stack with a rebuilt Home and a rebuilt edit view.

Used after the first save of a newly created set so UI Back and system back reveal a fresh home catalog instead of the create-set form and a stale tile list. Both views are new objects assigned in one update so Flutter's navigator is not left with the previous create-set stack under a mutated live edit view.

Parameters:

Name Type Description Default
page Page

Page whose view stack should be rewritten.

required
edit_route str

Canonical edit route URL (typically file only, no create title / subtitle query params).

required
Source code in src/learning_app/ui/router.py
async def reanchor_active_deep_view_on_home(page: ft.Page, edit_route: str) -> None:
    """Replace the stack with a rebuilt Home and a rebuilt edit view.

    Used after the first save of a newly created set so UI Back and system back
    reveal a fresh home catalog instead of the create-set form and a stale tile
    list. Both views are new objects assigned in one update so Flutter's
    navigator is not left with the previous create-set stack under a mutated
    live edit view.

    Args:
        page: Page whose view stack should be rewritten.
        edit_route: Canonical edit route URL (typically ``file`` only, no create
            ``title`` / ``subtitle`` query params).
    """
    from learning_app.ui.inplace_search import close_inplace_search

    close_inplace_search(page)
    LayoutMetricsStore.refresh(page)
    _detach_shared_chrome_from_views(page)

    home = _build_route_view(HOME_ROUTE, page)
    edit = _build_route_view(edit_route, page)
    resolved_edit = edit.route or HOME_ROUTE
    if not routes_match(resolved_edit, edit_route):
        await reset_to_route(page, resolved_edit)
        return

    page.views[:] = [home, edit]
    _sync_chrome_for_route(resolved_edit, page)
    await page.push_route(resolved_edit)
    AppTheme.apply_to_page(page)
    page.update()

push_route_view async

push_route_view(page: Page, full_route: str)

Push a normalized deep route or reset to a shell route.

An already-active complete route is not duplicated. Unknown routes and registered shell routes are handled as stack replacements. When the deep factory falls back (missing file, deleted set CSV, or stale create-set edit URL), the stack is reset to the fallback route instead of pushing a mismatched URL.

Parameters:

Name Type Description Default
page Page

Page whose view stack should be updated.

required
full_route str

Route path with optional encoded query parameters.

required
Source code in src/learning_app/ui/router.py
async def push_route_view(page: ft.Page, full_route: str):
    """Push a normalized deep route or reset to a shell route.

    An already-active complete route is not duplicated. Unknown routes and
    registered shell routes are handled as stack replacements. When the deep
    factory falls back (missing ``file``, deleted set CSV, or stale create-set
    edit URL), the stack is reset to the fallback route instead of pushing a
    mismatched URL.

    Args:
        page: Page whose view stack should be updated.
        full_route: Route path with optional encoded query parameters.
    """
    from learning_app.ui.inplace_search import close_inplace_search

    close_inplace_search(page)
    full_route = _normalize_full_route(full_route)
    path = route_path(full_route)

    if path not in DEEP_ROUTES:
        await reset_to_route(page, full_route)
        return

    if _route_already_active(page, full_route):
        AppTheme.apply_to_page(page)
        page.update()
        return

    LayoutMetricsStore.refresh(page)
    view = _build_route_view(full_route, page)
    if not routes_match(view.route, full_route):
        # Factory fell back (missing file, etc.): replace stack and sync URL.
        resolved_route = view.route or HOME_ROUTE
        _update_drawer_selection(resolved_route)
        _detach_shared_chrome_from_views(page)
        page.views[:] = [view]
        await page.push_route(resolved_route)
        AppTheme.apply_to_page(page)
        if route_params(full_route).get("file"):
            _notify_unavailable_set(page, full_route)
        page.update()
        return

    page.views.append(view)
    await page.push_route(full_route)
    AppTheme.apply_to_page(page)
    page.update()

remove_views_for_set_file

remove_views_for_set_file(
    page: Page, file_name: str
) -> None

Drop stacked views that reference a deleted set and resync the top route.

Parameters:

Name Type Description Default
page Page

Page whose view stack may contain learn/edit/session views.

required
file_name str

Set basename or path that was removed.

required
Source code in src/learning_app/ui/router.py
def remove_views_for_set_file(page: ft.Page, file_name: str) -> None:
    """Drop stacked views that reference a deleted set and resync the top route.

    Args:
        page: Page whose view stack may contain learn/edit/session views.
        file_name: Set basename or path that was removed.
    """
    if not page.views:
        return

    kept = [view for view in page.views if not _view_refers_to_set_file(view.route, file_name)]
    if len(kept) == len(page.views):
        return

    if not kept:
        page.run_task(reset_to_route, page, HOME_ROUTE)
        return

    page.views[:] = kept
    top_route = kept[-1].route or HOME_ROUTE
    page.run_task(_resync_after_view_prune, page, top_route)

pop_route_view async

pop_route_view(page: Page)

Remove the active view and restore state for the revealed route.

When no previous view exists, the page is reset to the home route.

Parameters:

Name Type Description Default
page Page

Page whose active route view should be removed.

required
Source code in src/learning_app/ui/router.py
async def pop_route_view(page: ft.Page):
    """Remove the active view and restore state for the revealed route.

    When no previous view exists, the page is reset to the home route.

    Args:
        page: Page whose active route view should be removed.
    """
    if len(page.views) <= 1:
        await reset_to_route(page, HOME_ROUTE)
        return

    page.views.pop()
    top_view = page.views[-1]
    full_route = top_view.route or HOME_ROUTE

    _restore_active_view_state(page, full_route)
    AppTheme.apply_to_page(page)
    await page.push_route(full_route)
    page.update()

initialize_routes async

initialize_routes(page: Page)

Initialize a page with the home route.

Parameters:

Name Type Description Default
page Page

Page whose route stack should be initialized.

required
Source code in src/learning_app/ui/router.py
async def initialize_routes(page: ft.Page):
    """Initialize a page with the home route.

    Args:
        page: Page whose route stack should be initialized.
    """
    await reset_to_route(page, HOME_ROUTE)

handle_route_change

handle_route_change(e: RouteChangeEvent)

Synchronize or schedule navigation after a Flet route change.

Active routes are refreshed in place. Routes already present below the top of the view stack (typical browser back/forward) pop down to that view instead of pushing a duplicate. Drawer routes replace the stack, and new deep routes are pushed above the active shell view.

Parameters:

Name Type Description Default
e RouteChangeEvent

Route-change event containing the affected page.

required
Source code in src/learning_app/ui/router.py
def handle_route_change(e: ft.RouteChangeEvent):
    """Synchronize or schedule navigation after a Flet route change.

    Active routes are refreshed in place. Routes already present below the top
    of the view stack (typical browser back/forward) pop down to that view
    instead of pushing a duplicate. Drawer routes replace the stack, and new
    deep routes are pushed above the active shell view.

    Args:
        e: Route-change event containing the affected page.
    """
    page = e.page
    full_route = page.route or HOME_ROUTE
    path = _resolve_path(route_path(full_route))

    if _route_already_active(page, full_route):
        _update_drawer_selection(full_route)
        _sync_chrome_for_route(full_route, page)
        _apply_layout_for_route(page, path)
        AppTheme.apply_to_page(page)
        page.update()
        return

    # Browser history moved to a route that is already under the top view.
    existing_index = _index_of_route_in_views(page, full_route)
    if existing_index is not None:
        del page.views[existing_index + 1 :]
        _restore_active_view_state(page, full_route)
        AppTheme.apply_to_page(page)
        page.update()
        return

    if path in DRAWER_ROUTES:
        page.run_task(reset_to_route, page, full_route)
    elif path in DEEP_ROUTES:
        page.run_task(push_route_view, page, full_route)

handle_view_pop async

handle_view_pop(e: ViewPopEvent)

Remove the popped view and restore the route exposed underneath.

When in-place search is open, close it instead of popping the shell view (search is not a route).

Parameters:

Name Type Description Default
e ViewPopEvent

View-pop event containing the affected page.

required
Source code in src/learning_app/ui/router.py
async def handle_view_pop(e: ft.ViewPopEvent):
    """Remove the popped view and restore the route exposed underneath.

    When in-place search is open, close it instead of popping the shell view
    (search is not a route).

    Args:
        e: View-pop event containing the affected page.
    """
    from learning_app.ui.inplace_search import close_inplace_search, is_inplace_search_active

    page = e.page

    if is_inplace_search_active(page):
        close_inplace_search(page)
        return

    _drop_popped_view(page, e)

    if not page.views:
        await initialize_routes(page)
        return

    top_view = page.views[-1]
    full_route = top_view.route or HOME_ROUTE

    _restore_active_view_state(page, full_route)
    AppTheme.apply_to_page(page)
    await page.push_route(full_route)
    page.update()

handle_page_resize

handle_page_resize(e: ControlEvent)

Reapply route layout and theme after a page resize.

Parameters:

Name Type Description Default
e ControlEvent

Resize event containing the affected page.

required
Source code in src/learning_app/ui/router.py
def handle_page_resize(e: ft.ControlEvent):
    """Reapply route layout and theme after a page resize.

    Args:
        e: Resize event containing the affected page.
    """
    page = e.page
    _apply_layout_for_route(page, route_path(page.route or HOME_ROUTE))
    from learning_app.ui.inplace_search import sync_inplace_search_layout

    sync_inplace_search_layout(page)
    AppTheme.apply_to_page(page)