Как ждать нескольких заданий в Android ViewModel? - PullRequest
1 голос
/ 20 октября 2019

Учитывая следующие компоненты

data class Account(val name: String)

data class GetAccountRequest(val name: String)

@Dao
interface AccountDao {
    @Query("SELECT * FROM accounts ORDER BY name ASC")
    fun all(): LiveData<List<Account>>
}

interface AccountOperations {
    @GET("/foo/account")
    suspend fun getAccount(@Body request: GetAccountRequest): Account
}

class AccountRepository(private val dao: AccountDao, private val api: AccountOperations) {
    val accounts: LiveData<List<Account>> = dao.all()

    suspend fun refresh(name: String) {
        val account = api.getAccount(GetAccountRequest(name))
        dao.insert(account)
    }
}

Я работаю над приложением Android, которое использует эти компоненты (питание от Room для базы данных и Retrofit для доступа к API).

В моемViewModel Я поддерживаю RecyclerView, который перечисляет все учетные записи. Я даю пользователям возможность обновить этот список вручную. Соответствующая (часть) ViewModel выглядит следующим образом:

fun refresh() {
    viewModelScope.launch {
        repository.accounts.value?.forEach {
            launch { repository.refresh(it.name) }
        }
    }
    Timber.i("Done refreshing!")
}

Я хочу, чтобы обновление обновляло все учетные записи параллельно, поэтому я использую launch. Я также решил сделать это в ViewModel, а не в хранилище, поскольку для этого потребовалось бы запустить новую сопрограмму в хранилище. Что за этот пост не рекомендуется, так как репозитории не имеют естественного жизненного цикла.

Вышеуказанная функция refresh вызывается из пользовательского интерфейса и показывает индикатор обновления, пока RecyclerViewобновляется. Поэтому я хочу остановить этот индикатор, как только все учетные записи будут обновлены.

Мой код, как показано выше, этого не делает, так как он запустит все обновления и затем напечатает инструкцию журнала до того, как все обновления будут завершены. ,В результате индикатор обновления исчезает, хотя обновления все еще существуют.

Поэтому мой вопрос (наконец-то) заключается в следующем: как мне выполнить рефакторинг кода, чтобы он выполнял все обновления параллельно, но при этом refresh не делаетне вернетесь, пока все они не закончили?

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

Возвращаясь к тому, чего я хочу достичь: показывать индикатор обновления во время обновления представленияЯ придумал следующее (изменил функцию refresh в ViewModel):

fun refresh() {
        viewModelScope.launch {
            try {
                coroutineScope {
                    _refreshing.value = true
                    repository.accounts.value?.map { account ->
                        async {
                            repository.refresh(account.name)
                        }
                    }
                }
            } catch (cause: CancellationException) {
                throw cause
            } catch (cause: Exception) {
                Timber.e(cause)
            } finally {
                _refreshing.value = false
            }
        }
    }

ViewModel предоставляет LiveData, когда он обновляется, и фрагмент может наблюдать его, чтобы показать или скрыть счетчик. ,Это, кажется, делает трюк. Тем не менее, он все еще не чувствует себя хорошо, и я ценю любые улучшенные решения.

1 Ответ

0 голосов
/ 21 октября 2019

Чтобы await для всех ваших параллельных refresh() операций, просто используйте awaitAll():

coroutineScope.launch {
            _refreshing.value = true

            repository.accounts.value?.map { account ->
                async {
                    repository.refresh(account.name)
                }
            }.awaitAll()

            _refreshing.value = false
        } 

Кроме того, не рекомендуется заключать сопрограммы в try/catch. Вы можете прочитать больше об этом здесь .

...