01 — The Idea
Food waste is a practical, daily problem. Most households have imperfect awareness of what is in their refrigerator — what is about to expire, what they have too much of, what they are running low on.
Fridgo started as a personal project to solve that problem with a well-designed, cross-platform application. The secondary goal was to build something that demonstrates what modern Kotlin Multiplatform development looks like in practice: a real product, with real users, built on a shared codebase across Android and iOS.
02 — Product
The core Fridgo experience is simple:
- Track inventory: Add items to your shared household inventory with quantity, unit, and expiration date.
- Get alerts: Proactive notifications when items are approaching expiration.
- Shared storage: Multiple users can manage a single household inventory — designed for shared living situations.
- Recipe recommendations: Based on what is currently in stock, Fridgo suggests recipes via external API integration.
- Multilingual: Full support for English and Bahasa Indonesia.
- Unit preferences: Metric and Imperial units, with canonical metric storage and transparent UI conversion.
03 — Architecture
Fridgo is built as a genuine KMP application — not just shared utilities, but a shared application architecture across Android and iOS.
┌─────────────────────────────────────────┐
│ UI LAYER (Platform-Specific) │
│ Compose Multiplatform (Android + iOS) │
├─────────────────────────────────────────┤
│ PRESENTATION LAYER (Shared) │
│ ViewModel · StateFlow · UI State │
├─────────────────────────────────────────┤
│ DOMAIN LAYER (Shared, Pure Kotlin) │
│ Use Cases · Entities · Repo Contracts │
├─────────────────────────────────────────┤
│ DATA LAYER (Shared) │
│ Repository Impl · Mappers │
├─────────────────────────────────────────┤
│ INFRASTRUCTURE │
│ Supabase · Local Cache · Platform APIs │
└─────────────────────────────────────────┘
The Domain layer contains zero platform-specific imports. It can be unit-tested on any JVM without a device or emulator.
04 — Engineering Decisions
Why Compose Multiplatform?
The goal was maximum code sharing with a native-quality UI. Compose Multiplatform allows sharing the UI layer in addition to the business logic — something the older KMP approach (shared logic, separate UI) does not. For a consumer product with a relatively uniform UI across platforms, this is the right tradeoff.
Why Supabase?
Supabase provides authentication, a PostgreSQL-backed real-time database, and file storage — all with good Kotlin support. For a product that needs multi-user shared state and cloud sync without managing a custom backend, Supabase offered the right balance of capability and operational simplicity.
Canonical Metric Storage
Unit preferences are a classic source of data bugs: store in the user's preferred unit, and you need to convert on every data transfer between users with different preferences. Fridgo stores all quantities in canonical metric units (grams, milliliters) and converts to the user's preferred display unit at the UI layer. This is a simple decision with significant data consistency benefits for shared household inventories.
Shared Inventory Data Model
The data model for shared household inventory is non-trivial. Multiple users may add, modify, or consume items concurrently. The data model uses Supabase's real-time capabilities for live updates, with conflict resolution strategies for simultaneous edits.
05 — Engineering Challenges
Shared state across multiple users in real-time.
When two household members are both using Fridgo simultaneously, the inventory state must stay consistent. This means handling real-time updates from the database, reconciling local optimistic updates with server state, and presenting a coherent view even during sync.
Canonical unit conversion without UI friction.
Users should never have to think about unit conversion. The conversion logic is encapsulated in the Domain layer as a pure function. The UI layer calls it transparently — no conversion code in ViewModel or Composable.
Cross-platform notifications.
Expiration alerts require platform-specific notification APIs. In KMP, this means defining an expect/actual interface in shared code and implementing it separately on Android (WorkManager) and iOS (UNUserNotificationCenter). The scheduling logic lives in shared Domain code — only the delivery mechanism differs per platform.
06 — Result
A functional, production-ready cross-platform application that:
- Demonstrates genuine KMP architecture — not just shared utilities
- Solves a real daily problem with a thoughtful product experience
- Showcases clean architectural decisions with concrete rationale
- Is independently built, owned, and maintained
The project is actively developed and reflects current Kotlin Multiplatform and Compose Multiplatform best practices.
07 — Technologies
KOTLIN Primary language
COMPOSE MULTIPLATFORM Shared UI (Android + iOS)
KMP Shared business logic + data
SUPABASE Auth · Database · Real-time sync
COROUTINES + FLOW Async state management
CLEAN ARCHITECTURE Layer separation
MATERIAL 3 Design system
WORKMANAGER (Android) Notification scheduling