| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent bf6bb57 commit 828bbb0
17 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,14 +29,25 @@ Function component primitives: | |||
| 29 | 29 | - `pythonnative.use_state(initial)` — local component state | |
| 30 | 30 | - `pythonnative.use_reducer(reducer, initial_state)` — reducer-based state management; returns `(state, dispatch)` | |
| 31 | 31 | - `pythonnative.use_effect(effect, deps)` — side effects, run after native commit | |
| 32 | - - `pythonnative.use_navigation()` — navigation handle (push/pop/get_args) | ||
| 32 | + - `pythonnative.use_navigation()` — navigation handle (navigate/go_back/get_params) | ||
| 33 | + - `pythonnative.use_route()` — convenience hook for current route params | ||
| 34 | + - `pythonnative.use_focus_effect(effect, deps)` — like `use_effect` but only runs when the screen is focused | ||
| 33 | 35 | - `pythonnative.use_memo(factory, deps)` — memoised values | |
| 34 | 36 | - `pythonnative.use_callback(fn, deps)` — stable function references | |
| 35 | 37 | - `pythonnative.use_ref(initial)` — mutable ref object | |
| 36 | 38 | - `pythonnative.use_context(context)` — read from context | |
| 37 | 39 | - `pythonnative.create_context(default)` — create a new context | |
| 38 | 40 | - `pythonnative.Provider(context, value, child)` — provide a context value | |
| 39 | 41 | ||
| 42 | + ### Navigation | ||
| 43 | + | ||
| 44 | + Declarative, component-based navigation system: | ||
| 45 | + | ||
| 46 | + - `pythonnative.NavigationContainer(child)` — root container for the navigation tree | ||
| 47 | + - `pythonnative.create_stack_navigator()` — create a stack-based navigator (returns object with `.Navigator` and `.Screen`) | ||
| 48 | + - `pythonnative.create_tab_navigator()` — create a tab-based navigator | ||
| 49 | + - `pythonnative.create_drawer_navigator()` — create a drawer-based navigator | ||
| 50 | + | ||
| 40 | 51 | ### Batching | |
| 41 | 52 | ||
| 42 | 53 | - `pythonnative.batch_updates()` — context manager that batches multiple state updates into a single re-render | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -135,10 +135,17 @@ PythonNative provides cross-platform modules for common device APIs: | |||
| 135 | 135 | ||
| 136 | 136 | ## Navigation model overview | |
| 137 | 137 | ||
| 138 | - - See the Navigation guide for full details. | ||
| 139 | - - Navigation is handled via the `use_navigation()` hook, which returns a `NavigationHandle` with `.push()`, `.pop()`, and `.get_args()`. | ||
| 140 | - - iOS: one host `UIViewController` class, many instances pushed on a `UINavigationController`. | ||
| 141 | - - Android: single host `Activity` with a `NavHostFragment` and a stack of generic `PageFragment`s driven by a navigation graph. | ||
| 138 | + PythonNative provides two navigation approaches: | ||
| 139 | + | ||
| 140 | + - **Declarative navigators** (recommended): `NavigationContainer` with `create_stack_navigator()`, `create_tab_navigator()`, and `create_drawer_navigator()`. Navigation state is managed in Python as component state, and navigators are composable — you can nest tabs inside stacks, etc. | ||
| 141 | + - **Page-level navigation**: `use_navigation()` returns a `NavigationHandle` with `.navigate()`, `.go_back()`, and `.get_params()`, delegating to native platform navigation when running on device. | ||
| 142 | + | ||
| 143 | + Both approaches are supported. The declarative system uses the existing reconciler pipeline — navigators are function components that render the active screen via `use_state`, and navigation context is provided via `Provider`. | ||
| 144 | + | ||
| 145 | + See the [Navigation guide](../guides/navigation.md) for full details. | ||
| 146 | + | ||
| 147 | + - iOS: one host `UIViewController` class, many instances pushed on a `UINavigationController`. | ||
| 148 | + - Android: single host `Activity` with a `NavHostFragment` and a stack of generic `PageFragment`s driven by a navigation graph. | ||
| 142 | 149 | ||
| 143 | 150 | ## Related docs | |
| 144 | 151 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -174,7 +174,9 @@ Changing one `Counter` doesn't affect the other — each has its own hook state. | |||
| 174 | 174 | - `use_callback(fn, deps)` — stable function references | |
| 175 | 175 | - `use_ref(initial)` — mutable ref that persists across renders | |
| 176 | 176 | - `use_context(context)` — read from a context provider | |
| 177 | - - `use_navigation()` — navigation handle for push/pop between screens | ||
| 177 | + - `use_navigation()` — navigation handle for navigate/go_back/get_params | ||
| 178 | + - `use_route()` — convenience hook for current route params | ||
| 179 | + - `use_focus_effect(effect, deps)` — like `use_effect` but only runs when the screen is focused | ||
| 178 | 180 | ||
| 179 | 181 | ### Custom hooks | |
| 180 | 182 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -119,7 +119,7 @@ Dependency control: | |||
| 119 | 119 | ||
| 120 | 120 | ### use_navigation | |
| 121 | 121 | ||
| 122 | - Access the navigation stack from any component. Returns a `NavigationHandle` with `.push()`, `.pop()`, and `.get_args()`. | ||
| 122 | + Access navigation from any component. Returns a `NavigationHandle` with `.navigate()`, `.go_back()`, and `.get_params()`. | ||
| 123 | 123 | ||
| 124 | 124 | ```python | |
| 125 | 125 | @pn.component | |
@@ -130,25 +130,49 @@ def HomeScreen(): | |||
| 130 | 130 | pn.Text("Home", style={"font_size": 24}), | |
| 131 | 131 | pn.Button( | |
| 132 | 132 | "Go to Details", | |
| 133 | - on_click=lambda: nav.push(DetailScreen, args={"id": 42}), | ||
| 133 | + on_click=lambda: nav.navigate("Detail", params={"id": 42}), | ||
| 134 | 134 | ), | |
| 135 | 135 | style={"spacing": 12, "padding": 16}, | |
| 136 | 136 | ) | |
| 137 | 137 | ||
| 138 | 138 | @pn.component | |
| 139 | 139 | def DetailScreen(): | |
| 140 | 140 | nav = pn.use_navigation() | |
| 141 | - item_id = nav.get_args().get("id", 0) | ||
| 141 | + item_id = nav.get_params().get("id", 0) | ||
| 142 | 142 | ||
| 143 | 143 | return pn.Column( | |
| 144 | 144 | pn.Text(f"Detail #{item_id}", style={"font_size": 20}), | |
| 145 | - pn.Button("Back", on_click=nav.pop), | ||
| 145 | + pn.Button("Back", on_click=nav.go_back), | ||
| 146 | 146 | style={"spacing": 12, "padding": 16}, | |
| 147 | 147 | ) | |
| 148 | 148 | ``` | |
| 149 | 149 | ||
| 150 | 150 | See the [Navigation guide](../guides/navigation.md) for full details. | |
| 151 | 151 | ||
| 152 | + ### use_route | ||
| 153 | + | ||
| 154 | + Convenience hook to read the current route's parameters: | ||
| 155 | + | ||
| 156 | + ```python | ||
| 157 | + @pn.component | ||
| 158 | + def DetailScreen(): | ||
| 159 | + params = pn.use_route() | ||
| 160 | + item_id = params.get("id", 0) | ||
| 161 | + return pn.Text(f"Detail #{item_id}") | ||
| 162 | + ``` | ||
| 163 | + | ||
| 164 | + ### use_focus_effect | ||
| 165 | + | ||
| 166 | + Like `use_effect` but only runs when the screen is focused. Useful for refreshing data when navigating back to a screen: | ||
| 167 | + | ||
| 168 | + ```python | ||
| 169 | + @pn.component | ||
| 170 | + def FeedScreen(): | ||
| 171 | + items, set_items = pn.use_state([]) | ||
| 172 | + pn.use_focus_effect(lambda: load_items(set_items), []) | ||
| 173 | + return pn.FlatList(data=items, render_item=lambda item, i: pn.Text(item)) | ||
| 174 | + ``` | ||
| 175 | + | ||
| 152 | 176 | ### use_memo | |
| 153 | 177 | ||
| 154 | 178 | Memoise an expensive computation: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,53 +1,170 @@ | |||
| 1 | 1 | # Navigation | |
| 2 | 2 | ||
| 3 | - This guide shows how to navigate between screens and pass data using the `use_navigation()` hook. | ||
| 3 | + PythonNative offers two approaches to navigation: | ||
| 4 | 4 | ||
| 5 | - ## Push / Pop | ||
| 5 | + 1. **Declarative navigators** (recommended) — component-based, inspired by React Navigation | ||
| 6 | + 2. **Legacy push/pop** — imperative navigation via `use_navigation()` | ||
| 6 | 7 | ||
| 7 | - Call `pn.use_navigation()` inside a `@pn.component` to get a `NavigationHandle`. Use `.push()` and `.pop()` to change screens, passing a component reference with optional `args`. | ||
| 8 | + ## Declarative Navigation | ||
| 9 | + | ||
| 10 | + Declarative navigators manage screen state as components. Define your screens once, and the navigator handles rendering, transitions, and state. | ||
| 11 | + | ||
| 12 | + ### Stack Navigator | ||
| 13 | + | ||
| 14 | + A stack navigator manages a stack of screens — push to go forward, pop to go back. | ||
| 8 | 15 | ||
| 9 | 16 | ```python | |
| 10 | 17 | import pythonnative as pn | |
| 11 | - from app.second_page import SecondPage | ||
| 18 | + from pythonnative.navigation import NavigationContainer, create_stack_navigator | ||
| 19 | + | ||
| 20 | + Stack = create_stack_navigator() | ||
| 12 | 21 | ||
| 22 | + @pn.component | ||
| 23 | + def App(): | ||
| 24 | + return NavigationContainer( | ||
| 25 | + Stack.Navigator( | ||
| 26 | + Stack.Screen("Home", component=HomeScreen), | ||
| 27 | + Stack.Screen("Detail", component=DetailScreen), | ||
| 28 | + initial_route="Home", | ||
| 29 | + ) | ||
| 30 | + ) | ||
| 13 | 31 | ||
| 14 | 32 | @pn.component | |
| 15 | 33 | def HomeScreen(): | |
| 16 | 34 | nav = pn.use_navigation() | |
| 17 | 35 | return pn.Column( | |
| 18 | 36 | pn.Text("Home", style={"font_size": 24}), | |
| 19 | 37 | pn.Button( | |
| 20 | - "Go next", | ||
| 21 | - on_click=lambda: nav.push( | ||
| 22 | - SecondPage, | ||
| 23 | - args={"message": "Hello from Home"}, | ||
| 24 | - ), | ||
| 38 | + "Go to Detail", | ||
| 39 | + on_click=lambda: nav.navigate("Detail", params={"id": 42}), | ||
| 25 | 40 | ), | |
| 26 | 41 | style={"spacing": 12, "padding": 16}, | |
| 27 | 42 | ) | |
| 43 | + | ||
| 44 | + @pn.component | ||
| 45 | + def DetailScreen(): | ||
| 46 | + nav = pn.use_navigation() | ||
| 47 | + params = nav.get_params() | ||
| 48 | + return pn.Column( | ||
| 49 | + pn.Text(f"Detail #{params.get('id')}", style={"font_size": 20}), | ||
| 50 | + pn.Button("Back", on_click=nav.go_back), | ||
| 51 | + style={"spacing": 12, "padding": 16}, | ||
| 52 | + ) | ||
| 53 | + ``` | ||
| 54 | + | ||
| 55 | + ### Tab Navigator | ||
| 56 | + | ||
| 57 | + A tab navigator renders a tab bar and switches between screens. | ||
| 58 | + | ||
| 59 | + ```python | ||
| 60 | + from pythonnative.navigation import create_tab_navigator | ||
| 61 | + | ||
| 62 | + Tab = create_tab_navigator() | ||
| 63 | + | ||
| 64 | + @pn.component | ||
| 65 | + def App(): | ||
| 66 | + return NavigationContainer( | ||
| 67 | + Tab.Navigator( | ||
| 68 | + Tab.Screen("Home", component=HomeScreen, options={"title": "Home"}), | ||
| 69 | + Tab.Screen("Settings", component=SettingsScreen, options={"title": "Settings"}), | ||
| 70 | + ) | ||
| 71 | + ) | ||
| 28 | 72 | ``` | |
| 29 | 73 | ||
| 30 | - On the target screen, retrieve args with `nav.get_args()`: | ||
| 74 | + ### Drawer Navigator | ||
| 75 | + | ||
| 76 | + A drawer navigator provides a side menu for switching screens. | ||
| 31 | 77 | ||
| 32 | 78 | ```python | |
| 79 | + from pythonnative.navigation import create_drawer_navigator | ||
| 80 | + | ||
| 81 | + Drawer = create_drawer_navigator() | ||
| 82 | + | ||
| 83 | + @pn.component | ||
| 84 | + def App(): | ||
| 85 | + return NavigationContainer( | ||
| 86 | + Drawer.Navigator( | ||
| 87 | + Drawer.Screen("Home", component=HomeScreen, options={"title": "Home"}), | ||
| 88 | + Drawer.Screen("Profile", component=ProfileScreen, options={"title": "Profile"}), | ||
| 89 | + ) | ||
| 90 | + ) | ||
| 91 | + | ||
| 33 | 92 | @pn.component | |
| 34 | - def SecondPage(): | ||
| 93 | + def HomeScreen(): | ||
| 35 | 94 | nav = pn.use_navigation() | |
| 36 | - message = nav.get_args().get("message", "Second Page") | ||
| 37 | 95 | return pn.Column( | |
| 38 | - pn.Text(message, style={"font_size": 20}), | ||
| 39 | - pn.Button("Back", on_click=nav.pop), | ||
| 40 | - style={"spacing": 12, "padding": 16}, | ||
| 96 | + pn.Button("Open Menu", on_click=nav.open_drawer), | ||
| 97 | + pn.Text("Home Screen"), | ||
| 98 | + ) | ||
| 99 | + ``` | ||
| 100 | + | ||
| 101 | + ### Nesting Navigators | ||
| 102 | + | ||
| 103 | + Navigators can be nested — for example, tabs containing stacks: | ||
| 104 | + | ||
| 105 | + ```python | ||
| 106 | + Stack = create_stack_navigator() | ||
| 107 | + Tab = create_tab_navigator() | ||
| 108 | + | ||
| 109 | + @pn.component | ||
| 110 | + def HomeStack(): | ||
| 111 | + return Stack.Navigator( | ||
| 112 | + Stack.Screen("Feed", component=FeedScreen), | ||
| 113 | + Stack.Screen("Post", component=PostScreen), | ||
| 114 | + ) | ||
| 115 | + | ||
| 116 | + @pn.component | ||
| 117 | + def App(): | ||
| 118 | + return NavigationContainer( | ||
| 119 | + Tab.Navigator( | ||
| 120 | + Tab.Screen("Home", component=HomeStack, options={"title": "Home"}), | ||
| 121 | + Tab.Screen("Settings", component=SettingsScreen, options={"title": "Settings"}), | ||
| 122 | + ) | ||
| 41 | 123 | ) | |
| 42 | 124 | ``` | |
| 43 | 125 | ||
| 44 | 126 | ## NavigationHandle API | |
| 45 | 127 | ||
| 46 | - `pn.use_navigation()` returns a `NavigationHandle` with: | ||
| 128 | + Inside any screen rendered by a navigator, `pn.use_navigation()` returns a handle with: | ||
| 129 | + | ||
| 130 | + - **`.navigate(route_name, params=...)`** — navigate to a named route with optional params | ||
| 131 | + - **`.go_back()`** — pop the current screen | ||
| 132 | + - **`.get_params()`** — get the current route's params dict | ||
| 133 | + - **`.reset(route_name, params=...)`** — reset the stack to a single route | ||
| 134 | + | ||
| 135 | + ### Drawer-specific methods | ||
| 136 | + | ||
| 137 | + When inside a drawer navigator, the handle also provides: | ||
| 138 | + | ||
| 139 | + - **`.open_drawer()`** — open the drawer | ||
| 140 | + - **`.close_drawer()`** — close the drawer | ||
| 141 | + - **`.toggle_drawer()`** — toggle the drawer open/closed | ||
| 142 | + | ||
| 143 | + ## Focus-aware Effects | ||
| 47 | 144 | ||
| 48 | - - **`.push(component, args=...)`** — navigate to a new screen. Pass a component reference (the `@pn.component` function itself), with an optional `args` dict. | ||
| 49 | - - **`.pop()`** — go back to the previous screen. | ||
| 50 | - - **`.get_args()`** — retrieve the args dict passed by the caller. | ||
| 145 | + Use `pn.use_focus_effect()` to run effects only when a screen is focused: | ||
| 146 | + | ||
| 147 | + ```python | ||
| 148 | + @pn.component | ||
| 149 | + def DataScreen(): | ||
| 150 | + data, set_data = pn.use_state(None) | ||
| 151 | + | ||
| 152 | + pn.use_focus_effect(lambda: fetch_data(set_data), []) | ||
| 153 | + | ||
| 154 | + return pn.Text(f"Data: {data}") | ||
| 155 | + ``` | ||
| 156 | + | ||
| 157 | + ## Route Parameters | ||
| 158 | + | ||
| 159 | + Use `pn.use_route()` for convenient access to route params: | ||
| 160 | + | ||
| 161 | + ```python | ||
| 162 | + @pn.component | ||
| 163 | + def DetailScreen(): | ||
| 164 | + params = pn.use_route() | ||
| 165 | + item_id = params.get("id", 0) | ||
| 166 | + return pn.Text(f"Item #{item_id}") | ||
| 167 | + ``` | ||
| 51 | 168 | ||
| 52 | 169 | ## Lifecycle | |
| 53 | 170 | ||
@@ -63,11 +180,6 @@ PythonNative forwards lifecycle events from the host: | |||
| 63 | 180 | - `on_save_instance_state` | |
| 64 | 181 | - `on_restore_instance_state` | |
| 65 | 182 | ||
| 66 | - ## Notes | ||
| 67 | - | ||
| 68 | - - On Android, `push` navigates via `NavController` to a `PageFragment` and passes `page_path` and optional JSON `args`. | ||
| 69 | - - On iOS, `push` uses the root `UINavigationController` to push a new `ViewController` and passes page info via KVC. | ||
| 70 | - | ||
| 71 | 183 | ## Platform specifics | |
| 72 | 184 | ||
| 73 | 185 | ### iOS (UIViewController per page) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -47,9 +47,9 @@ def MainPage() -> pn.Element: | |||
| 47 | 47 | counter_badge(), | |
| 48 | 48 | pn.Button( | |
| 49 | 49 | "Go to Second Page", | |
| 50 | - on_click=lambda: nav.push( | ||
| 50 | + on_click=lambda: nav.navigate( | ||
| 51 | 51 | "app.second_page.SecondPage", | |
| 52 | - args={"message": "Greetings from MainPage"}, | ||
| 52 | + params={"message": "Greetings from MainPage"}, | ||
| 53 | 53 | ), | |
| 54 | 54 | ), | |
| 55 | 55 | style=styles["section"], | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,15 +4,15 @@ | |||
| 4 | 4 | @pn.component | |
| 5 | 5 | def SecondPage() -> pn.Element: | |
| 6 | 6 | nav = pn.use_navigation() | |
| 7 | - message = nav.get_args().get("message", "Second Page") | ||
| 7 | + message = nav.get_params().get("message", "Second Page") | ||
| 8 | 8 | return pn.ScrollView( | |
| 9 | 9 | pn.Column( | |
| 10 | 10 | pn.Text(message, style={"font_size": 24, "bold": True}), | |
| 11 | 11 | pn.Button( | |
| 12 | 12 | "Go to Third Page", | |
| 13 | - on_click=lambda: nav.push("app.third_page.ThirdPage"), | ||
| 13 | + on_click=lambda: nav.navigate("app.third_page.ThirdPage"), | ||
| 14 | 14 | ), | |
| 15 | - pn.Button("Back", on_click=nav.pop), | ||
| 15 | + pn.Button("Back", on_click=nav.go_back), | ||
| 16 | 16 | style={"spacing": 16, "padding": 24, "align_items": "stretch"}, | |
| 17 | 17 | ) | |
| 18 | 18 | ) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,7 +8,7 @@ def ThirdPage() -> pn.Element: | |||
| 8 | 8 | pn.Column( | |
| 9 | 9 | pn.Text("Third Page", style={"font_size": 24, "bold": True}), | |
| 10 | 10 | pn.Text("You navigated two levels deep."), | |
| 11 | - pn.Button("Back to Second", on_click=nav.pop), | ||
| 11 | + pn.Button("Back to Second", on_click=nav.go_back), | ||
| 12 | 12 | style={"spacing": 16, "padding": 24, "align_items": "stretch"}, | |
| 13 | 13 | ) | |
| 14 | 14 | ) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -52,6 +52,14 @@ def App(): | |||
| 52 | 52 | use_ref, | |
| 53 | 53 | use_state, | |
| 54 | 54 | ) | |
| 55 | + from .navigation import ( | ||
| 56 | + NavigationContainer, | ||
| 57 | + create_drawer_navigator, | ||
| 58 | + create_stack_navigator, | ||
| 59 | + create_tab_navigator, | ||
| 60 | + use_focus_effect, | ||
| 61 | + use_route, | ||
| 62 | + ) | ||
| 55 | 63 | from .page import create_page | |
| 56 | 64 | from .style import StyleSheet, ThemeContext | |
| 57 | 65 | ||
@@ -86,12 +94,19 @@ def App(): | |||
| 86 | 94 | "use_callback", | |
| 87 | 95 | "use_context", | |
| 88 | 96 | "use_effect", | |
| 97 | + "use_focus_effect", | ||
| 89 | 98 | "use_memo", | |
| 90 | 99 | "use_navigation", | |
| 91 | 100 | "use_reducer", | |
| 92 | 101 | "use_ref", | |
| 102 | + "use_route", | ||
| 93 | 103 | "use_state", | |
| 94 | 104 | "Provider", | |
| 105 | + # Navigation | ||
| 106 | + "NavigationContainer", | ||
| 107 | + "create_drawer_navigator", | ||
| 108 | + "create_stack_navigator", | ||
| 109 | + "create_tab_navigator", | ||
| 95 | 110 | # Styling | |
| 96 | 111 | "StyleSheet", | |
| 97 | 112 | "ThemeContext", | |
| Back | FazBrowse Home | New Git URL |
0 commit comments