How to make a sync call using RxJava - authentication

I need to make a sync call to reauthenticate the user and get a new token, but I haven't found a way that works. The code below blocks the thread and it is never unblocked, ie. I have an infinite loop
class ApolloAuthenticator(private val authenticated: Boolean) : Authenticator {
#Throws(IOException::class)
override fun authenticate(route: Route, response: Response): Request? {
// Refresh your access_token using a synchronous api request
if (response.request().header(HEADER_KEY_APOLLO_AUTHORIZATION) != null) {
return null //if you've tried to authorize and failed, give up
}
synchronized(this) {
refreshTokenSync() // This is blocked and never unblocked
val newToken = getApolloTokenFromSharedPreference()
return response.request().newBuilder()
.header(HEADER_KEY_APOLLO_AUTHORIZATION, newToken)
.build()
}
private fun refreshTokenSync(): EmptyResult {
//Refresh token, synchronously
val repository = Injection.provideSignInRepository()
return repository
.signInGraphQL()
.toBlocking()
.first()
}
fun signInGraphQL() : Observable<EmptyResult> =
sharedPreferencesDataSource.identifier
.flatMap { result -> graphqlAuthenticationDataSource.getAuth(result) }
.flatMap { result -> sharedPreferencesDataSource.saveApolloToken(result) }
.onErrorReturn { EmptyResult() }
}
---------- Use of it
val apollAuthenticator = ApolloAuthenticator(authenticated)
val okHttpBuilder =
OkHttpClient.Builder()
.authenticator(apollAuthenticator)

I haven't found a way to make a sync call using RxJava, but I can make it by using kotlin coutorine runBlocking, which will block the thread until the request is finished:
synchronized(this) {
runBlocking {
val subscription = ApolloReauthenticator.signInGraphQl() // await until it's finished
subscription.unsubscribe()
}
}
fun signInGraphQl(): Subscription {
return repository.refreshToken()
.subscribe(
{ Observable.just(EmptyResult()) },
{ Observable.just(EmptyResult()) }
)
}

Related

How to create a polling mechanism with kotlin coroutines?

I am trying to create a polling mechanism with kotlin coroutines using sharedFlow and want to stop when there are no subscribers and active when there is at least one subscriber. My question is, is sharedFlow the right choice in this scenario or should I use channel. I tried using channelFlow but I am unaware how to close the channel (not cancel the job) outside the block body. Can someone help? Here's the snippet.
fun poll(id: String) = channelFlow {
while (!isClosedForSend) {
try {
send(repository.getDetails(id))
delay(MIN_REFRESH_TIME_MS)
} catch (throwable: Throwable) {
Timber.e("error -> ${throwable.message}")
}
invokeOnClose { Timber.e("channel flow closed.") }
}
}
You can use SharedFlow which emits values in a broadcast fashion (won't emit new value until the previous one is consumed by all the collectors).
val sharedFlow = MutableSharedFlow<String>()
val scope = CoroutineScope(Job() + Dispatchers.IO)
var producer: Job()
scope.launch {
val producer = launch() {
sharedFlow.emit(...)
}
sharedFlow.subscriptionCount
.map {count -> count > 0}
.distinctUntilChanged()
.collect { isActive -> if (isActive) stopProducing() else startProducing()
}
fun CoroutineScope.startProducing() {
producer = launch() {
sharedFlow.emit(...)
}
}
fun stopProducing() {
producer.cancel()
}
First of all, when you call channelFlow(block), there is no need to close the channel manually. The channel will be closed automatically after the execution of block is done.
I think the "produce" coroutine builder function may be what you need. But unfortunately, it's still an experimental api.
fun poll(id: String) = someScope.produce {
invokeOnClose { Timber.e("channel flow closed.") }
while (true) {
try {
send(repository.getDetails(id))
// delay(MIN_REFRESH_TIME_MS) //no need
} catch (throwable: Throwable) {
Timber.e("error -> ${throwable.message}")
}
}
}
fun main() = runBlocking {
val channel = poll("hello")
channel.receive()
channel.cancel()
}
The produce function will suspended when you don't call the returned channel's receive() method, so there is no need to delay.
UPDATE: Use broadcast for sharing values across multiple ReceiveChannel.
fun poll(id: String) = someScope.broadcast {
invokeOnClose { Timber.e("channel flow closed.") }
while (true) {
try {
send(repository.getDetails(id))
// delay(MIN_REFRESH_TIME_MS) //no need
} catch (throwable: Throwable) {
Timber.e("error -> ${throwable.message}")
}
}
}
fun main() = runBlocking {
val broadcast = poll("hello")
val channel1 = broadcast.openSubscription()
val channel2 = broadcast.openSubscription()
channel1.receive()
channel2.receive()
broadcast.cancel()
}

How to await billingClient.startConnection in Kotlin

I'm following the google billing integration instructions and have got stuck with how to await for the billing client connection result.
Whenever I need to query sku details or purchases I need to make sure that the billing client is initialized and connected. There are querySkuDetails and queryPurchasesAsync awaitable kotlin extension functions, but startConnection is based on listeners instead. Here are code samples from the docs.
private var billingClient = BillingClient.newBuilder(activity)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})
suspend fun querySkuDetails() {
// prepare params
// leverage querySkuDetails Kotlin extension function
val skuDetailsResult = withContext(Dispatchers.IO) {
billingClient.querySkuDetails(params.build())
}
// Process the result.
}
How to put all this together using suspend functions?
One way to create a suspending version of startConnection is the following:
/**
* Returns immediately if this BillingClient is already connected, otherwise
* initiates the connection and suspends until this client is connected.
*/
suspend fun BillingClient.ensureReady() {
if (isReady) {
return // fast path if already connected
}
return suspendCoroutine { cont ->
startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingResponseCode.OK) {
cont.resume(Unit)
} else {
// you could also use a custom, more precise exception
cont.resumeWithException(RuntimeException("Billing setup failed: ${billingResult.debugMessage} (code ${billingResult.responseCode})"))
}
}
override fun onBillingServiceDisconnected() {
// no need to setup reconnection logic here, call ensureReady()
// before each purchase to reconnect as necessary
}
})
}
}
This will fail if another coroutine already initiated a connection.
If you want to deal with potential concurrent calls to this method, you can use a mutex to protect the connection part:
val billingConnectionMutex = Mutex()
/**
* Returns immediately if this BillingClient is already connected, otherwise
* initiates the connection and suspends until this client is connected.
* If a connection is already in the process of being established, this
* method just suspends until the billing client is ready.
*/
suspend fun BillingClient.ensureReady() {
billingConnectionMutex.withLock {
// fast path: avoid suspension if another coroutine already connected
if (isReady) {
return
}
connectOrThrow()
}
}
private suspend fun BillingClient.connectOrThrow() = suspendCoroutine<Unit> { cont ->
startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
cont.resume(Unit)
} else {
cont.resumeWithException(RuntimeException("Billing setup failed: ${billingResult.debugMessage} (code ${billingResult.responseCode})"))
}
}
override fun onBillingServiceDisconnected() {
// no need to setup reconnection logic here, call ensureReady()
// before each purchase to reconnect as necessary
}
})
}
Here, the release of the mutex corresponds to the end of connectOrThrow() for whichever other coroutine was holding it, so it's released as soon as the connection succeeds. If that other connection fails, this method will attempt the connection itself, and will succeed or fail on its own so the caller will be notified in case of error.
If you prefer to deal with result codes directly in if statements, you can return results instead of throwing:
private val billingConnectionMutex = Mutex()
private val resultAlreadyConnected = BillingResult.newBuilder()
.setResponseCode(BillingClient.BillingResponseCode.OK)
.setDebugMessage("Billing client is already connected")
.build()
/**
* Returns immediately if this BillingClient is already connected, otherwise
* initiates the connection and suspends until this client is connected.
* If a connection is already in the process of being established, this
* method just suspends until the billing client is ready.
*/
suspend fun BillingClient.connect(): BillingResult = billingConnectionMutex.withLock {
if (isReady) {
// fast path: avoid suspension if already connected
resultAlreadyConnected
} else {
unsafeConnect()
}
}
private suspend fun BillingClient.unsafeConnect() = suspendCoroutine<BillingResult> { cont ->
startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
cont.resume(billingResult)
}
override fun onBillingServiceDisconnected() {
// no need to setup reconnection logic here, call ensureReady()
// before each purchase to reconnect as necessary
}
})
}
As discussed in BillingClient.BillingClientStateListener.onBillingSetupFinished is called multiple times, it is not possible to translate startConnection into the Coroutine world, because its callback will be called multiple times.
Thanks to Joffrey's answer and comments I have managed to construct a solution with BillingResult returned
val billingConnectionMutex = Mutex()
suspend fun BillingClient.connect(): BillingResult =
billingConnectionMutex.withLock {
suspendCoroutine { cont ->
startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode != BillingResponseCode.OK)
Log.d(TAG, "Billing setup failed: ${billingResult.debugMessage} (code ${billingResult.responseCode})")
cont.resume(billingResult)
}
override fun onBillingServiceDisconnected() {}
})
}
}
and then call it like this within the billing flow
if(billingClient!!.connect().responseCode != BillingResponseCode.OK)
return
If billingClient is already connected it will return responseCode.OK, so no need to check for isReady.

