Benjamin ThaiSoftware Engineer · ML
← Projects
Mobile2023Complete

FoodFit

A high school Android app that points a camera at a plate and tells you what is on it. The first project that taught me to finish things before adding to them.

GitHub ↗
JavaAndroidFirebaseMobile
01THE PROBLEM

One good question, and far too many answers.

The question was worth building around: what if your phone could look at your meal and tell you what was in it? I'd started paying attention to what I ate, kept wondering what the numbers actually were, and it turned into a group project none of us were qualified to attempt.

What went wrong is visible in the feature list. Food recognition, calorie estimates, recipes, to-do lists, goal tracking, accounts. Every idea sounded reasonable in a planning meeting, and each one was a project. We built the breadth and ran out of the two months before any single piece finished. The recognition screen predicts a class and stops; the recipes page never got the nutrition data that was the entire point of recognizing food.

02SCOPE

What four high schoolers actually shipped.

Accounts

Firebase email and password sign-up and sign-in, with a session check on launch that skips the login screen when someone is already signed in. This is the one system that is genuinely finished.

Recognition

Take a photo, send it to a recognition service, print what comes back. My teammate owned the model side; the screen in this repo is the HTTP version of it, and it stops at displaying the raw response.

Everything else

Nine activities of recipes, goals, benefits, credits, and curated links, each one a screen that works and none of them wired to the others or to any stored data.

Team of four: Shawn Gazin, Minh Tran, Matthew Dinh, and me. Java against the Android SDK on API 24+, submitted to the 2023 Congressional App Challenge. We didn't place.

03THE APPROACH

Nine activities and an Intent between each pair.

There is no architecture here to describe, and that is itself the accurate description. Every screen is an Activity, every navigation is an explicit Intent, and several of the handlers are hooked up through android:onClick attributes in XML rather than in code. No fragments, no view models, no repository layer. The app is exactly the sum of its screens.

That shape came from what we knew. We had just finished AP Computer Science A, so Java and Android Studio were the tools within reach, and the Android docs teach activities first. It works, and it holds together at nine screens; the seams show anywhere state has to outlive a single screen, which is most of the places a health app actually needs.

The one part with real plumbing is auth. SignInPage checks FirebaseAuth for a current user in onStart and jumps straight to the home screen if there is one, HomeActivity bounces back to the login screen if there isn't, and sign-out clears the session and finishes the activity so back doesn't return to it. It's a small loop and it's closed, the only place in the app where I thought about what happens on the second launch.

04HOW IT WORKS

Three screens, three levels of finished.

01 · DONE

Auth, start to finish

Register with a client-side six-character minimum, sign in, stay signed in across launches, sign out into a cleared back stack. Firebase does the hard part, but the flow around it is complete.

02 · HALF

Recognition, stopped at the response

The camera intent returns a thumbnail, the bitmap is compressed to JPEG and posted on a background executor, and the reply is dropped into a TextView as raw text. Nothing parses it, so nothing can use it.

03 · SKETCHED

Goals, alive only while the screen is

An ArrayList behind an ArrayAdapter, add by button, long-press to complete. It never touches storage, so a rotation or a back press empties it. That's the bug that taught me Android state has to be designed for.

05INTERFACE DECISIONS

What we chose, mostly for reasons that were true at the time.

Native Android in Java
We had just finished AP CS A and were comfortable in Java, so the platform followed the language rather than the other way around. For a two-month deadline with four first-timers, building in the thing we already knew was the right call and I'd make it again.
Firebase for authentication
None of us could have built and hosted a credential store safely, and it took an afternoon instead of a week. The first time I understood what a managed backend buys you.
Recipes as links out to AllRecipes
Six ImageButtons firing ACTION_VIEW intents at recipe URLs. Reasonable for a two-month project, but it means the recipes page has no data of its own, so there was nothing for the nutrition numbers to attach to when we finally got to them.
Breadth over depth, feature by feature
This is the one I'd reverse. Every new screen felt like progress and demoed like progress, so we kept starting things. Depth is invisible until it's finished, which is exactly why it kept losing.
06SCREENS
Our Congressional App Challenge submission, the two minutes we had left at the end. Uploaded by Matthew Dinh; built with Shawn Gazin, Minh Tran, Matthew Dinh, and me.▶Watch on YouTube ↗
Our Congressional App Challenge submission, the two minutes we had left at the end. Uploaded by Matthew Dinh; built with Shawn Gazin, Minh Tran, Matthew Dinh, and me.
The idea the whole app grew out of: point the camera at a plate, get a prediction back. Typo included, exactly as it shipped.
The idea the whole app grew out of: point the camera at a plate, get a prediction back. Typo included, exactly as it shipped.
The hub, and a clear picture of where we ran out of time, with the welcome line landing on top of the tiles it was supposed to sit above
The hub, and a clear picture of where we ran out of time, with the welcome line landing on top of the tiles it was supposed to sit above
Six recipes, six ImageButtons, six hardcoded links out to AllRecipes, and the nutrition numbers never made it in
Six recipes, six ImageButtons, six hardcoded links out to AllRecipes, and the nutrition numbers never made it in
Goals: type one, add it, long-press to complete it. It lives in an ArrayList and is gone the moment you leave the screen.
Goals: type one, add it, long-press to complete it. It lives in an ArrayList and is gone the moment you leave the screen.
Firebase email and password auth, the first backend any of us had wired up
Firebase email and password auth, the first backend any of us had wired up
The logo we settled on
The logo we settled on
07TRADEOFFS

What it cost, looking at the code now.

Chose

Adding screens instead of finishing them

Cost

Nine activities, and the thing the app was named for never completes. Recognition returns a string nobody reads, the recipes carry no nutrition data, and goals don't survive leaving the page. One finished feature would have demoed better than six sketched ones, and the judging was on the demo.

Chose

Building the UI before deciding where data lived

Cost

There is no storage layer anywhere in the app except Firebase's session. Goals are an in-memory ArrayList; the recognition result is a TextView. Every screen would have needed reworking to persist anything, which is why nothing does.

Chose

Leaving the scaffolding in

Cost

The recognition screen is still locked to landscape and still carries the Android TV banner and the label 'Leanback APIActivity' from the template it was generated out of. A start screen activity sits in the source with no manifest entry. Glide is in the dependency list and imported nowhere. None of it breaks the build, and all of it is a note to myself about cleaning up as I go.

We ran out of time for the demo, which is the part that got judged.

We spent the last stretch of the two months trying to close features, so the submission video got made in whatever was left. That felt like a scheduling mistake at the time and I've since decided it wasn't. It was the same mistake as everything else, arriving at the end. We had optimized the whole project for the number of things that existed rather than for what we could show working, and a demo is a forcing function that only rewards the second one. Nobody judging the Congressional App Challenge could see the six features we'd started. They could see the two minutes we had left to explain them. Now I build the thing I would show first, and let the rest of the plan survive contact with that.

08WHAT'S NEXT

What I'd do if I rebuilt it.

→

Ship recognition alone. Photograph a plate, identify it, show real nutrition numbers against it, and stop. That's the app. Recipes, goals, and resources were all easier to build than the one feature the idea depended on, which is precisely why they got built first.

→

Put the data model before the screens. Goals in a real store instead of an ArrayList, recipes as records instead of six hardcoded links, and the recognition response parsed into something structured. None of that is a UI change, which is why none of it happened when I was thinking screen-first.

→

Take the camera's full-resolution photo rather than the thumbnail the capture intent hands back in its extras. That thumbnail is a few hundred pixels wide, and we were sending it to something being asked to identify food.