Laying the Foundation: Chicome's Kitchen Dev Log #1
I've started building Chicome's Kitchen — an AI-powered nutritional assistant app — and I'm documenting the journey as I go, partly as a record and partly because I'm learning a lot of this stack for the first time. Here's where things stand after the first two stories.
The Plan
Before writing any code, I worked through the major architecture decisions: an ASP.NET Core 9 backend (Minimal APIs + MediatR for CQRS) talking to PostgreSQL via EF Core 9, a Flutter app for iOS/Android/web built around Riverpod and go_router, a separate Python FastAPI microservice for the AI/scraping/OCR work, all hosted on Azure. Authentication will be ASP.NET Core Identity plus Google OAuth2, with RS256 JWTs and rotating refresh tokens — no third-party auth service, since the budget doesn't really support one yet.
Each of those decisions came with trade-offs (Flutter vs. React Native vs. MAUI, Azure vs. AWS vs. cheaper PaaS options, Auth0 vs. rolling my own), and I wrote them all up as ADRs so future-me remembers why I picked what I picked.
With the plan in place, I scaffolded the monorepo, set up .NET User Secrets and generated an RS256 key pair for JWT signing (never going in the repo, obviously), and got the backlog into Azure DevOps.
Story 92: Health Checks
First real story: get the .NET API reporting its own health. This meant adding the health check packages, wiring connection strings for the database and cache, and registering checks in Program.cs. Mapped /health (liveness) and /health/ready (readiness, including dependency checks) as endpoints, tested locally, and merged it in. Small story, but it's the kind of thing that pays off the first time something breaks in production and you need to know what broke.
Story 93: The Flutter Scaffold
This was the bigger one — and my first real time inside Flutter/Dart. The goal was a working app skeleton with all the cross-cutting concerns in place before any real features get built on top.
What went in:
- Product flavors (dev/staging/prod) at the Android Gradle level, so each environment gets its own package ID, app name, and base URL.
- Feature-first folder structure — each feature gets its own
domain/,data/, andpresentation/layers, with ashared/folder for cross-cutting widgets and utilities. Sets up clean architecture from day one instead of retrofitting it later. - Riverpod for state management, wired up via
ProviderScopeat the app root. - Secure token storage using
flutter_secure_storage, plus a Dio HTTP client with a JWT interceptor that attaches the access token to outgoing requests (refresh-on-401 logic is stubbed in for now — that lands with the real auth work). - Hive for an offline cache, initialized asynchronously before
runAppand injected via provider overrides. - go_router with named routes and an auth guard — a
redirectfunction that bounces unauthenticated users to/loginand authenticated users away from it. I built a tiny "simulate login/logout" toggle on placeholder screens just to prove the guard actually redirects both directions.
The Debugging Tour
Almost nothing worked on the first try, which — turns out — is just what learning a new framework feels like. The fun parts:
- Riverpod 3.x removed
StateProvider. My first pass at the auth guard used the old shorthand provider API, which doesn't exist anymore. Replaced it with the unifiedNotifier<bool>/NotifierProviderpattern — a small class with abuild()for initial state and custom methods for mutations. resValueneeds an opt-in build feature. The product-flavor config usesresValue()to give each flavor a different app name, but newer Android Gradle Plugin versions disable resource-value generation by default. One line (buildFeatures { resValues = true }) fixed it.- The missing Android SDK platform. The build needed Android SDK Platform 36 (revision 2), which had failed to auto-download. A trip to the SDK Manager to manually check and install it sorted that out.
- The apostrophe that broke everything. "Chicome's Kitchen" has an apostrophe in it, and Android string resources don't like unescaped apostrophes —
aapt2flat-out refused to compile the resource. Fix: escape it as\'in the Kotlin Gradle DSL ("Chicome\\'s Kitchen"), which generates a properly escaped XML string resource. - Emulator ran out of storage. Running all three flavors installs three separate apps (each flavor gets its own package ID via
applicationIdSuffix), and the emulator's virtual disk filled up. Uninstalling the dev and staging builds viaadbfreed up enough room for prod to install.
By the end, all three flavors (dev/staging/prod) built and ran cleanly, each showing its own app name and base URL, with the login/logout auth guard working in both directions.
What's Next
The scaffold is in place, but it's not connected to anything real yet — the "login" button just flips a boolean. Next up is the actual auth backend: Users and RefreshTokens schemas, then the registration, login, and token-refresh endpoints. Once those exist, the Flutter app's Dio client and secure storage finally have something real to talk to. Email verification and Google OAuth are queued up right behind that.
More soon.