Flutter Evaluation
Status
Evaluated and rejected. This document preserves the rationale.
Context
The developer of the existing FlutterFlow app suggested staying on Flutter for the new stack. Flutter’s “one codebase, ship everywhere” value proposition is compelling for conventional teams. This evaluation explains why it doesn’t fit our specific situation.
Assumptions
The Flutter option assumes Dart/Flutter for all client apps and any suitable backend language for the server (Rust, Go, Node.js, etc.). The key architectural difference is that client business logic lives in Dart while server business logic lives in a separate language — two implementations of shared concerns like validation and scoring.
Comparison
| Concern | Flutter + Separate Server | Shared Rust Core + Native |
|---|---|---|
| UI codebase | One (Dart/Flutter) | Three (Dioxus, SwiftUI, Jetpack Compose) |
| Business logic | Dart on clients, separate language on server — two implementations of validation, scoring, data models | Rust everywhere — written once, runs on all platforms |
| Logic drift risk | High — client and server implementations can diverge, bugs hide in the gap | None — same compiled code on every platform |
| Mobile UX | Good, but not native — custom rendering engine, platform conventions approximated | Best possible — full native UI, platform conventions followed exactly |
| Platform APIs | Plugin-dependent (camera, BLE, background sync) — quality varies, maintainer risk | Direct access — no plugins, no wrappers |
| Web bundle size | Heavy — Flutter web is 1.5-3+ MB minimum, poor initial load | ~50-70 KB WASM (Dioxus) |
| Web quality | Flutter web’s weakest target — accessibility issues, SEO problems, non-standard scrolling | Native web with Dioxus — standard DOM, standard accessibility |
| Offline | Possible but business logic is in Dart, not shared with server — validation and scoring must be reimplemented | Core crate runs locally — full validation and scoring offline, identical to server logic |
| White-label server | Server is a separate codebase with its own logic — no shared core with clients | Rust server imports the same core crate — one language, one logic source |
| Deplatforming resilience | Flutter web PWA is the fallback, but heavy and limited | Dioxus PWA is lightweight, scores offline, flushes on reconnect |
| Framework risk | Single dependency on Google — Flutter’s long-term commitment is a recurring concern in the community | Framework-independent — if SwiftUI or Jetpack Compose change, business logic is untouched |
| Shot timer (camera/BLE) | Plugins — camera plugin is decent, BLE plugins have known issues | Native APIs directly — no plugin layer |
| Hot reload | Excellent (Flutter’s strongest feature) | Subsecond (Dioxus), instant preview (SwiftUI/Compose) |
| Team fit | One language for UI, but business logic split between client and server | AI-assisted parallel implementation across Rust/Swift/Kotlin from a single feature description |
Why Flutter Doesn’t Fit
1. Business Logic Must Exist in Two Places
Regardless of the server language, Flutter means client business logic is in Dart and server business logic is in something else. Validation rules, scoring calculations, data model constraints — all must be implemented and maintained in two languages. They will drift. The shared Rust core eliminates this entirely: one implementation compiles to server, WASM, iOS, and Android.
2. AI-Assisted Development Replaces “Write Once”
Flutter’s core value is avoiding the cost of writing UI three times. In our workflow (one developer + AI), features are described once and implemented across platforms in parallel. The cost of native UI on three platforms drops dramatically — best UX on every platform without the usual team size requirement.
3. Web Is a First-Class Target
The dashboard and PWA are critical surfaces — admin workflow and deplatforming fallback respectively. Flutter web’s large bundle size, accessibility gaps, and non-standard DOM rendering make it a poor fit for these. Dioxus compiles to ~50-70 KB WASM with standard DOM output.
4. Platform API Access Without Plugins
On-range scoring needs camera (shot timer OCR), BLE (timer pairing), and background sync (queue flush). Native apps access these directly. Flutter routes through plugins with varying quality and maintenance.
What Flutter Does Better
- Simpler build tooling — no UniFFI, no cross-compilation, no protobuf code generation
- One UI language — less context switching when reading UI code
- Larger hiring pool — Flutter developers are easier to find than Rust developers (not relevant for our current team)
Decision
Flutter’s advantages are real for conventional teams but don’t outweigh the architectural benefits of shared Rust logic, native platform access, and lightweight web targets in our specific situation.