我想实现Clean Architecture但是我在useCase中遇到了一些错误处理的麻烦。我不能处理从Usecase类抛出的异常,尽管其他基于库的异常已经成功处理。这意味着,当Firebase抛出任何异常时,它被成功处理并在Toast中显示异常文本。但是当我从Usecase类抛出任何异常时,比如
请同时填写
或
请给出邮箱
或
请给出密码
然后应用程序崩溃。那么我如何处理用例类的异常并在Toast中显示文本呢?
这是我试过的代码。
FirebaseAuthRepository.kt
interface FirebaseAuthRepository {
suspend fun register(email: String, password: String): Flow<User>
}
FirebaseAuthRepositoryImpl.kt
class FirebaseAuthRepositoryImpl @Inject constructor(
private val firebaseAuthDataSource: FirebaseAuthDataSource,
private val firebaseAuthMapper: FirebaseAuthMapper
) : FirebaseAuthRepository {
override suspend fun register(email: String, password: String): Flow<User> {
return flow {
emit(firebaseAuthDataSource.register(email, password))
}.map {
firebaseAuthMapper.toUser(it)
}
}
}
FirebaseAuthDataSource.kt
class FirebaseAuthDataSource @Inject constructor(private val firebaseAuth: FirebaseAuth) {
suspend fun register(email: String, password: String): FirebaseUser? {
val authResult: AuthResult = firebaseAuth.createUserWithEmailAndPassword(email, password).await()
return authResult.user
}
}
RegisterUseCase.kt
class RegisterUseCase @Inject constructor(
private val firebaseAuthRepository: FirebaseAuthRepository
) {
suspend fun execute(email: String, password: String): Flow<User> {
if (email.trim().isEmpty() && password.trim().isEmpty()) {
throw Exception("Please fill both field")
} else {
if (email.trim().isEmpty()) throw Exception("Please give email")
if (password.trim().isEmpty()) throw Exception("Please give password")
}
return firebaseAuthRepository.register(email, password)
}
}
LoginViewModel.kt
@HiltViewModel
class LoginViewModel @Inject constructor(
private val registerUseCase: RegisterUseCase
) : BaseViewModel() {
val register: MutableStateFlow<Resource<User>> = MutableStateFlow(Loading(true))
fun register(email: String, password: String) {
viewModelScope.launch {
loginUseCase.execute(email, password)
.flowOn(Dispatchers.IO)
.onStart { }
.onCompletion { register.value = Loading(false) }
.catch { register.value = Error(it) }
.collectLatest { register.value = Success(it) }
}
}
}
Resource.kt
sealed class Resource<out T> {
data class Loading(val isLoading: Boolean) : Resource<Nothing>()
data class Success<T>(val item: T) : Resource<T>()
data class Error(val throwable: Throwable) : Resource<Nothing>()
}
RegistrationFragment.kt
@AndroidEntryPoint
class RegistrationFragment : BaseFragment<FragmentRegisterBinding>() {
private val viewModel: RegistrationViewModel by viewModels()
override fun getViewBinding(
inflater: LayoutInflater,
container: ViewGroup?
): FragmentRegisterBinding {
return FragmentRegisterBinding.inflate(inflater, container, false)
}
override fun configureViews() {
binding?.registerButton?.setOnClickListener {
viewModel.register(
binding?.mailEditText?.value() ?: "",
binding?.passwordEditText?.value() ?: ""
)
}
}
override fun bindWithViewModel() {
lifecycleScope.launch {
viewModel.registration.collectLatest {
when(it) {
is Loading -> {
if (it.isLoading) {
} else {
}
}
is Success -> {
findNavController().navigate(R.id.home_fragment)
}
is Error -> {
requireContext().toast(it.throwable.message ?: "")
}
}
}
}
}
}
使用如下的CoroutineExceptionHandler
处理程序(您可以将其放入BaseViewModel中)
val handler = CoroutineExceptionHandler { _, exception ->
run {
toast.value = exception.message
exception.printStackTrace()
Log.e(TAG, "Caught $exception")
}
}
然后添加handler
到您的viewModel,其中viewModelScope.launch
fun register(email: String, password: String) {
viewModelScope.launch(handler) {
loginUseCase.execute(email, password)
.flowOn(Dispatchers.IO)
.onStart { }
.onCompletion { register.value = Loading(false) }
.catch { register.value = Error(it) }
.collectLatest { register.value = Success(it) }
}
}
最后我解决了这个问题。非常感谢@Rifan vai的回答。这里的代码,我试过了。
BaseViewmodel.kt
abstract class BaseViewModel : ViewModel() {
fun handleException(action: (Throwable)->Unit): CoroutineExceptionHandler {
return CoroutineExceptionHandler { _, exception ->
action(exception)
}
}
}
LoginViewModel.kt
@HiltViewModel
class LoginViewModel @Inject constructor(
private val loginUseCase: LoginUseCase
) : BaseViewModel() {
val login: MutableStateFlow<Resource<User>> = MutableStateFlow(Loading(true))
fun login(email: String, password: String) {
viewModelScope.launch(handleException { login.value = Error(it) }) {
loginUseCase.execute(email, password)
.flowOn(Dispatchers.IO)
.onCompletion { login.value = Loading(false) }
.collectLatest { login.value = Success(it) }
}
}
}