启动时为Room设置密码,密码错误时显示Compose表单如何实现?
实现带密码重试功能的Room数据库(Dagger-Hilt + Jetpack Compose)
你当前的Hilt配置是硬编码密码直接生成数据库实例,这种方式无法处理密码错误的重试场景。要实现密码错误时显示Compose输入表单重新尝试,需要重构数据库初始化逻辑,让密码可以动态传入,并结合ViewModel管理UI状态。
步骤1:重构Hilt依赖,改用数据库工厂类
不再直接通过Hilt提供MyDatabase单例,而是提供一个工厂类,允许动态传入密码创建数据库实例:
@Singleton class DatabaseFactory @Inject constructor(private val application: Application) { fun createDatabase(passphrase: ByteArray): MyDatabase { val factory = SupportFactory(passphrase) return Room.databaseBuilder(application, MyDatabase::class.java, "my_database") .openHelperFactory(factory) .build() // 正式环境请移除allowMainThreadQueries,改用协程处理数据库操作 } } @Module @InstallIn(SingletonComponent::class) object DatabaseModule { @Provides @Singleton fun provideDatabaseFactory(application: Application): DatabaseFactory { return DatabaseFactory(application) } }
步骤2:用ViewModel管理密码验证与状态
创建ViewModel负责处理密码输入、数据库初始化尝试、错误状态管理:
class DatabaseViewModel @Inject constructor(private val databaseFactory: DatabaseFactory) : ViewModel() { // UI状态:是否显示密码输入表单 val showPasswordForm = mutableStateOf(true) // 密码错误提示 val passwordError = mutableStateOf<String?>(null) // 成功初始化后的数据库实例 private var _database: MyDatabase? = null val database: MyDatabase? get() = _database fun tryPassword(password: String) { viewModelScope.launch(Dispatchers.IO) { try { val passphrase = SQLiteDatabase.getBytes(password.toCharArray()) val db = databaseFactory.createDatabase(passphrase) // 执行简单查询触发密码验证(Room构建时不会立即验证密码) db.yourDao().getItemCount() // 替换成你DAO中的实际方法,比如计数查询 // 验证成功,切回主线程更新UI withContext(Dispatchers.Main) { _database = db showPasswordForm.value = false passwordError.value = null } } catch (e: SQLiteException) { // 密码错误或数据库解密失败 withContext(Dispatchers.Main) { passwordError.value = "密码错误,请重试" _database = null } } catch (e: Exception) { withContext(Dispatchers.Main) { passwordError.value = "数据库初始化失败:${e.message}" } } } } }
步骤3:编写Compose UI实现密码输入与界面切换
根据ViewModel的状态,动态显示密码输入表单或主界面:
@Composable fun DatabaseScreen(viewModel: DatabaseViewModel = hiltViewModel()) { if (viewModel.showPasswordForm.value) { PasswordInputForm( onSubmit = viewModel::tryPassword, errorMessage = viewModel.passwordError.value ) } else { MainContent(database = viewModel.database!!) } } @Composable fun PasswordInputForm(onSubmit: (String) -> Unit, errorMessage: String?) { val passwordInput = remember { mutableStateOf("") } Column( modifier = Modifier .fillMaxSize() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { OutlinedTextField( value = passwordInput.value, onValueChange = { passwordInput.value = it }, label = { Text("输入数据库密码") }, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), modifier = Modifier.fillMaxWidth() ) Spacer(modifier = Modifier.height(16.dp)) Button( onClick = { onSubmit(passwordInput.value) }, enabled = passwordInput.value.isNotEmpty(), modifier = Modifier.fillMaxWidth() ) { Text("确认") } errorMessage?.let { Text( text = it, color = Color.Red, modifier = Modifier .fillMaxWidth() .padding(top = 8.dp) ) } } } @Composable fun MainContent(database: MyDatabase) { // 数据库验证成功后的主界面逻辑,比如展示数据 Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text("数据库已成功解锁!") // 这里可以添加查询数据并展示的逻辑 } }
关键注意事项
- 不要硬编码密码:通过工厂类动态传入密码,才能支持多次重试不同密码。
- 必须触发数据库操作:Room的
build()方法不会立即验证密码,只有执行实际DAO操作时才会解密数据库并校验密码,所以要在tryPassword中执行一个简单查询。 - 线程安全:数据库操作必须在IO线程执行,通过
Dispatchers.IO切换,更新UI时切回主线程。 - 状态管理:用ViewModel的
mutableStateOf管理UI状态,确保Compose能自动响应状态变化。
内容的提问来源于stack exchange,提问作者EoinKanro
相关产品推荐
相关产品推荐

