Architecture Layers¶
A detailed look at each layer, what lives in it, and how data flows through the stack.
Data Sources¶
Located in data/datasource/.
Data sources are the only layer that knows about Android platform specifics or external SDKs.
OnboardingDataSource¶
Wraps a DataStore Preferences instance. Stores and reads a single boolean: whether onboarding has been completed.
// Key usage pattern
val onboardingCompleted: Flow<Boolean>
suspend fun setOnboardingCompleted(completed: Boolean)
UserProfileDataSource¶
Wraps a DataStore Preferences instance. Stores the full UserProfile data class as a JSON string using kotlinx.serialization. Exposes it as a Flow<UserProfile?> and provides a save(profile) suspend function.
SamsungHealthDataSource¶
Wraps the Samsung Health Data SDK. Handles:
- Availability detection
- Permission request delegation
- Per-metric data reads (sleep, steps, heart rate, stress)
- Historical vs. recent data probing to populate
WearableSignalstates
All reads are wrapped in try/catch — Samsung Health throws liberally and the app must never crash during a demo.
Repositories¶
Located in data/repository/, simulation/, and chat/.
Repositories aggregate data sources and expose clean, domain-oriented APIs to ViewModels.
OnboardingRepository¶
Thin facade over OnboardingDataSource. ViewModels call isOnboardingComplete(): Flow<Boolean> and completeOnboarding().
UserProfileRepository¶
Facade over UserProfileDataSource. Provides getProfile(): Flow<UserProfile?> and saveProfile(profile). Contains no business logic — the ViewModel drives profile mutations.
HealthRepository¶
Facade over SamsungHealthDataSource. Maps raw SDK results to domain types (WearableSignal, WearableHistoryPoint). Shields the rest of the app from Samsung SDK types entirely.
Key methods:
suspend fun getWearableSignal(metric: SamsungHealthMetric): WearableSignal
suspend fun getTodaySteps(): Int?
suspend fun getTodaySleep(): Float?
suspend fun getRestingHeartRate(): Int?
suspend fun getRecentHistory(metric, days): List<WearableHistoryPoint<*>>
SimulationRepository¶
Aggregates UserProfileRepository, HealthRepository, and SimulationEngine. The single entry point for computing a SimulationBundle:
- Loads the current
UserProfile - Reads recent wearable history (7-day averages, 3-day HR average)
- Applies manual fallbacks where wearable data is absent
- Computes
SimulationInput - Passes it to
SimulationEngine.evaluate()andSimulationEngine.compare()for each scenario - Returns the complete
SimulationBundle
ChatRepository¶
Assembles the full context for an AI chat turn:
- Loads
UserProfile - Loads
HealthRepositoryhistory - Calls
SimulationRepositoryfor the currentSimulationBundle - Passes everything to
ClaudeContextBuilder.build()to produce aClaudeContextSnapshot - Delegates streaming to
ClaudeApiService.streamMessage()
ViewModels¶
Located in ui/viewmodels/.
ViewModels hold StateFlow-backed UiState data classes and expose suspend fun or regular fun actions to the UI.
| ViewModel | Screen | Key State |
|---|---|---|
RootViewModel |
MainActivity |
startDestination: String |
OnboardingViewModel |
OnboardingScreen |
OnboardingUiState (step, field values, permission signals) |
HomeViewModel |
HomeScreen |
HomeUiState (metrics, date, override flags) |
SimulationViewModel |
SimulationScreen |
SimulationUiState (loading, bundle, error) |
ChatViewModel |
ChatScreen |
ChatUiState (messages, input, isSending) |
RootViewModel¶
Reads OnboardingRepository.isOnboardingComplete() and immediately produces a startDestination string (Routes.HOME or Routes.WELCOME). MainActivity waits for this before rendering AppNavGraph.
Screens¶
Located in ui/screens/.
All screens are @Composable functions. They observe vm.uiState.collectAsStateWithLifecycle() and call ViewModel methods in response to user actions. No business logic lives here.
Each screen receives navigation callbacks as lambda parameters — screens never know about the NavController directly.
Models¶
Located in data/model/, simulation/, and chat/.
Pure data classes and enums with zero Android dependencies.
Key types:
| Type | Location | Purpose |
|---|---|---|
UserProfile |
data/model |
All onboarding-collected data for a user |
WearableSignal |
data/model |
Four-state enum for wearable presence |
WearableHistoryPoint<T> |
data/model |
A timestamped data point from the wearable |
SimulationInput |
simulation |
Flattened inputs consumed by SimulationEngine |
SimulationReport |
simulation |
Scores + narrative for one simulation run |
SimulationBundle |
simulation |
Baseline report + all scenario comparisons |
ChatMessage |
chat |
A single message in the conversation |
ClaudeContextSnapshot |
chat |
The assembled system prompt for Claude |