Kotlin lambda function with arguments - android

I am currently developing an Android application that uses the Retrofit library for REST api usage.
For instance, I have the following code from MainActivity.kt :
fun userLogin(){
calls.userLogin() { updateUiComponents() }
}
fun updateUiComponents(){
Toast.makeText(applicationContext, "LAMBDA EXECUTED",Toast.LENGTH_SHORT).show()
}
And I have in a separate file the definition of the Retrofit calls:
fun userLogin(postActionMethod: () -> Unit){
val call = service.userLogin()
call.enqueue(object : Callback<LoginResponse>{
override fun onFailure(call: Call<LoginResponse>?, t: Throwable?) {
Log.i("ERROR RUNNING CALL", t?.message.toString())
}
override fun onResponse(call: Call<LoginResponse>?, response: Response<LoginResponse>?) {
postActionMethod()
}
})
}
After the Retrofit call is implemented and it is successful, reaching the onResponse method, I would like to send the Response object as a parameter of the lambda function back to the MainAcativity.kt. From the MainActivity.kt, the lambda function would use this information to perform some specific task.
Is that a way of defining a lambda function like this, with arguments? If it is the case, how can I pass the lambda function as a parameter like done on the following line:
calls.userLogin(body) { updateUiComponents() }
Thank u!

I don't know if I get what your problem is but a lambda does not need to do not have any parameter. You can easily do something like
fun userLogin(postActionMethod: (Response<LoginResponse>?) -> Unit){
val call = service.userLogin()
call.enqueue(object : Callback<LoginResponse>{
override fun onFailure(call: Call<LoginResponse>?, t: Throwable?) {
Log.i("ERROR RUNNING CALL", t?.message.toString())
}
override fun onResponse(call: Call<LoginResponse>?, response: Response<LoginResponse>?) {
postActionMethod(response)
}
})
}
so you consume it with
fun userLogin(){
calls.userLogin() { updateUiComponents(it) }
}
fun updateUiComponents(response: Response<LoginResponse>?){
Toast.makeText(applicationContext, "LAMBDA EXECUTED",Toast.LENGTH_SHORT).show()
}

Related

How to convert custom callback to coroutine

I am using Stripe library which provides me with custom callback functionality.
I want a custom callback convert to Kotlin coroutine
Here is the code
override fun retrievePaymentIntent(clientSecret: String): Flow<Resource<PaymentIntent>> = flow{
emit(Resource.Loading())
Terminal.getInstance().retrievePaymentIntent(clientSecret,
object : PaymentIntentCallback {
override fun onFailure(e: TerminalException) {}
override fun onSuccess(paymentIntent: PaymentIntent) {
emit(Resource.Success(paymentIntent))
}
})
}
The problem is I can't call emit function inside onSuccess/onFailure. The error shown in the picture.
Is it possible to change something here to make it work or how could I convert custom callback to coroutine?
You can use suspendCancellableCoroutine to model your callback-based one-shot request like so:
suspend fun retrievePaymentIntent(clientSecret: String): PaymentIntent =
suspendCancellableCoroutine { continuation ->
Terminal.getInstance().retrievePaymentIntent(clientSecret,
object : PaymentIntentCallback {
override fun onFailure(e: TerminalException)
{
continuation.resumeWithException(e)
}
override fun onSuccess(paymentIntent: PaymentIntent)
{
continuation.resume(paymentIntent)
}
})
continuation.invokeOnCancellation { /*cancel the payment intent retrieval if possible*/ }
}

Android Architecture repository with TCP source of information

