Просто интересно мнение других, у меня есть 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)
}
}
}
как еще я могу вызвать withContext и получить ответ, который не может использовать запуск, поскольку он возвращает задание?
launch используется для запуска неблокирующей сопрограммы. Вы можете создать функцию, которая оборачивает вызов launch и возвращает Unit, если вы не хотите, чтобы ваша функция возвращала Job.
Хорошо, но что, если я хочу вернуть Int?
Используя сопрограммы, вы не можете вернуть значение, не блокируя поток. Если у вас есть объект MutableLiveData в ViewModel, вы можете использовать его для обновления пользовательского интерфейса: launch { liveData.value = wordRepository.deleteAllLogsOlderThan(XDays)}
Итак, решение состоит в том, чтобы изменить WordViewModel на приостановку и обработать цепочку, чтобы объект, вызывающий эту функцию, работал в своей собственной области или GlobalScope, поскольку ответ не требуется вне вызова функции. как я называю это из приложения main.
Итак, в репо есть это @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") }
Я предпочитаю запускать сопрограмму в ViewModel или Presenter, используя локальную область видимости. Чтобы в WordViewModel функции deleteAllLogsOlderThanA запускалась сопрограмма
Верно, но в этом случае вызов базы данных полностью автономен и не требует доступа к ViewModel или чему-либо еще. В общем, беги и забудь.
Честно говоря, я думаю, что в любом случае это нормально, но если мне придется выбирать, я предпочитаю хранить код, связанный с потоком, на уровне репозитория. (Версия А в примере)
Мое обоснование:
(A) Модели просмотра не должны предполагать внутреннюю работу репозиториев. Версия Б подразумевает, что модель представления предполагает, что репозиторий будет работать в вызывающем потоке.
(B) Кроме того, репозиторий не должен зависеть от других компонентов в использовании определенного потока для вызова своего метода. Репозиторий должен быть завершен сам по себе.
(C) Чтобы избежать дублирования кода. Наличие нескольких моделей View, вызывающих один метод репозитория, является очень распространенной ситуацией. В этом случае Версия А выигрывает, потому что вам нужно Corountine только в одном месте, в репозитории.
Я предлагаю взглянуть на некоторые из примеров Google, чтобы увидеть, где они предпочитают обрабатывать Coroutine и потоковые коды доступа к данным.
Подсолнух использует код Coroutine в своем репозитории. Это, вероятно, особенно полезно для вас, потому что включает в себя примеры запросов типа «выстрелил-забыл».
GitHubBrowserRepo не использует Coroutine, но репозиторий хранит ссылки на экземпляры Executor, которые используются при доступе к данным.
Спасибо :) Я думал о версии A, но моей основной причиной была только ваша C, поэтому другие причины очень полезны. Я обязательно посмотрю 2 примера проектов Google.
Где мне тогда создать Coroutine Job? в модели просмотра или в репозитории? Когда я должен очистить, чтобы предотвратить утечку памяти?
Если вы используете Android ViewModel, вы можете создавать задания в модели представления и очищать задания в ViewModel#onCleared.
Для (A): Версия B предполагает, что репозиторий находится в работающем потоке, а версия A предполагает, что он находится в рабочем потоке. Что еще хуже?
@Malachiasz В версии A ViewModel не знает, что репозиторий работает в рабочем потоке. Вы можете скопировать и вставить ViewModels версии B в версию A, и она все еще может работать. Обратное, скорее всего, не получится.
Я принимаю ваш аргумент. Переключенный диспетчер где-то в начале цепочки вызовов не гарантирует, что он не переключился снова где-то глубже. Другими словами, язык не предоставляет вызывающей стороне возможность привязать вызываемый метод к данному диспетчеру. Разве что по хрупкому обычаю.
А как насчет библиотеки пагинации? Можно ли выполнить сетевой вызов внутри репозитория и использовать его в источнике данных?
Я не могу придумать ни одной причины, которая сделала бы это невозможным.
Вопрос в том, где указать, что задания репозитория должны выполняться в пулах потоков ввода-вывода:
Честно говоря, я не уверен, какой путь лучше.
Б) Наличие его в ViewModel означает большую прозрачность того, что выполняется в каком потоке. Именно так я в основном работал с RxJava. Как недостаток, ваша ViewModel будет загромождена переключением потоков (withContext()), в то время как на самом деле очевидно, что все задания репозитория должны выполняться в фоновом потоке. Так полезна ли эта дополнительная информация?
A) Наличие его в репозитории означает большую гибкость в отношении переключения потоков в репозитории и более чистый код в ViewModel. Код будет менее явным с точки зрения ViewModel в отношении потоков. Как недостаток, все методы репозитория должны быть приостановлены, поэтому их можно использовать только из сопрограмм.
runBlockingблокирует текущую ветку, вы уверены, что хотите ее использовать?