Skip to content

全局状态

介绍

core/state 存放需要跨页面共享的应用级状态。当前包含用户状态持有者 UserState 和计数示例 DemoCounterState:前者集中维护登录标记、用户 ID、认证信息和用户资料,后者只维护 Demo 计数。页面局部状态仍应保留在 Feature ViewModel 中。

全局状态解决的是“多个页面需要观察同一份数据”,不是“任何状态都放到单例”。搜索文本、弹窗开关和一次性加载状态仍应放在对应 ViewModel;只有跨页面且需要应用进程内共享的数据才进入 core/state

UserState 的依赖与生命周期

以下链路展示 UserState 的三个数据来源和协程作用域。

text
UserState
├── AuthStoreRepository       → MMKV auth_info
├── UserInfoStoreRepository   → MMKV user_info
├── UserInfoRepository        → 用户信息网络接口
└── @ApplicationScope CoroutineScope

AppStateModule 提供 SupervisorJob() + Dispatchers.Default 的应用级作用域。Application.onCreate()MMKVUtils.init(this) 后显式调用 userState.initialize(),因此恢复本地登录状态不会早于 MMKV 初始化。

SupervisorJob 保证某个刷新任务失败时不会取消其他应用级任务;它不会替代 Repository 的 IO 线程切换,也不会让网络请求自动重试。

UserState 状态流

名称类型初始值含义
isLoggedInStateFlow<Boolean>false本地 Token 存在且未过期
userIdStateFlow<Long>0L当前用户 ID
authStateFlow<Auth?>null当前认证令牌信息
userInfoStateFlow<User?>null当前用户资料

这些属性由 asStateFlow() 暴露,页面只能收集,不能直接修改。

UserState 初始化与写入

初始化

initialize() 在应用作用域启动 initializeState():读取 AuthStoreRepository,判断登录状态;仅当已登录时读取用户资料并填充 userId

登录成功

updateUserState(auth, user) 先分别写入认证和用户资料仓库,再更新 _auth_userInfo_userId_isLoggedIn

更新 Token 或资料

  • updateAuth(auth) 保存新认证信息并保持登录态。
  • updateUserInfo(user) 保存完整用户资料,并同步资料与用户 ID 状态流。
  • refreshUserInfo() 在已登录时调用网络仓库,将成功数据交给 updateUserInfo

退出登录

logout() 清除认证和用户资料本地缓存,再把四个 StateFlow 重置为未登录初始值。

UserState 的 ViewModel 用法

文件位置:feature/main/viewmodel/NavigationViewModel.kt

当前 Navigation 页面直接注入 UserState,把全局登录状态作为只读 StateFlow 暴露给 Route。下面省略了导航结果相关状态。

kotlin
package com.joker.kit.feature.main.viewmodel

import com.joker.kit.core.base.viewmodel.BaseViewModel
import com.joker.kit.core.state.UserState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject

/**
 * Navigation 页面 ViewModel
 *
 * @param userState 用户状态
 */
@HiltViewModel
class NavigationViewModel @Inject constructor(
    private val userState: UserState
) : BaseViewModel() {

    /** 全局登录状态 */
    val isLoggedIn: StateFlow<Boolean> = userState.isLoggedIn
}

UserState 的 View 用法

文件位置:feature/main/view/NavigationScreen.kt

kotlin
package com.joker.kit.feature.main.view

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.joker.kit.feature.main.viewmodel.NavigationViewModel

/**
 * Navigation 页面路由
 *
 * @param viewModel Navigation 页面 ViewModel
 */
@Composable
internal fun NavigationRoute(
    viewModel: NavigationViewModel = hiltViewModel()
) {
    // Route 收集应用级登录状态并转换为 Screen 参数
    val isLoggedIn by viewModel.isLoggedIn.collectAsState()

    NavigationScreen(
        cards = emptyList(),
        isLoggedIn = isLoggedIn,
        onCardClick = {}
    )
}

当前完整 Route 还会收集卡片和导航回传结果。Screen 只接收值和回调;登录状态为 true 时展示登录提示卡片。

调用 userState.updateUserInfo(user) 后,所有已订阅 userInfo 的 Route 都会收到新对象;调用 logout() 后则依次清理本地仓库并把 isLoggedInauthuserInfouserId 恢复到初始值。UI 不需要手动刷新页面。

UserState API

方法参数作用
initialize()应用启动时异步恢复本地状态
updateUserState(auth, user)Auth, User登录成功后同时写入认证和用户资料
updateAuth(auth)Auth更新 Token 与登录标记
updateUserInfo(user)User更新本地资料和内存状态
refreshUserInfo()已登录时请求网络资料并更新状态
shouldRefreshToken()无,挂起委托认证仓库判断刷新窗口
logout()无,挂起清除本地缓存并重置状态

UserState 注意事项

  • UserState@Singleton,但它的 initialize() 仍需由 Application 手动调用一次;当前实现不会在构造函数中自动初始化。
  • refreshUserInfo() 在未登录时直接返回,不会发起网络请求。
  • updateAuth()_isLoggedIn 设为 true,调用方应确保传入的 Auth 有效。
  • 应用级状态使用 Dispatchers.Default;具体网络与数据库线程切换仍由 Repository 或 DataSource 负责。
  • refreshUserInfo() 只在当前 isLoggedIntrue 时发起请求;网络失败由 ResultHandler 记录和提示,旧的 userInfo 不会因为失败自动清空。
  • initialize() 是异步且无返回值的启动操作;需要等待初始化完成的页面应观察 isLoggedInauthuserInfo 的变化,不要假设调用后立即完成。

DemoCounterState

DemoCounterState 用于展示最小全局状态,不依赖 Repository:

方法行为
increase()计数加 1
decrease()计数减 1,最低为 0
reset()重置为 0

它的 count 也是只读 StateFlow。新增业务全局状态时,先确认它确实需要跨页面共享,再仿照 UserState 使用 @Singleton@ApplicationScope

官方文档