Сопрограммы Kotlin: проблема с планированием заданий.(invokeOnCompletion)

#android #kotlin #viewmodel #kotlin-coroutines #job-scheduling

#Android #kotlin #viewmodel #kotlin-сопрограммы #планирование заданий

Вопрос:

Я довольно новичок в этой сопрограмме kotlin, и у меня проблема с планированием заданий.В приведенном ниже коде сначала я извлекаю названия тем из кэша пользователя во фрагменте. (topicsList), А затем мне нужно извлекать эти темы из API одну за другой. То, что я хочу сделать, это просмотреть topicsList, сделать запрос для каждой темы и получить все ответы один раз при завершении всех запросов. Чтобы добиться этого, в методе getEverything()(который запускает запрос) я добавляю ответы в arraylist для каждого раза.(Список ответов) В цикле for я запускаю все запросы. После завершения задания вызывается job.invokeOnCompletion{}, и я устанавливаю для своих LiveData значение responseList . Однако этот подход не работает. Проблема в том, что я обновляю LiveData перед настройкой списка ответов. Я не знаю, как это может быть возможно. Кто-нибудь может мне помочь в этом?

Вот моя сопрограмма в MyFragment:

 val topicsList = dataMap["topics"] // GOT THE TOPICS
topicsList?.let {
    var job: Job
    CoroutineScope(Dispatchers.Main).launch {
        job = launch {
            for (topic in topicsList) {
                mViewModel.getEverything(topic, API_KEY)
            }
        }
        job.join()
        job.invokeOnCompletion {
        mViewModel.updateLiveData()
        }
    }
} ?: throw Exception("NULL")
  

Метод getEverything() в ViewModel:

  suspend fun getEverything(topic: String, apiKey: String) {

        viewModelScope.launch {
            _isLoading.value = true
            withContext(Dispatchers.IO) {
                val response = api.getEverything(topic, apiKey)
                withContext(Dispatchers.Main) {
                    if (response.isSuccessful) {
                        if (response.body() != null) {
                            responseList.add(response.body()!!)
                            println("Response is successful: ${response.body()!!}")
                            _isLoading.value = false
                            _isError.value = false
                        }
                    }
                    else {
                        Log.d(TAG, "getEverything: ${response.errorBody()}")
                        _isError.value = true
                        _isLoading.value = false
                    }
                }
            }
        }

    }
  

И метод updateLiveData:

 fun updateLiveData() {
        _newsResponseList.value = responseList
        println("response list : ${responseList.size}")
        responseList.clear()
}

  

И вот как это выглядит в журналах: Журналы

Журналы для тех, кто не может открыть изображение :

 I/System.out: response list : 0
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=wired, ...
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=techcrunch, ...
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=wired, ...
I/System.out: Response is successful: NewsResponse(articleList=[Article(source=Source(id=the-verge, ...
  

Кстати, данные извлекаются без ошибок и являются правильными. У меня с этим нет проблем.

Ответ №1:

Проблема в том, что getEverything используется launch для создания фонового задания, а затем возвращается до того, как узнает, что задание завершено.

Чтобы исправить это, getEverything верните данные напрямую:

 suspend fun getEverything(topic: String, apiKey: String): Response? {
    _isLoading.value = true
    val response = withContext(Dispatchers.IO) {
        api.getEverything(topic, apiKey)
    }
    _isLoading.value = false

    return response.takeIf { it.isSuccessful }?.body()?.let { body ->
        println("Response is successful: $body")
    }.also {
        _isError.value = it == null
    }
}
  

В вашем фрагменте запросите результаты и назначьте их:

 lifecycleScope.launch {
    _responseList.value = topicsList.mapNotNull { topic ->
        model.getResponse(topic, apiKey)
    }
}