Как получить CoroutineScope в Interactor / UseCase - PullRequest
0 голосов
/ 23 апреля 2020

Рассмотрим этот вариант использования:

class GetPhotosUseCase(
    private val photosRepository: IPhotosRepository,
    private val favoritesRepository: IFavoritesRepository
) : IGetPhotosUseCase {

    override suspend fun getPhotos(): List<Photo> {
        val photos = photosRepository.getPhotos()
        val favoriteIds = favoritesRepository.getFavoriteIds()
        return photos.map {
            it.copy(isFavorite = favoriteIds.contains(it.id))
        }
    }

}

interface IPhotosRepository {

    suspend fun getPhotos(): List<Photo>

}

interface IFavoritesRepository {

    suspend fun getFavoriteIds(): List<Int>

}

Я выбираю данные из 2 разных источников и объединяю их. Прямо сейчас это работает последовательно. Когда я хочу запустить photosRepository.getPhotos() и favoritesRepository.getFavoriteIds() параллельно, чтобы сэкономить время выполнения, мой наивный подход будет следующим:

override suspend fun getPhotos(): List<Photo> {
    val photosDeferred = GlobalScope.async { photosRepository.getPhotos() }
    val favoriteIdsDeferred = GlobalScope.async { favoritesRepository.getFavoriteIds() }
    return applyFavoritesToPhotos(photosDeferred.await(), favoriteIdsDeferred.await())
}

private fun applyFavoritesToPhotos(photos: List<Photo>, favoriteIds: List<Int>) = photos.map {
    it.copy(isFavorite = favoriteIds.contains(it.id))
}

Использование GlobalScope не рекомендуется, потому что задание не будет отменено, когда жизненный цикл звонящего заканчивается.

Поскольку мой сценарий использования не знает о жизненном цикле вызывающего абонента, какую область он должен использовать? Было бы приемлемым решением передать область применения в сценарий использования, например:

   override suspend fun getPhotos(scope: CoroutineScope): List<Photo> {
        val photosDeferred = scope.async { photosRepository.getPhotos() }
        val favoriteIdsDeferred = scope.async { favoritesRepository.getFavoriteIds() }
        return applyFavoritesToPhotos(photosDeferred.await(), favoriteIdsDeferred.await())
    }

, или что было бы идеальным решением здесь? Должен ли случай использования вернуть Deferred и позволить вызывающему await it?

1 Ответ

3 голосов
/ 23 апреля 2020

Не передавайте scope своему UseCase, поскольку вы будете ломать Clean Architecture. Вместо этого просто сохраните свои функции как Suspend. Если вы хотите, чтобы функции были concurrent, просто оберните их в блок async.

Используйте UseCase в вашем ViewModel и используйте ViewModelScope для вызова suspend функция.

override suspend fun getPhotos(): List<Photo> {
    val photos = async { photosRepository.getPhotos() }
    val favoriteIds = async { favoritesRepository.getFavoriteIds() }

    return photos.await().map {
        it.copy(isFavorite = favoriteIds.await().contains(it.id))
    }
}

РЕДАКТИРОВАТЬ

Как уже упоминалось async нужна область, хорошее решение было бы создать простой CoroutineScope в UseCase, чья задача заключалась бы в том, чтобы охватить эту единственную сопрограмму, которая будет одновременно выполнять эти две async задачи.

val customScope = CoroutineScope(Dispatchers.Main)

override suspend fun getPhotos(): List<Photo> {
    customScope.launch {
        val photos = async { photosRepository.getPhotos() }
        val favoriteIds = async { favoritesRepository.getFavoriteIds()}
    }
    .
    .
    .
}
...