Files
Kordant/tasks/shieldai-unified-restructure/37-android-api-client.md
2026-05-25 12:23:23 -04:00

114 lines
6.9 KiB
Markdown

# 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<T>` 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<T>` or `suspend` functions with `Result<T>`
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<T>` or sealed classes (`Success`, `Error`, `Loading`) for API state management in ViewModels.