何时在 Kotlin 中一起使用挂起函数和 Flow,何时分开使用?

huangapple go评论57阅读模式
英文:

When to use suspend function and Flow together or seperate in kotlin?

问题

In reviewing Kotlin code, I noticed different usage patterns of suspend functions and Flow in the domain layer across different projects. Some projects use both together, some only use Flow, and others only use suspend functions.

The reason for these variations can be attributed to design and use case requirements. Here's a detailed explanation:

  1. suspend and Flow together:

    • This approach is suitable when you want to represent an asynchronous stream of data that can emit multiple values over time. Flow is used to handle the asynchronous data flow, while suspend functions are used when you need to perform a single asynchronous operation within a Flow.
  2. just Flow:

    • In cases where you only need to work with asynchronous data streams and don't have specific one-time asynchronous operations to perform, using just Flow is a cleaner and more concise choice. It simplifies the code by avoiding the use of suspend functions.
  3. just suspend:

    • When you have a use case that involves sequential asynchronous operations, using only suspend functions might be more straightforward. In the example provided, it fetches news articles one by one, making it a linear, sequential process.

The choice between these approaches depends on the nature of the task and the desired code simplicity. Combining suspend functions and Flow allows for more flexibility when dealing with asynchronous operations and data streams. However, using just Flow or suspend functions can make the code more focused and easier to understand for specific use cases.

Ultimately, the decision comes down to the specific requirements of the project and the developer's preference for code organization and readability.

英文:

While reviewing some code written in kotlin ,something caught my attention. I was looking about domain layer in some projects and in some of the projects, I saw that the suspend function and Flow were used together, and in some of the projects, I saw that only Flow was used.

for example suspend and Flow together :

class FetchMovieDetailFlowUseCase @Inject constructor(
    private val repository: MovieRepository
) : FlowUseCase<FetchMovieDetailFlowUseCase.Params, State<MovieDetailUiModel>>() {

    data class Params(val id: Long)

    override suspend fun execute(params: Params): Flow<State<MovieDetailUiModel>> =
        repository.fetchMovieDetailFlow(params.id)
}

just Flow

class GetCoinUseCase @Inject constructor(
    private val repository: CoinRepository
){
 
    operator fun invoke(coinId:String): Flow<Resource<CoinDetail>> = flow {

        try {
            emit(Resource.Loading())
            emit(Resource.Success(coin))

        }catch (e:HttpException){
            emit(Resource.Error(e.localizedMessage ?: "An unexpected error occured"))
        }catch (e:IOException){
            emit(Resource.Error("Couldn't reach server. Check your internet connection."))
        }
    }
}

just suspend

class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository,
  private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    suspend operator fun invoke(): List<ArticleWithAuthor> =
        withContext(defaultDispatcher) {
            val news = newsRepository.fetchLatestNews()
            val result: MutableList<ArticleWithAuthor> = mutableListOf()
            // This is not parallelized, the use case is linearly slow.
            for (article in news) {
                // The repository exposes suspend functions
                val author = authorsRepository.getAuthor(article.authorId)
                result.add(ArticleWithAuthor(article, author))
            }
            result
        }
}

All three are different projects, don't get stuck with the codes, these are just the projects I've come across, I'm sharing to show examples, but what I want to draw your attention to here is that sometimes only the suspend function is used, sometimes only Flow is used, and sometimes both are used. What is the reason of this ? can you explain in detail ?
I'm trying to make this into my logic

答案1

得分: 12

suspend函数在以暂停、同步的方式检索单个项目时很有意义。

Flow在您决定开始收集它时,会随着时间返回多个项目。它们通常用于监视随时间变化的内容,以获取某物的最新值。例如,来自数据库的Flow可以在数据库中的内容发生更改时发出新值。

suspend函数与返回Flow的方式结合使用很少有意义。这是因为Flow本身是一个轻量级对象,通常不需要提前耗时的工作来检索和创建。

避免将函数标记为suspend而返回Flow的原因在于,它会使其使用变得更加繁琐。可能存在一种情况,您希望传递Flow而不立即收集它,而不希望必须启动协程来获取Flow。或者,您可能只想使用以下漂亮而简洁的语法来创建用于收集的专用协程:

someUseCase.retrieveSomeFlow()
    .onEach { ... }
    .launchIn(someScope)

而不是:

someScope.launch {
    someUseCase.retrieveSomeFlow().collect {
        ...
    }
}

一个例外情况可能是,如果您必须检索某些内容以用作Flow的基础键。例如,从API检索用户ID,然后返回使用该用户ID获取Flow的Flow。但即使这样做通常也是不必要的,因为您可以将ID检索放入Flow中。例如:

fun getUserItems(): Flow<UserItem> = flow {
    val userId = someAPI.retrieveUserId() // 调用一个suspend函数
    emitAll(someAPI.getUserItemsFlow(userId))
}

唯一的缺点是,如果多次收集Flow,它将在每次开始收集时重新检索用户ID。

英文:

suspend functions make sense for retrieving a single item in a suspending, synchronous way.

Flows return multiple items over time, when you decide to start collecting it. They are often used to monitor something that changes over time to retrieve the most up-to-date value of something. For example, a flow from a database can emit a new value each time something in the database changes.

It rarely makes sense to combine both, using a suspend function that retrieves a Flow. This is because the Flow itself is a lightweight object that typically doesn't require any up-front, time-consuming work to retrieve and create.

The reason to avoid making a function suspend when returning a flow is that it makes it more cumbersome to use. There might be cases where you want to pass a flow along without collecting it yet, and you don't want to have to launch a coroutine just to get the flow. Or, you might just want to use this nice, concise syntax for creating a dedicated coroutine for collection:

someUseCase.retrieveSomeFlow()
    .onEach { ... }
    .launchIn(someScope)

instead of:

someScope.launch {
    someUseCase.retrieveSomeFlow().collect {
        ...
    }
}

An exception might be if you have to retrieve something to use as a key that is a basis for the Flow. For example, retrieving a user ID from an API and then returning a Flow that uses that user ID to fetch items for the flow. But even this is often unnecessary, since you can put that ID retrieval into the Flow. For example:

fun getUserItems(): Flow&lt;UserItem&gt; = flow {
    val userId = someAPI.retrieveUserId() // calling a suspend function
    emitAll(someAPI.getUserItemsFlow(userId))
}

The only downside with this is that if you collect the flow multiple times, it would have to retrieve the user ID again each time collection begins.

答案2

得分: 2

这里有一些情景:

挂起函数用于简单的异步操作。

  • Flow 用于异步处理数据流并应用转换。
  • 当您有简单的异步操作时,请单独使用挂起函数。
  • 当您需要处理数据流或应用转换时,请单独使用 Flow。
  • 当涉及异步操作和处理数据流的复杂场景时,请同时使用挂起函数和 Flow。
英文:

Here are some senarios:

Suspend functions are used for simple asynchronous operations.

  • Flow is used for handling streams of data asynchronously and applying transformations.
  • Use suspend functions alone when you have a simple asynchronous operation.
  • Use Flow alone when you need to handle a stream of data or apply transformations.
  • Use both suspend functions and Flow together when you have complex scenarios involving asynchronous operations and handling streams of data.

huangapple
  • 本文由 发表于 2023年4月17日 05:14:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76030366.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定