2 Different objects in a Retrofit response - android

I have a Retrofit request which can reply two different Json responses one response at a time. I want to catch both cases in onResponse. So what i did is create a BaseResponse class
data class ResponseBase(val responseClass: ResponseClass?, val errorClass: ErrorClass?)
and the two inside class are like that.
data class ResponseClass(
val config: Config
)
// Config class
data class Config(
val acceptGuest: Int,
val name: String,
val host: Long
)
And the Error Class is like that
data class ErrorResponse(
val error: Error
)
data class Error(
val id: String,
val message: String
)
So i guess that if the response json comes then i will have the responseClass object otherwise i will have the ErrorObject.
So when i get the following json response from Server which matches the ResponseClass i have an exception.
{"config":{"acceptGuest":0,"name":"server name","host":100}}
Unable to invoke no-args constructor for retrofit2.Call<....ResponseClass>. Registering an InstanceCreator with Gson for this type may fix this problem
What am i missing..?

I use a GSON converter in my retrofit builder.
#Provides
#Singleton
internal fun provideRetrofit(client: OkHttpClient): Retrofit.Builder {
return Retrofit.Builder()
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
}

Related

Add Multiple #Path with retrofit call

I have an api request where in the url i need to pass multiple #Path and i did so but i keep getting an error , i would like to get some help with this issue , thank you in advance
This is sample of url
https://api.site.dev/api/v2/entries/en_US/hello
This is the retrofit setup
#Singleton
#Provides
fun provideRetrofitInstance(): ApiInterface {
val httpLoggingInterceptor = HttpLoggingInterceptor()
val interceptor = httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC)
val okHttp = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
return Retrofit.Builder()
.baseUrl("https://api.site.dev/api/v2/entries/")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttp)
.build()
.create(ApiInterface::class.java)
}
This is my retrofit Call
Error Unable to create call adapter for class com.dic.mydictionnary.models.DictionnaryModelItem
for method ApiInterface.getDictionnaryWord
*This is my apiInterfac
#GET("{language_code}/{word}")
fun getDictionnaryWord(
#Path("language_code") language : String,
#Path("word") word : String,
) : DictionnaryModelItem
}
It looks like Retrofit is trying to find a way of creating a DictionnaryModelItem for your service interface. You need to change that to this:
#GET("{language_code}/{word}")
suspend fun getDictionnaryWord(
#Path("language_code") language : String,
#Path("word") word : String,
) : Response<DictionnaryModelItem>

Retrofit returns 200 response code but I'm receiving null when accessing fields

I'm using retrofit to make a network request to an API. The response code returns 200 but I am receiving null when trying to access the fields. I have checked out other solutions but can't seem to solve my problem. I am using hilt
Here is my API class
interface BlockIOApi{
#GET("/api/v2/get_balance/")
suspend fun getBalance(
#Query("api_key")
apiKey: String = BuildConfig.API_KEY
): Response<BalanceResponse>
}
and here is my app module object
AppModule
#Module
#InstallIn(ApplicationComponent::class)
object AppModule{
#Singleton
#Provides
fun provideOkHttpClient() = if (BuildConfig.DEBUG) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
} else OkHttpClient
.Builder()
.build()
#Provides
#Singleton
fun providesRetrofit(okHttpClient: OkHttpClient): Retrofit =
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
#Provides
#Singleton
fun providesApiService(retrofit: Retrofit): BlockIOApi = retrofit.create(BlockIOApi::class.java)
}
And finally here is my repositories, DefaultRepository.kt
class DefaultRepository #Inject constructor(
private val blockIOApi: BlockIOApi,
private val balanceDao: BalanceDao
):BlockIORepository {
override suspend fun getBalance(): Resource<BalanceResponse> {
return try {
val response = blockIOApi.getBalance()
Log.d("TAG", "getBalance>>Response:${response.body()?.balance} ")
if (response.isSuccessful){
response.body().let {
return#let Resource.success(it)
}
}else{
Log.d("TAG", "getBalance: Error Response >>> ${response.message()}")
Resource.error("An unknown error occured",null)
}
}catch (ex :Exception){
Resource.error("Could not reach the server.Check your internet connection",null)
}
}
and this interface,BlockIORepository.kt
interface BlockIORepository {
suspend fun getBalance(): Resource<BalanceResponse>
suspend fun insertBalance(balance: Balance)
suspend fun getCachedBalance(): Balance
suspend fun getAddresses(): Resource<DataX>
}
Here are my data classes
data class BalanceResponse(
val balance: Balance,
val status: String
)
#Entity
data class Balance(
val available_balance: String,
val network: String,
val pending_received_balance: String,
#PrimaryKey(autoGenerate = false)
var id: Int? = null
)
The problem comes when I try to access the data object. I am not getting null for the status object
I have been stuck on this for two days now. Any help will be highly appreciated. Thanks in advance.
The problem is occured here:
data class BalanceResponse(
val balance: Balance, <-- in postman it is "data"
val status: String
)
You should consider putting #SerializedName(xxx) for your class.
data class BalanceResponse(
#SerializedName("data") val balance: Balance,
val status: String
)
Your class should name filed as per the json or it should provide #SerializedName
So your BalanceResponse class should be
data class BalanceResponse(
#SerializedName("data")
val balance: Balance,
#SerializedName("status")
val status: String
)
Since you are trying to hold data in balance, you must provide SerializedName, but if they have the same name and with exact-case then the parser will automatically recognize them.

Moshi cannot parse nullable

Hello) Hope you can help me.
Using kotlin (Retrofit2 + moshi) i getting data from "https://api.spacexdata.com/v3/launches" and parsing it.
All is going fine (i getting attributes like: flight_number, mission_name), but some attributes have "null", like "mission_patch" - there are 111 objects. 109 of them have data at "mission_patch", 2 objects dont have it ("mission_patch":null).
My problem: moshi cannot parse correctly attribute which contains null.
if i using:
data class SpaceXProperty(
val flight_number: Int,
val mission_name: String,
val mission_patch: String)
i getting error: "Failure: Required value "mission_patch" missing at $[1]" - OK i changed data class to next:
data class SpaceXProperty(
val flight_number: Int,
val mission_name: String,
val mission_patch: String?)
with this i getting data, but every object have mission_patch=null. This is uncorrect, bc only 2 objects have mission_patch=null, not all.
Help me please. im new at kotlin, what i doing wrong?
My retrofit service:
private const val BASE_URL = "https://api.spacexdata.com/v3/"
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
//.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl(BASE_URL)
.build()
interface SpaceXApiService {
#GET("launches")
suspend fun getProperties():List<SpaceXProperty>
}
object SpaceXApi{
val retrofitservice :SpaceXApiService by lazy {
retrofit.create(SpaceXApiService::class.java)
}
}
mission_patch is not in the root object like flight_number etc. It's nested inside links. So your model should match. Try this:
data class SpaceXProperty(
val flight_number: Int,
val mission_name: String,
val links: Links) {
data class Links(val mission_patch: String?)
}