What is a clean way to wait for a response?

I am sending a message(custom protocol, no HTTP) to my server and want to wait for a response. It is working with the following code:
class Connection {
val messages: Observable<Message>
fun sendMessageWithAnswer(message: Message, timeout:Int = 10): Observable<Answer> {
if (!isConnected) {
return Observable.just(Answer.NoConnection)
}
val result = BehaviorSubject.create<Answer>()
val neverDisposed = messages.filter {
it.header.messageId == message.header.messageId
}
.map { Answer.Success(it) as Answer}
.mergeWith(Observable.timer(timeout.toLong(), TimeUnit.SECONDS)
.map { Answer.Timeout })
.take(1).singleOrError()
.subscribe(
{result.onNext(it)},
{
// Should never happen
throw IllegalStateException("Waiting for answer failed: $it")
}
)
sendMessage(message)
return result
}
}
The problem with this solution that "neverDisposed" gets never disposed, is this a memory leak?
My other solutions are not working for this test case:
#Test
fun ImmediateAnswer() {
prepare()
val message = ...
val answerObservable = connection.sendMessageWithAnswer(message, timeout = 1)
connection.receiveMessage(message)
val answer = answerObservable.test()
answer.awaitCount(1)
Thread.sleep(1000)
Assert.assertEquals(1, answer.valueCount())
Assert.assertEquals(Answer.Success(message), answer.values()[0])
}
Do you have a cleaner solution for this problem?

