https://kotlinlang.org logo
Title
c

CamilleBC

06/26/2019, 2:31 PM
Hello ! I'm in need of advice. I'm trying to get a list of chapters from webfictions. When I'm sending a list of IDs (part of URL for retrofit to dl the chapter), it is an ordered list. I want to be able to download all chapters asynchronously (in case some download faster than others), but still keep the order from the first list (first in the chapterIds list will be the first in the return List<Chapter>, etc...). Is it the proper way to do this, or I'm going at it completely the wrong way?
suspend fun getChapters(chapterIds: List<String>): List<Chapter> =
    mutableListOf<Chapter>().apply {
        chapterIds.forEach { launch { add(getChapter(it)) } }
    }

suspend fun getChapter(chapterId: String): Chapter {
    val response = service.getChapter(chapterId)
    return if (response.isSuccessful) {
        val doc = Jsoup.parse(response.body()?.string())
        Chapter(
            doc.select(CHAPTER_TITLE_QUERY).text(),
            doc.select(CHAPTER_CONTENT_QUERY).html().lines()
       )
    } else Chapter()
}
d

Dico

06/26/2019, 2:39 PM
You are accessing the list asynchronously here. Instead, do something like this:
suspend fun getChapters(chapterIds: List<String>): List<Deferred<Chapter>> = coroutineScope {
    chapterIds.map { 
        async { getChapter(it) } 
    }

}
for (deferred in result) {
   val chapter = deferred.getOrNull() ?: continue
   // draw chapter, perhaps
}
You can also do
result.map { it.await() }
which gives a
List<Chapter>
once all downloads completed
🙏 1
c

CamilleBC

06/26/2019, 2:44 PM
Oh nice. I will do that ! I can even do the
map,async
, and then the map.await() to return the list. That way, the person using my library won't have to do it himself. Thank you so much for the help !
I have done it like this:
suspend fun getChapters(chapterIds: List<String>): List<Chapter> {
    val deferredList : List<Deferred<Chapter>> = coroutineScope {
        chapterIds.map { async { getChapter(it) } }
    }
   return deferredList.map { it.await() }
}
I think that does what I need.
d

Dico

06/26/2019, 4:00 PM
Yes, it doesn't allow your app to use completed downloads though until all downloads completed. Moreover, failure of one download will cause all others to be lost. You can wrap
await
in a try/catch and make the chapters perhaps nullable, or use
mapNotNull
. Failures may also cancel the coroutine and its parents, to avoid this use
supervisorScope
instead of
coroutineScope
c

CamilleBC

06/26/2019, 5:08 PM
Thanks for the warnings @Dico! I'll keep that in mind and check how to implement a correct error management.