How to convert Play Store API calls to suspend functions - android

Converting my own http calls to KTor (& therefore coroutines) is a bit hollow, or at least not terribly satisfying, without also being able to convert Play Store API calls and their callbacks to use suspend functions.
This general answer, with specific example of a different API, is fabulous and I've managed to use it to produce something for "startBillingClient", as follows
private suspend fun BillingClient.startConnection(value: Int) =
suspendCoroutine<Unit> { cont ->
val callback = object: BillingClientStateListener {
override fun onBillingSetupFinished(billingResult:
BillingResult) {
isServiceConnected = true
}
override fun onBillingServiceDisconnected() {
isServiceConnected = false
startConnection(this)
}
}
startConnection(callback)
}
but now I'm struggling with producing something similar for querySkuDetailsAsync, which seems to be due to needing to pass parameters in. Should be easy, but...

Related

Run evaluateJavascript() function sequentially using Kotlin Corutines

I'm working on a pet project where I'm trying to create a hybrid app using a WebView. The web platform that I run in the WebView sends events to the WebView/App through a #JavascriptInterface object. I can also command the web navigation by running a set of javascript functions against the web platform via the WebView using the evaluateJavascript(String, (String) -> Unit) function.
What I'm trying to achieve right now is that these commands that I execute through the evaluateJavascript(String, (String) -> Unit) function run sequentially. I might execute these commands from many different places at the same time, so I want them to run, wait for the callback from the evaluateJavascript() function to get called, and then execute the next command in the queue.
This is what I have in my custom WebView class:
val scriptQueue = mutableListOf<String>()
fun queueEvaluateJavascript(script: String) {
if (webViewIsLoading) {
scriptQueue.add(script)
} else {
scriptQueue.add(script)
runScriptQueue()
}
}
fun runScriptQueue() {
for (script in scriptQueue) {
evaluateJavascript(script, { })
}
scriptQueue.clear()
}
As you can see this is a super basic approach, and I don't really account for the evaluateJavascript() callback. Ideally, I'd like to find a way to flat map each of this evaluateJavascript() calls so we execute one after another, but waiting for the callback to go through.
With RxJava I think I'd create an Observable and then have the evaluateJavascript() callback trigger the subscriber's onNext(). Since, I'm using Kotlin Coroutines I wanted to do something with Coroutines, so I can queue these evaulateJavascript() calls. But I'm not 100% sure what would be the equivalent here.
That would be a nice problem to approach with coroutines.
The usual way to convert callback based APIs to suspend functions is the following:
suspend fun evaluateJs(script: String) = suspendCoroutine<String> { cont ->
evaluateJavascript(script) { result ->
cont.resume(result)
}
}
You can then use that in combination maybe with a Channel (to serve as a queue) and a coroutine that processes this channel:
class MyWebView(context: Context) : WebView(context) {
private val jsQueue = Channel<String>(BUFFERED)
fun startJsProcessingLoopIn(scope: CoroutineScope) {
scope.launch {
for (script in jsQueue) {
evaluateJs(script)
}
}
}
// you could also make this function non-suspend if necessary by calling
// sendBlocking (or trySend depending on coroutines version)
suspend fun queueEvaluateJavascript(script: String) {
jsQueue.send(script)
}
private suspend fun evaluateJs(script: String) = suspendCoroutine<String> { cont ->
evaluateJavascript(script) { result ->
cont.resume(result)
}
}
}
Alternatively you can create your own coroutine scope and make sure to tie it with some sort of lifecycle of your webview (I'm not familiar with WebView so I'll let you judge which kind of method is correct):
class MyWebView2(context: Context) : WebView(context) {
// you can even further customize the exact thread pool used here
// by providing a particular dispatcher
private val jsProcessingScope = CoroutineScope(CoroutineName("js-processing"))
private val jsQueue = Channel<String>(BUFFERED)
// this starts the loop right away but you can also put this in a method
// to start it at a more appropriate moment
init {
jsProcessingScope.launch {
for (script in jsQueue) {
evaluateJs(script)
}
}
}
// you could also make this function non-suspend if necessary by calling
// sendBlocking (or trySend depending on coroutines version)
suspend fun queueEvaluateJavascript(script: String) {
jsQueue.send(script)
}
private suspend fun evaluateJs(script: String) = suspendCoroutine<String> { cont ->
evaluateJavascript(script) { result ->
cont.resume(result)
}
}
fun someCloseOrDisposeCallback() {
jsProcessingScope.cancel()
}
}

How To Use Async/Await/Coroutines In OnCompleteListener Firebase

