How to prevent SQLDelight RC-2.0 potential memory leak on mapToList() with coroutineContext?

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

How to prevent SQLDelight RC-2.0 potential memory leak on mapToList() with coroutineContext?

问题

我正在进行一个KMM项目,以更深入地了解Apollo GraphQL和SQLDelight的使用。我正在使用逐层多模块化的方式,在我的域模块中,我试图将我从SQLDelight收集到的实体映射到我的用例中的域模型对象中。

问题是,SQLDelight 2.0改变了coroutine-extensions包的使用,目前需要一个coroutineContext来运行操作(为什么这样做呢?)。

最后,我接受了我的命运,并决定注入一个调度程序以获取coroutine上下文,然而结果的代码如下:


class GetLikedCharacters(
    private val database: RickAndMortDatabase,
    private val coroutineContext: CoroutineContext
) {
    operator fun invoke(
        id: Int
    ): Flow<Resource<List<CharacterModel>>> = flow {
        emit(Resource.Loading)
        try {
            database.getAllLikedCharacters().mapToList(coroutineContext).collect { entities ->
                if (entities.isEmpty()) {
                    emit(Resource.EmptyOrNull)
                } else {
                    val mapped = entities.map { it.toCharacterModel() }
                    emit(Resource.Success(data = mapped))
                }
            }
        } catch (e: Exception) {
            e.message?.let {  message ->
                emit(Resource.Error(message = message))
            }
        }
    }
}

正如您所看到的,在这一行:“database.getAllLikedCharacters().mapToList(coroutineContext).collect”,我基本上在协程内启动了一个协程,这可能会导致内存泄漏。我考虑了许多防止这种情况发生的方法,最简单的方法之一是在我的ViewModel中映射查询,但是:

  • 这基本上迫使开发人员(或者可能是使用它的公司)在ViewModel内部执行业务逻辑。
  • 如果不在ViewModel中完成,就需要手动处理协程范围的注入,这也可能导致导航时内存泄漏,如果启动的协程没有被取消。

我不想回滚到SQLDelight的早期版本,因为当前版本是发布候选版,我们开发人员将在不久的将来面临新的“破坏性”API。

那么可以做什么?任何帮助都将不胜感激。

英文:

I am working on a KMM project to understand the usage of Apollo GraphQL and SQLDelight more deeply. I am using layer-by-layer multi modularization and in my domain module, I am trying to map entities I have collected from SQLDelight to my domain model objects in my useCases.

The problem is, SQLDelight 2.0 changes the usage of coroutine-extensions package and currently requires a corutineContext in order to run operations (JUST WHY?).

In the end, I have accepted my fate and decided to inject a dispatcher in order to obtain the coroutine context, however the resulting code turn out like this:


class GetLikedCharacters(
    private val database: RickAndMortDatabase,
    private val coroutineContext: CoroutineContext
) {
    operator fun invoke(
        id: Int
    ): Flow<Resource<List<CharacterModel>>> = flow {
        emit(Resource.Loading)
        try {
            database.getAllLikedCharacters().mapToList(coroutineContext).collect { entities ->
                if (entities.isEmpty()) {
                    emit(Resource.EmptyOrNull)
                } else {
                    val mapped = entities.map { it.toCharacterModel() }
                    emit(Resource.Success(data = mapped))
                }
            }
        } catch (e: Exception) {
            e.message?.let {  message ->
                emit(Resource.Error(message = message))
            }
        }
    }
}

As you can see, in the line: "database.getAllLikedCharacters().mapToList(coroutineContext).collect", I am basically launching a coroutne inside of a coroutine, which can result in a memory leak. I have thought about many ways to prevent this and the most easiest one is to map the query inside my ViewModel, but:

  • It basically pushes the developers (or maybe the companies using it) to do business logic inside ViewModel.
  • If not done in ViewModel, it requires injection of a coroutine scope that needs to be handled manually, which can also cause memory leaks on navigation if the launched coroutines does not cancelled.

I do not want to rollback to previous versions of SQLDelight as current version is on Release Candidate and us developers will be doomed on new "breaking" API"s in the near future.

So what can be done? Any help is appreciated.

答案1

得分: 1

你可以在 flow {} 构建器中直接使用 currentCoroutineContext()。然后,mapToList 将在流的范围内运行。不需要在构造函数中提供额外的协程上下文。

database
  .getAllLikedCharacters()
  .mapToList(currentCoroutineContext())
  .collect {
     // 在这里进行收集
  }
英文:

You can use currentCoroutineContext() from the flow {} builder itself. mapToList will run in the scope of the flow then. No need for providing an additional coroutine context in constructor.

database
  .getAllLikedCharacters()
  .mapToList(currentCoroutineContext())
  .collect {
     // collect here
  }

huangapple
  • 本文由 发表于 2023年6月5日 16:08:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76404559.html
匿名

发表评论

匿名网友

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

确定