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
}
Related
I want to pass a Retrofit API Call as a parameter to a method. Here is my minified code:
interface API {
#POST("/test")
fun postTest(): Call<Response>
}
fun test() {
post(client.postTest())
}
private lateinit var client: Api
private fun post(function: Call<Response>) {
if (::client.isInitialized) {
function.enqueue(object : Callback<Response> {
override fun onResponse(
call: Call<Response>,
response: Response<Response>
) {
if (response.isSuccessful) {
// do something
} else {
// do something
}
}
override fun onFailure(call: Call<Response>, t: Throwable) {
// do something
}
})
}
}
The APIClient is initiated correctly. My problem is that the API Call is executed before passed to the post()-function, which leads to unhandled errors if something went wrong.
You can pass a method reference that will create your Call object lazily in your post function:
fun test() {
post(client::postTest)
}
private fun post(lazyCall: () -> Call<Response>) {
lazyCall().enqueue(object : Callback<Response> {
//...
})
}
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())
}
})
}
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
}
})
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.
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
}
})