Skip to content

Commit 8103710

Browse files
committed
feat!: replace class-based Page with function components, style prop, and use_navigation hook
1 parent 7a5923c commit 8103710

File tree

33 files changed

+958
-1166
lines changed

33 files changed

+958
-1166
lines changed

README.md

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,18 @@
2626

2727
## Overview
2828

29-
PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS. Describe your UI as a tree of elements, manage state with `set_state()`, and let PythonNative handle creating and updating native views.
29+
PythonNative is a cross-platform toolkit for building native Android and iOS apps in Python. It provides a **declarative, React-like component model** with hooks and automatic reconciliation, powered by Chaquopy on Android and rubicon-objc on iOS. Write function components with `use_state`, `use_effect`, and friends, just like React, and let PythonNative handle creating and updating native views.
3030

3131
## Features
3232

3333
- **Declarative UI:** Describe *what* your UI should look like with element functions (`Text`, `Button`, `Column`, `Row`, etc.). PythonNative creates and updates native views automatically.
34-
- **Reactive state:** Call `self.set_state(key=value)` and the framework re-renders only what changed — no manual view mutation.
34+
- **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect`, and navigation with `use_navigation`, all through one consistent pattern.
35+
- **`style` prop:** Pass all visual and layout properties through a single `style` dict, composable via `StyleSheet`.
3536
- **Virtual view tree + reconciler:** Element trees are diffed and patched with minimal native mutations, similar to React's reconciliation.
3637
- **Direct native bindings:** Python calls platform APIs directly through Chaquopy and rubicon-objc, with no JavaScript bridge.
3738
- **CLI scaffolding:** `pn init` creates a ready-to-run project; `pn run android` and `pn run ios` build and launch your app.
38-
- **Navigation:** Push and pop screens with argument passing for multi-page apps.
39-
- **Bundled templates:** Android Gradle and iOS Xcode templates are included scaffolding requires no network access.
39+
- **Navigation:** Push and pop screens with argument passing via the `use_navigation()` hook.
40+
- **Bundled templates:** Android Gradle and iOS Xcode templates are included, so scaffolding requires no network access.
4041

4142
## Quick Start
4243

@@ -52,21 +53,17 @@ pip install pythonnative
5253
import pythonnative as pn
5354

5455

55-
class MainPage(pn.Page):
56-
def __init__(self, native_instance):
57-
super().__init__(native_instance)
58-
self.state = {"count": 0}
59-
60-
def render(self):
61-
return pn.Column(
62-
pn.Text(f"Count: {self.state['count']}", font_size=24),
63-
pn.Button(
64-
"Tap me",
65-
on_click=lambda: self.set_state(count=self.state["count"] + 1),
66-
),
67-
spacing=12,
68-
padding=16,
69-
)
56+
@pn.component
57+
def MainPage():
58+
count, set_count = pn.use_state(0)
59+
return pn.Column(
60+
pn.Text(f"Count: {count}", style={"font_size": 24}),
61+
pn.Button(
62+
"Tap me",
63+
on_click=lambda: set_count(count + 1),
64+
),
65+
style={"spacing": 12, "padding": 16},
66+
)
7067
```
7168

7269
## Documentation

docs/api/component-properties.md

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Component Property Reference
22

3-
All style and behaviour properties are passed as keyword arguments to element functions.
3+
All visual and layout properties are passed via the `style` dict (or list of dicts) to element functions. Behavioural properties (callbacks, data, content) remain as keyword arguments.
44

5-
## Common layout properties
5+
## Common layout properties (inside `style`)
66

7-
All components accept these layout properties:
7+
All components accept these layout properties in their `style` dict:
88

99
- `width` — fixed width in dp (Android) / pt (iOS)
1010
- `height` — fixed height
@@ -13,108 +13,104 @@ All components accept these layout properties:
1313
- `min_width`, `max_width` — width constraints
1414
- `min_height`, `max_height` — height constraints
1515
- `align_self` — override parent alignment (`"fill"`, `"center"`, etc.)
16-
- `key` — stable identity for reconciliation
16+
- `key` — stable identity for reconciliation (passed as a kwarg, not inside `style`)
1717

1818
## Text
1919

2020
```python
21-
pn.Text(text, font_size=None, color=None, bold=False, text_align=None,
22-
background_color=None, max_lines=None)
21+
pn.Text(text, style={"font_size": 18, "color": "#333", "bold": True, "text_align": "center"})
2322
```
2423

