Packly
An AI travel planner designed, built, and shipped solo on Google Play. Less a story of obstacles, more an exercise in doing Android architecture properly, from the first module.
In 30 seconds
Packly plans trips with AI: smart packing lists, generated itineraries, multi-currency expenses. I built it alone, end to end, as a technical exercise taken seriously: a greenfield app structured like the codebase of a large team, with an architecture based on researching what Google itself recommends today. This page walks through that architecture and the reasoning behind it.
What it is, and why it exists
Packly gives each trip three surfaces: a packing list with templates and AI suggestions, an itinerary planned day by day (by hand or generated), and expense tracking with live exchange rates. It is live on Google Play in 10+ languages, with a free tier and a Pro subscription via Play Billing.
It was born as an exercise: I wanted to learn how to integrate AI into an Android project for real, and a greenfield app with no legacy was the chance to apply the current state of the art without compromise. Before writing the first module I researched what Google itself recommends today, taking inspiration from Now in Android, their open-source architecture showcase, and adapting it to this product.
My role
Everything is mine: interface design, development, testing, release, and store management. And precisely because nobody else works on it, the architecture matters more, not less: conventions and module boundaries do the job that a team’s code review would otherwise do.
The architecture
30+ Gradle modules with a strict scoping rule
The app is split into 6+ feature modules (packing list, itinerary, expenses...) and 20+ core modules (UI kit, data, database, network, AI...). Feature modules never depend on each other, and code is promoted to a core module only when at least two features need it. Boundaries stay honest, builds stay parallel, and every piece has one clear home.
Convention plugins: build logic written once
Four custom Gradle plugins (library, compose, hilt, feature) live in build-logic and are applied to every module. Creating a new module takes minutes and zero copy-paste, and a build-configuration change lands everywhere at once. This is what keeps 30+ modules manageable for one person.
One UI paradigm, no exceptions
100% Jetpack Compose with Material 3, and type-safe navigation (Navigation 3 with serializable routes). Each screen is a ViewModel exposing a single immutable UI state via StateFlow, with events flowing one way. No screen deviates from the pattern, so every screen is readable in the same way.
Abstraction where it buys testability
Repositories are internal behind public interfaces, and each Hilt module lives next to the implementation it binds, so features only see contracts. Unit tests use fakes rather than mocking frameworks. One deliberate trade-off: the domain model is the Room entity, zero mapping layers, because in an app this size mappers would be ceremony, not protection.
AI as a replaceable component
Three LLM providers (Anthropic, OpenAI, Google) sit behind a single interface with automatic fallback: if one fails, the next answers. The rest of the app does not know which model is talking. Keys never ship in the APK; they are delivered via Remote Config, gated by Play Integrity.
The numbers
- 30+ Gradle modules, 6+ feature and 20+ core
- 4 custom convention plugins in build-logic
- 100% Jetpack Compose, Material 3
- 10+ locales on the Play Store, releases via Fastlane
- Live on Google Play with a real business model (free tier plus Pro subscription). No user numbers here by choice: the app is young, and its value in this portfolio is the engineering.







What I learned
Working alone, discipline has to live in the structure, not in memory: conventions, module boundaries, and plugins are what keep the hundredth module as clean as the first.
The exercise paid off beyond the app itself: production-grade patterns for LLM integration (pluggable providers, fallback, secret delivery) and a tested blueprint for greenfield Android that I can defend choice by choice.