I have an instance of retrofit built like this
val moshi = Moshi.Builder()
.add(SkipBadElementsListAdapter.Factory)
.add(KotlinJsonAdapterFactory())
.add(Date::class.java, MoshiDateAdapter())
.build()
val okHttpClient = createHttpClientBuilder()
.build()
return Retrofit.Builder()
.client(okHttpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(Interactors.apiEndpoint)
.build()
.create(UserApiClient::class.java)
I'm sending an List of this object
internal open class QuizAnswerDto(
#Json(name = "questionOrder") val questionOrder: Int?,
#Json(name = "questionKind") val type: String?,
#Json(name = "questionId") val questionId: String?,
#Json(name = "response") val response: Any?,
#Json(name = "order") val answerOrder: Int?,
#Json(name = "text") val answerText: String?,
#Json(name = "responses") val answersMap: Map<Int, String>?){
companion object {
const val ANGRY_ID = 0
const val UPSET_ID = 1
const val NEUTRAL_ID = 2
const val SATISFIED_ID = 3
const val HAPPY_ID = 4
const val UNKNOWN = -1
const val LIKE_DISLIKE= "yes_no"
const val SENTIMENT ="viewer_sentiment"
const val SINGLE_ANSWER="multiple_choice"
const val MULTIPLE_ANSWERS="select_all_that_apply"
const val SHORT_ANSWER="short_answer"
}
}
With this API call
#POST("campaigns/influencer/sponsorships/watchandrespond/{influencerSponsorshipId}/answers")
#JvmSuppressWildcards
fun submitAnswers(#Path("influencerSponsorshipId") influencerSponsorshipId: String,
#Body request: List<QuizAnswerDto>): Completable
When I do, I get this error:
java.lang.IllegalArgumentException: Unable to create #Body converter
for java.util.List<com.weare8.android.data.quiz.QuizAnswerDto>
(parameter #2)
Caused by: java.lang.IllegalArgumentException: No JsonAdapter for E (with no annotations)
Parameter #2 (questionKind) is always one of the const strings in the companion object, I have no idea what "type variable or wildcard" it is talking about. What am I doing wrong?
From the moshi documentation
val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
KotlinJsonAdapterFactory should be added as last in the builder. This may solve your problem.
Related
I am using retrofit2.6.2 for api call. LoggingInterceptor showing full response in logcat but retrofit response body return null. I didn't figure out where is my problem.
My json data schema is
{
"error":false,
"msg":"Banner Found",
"id":"9",
"activity":"VipPremium1",
"imageUrl":"https:\/\/1.bp.blogspot.com\/-Kh3RQlJH7Xw\/X-1mIPi7_HI\/AAAAAAAAFME\/Y2bCnU5odngcdDT83uC9QwUr7IGJdTDfACLcBGAsYHQ\/s2616\/COMPRESSED_IMG_1609393684674.jpg",
"actionUrl":"https:\/\/www.youtube.com\/watch?v=ukJX5ZgJec4",
"actionType":1,
"visible":true
}
Model Class BannerRes
data class BannerRes(
#SerializedName("actionType")
val actionType: Int?,
#SerializedName("actionUrl")
val actionUrl: String?,
#SerializedName("activity")
val activity: String?,
#SerializedName("error")
val error: Boolean?,
#SerializedName("id")
val id: String?,
#SerializedName("imageUrl")
val imageUrl: String?,
#SerializedName("msg")
val msg: String?,
#SerializedName("visible")
val visible: Boolean?
)
Api Interface
#GET("api/helper.getBanner.php")
suspend fun getBanner(
#Query("bannerName") bannerName: String,
): Response<BannerRes>
Api call done here
private fun loadPremiumBanner() {
Coroutines.main {
val res = viewModel.getBanner("VipPremium1")
Log.d("Response", res.body()!!.msg!!)
}
}
When I print response body using
Log.d("Response", Gson().toJson(res.body()))
It shows the the response in logcat,
Logcat
{"error":false,"msg":"Banner Found","id":"9","activity":"VipPremium1","imageUrl":"https://1.bp.blogspot.com/-Kh3RQlJH7Xw/X-1mIPi7_HI/AAAAAAAAFME/Y2bCnU5odngcdDT83uC9QwUr7IGJdTDfACLcBGAsYHQ/s2616/COMPRESSED_IMG_1609393684674.jpg","actionUrl":"https://www.youtube.com/watch?v\u003dukJX5ZgJec4","actionType":1.0,"visible":true}
but when access res.body()!!.msg It shows null.
Retrofit Setup
companion object {
#Volatile
private var myApiInstance: MyApi? = null
private val LOCK = Any()
operator fun invoke() = myApiInstance ?: synchronized(LOCK) {
myApiInstance ?: createClient().also {
myApiInstance = it
}
}
private fun createClient(): MyApi {
val AUTH: String = "Basic ${
Base64.encodeToString(
("${BuildConfig.USER_NAME}:${BuildConfig.USER_PASSWORD}").toByteArray(),
Base64.NO_WRAP
)
}"
val interceptor = run {
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.apply {
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
}
}
val okHttpClient: OkHttpClient = OkHttpClient.Builder()
.callTimeout(10,TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addInterceptor { chain ->
val original: Request = chain.request()
val requestBuilder: Request.Builder = original.newBuilder()
.addHeader("Authorization", AUTH)
.method(original.method, original.body)
val request: Request = requestBuilder.build()
chain.proceed(request)
}
.build()
val gsonBuilder = GsonBuilder()
gsonBuilder.setLenient()
val gson = gsonBuilder.create()
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addConverterFactory(ScalarsConverterFactory.create())
.client(okHttpClient)
.build()
.create(MyApi::class.java)
}
}
I resolved this issue by adding kotlin model data class member filed default value. I don't know what is the reason behind this,
Old data class
data class BannerRes(
#SerializedName("actionType")
val actionType: Int?,
#SerializedName("actionUrl")
val actionUrl: String?,
#SerializedName("activity")
val activity: String?,
#SerializedName("error")
val error: Boolean?,
#SerializedName("id")
val id: String?,
#SerializedName("imageUrl")
val imageUrl: String?,
#SerializedName("msg")
val msg: String?,
#SerializedName("visible")
val visible: Boolean?
)
Modified or data class with member field default value which fix my problem
data class BannerRes(
#SerializedName("error") var error : Boolean = true,
#SerializedName("msg") var msg : String? = null,
#SerializedName("id") var id : String? = null,
#SerializedName("activity") var activity : String? = null,
#SerializedName("imageUrl") var imageUrl : String? = null,
#SerializedName("actionUrl") var actionUrl : String? = null,
#SerializedName("actionType") var actionType : Int = 0,
#SerializedName("visible") var visible : Boolean = false
)
I think you can't use both Gson and Scalars Converter in Retrofit because retrofit confuse to wrap it.
Remove Scaler (I prefer Gson) and try again.
If not work then use GsonConverterFactory.create() this.
With Retrofit you can consume the response body only once. So the first call to body() will return the response but further calls will not. You're consuming your body when you're logging it.
I am developing new android app where I want to add baseurl and api_key in koin module retrofit but I am confused
Ending Point: https://api-aws-eu-qa-1.auto1-test.com/v1/car-types/manufacturer?wa_key=coding-puzzle-client-449cc9d&page=0&pageSize=15
API Key: coding-puzzle-client-449cc9d
Base Url: https://api-aws-eu-qa-1.auto1-test.com/
I want to add base_url and api_key correctly in my modules.kt file correctly so that later I can fetch data correctly from server if you check my modules.kt first I am calling api_key then base url. and setup my interface where I am call get method in interface following way
interface ApiInterface {
#GET("v1/car-types/manufacturer?")
suspend fun getCarResponse(): Call<CarManufactureResponse>
}
below CarManifacturerResponse
data class CarManufactureResponse(
#SerializedName("page")
val page: Int,
#SerializedName("pageSize")
val pageSize: Int,
#SerializedName("totalPageCount")
val totalPageCount: Int,
#SerializedName("mkda")
val mkda: ManufacturerId
)
below ManifacturerId
data class ManufacturerId(
#SerializedName("020")
val x020: String,
#SerializedName("040")
val x040: String,
#SerializedName("042")
val x042: String,
#SerializedName("043")
val x043: String,
#SerializedName("057")
val x057: String,
#SerializedName("060")
val x060: String,
#SerializedName("095")
val x095: String,
#SerializedName("107")
val x107: String,
#SerializedName("125")
val x125: String,
#SerializedName("130")
val x130: String,
#SerializedName("141")
val x141: String,
#SerializedName("145")
val x145: String,
#SerializedName("150")
val x150: String,
#SerializedName("157")
val x157: String,
#SerializedName("160")
val x160: String
)
below my Modules.kt koin setup module where I want to pass baseurl and api_key
val viewModels = module {
//viewModel { CarViewModel(get()) }
}
val apiModule = module {
single {
val tokenInterceptor = Interceptor { chain ->
val request =
chain
.request()
.newBuilder()
.addHeader(
"API_KEY",Constants.API_KEY
)
.build()
chain.proceed(request)
}
val logInterceptor: HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
this.level = HttpLoggingInterceptor.Level.BODY
}
val okHttpClient =
OkHttpClient.Builder()
.addInterceptor(tokenInterceptor)
.addInterceptor(logInterceptor)
.build()
val retrofit =
Retrofit.Builder()
.client(okHttpClient)
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
retrofit.create(ApiInterface::class.java)
}
}
below Constants.kt
object Constants {
const val API_KEY = "wa_key=coding-puzzle-client-449cc9d"
const val BASE_URL = "https://api-aws-eu-qa-1.auto1-test.com/"
}
how can I pass correctly baseurl and apiKey in koin module so that I can implement retrofit logic correctly
Use the code below to get your desired url:
#GET("v1/car-types/manufacturer?{apiKey}")
suspend fun getCarResponse( #Path("apiKey") type: String,#Query("page") page:String,#Query("pageSize") pageSize:String): Call<CarManufactureResponse>
I'm trying to use PolymorphicJsonAdapterFactory in order to obtain different types, but am always getting odd exception:
Missing label for test_type
My entity:
#JsonClass(generateAdapter = true)
data class TestResult(
#Json(name = "test_type") val testType: TestType,
...
#Json(name = "session") val session: Session,
...
)
Here is my moshi factory:
val moshiFactory = Moshi.Builder()
.add(
PolymorphicJsonAdapterFactory.of(Session::class.java, "test_type")
.withSubtype(FirstSession::class.java, "first")
.withSubtype(SecondSession::class.java, "second")
)
.build()
Structure of json response:
{
response: [
test_type: "first",
...
]
}
test_type must be a field of session class.
if the test_type can't be inside a session class, then you must declare a class for every variant of TestResult containing the specific Session class as follows:
sealed class TestResultSession(open val testType: String)
#JsonClass(generateAdapter = true)
data class TestResultFirstSession(
#Json(name = "test_type") override val testType: String,
#Json(name = "session") val session: FirstSession
) : TestResultSession(testType)
#JsonClass(generateAdapter = true)
data class TestResultSecondSession(
#Json(name = "test_type") override val testType: String,
#Json(name = "session") val session: SecondSession
) : TestResultSession(testType)
and your moshi polymorphic adapter:
val moshiFactory = Moshi.Builder()
.add(
PolymorphicJsonAdapterFactory.of(TestResultSession::class.java,"test_type")
.withSubtype(TestResultFirstSession::class.java, "first")
.withSubtype(TestResultSecondSession::class.java, "second")
)
.build()
it is always good practise to provide a fallback, so your deserialisation doesn't fail, in case test_type is unknown:
#JsonClass(generateAdapter = true)
data class FallbackTestResult(override val testType: String = "") : TestResultSession(testType)
val moshiFactory = Moshi.Builder()
.add(
PolymorphicJsonAdapterFactory.of(TestResultSession::class.java,"test_type")
.withSubtype(TestResultFirstSession::class.java, "first")
.withSubtype(TestResultSecondSession::class.java, "second")
.withDefaultValue(FallbackTestResult())
)
.build()
i'm currently working on some basic app which i try to get
an response from API - List of objects.
My data classes Are:
#JsonClass(generateAdapter = true)
data class Tag(
#Json(name = "id")
val id: Int,
#Json(name = "name")
val name: String
)
#JsonClass(generateAdapter = true)
data class Test(
#Json(name = "count")
val count: Int,
#Json(name = "next")
val next: Int,
#Json(name = "previous")
val previous: Int,
#Json(name = "results")
val results: List<Tag>
)
My retrofit build code is:
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
return Retrofit.Builder()
.baseUrl(SERVER_BASE_URL)
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
And my request is very simple:
#GET("api/tags")
suspend fun getTags(): Deferred<Test>
But when i am calling getTags() i get the following error:
java.lang.IllegalArgumentException: Unable to create converter for kotlinx.coroutines.Deferred<com.example.kotlin_ex2.models.Test>
Caused by: java.lang.IllegalArgumentException: No JsonAdapter for kotlinx.coroutines.Deferred<com.example.kotlin_ex2.models.Test> (with no annotations)
Already tried many other ways with no success, what could be the problem?
Thank you
It is because you are using both suspend and Deferred in one function. Convert
#GET("api/tags")
suspend fun getTags(): Deferred<Test>
to
#GET("api/tags")
fun getTags(): Deferred<Test>
Solved.
As i can see, i just needed to remove the Deferred class and write it that way:
#GET("api/tags")
suspend fun getTags(): Test
and no need to call await() from inside the Coroutine.
Hi I am using gson library to map values of response to model. I am doing it like this but it is not mapping values of response to model. I am getting list of 50 models but values in it is zero.
#Provides
#Singleton
fun provideRestApiHelper(
okHttpClient: OkHttpClient,
gson: Gson,
rxJava2CallAdapterFactory: RxJava2CallAdapterFactory): RestApi {
val builder = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addCallAdapterFactory(rxJava2CallAdapterFactory)
.addConverterFactory(GsonConverterFactory.create(gson))
val retrofit = builder.client(okHttpClient).build()
return retrofit.create(RestApi::class.java)
}
RestApi.kt
interface RestApi {
#GET(ApiEndPoint.ENDPOINT_GITHUB_JOBS)
fun getJobsApiCall(): Observable<List<JobsResponse>>
}
ApiHelperImpl.kt
class ApiHelperImpl #Inject constructor(private val restApi: RestApi) : ApiHelper {
override fun getJobsApiCall(): Observable<List<JobsResponse>> {
return restApi.getJobsApiCall()
}
}
JobsResponse.kt
data class JobsResponse(
#field:SerializedName("company_logo")
val companyLogo: String?,
#field:SerializedName("how_to_apply")
val howToApply: String?,
#field:SerializedName("created_at")
val createdAt: String?,
#field:SerializedName("description")
val description: String?,
#field:SerializedName("location")
val location: String?,
#field:SerializedName("company")
val company: String?,
#field:SerializedName("company_url")
val companyUrl: String?,
#field:SerializedName("id")
val id: String?,
#field:SerializedName("title")
val title: String?,
#field:SerializedName("type")
val type: String?,
#field:SerializedName("url")
val url: String?
) : BaseResponse()
I am calling this API https://jobs.github.com/positions.json. Does anyone know what could be the issue ?
That's because you rely on auto-converted java code
remove #field:SerializedName changed it to #SerializedName
don't put them in to primary constructor
define them like this:
data class JobsResponse(){
#SerializedName("company_logo")
val companyLogo: String? = null
....
}