Retrofit looking for non-existent param? - android

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.

Related

What is Call<> in retrofit?

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)

Using custom converter with rxandroid Completable

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.

Using Coroutines along with RxJava and Moshi not working

I am working an Android project that uses RxJava for its network calls and now I'm migrating it to Coroutines. Since the code base is large, I have to use RxJava and Coroutines simultaneously and iteratively convert RxJava methods to Coroutines to eventually lead to a code base that is fully based on Coroutines. Moshi is the converter of choice. When I use RxJava, the network calls work fine and return expected values. But my suspend methods that utilize coroutines are not returning data as expected. The returned POJOs are not populated by the JSON response values.
Here goes my code for building Retrofit
retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.client(buildHttpClient())
.build()
And here goes my suspend method where the POJO in the body of response is not populated.
#PUT(EndPoints.SOME_ENDPOINT + "{some_id}/")
suspend fun addSomethingSomewhere(#Body body: RequestBody, #Path("some_id") someID: String): Response<SomeModel>
Here are different versions of the POJO I have tried
// version 1
#Parcelize
data class SomeModel(var someValue: String? = null,
var someOtherValue: List<String>? = null)
// version 2
#JsonClass(generateAdapter = true)
class SomeModel(#field:Json(name = "someValue") var someValue: String? = null,
#field:Json(name = "someOtherValue") var someOtherValue: List<String>? = null)
I have tried replacing the Response<SomeModel> with only SomeModel in return type of suspend method, but it still doesn't work. I think the issue has got something to do with this line when building retrofit addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) because the call adapter is provided for RxJava, and maybe that is why my coroutine-based code is not populating the POJOs as expected. Any help will be appreciated. Thanks!

Retrofit 2.6.0 exception: java.lang.IllegalArgumentException: Unable to create call adapter for kotlinx.coroutines.Deferred

I have a project with Kotlin coroutines and Retrofit.
I had these dependencies:
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
Today I have updated Retrofit to 2.6.0 in the project. In https://github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter it is written that it is deprecated now. In https://github.com/square/retrofit/blob/master/CHANGELOG.md#version-260-2019-06-05 it is written that Retrofit currently supports suspend.
So, I removed retrofit2-kotlin-coroutines-adapter:0.9.2 and in Retrofit client changed these lines:
retrofit = Retrofit.Builder()
.baseUrl(SERVER_URL)
.client(okHttpClient)
.addConverterFactory(MyGsonFactory.create(gson))
//.addCallAdapterFactory(CoroutineCallAdapterFactory()) - removed it.
.build()
When run, the first request catches an exception:
java.lang.IllegalArgumentException: Unable to create call adapter for kotlinx.coroutines.Deferred<com.package.model.response.UserInfoResponse>
for method Api.getUserInfo
As I understood, instead of CoroutineCallAdapterFactory() I could use CallAdapter.Factory(), but it is abstract.
If in Api class I change a request adding suspend in the beginning:
#FormUrlEncoded
#POST("user/info/")
suspend fun getUserInfo(#Field("token") token: String): Deferred<UserInfoResponse>
override suspend fun getUserInfo(token: String): Deferred<UserInfoResponse> =
service.getUserInfo(token)
I get this exception:
java.lang.RuntimeException: Unable to invoke no-args constructor for kotlinx.coroutines.Deferred<com.package.model.response.UserInfoResponse>. Registering an InstanceCreator with Gson for this type may fix this problem.
Reading https://github.com/square/retrofit/blob/master/CHANGELOG.md#version-260-2019-06-05 I saw:
New: Support suspend modifier on functions for Kotlin! This allows you
to express the asynchrony of HTTP requests in an idiomatic fashion for
the language.
#GET("users/{id}") suspend fun user(#Path("id") long id): User
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.
Currently this integration only supports non-null response body types.
Follow issue 3075 for nullable type support.
I changed requests so: added suspend and removed Deferred:
#FormUrlEncoded
#POST("user/info/")
suspend fun getUserInfo(#Field("token") token: String): UserInfoResponse
override suspend fun getUserInfo(token: String): UserInfoResponse =
service.getUserInfo(token)
Then in interactor (or simply when called the method getUserInfo(token)) removed await():
override suspend fun getUserInfo(token: String): UserInfoResponse =
// api.getUserInfo(token).await() - was before.
api.getUserInfo(token)
UPDATE
Once I encountered a situation when downloading PDF files required removing suspend in Api class. See How to download PDF file with Retrofit and Kotlin coroutines?.
In my case I was missing the CoroutineCallAdapterFactory in my Retrofit initialization. Retrofit v2.5.0
Before:
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(httpClient)
.addConverterFactory(MoshiConverterFactory.create())
.build()
After: (working code)
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(httpClient)
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()

Retrofit 2 : Custom annotations for custom interceptor

I have custom interceptor for authentication:
#Named("authInterceptor")
#Provides
#Singleton
fun providesAuthInterceptor(preferencesManager: PreferencesManager): Interceptor {
return Interceptor { chain ->
val newBuilder = chain.request().newBuilder()
newBuilder.addHeader("access-token", preferencesManager.getAccessToken())
val request = newBuilder.build()
return#Interceptor chain.proceed(request)
}
}
But I have some calls that not need auth header.
What I would like to have in my service is:
interface NetService {
#NEEDAUTH
#GET("users")
fun getAllShops(key: String): Single<SomeResponse>
#FormUrlEncoded
#POST("users")
fun register(#Field("nickname") nickname: String): Single<SomeResponse>
}
So, the first call will use authInterceptor, the second one will not use it.
Since the version of Retrofit 2.6.0, you can get the annotations in OkHttp Interceptor using the tag field like this:
response.request.tag(Invocation::class.java)?.method()?.getAnnotation(YourAnnotation::class.java)
Then inside of the interceptor, you can verify if the request is annotated or no.
Retrofit Changelog:
New: #Tag parameter annotation for setting tags on the underlying OkHttp Request object. These can be read in CallAdapters or OkHttp Interceptors for tracing, analytics, varying behavior, and more.
https://github.com/square/retrofit/pull/2899/files
I have similar requirement, what I found is Annotation can be read in Converter.Factory:requestBodyConverter(),
Converter.Factory:responseBodyConverter() and CallAdapter.Factory.get().
I also found two articles as examples for implementation on each way.
Using Converter: Auto Caching with Retrofit
We’ll use the gson converter (GsonConverterFactory) provided by Retrofit and modify it slightly to include a listener in GsonResponseBodyConverter.class which handles the http response parsing.
In GsonCacheableConverter, it overrides responseBodyConverter() to persist response tagged with #Cacheable.
Using CallAdapter: Custom Annotations with Retrofit 2
We read the annotation in the CallAdapter.Factory and when the request gets created in the CallAdapter, we will store some information for this kind of request within some map, to identify it later in some interceptor.
It uses a custom CallAdapter to get annotation #Authenticated, and put data into a map, which later parsed in the Interceptor.
I think requestBodyConverter() and CallAdapter are closer to your requirement.
While if you do not insist on custom annotations, the easiest way for now in my opinion is to add custom header to the api interface, then read and remove it in the interceptor.
That is, adding #Headers("needauth: 1") to your services, and using chain.request().header("needauth") to get the value.
Example: Sneaking Data into an OkHttp Interceptor.
Interceptors are concepts that exist in OkHttp, Retrofit knows nothing about them.
What you need to do is have two OkHttp clients, with their respective instances of Retrofit.
One with the authentications headers
One for the rest
Whether you need the authentication headers or not will decide which instance to inject.
Based on Max Cruz answer I'm using as extension function:
fun <T : Annotation> Request.getCustomAnnotation(annotationClass: Class<T>): T? = this.tag(Invocation::class.java)?.method()?.getAnnotation(annotationClass)
And you can use then like that:
request.getCustomAnnotation(YourAnnotation::class.java)

Categories

Resources