# 37. Android App — API Client, tRPC Bridge, and Offline Support meta: id: shieldai-unified-restructure-37 feature: shieldai-unified-restructure priority: P1 depends_on: [shieldai-unified-restructure-34, shieldai-unified-restructure-35, shieldai-unified-restructure-36] tags: [android, kotlin, api, networking, offline, mobile] objective: - Build the API client layer for the Android app that communicates with the unified monolith's tRPC endpoints. Use a thin HTTP bridge approach with Retrofit or Ktor Client, plus offline support with Room caching and WorkManager request queuing. deliverables: - `android/app/src/main/java/com/shieldai/android/data/remote/` — Remote data layer: - `TRPCApiService.kt` — Retrofit interface or Ktor client defining all tRPC endpoints: - `@POST("api/trpc/user.me")`, `@POST("api/trpc/darkwatch.getWatchlist")`, etc. - Request/response wrappers for tRPC batch format - `TRPCResponse.kt` / `TRPCError.kt` — Data classes for tRPC response/error parsing - `AuthInterceptor.kt` — OkHttp interceptor injecting JWT from EncryptedSharedPreferences - `ErrorHandler.kt` — Centralized error handling with retry logic - `android/app/src/main/java/com/shieldai/android/data/local/` — Local data layer: - `ShieldAIDatabase.kt` — Room database with entities: - `UserEntity`, `SubscriptionEntity`, `WatchlistItemEntity`, `ExposureEntity`, `AlertEntity`, `VoiceEnrollmentEntity`, `VoiceAnalysisEntity`, `SpamRuleEntity`, `PropertyEntity`, `RemovalRequestEntity`, `BrokerListingEntity` - `DAO` interfaces for each entity with CRUD operations - `CacheManager.kt` — TTL-based cache logic using Room - `android/app/src/main/java/com/shieldai/android/data/repository/` — Repository layer: - `UserRepository.kt`, `DarkWatchRepository.kt`, `VoicePrintRepository.kt`, etc. - Each repository: remote fetch → cache to Room → return flow/coroutine - `RepositoryModule.kt` — Hilt module for DI - `android/app/src/main/java/com/shieldai/android/data/model/` — Data models: - `User.kt`, `Subscription.kt`, `WatchlistItem.kt`, `Exposure.kt`, `Alert.kt`, etc. - All models as Kotlin data classes with `Parcelable` where needed - Enum classes matching backend enums - `android/app/src/main/java/com/shieldai/android/data/sync/` — Offline support: - `OfflineWorker.kt` — WorkManager worker for retrying queued requests - `PendingRequestDao.kt` — Room table for queued mutations - `SyncManager.kt` — Manages queue, triggers WorkManager on connectivity restore - `android/app/src/main/java/com/shieldai/android/di/` — Dependency injection: - `NetworkModule.kt` — Retrofit/OkHttp setup - `DatabaseModule.kt` — Room database provider - `RepositoryModule.kt` — Repository bindings steps: 1. Add dependencies to `build.gradle.kts`: - Retrofit: `com.squareup.retrofit2:retrofit`, `converter-gson` or `converter-kotlinx-serialization` - OkHttp: `logging-interceptor` - Room: `androidx.room:room-runtime`, `room-ktx`, `room-compiler` - WorkManager: `androidx.work:work-runtime-ktx` - Hilt: `com.google.dagger:hilt-android`, `hilt-compiler` - Kotlinx Serialization: `org.jetbrains.kotlinx:kotlinx-serialization-json` 2. Create `TRPCApiService.kt`: - Define POST endpoints for each tRPC procedure - Request body: `{"0": {"json": { ... }}}` (tRPC batch format for single call) - Response: `TRPCResponse` with `result.data` field - Use Kotlinx Serialization for JSON parsing 3. Create data models: - Kotlin data classes with `@Serializable` annotation - Enum classes with `@Serializable` and custom serializers if needed - Date parsing using `kotlinx.datetime` or `java.time.Instant` 4. Create Room database: - `ShieldAIDatabase` abstract class extending `RoomDatabase` - Entity classes with `@Entity`, `@PrimaryKey`, `@ColumnInfo` - DAO interfaces with `@Dao`, `@Query`, `@Insert`, `@Update`, `@Delete` - Type converters for complex types (enums, dates, JSON) 5. Create repositories: - Each repository has `remoteDataSource` (API service) and `localDataSource` (DAO) - `getData()`: check cache first, if stale or missing → fetch remote → save to cache → return - Return `Flow` or `suspend` functions with `Result` 6. Create offline support: - `PendingRequest` entity: `endpoint`, `method`, `body`, `timestamp`, `retryCount` - `SyncManager`: add request to pending queue when offline - `OfflineWorker`: periodic WorkManager task that processes pending requests - Network monitor using `ConnectivityManager` to trigger immediate sync 7. Create DI modules: - `NetworkModule`: provide `Retrofit`, `OkHttpClient` with auth interceptor and logging - `DatabaseModule`: provide `ShieldAIDatabase` singleton - `RepositoryModule`: bind repository interfaces to implementations 8. Test all layers with mocked dependencies. steps: - Unit: Retrofit service creates correct HTTP requests - Unit: Room DAO inserts and retrieves entities correctly - Unit: Repository returns cached data when offline - Unit: SyncManager queues requests when network unavailable - Integration: API client successfully calls `user.me` against local dev server acceptance_criteria: - [ ] Retrofit/Ktor client makes authenticated HTTP requests to tRPC endpoints - [ ] tRPC response format is correctly parsed into Kotlin data classes - [ ] Room database caches API responses with TTL-based invalidation - [ ] Offline mutations are queued and retried when connectivity restored - [ ] All common API procedures have type-safe Kotlin wrappers - [ ] Network errors trigger retry with exponential backoff - [ ] Repository layer provides clean abstraction over remote and local data - [ ] Hilt dependency injection is configured for all layers - [ ] API configuration supports different environments (dev, staging, prod) validation: - Point API client to local dev server (`http://10.0.2.2:3000` for emulator) - Call `user.me()` and verify response parsed into `User` model - Disconnect network, attempt a mutation, verify it queues in Room - Reconnect network, verify WorkManager processes queued request - Verify cache hit by calling same endpoint twice with network disabled - Run `./gradlew test` for unit tests notes: - Retrofit with Kotlinx Serialization is the recommended stack. Gson works too but Kotlinx Serialization has better null safety. - For tRPC, the Android client cannot use tRPC's type-safe client directly. The HTTP bridge is the pragmatic approach. - The tRPC batch link sends multiple procedures in one HTTP request. For simplicity, use single-procedure requests. - Room database schema should mirror the Drizzle schema closely, but can be simplified (fewer columns) for caching purposes. - WorkManager requires `androidx.work:work-runtime-ktx`. It handles background execution reliably across Android versions. - For network monitoring, `ConnectivityManager.registerDefaultNetworkCallback` is the modern approach (API 24+). - Use `Result` or sealed classes (`Success`, `Error`, `Loading`) for API state management in ViewModels.