How to write rx concatArrayEager equivalent in Kotlin CoRoutine?

I would like to convert my rxJava Code to Kotlin CoRoutine.
Below is the code makes both the api and db call and returns the data to UI whatever comes first. Let us say if DB response happens to be quicker than the api. In that case still, the api response would continue until it receives the data to sync with db though it could have done the UI update earlier.
How Would I do it?
class MoviesRepository #Inject constructor(val apiInterface: ApiInterface,
val MoviesDao: MoviesDao) {
fun getMovies(): Observable<List<Movie>> {
val observableFromApi = getMoviesFromApi()
val observableFromDb = getMoviesFromDb()
return Observable.concatArrayEager(observableFromApi, observableFromDb)
}
fun getMoviesFromApi(): Observable<List<Movie>> {
return apiInterface.getMovies()
.doOnNext { it ->
it.data?.let { it1 -> MoviesDao.insertAllMovies(it1) }
println("Size of Movies from API %d", it.data?.size)
}
.map({ r -> r.data })
}
fun getMoviesFromDb(): Observable<List<Movie>> {
return MoviesDao.queryMovies()
.toObservable()
.doOnNext {
//Print log it.size :)
}
}
}
As the first step you should create suspend funs for your ApiInterface and MovieDao calls. If they have some callback-based API, you can follow these official instructions.
You should now have
suspend fun ApiInterface.suspendGetMovies(): List<Movie>
and
suspend fun MoviesDao.suspendQueryMovies(): List<Movie>
Now you can write this code:
launch(UI) {
val fromNetwork = async(UI) { apiInterface.suspendGetMovies() }
val fromDb = async(UI) { MoviesDao.suspendQueryMovies() }
select<List<Movie>> {
fromNetwork.onAwait { it }
fromDb.onAwait { it }
}.also { movies ->
// act on the movies
}
}
The highlight is the select call which will simultaneously await on both Deferreds and act upon the one that gets completed first.
If you want to ensure you act upon the result from the network, you'll need some more code, for example:
val action = { movies: List<Movie> ->
// act on the returned movie list
}
var gotNetworkResult = false
select<List<Movie>> {
fromNetwork.onAwait { gotNetworkResult = true; it }
fromDb.onAwait { it }
}.also(action)
if (!gotNetworkResult) {
action(fromNetwork.await())
}
This code will act upon the DB results only if they come in before the network results, which it will process in all cases.
Something along those lines should work:
data class Result(val fromApi: ???, val fromDB: ???)
fun getMovies(): Result {
val apiRes = getMoviesFromApiAsync()
val dbRes = getMoviesFromDbAsync()
return Result(apiRes.await(), dbRes.await())
}
fun getMoviesFromApiAsync() = async {
return apiInterface.getMovies()
.doOnNext { it ->
it.data?.let { it1 -> MoviesDao.insertAllMovies(it1) }
println("Size of Movies from API %d", it.data?.size)
}
.map({ r -> r.data })
}
fun getMoviesFromDbAsync() = async {
return MoviesDao.queryMovies()
}
I don't know what you're returning, so I just put ??? instead.

