在 Android 中,无法处理从 Clean Architecture 中的用例抛出的异常
In Android, can't handle exception thrown from Use-case in Clean Architecture
我想在我最近的应用程序中实施 Clean Architecture。但是我在用例中遇到了一些错误处理的麻烦。尽管成功处理了其他基于库的异常,但我无法处理从 Usecase class 抛出的异常。这意味着,当 Firebase 抛出任何异常时,它都会被成功处理并在 Toast 中显示异常文本。但是当我从 Usecase class 中抛出任何异常时,比如
Please fill both field
或
Please give email
或
Please give password
然后应用程序崩溃了。那么如何处理用例异常 class 并在 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) }
}
}
}
我想在我最近的应用程序中实施 Clean Architecture。但是我在用例中遇到了一些错误处理的麻烦。尽管成功处理了其他基于库的异常,但我无法处理从 Usecase class 抛出的异常。这意味着,当 Firebase 抛出任何异常时,它都会被成功处理并在 Toast 中显示异常文本。但是当我从 Usecase class 中抛出任何异常时,比如
Please fill both field
或
Please give email
或
Please give password
然后应用程序崩溃了。那么如何处理用例异常 class 并在 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) }
}
}
}