$ skip to content
~ home

cat parsnip.mdx

Parsnip

Senior Android Engineer · Tapforce · 2022–present

  • Kotlin
  • Jetpack Compose
  • Hilt
  • Coroutines
  • Firebase
  • Supabase
  • Media3

View on Google Play: open play://com.tapforce.parsnip

Context

Parsnip teaches people to cook through interactive, quiz-based lessons rather than another recipe catalogue. Skills — knife work, heat control, plating — are broken into structured learning paths and multi-dish Bootcamp curricula, and the app tracks progress with star scores and badges as users work through them. It is a consumer app for home cooks who want to genuinely get better, with a free tier and a Parsnip Pro subscription.

Approach

Parsnip is a single-module app organized feature-first: quiz, recipe, learning, onboarding, auth, home, profile, and subscription are each a top-level package owning their own presentation — and, where they need them, data, domain, and di — over a shared core. It did not start that way. The codebase began layer-based, and once the presentation layer's thirty-odd feature packages made the real seams obvious, I migrated it feature by feature. The boundaries are enforced rather than aspirational: Konsist tests fail the build if core imports a feature or one feature reaches into another uninvited, and a custom detekt rule module keeps the smaller conventions honest.

Technical decisions

A Parsnip lesson is a run of quiz questions, and the app teaches through six distinct formats — knowledge checks, matching, multi-select, sequencing, picture answers, and timed fast questions. Modeling them as a sealed type, rather than a loose string, makes every place that renders, scores, or generates a question exhaustive over the set: adding a seventh format is a compile error until every when accounts for it.

@Keep
sealed class QuestionType(val name: String) {
    data object KnowledgeQuestion : QuestionType("knowledge_question")
    data object MatchingQuestion : QuestionType("matching_questions")
    data object MultiselectQuestion : QuestionType("multiselect_questions")
    data object OrderingQuestion : QuestionType("ordering_question")
    data object PictureAnswerQuestion : QuestionType("picture_answer")
    data object FastQuestion : QuestionType("fast_question")
}

The other decision that shaped recent months is the design-token pipeline. Design lives in Figma; a custom Figma plugin exports tokens into a dedicated design-system repo, where CI validates and diffs every change; the app pins that repo as a git submodule, and a Gradle task generates typed Compose constants from the manifest at build time. A raw-color ratchet in CI fails any PR that adds a hex literal outside the generated theme — and the baseline it ratchets against is now zero.

Outcome

Parsnip is live on the Play Store and well past its early days — version 3.8 across nearly nine hundred commits, shipped on a steady cadence through Fastlane and GitHub Actions. The feature-sliced structure has held up across all of it: adding a feature still means slotting into a shape that already exists rather than carving out a new one, and the machine-enforced boundaries mean the shape survives the weeks nobody is thinking about it.

cat ~/contact.txt

cat builder-brigade.mdx # next

ls -la # all projects

dimvlachos@portfolio:~$ help

/
jump to a page or project
g h
home
g p
projects
g a
about
g n
now
g u
uses
?
this help
esc
close
>

10 results