My Android Application is base on a TCP protocol.
When I'm initializing a connection to the server, I'm sending a special bytes message and have to wait the response of the server.
In all the repositories example I have seen the repository have always methods to call the source of information with a return (from Android Developers) :
class UserRepository {
private val webservice: Webservice = TODO()
// ...
fun getUser(userId: String): LiveData<User> {
// This isn't an optimal implementation. We'll fix it later.
val data = MutableLiveData<User>()
webservice.getUser(userId).enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
data.value = response.body()
}
// Error case is left out for brevity.
override fun onFailure(call: Call<User>, t: Throwable) {
TODO()
}
})
return data
}
}
The function getUser return a data of LiveData.
In my app the method Login return nothing because I wait for the server to send bytes with a special code to know that is responding to my login request.
Is there a way to implement this pattern with TCP protocols like that ?
Thanks
They honestly should have just written the following code:
class UserRepository {
private val webService: WebService = TODO()
// ...
fun getUser(userId: String, successCallback: (User) -> Unit) {
webService.getUser(userId).enqueue(object : Callback<User> {
override fun onResponse(call: Call<User>, response: Response<User>) {
successCallback(response.body())
}
// Error case is left out for brevity.
override fun onFailure(call: Call<User>, t: Throwable) {
}
})
}
}
LiveData is not meant to represent one-off callbacks.
Then call it as
userRepository.getUser(userId) { user ->
// do whatever
}
For a proper reactive implementation, refer to https://stackoverflow.com/a/59109512/2413303

Methods returns a null value before Retrofit Callbacks are executed