I am building a client application which uses Firebase for two things:
User Authentication
Using a realtime database
I have managed to set up everything correctly on my client and on my backend server (using Firebase's Admin SDK) and am able to correctly authenticate users and allow them to read/write to the database.
I am also using Retrofit2 to send requests from the client to the backend.
As part of allowing users access to the database, it is needed to send the user's token to the backend so the user can be verified.
To do this, I have the following logic:
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
user.getIdToken(false).addOnCompleteListener {
if (it.isSuccessful) {
val token = it.result?.token
//retrofit logic to send request happens from here
}
}
As you can see, getting the Id token of the user is an asynchronous call and in the current code base that I have, I have this code block for each one of my calls to the backend (duplication).
I want to know how I can export this snippet to a function (maybe a suspend method?) so that it can be reused for every call to the backend
I have searched online and have seen many SO questions, but none that fit this scenario.
I have thought about passing in a callback, but I have several methods that communicate to the backend, and each of them will require a different callback method.
The solution I am looking for looks something like this:
fun fetchDataFromDB() {
getIdTokenForUser()
//wait till it finishes and then
//perform request to DB
}
fun updateDataInDB() {
getIdTokenForUser()
//wait till it finishes and then
//perform request to DB
}
//......
I have tried reading about and implementing coroutines, but I lack the knowledge to do so correctly.
EDIT
Thanks to #Doug Stevenson for his answer and direction, I have managed to construct the following:
private suspend fun getUserIdToken(user: FirebaseUser) = coroutineScope {
val job = async {
user.getIdToken(false).result?.token
}
job.await()
}
And I use it in this fashion:
fun updateDB(context: Context) = runBlocking {
val user = FirebaseAuth.getInstance().currentUser
if (user != null) {
val token = getUserIdToken(user)
}
}
Is this the correct approach? Since the answers given below present a different implementation.
getIdToken is asynchronous returns a Task object. If you want to use a Task object in a Kotlin coroutine, you can use the library kotlinx-coroutines-play-services to add an extension method await() to the Task that makes it usable in a coroutine. With that, you can write something like this:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.9"
import kotlinx.coroutines.tasks.await
suspend fun getIdTokenForUser(user: FirebaseUser): GetTokenResult {
return try {
user.getIdToken(false).await()
}
catch (e: Exception) {
// handle error
}
}
You might have to update the types here - I didn't try to compile or test this.
See also:
Android kotlin task to be executed using coroutines
Coroutines And Firebase: How to Implement Javascript-like Promise.all()
Using Firebase with Kotlin coroutines
In order to go from a callback based API like the following one:
val myCallback = object : ServiceCallback() {
override fun onResult(theobject: Something) {
// your callback code here
}
override fun onFailure(ex: Throwable) {
// error handling
}
}
theService.enqueue(callback)
You can use suspendCoroutine
What it does is that it suspends execution until the continuation is satified by the callback. So you can write a KTX like the following:
suspend fun Service.getSomething(): Something = suspendCoroutine{ cont ->
val callback = object : ServiceCallback(){
override fun onSuccess(data: Something): Unit = cont.resume(data)
override fun onFailure(ex: Throwable): Unit = cont.resume(ex)
}
this.enqueue(callback)
}

Unit Test for retrofit2.Call

Since Retrofit 2.6.0 has support for Coroutines and the library of kotlinCoroutinesAdapter is deprecated I started to refactor my API calls. I changed it and is working fine but I have problems with Unit Tests.
This is how I had my calls:
//Retrofit API
#GET("playlists/{playlistId}")
fun getPlaylistAsync(#Path("playlistId") id: String): Deferred<PlaylistData>
//DataSource
fun getPlaylistAsync(playlistId: String): Deferred<PlaylistData> = playlistApi.getPlaylistAsync(playlistId)
//Repository
override suspend fun getPlaylist(playlistId: String): PlaylistDomain {
val playlistDeferred = remoteDataSource.getPlaylistAsync(playlistId)
return playlistRemoteMapper.map(playlistDeferred.await())
}
And this is what I changed to make it work:
//Retrofit API
#GET("playlists/{playlistId}")
fun getPlaylistAsync(#Path("playlistId") id: String): Call<PlaylistData>
//DataSource
fun getPlaylistAsync(playlistId: String): Call<PlaylistData> = playlistApi.getPlaylistAsync(playlistId)
//Repository
override suspend fun getPlaylist(playlistId: String): PlaylistDomain {
val playlistDeferred = remoteDataSource.getPlaylistAsync(playlistId)
return playlistRemoteMapper.map(playlistDeferred.await())
}
Basically is the same... the await() used with Deferred and with Call is different but everything else is so similar.
As I said the problems comes with the test. Here I have what I did for the first example:
#Test
fun `getPlaylist() maps correctly the playlist and the favorite request when is favorite`() = runBlocking {
val playlist = PlaylistDataBuilder().getPlaylistData(id = PLAYLIST_ID)
every { playlistDataSource.getPlaylistAsync(PLAYLIST_ID) } answers { async { playlist } }
val response = playlistRepository.getPlaylist(PLAYLIST_ID)
assertTrue(response.isFavorite)
}
Now I want to change this to fit the second example... but async returns a Deferred and now I need Call to fit my needs. I tried to mock Call and do the following:
#Test
fun `getPlaylist() maps correctly the playlist and the favorite request when is favorite`() = runBlocking {
val playlist = PlaylistDataBuilder().getPlaylistData(id = PLAYLIST_ID)
every { playlistCall.await() }
every { playlistDataSource.getPlaylistAsync(PLAYLIST_ID) } answers { playlistCall }
val response = playlistRepository.getPlaylist(PLAYLIST_ID)
assertTrue(response.isFavorite)
}
The problem here is that playlistCall.await() must be into a suspend method so I cannot make it work this way. I would need a way to make a Unit Test for retrofit Call.

Convert Listener to Single in RxJava2

I am using the Play Services Auth api Phone and so far I have the foll
fun startSmsListener() {
val client = SmsRetriever.getClient(applicationContext /* context */);
val task = client.startSmsRetriever();
task.addOnSuccessListener(object : OnSuccessListener<Void> {
override fun onSuccess(p0: Void?) {
//do somethin
}
})
task.addOnFailureListener(object : OnFailureListener {
override fun onFailure(p0: Exception) {
//Handle error
}
})
}
Now I want to put this in an SmsManager class and convert it into an Single/Observable so I can handle it in a reactive way in my viewmodel. How can I do that?
So far I've got this:
var single = Single.create(SingleOnSubscribe<Void> { e ->
val task = client.startSmsRetriever()
task.addOnSuccessListener {
e.onSuccess(it)
}
task.addOnFailureListener {
e.onError(it)
}
})
But I am unsure as to whether this code is correct or not, whether there is something im missing like removing the listeners after disposal.
Any help?
You are interested in a "boolean" value - either connected or not connected, thus instead of Single you should use Completable:
Completable.create { emitter ->
val client = SmsRetriever.getClient(applicationContext)
val task = client.startSmsRetriever()
task.addOnSuccessListener { emitter.onComplete() }
task.addOnFailureListener { emitter.tryOnError(it) }
}
While creating a Completable manually will work, you might also have a look at the RxTask project. It provides "RxJava 2 binding for Google Play Services Task APIs".
If you need it just in one place, an extra library would certainly be an overkill. But if you plan to use more Play Services together with RxJava, it might be worth a look...
It doesn't (yet) provide a wrapper explicitly for SmsRetriever, but the general task helper classes would probably be enough:
val client = SmsRetriever.getClient(applicationContext)
val smsReceiver = CompletableTask.create(client::startSmsRetriever)

Kotlin Coroutine to escape callback hell

I'm trying to use Kotlin's coroutines to avoid callback hell, but it doesnt look like I can in this specific situation, I would like some thougths about it.
I have this SyncService class which calls series of different methods to send data to the server like the following:
SyncService calls Sync Student, which calls Student Repository, which calls DataSource that makes a server request sending the data through Apollo's Graphql Client.
The same pattern follows in each of my features:
SyncService -> Sync Feature -> Feature Repository -> DataSource
So every one of the method that I call has this signature:
fun save(onSuccess: ()-> Unit, onError:()->Unit) {
//To Stuff here
}
The problem is:
When I sync and successfully save the Student on server, I need to sync his enrollment, and if I successfully save the enrollment, I need to sync another object and so on.
It all depends on each other and I need to do it sequentially, that's why I was using callbacks.
But as you can imagine, the code result is not very friendly, and me and my team starting searching for alternatives to keep it better. And we ended up with this extension function:
suspend fun <T> ApolloCall<T>.execute() = suspendCoroutine<Response<T>> { cont ->
enqueue(object: ApolloCall.Callback<T>() {
override fun onResponse(response: Response<T>) {
cont.resume(response)
}
override fun onFailure(e: ApolloException) {
cont.resumeWithException(e)
}
})
}
But the function in DataSource still has a onSuccess() and onError() as callbacks that needs to be passed to whoever call it.
fun saveStudents(
students: List<StudentInput>,
onSuccess: () -> Unit,
onError: (errorMessage: String) -> Unit) {
runBlocking {
try {
val response = GraphQLClient.apolloInstance
.mutate(CreateStudentsMutation
.builder()
.students(students)
.build())
.execute()
if (!response.hasErrors())
onSuccess()
else
onError("Response has errors!")
} catch (e: ApolloException) {
e.printStackTrace()
onError("Server error occurred!")
}
}
}
The SyncService class code changed to be like:
private fun runSync(onComplete: () -> Unit) = async(CommonPool) {
val syncStudentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncStudents()
}
val syncEnrollmentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncEnrollments()
}
syncStudentProcess.await()
syncEnrollmentProcess.await()
onComplete()
}
It does execute it sequentially, but I need a way to stop every other coroutine if any got any errors. Error that might come only from Apollo's
So I've been trying a lot to find a way to simplify this code, but didn't get any good result. I don't even know if this chaining of callbacks can be simplify at all. That's why I came here to see some thoughts on it.
TLDR: I want a way to execute all of my functions sequentially, and still be able to stop all coroutines if any got an exception without a lot o chaining callbacks.

Categories

Resources