Skip to content
PerfectWin7777Public

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

Β 

History

58 Commits

Folders and files

Repository files navigation

Afik

Python Flutter Status

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).


Why Afik?

  • 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.

Architecture

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
Loading

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.


What works today

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.


πŸš€ Quickstart

1. Installation

git clone https://github.com/PerfectWin7777/afik.git
cd afik/py_framework
pip install -e .

2. Create Your First App (main.py)

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())

3. Run

# 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

Requirements

  • Python 3.10+
  • Flutter SDK (3.32 or newer) in PATH, and a device, emulator or desktop target (afik devices)
  • Rust toolchain and protoc to build the bridge once: cargo build --manifest-path rust_bridge/Cargo.toml
  • Android: Android SDK / NDK as required by Flutter

Showcase examples

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.py

State lifecycle

A 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 Navigator keeps 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.

Native packages

Afik does not try to expose every pub.dev package automatically, and it does not put every package in every app. The model is:

  1. Each supported package has an entry in dart_runtime/plugin_catalog/: a plugin.yaml (package, version, native settings) and a hand-written Dart shim that calls the real package API.
  2. A Python module afik.plugins.<package> mirrors the package's classes and methods.
  3. A project lists the plugins it uses in afik.yaml (plugins:); afik add <name> edits that list and wires the shim, the pubspec.yaml dependency 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_auth

Each 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.


CLI reference

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).


Tests and checks

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 consistency

The 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).


Roadmap

  • 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 add with 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.


License

MIT (the LICENSE file is still to be added).

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages