|
Aria 3.2.6
C++23 MVVM framework — reactive, coroutine-first, ABI-layered
|
Want to learn Aria systematically? The companion repository AriaTutorial is a 20-chapter, bilingual (Chinese/English) progressive tutorial, each chapter shipping a minimal demo you can compile and run.
Start with AriaTools to see Aria in a real application. It is Aria's single flagship cross-platform example, driving Qt, iOS, Android, and Web from one C++ ViewModel. This repository now stays focused on the framework, acceptance tests, and minimal documentation snippets.
Aria splits a screen in two. The ViewModel is plain C++ and knows nothing about any UI library; the View is native widgets. BindingEngine joins them, and it only ever talks to the IViewAdapter interface — so porting means swapping the adapter, nothing else.
Upper half — the ViewModel (plain C++, unit-testable, shared by every platform)
There is no UI in that code and no UI header included — it runs under a console test.
Lower half — wiring up the View (a dozen lines per platform; the UI itself stays native)
First, the thing most likely to be misread: you do not write the UI in C++. Buttons, layout and animation are still authored the usual way — Qt Designer, Storyboard, Compose, HTML. The code below only hands widgets that already exist over to the engine, and the three steps never change: ① construct the platform adapter ② build a BindingEngine from it ③ bind a widget to a Property.
.mm; the UI is still Storyboard / SwiftUI)UIKitView retains the UIView* under ARC and, on destruction, tells BindingEngine to drop its subscriptions while the native view is still valid — so no callback ever reaches a released widget.
.mm + NSView)Kotlin-side listeners forward native events back in (adapter->notify_text_changed(...) / notify_click(...)), so listener ownership stays on Android while the C++ side stays strongly typed. Compose has no addressable view object; use the side-channel shape from the adapter guide.
That is the point: five wiring snippets that look nearly identical, and the BillViewModel above is byte-for-byte unchanged across all of them. Porting costs you those dozen lines, not your business logic.
After wiring — mutate data, the UI follows
Once bound, the rest is platform-independent. The code below behaves identically on all five platforms, and not one line of refresh code is written by hand:
Change either bill or people and per_person recomputes and pushes to the label — because its dependencies were recorded automatically on the Computed's first evaluation. You never wrote anything resembling "when people changes, update the label".
One detail worth knowing when you change several values in a row:
And a convenient default: if the final result equals the current value, nothing is notified at all. Following on from above, a batch setting bill=600, people=4 (still 150) leaves the label untouched.
The whole architecture in one picture — upper half is the pure C++ ViewModel, the middle is BindingEngine (which only knows the IViewAdapter interface), and the bottom row is the five native adapters:

Continue with the binding guide, the per-platform adapter guides, the cookbook, or the full four-platform AriaTools application.
Aria brings reactive state, asynchronous coroutines, and cross-platform binding into one modern C++ architecture, independent of UI toolkits. Implement complex business logic once and give each platform its native experience.
Elegance comes from clear responsibilities: a ViewModel is an ordinary C++ class, with no required framework base class, macros, or code generator. UI layers connect through IViewAdapter. Qt6 / AppKit / UIKit / JNI / HTTP share one binding protocol, so changing UI toolkits leaves the ViewModel intact.
Built to the engineering standards of world-class industrial software. Lifecycle, threading, error handling, and collection events have traceable engineering contracts, checked through automated tests, fuzz testing, and reproducible performance benchmarks. Architectural elegance carries through to state updates, asynchronous cancellation, and cross-platform delivery.
| Architectural strength | Engineering design |
|---|---|
| Modern C++ foundation | C++23 baseline (GCC 16+ / Clang 23+ / AppleClang from Xcode 27+ / MSVC v145, i.e. Visual Studio 2026) with full coroutines and concepts. Integrating projects must use C++23 or later. |
| Native UI freedom | Your UI toolkit owns widgets, layout, and animation. Aria unifies one-way and two-way state binding, sharing the business core while preserving native experiences. |
| Layered compatibility | aria-abi / aria-runtime / aria-binding maintain ABI stability within a major version, with matching compiler, standard library, and build options. Rebuild templates such as Property<T> and their containing types after updates. |
| Open adapter protocol | Qt6 / AppKit / UIKit / JNI / HTTP ship out of the box. Integrate other UI toolkits by implementing IViewAdapter (see the adapter guides). |
| Applications and learning resources | AriaTools, AriaAgent, and AriaRead demonstrate cross-platform workbenches, agent GUIs, and reading engines. Guides, the Cookbook, and engineering contracts cover the path from first integration to custom extensions. |
Designed for shared C++ business logic, native multi-platform UIs, and long-term maintenance. Aria owns business state and binding; the chosen UI toolkit owns rendering and UI reuse. An explicit adapter protocol connects the two.
| Module | Type | Depends on | Notes |
|---|---|---|---|
| aria-abi | SHARED by default | Threads | Compiled foundation: signals, shared reactive graph, diagnostics storage, scheduler base, and version metadata. Static builds are supported. |
| aria-core | header-only | abi | All the templates: Property, Computed, Command, ObservableList, Validator. Source-compatible only (not ABI-stable). |
| aria-async | header-only | core | Coroutine Task<T>, executors. Source-compatible only. |
| aria-runtime | SHARED | core, abi | EventBus / Container / Dispatcher / Logger — singletons live in one dylib. ABI-stable (non-template exports). |
| aria-binding | SHARED | core, runtime | BindingEngine, IViewAdapter. ABI-stable (non-template exports). |
| Adapters | SHARED/STATIC | binding | Qt6 / AppKit / UIKit / JNI / HTTP (each opt-in). WASM is conditional roadmap work. |
Windows is supported on two toolchains: MSYS2 UCRT64 (GCC) and MSVC / Visual Studio 2026. Pick whichever fits your team's existing stack — both build the full framework + tests + adapters from a single tree, no source forks. See "Windows toolchains" below.
build/ is a container for build trees — never configure straight into it. The layout convention is enforced by the build-tree guard at the top of CMakeLists.txt; python scripts/build.py picks an isolated default directory for you.
Test and HTTP builds use the pinned ForgeDeps package for verified dependency environments and external ForgeRecipes build recipes. Initialize a shared workspace with python -m forgedeps init /path/to/deps; offline builds reuse its verified sources and installed artifacts. No source is vendored.
python scripts/build.py --test configures, builds and runs host CTest. Select --platform qt|web|ios|android; --dry-run prints the complete plan without writing files. Default trees isolate platform, toolchain, configuration and architecture. Use --build-dir for an existing cache and --offline for cached dependencies. Android requires --ndk; iOS requires macOS/Xcode. Mobile tests still use the existing device runners. Windows defaults to MSVC; MinGW is an explicit separate cache. Ninja/MSVC requires a developer environment. Existing SDK packaging and deployment scripts remain available.
On macOS, --arch x86_64 / --arch arm64 selects the actual target architecture. Visual Studio accepts --generator-platform x64 (or ARM64). Extra definitions use --cmake-arg=-DNAME[:TYPE]=VALUE and cannot override the selected configuration or toolchain. Compiler, toolchain and architecture cache conflicts are checked before fetching dependencies; existing build directories are preserved.
Common invocations:
Aria supports two parallel Windows toolchains through the same portable entry, each in its own isolated build tree.
| Toolchain | Command | Notes |
|---|---|---|
| MSVC (VS2026; VS2022 discovery retained) | python scripts/build.py --toolchain msvc | Default. Selects the Visual Studio generator matching the installation. |
| MSYS2 UCRT64 (GCC 16+ / Clang 23+) | python scripts/build.py --toolchain mingw | Lightweight (~300 MB). Pre-installed on most CI images. Auto-detected from C:\msys64\ucrt64\bin and a few other common paths. |
You can switch back and forth without cleanup — the trees stay isolated. CI validates both toolchains on pushes and pull requests.
Rationale for shipping both: Aria uses C++ coroutines extensively that libstdc++, libc++, and the MSVC STL all handle cleanly. Pinning a single Windows toolchain artificially excluded a large chunk of users in the .NET / Visual Studio ecosystem — we now validate against MSVC v145 / VS2026 on the same release gate as macOS, Ubuntu, and MSYS2. The Visual Studio 18 2026 generator requires CMake >= 4.2. The automatic build script retains discovery of existing VS2022 installations; current MSVC release validation uses VS2026.
Option A — find_package after install (recommended for production):
Installed Linux shared libraries resolve other Aria libraries from their own directory. Keep these libraries together; after moving the SDK, configure consumers against its new installation path.
Option B — separate source checkout (no install):
Clone Aria separately outside your project, then pass its absolute path when configuring the application: cmake -S . -B build -DARIA_SOURCE_DIR=/path/to/Aria.
AriaTools is the single flagship cross-platform example, driving Qt, iOS, Android, and Web from one C++ ViewModel, with all four shells gated in CI. It is also the reference for both Android integration shapes: the Compose side-channel and the typed JniAdapter. The Aria repository no longer carries application examples. Framework behavior is pinned by tests/acceptance/ and module tests, while these docs keep only focused, minimal snippets.
| Option | Default | Description |
|---|---|---|
| ARIA_BUILD_TESTS | ON | Build unit tests + ctest registration. |
| ARIA_BUILD_BENCHMARK | ON | Build the micro-benchmark suite. |
| ARIA_BUILD_SHARED | ON | Runtime/binding as .dylib/.so/.dll instead of .a. |
| ARIA_BUILD_QT6 | OFF | Build the Qt6 adapter (requires Qt6Widgets). |
| ARIA_BUILD_APPKIT | OFF | (production-grade) Build the macOS AppKit adapter as a first-class STATIC CMake module using Objective-C++; ships aria::adapters::appkit and passes the shared adapter_conformance battery. Requires APPLE. |
| ARIA_BUILD_UIKIT | OFF | (production-grade) Build the iOS UIKit adapter as a first-class STATIC CMake module using Objective-C++; ships aria::adapters::uikit and passes the shared conformance battery. Requires APPLE. |
| ARIA_BUILD_JNI | OFF | Build Android JNI adapter as a first-class CMake module — built as STATIC, ships aria::adapters::jni, implementing the same IViewAdapter contract as Qt/AppKit/UIKit via reflective JNI dispatch (text / bool / int / double / visibility / click). Requires an Android NDK toolchain (NDK r30, validated in CI for the C++23 core). |
| ARIA_ENABLE_ASAN | OFF | AddressSanitizer. |
| ARIA_ENABLE_UBSAN | OFF | UndefinedBehaviorSanitizer. |
| ARIA_ENABLE_TSAN | OFF | ThreadSanitizer. |
The section above needs a UI adapter. To see the reactive core on its own, you need no UI at all:
So sub exists to express the subscription's lifetime as a scope: while the variable lives the subscription lives, and when it goes the subscription is torn down. In a real UI this Subscription is usually a member of the View, so destroying the View detaches the binding and no callback ever reaches a destroyed widget.
| Platform | UI host | Adapter |
|---|---|---|
| Windows | Qt6 | aria-qt6 ✅ ready (MSYS2 UCRT64 + MSVC 2026) |
| macOS | AppKit / Qt6 | aria-qt6 ✅ ready; AppKit ✅ ready |
| Linux | Qt6 | aria-qt6 ✅ ready |
| iOS | UIKit | aria-uikit ✅ ready |
| Android | Compose / View | aria-jni ✅ ready (NDK r30) |
| Web (server-driven) | HTML/JS in browser | aria-http ✅ ready (REST + SSE) |
| Web (in-browser C++) | DOM via WASM | Not implemented; conditional roadmap work |
The HTTP adapter ships a small server (HttpAdapter) that exposes any ViewModel over a JSON REST + Server-Sent-Events protocol, plus a vanilla-JS browser SDK (aria_client.js). The server is built on the coroutine-native Mira networking library (HTTP/1.1 + SSE) and nlohmann::json (encode/decode). Dependencies default to the latest stable release on first resolution and remain locked until explicitly updated; explicit versions take precedence. CMake fetches checksum-verified archives into the ignored build dependency cache; see dependency versions. aria itself owns the wire protocol, view registry, subscription dispatch and SSE fan-out. It is the right shape for desktop apps that want a web UI on the side, headless services, and local debug dashboards. The WASM adapter — which compiles C++ business logic into the browser sandbox — solves a different, more constrained problem and remains conditional on a concrete consumer. See RFC 0001 for the design.
The current release ships the platform-agnostic core, runtime, async, and binding layers — fully unit-tested. Qt6, AppKit, UIKit, JNI, and HTTP are first-class opt-in adapters in the CMake tree (subject to their platform requirements). WASM and SwiftUI remain conditional roadmap work.
The big picture first — three real applications grew out of one framework:

Below is what Aria looks like in real applications — one C++ ViewModel, native shells per platform. AriaTools (17-module cross-platform workbench on Qt / iOS / Android / Web), AriaAgent (provider-agnostic LLM Agent GUI), and AriaRead (cross-platform book-source engine, HTTP/SSE web shell) all run Aria 1.x in production shape. Every screenshot comes from a stable release: one build, one shared C++ business core across platforms.
Aria's flagship example: one ViewModel, four platforms. The cart / theme switching / Framework Lab / Echo modules in the side navigation are all driven by ObservableList, Computed, and reactive::batch.
| Platform | Screenshot | Adapter |
|---|---|---|
| macOS (Qt6) | | aria-qt6 |
| iOS / UIKit | | aria-uikit |
| Android (Compose side-channel) | | aria-jni |
| Web (HTTP/REST/SSE) | | aria-http |
A provider-agnostic Agent GUI built on Aria + Qt6: true token-level streaming SSE, tool-call chain visualization, permission approval (fail-closed), Markdown rendering.
| View | Screenshot |
|---|---|
| Main chat | |
| Settings (General / Model / Plugins / Agent Presets) | |
A book-source manager powered by the Aria HTTP adapter: source list on the left, book cards on the right — search, subscribe, and debug in one place. The same C++ core drives two web shapes: a REST+SSE thin client and an SSR variant.
| View | Screenshot |
|---|---|
| Source manager (Web, REST + SSE) | |
| Source manager (Web, SSR) | |
These screenshots show the macOS example applications. Other platforms reuse the ViewModel; native appearance depends on the platform, Qt style, and host application. Consult CI for framework build and test results on Windows/Linux.
Tests exercise reactive state, collection events, async cancellation, binding lifetimes, ABI, and adapter contracts. Enabled suites depend on platform and build options. See CI results, lifecycle contracts, and the error model.
Measured on 2026-09-13: Apple M3 Pro / Apple Clang 21 / C++20 Release (-O3 -DNDEBUG). Each entry is the median of five paired runs' mean operation times, comparing original revision aa039f6 with 2.0 snapshot 2f22e17.
| Operation | Original | Current |
|---|---|---|
| Property set, no observers | 20.5 ns | 13.2 ns |
| Property set, one observer | 98.9 ns | 35.0 ns |
| Computed chain ×5 | 570.8 ns | 229.0 ns |
| Ten sets in one batch | 250.6 ns | 102.5 ns |
| FilteredList tail append, 10k initial rows | 11.18 μs | 0.23 μs |
| SortedList random-key append, 10k initial rows | 8.38 μs | 17.28 μs |
Performance is mixed: owning event data and maintaining correct ordering after batched changes also have costs. See the performance reference for all scenarios, complexity, remaining regressions, and reproduction details.
Every non-trivial behaviour Aria promises is pinned in a numbered contract document. Each contract item carries an ID (e.g. L-13, E-22, LD-7, D-4, S-31) so a failing assertion or PR review comment can point straight at the canonical description.
| Document | Prefix | Scope |
|---|---|---|
| docs/reference/api-style.md | S-N | Naming, namespace, error and async-entry style |
| docs/reference/lifecycle.md | L-N | Threading, subscription, reactive flush, view-destroy, async cancel/dtor invariants |
| docs/reference/error-model.md | E-N | aria::Error / ErrorKind taxonomy and per-subsystem error contracts |
| docs/reference/list-diff-contract.md | LD-N | Insert / Remove / Replace / Move / Reset / ItemChanged semantics |
| docs/reference/diagnostics.md | D-N | aria::TraceEvent + aria::TraceSink protocol |
| docs/reference/performance.md | PERF-N | Complexity bounds and per-operation baselines for every public API |
The P0 hard-bedrock pass (see CHANGELOG → Latest framework-grade hardening) closed every open contract above; the seven framework-level fuzzers in modules/core/fuzz/ stress-verify the lifecycle invariants (default 50k iterations / fuzzer; nightly runs set ARIA_FUZZ_ITERS=1000000).
| Capability | Type | Where |
|---|---|---|
| Reactive state | Property<T> / Computed<T> / Effect | aria/reactive/reactive.hpp |
| Commands | Command<Args...> (reactive can_execute) | aria/command.hpp |
| Collections | ObservableList<T> + derived Filtered/Sorted/Mapped/Distinct/Grouped/Paged | aria/observable_list.hpp, aria/derived/* |
| Selection | Selection<T> / MultiSelection<T> (SE-1..SE-5) | aria/selection.hpp |
| Validation | Validator<T> / FormValidator / ValidationState + async rules | aria/validator.hpp, aria/binding/form.hpp, aria/async/async_validator.hpp |
| Async | Task<T> / AsyncCommand / with_timeout / when_any / when_all / CancellationToken | aria/async/* |
| Data fetching | AsyncResource<T> (SWR + dedupe) / Loadable<T> (5-state) | aria/async/async_resource.hpp, aria/loadable.hpp |
| Navigation | Navigator (push/pop/push_for_result<R>, route patterns) | aria/binding/navigation.hpp |
| Binding | BindingEngine / IViewAdapter / IView / Converter / bind_view_lifetime | aria/binding/* |
| Diagnostics | TraceEvent / TraceSink / GraphInspector (atomic check when disabled) | aria/diagnostics.hpp |
Learn it: the documentation index links the guides, the Cookbook (task-oriented recipes), and the contract references. The public API reference is generated from main. See the 2.0 migration guide when upgrading from 1.2.x. Build the reference locally with cmake -B build/flavors/docs -DARIA_BUILD_DOCS=ON && cmake --build build/flavors/docs --target aria_docs.
Aria is open source (MIT License), hosted on GitHub. The roadmap defines development priorities, planned capabilities, and explicitly deferred work. See the changelog for delivered capabilities and release history.
Contributions are welcome! Please open an issue first to discuss design changes.
MIT © 2026 aria contributors