Mobile Engineer · Flutter · Clean Architecture
I build Android applications with an emphasis on maintainability and architectural correctness — feature-first clean architecture, strict layer separation, and domain-driven design using functional error handling. My work prioritizes structural decisions that scale, not just code that runs.
| Domain | Tools |
|---|---|
| Language | Dart |
| Framework | Flutter |
| Architecture | Feature-First Clean Architecture, SOLID |
| State Management | BLoC / Cubit |
| Error Handling | fpdart (Either throughout the domain layer) |
| Persistence | SQLite (sqflite), ObjectBox, Hive |
| DI | get_it |
| Routing | auto_route, go_router |
| Networking | dio |
| Backend | Firebase (Auth, Firestore) |
| AI / ML | Sherpa-ONNX (VAD/STT/TTS), Groq API, Gemini API |
| Code Generation | freezed, injectable, build_runner |
| CI/CD | GitHub Actions |
Valoqui — Real-Time AI Spanish Conversation Tutor
Real-time voice conversation partner for language learning. Eliminates wait times by running VAD and TTS fully on-device — one network roundtrip per exchange (LLM) rather than three. Built with AI agents under my architectural direction — see JOURNEY.md.
What's technically interesting:
-
Segment-based VAD contract —
SherpaVadDatasourceemits completeFloat32Listutterances after silence detection rather than raw bytes plus a separate boolean stream. Eliminates the timing correlation problem between two independent streams; STT receives exactly what VAD identified as speech. -
Perceived latency optimization — partial STT decoding begins while the user is still speaking. By the time silence is detected the final transcript arrives almost immediately rather than requiring a full decode pass post-speech. The actual decode time is identical; the perceived wait is not.
-
Sentence-boundary TTS trigger — the BLoC detects
.,?,!in the LLM token stream and fires TTS on the first complete sentence before the full response has arrived. Warm-up runs fire-and-forget during LLM generation so the engine is hot by the time the first boundary is hit. -
Application-level echo cancellation — VAD monitoring stops during TTS playback and restarts after, flushing internal buffers. Without this, Lucia's voice feeds back into the mic as a spurious VAD segment and drops the user's next utterance.
-
Lifecycle correctness under session cycling — repositories registered as
registerFactory, datasources asregisterLazySingleton. Prevents the dead-singleton crash on second session when a route-scoped BLoC disposes a singleton on close. A registration type decision, not a code change.
Stack: Flutter · BLoC · Freezed · GetIt · fpdart · Firebase · Sherpa-ONNX (VAD/STT/TTS) · Groq LLaMA 3.3 70B · Gemini 2.0 Flash · Dio · GoRouter
Osserva — Offline Music Player with Local Analytics
9 features, 221 files, 139 directories. The core idea: give users the listening analytics that streaming services provide, without routing any data off-device.
What's technically interesting:
-
Analytics data layer split into 4 distinct responsibilities —
analytics_recorder(write path),analytics_reader(query path),analytics_aggregator(rollup computation), andaudio_analytics_tracker(session tracking). Separate datasource classes, not methods on a single class. 7 use cases sit above them includingwatch_playback_history, which exposes a reactive stream rather than a one-shot fetch. -
Star schema SQLite analytics engine — play events recorded against a fact table with dimension tables for songs, artists, albums, and genres. Daily rollups use a hot/cold record pattern to minimize write amplification on repeat plays.
-
Per-feature DI modules — 8 dedicated
get_itmodules, each registering only its own dependencies. No monolithic init file; features are self-contained at the DI level. -
Android Home Screen Widget with full cold-start support —
OsWidgetManager+WidgetSyncServicehandle state sync; playback can be initiated from the widget even when the app process is not running. -
fpdartEither types propagated through every repository and use-case boundary. Each feature defines its own typed failure classes (AnalyticsFailure,ArtistFailure,PlaylistFailure, etc.) rather than sharing a global failure type.
Stack: Flutter · BLoC · sqflite · fpdart · freezed · get_it · auto_route · home_widget · just_audio · audio_service
Git-RS — Git Object Engine in Rust
A from-scratch implementation of Git's core storage layer in Rust. Every object written by git-rs is verified readable by the official git binary — if the hashes match, the binary format is correct.
Implements: init, hash-object, cat-file, write-tree, commit-tree, commit.
What's technically interesting:
- Hash-before-compress pipeline —
write_objectconstructs the<type> <size>\0<content>header, computes SHA-1 over the raw object, then Zlib-compresses for storage. The hash is a commitment to the content before any encoding — a single transposition and officialgitrejects the object entirely. - Content-addressed path derivation — the filesystem path
.git/objects/XX/YYY...is derived mathematically from the SHA-1 hash, not assigned. Two identical files produce one object on disk. Enforced by the storage layout, not application logic. - Size validation on read —
read_objectparses the declared size from the object header and verifies it against the actual decompressed content length. Corruption is caught explicitly rather than propagating silently. - Post-order DAG traversal via call stack —
write_treerecurses into subdirectories before writing the parent entry. The recursion itself enforces the invariant that child hashes exist before a parent commits to them — no explicit queue or ordering logic required. Option<String>for the initial commit —read_refreturnsNonewhen the branch file doesn't yet exist, propagating cleanly intowrite_commit_objectasparent: None. No special-casing at the call site.- Detached HEAD guard —
read_headexplicitly rejects a raw hash in.git/HEADwith a meaningful error rather than silently treating it as a ref path. Stack: Rust ·sha1·flate2·hex
Trip Planner — Offline-First Travel Manager
Feature-first clean architecture applied to a multi-feature CRUD application. Offline-first by design using ObjectBox for persistence.
Notable: Budget estimation with live currency conversion using a cached-fallback strategy — the app degrades gracefully when the conversion API is unavailable, falling back to the last known rate.
Stack: Flutter · BLoC/Cubit · ObjectBox · Dio · freezed · auto_route
Two sequential iterations of the same application, each targeting a specific architectural concern.
- v1: BLoC pattern and computed state properties.
ExploreStateexposes derived getters (todaysChallenge,filteredRecipes) to keep the UI purely declarative. - v2: REST API integration with explicit repository-level error mapping —
DioExceptionis caught at the data layer and converted to typedFailureobjects before crossing into the domain.
Stack: Flutter · BLoC · Dio · freezed · auto_route · shared_preferences


