- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
上下文
我开始从事一个新项目,并决定从 RxJava 迁移到 Kotlin Coroutines。我正在使用 MVVM 干净架构,这意味着我的 ViewModels
联系 UseCases
类,以及这些 UseCases
类使用一个或多个 Repositories
从网络中获取数据。
让我给你举个例子。假设我们有一个应该显示用户个人资料信息的屏幕。所以我们有 UserProfileViewModel
:
@HiltViewModel
class UserProfileViewModel @Inject constructor(
private val getUserProfileUseCase: GetUserProfileUseCase
) : ViewModel() {
sealed class State {
data SuccessfullyFetchedUser(
user: ExampleUser
) : State()
}
// ...
val state = SingleLiveEvent<UserProfileViewModel.State>()
// ...
fun fetchUserProfile() {
viewModelScope.launch {
// ⚠️ We trigger the use case to fetch the user profile info
getUserProfileUseCase()
.collect {
when (it) {
is GetUserProfileUseCase.Result.UserProfileFetched -> {
state.postValue(State.SuccessfullyFetchedUser(it.user))
}
is GetUserProfileUseCase.Result.ErrorFetchingUserProfile -> {
// ...
}
}
}
}
}
}
GetUserProfileUseCase
用例看起来像这样:
interface GetUserProfileUseCase {
sealed class Result {
object ErrorFetchingUserProfile : Result()
data class UserProfileFetched(
val user: ExampleUser
) : Result()
}
suspend operator fun invoke(email: String): Flow<Result>
}
class GetUserProfileUseCaseImpl(
private val userRepository: UserRepository
) : GetUserProfileUseCase {
override suspend fun invoke(email: String): Flow<GetUserProfileUseCase.Result> {
// ⚠️ Hit the repository to fetch the info. Notice that if we have more
// complex scenarios, we might require zipping repository calls together, or
// flatmap responses.
return userRepository.getUserProfile().flatMapMerge {
when (it) {
is ResultData.Success -> {
flow { emit(GetUserProfileUseCase.Result.UserProfileFetched(it.data.toUserExampleModel())) }
}
is ResultData.Error -> {
flow { emit(GetUserProfileUseCase.Result.ErrorFetchingUserProfile) }
}
}
}
}
}
UserRepository
存储库看起来像这样:
interface UserRepository {
fun getUserProfile(): Flow<ResultData<ApiUserProfileResponse>>
}
class UserRepositoryImpl(
private val retrofitApi: RetrofitApi
) : UserRepository {
override fun getUserProfile(): Flow<ResultData<ApiUserProfileResponse>> {
return flow {
val response = retrofitApi.getUserProfileFromApi()
if (response.isSuccessful) {
emit(ResultData.Success(response.body()!!))
} else {
emit(ResultData.Error(RetrofitNetworkError(response.code())))
}
}
}
}
最后,
RetrofitApi
为后端 API 响应建模的响应类如下所示:
data class ApiUserProfileResponse(
@SerializedName("user_name") val userName: String
// ...
)
interface RetrofitApi {
@GET("api/user/profile")
suspend fun getUserProfileFromApi(): Response<ApiUserProfileResponse>
}
到目前为止一切正常,但是在实现更复杂的功能时我开始遇到一些问题。
POST /send_email_link
用户首次登录时的端点,该端点将检查我在正文中发送的电子邮件是否已经存在,如果不存在,它将返回
404
错误代码和
(2) 如果一切顺利,我应该点击
POST /peek
将返回有关用户帐户的一些信息的端点。
UserAccountVerificationUseCase
实现的。 :
interface UserAccountVerificationUseCase {
sealed class Result {
object ErrorVerifyingUserEmail : Result()
object ErrorEmailDoesNotExist : Result()
data class UserEmailVerifiedSuccessfully(
val canSignIn: Boolean
) : Result()
}
suspend operator fun invoke(email: String): Flow<Result>
}
class UserAccountVerificationUseCaseImpl(
private val userRepository: UserRepository
) : UserAccountVerificationUseCase {
override suspend fun invoke(email: String): Flow<UserAccountVerificationUseCase.Result> {
return userRepository.postSendEmailLink().flatMapMerge {
when (it) {
is ResultData.Success -> {
userRepository.postPeek().flatMapMerge {
when (it) {
is ResultData.Success -> {
val canSignIn = it.data?.userName == "Something"
flow { emit(UserAccountVerificationUseCase.Result.UserEmailVerifiedSuccessfully(canSignIn)) }
} else {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
}
}
}
is ResultData.Error -> {
if (it.exception is RetrofitNetworkError) {
if (it.exception.errorCode == 404) {
flow { emit(UserAccountVerificationUseCase.Result.ErrorEmailDoesNotExist) }
} else {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
} else {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
}
}
}
}
}
问题
POST /send_email_link
的第一次 API 调用,上述解决方案按预期工作。曾经返回 404,用例将按预期运行并返回
ErrorEmailDoesNotExist
回应所以
ViewModel
可以将其传递回 UI 并显示预期的 UX。
...
override fun getUserProfile(): Flow<ResultData<ApiUserProfileResponse>> {
return flow {
val response = retrofitApi.getUserProfileFromApi()
if (response.isSuccessful) {
emit(ResultData.Success(response.body()!!))
} else {
emit(ResultData.Error(RetrofitNetworkError(response.code())))
}
}
}
...
对于这样的事情:
...
override fun getUserProfile(): Flow<ResultData<ApiUserProfileResponse>> {
return flow {
val response = retrofitApi.getUserProfileFromApi()
if (response.isSuccessful) {
emit(ResultData.Success(response.body()!!))
} else {
error(RetrofitNetworkError(response.code()))
}
}
}
..
所以我可以使用
catch()
像我使用 RxJava 的
onErrorResume()
一样的功能:
class UserAccountVerificationUseCaseImpl(
private val userRepository: UserRepository
) : UserAccountVerificationUseCase {
override suspend fun invoke(email: String): Flow<UserAccountVerificationUseCase.Result> {
return userRepository.postSendEmailLink()
.catch { e ->
if (e is RetrofitNetworkError) {
if (e.errorCode == 404) {
flow { emit(UserAccountVerificationUseCase.Result.ErrorEmailDoesNotExist) }
} else {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
} else {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
}
.flatMapMerge {
userRepository.postPeek().flatMapMerge {
when (it) {
is ResultData.Success -> {
val canSignIn = it.data?.userName == "Something"
flow { emit(UserAccountVerificationUseCase.Result.UserEmailVerifiedSuccessfully(canSignIn)) }
} else -> {
flow { emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail) }
}
}
}
}
}
}
}
这确实减少了样板代码,但我无法让它工作,因为一旦我尝试运行这样的用例,我就会开始收到错误消息,说我不应该在
catch()
中发出项目。 .
...
class UserAccountVerificationUseCaseImpl(
private val userRepository: AuthRepository
) : UserAccountVerificationUseCase {
override suspend fun invoke(email: String): Flow<UserAccountVerificationUseCase.Result> {
return flow {
coroutineScope {
val sendLinksResponse = userRepository.postSendEmailLink()
if (sendLinksResponse is ResultData.Success) {
val peekAccount = userRepository.postPeek()
if (peekAccount is ResultData.Success) {
emit(UserAccountVerificationUseCase.Result.UserEmailVerifiedSuccessfully())
} else {
emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail)
}
} else {
if (sendLinksResponse is ResultData.Error) {
if (sendLinksResponse.error == 404) {
emit(UserAccountVerificationUseCase.Result.ErrorEmailDoesNotExist)
} else {
emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail)
}
} else {
emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail)
}
}
}
}
}
}
...
这就是我对使用 Kotlin Coroutines 的想象。抛弃 RxJava 的
zip()
,
contact()
,
delayError()
,
onErrorResume()
以及所有这些
Observable
函数支持更易读的东西。
ViewModel
调用存储库。层,但我喜欢这个
UseCase
中间的层,所以我可以在这里包含与切换流和处理错误相关的所有代码。
data class ApiUserProfileResponse(
@SerializedName("user_name") val userName: String
// ...
)
interface RetrofitApi {
@GET("api/user/profile")
suspend fun getUserProfileFromApi(): Response<ApiUserProfileResponse>
}
存储库现在返回一个可挂起的函数,我删除了
Flow
包装:
interface UserRepository {
suspend fun getUserProfile(): ResultData<ApiUserProfileResponse>
}
class UserRepositoryImpl(
private val retrofitApi: RetrofitApi
) : UserRepository {
override suspend fun getUserProfile(): ResultData<ApiUserProfileResponse> {
val response = retrofitApi.getUserProfileFromApi()
return if (response.isSuccessful) {
ResultData.Success(response.body()!!)
} else {
ResultData.Error(RetrofitNetworkError(response.code()))
}
}
}
用例不断返回
Flow
因为我也可能在这里插入对 Room DB 的调用:
interface GetUserProfileUseCase {
sealed class Result {
object ErrorFetchingUserProfile : Result()
data class UserProfileFetched(
val user: ExampleUser
) : Result()
}
suspend operator fun invoke(email: String): Flow<Result>
}
class GetUserProfileUseCaseImpl(
private val userRepository: UserRepository
) : GetUserProfileUseCase {
override suspend fun invoke(email: String): Flow<GetUserProfileUseCase.Result> {
return flow {
val userProfileResponse = userRepository.getUserProfile()
when (userProfileResponse) {
is ResultData.Success -> {
emit(GetUserProfileUseCase.Result.UserProfileFetched(it.toUserModel()))
}
is ResultData.Error -> {
emit(GetUserProfileUseCase.Result.ErrorFetchingUserProfile)
}
}
}
}
}
这看起来更干净。现在,对
UserAccountVerificationUseCase
应用相同的内容:
interface UserAccountVerificationUseCase {
sealed class Result {
object ErrorVerifyingUserEmail : Result()
object ErrorEmailDoesNotExist : Result()
data class UserEmailVerifiedSuccessfully(
val canSignIn: Boolean
) : Result()
}
suspend operator fun invoke(email: String): Flow<Result>
}
class UserAccountVerificationUseCaseImpl(
private val userRepository: UserRepository
) : UserAccountVerificationUseCase {
override suspend fun invoke(email: String): Flow<UserAccountVerificationUseCase.Result> {
return flow {
val sendEmailLinkResponse = userRepository.postSendEmailLink()
when (sendEmailLinkResponse) {
is ResultData.Success -> {
val peekResponse = userRepository.postPeek()
when (peekResponse) {
is ResultData.Success -> {
val canSignIn = peekResponse.data?.userName == "Something"
emit(UserAccountVerificationUseCase.Result.UserEmailVerifiedSuccessfully(canSignIn)
}
else -> {
emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail)
}
}
}
is ResultData.Error -> {
if (sendEmailLinkResponse.isNetworkError(404)) {
emit(UserAccountVerificationUseCase.Result.ErrorEmailDoesNotExist)
} else {
emit(UserAccountVerificationUseCase.Result.ErrorVerifyingUserEmail)
}
}
}
}
}
}
这看起来更干净,而且效果很好。我仍然想知道这里是否还有改进的空间。
最佳答案
我在这里看到的最明显的问题是您正在使用 Flow
对于单个值而不是 suspend
功能。
协程通过使用返回普通值或抛出异常的挂起函数使单值用例变得更加简单。你当然也可以让他们返回 Result
类来封装错误而不是实际使用异常,但重要的部分是使用 suspend
您正在公开看似同步(因此很方便)的 API 的函数,同时仍受益于异步运行时。
在提供的示例中,您没有在任何地方订阅更新,所有流实际上只提供一个元素并且是完整的,因此没有真正的理由使用流并且它使代码复杂化。它还使习惯于协程的人更难阅读,因为它看起来像多个值即将到来,并且可能 collect
是无限的,但事实并非如此。
每次写flow { emit(x) }
它应该只是 x
.
根据上述内容,您有时会使用 flatMapMerge
在 lambda 中,您使用单个元素创建流。除非您正在寻找计算的并行化,否则您应该直接选择 .map { ... }
。反而。所以替换这个:
val resultingFlow = sourceFlow.flatMapMerge {
if (something) {
flow { emit(x) }
} else {
flow { emit(y) }
}
}
有了这个:
val resultingFlow = sourceFlow.map { if (something) x else y }
关于android - 与 Kt Flow 和 Retrofit 的用例或交互,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70246942/
在 Kotlin 中使用 .kts 文件的目的是什么?发布应用程序时,这些文件是否包含在应用程序包中? 最佳答案 .kt — 正常源文件,.kts — 脚本文件 您不需要 main .kts 中的函数
我最近一直在使用 kotlin arrow,但遇到了一个让我陷入困境的特定用例。 假设我有一些对象的集合,我想使用转换函数将其转换为另一种数据类型。假设这个 convert 函数有失败的能力——但它不
我很难找到一种很好的方法来协调使用RxJava以及arrow-kt的Either和Option类型。我有两种方法都返回Single class Foo(val qux: Option) class B
是否可以获取 .kt 文件中的类列表?看来 KDeclarationContainer 是我正在寻找的类,但我不确定如何使用它。 编辑:我正在使用 Kotlin Reflection。 最佳答案 不可
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 这个问题似乎不是关于 a specific programming problem, a softwar
在我的一个 Android Studio Kotlin 项目中,在项目资源管理器的 Activity 文件夹中,有时显示 .KT 有时不显示。 Android Studio 使用什么标准来显示或隐藏
我想将所有与主题相关的 xml 代码移动到撰写文件中。有一个闪屏 xml 代码,是否可以让它以 compose 样式显示? @drawable/splash_screen @Compos
我想在 java.util.function 中使用一些功能接口(interface)像 DoubleBinaryOperator 这样的包界面。 我可以在Java中使用它,如下所示: public
我正在尝试更新 firebase_messaging到新版本8.0.0-dev.10 . 但是,我收到以下错误: Running Gradle task 'assembleDebug'... Appl
我试图在 Kotlin 文档中找到有关此决定的更多详细信息,但没有任何部分“谈论”它。有谁知道是什么促使决定在 Kotlin 的类名中使用“Kt”后缀?是为了避免与 Java 类名冲突,还是还有其他原
我有以下接口(interface): interface UserRepository { fun role(codename: String): IO> fun accessRights(r
我正在尝试使用Option.getOrElse()方法。 根据消息来源: inline fun fold(ifEmpty: () -> R, ifSome: (A) -> R): R = when
我今天打开我的 Studio,Android Studio 无法识别 Kotlin 文件。 .kt 文件开始显示为普通文件。我检查了我的插件,我仍然有 Kotlin 插件。我尝试卸载并重新安装它。但是
上下文 我开始从事一个新项目,并决定从 RxJava 迁移到 Kotlin Coroutines。我正在使用 MVVM 干净架构,这意味着我的 ViewModels联系 UseCases类,以及这些
我的 Application.kt 文件是这样的。找不到更改 PluginRegistrantCallback 和 FlutterFirebaseMessagingService 的方法 import
我正在努力学习 Arrow-Kt因为我对 Functional Programming in Kotlin 感到好奇. 有人能解释一下为什么需要创建另一个功能与 Kotlin Coroutines 几
EDIT: I NEED TO PASS A CONTEXT AS A PARAMETER TO THE CLASS(DataStore 和 repository 是同一类,不要混淆) 我有两个 Ac
我正在尝试在一个新项目中使用底部导航实现 NavController。这是我的第一次尝试,到处都有很多关于此的模棱两可的信息。 所以我的问题涉及每个底部选项卡都有自己的返回堆栈并在底部导航点击之间保留
我正在使用函数库 arrow-kt 进行编程(以前称为 kategory)。我正在使用 Either monad 用于聚合 api 调用的错误或成功信息。我让自己进入了一个状态(这首先不应该发生)我有
在 Android 中,网络操作通常在 ViewModel 内完成。 .这确保即使 Activity或 Fragment重新创建(例如,当设备旋转时),网络调用会继续进行并且不会被取消。 现在提交来自
我是一名优秀的程序员,十分优秀!