25-
- `text` — display string
26-
- `font_size` — size in sp (Android) / pt (iOS)
27-
- `color` — text colour (`#RRGGBB` or `#AARRGGBB`)
28-
- `bold` — bold weight
29-
- `text_align``"left"`, `"center"`, or `"right"`
30-
- `background_color` — view background
31-
- `max_lines` — limit visible lines
24+
- `text` — display string (positional)
25+
- Style properties: `font_size`, `color`, `bold`, `text_align`, `background_color`, `max_lines`
3226

3327
## Button
3428

3529
```python
36-
pn.Button(title, on_click=None, color=None, background_color=None,
37-
font_size=None, enabled=True)
30+
pn.Button(title, on_click=handler, style={"color": "#FFF", "background_color": "#007AFF", "font_size": 16})
3831
```
3932

40-
- `title` — button label
33+
- `title` — button label (positional)
4134
- `on_click` — callback `() -> None`
42-
- `color` — title text colour
43-
- `background_color` — button background
44-
- `enabled` — interactive state
35+
- `enabled` — interactive state (kwarg, default `True`)
36+
- Style properties: `color`, `background_color`, `font_size`
4537

4638
## Column / Row
4739

4840
```python
49-
pn.Column(*children, spacing=0, padding=None, alignment=None, background_color=None)
50-
pn.Row(*children, spacing=0, padding=None, alignment=None, background_color=None)
41+
pn.Column(*children, style={"spacing": 12, "padding": 16, "align_items": "center"})
42+
pn.Row(*children, style={"spacing": 8, "justify_content": "space_between"})
5143
```
5244

53-
- `spacing` — gap between children (dp / pt)
54-
- `padding` — inner padding (int for all sides, or dict with `horizontal`, `vertical`, `left`, `top`, `right`, `bottom`)
55-
- `alignment` — cross-axis: `"fill"`, `"center"`, `"leading"`, `"trailing"`, `"start"`, `"end"`, `"top"`, `"bottom"`
56-
- `background_color` — container background
45+
- `*children` — child elements (positional)
46+
- Style properties:
47+
- `spacing` — gap between children (dp / pt)
48+
- `padding` — inner padding (int for all sides, or dict with `horizontal`, `vertical`, `left`, `top`, `right`, `bottom`)
49+
- `alignment` — cross-axis alignment shorthand
50+
- `align_items` — cross-axis alignment: `"fill"`, `"center"`, `"leading"` / `"start"`, `"trailing"` / `"end"`, `"stretch"`
51+
- `justify_content` — main-axis distribution: `"start"`, `"center"`, `"end"`, `"space_between"`, `"space_around"`
52+
- `background_color` — container background
5753

5854
## View
5955

6056
```python
61-
pn.View(*children, background_color=None, padding=None)
57+
pn.View(*children, style={"background_color": "#F5F5F5", "padding": 16})
6258
```
6359

64-
Generic container (UIView / FrameLayout). Supports all layout properties.
60+
Generic container (UIView / FrameLayout). Supports all layout properties in `style`.
6561

6662
## SafeAreaView
6763

6864
```python
69-
pn.SafeAreaView(*children, background_color=None, padding=None)
65+
pn.SafeAreaView(*children, style={"background_color": "#FFF", "padding": 8})
7066
```
7167

7268
Container that respects safe area insets (notch, status bar).
7369

7470
## ScrollView
7571

7672
```python
77-
pn.ScrollView(child, background_color=None)
73+
pn.ScrollView(child, style={"background_color": "#FFF"})
7874
```
7975

8076
## TextInput
8177

8278
```python
83-
pn.TextInput(value="", placeholder="", on_change=None, secure=False,
84-
font_size=None, color=None, background_color=None)
79+
pn.TextInput(value="", placeholder="Enter text", on_change=handler, secure=False,
80+
style={"font_size": 16, "color": "#000", "background_color": "#FFF"})
8581
```
8682

8783
- `on_change` — callback `(str) -> None` receiving new text
8884

8985
## Image
9086

9187
```python
92-
pn.Image(source="", width=None, height=None, scale_type=None, background_color=None)
88+
pn.Image(source="https://example.com/photo.jpg", style={"width": 200, "height": 150, "scale_type": "cover"})
9389
```
9490

9591
- `source` — image URL (`http://...` / `https://...`) or local resource name
96-
- `scale_type``"cover"`, `"contain"`, `"stretch"`, `"center"`
92+
- Style properties: `width`, `height`, `scale_type` (`"cover"`, `"contain"`, `"stretch"`, `"center"`), `background_color`
9793

9894
## Switch
9995

10096
```python
101-
pn.Switch(value=False, on_change=None)
97+
pn.Switch(value=False, on_change=handler)
10298
```
10399

104100
- `on_change` — callback `(bool) -> None`
105101

106102
## Slider
107103