Deserialize a response with Kotlinx.Serialization and Retrofit

I'm using kotlinx.serialization and implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.5.0") with Retrofit2
I'm probably missing something obvious, but I'm just not putting 2 and 2 together on this one.
I have a serializable class that has a list of another serializable class:
#Serializable
data class A(
val resultCount: Int,
val results: List<B>
)
#Serializable
data class B(
val x: Int,
val y: Int,
val z: Int
)
And retrofit setup like this:
fun provideRetrofit(): Retrofit {
val contentType = MediaType.get("application/json")
return Retrofit.Builder()
.baseUrl(BaseUrl)
.addConverterFactory(Json.asConverterFactory(contentType))
.build()
}
I have a service:
#GET("search/")
suspend fun GetStuff(): A
When I make the network call GetStuff expecting to get an instance of A, I get this exception:
kotlinx.serialization.SerializationException: Can't locate argument-less serializer for class A. For generic classes, such as lists, please provide serializer explicitly. So I guess it's expecting me to provide a serializer for the List?
I've read I should be able to use a list serializer like B.serializer().list but I'm not sure where to use that in my context, and when I try to put it anywhere I get Unresolved reference: serializer.

Retrofit with kotlin, unable to create #Body

Hi i get the following error:
java.lang.IllegalArgumentException: Unable to create #Body converter for class com.jr.app.models.ExampleData (parameter #1)
Here is my ExampleData.kt
data class ExampleData(val id: String,
val firstName: String,
val secondName: String,
val profilImages: String,
val info: String) {
}
My interface retrofit
interface UsersService {
#GET("/usersProfile")
fun getAllUsers(): Call<List<ExampleData>>
#POST("/usersProfile")
fun addUser(#Body exampleData: ExampleData): Call<ResponseBody>
}
function that addsUser
override fun addUser(user: ExampleData) {
val retrofit = Retrofit.Builder().baseUrl(baseUrl).client(httpAuthClient).build();
val userService = retrofit.create(UsersService::class.java);
userService.addUser(user).enqueue(callbackResponse);
}
private val httpAuthClient: OkHttpClient
get() {
val okHttpClient = OkHttpClient().newBuilder().addInterceptor { chain ->
val originalRequest = chain.request()
val builder = originalRequest.newBuilder().header(authorizeHeader,
Credentials.basic(userName, password))
val newRequest = builder.build()
chain.proceed(newRequest)
}.build()
return okHttpClient
}
Add the gradle dependency to your project:
compile 'com.squareup.retrofit2:converter-gson:VERSION_OF_RETROFIT'
When you build an instance of retrofit add this line:
.addConverterFactory(GsonConverterFactory.create())
In your project building the retrofit object will look like:
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(httpAuthClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
I believe this has nothing to do with Kotlin but your Retrofit configuration and your data class ExampleData.
Retrofit has no idea how to serialize your instance of ExampleData to JSON. You need to add a specific converter factory when creating instance of Retrofit client (see Builder#addConverterFactory method for details).

Categories

Resources