- Build config: build.gradle (app + root), settings.gradle, AndroidManifest - Application entry: PantreeApplication (Hilt), MainActivity (Compose host) - Data layer: - ApiService (Retrofit interface, all 20 endpoints) - ApiModels (all request/response DTOs) - AuthInterceptor (JWT Bearer header injection) - TokenStore (EncryptedSharedPreferences) - SyncPreferences (DataStore, last-sync timestamp) - PantreeDatabase (Room, 5 entities) - Entities: PantryItemEntity, RecipeEntity, RecipeIngredientEntity, ShoppingListEntity, ShoppingListItemEntity - DAOs: PantryDao, RecipeDao, ShoppingListDao - Repositories: AuthRepository, PantryRepository, RecipeRepository, ShoppingListRepository, SyncRepository - DI: AppModule (Hilt, provides OkHttp/Retrofit/Room/DAOs) - Util: Result<T> sealed class, safeApiCall, toUserMessage (human-readable error mapping) - Sync: SyncManager (DefaultLifecycleObserver, triggers on app open/foreground) - Theme: Color, Type, Theme (Material 3, light + dark) - Navigation: Screen sealed class, PantreeNavGraph (deep links for password reset) - Shared components: LoadingState, ErrorState, EmptyState, OfflineBanner, PantreeTopBar - Auth screens: SplashScreen, SignInScreen, SignUpScreen, ForgotPasswordScreen, ResetPasswordScreen + AuthViewModel - Pantry screen: PantryScreen (list/add/edit/delete dialogs) + PantryViewModel - Recipe screens: RecipesScreen (filter chips, search, availability badges) + RecipeDetailScreen (scale 1x/2x/3x, ingredient pantry status) + RecipeViewModel - Shopping screens: ShoppingListsScreen + ShoppingListDetailScreen (check-off, progress bar, merge feedback, add-from-recipes) + ShoppingListViewModel - Settings screen: sign out + account deletion (15-day window copy) + SettingsViewModel - Tests: ResultTest (unit), RecipeDetailScreenTest (unit), PantryScreenTest (instrumented) - README: architecture, module structure, UI states table, sync strategy, setup Every screen handles loading / error / empty / success states. Offline: cached Room data shown read-only.
3.0 KiB
3.0 KiB
Pantree Android
Android client for Pantree — the app that tells you what you can cook with what you already have.
Architecture
- MVVM — ViewModel + StateFlow, no LiveData
- Hilt — dependency injection
- Room — local cache (pantry items, recipes, shopping lists)
- Retrofit + OkHttp — network layer with JWT interceptor
- Jetpack Compose — declarative UI, Material 3
- EncryptedSharedPreferences — JWT stored at rest
- DataStore — last-sync timestamp
Module Structure
app/src/main/java/com/pantree/app/
├── data/
│ ├── local/ # Room DB, DAOs, entities, TokenStore, SyncPreferences
│ ├── model/ # API DTOs (request/response)
│ ├── remote/ # ApiService (Retrofit), AuthInterceptor
│ └── repository/ # AuthRepository, PantryRepository, RecipeRepository,
│ # ShoppingListRepository, SyncRepository
├── di/ # Hilt AppModule
├── sync/ # SyncManager (lifecycle observer)
├── ui/
│ ├── components/ # LoadingState, ErrorState, EmptyState, OfflineBanner, PantreeTopBar
│ ├── navigation/ # Screen sealed class, PantreeNavGraph
│ ├── screens/
│ │ ├── auth/ # SplashScreen, SignInScreen, SignUpScreen,
│ │ │ # ForgotPasswordScreen, ResetPasswordScreen + AuthViewModel
│ │ ├── pantry/ # PantryScreen + PantryViewModel
│ │ ├── recipe/ # RecipesScreen, RecipeDetailScreen + RecipeViewModel
│ │ ├── shopping/ # ShoppingListsScreen, ShoppingListDetailScreen + ShoppingListViewModel
│ │ └── settings/ # SettingsScreen + SettingsViewModel
│ └── theme/ # Color, Type, Theme
└── util/ # Result<T>, safeApiCall, toUserMessage
UI States
Every screen handles all four states:
| State | Implementation |
|---|---|
| Loading | LoadingState composable — spinner + contextual message |
| Error | ErrorState composable — emoji + message + retry button |
| Empty | EmptyState composable — emoji + title + subtitle + optional CTA |
| Success | Full content with LazyColumn / detail view |
Offline: cached data shown read-only, OfflineBanner displayed.
Sync Strategy
SyncManagerimplementsDefaultLifecycleObserver— triggers ononStart(app open + foreground)- Delta sync via
GET /sync?since=<last_timestamp> - Server timestamp stored in DataStore, used as
sinceon next sync - Room cache updated; UI observes
Flow<List<Entity>>— updates automatically
Setup
- Set
BASE_URLinapp/build.gradledebug/release buildConfigFields - Set
GOOGLE_WEB_CLIENT_IDfor Google Sign-In - Run backend (
npm run devfrom repo root) ./gradlew assembleDebug
Running Tests
# Unit tests
./gradlew test
# Instrumented tests (requires emulator/device)
./gradlew connectedAndroidTest