108104
```python
109-
pn.Slider(value=0.0, min_value=0.0, max_value=1.0, on_change=None)
105+
pn.Slider(value=0.5, min_value=0.0, max_value=1.0, on_change=handler)
110106
```
111107

112108
- `on_change` — callback `(float) -> None`
113109

114110
## ProgressBar
115111

116112
```python
117-
pn.ProgressBar(value=0.0, background_color=None)
113+
pn.ProgressBar(value=0.5, style={"background_color": "#EEE"})
118114
```
119115

120116
- `value` — 0.0 to 1.0
@@ -128,13 +124,13 @@ pn.ActivityIndicator(animating=True)
128124
## WebView
129125

130126
```python
131-
pn.WebView(url="")
127+
pn.WebView(url="https://example.com")
132128
```
133129

134130
## Spacer
135131

136132
```python
137-
pn.Spacer(size=None, flex=None)
133+
pn.Spacer(size=16, flex=1)
138134
```
139135

140136
- `size` — fixed dimension in dp / pt
@@ -143,24 +139,25 @@ pn.Spacer(size=None, flex=None)
143139
## Pressable
144140

145141
```python
146-
pn.Pressable(child, on_press=None, on_long_press=None)
142+
pn.Pressable(child, on_press=handler, on_long_press=handler)
147143
```
148144

149145
Wraps any child element with tap/long-press handling.
150146

151147
## Modal
152148

153149
```python
154-
pn.Modal(*children, visible=False, on_dismiss=None, title=None, background_color=None)
150+
pn.Modal(*children, visible=show_modal, on_dismiss=handler, title="Confirm",
151+
style={"background_color": "#FFF"})
155152
```
156153

157154
Overlay dialog shown when `visible=True`.
158155

159156
## FlatList
160157

161158
```python
162-
pn.FlatList(data=None, render_item=None, key_extractor=None,
163-
separator_height=0, background_color=None)
159+
pn.FlatList(data=items, render_item=render_fn, key_extractor=key_fn,
160+
separator_height=1, style={"background_color": "#FFF"})
164161
```
165162

166163
- `data` — list of items

docs/api/pythonnative.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22

33
## Public API
44

5-
### Page
5+
### create_page
66

7-
`pythonnative.Page`base class for screens. Subclass it, implement `render()`, and use `set_state()` to trigger re-renders.
7+
`pythonnative.create_page(...)`called internally by native templates to bootstrap the root component. You don't call this directly.
88

99
### Element functions
1010

1111
- `pythonnative.Text`, `Button`, `Column`, `Row`, `ScrollView`, `TextInput`, `Image`, `Switch`, `ProgressBar`, `ActivityIndicator`, `WebView`, `Spacer`
1212
- `pythonnative.View`, `SafeAreaView`, `Modal`, `Slider`, `Pressable`, `FlatList`
1313

14-
Each returns an `Element` descriptor. See the Component Property Reference for full signatures.
14+
Each returns an `Element` descriptor. Visual and layout properties are passed via `style={...}`. See the Component Property Reference for full details.
1515

1616
### Element
1717

@@ -24,6 +24,7 @@ Function component primitives:
2424
- `pythonnative.component` — decorator to create a function component
2525
- `pythonnative.use_state(initial)` — local component state
2626
- `pythonnative.use_effect(effect, deps)` — side effects
27+
- `pythonnative.use_navigation()` — navigation handle (push/pop/get_args)
2728
- `pythonnative.use_memo(factory, deps)` — memoised values
2829
- `pythonnative.use_callback(fn, deps)` — stable function references
2930
- `pythonnative.use_ref(initial)` — mutable ref object
@@ -47,12 +48,12 @@ Function component primitives:
4748

4849
- `pythonnative.utils.IS_ANDROID` — platform flag with robust detection for Chaquopy/Android.
4950
- `pythonnative.utils.get_android_context()` — returns the current Android `Activity`/`Context` when running on Android.
50-
- `pythonnative.utils.set_android_context(ctx)` — set by `Page` on Android; you generally don't call this directly.
51+
- `pythonnative.utils.set_android_context(ctx)` — set internally during page bootstrapping; you generally don't call this directly.
5152
- `pythonnative.utils.get_android_fragment_container()` — returns the current Fragment container `ViewGroup` used for page rendering.
5253

5354
## Reconciler
5455

55-
`pythonnative.reconciler.Reconciler` — diffs element trees and applies minimal native mutations. Supports key-based child reconciliation, function components, and context providers. Used internally by `Page`.
56+
`pythonnative.reconciler.Reconciler` — diffs element trees and applies minimal native mutations. Supports key-based child reconciliation, function components, and context providers. Used internally by `create_page`.
5657

5758
## Hot reload
5859

0 commit comments

Comments
 (0)