Overview
A native Android app that shows the monthly food menu of a chosen institution — typically a university canteen. The day's items render as cards in a month grid; tapping a day opens the full meal, and any day can be pushed to the device's calendar in one tap. The app is local-first: after the initial fetch from Firebase Realtime Database, everything works offline, and a "Remember Me" path skips the login screen entirely on subsequent launches.
Why I built it
Two reasons. First, I wanted a real Jetpack Compose project — not a tutorial clone. Compose was the native UI direction Google had committed to, and the ergonomics of declarative UI on Android were genuinely different from the XML-and-ViewBinding muscle memory I had. Second, my university's canteen posted a monthly PDF that was tedious to consult on a phone. The app was a useful artefact for me and for friends on the same campus before it was a portfolio piece.
The constraint that shaped most of the design: students rely on this between classes, sometimes underground, sometimes on flaky campus Wi-Fi. Anything that required a network round-trip to read today's menu was wrong.
How it works
On first launch (and on pull-to-refresh), the app pulls the institution's menu from Firebase Realtime Database and writes it into a Room database on the device. Firebase is the source of truth; Room is the read path.
The calendar screen reads from Room only. Each day becomes one of five card variants — normal, updated, weekend, holiday, or canceled — picked from flags on the day record. The month navigator scrolls horizontally; days flow into a vertical list under the active month.
Tapping a day opens a detail screen with the full meal breakdown and an "Add to calendar" action that hands the event to the system calendar intent. Local-first all the way: no further fetch.
Authentication runs through Firebase Auth — email/password with a reset flow. Profile pictures land in Firebase Storage. Login is required once; "Remember Me" persists the session and the calendar opens directly from cold start.
Stack and shape
MVVM with Hilt for dependency injection, Room for the local store, Firebase (Auth + Realtime Database + Storage) for the cloud side, Jetpack Compose end-to-end for the UI. The architecture is the textbook Android one because the textbook one is right for this size of app — separating ViewModels from Compose screens kept theme switches, language switches, and pull-to-refresh from leaking state into the wrong place.
The two interesting style decisions: Material You dynamic color is on by default (the app picks up the user's wallpaper-derived palette on Android 12+), and a small set of hand-tuned themes cover everything older. Three languages — Turkish, English, Arabic — flow through Android's standard resource qualifiers, which means the app itself translates cleanly but the food item names, which come from Firebase verbatim, do not. That's a known limitation, not a bug.
What I'd change
The biggest mistake was coupling the app's data layer to Firebase. Every institution needs its own Firebase project, its own rules, and a build of the app pointed at it. The next version is API-driven: the user pastes a link to any compliant OpenAPI source and the app talks to that.
- Per-locale food item names. The schema stores one list of strings per day. Switching the app to Arabic translates the chrome but not the meal, because there's no other meal to switch to. The next-version schema has a list per locale with a fallback chain.
- A working background sync. A
WorkManagerjob is registered to refresh the menu every two hours; it's visible in App Inspection but doesn't run. Diagnosing that needs a quiet afternoon I haven't given it. - Notifications on data change. The foreground service that listens for Firebase events exists; the notification builder on the other end of it doesn't. A day's work to wire up properly.
- Localized month names in the navigator. The month strip is an enum of
Gregorian month constants rendered via
toString()— aSimpleDateFormatpass with the active locale would fix it.
Repo
Source on GitHub. GPL-3.0.
