question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Question: How to properly wrap RxJava 2 functions with suspend functions

See original GitHub issue

Let’s say I have the following RxJava-based API client class:

class RxJavaBasedApi {
    fun getUser(): Single<User> {
        // Underlying implementation details may be Retrofit + RxJava 2
    }
}

Now I wish to wrap this in a suspend function like so:

class UserApiClient(private val rxJavaBasedApi) {
    suspend fun getUser(): User {
        return rxJavaBasedApi.getUser().await() 
    }
}

Then in an Android ViewModel, let’s say I have the following:

class UserViewModel(
    private val userApiClient: UserApiClient,
    private val dispatchers: Dispatchers // Wrapper Dispatchers instance for DI purposes
) : ViewModel(),
    CoroutineScope {
    override val coroutineContext: CoroutineContext = Job() + dispatchers.main

    init {
        launch {
            val user = userApiClient.getUser() // NetworkOnMainThreadException
        }
    }
    ...
}

When getUser() gets called, the underlying Single will get subscribed which by default operates in a synchronous, blocking manner since there was no subscribeOn()/observerOn() specified downstream.

Although Single.await() is a suspend function, in this particular case, it will block on the current thread in which it was invoked.

Questions:

  1. Should the ViewModel take on the responsibility of having to wrap the call with withContext(dispatchers.io) in the init block?
  2. Should UserApiClient instead take care of the implementation details of wrapping the call within withContext(dispatchers.io)? This avoids having to specify withContext in the ViewModel. If so:
    • Should the Single.await() extension still be used? Does it provide any value within withContext?
    • Should Single.blockingGet() be used instead? It would be a non-suspend invocation within withContext.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
qwwdfsadcommented, Dec 6, 2018

Both are fine, though I’m biased towards .await just to make it idiomatic 😃 Don’t you mind if I close the issue?

1reaction
qwwdfsadcommented, Dec 6, 2018

The problem arises from the fact, that you have to control two concurrency levels: one from Rx and one from coroutines. Yes, you can use subscribeOn, but if you already opted-in in coroutines-based API (by making getUser suspendable), it is better to use withContext.

Should the ViewModel take on the responsibility of having to wrap the call with withContext(dispatchers.io) in the init block?

Better not to. If your API is already suspendable and IO-related, it should use withContext(IO) on its own. Scheduler and dispatcher are an implementation detail and should be encapsulated in the service itself (UserApiClient). In that case, it could provide a stable contract (“use me from everywhere”), be easily mocked in tests no matter in what context it is used and its context will be configurable in a single place (UserApiClient constructor)

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to create a safe call wrapper for RxJava functions in Kotlin?
You can easily make this work with RxJava by converting your observables to suspending functions using the awaitFirst() or await() (for ...
Read more >
Kotlin Coroutines by Tutorials, Chapter 4: Suspending Functions
In this chapter, you'll learn more about how suspendable functions work from within. You will see how you can convert existing code, which...
Read more >
Coroutines under the hood - Kt. Academy
Here there are two suspending functions: getUserId and getUserName . We also added a parameter token , and our suspending function also returns...
Read more >
Are Kotlin Coroutines Enough to Replace RxJava? - JVM Advent
But keep in mind that you don't necessarily need to wrap all your suspend functions in try/catch to handle exceptions.
Read more >
Misnomers, Mistakes and Misunderstandings to watch for ...
When designing your methods and functions, start with a suspend function and if you get stuck, change it to a Flow. Such a...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found