如何将DataStore凭证读写逻辑迁移到ViewModel以消除冗余代码
迁移DataStore逻辑到ViewModel的实现方案
核心思路
将DataStore的读写操作全部下沉到数据层UserRepository,所有用户相关的业务逻辑(自动登录、凭证存储、状态管理)全部收敛到ViewModel层,Activity仅负责UI交互和状态监听,彻底剥离Activity中的数据处理逻辑。
步骤1:重构UserRepository,集成DataStore操作
Repository作为数据层统一出入口,负责管理本地+远程的用户数据,首先将DataStore的相关操作封装到这里:
// 先定义DataStore存储的常量Key val USER = stringSetPreferencesKey("user_info") class UserRepository(private val userDataStore: DataStore<Preferences>) { // 原有远程登录逻辑保持不变 suspend fun auth(user: User): Resource<Token> { // 原有接口请求逻辑 } // 新增:保存用户凭证到本地DataStore suspend fun saveUserCredential(user: User) { userDataStore.edit { ds -> ds[USER] = setOf(user.username, user.password) } } // 新增:从DataStore读取本地存储的用户凭证 fun getStoredUser(): Flow<User?> { return userDataStore.data.map { prefs -> prefs[USER]?.takeIf { it.size == 2 }?.let { usrSet -> User(usrSet.first(), usrSet.last()) } } } }
步骤2:重构ViewModel,内置自动登录与凭证保存逻辑
ViewModel初始化时自动触发本地凭证读取、尝试自动登录,登录成功后自动存储凭证,不需要上层Activity通知:
class UserViewModel( private val userRepository: UserRepository ) : ViewModel() { private val _token = MutableStateFlow<Resource<Token>>(Resource.Loading()) val token: StateFlow<Resource<Token>> = _token private val _user = MutableStateFlow<Resource<User>>(Resource.Loading()) val user: StateFlow<Resource<User>> = _user init { // 初始化时自动读取本地凭证尝试登录 viewModelScope.launch { userRepository.getStoredUser().collect { storedUser -> storedUser?.let { login(it) } ?: run { _user.value = Resource.Error("No user yet") } } } } fun login(user: User) = viewModelScope.launch { _token.value = Resource.Loading() val authResult = userRepository.auth(user) _token.value = authResult if (authResult is Resource.Success) { _user.value = Resource.Success(user) // 登录成功直接保存凭证到本地,无需Activity参与 userRepository.saveUserCredential(user) } } // 其他原有逻辑保持不变 ... }
如果没有使用Hilt等依赖注入框架,需要自定义ViewModel工厂来传入Repository实例,示例如下:
class UserViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(UserViewModel::class.java)) { @Suppress("UNCHECKED_CAST") return UserViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
步骤3:精简MainActivity,仅保留UI逻辑
删除所有DataStore相关的定义和收集逻辑,仅监听ViewModel的状态更新UI即可:
class MainActivity : ComponentActivity() { // 扩展属性移到Application层级或者在这里定义即可,不需要参与业务逻辑 private val Context.userPreferencesDataStore: DataStore<Preferences> by preferencesDataStore( name = "user" ) private val viewModel: UserViewModel by viewModels { UserViewModelFactory(UserRepository(applicationContext.userPreferencesDataStore)) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launchWhenStarted { viewModel.user.collect { userState -> // 仅处理UI逻辑:登录成功跳主页/加载中展示加载框/错误时提示用户跳登录页 when(userState) { is Resource.Success -> { /* 登录成功UI逻辑 */ } is Resource.Error -> { /* 错误提示+跳登录页逻辑 */ } is Resource.Loading -> { /* 加载态UI逻辑 */ } } } } // 登录页点击登录时,直接调用viewModel.login(用户输入的User对象)即可 } // 原有的useUser方法全部删除 .... }
优化收益
- 符合单一职责原则:Activity仅处理UI交互,数据逻辑全部收敛到数据层和ViewModel,内聚性大幅提升
- 消除冗余逻辑:删除了Activity中两个不必要的Flow收集逻辑,减少冗余协程创建和性能开销
- 可测试性提升:ViewModel和Repository不直接依赖Activity上下文,可直接进行单元测试
注意:不要在DataStore中明文存储用户密码,建议搭配Jetpack Security等加密方案存储敏感凭证,避免数据泄露风险
内容的提问来源于stack exchange,提问作者mama
相关产品推荐
相关产品推荐

