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> {
//...
})
}
Related
I'm new to kotlin coroutines. I've been trying to run multiple API calls in parallel and then when all the calls are done update my UI and dismiss the loader, but with no success. This is my code
private fun getScoreForType() {
val job = CoroutineScope(Dispatchers.IO).launch {
types.forEach { type ->
getScore(type)
}
}
runBlocking {
job.join()
// do some ui work
dismissLoader()
}
}
private fun getScore(type: String) {
val call = MyApi.getScores(type)
call.enqueue(object : Callback<Score> {
override fun onResponse(call: Call<Score>, response: Response<Score>) {
setScore(response)
}
override fun onFailure(call: Call<Score>, t: Throwable) {
}
})
}
I've also tried using async and awaitAll but couldn't make it work either. The loader is always dismissed before all the calls are done. Any help on how I could make this work would be much appreciated
Use Flow and collectData it will works as LiveData.
For example:
val myIntFlow = MutableStateFlow(-1)
Try something like;
in ViewModelMethods
private fun getScoreForType() {
It goes first:
CoroutineScope(Dispatchers.IO).launch {
types.forEach { type ->
getScore(type)
}
// it means to change value of flow
myIntFlow.value = 1
}
// Now collect data in fragment to change UI
}
// in fragment like:
CoroutineScope(Dispatchers.Main).launch {
// flow will be triggered, on every changed value
viewModel.myIntFlow.collect {
viewModel.methodFromViewModelToChangeUI()
dissmisloader()
myIntFlow.value = -1
}
}
// try the same here as you wish
private fun getScore(type: String) {
val call = MyApi.getScores(type)
call.enqueue(object : Callback<Score> {
override fun onResponse(call: Call<Score>, response: Response<Score>) {
setScore(response)
}
override fun onFailure(call: Call<Score>, t: Throwable) {
}
})
}
i have more than 5 api calls in one fragment. Doing this, the app get slower in performance loading.
So i planned to make it run parallel with kotlin. How to use executors, threads in my code.
I implemented api in below format.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.callApiOne()
viewModel.callApiTwo()
viewModel.callApiThree()
viewModel.getResponseInViewModel().observe(this.viewLifecycleOwner, Observer {
if (it.errorResponse?.code() == 200) {
}
})
}
ViewModel.kt
fun callApiOne() {
repository.callApiOne()
}
fun getResponseInViewModel(): MutableLiveData<Resource<Model>> {
respp = repository.getResponse()
return respp
}
Repository.kt
private val resp by lazy { MutableLiveData<Resource<Model>>() }
fun callApiOne() {
val api = AppMain.restClient?.services?.callApiOne()
api?.enqueue(object : Callback<Model> {
override fun onFailure(call: Call<Model>, t: Throwable) {
resp.postValue(Resource.failure(t.message!!, null))
}
override fun onResponse(
call: Call<Model>,
response: Response<Model>
) {
if (response.isSuccessful) {
resp.postValue(Resource.successResp(response))
} else {
resp.postValue(Resource.errorresponse(response))
}
}
})
}
fun getResponse(): MutableLiveData<Resource<Model>> = resp
You are supposed to use coroutines in this case.
Example:
MyFragment : Fragment(), CoroutineScope by MainScope() {
...
private fun init() {
launch { // starts a coroutine on main thread
viewModel.fooSuspend()
}
}
}
MyViewModel : ViewModel() {
...
suspend fun fooSuspend() {
withContext(Dispatchers.IO) { //do on IO Thread
doIOHaveOperation()
}
}
}
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
}
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
}
})
class API {
val nonBlockingStub: HealthcareAPIGrpc.HealthcareAPIStub //Generated Java GRPC Stub
suspend fun exampleRequest(params: Params) : ReturnedResult = suspendCancellableCoroutine { routine ->
val obs = object : StreamObserver<ReturnedResult> {
override fun onNext(value: ReturnedResult?) {
routine.resume(value!!)
}
override fun onError(t: Throwable?) {
routine.resumeWithException(t!!)
}
override fun onCompleted() {
}
}
nonBlockingStub.exampleRequest(params, obs)
}
}
So I'm working on an Kotlin android application, which has a grpc client generated in java. Recently I had to move all the API requests to use kotlin coroutines. For every request I have to copy-paste this exampleRequest function.
I am curious if it's possible to make a generic function which does this, takes some params and calls the underlying stub function
Ideally there should be a stub generator that generates the appropriate calls as suspend/Flow methods, but you can still abstract much of the conversion with a dedicated helper function:
fun <T> grpcFlow(
#BuilderInference block: suspend (StreamObserver<T>) -> Unit
): Flow<T> = callbackFlow {
// Use ClientCallStreamObserver to support cancellation
val observer = object : ClientCallStreamObserver<T>() {
override fun onNext(value: T) {
sendBlocking(value)
}
override fun onError(t: Throwable) {
cancel(CancellationException("GRPC Error", t))
}
override fun onCompleted() = channel.close()
}
block(observer)
awaitClose {
observer.cancel("flow cancellation", null)
}
}
Then your API simply becomes:
class API {
val nonBlockingStub: HealthcareAPIGrpc.HealthcareAPIStub
suspend fun exampleRequest(params: Params) = grpcFlow {
// #BuilderInference should ensure the correct type is used
nonBlockingStub.exampleRequest(params, it)
}.single() // Since we only expect a single response value.
// And for returns (stream ReturnedResult)
suspend fun exampleStreamingRequest(params: Params) = gcpcFlow {
nonBlockingStub.exampleStreamingRequest(params, it)
} // Flow<ReturnedResult>
}