Что лучше поместить вызов Coroutine в репозиторий или ViewModel?

Просто интересно мнение других, у меня есть 2 способа сделать что-то, и мне было любопытно, какой из них лучше (и, надеюсь, почему вы так думаете)

У меня есть 2 файла WordRepository и WordViewModel. Я могу выполнять сопрограммы в репо или в ViewModel в обоих случаях, но надеюсь, что кто-то может дать мне понять, почему я должен выполнять сопрограммы в одном или другом и наоборот.

Версия A. (где сопрограмма находится в репо)

WordRepo:

class WordRepository(private val wordDao: WordDao): WordRepo {

    @WorkerThread
    override suspend fun deleteAllLogsOlderThan(XDays: Int): Int = withContext(IO) {
        return@withContext wordDao.deleteAll()
    }

}

WordViewModel:

class WordViewModel(private val wordRepository: WordRepo) : ViewModel() {

    fun deleteAllLogsOlderThanA(XDays:Int): Int = runBlocking {
        wordRepository.deleteAllLogsOlderThan(XDays)
    }

}

Версия B. (Где сопрограмма находится в ViewModel)

Word Repo:
class WordRepository(private val wordDao: WordDao): WordRepo {

    @WorkerThread
    override suspend fun deleteAllLogsOlderThan(XDays: Int): Int = wordDao.deleteAll()

}

WordViewModel:
class WordViewModel(private val wordRepository: WordRepo) : ViewModel() {

    fun deleteAllLogsOlderThanA(XDays:Int): Int = runBlocking {
            withContext(IO) {
                wordRepository.deleteAllLogsOlderThan(XDays)
            }
        }

}
runBlocking блокирует текущую ветку, вы уверены, что хотите ее использовать?
Sergey 13.03.2019 17:05

как еще я могу вызвать withContext и получить ответ, который не может использовать запуск, поскольку он возвращает задание?

Crash1hd 13.03.2019 17:08
launch используется для запуска неблокирующей сопрограммы. Вы можете создать функцию, которая оборачивает вызов launch и возвращает Unit, если вы не хотите, чтобы ваша функция возвращала Job.
Sergey 13.03.2019 17:29

Хорошо, но что, если я хочу вернуть Int?

Crash1hd 13.03.2019 17:31

Используя сопрограммы, вы не можете вернуть значение, не блокируя поток. Если у вас есть объект MutableLiveData в ViewModel, вы можете использовать его для обновления пользовательского интерфейса: launch { liveData.value = wordRepository.deleteAllLogsOlderThan(XDays)}

Sergey 13.03.2019 17:43

Итак, решение состоит в том, чтобы изменить WordViewModel на приостановку и обработать цепочку, чтобы объект, вызывающий эту функцию, работал в своей собственной области или GlobalScope, поскольку ответ не требуется вне вызова функции. как я называю это из приложения main.

Crash1hd 13.03.2019 19:04

Итак, в репо есть это @WorkerThread override suspend fun deleteAllLogsOlderThan(XDays: Int): Int = wordDao.deleteAll() В wordViewModel есть suspend fun deleteAllLogsOlderThan(XDays:Int): Int = wordRepository.deleteAllLogsOlderThan(XDays), а затем объект журнала вызывает это suspend fun purgeOldLogsGreaterThan(XDays: Int): Int { return wordViewModel.deleteAllLogsOlderThan(XDays) }, а затем вызывается из приложения GlobalScope.launch { Log.i("Log", "Purging ${LogA.purgeOldLogsGreaterThan(xDays)} logs which where older than $xDays old") }

Crash1hd 13.03.2019 19:08

Я предпочитаю запускать сопрограмму в ViewModel или Presenter, используя локальную область видимости. Чтобы в WordViewModel функции deleteAllLogsOlderThanA запускалась сопрограмма

Sergey 13.03.2019 19:22

Верно, но в этом случае вызов базы данных полностью автономен и не требует доступа к ViewModel или чему-либо еще. В общем, беги и забудь.

Crash1hd 13.03.2019 19:24
12
9
2 821
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Честно говоря, я думаю, что в любом случае это нормально, но если мне придется выбирать, я предпочитаю хранить код, связанный с потоком, на уровне репозитория. (Версия А в примере)

Мое обоснование:

(A) Модели просмотра не должны предполагать внутреннюю работу репозиториев. Версия Б подразумевает, что модель представления предполагает, что репозиторий будет работать в вызывающем потоке.

(B) Кроме того, репозиторий не должен зависеть от других компонентов в использовании определенного потока для вызова своего метода. Репозиторий должен быть завершен сам по себе.

(C) Чтобы избежать дублирования кода. Наличие нескольких моделей View, вызывающих один метод репозитория, является очень распространенной ситуацией. В этом случае Версия А выигрывает, потому что вам нужно Corountine только в одном месте, в репозитории.


Я предлагаю взглянуть на некоторые из примеров Google, чтобы увидеть, где они предпочитают обрабатывать Coroutine и потоковые коды доступа к данным.

  1. Подсолнух использует код Coroutine в своем репозитории. Это, вероятно, особенно полезно для вас, потому что включает в себя примеры запросов типа «выстрелил-забыл».

  2. GitHubBrowserRepo не использует Coroutine, но репозиторий хранит ссылки на экземпляры Executor, которые используются при доступе к данным.

Спасибо :) Я думал о версии A, но моей основной причиной была только ваша C, поэтому другие причины очень полезны. Я обязательно посмотрю 2 примера проектов Google.

Crash1hd 13.03.2019 22:00

Где мне тогда создать Coroutine Job? в модели просмотра или в репозитории? Когда я должен очистить, чтобы предотвратить утечку памяти?

Viktor Vostrikov 29.05.2019 09:51

Если вы используете Android ViewModel, вы можете создавать задания в модели представления и очищать задания в ViewModel#onCleared.

Sanlok Lee 29.05.2019 20:39

Для (A): Версия B предполагает, что репозиторий находится в работающем потоке, а версия A предполагает, что он находится в рабочем потоке. Что еще хуже?

Malachiasz 08.11.2019 13:57

@Malachiasz В версии A ViewModel не знает, что репозиторий работает в рабочем потоке. Вы можете скопировать и вставить ViewModels версии B в версию A, и она все еще может работать. Обратное, скорее всего, не получится.

Sanlok Lee 08.11.2019 19:24

Я принимаю ваш аргумент. Переключенный диспетчер где-то в начале цепочки вызовов не гарантирует, что он не переключился снова где-то глубже. Другими словами, язык не предоставляет вызывающей стороне возможность привязать вызываемый метод к данному диспетчеру. Разве что по хрупкому обычаю.

Malachiasz 09.11.2019 17:40

А как насчет библиотеки пагинации? Можно ли выполнить сетевой вызов внутри репозитория и использовать его в источнике данных?

G_comp 18.11.2019 21:33

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

Sanlok Lee 20.11.2019 22:52

Вопрос в том, где указать, что задания репозитория должны выполняться в пулах потоков ввода-вывода:

  • А) В хранилище
  • Б) В ViewModel

Честно говоря, я не уверен, какой путь лучше.

Б) Наличие его в ViewModel означает большую прозрачность того, что выполняется в каком потоке. Именно так я в основном работал с RxJava. Как недостаток, ваша ViewModel будет загромождена переключением потоков (withContext()), в то время как на самом деле очевидно, что все задания репозитория должны выполняться в фоновом потоке. Так полезна ли эта дополнительная информация?

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

Другие вопросы по теме