~/notes
Quick thoughts, TILs, and observations from building mobile & backend systems.
Making local key-value reads asynchronous (Future<T>) creates microtask queue hops and forces widget build methods into FutureBuilder or unnecessary state churn. With flutter_rust_bridge synchronous wire functions, in-memory cache hits in Rust return across the FFI boundary in microseconds with zero Future allocation overhead.
Encrypting values with AES-256-GCM isn’t enough if you don’t bind the key name in the Authenticated Additional Data (AAD). Without AAD binding, an attacker with write access to the raw store could swap the ciphertext of user_role: "admin" into another user’s slot. Binding the key name to the AAD ensures the payload will fail authentication if decrypted under any other key.
Rewriting a full JSON or binary snapshot on every mutation scales with $O(N)$ data size and kills mobile flash storage with write amplification. An append-only Write-Ahead Log (WAL) makes every mutation $O(1)$ disk I/O. Pair it with an in-memory index for $O(1)$ reads, and run log compaction (compact()) lazily in the background.
In embedded storage engines, using a single Mutex over the entire state causes unnecessary read contention. Pairing RwLock<HashMap<K, V>> for concurrent multi-reader access with a focused Mutex<Wal> for serializing disk appends gives you thread-safe mutations without stalling concurrent cache hits.
Dart isolates serialize and copy memory across message ports on separate event loops. When heavy computation or I/O occurs on the native side, spawning an OS thread in Rust and communicating back to Dart via StreamSink avoids isolate serialization overhead and keeps the Flutter UI thread completely unblocked.
Circuit breakers beat blind retries when the downstream is already on fire. Fail fast, shed load, recover clean. Half-open state is the part everyone forgets to implement properly — without it you just DDOS yourself on recovery.
Riverpod’s ref.listen is underrated for side-effects — stop cramming navigation and snackbars into build. Listen fires once per state change, not on every frame. Your widget tree will thank you.
Go’s errors.Join finally made aggregating failures readable. Wrap with %w, join at the boundary, unwrap with errors.Is at the handler. No more string concatenation that swallows stack context.
Server-driven UI pays off the moment product wants to A/B a flow without shipping a new build. The tax is a robust schema contract and a typed renderer on the client. Pay it once, win every sprint after.
Growing SDK test coverage from 4% to 76% taught me one thing: start with the event dispatcher, not the UI. Dispatchers are pure functions with clear contracts — easy to test, impossible to skip. UI tests are flaky; logic tests are gold.
gRPC shines on internal service communication — typed contracts, bi-directional streaming, and a fraction of the JSON overhead. REST still wins at the public boundary where you need browser support and human-readable payloads. Use both, know why.
Kotlin Channel vs SharedFlow — Channel is a queue with backpressure (good for work items), SharedFlow is a broadcast with replay (good for events). Mixing them up causes dropped UI events or unbounded queues. Know the difference before you pick.
Face liveness with ML Kit: blink + smile + head-turn challenges cut spoofing attempts by ~65% in our KYC flow. The real gain wasn’t the challenges themselves — it was randomising the sequence so recorded videos can’t replay. Spoofers hate randomness.
Kafka consumer group rebalances are the silent killer of throughput during deploys. Use session.timeout.ms and max.poll.interval.ms to tune how quickly the group detects a dead consumer vs how long a slow consumer gets before eviction. Default values are too conservative for most production loads.
Swift’s @MainActor is a compile-time guarantee that UI mutations stay on the main thread — something we used to enforce with DispatchQueue.main.async and hope. Actors eliminate the data races but they shift the discipline from runtime crashes to await boundaries. Trust the compiler, not your memory.