Why am I getting a NullPointerException when fetching JSON?

I keep getting a NullPointerException at return place.
When I was debugging the app, the code skips the onFailure() and onResponse() methods.
Previously, this worked but I refactored it into the current classes.
class Repository private constructor() {
private val baseUrl: String = "http://api.openweathermap.org/"
val client = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY))
.build()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(MoshiConverterFactory.create())
.client(client)
.build()
val networkApi = retrofit.create(NetworkApi::class.java)
private object Holder { val INSTANCE = Repository() }
companion object {
val instance: Repository by lazy { Holder.INSTANCE }
}
fun fetchWeatherData(placeName: String): Place {
var place: Place? = null
val call: Call<Place> = networkApi.getPlaceWeather(placeName)
call.enqueue(object : Callback<Place> {
override fun onFailure(call: Call<Place>?, t: Throwable?) {
println(t?.message)
}
override fun onResponse(call: Call<Place>?, response: Response<Place>?) {
if (response != null && response.isSuccessful && response.body() != null) {
place = response.body() as Place
println(place.toString())
}
}
})
return place!!
}
}
class MainPresenter(private val view: MainContract.View, val context: Context) : MainContract.Presenter {
val repository = Repository.instance
...
override fun updateListOfPlaces() {
var places = mutableListOf<Place>()
for (index in 0 until favPlaceStrings.size) {
places.add(repository.fetchWeatherData(favPlaceStrings.elementAt(index)))
}
view.showFavouritePlaces(places)
}
}
The way you're using retrofit makes it have an asynchronous behaviour, meaning the code within onFailure and onResponse might run after or before you have a chance to return from fetchWeatherData. In other words, you cannot assume that place will have a value when you return from fetchWeatherData and this is actually what's happening, place is still null and calling !! will cause the null pointer exception you're having.
To fix this you either change the way you're using retrofit to be synchronous, or you use an approach like callbacks.
Personally, I prefer the callback approach / reactive streams and this you can check here.
Making the code synchronous will most likely lead to other issues such as network calls on the main thread, which are not allowed and crash the app.
You need to invert your logic. You cannot simply "return data" from a network call that you're waiting for
Instead, loop over the list, make requests, then show/update the view
for (index in 0 until favPlaceStrings.size) {
val call: Call<Place> = networkApi.getPlaceWeather(favPlaceStrings.elementAt(index))
call.enqueue(object : Callback<Place> {
override fun onFailure(call: Call<Place>?, t: Throwable?) {
println(t?.message)
}
override fun onResponse(call: Call<Place>?, response: Response<Place>?) {
if (response != null && response.isSuccessful && response.body() != null) {
val place: Place = response.body() as Place
places.add(place) // move this list to a field
println(place.toString())
view.showFavouritePlaces(places) // this is fine that it's inside a loop
}
}
})
}