Building AppointaKit: A Modern Android Showcase
When I set out to build a showcase app for my portfolio, I wanted it to be more than a toy project. I wanted something that demonstrates how I architect, build, and ship Android software at scale.
The Challenge
Most portfolio projects are simple CRUD apps that don’t reflect the complexity of real-world development. I needed something that showcased:
- Clean Architecture with proper separation of concerns
- Modern Jetpack libraries working together harmoniously
- Production patterns like process death survival, pagination, and background work
The Stack
AppointaKit uses a single-module architecture with clean package separation:
- Jetpack Compose for all UI — zero XML layouts
- Material 3 with Dynamic Color support
- Hilt for dependency injection across all layers
- Room with Paging 3 for local persistence
- WorkManager for appointment reminders
- Navigation Compose 2.8+ with type-safe routes
- Kotlin Coroutines + Flow for reactive data streams
Architecture Decisions
MVVM + UiState
Every ViewModel exposes a single StateFlow<UiState> and accepts events through an onEvent() function. This unidirectional data flow makes state management predictable:
data class BookingUiState(
val currentStep: BookingStep,
val service: Service?,
val isLoading: Boolean,
)
SavedStateHandle for Process Death
The booking wizard persists its state through SavedStateHandle, surviving process death without losing the user’s progress across 4 steps.
Mock API with Real Architecture
Even though the data is mocked, the entire network layer uses real Retrofit + OkHttp with a MockInterceptor. Swapping to a real API would require changing one Hilt module.
What I Learned
Building this reinforced that good architecture isn’t about following patterns blindly — it’s about making the right trade-offs for your context. A single module was the right call for a showcase app, but the clean package separation means it could be split into modules without architectural changes.
The app is live on my portfolio — you can interact with it directly in your browser through an embedded demo.