PythonNative combines direct native bindings with a declarative reconciler, giving you React-like ergonomics while calling native platform APIs synchronously from Python.
- Declarative element tree: Your
Page.render()method returns a tree ofElementdescriptors (similar to React elements / virtual DOM nodes). - Reconciler: On first render, the reconciler walks the tree and creates real native views via the platform backend. On subsequent renders (triggered by
set_state), it diffs the new tree against the old one and applies the minimal set of native mutations. - Direct bindings: Under the hood, native views are created and updated through direct platform calls:
- iOS: rubicon-objc exposes Objective-C/Swift classes (
UILabel,UIButton,UIStackView, etc.). - Android: Chaquopy exposes Java classes (
android.widget.TextView,android.widget.Button, etc.) via the JNI bridge.
- iOS: rubicon-objc exposes Objective-C/Swift classes (
- Thin native bootstrap: The host app remains native (Android
Activityor iOSUIViewController). It passes a live instance/pointer into Python, and Python drives the UI through the reconciler.
Page.render() → Element tree → Reconciler → Native views
↑
Page.set_state() → re-render → diff → patch native views
The reconciler uses positional diffing (comparing children by index). When a child at a given position has the same element type, its props are updated in-place on the native view. When the type changes, the old native view is destroyed and a new one is created.
- Versus React Native: RN uses JSX + a JavaScript bridge + Yoga layout. PythonNative uses Python + direct native calls + platform layout managers. No JS bridge, no serialisation overhead.
- Versus NativeScript: Similar philosophy (direct, synchronous native access), but PythonNative adds a declarative reconciler layer that NativeScript does not have by default.
- Versus the old imperative API: The previous PythonNative API required manual
add_view()calls and explicit setter methods. The new declarative model handles view lifecycle automatically.
- The iOS template (Swift + PythonKit) boots Python and instantiates your
MainPagewith the currentUIViewControllerpointer. Page.on_create()callsrender(), the reconciler creates UIKit views, and attaches them to the controller's view.- State changes trigger
render()again; the reconciler patches UIKit views in-place.
- The Android template (Kotlin + Chaquopy) initializes Python in
MainActivityand passes theActivityto Python. PageFragmentcallson_create()on the PythonPage, which renders and attaches views to the fragment container.- State changes trigger re-render; the reconciler patches Android views in-place.
- See the Navigation guide for full details.
- iOS: one host
UIViewControllerclass, many instances pushed on aUINavigationController. - Android: single host
Activitywith aNavHostFragmentand a stack of genericPageFragments driven by a navigation graph.
- iOS: one host
- Guides / Android: guides/android.md
- Guides / iOS: guides/ios.md
- Concepts / Components: concepts/components.md