| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 2488faf commit d8e8933
36 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -107,12 +107,15 @@ Recommended scopes (choose the smallest, most accurate unit; prefer module/direc | |||
| 107 | 107 | - `native_modules` – native API modules for device capabilities (`native_modules/`) | |
| 108 | 108 | - `native_views` – platform-specific native view creation and updates (`native_views/`) | |
| 109 | 109 | - `navigation` – navigation containers and stack/tab/drawer navigators (`navigation.py`) | |
| 110 | + - `net` – awaitable HTTP client (`net.py`) | ||
| 110 | 111 | - `package` – `src/pythonnative/__init__.py` exports and package boundary | |
| 111 | 112 | - `platform` – `Platform.OS`/`Platform.select` and version detection (`platform.py`) | |
| 112 | 113 | - `platform_metrics` – platform-reported metrics like safe-area insets and bar heights (`platform_metrics.py`) | |
| 113 | 114 | - `reconciler` – virtual view tree diffing and reconciliation (`reconciler.py`) | |
| 115 | + - `runtime` – framework-wide asyncio loop and thread-safe future helpers (`runtime.py`) | ||
| 114 | 116 | - `screen` – screen host, native lifecycle bridge, and render scheduling (`screen.py`) | |
| 115 | 117 | - `sdk` – public extension SDK for custom native components (`sdk/`) | |
| 118 | + - `storage` – AsyncStorage key/value persistence and `use_persisted_state` (`storage.py`) | ||
| 116 | 119 | - `style` – StyleSheet and theming (`style.py`) | |
| 117 | 120 | - `utils` – shared utilities (`utils.py`) | |
| 118 | 121 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,9 +1,18 @@ | |||
| 1 | 1 | # Alerts | |
| 2 | 2 | ||
| 3 | - The [`Alert`][pythonnative.Alert] class provides imperative access to | ||
| 4 | - the host platform's alert dialogs and action sheets. Alerts are *not* | ||
| 5 | - part of the element tree — they're fire-and-forget calls that present | ||
| 6 | - a native dialog and dispatch button callbacks. | ||
| 3 | + The [`Alert`][pythonnative.alerts.Alert] class provides imperative | ||
| 4 | + access to the host platform's alert dialogs and action sheets. Alerts | ||
| 5 | + are *not* part of the element tree. | ||
| 6 | + | ||
| 7 | + There are three entry points: | ||
| 8 | + | ||
| 9 | + - [`Alert.show`][pythonnative.alerts.Alert.show]: fire-and-forget | ||
| 10 | + one-button notice (no return value). | ||
| 11 | + - [`Alert.confirm`][pythonnative.alerts.Alert.confirm]: awaitable | ||
| 12 | + two-button yes/no, resolves to a ``bool``. | ||
| 13 | + - [`Alert.choose`][pythonnative.alerts.Alert.choose]: awaitable | ||
| 14 | + multi-button picker / action sheet, resolves to the selected | ||
| 15 | + label (or ``None`` if dismissed). | ||
| 7 | 16 | ||
| 8 | 17 | ::: pythonnative.alerts | |
| 9 | 18 | options: | |
@@ -14,17 +23,20 @@ a native dialog and dispatch button callbacks. | |||
| 14 | 23 | ||
| 15 | 24 | ## Patterns | |
| 16 | 25 | ||
| 17 | - - **Confirm before destructive actions**: pair a `"destructive"` | ||
| 18 | - button with a `"cancel"` button via | ||
| 19 | - [`Alert.confirm`][pythonnative.alerts.Alert.confirm]. | ||
| 20 | - - **Action sheets**: pass `style="action_sheet"` to render an iOS-style | ||
| 21 | - bottom sheet; on Android this falls back to a regular dialog. | ||
| 22 | - - **Pickers**: the built-in [`Picker`][pythonnative.Picker] component | ||
| 23 | - is implemented on top of action sheets — use it for select/dropdown | ||
| 26 | + - **Confirm before destructive actions**: ``await pn.Alert.confirm(...)`` | ||
| 27 | + inside an `async def`, then branch on the boolean result. | ||
| 28 | + - **Pick from options**: ``await pn.Alert.choose(title, options=[...])`` | ||
| 29 | + returns the selected label. | ||
| 30 | + - **Pickers**: the built-in | ||
| 31 | + [`Picker`][pythonnative.components.Picker] component is | ||
| 32 | + implemented on top of action sheets — use it for select/dropdown | ||
| 24 | 33 | widgets. | |
| 25 | 34 | ||
| 26 | 35 | ## Testing | |
| 27 | 36 | ||
| 28 | - When running off-device (e.g., in unit tests), `Alert.show` records | ||
| 29 | - each call to `Alert._test_log` instead of presenting a dialog. Reset | ||
| 30 | - the log with `Alert._test_log.clear()` between cases. | ||
| 37 | + When running off-device (e.g., in unit tests), the alert dispatch | ||
| 38 | + records each call to `Alert._test_log` instead of presenting a | ||
| 39 | + dialog. Use | ||
| 40 | + [`Alert.set_test_response(*indices)`][pythonnative.alerts.Alert.set_test_response] | ||
| 41 | + to script the user's choices for upcoming ``confirm`` / ``choose`` | ||
| 42 | + calls. Reset the log with `Alert._test_log.clear()` between cases. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,6 +12,25 @@ slot across renders. | |||
| 12 | 12 | members_order: source | |
| 13 | 13 | filters: ["!^_"] | |
| 14 | 14 | ||
| 15 | + ## Async hooks | ||
| 16 | + | ||
| 17 | + For coroutines and data-driven UI, PythonNative ships dedicated | ||
| 18 | + async-aware hooks layered on top of `use_state` / `use_effect`: | ||
| 19 | + | ||
| 20 | + - [`use_async_effect`][pythonnative.use_async_effect] — async sibling | ||
| 21 | + of `use_effect`; cancels the in-flight coroutine on re-run / | ||
| 22 | + unmount. | ||
| 23 | + - [`use_query`][pythonnative.use_query] — subscribes to an async | ||
| 24 | + fetcher and re-renders on data / error / refetch. | ||
| 25 | + - [`use_mutation`][pythonnative.use_mutation] — wraps an async | ||
| 26 | + mutator with loading / error state and a trigger. | ||
| 27 | + - [`use_persisted_state`][pythonnative.use_persisted_state] — | ||
| 28 | + `use_state` backed by | ||
| 29 | + [`AsyncStorage`][pythonnative.AsyncStorage]. | ||
| 30 | + | ||
| 31 | + See the [Async + data guide](../guides/async.md) for a complete | ||
| 32 | + walkthrough. | ||
| 33 | + | ||
| 15 | 34 | ## Platform-metric hooks | |
| 16 | 35 | ||
| 17 | 36 | These hooks subscribe to values published by | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,12 +6,10 @@ local notifications. Each module is implemented twice (once per | |||
| 6 | 6 | platform) and dispatches at runtime based on the `IS_ANDROID` and | |
| 7 | 7 | `IS_IOS` flags from `pythonnative.utils`. | |
| 8 | 8 | ||
| 9 | - ::: pythonnative.native_modules | ||
| 10 | - options: | ||
| 11 | - show_root_heading: false | ||
| 12 | - show_root_toc_entry: false | ||
| 13 | - members_order: source | ||
| 14 | - filters: ["!^_"] | ||
| 9 | + Apart from `FileSystem`, every public method is a coroutine: ``await | ||
| 10 | + Camera.take_photo()``, ``await Location.get_current()``, and so on. | ||
| 11 | + For the call-site patterns and the runtime they're scheduled on, see | ||
| 12 | + the [Async + data guide](../guides/async.md). | ||
| 15 | 13 | ||
| 16 | 14 | ## Camera | |
| 17 | 15 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,23 @@ | |||
| 1 | + # Network | ||
| 2 | + | ||
| 3 | + A small, dependency-free async HTTP client. Use [`fetch`][pythonnative.fetch] | ||
| 4 | + for the common "call a JSON API" path; reach for `httpx` / `aiohttp` | ||
| 5 | + if you need multipart, streaming, or HTTP/2. | ||
| 6 | + | ||
| 7 | + ::: pythonnative.net | ||
| 8 | + options: | ||
| 9 | + show_root_heading: false | ||
| 10 | + show_root_toc_entry: false | ||
| 11 | + members_order: source | ||
| 12 | + filters: ["!^_"] | ||
| 13 | + | ||
| 14 | + ## Patterns | ||
| 15 | + | ||
| 16 | + - **Inside a component**: pair with | ||
| 17 | + [`use_query`][pythonnative.use_query] for loading/error state and | ||
| 18 | + automatic cancellation on unmount. | ||
| 19 | + - **In an event handler**: wrap an `async def` in | ||
| 20 | + [`pn.run_async`][pythonnative.run_async] so a sync `on_click` can | ||
| 21 | + drive an awaitable request. | ||
| 22 | + - **Mutations**: pair with [`use_mutation`][pythonnative.use_mutation] | ||
| 23 | + to track ``loading`` / ``error`` for POST/PUT/DELETE flows. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,41 @@ | |||
| 1 | + # Async runtime | ||
| 2 | + | ||
| 3 | + PythonNative runs a single framework-wide ``asyncio`` event loop on a | ||
| 4 | + dedicated daemon thread. Every awaitable surface in the framework — | ||
| 5 | + [`use_async_effect`][pythonnative.hooks.use_async_effect], | ||
| 6 | + [`use_query`][pythonnative.hooks.use_query], | ||
| 7 | + [`use_mutation`][pythonnative.hooks.use_mutation], | ||
| 8 | + [`fetch`][pythonnative.net.fetch], | ||
| 9 | + [`AsyncStorage`][pythonnative.storage.AsyncStorage], the awaitable native | ||
| 10 | + modules ([`Camera`][pythonnative.native_modules.camera.Camera] / | ||
| 11 | + [`Location`][pythonnative.native_modules.location.Location] / | ||
| 12 | + [`Notifications`][pythonnative.native_modules.notifications.Notifications]), | ||
| 13 | + and [`Animated`][pythonnative.animated.Animated] composites — schedules | ||
| 14 | + its work on this loop. | ||
| 15 | + | ||
| 16 | + ::: pythonnative.runtime | ||
| 17 | + options: | ||
| 18 | + show_root_heading: false | ||
| 19 | + show_root_toc_entry: false | ||
| 20 | + members_order: source | ||
| 21 | + filters: ["!^_"] | ||
| 22 | + | ||
| 23 | + ## Pattern: bridge a sync handler into async code | ||
| 24 | + | ||
| 25 | + ```python | ||
| 26 | + import pythonnative as pn | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + @pn.component | ||
| 30 | + def Toolbar(): | ||
| 31 | + async def export(): | ||
| 32 | + report = await build_report() | ||
| 33 | + await save_to_disk(report) | ||
| 34 | + | ||
| 35 | + return pn.Button("Export", on_click=lambda: pn.run_async(export())) | ||
| 36 | + ``` | ||
| 37 | + | ||
| 38 | + ## Next steps | ||
| 39 | + | ||
| 40 | + - Walk through the async surface end-to-end: | ||
| 41 | + [Async + data guide](../guides/async.md). | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,31 @@ | |||
| 1 | + # Storage | ||
| 2 | + | ||
| 3 | + Key/value persistence backed by the platform-native store | ||
| 4 | + (`NSUserDefaults` on iOS, `SharedPreferences` on Android, a local | ||
| 5 | + JSON file in desktop tests). All operations are coroutines so they | ||
| 6 | + don't block the framework loop. | ||
| 7 | + | ||
| 8 | + ::: pythonnative.storage | ||
| 9 | + options: | ||
| 10 | + show_root_heading: false | ||
| 11 | + show_root_toc_entry: false | ||
| 12 | + members_order: source | ||
| 13 | + filters: ["!^_"] | ||
| 14 | + | ||
| 15 | + ## Patterns | ||
| 16 | + | ||
| 17 | + - **Token / session storage**: write strings with | ||
| 18 | + [`set`][pythonnative.AsyncStorage] and read with | ||
| 19 | + [`get`][pythonnative.AsyncStorage]. | ||
| 20 | + - **Complex values**: use | ||
| 21 | + [`set_json`][pythonnative.AsyncStorage] / | ||
| 22 | + [`get_json`][pythonnative.AsyncStorage] for dicts, lists, and | ||
| 23 | + primitives. | ||
| 24 | + - **Component state that survives restarts**: reach for | ||
| 25 | + [`use_persisted_state`][pythonnative.use_persisted_state] instead | ||
| 26 | + of manually wiring an effect to `AsyncStorage`. | ||
| 27 | + | ||
| 28 | + ## Next steps | ||
| 29 | + | ||
| 30 | + - The pattern is walked through end-to-end in the | ||
| 31 | + [Async + data guide](../guides/async.md). | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -104,10 +104,13 @@ pn.Column( | |||
| 104 | 104 | ||
| 105 | 105 | **Imperative APIs:** | |
| 106 | 106 | ||
| 107 | - - [`Alert.show(title, message, buttons, style)`][pythonnative.Alert]: | ||
| 108 | - present a native alert dialog or action sheet. | ||
| 109 | - - [`Alert.confirm(title, on_confirm, on_cancel)`][pythonnative.alerts.Alert.confirm]: | ||
| 110 | - two-button confirm/cancel. | ||
| 107 | + - [`Alert.show(title, message)`][pythonnative.alerts.Alert.show]: | ||
| 108 | + fire-and-forget single-button notice. | ||
| 109 | + - [`await Alert.confirm(title, message)`][pythonnative.alerts.Alert.confirm]: | ||
| 110 | + awaitable two-button yes/no — resolves to a ``bool``. | ||
| 111 | + - [`await Alert.choose(title, options=[...])`][pythonnative.alerts.Alert.choose]: | ||
| 112 | + awaitable multi-button picker / action sheet — resolves to the | ||
| 113 | + selected label (or ``None``). | ||
| 111 | 114 | ||
| 112 | 115 | **Animations:** | |
| 113 | 116 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,10 +1,9 @@ | |||
| 1 | 1 | # Animations | |
| 2 | 2 | ||
| 3 | 3 | PythonNative ships an `Animated` API modelled on React Native's. It's | |
| 4 | - designed for the common case where a small set | ||
| 5 | - of style properties (opacity, transform, color) need to interpolate | ||
| 6 | - smoothly over time without re-rendering the component tree on every | ||
| 7 | - frame. | ||
| 4 | + designed for the common case where a small set of style properties | ||
| 5 | + (opacity, transform, color) need to interpolate smoothly over time | ||
| 6 | + without re-rendering the component tree on every frame. | ||
| 8 | 7 | ||
| 9 | 8 | ## Mental model | |
| 10 | 9 | ||
@@ -14,8 +13,10 @@ frame. | |||
| 14 | 13 | 2. Bind the value into the `style` of an `Animated.View`, | |
| 15 | 14 | `Animated.Text`, or `Animated.Image`. | |
| 16 | 15 | 3. Drive the value with `Animated.timing`, `Animated.spring`, or | |
| 17 | - `Animated.decay`. Each driver returns a handle with `.start()` / | ||
| 18 | - `.stop()`. | ||
| 16 | + `Animated.decay`. Each driver returns a handle with two faces: | ||
| 17 | + - `handle.start()` is fire-and-forget (returns `self`). | ||
| 18 | + - `await handle` runs the animation and suspends until it | ||
| 19 | + completes. Cancelling the awaiting task stops the animation. | ||
| 19 | 20 | ||
| 20 | 21 | The animated component captures a `ref` to the underlying native view | |
| 21 | 22 | (via the same [`use_ref`][pythonnative.use_ref] mechanism users have | |
@@ -34,10 +35,10 @@ import pythonnative as pn | |||
| 34 | 35 | def FadeInBox(): | |
| 35 | 36 | opacity = pn.use_animated_value(0.0) | |
| 36 | 37 | ||
| 37 | - def _fade_in(): | ||
| 38 | - pn.Animated.timing(opacity, to=1.0, duration=400).start() | ||
| 38 | + async def _fade_in(): | ||
| 39 | + await pn.Animated.timing(opacity, to=1.0, duration=400) | ||
| 39 | 40 | ||
| 40 | - pn.use_effect(_fade_in, []) | ||
| 41 | + pn.use_async_effect(_fade_in, []) | ||
| 41 | 42 | ||
| 42 | 43 | return pn.Animated.View( | |
| 43 | 44 | pn.Text("Hello!"), | |
@@ -51,7 +52,17 @@ def FadeInBox(): | |||
| 51 | 52 | ``` | |
| 52 | 53 | ||
| 53 | 54 | `opacity` starts at `0.0` and the timing animation interpolates it to | |
| 54 | - `1.0` over 400 ms. | ||
| 55 | + `1.0` over 400 ms. Using `use_async_effect` means the in-flight | ||
| 56 | + animation is automatically cancelled if the component unmounts before | ||
| 57 | + the 400 ms is up. | ||
| 58 | + | ||
| 59 | + If you don't need to react to completion, the synchronous form is fine | ||
| 60 | + too: | ||
| 61 | + | ||
| 62 | + ```python | ||
| 63 | + def _press(): | ||
| 64 | + pn.Animated.timing(opacity, to=1.0, duration=400).start() | ||
| 65 | + ``` | ||
| 55 | 66 | ||
| 56 | 67 | ## Spring animation on press | |
| 57 | 68 | ||
@@ -80,17 +91,21 @@ animation property. | |||
| 80 | 91 | ## Sequencing and parallel composition | |
| 81 | 92 | ||
| 82 | 93 | ```python | |
| 83 | - opacity = pn.use_animated_value(0.0) | ||
| 84 | - translate_y = pn.use_animated_value(20.0) | ||
| 85 | - | ||
| 86 | - pn.Animated.parallel([ | ||
| 87 | - pn.Animated.timing(opacity, to=1.0, duration=300), | ||
| 88 | - pn.Animated.spring(translate_y, to=0.0), | ||
| 89 | - ]).start() | ||
| 94 | + async def _intro(): | ||
| 95 | + opacity = pn.use_animated_value(0.0) | ||
| 96 | + translate_y = pn.use_animated_value(20.0) | ||
| 97 | + | ||
| 98 | + await pn.Animated.parallel([ | ||
| 99 | + pn.Animated.timing(opacity, to=1.0, duration=300), | ||
| 100 | + pn.Animated.spring(translate_y, to=0.0), | ||
| 101 | + ]) | ||
| 102 | + await pn.Animated.delay(80) | ||
| 103 | + await pn.Animated.timing(opacity, to=0.5, duration=200) | ||
| 90 | 104 | ``` | |
| 91 | 105 | ||
| 92 | - Use `Animated.sequence` for one-after-another execution and | ||
| 93 | - `Animated.delay(ms)` to insert pauses inside a sequence. | ||
| 106 | + `Animated.parallel` returns when **all** animations finish. | ||
| 107 | + `Animated.sequence` runs animations one-after-another. Both are also | ||
| 108 | + awaitable. | ||
| 94 | 109 | ||
| 95 | 110 | ## Easing | |
| 96 | 111 | ||
@@ -101,7 +116,18 @@ Use `Animated.sequence` for one-after-another execution and | |||
| 101 | 116 | ||
| 102 | 117 | `start()` returns the handle you started with, and the handle exposes | |
| 103 | 118 | `.stop()`. A common pattern is to keep the handle in a `use_ref` so | |
| 104 | - you can cancel a long-running animation when the user interrupts. | ||
| 119 | + you can cancel a long-running animation when the user interrupts. If | ||
| 120 | + you're awaiting the animation instead, cancelling the awaiting task | ||
| 121 | + stops the animation: | ||
| 122 | + | ||
| 123 | + ```python | ||
| 124 | + async def _enter(): | ||
| 125 | + await pn.Animated.timing(opacity, to=1.0, duration=2000) | ||
| 126 | + | ||
| 127 | + task = pn.run_async(_enter()) | ||
| 128 | + # Sometime later: | ||
| 129 | + task.cancel() # animation snaps to wherever it was; opacity stops here. | ||
| 130 | + ``` | ||
| 105 | 131 | ||
| 106 | 132 | ## When NOT to use `Animated` | |
| 107 | 133 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments