How to use nested suspend in Kotlin android - android

I have this function in Kotlin:
class DictionaryWorker constructor(
context: Context,
private val workerParameters: WorkerParameters,
private val apiInterface: ApiInterface
) :
KneuraWorker(context, workerParameters), BaseDataSource {
private var isJobSuccess: Boolean = false
override suspend fun doWorkerJob(): Result = withContext(Dispatchers.IO) {
val call = apiInterface.downloadDictionaryFille(DICTIONARY_FILE_URL)
call!!.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(
call: Call<ResponseBody?>?,
response: Response<ResponseBody?>
) {
if (response.isSuccessful) {
} else {
Log.d("TAG", "server contact failed")
isJobSuccess = false
}
}
override fun onFailure(call: Call<ResponseBody?>?, t: Throwable?) { }
})
return#withContext if (isJobSuccess)
Result.success()
else
Result.failure()
}
}
What is currently happening:
Before this block-1 below
call!!.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(
call: Call<ResponseBody?>?,
response: Response<ResponseBody?>
) {
if (response.isSuccessful) {
} else {
Log.d("TAG", "server contact failed")
isJobSuccess = false
}
}
override fun onFailure(call: Call<ResponseBody?>?, t: Throwable?) { }
})
This block-2 executes
return#withContext if (isJobSuccess)
Result.success()
else
Result.failure()
What I am trying to do
Make sure only after block 1 is executed block 2 is executed

Not sure what call!!.enqueue() does, but it's quite likely that it starts another thread and performs it's work asynchronously.
So block 2 is not waiting till block 1 is done.
A really ugly way (which I don't recommend) handling this would be using a CountDownLatch.
But I'd rather add a callback to doWorkerJob():
override fun doWorkerJob(callback: (Result) -> Unit) {
val call = apiInterface.downloadDictionaryFille(DICTIONARY_FILE_URL)
if (call == null) {
callback(Result.failure())
}
call?.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(
call: Call<ResponseBody?>?,
response: Response<ResponseBody?>
) {
if (response.isSuccessful) {
callback(Result.success())
} else {
Log.d("TAG", "server contact failed")
callback(Result.failure())
}
}
override fun onFailure(call: Call<ResponseBody?>?, t: Throwable?) {
callback(Result.failure())
}
})
}

Related

How to return to secondary function?

i already create some retrofit function like this code
class CallBack<T>(val context: Context) : Callback<T> {
lateinit var response: ((Response<T>) -> Unit)
override fun onResponse(call: Call<T>, response: Response<T>) {
if (!response.isSuccessful) {
(context as Activity).window.decorView.rootView.snackBar(Toast.ERROR,
context.getString(R.string.error, response.code(), response.message()), R.drawable.ic_error)
} else {
this.response.invoke(response)
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
(context as Activity).window.decorView.rootView.snackBar(Toast.ERROR,
context.getString(R.string.cannotConnect), R.drawable.ic_error)
}
}
fun <T> Call<T>?.queue(context: Context, callback: CallBack<T>.() -> Unit) {
val callBack = CallBack<T>(context)
callback.invoke(callBack)
this?.enqueue(callBack)
}
but everytime i used it, i need to write something like this
api().getNotification(token, user.id).queue(requireContext()){
response = {
//command
}
}
how to make function that i dont have to rewrite response = {}
sorry for bad english btw.
Thanks
Your implementation is correct except one thing, you can directly assign your response lambda receiver while creating CallBackclass rather than explicitly assigning it on consumer side.
Check out refactored code below:
class CallBack<T>(
val context: Context,
private val response: (Response<T>.() -> Unit) // Accept argument by reference
) : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (!response.isSuccessful) {
(context as Activity).window.decorView.rootView.snackBar(
Toast.ERROR,
context.getString(R.string.error, response.code(), response.message()),
R.drawable.ic_error
)
} else {
this.response.invoke(response)
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
(context as Activity).window.decorView.rootView.snackBar(
Toast.ERROR,
context.getString(R.string.cannotConnect),
R.drawable.ic_error
)
}
}
fun <T> Call<T>?.queue(context: Context, response: Response<T>.() -> Unit) {
val callBack = CallBack(context, response) // Pass your response callback here directly
this?.enqueue(callBack)
}
And then you can use it like below:
api().getNotification(token, user.id).queue(requireContext()) {
// `this` will be your response here
}

error handling with Retrofit2 using Kotlin coroutines

