Architecture Overview¶
MyTwin follows MVVM (Model–View–ViewModel) with a lightweight manual Service Locator for dependency wiring. No Hilt or Dagger — the trade-off was deliberate for a 36-hour hackathon: fast setup, zero annotation processing overhead, trivially inspectable.
High-Level Diagram¶
graph TD
subgraph UI["UI Layer"]
S[Screens<br/>Composable]
VM[ViewModels]
end
subgraph Domain["Domain / Simulation"]
SR[SimulationRepository]
SE[SimulationEngine]
CR[ChatRepository]
CB[ClaudeContextBuilder]
CA[ClaudeApiService]
end
subgraph Data["Data Layer"]
UPR[UserProfileRepository]
HR[HealthRepository]
OR[OnboardingRepository]
UPD[UserProfileDataSource<br/>DataStore JSON]
SHD[SamsungHealthDataSource<br/>Samsung Health SDK]
OD[OnboardingDataSource<br/>DataStore Preferences]
end
SL[ServiceLocator]
S -->|observes state| VM
VM --> SR
VM --> CR
VM --> UPR
VM --> OR
SR --> UPR
SR --> HR
SR --> SE
CR --> UPR
CR --> HR
CR --> SR
CR --> CB
CR --> CA
HR --> SHD
UPR --> UPD
OR --> OD
SL -.->|lazy init| VM
SL -.->|lazy init| SR
SL -.->|lazy init| CR
Layer Responsibilities¶
| Layer | Package | Purpose |
|---|---|---|
| Screens | ui/screens |
Composable UI, no business logic |
| ViewModels | ui/viewmodels |
UI state holders, coroutine scope owners |
| Repositories | data/repository, chat, simulation |
Business logic, data aggregation |
| Data Sources | data/datasource |
Raw access to DataStore and Samsung Health SDK |
| Models | data/model, simulation, chat |
Pure data classes and enums |
| Service Locator | di/ServiceLocator |
Singleton DI container |
Key Design Decisions¶
Manual Service Locator over Hilt¶
ServiceLocator is an object initialized once in MyTwinApplication.onCreate(). Every dependency is a lazy val — created on first access. This avoids generated code, annotation processing, and the learning curve of Hilt, which matters when you have 36 hours.
No XML layouts¶
Every screen is a Jetpack Compose @Composable. MainActivity hosts a single AppNavGraph. There are no fragments.
DataStore for persistence¶
Two DataStore instances:
- Onboarding flag — a single boolean Preference
- User profile — the full
UserProfiledata class serialized to JSON viakotlinx.serialization
Streaming chat¶
ClaudeApiService opens a raw HttpURLConnection to api.anthropic.com/v1/messages with Accept: text/event-stream. Tokens arrive as SSE content_block_delta events and are emitted as a Kotlin Flow<String>, which ChatViewModel collects into the current message bubble in real time.