I have a problem using retrofit with rxandroid.
I've created a custom converter as below:
class CustomResponseConverter<T>(private val converter: Converter<ResponseBody, *>): Converter<ResponseBody, T> {
override fun convert(value: ResponseBody): T? {
// custom convert response here
}
}
It's all working fine when i'm returning Single like this:
#GET("route")
fun simpleFetch(): Single<FetchData>
but when i try returning Completable like this:
#GET("route")
fun simpleFetch(): Completable
I found that the convert function doesn't get call. Please help.
Thanks in advance.
For anyone who's running into the same case as me, apparently according to retrofit team here:
Using Completable bypasses all converters, yes, and simply closes the response body so it is consumed. Since there is nowhere for the converted body to go with a Completable, there is no need to call it and perform conversion.
So I guess we'll just keep using Single on this case.
Related
I am trying to understand how retrofit implements the following suspend function:
#GET("api/me")
suspend fun getUser(): User
Is there anyway that I can access the locally generated implementation code?
Thanks!
According to docs,
Behind the scenes this behaves as if defined as fun user(...):
Call and then invoked with Call.enqueue. You can also return
Response for access to the response metadata.
Everything else lies inside the sources like this class. Btw, I'm just curious, why do you need to understand the exact implementation from the Retrofit side?
I'm using retrofit to generate the POST requests in my simple API call:
interface IRetrofitCommService {
#POST("pushnotifications/{entityId}")
suspend fun getNotificationsAsync(
#Path("entityId") entityId: Long,
#Body model: GetNotificationsDto
): Response<List<NotificationData>>
#POST("pushnotifications")
suspend fun registerDeviceAsync(#Body model: RegisterEntityDto):
Response<RegisterEntityResultDto>
}
Note that in the second call, I have only 1 parameter, which is marked with the #Body annotation.
Yet, when I try to use the web call, I get this exception: No annotation found for param 2
Here is my factory for creating the call:
object RetrofitFactory {
const val BASE_URL = "https://localhost:5051/api/"
fun createService(): IRetrofitCommService {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(IRetrofitCommService::class.java)
}
}
Here is the DTO in question:
data class RegisterEntityDto(val name: String, val eventType: Short, val clientId: String)
Why is it looking for a 2nd parameter then? What am I missing?
I'm not very familiar with the Retrofit and I'm doing an educated guess here, but after a short discussion in comments it seems my understanding is actually correct.
Internally, suspend functions in Kotlin receive additional Continuation parameter. It is always the last parameter and it is hidden from the Kotlin code. However, if we look into the generated bytecode, we'll see registerDeviceAsync() function actually receives 2 parameters.
If some tool we use is not aware of suspend functions, it won't be able to properly interpret this additional parameter. It will just "think" registerDeviceAsync() has two params. Retrofit added support for suspend functions in version 2.6.0, so I guess if using older versions with suspend functions we will get exactly the behavior you faced.
You just need to update Retrofit to newer version.
I am learning retrofit and cannot understand what is Call<> in that means.
Even the docs are too hard to understand.
Can someone give a clear explanation on this?
Think of Call as a simple class which wraps your API response and you need this class make an API call and provide listeners/callback to notify you with error and response , although if you use kotlin coroutines then after version 2.6.0 or retrofit you can totally abandon Call , you can directly return response from the function and you don't need any callback which is very clean.
do if like
#GET("users/{id}")
suspend fun user(#Path("id") id: Long): User
or
#GET("users/{id}")
suspend fun user(#Path("id") id: Long): Response<User>
Call is a method to request to the webserver/API to get data.(Based on my understanding)
I am new to Kotlin, coming from C# it I am quite used to async\await
How do I wait for tvClient to get the response before returning the list of channels?
override fun getChannels(): MutableList<Channel> {
disposable = tvClient.getChannels()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{Log.d("***", it.toString())},
{Log.d("***",it.toString())}
)
TODO("wait for tvClient to return results")
return mChannels;
}
I tried using coroutines but no luck
What is the best way to wait for async operation to complete in Kotlin?
You're using RxJava and thus you should implement it in a reactive way.
If you're app is not build for it yet, you can get the value blocking. Assuming getChannels() returns a single you could just call blockingGet() instead of subscribe().
But be aware that this blocks the thread the outer getChannels() is called from.
Using coroutines might be better for you. It's a little nearer to what you know from C# and with the retrofit2-kotlin-coroutines-adapter you can integrate directly with Retrofit.
You could look into using the retrofit coroutine adapters from Jake Wharton https://github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter
you can check a functional implementation of kotlin v1.3 retrofit + stable coroutines using DSL here https://github.com/eriknyk/networkcall-sample/commits/master
DSL template:
fun <RESPONSE: DataResponse<*>, DATA: Any> networkCall(block: CallHandler<RESPONSE, DATA>.() -> Unit): MutableLiveData<Resource<DATA>>
= CallHandler<RESPONSE, DATA>().apply(block).makeCall()
interface DataResponse<T> {
fun retrieveData(): T
}
and using it:
fun getRepos(query: String) = networkCall<ResposResponse, List<Repo>> {
client = githubService.getRepos(query)
}
Hope it helps.
I am wondering how to test Retrofit2 call via rxjava2. My retrofit api interface looks like:
public interface LoginApiMapping {
#POST("v1/secm/oam/oauth2/token")
Observable<Response<RestResponseHolder<LoginResponseModel>>> login(#Body LoginModel model);
}
and i would like to write the test which will send this request via RxJava2 and check the response. I consider that there is the problem with RxJava as it is asynchronous and the test finishes before i get the response, so i tried to use TestSubscriber as below, but it's not possible to subscribe TestSubscriber<Response<RestResponseHolder<LoginResponseModel>>> as i expected
#Test
public void loginTest(){
Context appContext = InstrumentationRegistry.getTargetContext();
Api t = TestApi.create(appContext);
LoginModel loginModel = new LoginModel("username","password");
TestSubscriber<Response<RestResponseHolder<LoginResponseModel>>> testSubscriber = new TestSubscriber<>();
t.get(LoginApiMapping.class).login(loginModel).subscribe(testSubscriber);
}
anyone who solved that? thanks
I finally found the solution, so i decided to post it here.
First you have to call toFlowable() on the observable and set the Backpressure strategy and then you can subscribe with TestSubscriber as below
t.get(LoginApiMapping.class).login(loginModel).toFlowable(BackpressureStrategy.DROP).subscribe(testSubscriber);
Then add
testSubscriber.awaitTerminalEvent();
at the end that you can check various assertions on testSubscriber as R. Zagórski pointed in answer above.
At the end of of test add:
testSubscriber.awaitTerminalEvent();
Then you can check various assertions on testSubscriber.
You use TestSubscriber with Flowable and TestObserver with Observable. You can test Single, Completable and Maybe by converting them to either Flowable or Observable, but the built-in test()s for these three return TestSubscriber.