When working with the MVVM pattern in Android developments, we create a repository class where we execute all the network requests. The problem is since retrofit's .enqueue() method is asynchronous, my method that calls .enqueue doesn't wait until the callback is obtained(which is pretty logical) and returns null.
One way to solve this problem is to pass MutableLiveData object to my repository method and set its value in the callback, but I don't want to observe all my ViewModel properties in my view(fragment).
What is the common way to solve this problem?
fun createRoute(newRoute: RouteToSend): String {
var responseMessage: String? = null
webService.createRoute(authToken!!, newRoute).enqueue(object: Callback<Message> {
override fun onFailure(call: Call<Message>, t: Throwable) {
Log.e(TAG, t.message!!)
}
override fun onResponse(call: Call<Message>, response: Response<Message>) {
response.body()?.let { responseMessage = it.message }
}
})
return responseMessage!!
}
Pass a callback as an argument, e.g.
createRoute(newRoute: RouteToSend, callback: CreateRouteListener)
with
interface CreateRouteListener {
fun onFailure()
fun onResponse(response: String)
}
and call the corresponding method when the async process finishes:
override fun onFailure(call: Call<Message>, t: Throwable) {
Log.e(TAG, t.message!!)
callback.onFailure()
}
override fun onResponse(call: Call<Message>, response: Response<Message>) {
response.body()?.let {
responseMessage = it.message
callback.onResponse(responseMessage)
}
}
Calling createRoute will then look like this:
createRoute(RouteToSend(), object: CreateRouteListener {
override fun onFailure() {
// handle failure
}
override fun onResponse(response: String) {
// handle response
}
}
Yes, using MutableLiveData is one way, on the other hand using callback mechanism is another and more suitable way.
If you want to use callbacks you can change your method like
fun createRoute(newRoute: RouteToSend, callback : (String?) -> Unit): String {
var responseMessage: String? = null
webService.createRoute(authToken!!, newRoute).enqueue(object: Callback<Message> {
override fun onFailure(call: Call<Message>, t: Throwable) {
Log.e(TAG, t.message!!)
callback(responseMessage)
}
override fun onResponse(call: Call<Message>, response: Response<Message>) {
response.body()?.let { responseMessage = it.message
callback(responseMessage)}
}
})
}
then you can call your createRoute method like this
createRoute(route_to_send_variable,
callback = {
it?.let {
// use the response of your createRoute function here
}
})

Recover the response of my service in another class with retrofit2 in kotlin

I have a call to my service with retrofit in which I get an answer and I need to send it to another class.
I have tried to save the response data in a ContentValues ​​and send them by means of a function but this does not work.
fun dataEmployee(name: String, numEmp: String): ConsultMovResponse? {
var cMov = PersonData(name, numEmp)
var pos: ConsultMovResponse?
RetrofitClient.instMov.consultMov(cMov).enqueue(object : Callback<ConsultMovResponse> {
override fun onResponse(call: Call<ConsultMovResponse>, response: Response<ConsultMovResponse>) {
pos = response?.body()
//return response, this code does not work.
return pos?
}
override fun onFailure(call: Call<ConsultMovResponse>, t: Throwable) {
println("Error : " + t.stackTrace.toString())
println("Error : " + t.message)
}
})
return pos?
}
The way you're using Retrofit, it'll execute the request asynchronously. This means that before it has a chance to finish the request, the function dataEmployee will return an uninitialised pos.
There are different ways to go about this, but an easy one is to propagate the callback. Say you define the function as:
fun dataEmployee(name: String, numEmp: String, callback: (ConsultMovResponse?) -> Unit)
The last argument is a function that should be called when onResponse is called. Something like:
override fun onResponse(call: Call<ConsultMovResponse>, response: Response<ConsultMovResponse>) {
callback(response?.body())
}
The way you can call now the method would be:
dataEmployee("Foo", "1234") {
// Use the implicit parameter `it` which will be the response
}
Edit
For the error you can follow a similar process. Let's change dataEmployee to:
fun dataEmployee(name: String, numEmp: String, onSuccess (ConsultMovResponse?) -> Unit, onFailure: (Throwable) -> Unit)
On failure you can then call:
override fun onFailure(call: Call<ConsultMovResponse>, throwable: Throwable) {
onFailure(throwable)
}
Now you call dataEmployee like so:
dataEmployee("foo", "1234",
onSuccess = { /*handle success*/ },
onFailure = { /*`it` will be the error */ })

Retrofit2 enqueue onResponse() in Kotlin

Function returns null before data.value is set in asynchronous onResponse().
How to make it first fetch data and then return that data?
fun getNews(code: String): LiveData<List<News>>{
val call = service.getNewsByCountry(code, Constant.API_KEY)
var data = MutableLiveData<List<News>>()
call.enqueue(object : Callback<NewsResponse> {
override fun onFailure(call: Call<NewsResponse>?, t: Throwable?) {
Log.v("retrofit", "call failed")
}
override fun onResponse(call: Call<NewsResponse>?, response: Response<NewsResponse>?) {
data.value = response!!.body()!!.articles
}
})
return data
}
You're making an asynchronous call, so data.value will not be set until that asynchronous call resolves. However, since you are generating a MutableLiveData, you should be able to observe, which will give you an update when your asynchronous call sets the value.
Just use object:Callback
accessTocken.enqueue(object : Callback<AccessToken> {
override fun onFailure(call: Call<AccessToken>, t: Throwable) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onResponse(call: Call<AccessToken>, response: Response<AccessToken>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
})
Try
fun getNews(code: String): LiveData<List<News>>{
val call = service.getNewsByCountry(code, Constant.API_KEY)
var data = MutableLiveData<List<News>>()
doAsync {
call.enqueue(object : Callback<NewsResponse> {
override fun onFailure(call: Call<NewsResponse>?, t: Throwable?) {
Log.v("retrofit", "call failed")
}
override fun onResponse(call: Call<NewsResponse>?, response: Response<NewsResponse>?) {
data.value = response!!.body()!!.articles
}
})
}
return data
}
If not exists doAsync try add follow anko dependency on your app/build.gralde
implementation "org.jetbrains.anko:anko-design:0.10.5"
Here I found a more extensive answer to your question on this article.
Before retrofit 2.6.0 you have to call enqueue() and implement callbacks. Now it is noτ necessary anymore.
You should change from this:
class TodoRepository {
var client: Webservice = RetrofitClient.webservice
fun getTodo(id: Int): LiveData<Todo> {
val liveData = MutableLiveData<Todo>()
client.getTodo(id).enqueue(object: Callback<Todo>{
override fun onResponse(call: Call<Todo>, response: Response<Todo>) {
if (response.isSuccessful) {
// When data is available, populate LiveData
liveData.value = response.body()
}
}
override fun onFailure(call: Call<Todo>, t: Throwable) {
t.printStackTrace()
}
})
// Synchronously return LiveData
// Its value will be available onResponse
return liveData
}
}
to this:
class TodoRepository {
var client: Webservice = RetrofitClient.webservice
suspend fun getTodo(id: Int) = client.getTodo(id)
}
Here you have the complete answer -> https://proandroiddev.com/suspend-what-youre-doing-retrofit-has-now-coroutines-support-c65bd09ba067

Categories

Resources