You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Room插入错误处理:如何在ViewModel捕获重复邮箱异常

解决Android Room插入用户时异常通知的问题

要让ViewModel知晓插入失败的原因并向用户提示邮箱已存在,可以通过以下步骤实现:

1. 重构Repository,让插入操作结果可被ViewModel感知

把Repository中的insertUser改成suspend函数,移除内部独立的CoroutineScope,让ViewModel在自身协程作用域中调用,这样异常可以直接传递到ViewModel层处理:

class UserRepository(private val appDatabase: AppDatabase) {
    val tag = "UserRepository"

    suspend fun insertUser(user: User) {
        appDatabase.userDao().insertUser(user)
    }
}

2. 在ViewModel中捕获异常并通知UI

ViewModel使用viewModelScope调用Repository的suspend函数,捕获SQLiteException后判断是否为邮箱唯一约束冲突,再通过LiveData/StateFlow将错误状态传递给UI:

首先在ViewModel中定义用于通知UI的错误状态:

class YourViewModel(private val userRepository: UserRepository) : ViewModel() {
    val insertError = MutableLiveData<String?>()

    fun setupUser() {
        // 保留原有User对象创建逻辑
        val serviceDate =
            DateUtils.stringToMillis(serviceInDate.value ?: DateUtils.getFutureDateInString(4))
        val oilDate =
            DateUtils.stringToMillis(oilInDate.value ?: DateUtils.getFutureDateInString(4))
        val lubeDate =
            DateUtils.stringToMillis(lubeInDate.value ?: DateUtils.getFutureDateInString(1))
        val user = User(
            name = name,
            email = email,
            photoUrl = null,
            lubingIntervalInKms = (lubeInKms.value ?: "300").toInt(),
            lubingIntervalInDate = lubeDate,
            serviceIntervalInKms = (serviceInKms.value ?: "4000").toInt(),
            serviceIntervalInDate = serviceDate,
            oilingIntervalInKms = (oilInKms.value ?: "5000").toInt(),
            oilingIntervalInDate = oilDate,
        )

        viewModelScope.launch {
            try {
                userRepository.insertUser(user)
                // 插入成功后的逻辑(如页面跳转)
            } catch (e: SQLiteException) {
                // 通过异常信息判断是否为唯一约束冲突
                val errorMsg = if (e.message?.contains("UNIQUE constraint failed") == true) {
                    "该邮箱地址已存在"
                } else {
                    "插入失败:${e.message}"
                }
                insertError.postValue(errorMsg)
            }
        }
    }
}

3. UI层观察错误状态并显示提示

在Activity/Fragment中观察ViewModel的insertError状态,收到错误信息时展示给用户:

viewModel.insertError.observe(this) { errorMsg ->
    errorMsg?.let {
        Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
        viewModel.insertError.postValue(null) // 重置错误状态,避免重复提示
    }
}

可选:用密封类封装操作结果(更规范的状态管理)

如果需要更清晰的状态区分,可以定义密封类统一处理成功/失败结果:

sealed class InsertResult {
    object Success : InsertResult()
    data class Error(val message: String) : InsertResult()
}

修改Repository函数返回该密封类:

suspend fun insertUser(user: User): InsertResult {
    return try {
        appDatabase.userDao().insertUser(user)
        InsertResult.Success
    } catch (e: SQLiteException) {
        val msg = if (e.message?.contains("UNIQUE constraint failed") == true) {
            "该邮箱地址已存在"
        } else {
            "插入失败:${e.message}"
        }
        InsertResult.Error(msg)
    }
}

ViewModel中调用时:

viewModelScope.launch {
    when(val result = userRepository.insertUser(user)) {
        is InsertResult.Success -> {
            // 处理插入成功逻辑
        }
        is InsertResult.Error -> {
            insertError.postValue(result.message)
        }
    }
}

关键说明

  • 移除Repository内部的CoroutineScope(Dispatchers.IO):Room的suspend DAO方法会自动切换到IO线程执行,ViewModel的viewModelScope默认绑定主线程,无需手动指定线程。
  • 唯一约束冲突识别:SQLite抛出的异常信息会包含UNIQUE constraint failed关键字,可通过该特征精准判断邮箱重复问题。
  • 状态传递:使用LiveData/StateFlow确保UI能实时接收错误通知,符合Android组件间通信规范。

内容的提问来源于stack exchange,提问作者Sam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 11:10:11