Files
pantree/android/README.md
Azriel 75db2ea8f5 feat(android): implement full Android frontend
- 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.
2026-05-10 15:09:12 +00:00

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

  • SyncManager implements DefaultLifecycleObserver — triggers on onStart (app open + foreground)
  • Delta sync via GET /sync?since=<last_timestamp>
  • Server timestamp stored in DataStore, used as since on next sync
  • Room cache updated; UI observes Flow<List<Entity>> — updates automatically

Setup

  1. Set BASE_URL in app/build.gradle debug/release buildConfigFields
  2. Set GOOGLE_WEB_CLIENT_ID for Google Sign-In
  3. Run backend (npm run dev from repo root)
  4. ./gradlew assembleDebug

Running Tests

# Unit tests
./gradlew test

# Instrumented tests (requires emulator/device)
./gradlew connectedAndroidTest