I understand how to handle errors when not using coroutines:
#GET("user/{user}")
fun getHomeData(#Path("user") user: String?): Call<HomeDataBody>
fun getHomeData(id:String, callback: (Boolean, String?) -> Unit)
{
val call = service.getHomeData(id)
call.enqueue( object : Callback<HomeDataBody> {
override fun onResponse(call: Call<HomeDataBody>, response: Response<HomeDataBody>)
{
if (response.isSuccessful)
{
dataMgr.homeData = response.body()!!.user
callback(true, null)
}
else
{
callback(false, response.message())
}
}
override fun onFailure(call: Call<HomeDataBody>, t: Throwable)
{
callback(false, t.message)
}
})
}
But I cannot for the life of me figure out how to do this with coroutines, this is what I have for a coroutine that does not return errors:
#GET("user/{user}")
suspend fun getHomeDataCoroutine(#Path("user") user: String?): HomeData
suspend fun getHomeDataCoroutine(id:String) : Pair<Boolean, String>
{
val data = service.getHomeDataCoroutine(id)
if(data != null)
{
dataMgr.homeData = data
}
else
{
return Pair(false, "how do i get the error message??")
}
}
I also attempted this, but when I try to call service.getHomeDataCoroutine I get this error:
java.lang.IllegalArgumentException: Unable to create call adapter for class java.lang.Object
for method RiseServiceRetro.getHomeDataCoroutine
#GET("user/{user}")
suspend fun getHomeDataCoroutine(#Path("user") user: String?): Deferred<HomeDataBody>?
sealed class Result<out T : Any>
class Success<out T : Any>(val data: T) : Result<T>()
class Error(val exception: Throwable, val message: String = exception.localizedMessage) : Result<Nothing>()
suspend fun getHomeDataCoroutine(id:String): Result<HomeDataBody>
{
try {
val response = service.getHomeDataCoroutine(id)!!.await()
return Success(response)
} catch (e: Exception) {
return Error(e)
}
}
To handle errors when calling suspend function of Retrofit service wrap it in try-catch block:
#GET("user/{user}")
suspend fun getHomeDataCoroutine(#Path("user") user: String?): HomeDataBody
suspend fun getHomeDataCoroutine(id:String): Pair<Boolean, String> {
return try {
val data = service.getHomeDataCoroutine(id)
dataMgr.homeData = data
Pair(true, "")
} catch(e: Throwable) {
Pair(false, e.message ?: "error")
}
}

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
}
})

How to make synchronous call in Coroutine

I want to make my network request synchronous because the input of second request comes from the output of first request.
override fun onCreate(savedInstanceState: Bundle?) {
retrofit1 =Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/").addConverterFactory(GsonConverterFactory.create()).build()
retrofit2 =Retrofit.Builder()
.baseUrl("https://samples.openweathermap.org/").addConverterFactory(GsonConverterFactory.create()).build()
button.setOnClickListener { view ->
CoroutineScope(IO).launch {
fakeApiRequest()
}}
In my fakeApiRequest(),I am making two network request.
private suspend fun fakeApiRequest() {
val result1 :Geo?= getResult1FromApi()
val result2: Long? = getResult2FromApi(result1)}
Since,this is an asynchronous call,I am getting Null Pointer Exception in my getResult2FromApi(result1) method because the argument passed is null.
In order to fix this issue,I had to add delay(1500) in first call.
private suspend fun getResult1FromApi(): Geo? {
val service:CallService = retrofit1!!.create(CallService::class.java)
val call = service.getUsers()
call.enqueue(object : Callback<List<User>> {
override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
g = users.get(0).address.geo
}
override fun onFailure(call: Call<List<User>>, t: Throwable) {
}
})
delay(1500)
return g
}
-----------------------------------------------------------
private suspend fun getResult2FromApi(result1: Geo?): Long? {
val service2:CallService = retrofit2!!.create(CallService::class.java)
val call2 = service2.getWeather(result1?.lat!!, result1.lng,"b6907d289e10d714a6e88b30761fae22")
call2.enqueue(object : Callback<WeatherData> {
override fun onResponse(call: Call<WeatherData>, response: Response<WeatherData>) {
}
override fun onFailure(call: Call<WeatherData>, t: Throwable) {
}
})
return dt
}
Is there anyway I can make this synchronous, so that I don't have to pass any delay time.
You haven't implemented the suspendable function correctly. You must use suspendCoroutine:
suspend fun getResult1FromApi(): Geo? = suspendCoroutine { continuation ->
val service = retrofit1!!.create(CallService::class.java)
service.getUsers().enqueue(object : Callback<List<User>> {
override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
continuation.resume(response.result.getOrNull(0)?.address?.geo)
}
override fun onFailure(call: Call<List<User>>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
Now your function is synchronous and returns a Geo object.

Call retrofit inside other mathod and return result to main thread

write this code :
fun getStoreTitles():List<sample> {
var responseResult:List<sample>
responseResult= listOf(sample("","",""))
val service = getRetrofitInstance()!!.create(GetDataService::class.java)
val call = service.getAllPhotos()
call.enqueue(object : Callback<List<sample>> {
override fun onResponse(call: Call<List<sample>>, response: Response<List<sample>>) {
responseResult=response.body()!!
var t=0
}
override fun onFailure(call: Call<List<sample>>, t: Throwable) {
/*progressDoalog.dismiss()*/
//Toast.makeText(this#MainActivity, "Something went wrong...Please try later!", Toast.LENGTH_SHORT).show()
}
});
return responseResult
}
and want to call that method from main activity with this way:
var responseResult:List<sample>
val FrameWork=StoreTitle()
responseResult=FrameWork.getStoreTitles()
when run the app,retrofit run the successful but nothing return to the responseResult and that is null,i think retrofit run other thread and that's reason.how can i solve that problem?
Update your api call method:
fun getStoreTitles(callback : Callback<List<sample>>) {
var responseResult:List<sample>
responseResult= listOf(sample("","",""))
val service = getRetrofitInstance()!!.create(GetDataService::class.java)
val call = service.getAllPhotos()
call.enqueue(callback);
}
you have to call like this :
val FrameWork=StoreTitle()
FrameWork.getStoreTitles(object : Callback<List<sample>> {
override fun onResponse(call: Call<List<sample>>, response: Response<List<sample>>) {
val responseResult : List<sample>? =response.body()
//handle your success
}
override fun onFailure(call: Call<List<sample>>, t: Throwable) {
//handle your failure
}
})

Categories

Resources