Afik lets Python developers describe a user interface in Python and have it rendered by Flutter's native engine. The Python application runs on your machine, a small Rust bridge relays messages, and a Flutter shell draws the widgets and calls native packages.
Status: alpha, development mode only. The full loop (Python β Rust bridge β Flutter shell β callbacks back to Python, with hot reload) works while you develop on a connected device or desktop. Shipping a self-contained app that embeds Python is not implemented yet (see What works today).
- Pythonic API:
Component/StatefulComponent, Signals, or Qt-style fluent widgets (add_widget(),.clicked.connect()). - Flutter's real renderer: Material 3 widgets drawn by Flutter, not re-implemented.
- Compact binary protocol: Protobuf frames between Python, Rust and Dart; incremental patches instead of resending the whole tree when only properties change.
- Packages on demand: native Flutter packages are mapped to Python classes by hand-written shims (see Native packages); you only add the ones you use.
graph LR
subgraph Python ["Python application (your machine)"]
App["App / Component"] --> State["Signals / State"]
State --> Render["Tree resolve + diff"]
end
subgraph Bridge ["Rust bridge (subprocess)"]
Render -->|"Protobuf / JSON patch frames over stdio"| Rust["Relay"]
end
subgraph Flutter ["Flutter shell"]
Rust -->|"local TCP socket"| Dart["Widget builder"]
Dart --> Plugins["Per-package shims -> real Flutter packages"]
end
Native package calls travel the same way: Python sends {plugin, method, args}, a hand-written Dart shim calls the real package, and the answer comes back to the waiting Python call.
| Area | State |
|---|---|
afik run on Android / desktop devices with hot reload (r) and hot restart (R) |
Works |
| Material widgets, reactive state, forms, navigation stack, SnackBar / dialogs | Works (coverage is partial, see AFIK_VISION.md) |
Single UI thread: callbacks and builds never overlap, many updates cost one frame (pf.run_on_ui for your own threads) |
Works |
| Incremental tree patches, reconnection resync, session token on the local socket | Works |
| Native packages (22 catalog plugins, see below) | Real shims calling the real Flutter packages; installed per project with afik add. They compile (flutter analyze) individually and all together; they still need testing on devices |
hive boxes and sqflite |
Implemented in Python (JSON files, sqlite3), no Flutter package involved |
| Standalone app that embeds Python (APK / IPA / desktop bundle) | Not implemented: afik build compiles the Flutter shell, but no Python interpreter is embedded yet |
| Web and iOS | Not supported yet (the shell imports dart:io / dart:ffi; iOS needs the embedded runtime) |
Installation with pip install outside a repository clone |
Not available yet: the CLI needs the dart_runtime/ and rust_bridge/ folders of this repository |
AUDIT_BUGS.md lists every known bug and missing piece with the planned fix.
git clone https://github.com/PerfectWin7777/afik.git
cd afik/py_framework
pip install -e .import afik as pf
class CounterApp(pf.Component):
def __init__(self):
super().__init__()
self.count = 0
def increment(self):
self.count += 1
self.update() # Triggers instant reactive UI update
def build(self):
return pf.Scaffold(
app_bar=pf.AppBar(
title=pf.Text("Counter", color=pf.Colors.WHITE),
background_color="#1877F2",
),
body=pf.Center(
pf.Card(
pf.Column([
pf.Text("Compteur", font_size=14, color=pf.Colors.GREY),
pf.SizedBox(height=8),
pf.Text(str(self.count), font_size=48, font_weight="bold"),
pf.SizedBox(height=16),
pf.ElevatedButton("+1", on_click=self.increment),
], cross_axis_alignment="center"),
padding=24,
border_radius=16,
)
),
floating_action_button=pf.FloatingActionButton(
icon=pf.Icons.ADD,
on_click=self.increment,
),
)
if __name__ == "__main__":
# Run interactively on a connected device or desktop (development mode):
pf.run(CounterApp())# Run interactively with hot reload (needs the Rust bridge built once: see Requirements)
python main.py # or: afik run main.py
# Flutter shell build (does NOT embed Python yet)
afik build apk --release
afik build windows --release- Python 3.10+
- Flutter SDK (3.32 or newer) in
PATH, and a device, emulator or desktop target (afik devices) - Rust toolchain and
protocto build the bridge once:cargo build --manifest-path rust_bridge/Cargo.toml - Android: Android SDK / NDK as required by Flutter
See examples/:
examples/counter: Material 3 counter with a FloatingActionButton.examples/facebook_feed: social feed with dataclasses, like toggling, bottom navigation.examples/pyshop: e-commerce showcase (Qt-style layouts, live search, reactive cart, SnackBars,url_launcher).
cd examples/counter
python main.pyA StatefulComponent keeps its State between frames. The state is matched by where the widget sits in the tree (widget types and positions from the root, like Flutter), never by source line, so editing the code does not lose it. Rules:
State.dispose()is called once when the widget leaves the tree (a removed branch, a popped page) and on a hot restart: stop timers, threads and subscriptions there.- A page covered by another one in the
Navigatorkeeps its states until it is popped. - In a list that can be reordered, filtered or inserted into, give each stateful widget a
key=: its state then follows the key inside its parent instead of the position.
Afik does not try to expose every pub.dev package automatically, and it does not put every package in every app. The model is:
- Each supported package has an entry in
dart_runtime/plugin_catalog/: aplugin.yaml(package, version, native settings) and a hand-written Dart shim that calls the real package API. - A Python module
afik.plugins.<package>mirrors the package's classes and methods. - A project lists the plugins it uses in
afik.yaml(plugins:);afik add <name>edits that list and wires the shim, thepubspec.yamldependency and the native Android settings. Only those packages are compiled into the app.
afik plugin list # catalog and what this project uses
afik add local_auth # install a catalog plugin
afik add some_other_package # no shim yet: adds the Flutter dependency, then
afik plugin new some_other_package # scaffolds the shim, Python module and test to fill from the package docs
afik remove local_authEach project works on its own copy of the Flutter runtime, created in <project>/.afik/runtime/ the first time you
run, build or add (add .afik/ to your .gitignore; afik create does). dart_runtime/ in this repository is only
the template, so installing plugins or declaring permissions never touches it. afik.yaml is the source of truth: plugins:,
dependencies.flutter (packages without a shim, saved with the version Flutter resolved) and permissions:. Removing a plugin or a
permission removes it from the Android manifest and the iOS plist too.
A plugin that is not installed fails with a clear error (Plugin "x" is not installed in this runtime. Install it with: afik add x).
When a Flutter runtime is connected, a plugin error or timeout raises PluginError / PluginTimeoutError; it is never
replaced by simulated data, and a missing or malformed answer never reads as a success.
| Plugin (pub.dev package) | Python module | Notes |
|---|---|---|
shared_preferences, path_provider, device_info_plus, url_launcher, file_picker |
afik.plugins.<name> |
Key-value storage, system folders, device info, links/phone/email, file and folder dialogs |
connectivity_plus, share_plus, image_picker, audioplayers |
afik.plugins.<name> |
Network state, share sheet, gallery/camera picking, audio playback |
flutter_local_notifications |
afik.plugins.flutter_local_notifications |
Show / cancel notifications (needs core library desugaring, applied automatically) |
local_auth, permission_handler, flutter_secure_storage |
afik.plugins.<name> |
Biometrics, runtime permissions, encrypted storage |
camera, video_player, chewie, webview_flutter |
afik.plugins.<name> |
Real widgets CameraPreview, VideoPlayer, Chewie, WebView |
syncfusion_flutter_pdfviewer, pdfx, flutter_pdfview, printing |
afik.plugins.<name> |
PDF viewers (SfPdfViewer, PdfView, PDFView), page rendering, print and share. Syncfusion needs its own licence |
hive, sqflite |
afik.plugins.hive, afik.plugins.sqflite |
Pure Python (JSON boxes, sqlite3), no Flutter package |
Without a connected Flutter runtime (unit tests, scripts) plugin calls use a small local simulation so code can be tested offline. The first call of each simulated plugin logs an [offline] ... is simulated warning, and the security plugins (local_auth, permission_handler, flutter_secure_storage) refuse instead of faking an answer unless AFIK_ALLOW_INSECURE_MOCKS=1 is set (meant for tests). Calls that wait for a person (pickers, permission prompts, authentication, sharing) wait up to 120 s, the others 3 s.
To check a catalog change: python tools/verify_catalog.py (needs the Flutter SDK) installs every plugin into a temporary copy of the
runtime, runs flutter pub get and flutter analyze, then installs them all together to catch version conflicts.
| Command | Arguments / flags | Description |
|---|---|---|
afik run |
[entrypoint] [-d <device>] [-p <port>] [--attach] |
Run the app interactively on a device or desktop |
afik devices |
List devices and emulators detected by Flutter | |
afik build |
[target] [--debug|--release|--profile] [--split-per-abi] |
Build the Flutter shell (default: apk, debug). No embedded Python yet |
afik create |
<name> |
Scaffold a project |
afik init |
Initialise a project in the current directory | |
afik sync |
Sync afik.yaml permissions to the Android manifest and iOS plist |
|
afik add |
<package> |
Install a catalog plugin (or add a Flutter package that has no shim yet) |
afik remove |
<package> |
Remove a plugin / package |
afik plugin |
list | new <package> |
List the plugin catalog, or scaffold the mapping of a new package |
Build targets: apk, appbundle, windows, linux, macos, web, ipa (web and ipa are not supported yet, see above).
pip install -e "py_framework[dev]" # the framework, pytest and ruff
ruff check py_framework # lint (rules in py_framework/pyproject.toml)
cd py_framework && python -m pytest -q # the Python test-suite (unknown widget props raise in the tests)
python tools/check_contract.py # Python <-> contract <-> Dart props consistencyThe Rust relay tests (tests/test_bridge_relay.py) run the real bridge binary: build it first (cd rust_bridge && cargo build) or they are skipped; AFIK_REQUIRE_BRIDGE=1 turns a missing binary into a failure. The Dart runtime has a small unit test (cd dart_runtime && flutter test) and must pass flutter analyze; python tools/verify_catalog.py [--together] installs the plugins of the catalog into a temporary runtime and analyses them (needs the Flutter SDK).
The GitHub Actions workflow (.github/workflows/ci.yml) runs all of this: Python 3.10 to 3.13 on Linux (and 3.12 on Windows and macOS), the oldest supported dependencies, cargo clippy -D warnings and the relay tests, flutter analyze and flutter test, the contract check and the plugin catalog (every plugin, weekly).
- Duplex reactive bridge (Protobuf frames, Rust relay, Flutter shell) in development mode
- Material 3 widget catalog (partial coverage)
- Callback lifecycle (sweep, pinned one-shot callbacks), deterministic state keys
- Hot reload / hot restart, reconnection resync
- Packages on demand (
afik addwith a plugin catalog) - Per-project copy of the Flutter runtime (
<project>/.afik/runtime/) - Embedded Python interpreter for standalone builds
- Pip distribution with a prebuilt bridge
- Hot reload of every project module, file watcher
- Documentation site
The detailed, ordered plan is in AUDIT_BUGS.md.
MIT (the LICENSE file is still to be added).