114 lines
6.9 KiB
Markdown
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.
|