Completion Handler Android Kotlin - android

I´m sorry if this question has been asked previously, I really couldn´t find anything not even simillar! I´m as well sorry if the question is dumb, I´m an iOS Developer and I´m a bit lost here in Android...
So I´m using Fuel Library(https://github.com/kittinunf/Fuel) to GET JSON data from an API... In Swift there´s something called completion handler that whenever the function finished, it will return it and immediately run the code inside it. This is an example of it in Swift:
func hardProcessingWithString(input: String, completion: (result: String) -> Void) {
...
completion("we finished!")
}
What I need is to do something similar with this following function that I have in Kotlin.
fun recomendationsData() {
Fuel.get("https://rss.itunes.apple.com/api/v1/us/apple-music/hot-tracks/10/explicit.json").response { request, response, result ->
println(request)
println(response)
val (bytes, error) = result
if (bytes != null) {
val str = String(bytes)
val obj = JSONObject(str)
val resultsP = obj.getJSONObject("feed")
val results = resultsP.getJSONArray("results")
for (i in 0..(results.length() - 1)) {
val o = results.getJSONObject(i)
trackName1.add(o.getString("name"))
trackArtist1.add(o.getString("artistName"))
trackImage1.add(o.getString("artworkUrl100"))
}
}
}
}
I´ve read about something called "callback" but I really don´t understand how it works, nor how to implement it(The task must be done Asynchronously).
Thank you very much again!
Regards

In this case the syntax is similar to swift:
fun recommendationsData(callback: (String) -> Unit) {
Then in your function you have a function called callback that you can call with the result (change String to whatever you're returning).
Then change your function invocation from recommendationsData() to either recommendationsData(doSomething) or
recommendationsData {
doSomethingWith(it) // or you can get named argument
// do some more stuff
}

Related

What is the type of an async result?

I'm trying to create some coroutines (async) in a loop . I want to start everything in parallel then wait for them all to finish before proceeding. The documentation provides the following example:
coroutineScope {
val deferreds = listOf( // fetch two docs at the same time
async { fetchDoc(1) }, // async returns a result for the first doc
async { fetchDoc(2) } // async returns a result for the second doc
deferreds.awaitAll() // use awaitAll to wait for both network requests
}
but this requires that all the class instantiations be known in advance. However with a varying number of instantiations this is not practical. As a work around I found that the following works:
given a mutable List of class objects from class MyObject and MyObject has a method called myDo()
private val mObjects = mutableListOf<MyObject>()
and ignoring error checking and assuming the list has 2 or more objects then the following works but it's kind of clunky and not very elegant
coroutineScope {
val pd = async { myObjects[0].myDo() }
val dds = mutableListOf(pd)
for (i in 1..numObjects - 1) {
dds.add(async {mObjects[i].myDo() })
}
val nds = dds.toList()
nds.awaitAll()
}// end coroutineScope
What I'd hope to do was something like
val dds = mutableListOf<Job>()
for (i in 0..numObjects - 1) {
dds.add(async {mObjects[i].myDo() })
}
val nds = dds.toList()
nds.awaitAll()
but this doesn't work as the async result is a
Deferred<out T> : Job
interface not a Job interface. The problem with this is in the line
val dds = mutableListOf<Job>()
I don't know what to use in place of Job. That is, for async what is T?
Any help or suggestions would be appreciated
T in this case is whatever type myDo() returns.
I think you are overcomplicating it by creating the extra MutableLists. You can do it like this:
val results = coroutineScope {
mObjects.map { obj ->
async { obj.myDo() }
}.awaitAll()
}
results will be a List<MyDoReturnType>.
Edit: I just realized, since it wasn't obvious to you that the type of a Deferred is whatever the async lambda returns, maybe it's because myDo() doesn't return anything (implicitly returns Unit). If that's the case, you should use launch instead of async. The only difference between them is that async's lambda returns something and launch's doesn't. Deferred inherits from Job because a Deferred is a Job with a result. If myDo() doesn't return anything, your code should look like the following, with no result.
coroutineScope {
for (obj in mObjects) launch { obj.myDo() }
}
The answer from TenFour04 provided the key to my answer The following code works for me
coroutineScope {
val dds = mutableListOf<Deferred<Unit>>()
for (item in mObjects) { dds.add(async {item.myDo() }) }
val nds = dds.toList()
nds.awaitAll()
}
Am I stupid!!! or what. After I figured it out, the answer is almost trivial. The best solution I found is
private val mObjects = mutableListOf<MyObject>()
coroutineScope {val deferreds = listOf(mObjects.size){async{mObjects[it].myDo()}}
deferreds.awaitAll()
}// end coroutineScope
I like this better than the map solution as it doesn't create an intermediate Pair set

How to implement an optional callback from scratch in Kotlin?

I have this function, which works wonderfully.
inline fun <reified T:Any>String.parse() : T {
return GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create().fromJson<T>(this, T::class.java)
}
fun request (callback: (MyClass)->Unit) {
val url = URL("someurl").readText()
val myObject : MyClass = str.parse()
callback(myObject)
}
net.request {
it.myFunction()
println (it.myString)
}
myObject automatically filled with an object of type MyClass, and returned correctly to the callback.
Now I want to catch the error, and have the callback can return it as well.
fun request (callback: (MyClass?, Exception?)->Unit) {
try {
val url = URL("someurl").readText()
val myObject : MyClass = str.parse()
callback(myObject, null)
}
catch (e: Exception) {
callback(null, e)
}
}
net.request { response, error ->
if (response != null ) { // do something }
else { // report something }
}
But this is ugly, because no matter what, I will have to force the callback to always have two parameters, but only one is present at a time. So I'm searching for optional callback methods. I want to be able to call the method like this:
net.request {
onSuccess { response -> // do something }
onError { error -> // report something }
}
Or probably:
net.request
.onSuccess { response -> // do something }
.onError { error -> // report something }
If I don't want to handle the error, I simply make a call like this:
net.request {
onSuccess { // do something with 'it' }
}
What I can found over the internet is overwriting the existing callback methods like this. This is not what I want. I want to write that callback from scratch. Looking at the source code sometimes doesn't help either because the code is in Java, and I don't understand Java. Not yet.
And I understand that major library in Kotlin like retrofit or JavaRx probably already implement something like this, but I just want to know the bare minimum code needed to do this, as this is how I learn. I just can't find the correct tutorial for this.
You can try this library out. It helps model success/failure operations concisely. Your callback can then take in your MyClass object wrapped this way Result<MyClass, Exception> or just Result.
To pass a value to your callback, you then do Callback(Result.of(MyClass)) for a successful operation or a Callback(Result.of(Exception())) in case of a failure.
You can then consume the callback by using any of the functions below
//if successful
result.success {
}
//if failed
result.failure {
}
//fold is there, if you want to handle both success and failure
result.fold({ value ->
//do something with value
}, { error ->
//do something with error
})
Hope this helps :)
Edit: As #user2340612 pointed out, Result has been added to the Kotlin stdlib. See here for more details

good idea or not good : to use coroutines for response Kotlin

i want to ask a good developers. Maybe anyone can better explaine. Somewhere in web i found that few authors used coroutines instead for example asynctasks . Just trying to improve myself . Here a small part of code which i used . Just want to know - it's good or no. If no - how to make it's better or maybe in final im using this in wrong way.
fun demoCall(callback: OnResponse) {
CoroutineScope(Dispatchers.Main).launch {
val result = withContext(Dispatchers.IO) {
Api.getResponse("GET", ApiConstants.test_endpoint)//networkOnMainThread exception if i will not use withContext
}
callback?.onResponse(result))
}
}
This example is work . But im not sure it's good usage.
If back to past ,
getResponse
was in asyncTask. Call was same with annonymus callback.
If to use this way is good , looks like i can use this part without callback ?
Just like this
fun demoCall() {
CoroutineScope(Dispatchers.Main).launch {
val result = withContext(Dispatchers.IO) {
Api.getResponse("GET", ApiConstants.test_endpoint)
}
//do anything with result
//populate views , make new response etc..
}
will be very happy if any tell me - is it ok or no :) Regards
I prefer making asynchronous calls be seen like synchronous in caller's view using suspend keyword.
For example,
suspend fun demoCall(): String {
return withContext(Dispatchers.IO) {
Api.getResponse("GET", ApiConstants.test_endpoint) // let's assume it would return string
}
}
and caller can use it
CoroutineScope(Dispatchers.Main).launch {
val result = demoCall() //this is async task actually, but it seems like synchronous call here.
//todo something with result
}

Fuel httpGet() responseString, Failure: com.github.kittinunf.fuel.core.BubbleFuelError: null

Problem
I want to get the result of a get request by doing a synchronous call to an API by using Fuel as Httpclient.
I'm using Fuel in an Android (Anko) project.
The call is just a simple get request which always fails with this error:
Failure: com.github.kittinunf.fuel.core.BubbleFuelError: null
Caused by: com.github.kittinunf.fuel.core.BubbleFuelError: null
Background
I want to make a function for returning a result of a simple get request using Fuel. But I'm not able to retrieve the the result synchronous.
I cannot find any useful information about this subject on the internet.
I tried to await the result by using coroutines and use the awaitStringResponse function. --> Did not worked as expected.
Just responded to a Github issue covering this topic (marked as bug).
https://github.com/kittinunf/fuel/issues/606
Is there some workaround?
Code example
This code is working:
requestUrl.httpGet().responseString { _, _, result ->
when (result) {
is Result.Success -> {
// do something on success
}
is Result.Failure -> {
// do something on fail
}
}
}
But using this function, I am not able to return the result.
This code is NOT working
val (_,_,result)= Fuel.get(requestUrl).responseString()
I found a way to solve this with kotlin coroutines.
fun doRequest() = runBlocking {
val (_, _, result) = Fuel.get("https://jsonplaceholder.typicode.com/posts/1").awaitStringResponse()
result
}
Using runBlocking will block the current thread until it's completed.
Source: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
When you don't want to block the current thread you can start this function in a new thread like this:
Thread(
Runnable {
val result = doRequest()
Log.e("Result", result)
}
).start()
If someone knows a better way to handle this, please show your solution.

Multiple retrofit2 requests using Flowable in Kotlin

In order to improve my skills in kotlin, Rx, Retrofit2 I've decided to do a demo project.
The demo project consist to display posts in a recycler view then display details of the post in a detail activity.
I've encountered difficulties displaying data coming from different api call: the user name, the title, the body of the post and the number of comments of the post.
My problem is that I would like to do multiple request and then have all the data needed in order to display them in the detail activity. Which mean doing a call that give me the user name and then a call that give me the number of comments for the post. The title and the body of the post are coming from a request done in the main activity I just transmit it with the bundle to the detail activity.
Api calls:
// return the comments for the post 1
http://jsonplaceholder.typicode.com/comments?postId=1
// return the information of the user 2
http://jsonplaceholder.typicode.com/users/2
// call used to display posts in the main activity
http:/jsonplaceholder.typicode.com/posts
I'm still new on Rx, I was thinking to use a flatMap but I don't know how to use it with Flowable in kotlin..
var post = viewModel.getPost()
var userStream: Flowable<User> = postService.getUser(post.userId)
var commentsByPostIdCall: Flowable<List<Comment>> = postService.getCommentsByPostId(post.id)
userStream.subscribeOn(Schedulers.io())
.subscribe(object : Subscriber<User> {
override fun onError(t: Throwable?) {
Log.d(this.toString(), " Read of users failed with the following message: " + t?.message);
}
override fun onNext(user: User) {
userTextView.text = user.name
title.text = post.title
body.text = post.body
}
override fun onComplete() {
}
override fun onSubscribe(s: Subscription?) {
if (s != null) {
s.request(1)
}
}
})
I have put the second call in a method getNumberComments:
private fun getNumberComments(commentsByPostIdCall: Flowable<List<Comment>>): Int {
var listComments = listOf<Comment>()
var listCommentSize = 0
commentsByPostIdCall
.subscribeOn(Schedulers.io())
.subscribe(object : Subscriber<List<Comment>> {
override fun onError(t: Throwable?) {
Log.d(this.toString(), " Read of comments failed with the following message: " + t?.message);
}
override fun onNext(comment: List<Comment>) {
listComments = comment
}
override fun onComplete() {
print("onComplete!")
listCommentSize = listComments.size
}
override fun onSubscribe(s: Subscription?) {
if (s != null) {
s.request(1)
}
}
})
return listCommentSize
}
Other think that I've noticed is that sometimes the stream didn't go to onComplete, sometimes it remains blocked on onNext. Don't understand why?
Any help will be much appreciate! Thanks a lot :)
this is how i would solve it:
Flowable.zip<User, Comments, Pair<User, Comments>>(
postService.getUser(postId),
postService.getCommentsByPostId(postId),
BiFunction { user, comments -> Pair(user, comments) })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.bindToLifecycle(this)
.map { (first, second) -> Triple(first, second, ExtraDatasFromSomewhere) }
.subscribe({
Log.d("MainActivity", "OnNext")
}, {
Log.d("MainActivity", "OnError")
}, {
Log.d("MainActivity", "OnComplete")
})
Use the zip or zipWith functions to achieve your goal if the retrofit2 calls dont depent on each other.
You can find out more here:
RxZip() : http://reactivex.io/documentation/operators/zip .
You can easily map the data from the server with the mainActivity data together like this:
.map { (first, second) -> Triple(first, second, ExtraDatasFromSomewhere) }
Kotlin has a very beautiful syntax for lambda functions so i would encourage you to use them with the specific subscribe function:
subscribe() : http://reactivex.io/RxJava/javadoc/io/reactivex/Flowable.html#subscribe(io.reactivex.functions.Consumer,%20io.reactivex.functions.Consumer,%20io.reactivex.functions.Action)
Also very important to note that i did not use only the raw Rxjava2 lib. i used the libs below:
RxAndroid
for observeOn(AndroidSchedulers.mainThread()) to get the mainThread. This is because you manipulated the UI without specifying the thread you subscribed on. With this you can achieve that your subscription will be handled on the mainThread.
RxLifecycle
for .bindToLifecycle(this) this will make sure you don't leave memory leak if the activity is closed but your retrofit2 call did not finished
I've just adapted the solution suggested by Kioba with my needs. I post this here in case it can be useful to someone.
I don't know if it's an elegant way to obtain the number of comments though. I've just used List < Comment > instead of Comment and then I do something like it.second.size.toString() for obtaining the number of comments.
Since I only need two data: user and comment I decided to use Pair instead of Triple.
Flowable.zip<User, List<Comment>, Pair<User, List<Comment>>>(
postService.getUser(post.id),
postService.getCommentsByPostId(post.id),
BiFunction { user, comments -> Pair(user, comments) })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.map { (first, second) -> Pair(first, second) }
.subscribe({
Log.d("MainActivity", "OnNext")
userTextView.text = it.first.name
title.text = post.title
body.text = post.body
number_comments.text = it.second.size.toString()
}, {
Log.d("MainActivity", "OnError")
}, {
Log.d("MainActivity", "OnComplete")
})

Categories

Resources