我已经在流内部发出异常,并在异常之下。
IllegalStateException: Flow exception transparency is violated:
Previous 'emit' call has thrown exception java.lang.NullPointerException, but then emission attempt of value 'planetbeyond.domain.api.Resource$Error@85b4d28' has been detected.
Emissions from 'catch' blocks are prohibited in order to avoid unspecified behaviour, 'Flow.catch' operator can be used instead.
For a more detailed explanation, please refer to Flow documentation.
at kotlinx.coroutines.flow.internal.SafeCollector.exceptionTransparencyViolated(SafeCollector.kt:140)
at kotlinx.coroutines.flow.internal.SafeCollector.checkContext(SafeCollector.kt:104)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:83)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:66)
at planetbeyond.domain.use_cases.OptionSelectedCountUsecase$invoke$1.invokeSuspend(OptionSelectedCountUsecase.kt:20)
所选选项计数用例.kt
class OptionSelectedCountUsecase @Inject constructor(
private val repository: Repository
) {
operator fun invoke(questionId: Int): Flow<Resource<List<OptionSelectedCountModel>>> = flow {
emit(Resource.Loading())
try {
val data = repository.getOptionSelectedCount(questionId)
emit(Resource.Success(data))
} catch (e: Exception) {
emit(Resource.Error(e.toString()))// crashed at this line when api don't response anything or some sort of server error
}
}
}
存储库.kt
interface Repository{
suspend fun getOptionSelectedCount(questionId: Int):List<OptionSelectedCountModel>
}
存储库实施kt
class RepositoryImpl @Inject constructor(
private val apiService: ApiService
) : Repository {
override suspend fun getOptionSelectedCount(questionId: Int): List<OptionSelectedCountModel> {
return apiService.getOptionSelectedCount(questionId).data.map {
it.toModel()
}
}
}
应用程序服务.kt
interface ApiService {
@GET("get_option_selected_count")
suspend fun getOptionSelectedCount(
@Query("question_id") question_id: Int
): WebResponse<List<OptionSelectedCountDto>>
}
实时显示问题视图模型.kt
@HiltViewModel
class LiveShowQuestionsViewModel @Inject constructor(
private val optionSelectedCountUsecase: OptionSelectedCountUsecase
) : ViewModel() {
fun getOptionSelectedCount(questionId: Int) {
optionSelectedCountUsecase(questionId).onEach {
when (it) {
is Resource.Loading -> {
_optionSelectedCountState.value = OptionSelectedCountState(isLoading = true)
}
is Resource.Error -> {
_optionSelectedCountState.value = OptionSelectedCountState(error = it.message)
}
is Resource.Success -> {
_optionSelectedCountState.value = OptionSelectedCountState(data = it.data)
}
}
}///.catch { } // Why must I have to handle it here
.launchIn(viewModelScope)
}
}
有必要像上面评论的那样在流外处理异常吗?最佳实践是什么?
3条答案
按热度按时间ewm0tg9j1#
问题是你在
try
中 Package 了一个emit
调用,并试图在匹配的catch
块中发出。这意味着如果emit
调用本身抛出(这可能是由流的一些下游问题引起的),它将被指示再次发出。这是非常模糊和脆弱的行为。相反,您可以将emit调用移到try/catch之外:
不知何故,你在你的收集器中引起了一个NullPointerException,这是一个单独的问题需要解决。
ttygqcqt2#
根本问题是你的
抛出一个异常。当你捕捉到这个异常时,你仍然在"emit"块中,你正在尝试
这就像发射中的发射。所以这是错的。
但是让我们后退一步。为什么在第一次发射时会出现异常?看起来这个数据对象没有正确填充数据,可能是因为你提到的问题(错误响应等),在它到达收集器后会出现空指针异常。
所以基本流程应该是
1.尝试进行调用,并捕获http/parsing异常(如果存在异常)(发射失败)
1.如果没有异常,则验证对象是否包含正确的字段。如果数据不一致,则发出错误
1.如果一切正常,发射成功
例如:
理想情况下,应该将其拆分为以下几个部分,以免干扰emit的异常捕获:
hmae6n7t3#
您不应该手动发出异常和错误。否则,如果不检查发出的值是否为错误,流的用户将不知道异常是否实际发生。
您希望提供异常透明性,因此最好在收集流时处理它们。
其中一种方法是使用
catch
操作符,为了简化流收集,我们将捕获行为 Package 在一个函数中。然后,在收集流量时:
注意,如果你只想处理调用用例时的错误,你可以改变操作符的顺序。之前的顺序也允许你处理
onEach
块的错误。下面的例子只处理调用用例时的错误。Read more about exception handling in flows