【问题标题】:Clean Architecture UseCase with Progress[No RxJava]有进步的清洁架构用例[无 RxJava]
【发布时间】:2020-03-02 00:09:52
【问题描述】:

我在试图找出实现带有进度的 UseCase 的最佳方法时遇到了问题。我见过这样的例子:

Google I/O Android 应用 https://github.com/google/iosched/blob/master/shared/src/main/java/com/google/samples/apps/iosched/shared/domain/UseCase.kt

我不喜欢的有以下几点:

  • 您将在 UI 中接收结果并在那里做出许多决定。
  • 没有进展

我认为是对上述内容的升级:

Android 架构蓝图

https://github.com/android/architecture-samples/blob/usecases/app/src/main/java/com/example/android/architecture/blueprints/todoapp/domain/ActivateTasksUseCase.kt

他们正在使用协程,现在结果在 ViewModel 中,而且好多了。但同样:没有进展

我的问题是每个人都在使用 RxJava,因为其他人都在使用它。我看到很多人认为这是“在后台执行任务”。但这对我来说太多了。我不需要它。

我见过一些使用协程通道的例子,但它们真的很丑。

最近我偶然发现了 Roman Elizarov 的这篇文章:

https://medium.com/@elizarov/callbacks-and-kotlin-flows-2b53aa2525cf

我做了这样的事情:

class ProduceValueWithProgressUseCase @Inject constructor(
    private val executor: Executor
) {

    operator fun invoke(): Flow<Result<Int, Int>> {
        return callbackFlow {
            val callback = object : CallbackProgress {
                override fun onProgress(result: Int) {
                    offer(Result.Loading(result))
                }

                override fun onSuccess(result: Int) {
                    offer(Result.Success(result))
                    close()
                }

                override fun onError(e: Exception) {
                    offer(Result.Error(e))
                    close()
                }

            }

            val producer = ValueWithProgressProducer(callback)
            executor.execute(producer)
            awaitClose {}
        }
    }

}

这个想法是这个东西“生产者”使用回调来传播数据,就像大多数“旧”API一样。因此,我想通过流将代码传播到 ViewModel,而不是将回调放在那里。像这样:

viewModelScope.launch {
            produceValueWithProgressUseCase().collect {
                when (it) {
                    is Success -> view.showWithProgressResult(it.data)
                    is Loading -> view.updateProgress(it.progress)
                    else -> view.showError()
                }
            }
        }

所以是的,基本上 Flows API 将为我完成这项工作。我什至创建了一个小应用程序来测试它,我只是在其中生成数字,它运行良好。我不喜欢它的是:

  • 必须有太多的 ExperimentalCoroutinesApi 注释。例如这里(对不起格式):

    @Suppress("NOTHING_TO_INLINE") @ExperimentalCoroutinesApi 公共内联乐趣回调流(@BuilderInference noinline 块:挂起 ProducerScope.() -> Unit): Flow = 通道流(块)

以上代码是文件的一部分:kotlinx.coroutines.flow.Builders.kt 来自版本:kotlinx-coroutines-core-1.3.2

  • 在某些时候,我遇到了带有 @Preview 注释的内容。(老实说,不记得在哪里。这是我删除的内容。)
  • 我也尝试了一点,看看测试将如何进行,但这并不简单。您可以在 Blueprinst 的代码中看到相同的内容。
  • 我还将执行任务的代码和任务本身混合在一起。我的意思是callbackFlow()的用法。

所以最后我看到了一些看起来像是在尖叫着明年改变的事情。所以请发表你的想法。

【问题讨论】:

  • 你能发布一些代码sn-ps吗? Flow 自 2019 年 8 月以来一直保持稳定。一些转换和终端运算符处于实验和预览阶段
  • @GiorgosNeokleous 你在这里。
  • 确实callbackFlow 不稳定。但是你检查一下稳定的 API,我很确定你可以在没有 callbackFlow 的情况下实现你正在寻找的东西,而不是简单的旧 Flows。不幸的是,我不在我的机器上玩一个例子
  • 我留下了一个粗略的实现希望有帮助!

标签: android kotlin kotlin-coroutines clean-architecture


【解决方案1】:

因此,我将根据您在问题描述中的内容向您展示一个实现,请随时提出更多问题,甚至提出改进建议。

当前的实现基于 Kotlin Coroutines 1.3.2 的稳定版本。

回调接口

interface CallbackProgress {
    suspend fun onProgress(result: Int)
    suspend fun onSuccess(result: Int)
    suspend fun onError(exception: Exception)
}

制片人 迭代并执行一些回调的方法。试图模仿你的。

class Producer(private val callback: CallbackProgress) {
    suspend fun execute(fail: Boolean) {
        (0 until 10).forEach {
            when {
                it < 9 -> callback.onProgress(it)
                fail -> callback.onError(InterruptedException("blabla"))
                else -> callback.onSuccess(it)
            }
            delay(500)
        }
    }
}

sealed class State {
    class Success(val value: Int) : State()
    class Loading(val progress: Int) : State()
    class Error(val exception: Exception) : State()
}

交互者 您需要在这里小心,因为排放应该在同一个协程中完成,否则您将需要使用不稳定的 API,例如 channelFlow

class UseCase {
    operator fun invoke(fail: Boolean) = flow {
        val callback = object : CallbackProgress {
            override suspend fun onSuccess(result: Int) {
                withContext(coroutineContext) { emit(State.Success(result)) }
            }

            override suspend fun onError(exception: Exception) {
                withContext(coroutineContext) { emit(State.Error(exception)) }
            }

            override suspend fun onProgress(result: Int) {
                withContext(coroutineContext) { emit(State.Loading(result)) }
            }
        }

        Producer(callback).execute(fail)
    }
}

为了测试上述内容,我编写了以下内容,演示了故障和非故障排放。

fun main() = runBlocking {
    val useCase = UseCase()
    useCase(true).collect {
        when (it) {
            is State.Loading -> println("State for failure [Loading -> ${it.progress}]")
            is State.Success -> println("State for failure [Success -> ${it.value}]")
            is State.Error -> println("State for failure [Error -> ${it.exception.message}]")
        }
    }
    useCase(false).collect {
        when (it) {
            is State.Loading -> println("State  without failure [Loading -> ${it.progress}]")
            is State.Success -> println("State without failure [Success -> ${it.value}]")
            is State.Error -> println("State without failure [Error -> ${it.exception.message}]")
        }
    }
}

输出

State for failure [Loading -> 1]
State for failure [Loading -> 2]
State for failure [Loading -> 3]
State for failure [Loading -> 4]
State for failure [Loading -> 5]
State for failure [Loading -> 6]
State for failure [Loading -> 7]
State for failure [Loading -> 8]
State for failure [Error -> blabla]

-------------------------------------

State  without failure [Loading -> 0]
State  without failure [Loading -> 1]
State  without failure [Loading -> 2]
State  without failure [Loading -> 3]
State  without failure [Loading -> 4]
State  without failure [Loading -> 5]
State  without failure [Loading -> 6]
State  without failure [Loading -> 7]
State  without failure [Loading -> 8]
State without failure [Success -> 9]

【讨论】:

    猜你喜欢
    • 2022-05-18
    • 1970-01-01
    • 2021-10-16
    • 2017-03-20
    • 2018-06-15
    • 2022-10-23
    • 2016-05-07
    • 2016-09-17
    • 2018-04-24
    相关资源
    最近更新 更多