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
....
}
Related
I've been trying to make a POST with Retrofit 2 and Moshi but I've been unable to get it to work.
My data classes look like this:
data class Order(
val items: List<Item>?,
val customerName: String,
val customerPhoneNo: String,
val customerAddress: String,
val note: String
)
data class Item(
val productUid: String,
var quantity: Int
)
The interface looks like this
interface ProductService {
#POST("/api/order/saveorder")
suspend fun postProds(
#Field("customerName") customerName: String,
#Field("customerPhoneNo") customerPhone: String,
#Field("customerAddress") address: String,
#Field("note") customerNote:String,
#Field("items") orderItems: List<Item>
): Response<Order>
#GET("/api/product/allproducts")
suspend fun getProds(): Response<List<ProdsItem>>
}
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
object Network {
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi)
.asLenient()
)
.build()
object ProdsApi {
val retrofitService: ProductService by lazy {
retrofit.create(ProductService::class.java)
}
}
}
The postProds fun is called like this:
suspend fun sendOrder(order: Order) {
withContext(Dispatchers.Main){
try {
val orderResponse = Network.ProdsApi.retrofitService.postProds(
order.customerName,
order.customerPhoneNo,
order.customerAddress,
order.note,
order.items )
}
catch (e: Exception) {
Timber.e(e)
}
}
}
Trying to POST this way keeps yielding this response:
Response{protocol=h2, code=400, message=, url=
However, I tried converting the Order object to json directly in my viewModel as follows:
val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val jsonAdapter: JsonAdapter<Order> = moshi.adapter(Order::class.java)
val jsonString = jsonAdapter.toJson(customerOrder)
Timber.d(jsonString)
I then tested the generated jsonString on Postman and got a 200 response.
I need some help figuring out what I'm doing wrong in my code, please.
In postman, you are sending data in the body of the request. But in your code, it is going as key-value params. Try to send it in the body from your code. Try below snippet.
Update your Order Data class:
#JsonClass(generateAdapter = true)
data class Order(
#Json(name = "items")
val items: List<Item>?,
#Json(name = "customerName")
val customerName: String,
#Json(name = "customerPhoneNo")
val customerPhoneNo: String,
#Json(name = "customerAddress")
val customerAddress: String,
#Json(name = "note")
val note: String
)
#JsonClass(generateAdapter = true)
data class Item(
#Json(name = "productUid")
val productUid: String,
#Json(name = "quantity")
var quantity: Int
)
Now the ProductService Interface:
interface ProductService {
#POST("/api/order/saveorder")
suspend fun postProds(
#Body request: Order
): Response<Order>
#GET("/api/product/allproducts")
suspend fun getProds(): Response<List<ProdsItem>>
}
Now Pass the request object in your function call:
suspend fun sendOrder(order: Order) {
withContext(Dispatchers.Main){
try {
val orderResponse = Network.ProdsApi.retrofitService.postProds(order)
}
catch (e: Exception) {
Timber.e(e)
}
}
}
I'm trying to make a post with retrofit and moshi but keep getting the error mess
com.squareup.moshi.JsonDataException: Expected BEGIN_OBJECT but was STRING at path $
I can't seem to understand why this is so. This is a sample of the json tested on postman:
{
"customerName": "Name",
"customerPhoneNo": "090000000",
"customerAddress": "Address",
"note": "Please",
"items" : [{
"productUid": "5633e1f1-8b00-46de-b73e-43799245a4e8",
"quantity" : "3"
},{
"ProductUid": "fca3ffb1-0130-4e47-b499-721d046c1e32",
"Quantity" : "5"
},
{
"ProductUid": "6a7f3e24-03ff-408a-b67e-8530d411390c",
"Quantity" : "2"
}]
}
My data classes are set up like so:
#Parcelize
data class Order(
val items: List<Item>?,
val customerName: String,
val customerPhoneNo: String,
val customerAddress: String,
val note: String
) : Parcelable
and
#Parcelize
data class Item(
val productUid: String,
var quantity: Int
) : Parcelable
Service utils look like:
interface ProductService {
#Headers("Content-Type: application/json")
#POST("/api/order/saveorder")
suspend fun postProds(#Body order: Order
): Response<Order>
#GET("/api/product/allproducts")
suspend fun getProds(): Response<List<ProdsItem>>
}
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
object Network {
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi)
.asLenient()
)
.build()
object ProdsApi {
val retrofitService: ProductService by lazy {
retrofit.create(ProductService::class.java)
}
}
}
The sendOrder function is set up like so:
suspend fun sendOrder(order: Order) {
withContext(Dispatchers.Main){
try {
val orderResponse = Network.ProdsApi.retrofitService.postProds(
order )
}
catch (e: Exception) {
Timber.e(e)
}
}
}
The GET request works perfectly.
Any help on this would be appreciated please.
In your Item Data Class you are using quantity as an Int but if you see the Postman JSON response it is a String.
So your class should be like:
data class Item(
#Json(name = "productUid")
val productUid: String?,
#Json(name = "quantity")
var quantity: String
)
Also as I see the key in your JSON response are written in two different ways.
For example your "Product ID" is written as "productUid" in one of the object and is written as "ProductUid" in another object.
So your complete Item Data Class should more look like this :
data class Item(
#Json(name = "productUid")
val productUid: String?,
#Json(name = "ProductUid")
val productUid: String?,
#Json(name = "quantity")
val quantity: String?,
#Json(name = "Quantity")
val quantity: String?
)
Add to app/build.gradle
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.13.0"
Refactor your data class
check the key in your item and replace with right one
if productUid or ProductUid
quantity or Quantity
#JsonClass(generateAdapter = true)
data class Item(
#Json(name = "productUid")
val productUid: String,
#Json(name = "quantity")
var quantity: String
)
#JsonClass(generateAdapter = true)
data class Order(
#Json(name = "items")
val items: List<Item>,
#Json(name = "customerName")
val customerName: String,
#Json(name = "customerPhoneNo")
val customerPhoneNo: String,
#Json(name = "customerAddress")
val customerAddress: String,
#Json(name = "note")
val note: String
)
and try it again
data class NewsResponse(
val articles: List<Article>,
val status: String,
val totalResults: Int
)
data class Source(
val id: String,
val name: String
)
data class Article(
val author: String,
val content: String,
val description: String,
val publishedAt: String,
val source: Source,
val title: String,
val url: String,
val urlToImage: String
)
object RetrofitHelper {
fun getInstances():Retrofit{
return Retrofit.Builder()
.baseUrl("https://newsapi.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
interface NewsService {
#GET("v2/everything?")
suspend fun getNews(#Query("country")country:String,#Query("page")page:Int,#Query("apiKey")apiKey:String):Response<NewsResponse>
}
This below code is written on MainActivity
val newsservice=RetrofitHelper.getInstances().create(NewsService::class.java)
GlobalScope.launch(Dispatchers.IO) {
val result=newsservice.getNews(country,page, apiKey)
if(result.isSuccessful){
Toast.makeText(this#MainActivity,"HEllo World",Toast.LENGTH_LONG).show()
val list=result.body()!!.articles
list.forEach {
Log.d(debug,it.source.id)
}
}
}
I corrected my mistake #Query("country") this was wrong i have to use #Query("q") thats my result giving null list
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.
I'm trying to build custom deserializers for the responses I get from OMDb API.
Here's the data class for Movie:
data class Movie(
val title: String?,
val year: String?,
val imdbID: String?,
val type: String?,
val poster: String?,
val mpaRating: String?,
val runtime: String?,
val genres: String?,
val director: String?,
val writers: List<String>?,
val actors: List<String>?,
val plot: String?,
val awards: String?,
val boxOfficeEarnings: String?,
val ratings: List<Rating>,
val response: Boolean?
)
And for Rating:
data class Rating(
#SerializedName("Source")
val source: String,
#SerializedName("Value")
val value: String
)
This is the custom JsonDeserializer so far:
class MovieDeserializer : JsonDeserializer<Movie>
{
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): Movie
{
val movieJsonObject = json?.asJsonObject
return Movie(
movieJsonObject?.get("Title")?.asString,
movieJsonObject?.get("Year")?.asString,
movieJsonObject?.get("imdbID")?.asString,
movieJsonObject?.get("Type")?.asString,
movieJsonObject?.get("Poster")?.asString,
movieJsonObject?.get("Rated")?.asString,
movieJsonObject?.get("Runtime")?.asString,
movieJsonObject?.get("Genre")?.asString,
movieJsonObject?.get("Director")?.asString,
separateStringByComma(movieJsonObject?.get("Writer")?.asString),
separateStringByComma(movieJsonObject?.get("Actors")?.asString),
movieJsonObject?.get("Plot")?.asString,
movieJsonObject?.get("Awards")?.asString,
movieJsonObject?.get("BoxOffice")?.asString,
// this is where I need help,
movieJsonObject?.get("Response")?.asBoolean
)
}
fun separateStringByComma(stringToSeparate: String?): List<String>?
{
return stringToSeparate?.split(", ")
}
}
How can I convert that JsonElement directly to List<Rating> without some json string manipulation?
By the way, I'm using Retrofit with Gson:
val gsonMovieDeserializer = GsonBuilder()
.registerTypeAdapter(Movie::class.java, MovieDeserializer())
.create()
val retrofit = Retrofit.Builder()
.baseUrl("https://www.omdbapi.com/")
.addConverterFactory(GsonConverterFactory.create(gsonMovieDeserializer))
.build()
val omdbApi = retrofit.create(OmdbApi::class.java)
val movie = omdbApi.getMovie(movieImdbId.value.toString())
First of all, I'd like to point the usage of nullables there: instead of checking wheter movieJsonObject is null or not for every call inside deserialize(), you should change the function parameters not to be null and then check only once, right at the beggining, if json is a JsonObject, just skipping everything if it's not. That way, we have a solid base to extract the data. Also, for the Movie data class, check the API documentation for which fields are optional and only set those to nulalble (I'm pretty sure at least the title and ID there are always present, so it's way more useful to have them as non-nullable).
Now, for the question itself, you should probably be able to deserialize that list using context.deserialize<List<Rating>>(movieJsonObject.get("Ratings"), List::class.java), which, in Kotlin, will return a type-safe List<Rating> (but, again, make sure that's not an optional field in the API and, if it is, make it nullable).