How I Built and Shipped a Meal Logging App Alone Using AI in 5 Months
Published on: May 14, 2025
After quitting my job in 2024, I spent most of the year figuring things out.
Tried consulting. Didn't work out.
Got a fitness certification. Picked up skateboarding. Cooked a lot. Slept a lot.
Then in December, I caught up with an old friend from my startup village days. He told me something simple that stuck:
"You should try AI coding. It's wild what you can do now."
We ran the Dubai marathon together in January. After that, I gave it a shot.
Trying Things Out – The Early Versions
I started experimenting with AI tools in January 2025. Initially with ChatGPT and DeepSeek, then switching to Cursor by February. I didn't know Flutter well. I wasn't a seasoned dev.
I just had an idea, shaped partly by insights from an analysis of the MyFitnessPal community I had conducted:
- A private, no-login app to track food and support fat loss goals.
- No ads, no bloat.
- Something minimal that could actually work.
The early versions (1.0 and 1.1) were shipped quickly. Just a simple calorie calculator, code was messy, logic scattered, but it was a start. The AI tools were like having a senior engineer available 24/7. Not perfect, but helpful.
The Wall — and the Reset
By April, I hit a wall.
Vibe coding had worked… until it didn't. The app was functional but unmaintainable. I couldn't build new features without breaking others.
So I wiped it clean and started over.
That reset was driven by a new concept I had picked up from AI conversations—Domain-Driven Design (DDD).1 It made sense. Build clean layers. Separate logic. Make each part clear.
It was the first time I felt like I had a proper architectural backbone. And it came from repeatedly asking AI "how should I structure this?" until the pieces clicked.
+------------------------------------------------------+ | Presentation Layer (Flutter UI, Riverpod State) | | (Views, Widgets, State Notifiers) | +-----------------------^------------------------------+ | (User Interactions, State Updates) v +------------------------------------------------------+ | Application Layer / Use Cases | | (e.g., AnalyzeMealUseCase, ManualBackupUseCase) | | (Orchestrates Domain Layer and Repositories) | +-----------------------^------------------------------+ | (Calls Domain Logic, Accesses Data via Repo Interfaces) v +------------------------------------------------------+ | Domain Layer (Core Business Logic & Entities) | | (Entities: UserProfile, MealEntry, ProgressEntry) | | (Repository Interfaces: UserProfileRepository, etc.) | +-----------------------^------------------------------+ | (Data Operations via Repo Implementations) v +------------------------------------------------------+ | Data Layer (Data Access & Persistence) | | (Repository Implementations: UserProfileRepoImpl) | | (Data Models: UserProfileModel - Hive Adapters) | | (Interacts with Local DB (Hive) & Network Service) | +-----------------------^------------------------------+ | (External Services, Local Storage) v +------------------------------------------------------+ | Core Services / Infrastructure Components | | (EncryptionService, NetworkService, DeviceID, Hive) | | (Cloudflare Worker for Backups - see Fig 3) | | (Cloudflare Worker for AI Service - see Fig 4) | +------------------------------------------------------+
+---------------------+ +----------------------------+ +---------------------+ | | <--> | Cloudflare Worker | <--> | | | iOS Device | | (Backup Service) | | Cloudflare R2 | | (Getter App) | | | | (getter bucket) | | | | | | | +--------^------------+ +----------------------------+ +---------------------+ | | User Data Path (Local): | +--------------------------------+ | | 1. Data Entered in UI | | | 2. Processed by Domain/App | | | 3. Encrypted (AES-GCM) | --- Key in iCloud Keychain | | 4. Stored in Hive (Local DB) | | +--------------------------------+ | | Device UUID in iCloud Keychain Backup Data Flow to Cloud: 1. App collects data (Profile, Meals, etc.) from Hive. 2. Data is Gzip compressed. 3. Compressed data is Encrypted (AES-GCM) on device. 4. Encrypted & compressed payload sent via HTTPS to Cloudflare Worker. 5. Worker authenticates & stores payload in Cloudflare R2 (e.g., users/{uuid}/{backupName}). 6. Encrypted metadata (latest backup filename, timestamp) also sent and stored via Worker. Restore Data Flow from Cloud: 1. App requests metadata from Worker (gets encrypted, compressed metadata from R2). 2. App requests backup file from Worker (gets encrypted, compressed backup data from R2). 3. App decrypts and decompresses data locally. 4. Data is restored into local Hive database.
+---------------------+ +-----------------------------+ +--------------------------+ | |----->| Cloudflare Worker |----->| AI Providers | | iOS Device | | (getter-ai-service) | | (e.g., OpenAI, Gemini, | | (Getter App) | | | | Anthropic, Food APIs) | | |<-----| |<-----| | +---------------------+ +-----------------------------+ +--------------------------+ App sends meal text (e.g., "1 apple and 2 bananas") to AI Service Worker. AI Service Worker: - Authenticates request. - May query one or more AI providers for nutritional analysis. - Standardizes and parses the results. Returns structured nutritional data to the app.
The Real Build – Version 2.0
From mid-April, I rebuilt the app with everything I had learned.
- UI from my old versions.
- State managed with Riverpod.
- Local storage with Hive.4
- Encryption with AES-256 GCM.3
- Secure, optional cloud backups using a custom Cloudflare Worker + R2.5
- Meal analysis powered by an AI orchestrator service I hosted. This also work as the backbone of meal logging feature in the app.6
It felt surprisingly smooth. The same features that once took weeks now took hours. I wasn't fighting the code anymore. It was starting to make sense.
On May 13, I submitted Getter v2.0 to the App Store and is live on the App Store as of May 15th. Built solo. From scratch. In less than a month since the restart.
What the App Does
Getter App is simple and focused:
- No sign-up. Just install and use.2
- AI-powered meal analysis: type what you ate, get instant nutrition info.6
- All your data stays on your device, encrypted.3
- Optional encrypted cloud backups.5
- Works offline. Clean UI. No noise.
You don't need to know how it works to use it. But behind the scenes, it's running a well-defined, layered architecture based on DDD.1 Each layer has one job—just like AI taught me.
Final Note
Getter is now live on the App Store.
It's not perfect. But it's real. It works. And it was built by one person, with the help of AI tools.
If you've got a vision, now's the time to try.
—
Deljo Joseph
Built in Dubai. Guided by AI.
Footnotes
- Domain-Driven Design (DDD): A software design approach that focuses on modeling software to match a domain according to input from that domain's experts. For Getter, this meant organizing code into layers (Domain, Data, Presentation, Core) to separate business logic from UI and data persistence, making the app easier to understand and maintain. ↩
- Device ID & iCloud Keychain: Getter uses
flutter_secure_storage
to generate a unique UUID (Universally Unique Identifier) for your app installation. This ID is stored in your iCloud Keychain, which means if you reinstall Getter on the same device or another iOS device logged into the same iCloud account, the app can retrieve this ID and your data (if backed up). ↩ - End-to-End Encryption (E2EE): Your meal logs, profile, and preferences are encrypted on your device using AES-256-GCM (Galois/Counter Mode). The encryption key itself is also securely stored in your iCloud Keychain. This means only your device (or other devices you own logged into the same iCloud) can decrypt your data. ↩
- Hive Local Storage: Getter uses Hive, a lightweight and fast NoSQL database for Flutter, to store all your app data directly on your device. This ensures the app works smoothly even when you're offline. ↩
- Cloud Backup Worker & R2: If you enable cloud backups, your Gzip compressed and AES-GCM encrypted data is sent via HTTPS to a small, dedicated Cloudflare Worker that I run. This worker then stores the encrypted data in Cloudflare R2 object storage. The worker itself cannot decrypt your data as the encryption happens client-side, and the key is managed via your iCloud Keychain. This backup worker is distinct from the separate Cloudflare Worker used for AI meal analysis. The authentication token for the backup service is injected into the app at build time. ↩
- AI-Powered Meal Analysis Service: To enable effortless meal logging, Getter uses a dedicated Cloudflare Worker (often referred to as `getter-ai-service`). When you type what you ate, the app sends this text, typically packaged in a JSON payload (e.g., `{"feature": "mealLogging", "prompt": "your meal text"}`), to the worker. This worker, authenticated via an API key header (the key itself is injected into the app at build time), acts as an intelligent orchestrator. It may query various AI language models or specialized food APIs to obtain detailed nutritional information, then standardizes this data and returns it to the app. This system allows for flexibility in choosing the best AI providers and improving analysis quality over time without needing app updates. This service is distinct from the backup worker. ↩