架构
了解 MVVM、Repository、Hilt、导航和控制器如何协同工作。
Kotlin 模板采用常规 Compose 架构:由 Hilt 创建 controllers 和 repositories,ViewModels 暴露
StateFlow,stateless screen composables 渲染,Navigation Compose 使用 typed routes,Supabase
外面包一层小的 remote data sources。
形状
Composable Route
-> @HiltViewModel
-> repository/controller
-> remote data source or platform SDK
-> AppResult / StateFlow
-> stateless Screen目标不是重抽象,而是让每层职责清楚:screens 渲染,ViewModels 协调 UI state,repositories 标准化数据结果,controllers 拥有 app-wide services。
依赖注入
Hilt entry points:
| Type | 职责 |
|---|---|
@HiltAndroidApp SoarApp | Application graph 和 eager startup hooks |
@AndroidEntryPoint MainActivity | 注入 root Compose tree 使用的 controllers |
@HiltViewModel | 把 repositories/controllers 注入 screen view models |
@Singleton controllers/repositories | session、purchases、DataStore-backed controllers、analytics、toasts 等共享服务 |
core/di/ 当前包含:
| Module | 提供或绑定 |
|---|---|
CoroutineModule | @ApplicationScope CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) |
DataStoreModule | 名为 soar_preferences 的 Preferences DataStore |
RepositoryModule | Supabase remote data source interfaces 和 Google Sign-In controller binding |
SessionModule | 来自 SessionController.currentUser 的 Flow<UserInfo?> |
BiometricModule | BiometricSettings interface 到 BiometricController |
SoarApp.onCreate() 当前启动 Sentry、PostHog 和 update controller。RevenueCat 被注入到这里,
让 singleton 被构造并开始观察 session state。
MVVM 和 state
Feature folders 使用 route/screen split:
@Composable
fun TodosRoute(viewModel: TodosViewModel = hiltViewModel()) {
val state by viewModel.uiState.collectAsState()
TodosScreen(state = state, onAdd = viewModel::add)
}TodosViewModel 是参考模式:data class TodosUiState、private MutableStateFlow、public
StateFlow,以及在 viewModelScope 中执行的事件方法。
当前代码同时使用 collectAsState() 和 collectAsStateWithLifecycle()。新 route composables
收集 ViewModel state 时优先使用 collectAsStateWithLifecycle();root controllers 可沿用现有
plain collectAsState() 的写法。
Repository 边界
Repositories 用 AppResult<T> 包裹 backend 调用:
sealed interface AppResult<out T> {
data class Success<T>(val data: T) : AppResult<T>
data class Failure(val error: AppError) : AppResult<Nothing>
}appSuspendRunCatching 会保留 coroutine cancellation,把常见 Supabase 和网络失败映射成友好消息,
并避免 exception 泄漏出 repository boundary。
Repository pattern:
RemoteDataSource interface
-> SupabaseRemoteDataSource implementation
-> Repository
-> ViewModelUnit tests 使用 in-memory fake remote data sources 实例化 repositories。项目不使用 Mockk, unit tests 也不需要 Hilt。
导航
Routes 是 nav/Routes.kt 中的 Kotlin serialization objects/data classes:
@Serializable
data object Paywall
@Serializable
data class LegalDocument(val docId: String)AppNavGraph.kt 拥有:
UpdateGate -> OnboardingGate -> ConsentGate组合。- 外层
NavHost:MainShell、auth graph、paywall、refer、upload、subscription、redeem-code。 - private
MainNavGraphcomposable:消费 deep links 和 auth state。
MainShellScreen.kt 拥有 Home、Component、Showcase、Me 的 nested tab NavHost。Tab 切换使用
popUpTo(findStartDestination())、saveState = true、launchSingleTop = true 和
restoreState = true,所以每个 tab 会保留自己的 back stack。
Gates 是 composition wrappers,不是 route destinations。这样可以清楚区分哪些代码在导航前阻塞应用, 哪些代码参与导航本身。
并发
使用最小且合适的 scope:
| 工作 | Scope |
|---|---|
| UI actions 和 screen loading | viewModelScope |
| 长生命周期 app controllers | CoroutineModule 提供的 @ApplicationScope |
| DataStore-backed controllers | stateIn(applicationScope, SharingStarted.Eagerly, ...) |
| Root UI callbacks | hosting composable 中的 rememberCoroutineScope() |
示例:
SessionController和 entitlement/update/locale/theme controllers 在 application scope 中维护 app-wideStateFlow。TodosViewModel会在 signed-in user 变化时取消并替换 load job。NotificationsRepository作为 singleton 保存 push token 和 inboxStateFlow,因为 FCM events 可能在 screen 不可见时到达。
测试
命令:
./gradlew testDebugUnitTest
./gradlew connectedDebugAndroidTest当前约定:
- Unit tests 位于
app/src/test。 - Coroutine tests 使用
kotlinx-coroutines-test、runTest和共享MainDispatcherRule。 - Repository tests 使用 fake remote data sources。
- ViewModel tests 用 fakes 直接调用 constructor。
- Instrumented tests 位于
app/src/androidTest,需要连接设备或模拟器。
添加 feature
- 如果 feature 有持久化数据,在
data/model添加 model。 - 添加 remote data source interface 和 Supabase implementation。
- 在
core/di/RepositoryModule绑定实现。 - 添加返回
AppResult的 repository。 - 添加一个拥有单一 UI state 的
@HiltViewModel。 - 添加 route composable 收集 state,并添加接收 values/callbacks 的 stateless screen composable。
- 在
Routes.kt或 feature-local graph 注册 typed routes。 - 先添加 fake-backed unit tests,再接入真实服务行为。
