java.lang.IllegalArgumentException: Unable to create #Body converter for class - android

Recently I switched to MoshiConverterFactory from GSONConverterFactory. Every thing s working fine except the one call. Like other API calls here also I am using #Body annotation but I am getting this error
java.lang.IllegalArgumentException: Unable to create #Body converter for class
my request class :
data class DemoRequest(
val emailId: String? = null,
val demoData: List<DemoDomain?>? = null,
val userName: String? = null
)
One more thing here to mention that with GSONConverterFactory it is working fine but as I switched to MoshiConverterFactory it is throwing error.
retrofitVersion = '2.3.0'
service interface:
#POST("call/api")
fun sendToServer(#Body request: DemoRequest):retrofit2.Call<RemoteResponse>
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
UPDATE-------------
I was sending Date object in request so I need to use custom adapter and it is working fine now

Did you remember to change to MoshiConverterFactory when you build Retrofit?
Retrofit.Builder().baseUrl(...).addConverterFactory(MoshiConverterFactory.create()).build()
Also, the latest version of Retrofit is 2.5.0 so you could try upgrading and make sure your converter is also the same version.

Related

Moshi + Retrofit + Kotlin Reflection proguard rules

I have tried for the past couple of days to migrate my project from GSON to Moshi. So far so good, up to the point when I prepared a release build.
For reference, I am using Retrofit with Moshi
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_moshi_version"
implementation "com.squareup.moshi:moshi-kotlin:$moshi_version"
Moshi instance
val moshi: Moshi = Moshi.Builder()
.add(DateMoshiAdapter())
.addLast(KotlinJsonAdapterFactory())
.build()
Retrofit instance
private val retrofit: Retrofit by lazy {
Retrofit.Builder()
.addConverterFactory(
MoshiConverterFactory.create(moshi)
)
.baseUrl(baseUrl)
.client(httpClient)
.build()
}
Now, let's consider simple data models as the following ones
#Keep
#Parcelize
data class BodyModel(
#Json(name = "info")
val dhInfo: DhInfoModel
) : Parcelable
#Keep
#Parcelize
data class DhInfoModel(
#Json(name = "type")
val type: String,
#Json(name = "value")
val value: String
) : Parcelable
The error that I get when I want to execute the request (which works fine on debug builds) is the following
Unable to create #Body converter for class package.name.etc.api.data.BodyModel (parameter #1)
What I tried ?
using proguard rules found on different issues, like this one
using addLast to the KotlinJsonAdapterFactory() as it was mentioned here
using #field:Json() annotation for data class fields
using JsonClass(generateAdapter = true)
Later Edit:
All this moshi and retrofit is on the SDK part, which I include into my application after I build the release SDK. What is even more weird, is that I tried adding all of this directly into the application, with the same proguard rules, and it works fine on a sample request.
I am running out of ideas and StackOverflow suggestions. Any informations are appreciated.
Thanks in advance, and happy coding!

How can i handle retrofit errors or get url request for to detect problems

I am trying to learn develop android applications and I'm trying to implement the latest approaches. So i use as many jetpack libraries as possible. (Dagger-Hilt, Coroutines, Retrofit etc)
Here is my question:
i have AppModule object for dependency injection.
Here is my retrofit object:
#Singleton
#Provides
fun provideConverterApi(): ConverterAPI {
return Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ConverterAPI::class.java)
}
How can i get error messages from there or for example i need to see the url i use for the request, how can i do that?
You're doing great, to add a logger for your network call use this way:
.addConverterFactory(GsonConverterFactory.create())
.addInterceptor(HttpLoggingInterceptor().apply {
level = if (DEBUG) BODY else NONE
})
.build()
Base on #Amjad Alwareh, remember to add the dependence of the HTTP logging interceptor.
implementation "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}", // 3.12.1 or other version
Maybe the DEBUG should be BuildConfig.DEBUG

Where does Retrofit pull data from server?

I'm learning Coroutines of Kotlin and I'm a beginner of Retrofit.
The Code A is from the artical.
It seems that Retrofit get data from local server http://localhost/, I havn't run local web server.
Where does Retrofit get data ?
Code A
private val service: MainNetwork by lazy {
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(SkipNetworkInterceptor())
.build()
val retrofit = Retrofit.Builder()
.baseUrl("http://localhost/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofit.create(MainNetwork::class.java)
}
fun getNetworkService() = service
/**
* Main network interface which will fetch a new welcome title for us
*/
interface MainNetwork {
#GET("next_title.json")
suspend fun fetchNextTitle(): String
}
Itself!
If you look at the code: .addInterceptor(SkipNetworkInterceptor()). Interceptors process every call that is made through Retrofit.
Some people use it to log data about calls for debugging. Some people use it to automatically provide authorization tokens.
In this case, it is providing fake data.
You can read about that here:
https://github.com/googlecodelabs/kotlin-coroutines/blob/master/coroutines-codelab/finished_code/src/main/java/com/example/android/kotlincoroutines/util/SkipNetworkInterceptor.kt
You should write this IP which is the equivalent of localhost in base url.
val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2:3000/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
But you should check your port number on localhost. After checking, you should add your port number to the part that says 3000. Replace with 3000.

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

Categories

Resources