I’ve just read the new article about the recent <C...
# android
a
I’ve just read the new article about the recent Coroutines best practices on Android. Thank you so much for publishing these guidelines! I am happy to learn that I have been doing do a lot of these practices already and I’m sure the others are thankful too to have more guidelines regarding coroutines usage. I would just like to give my feedback (hopefully this Slack is a good place for it) in hopes of understanding these guidelines better for me and the others as well. I’ll take the chance to tag @Manuel Vivo hoping you can take time to read my thread 🙇. This just got me excited that I got up from my bed to ask and learn more about the article. My apologies if it’s too direct and you may ignore it. ⬇️
1. Is there any practical difference between doing this
Copy code
return coroutineScope {
    val books = async(defaultDispatcher) {
        booksRepository.getAllBooks()
    }
    val authors = async(defaultDispatcher) {
        authorsRepository.getAllAuthors()
    }
    BookAndAuthors(books.await(), authors.await())
}
and this
Copy code
return withContext(defaultDispatcher) {
     val books = async {
        booksRepository.getAllBooks()
    }
    val authors = async {
        authorsRepository.getAllAuthors()
    }
    BookAndAuthors(books.await(), authors.await())
}
The latter is what I naturally do when I need parallel loading behavior, I’m curious if this is considered bad practice.
2. After reading this article, I stopped injecting dispatchers directly by default. My understanding is that the actual long-running operations are being done by the libraries I use (Retrofit and Room), so I thought it’s still appropriate and results to more concise code. I thought of injecting my own dispatchers only if I really need it for something (complex behavior or testing). Do you have an opinion regarding this?
3. Lastly, exception-handling is something I hoped there would be more documentation on with concrete examples. Currently, I try to follow recommendations from this article by Roman Elizarov with my own interpretation (since there is no concrete example of his ideas). I’m curious if the guidelines follow those or have a separate opinion. Unfortunately for me, exception-handling is the least talked about in the new article. As for the guideline, is it recommending to catch all these exceptions from the ViewModel? I wish there are more examples on how to actually consume the Throwable, and hopefully somehow still avoiding bloating the ViewModel with exception handling code. And also it would be great if we could talk about how we could nicely handle exceptions from multiple asynchronous operations (think an operation that invokes multiples APIs and each can fail in different ways).
t
Hoping on to this thread here, since I'm also happy an article list this has finally been published, and thanks to @Manuel Vivo for this one as well https://medium.com/androiddevelopers/coroutines-patterns-for-work-that-shouldnt-be-cancelled-e26c40f142ad. I wanted to ask about longer-running coroutines that should survive their viewmodel. If the viewmodel is scoped to a composable in the nav graph, is there anything wrong with just declaring a new
coroutineScope
in the business layer class instead of passing in an
externalScope
managed by the application? I have something like this:
Copy code
class LoginInteractor(private val repository: NueveRepository, dispatcher: CoroutineDispatcher = Dispatchers.Main){

    private val coroutineScope = CoroutineScope(Job() + dispatcher)

    @ExperimentalCoroutinesApi
    fun loginUser(
        needsRefresh: Boolean = false,
        userEmail: String = String.empty,
        userPassword: String = String.empty,
        loginResult: (Result<Unit>) -> Unit = {}
    ) {
        coroutineScope.launch {
        // executes api call and returns the loginResult for the view event    (i.e. going to the next composable)
        // then, calls a suspend function that stores user info in a DataStore
This seems to work fine, as the user info is written to DataStore when navigation happens to the new composable. Is there anything wrong with this approach?
m
Hi! Thanks for the feedback 1. Looks good to me 🙂 if you’re using the same dispatcher is fine to wrap that in a
withContext
, that’s definitely not consider a bad practice 2. That’s fine too, only inject Dispatchers iff you need them. If the repo is only proxying to Room or Retrofit, there’s no need to use a Dispatchers in there 3. For that, I wrote about it here https://manuelvivo.dev/coroutines-cancellation-exceptions-3. You can catch the exceptions at the collection point or lower from the hierarchy. There isn’t really a rule of thumb for this, it depends on the requirements and the use case. Do what you think that feels right.
@Tony Kazanjian In that code, you’re injecting the Dispatcher with which you can control testing somehow. The problem with creating the scope inside that
LoginInteractor
is that it cannot be cancelled from outside. So, if the class needs to be garbage-collected because it’s no longer needed. It’ll be in memory as long as a coroutine created by that scope is active. On the other hand, if you inject the scope, the class that control the interactor’s lifecycle can cancel the injected coroutineScope to not waste resources. Hope this helps!
👍 1
a
@Manuel Vivo thank you for your response and linking your article on exceptions! I’ll be sure to read it 🙏
m
Anytime 🙂 happy to help
and sorry for the late reply
a
No problem, I know you’re busy helping to publish more articles for us! 😆
🥰 1