I cant make Kotlin Serializer work with Retrofit. I am using com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.5.0 package along with Retrofit.
data classes
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
#Serializable
data class YelpSearchResult(
#SerialName("total") val total: Int,
#SerialName("businesses") val restaurants: List<YelpRestaurant>
)
data class YelpRestaurant(
val name: String,
val rating: Double,
val price: String,
#SerialName("review_count") val numReviews: Int,
#SerialName("distance") val distanceInMeters: Double,
#SerialName("image_url") val imageUrl: String,
val categories: List<YelpCategory>,
val location: YelpLocation
) {
fun displayDistance(): String {
val milesPerMeter = 0.000621371
val distanceInMiles = "%.2f".format(distanceInMeters * milesPerMeter)
return "$distanceInMiles mi"
}
}
data class YelpCategory(
val title: String
)
data class YelpLocation(
#SerialName("address1") val address: String
)
service interface
public interface YelpService {
#GET("businesses/search")
fun searchRestaurants(
#Header("Authorization") authHeader: String,
#Query("term") searchTerm: String,
#Query("location") location: String): Call<YelpSearchResult>
}
The Activity
val contentType = MediaType.get("application/json")
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(Json.asConverterFactory(contentType))
.build()
val yelpService = retrofit.create(YelpService::class.java)
yelpService.searchRestaurants("Bearer ${api_key}",
"Something", "Some Location").enqueue(
object: Callback<YelpSearchResult> {
override fun onFailure(call: Call<YelpSearchResult>, t: Throwable) {
Log.d("MainActivity", "err ${t}")
}
override fun onResponse(call: Call<YelpSearchResult>, response: Response<YelpSearchResult>) {
Log.d("MainActivity", "succ ${response}")
}
}
)
When Running, the thrown exception is,
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.xx.xx/com.xx.xx.MainActivity}:
java.lang.IllegalArgumentException: Unable to create converter for
class com.xx.xx.YelpSearchResult
Unable to create converter for class com.xx.xx.YelpSearchResult
for method YelpService.searchRestaurants
Caused by: kotlinx.serialization.SerializationException: Can't locate
argument-less serializer for class YelpSearchResult. For generic
classes, such as lists, please provide serializer explicitly.
What am I doing wrong? thanks for any directions.
You forgot to put #Serializable annotation on top of YelpRestaurant
#Serializable
data class YelpRestaurant {
...
}
YellCategory and YelpLocation should also have the annotation at place.
Add Serializable in your data classes
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
#Serializable
data class YelpSearchResult(
#SerialName("total") val total: Int,
#SerialName("businesses") val restaurants: List<YelpRestaurant>
)
#Serializable
data class YelpRestaurant(
val name: String,
val rating: Double,
val price: String,
#SerialName("review_count") val numReviews: Int,
#SerialName("distance") val distanceInMeters: Double,
#SerialName("image_url") val imageUrl: String,
val categories: List<YelpCategory>,
val location: YelpLocation
) {
fun displayDistance(): String {
val milesPerMeter = 0.000621371
val distanceInMiles = "%.2f".format(distanceInMeters * milesPerMeter)
return "$distanceInMiles mi"
}
}
#Serializable
data class YelpCategory(
val title: String
)
#Serializable
data class YelpLocation(
#SerialName("address1") val address: String
)
Related
need help in parsing this json file from this endpoint
http://ergast.com/api/f1/constructors
this is my data class
data class Teams(
val MRData: MRdata
){
data class MRdata(
val ConstructorTable: ConstructorsTable,
val limit: String,
val offset: String,
val series: String,
val total: String,
val url: String,
val xmlns: String
){
data class ConstructorsTable(
val Constructors: List<Constructor>?
) {
data class Constructor(
val constructorId: String?,
val name: String?,
val nationality: String?,
val url: String?
)
}
}
}
when i use Teams class as a return model it logs actual data but, when i try to return specific data like constructorID or nationality it returns null.
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'm trying to create a request using Retrofit2 in Kotlin and my Json data looks like this:
{
"actionRights": [
{
"hasArticlePriceChangeRights": false,
"hasStockLevelViewRights": false
}
],
"stats": {
"errors": 12,
"warnings": 21,
"outOfOrder": 58,
"running": 42
},
"markers": []
}
I need to get the "stats" data from my response.
How is my model class look like? I tried something but I get this error.
Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
EDIT: I created this ModelClass and I'm still getting this error:
Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
data class StatsModel(
#SerializedName("actionRights") val actionRights : List<ActionRights>,
#SerializedName("stats") val stats : Stats,
#SerializedName("markers") val markers : List<Markers>
) {
data class ActionRights (
#SerializedName("hasArticlePriceChangeRights") val hasArticlePriceChangeRights : Boolean,
#SerializedName("hasStockLevelViewRights") val hasStockLevelViewRights : Boolean
)
data class Stats (
#SerializedName("errors") val errors : Int,
#SerializedName("warnings") val warnings : Int,
#SerializedName("outOfOrder") val outOfOrder : Int,
#SerializedName("running") val running : Int
)
data class Markers (
#SerializedName("deviceGuid") val deviceGuid : String,
#SerializedName("lat") val lat : Double,
#SerializedName("lng") val lng : Double,
#SerializedName("title") val title : Int,
#SerializedName("city") val city : String,
#SerializedName("street") val street : Int,
#SerializedName("serialNumber") val serialNumber : Int,
#SerializedName("LastStatusMessageDateTime") val lastStatusMessageDateTime : String,
#SerializedName("LastStatusMessageBitSet_0_63") val lastStatusMessageBitSet_0_63 : Int,
#SerializedName("LastStatusMessageBitSet_64_127") val lastStatusMessageBitSet_64_127 : Int,
#SerializedName("ActionsStatus") val actionsStatus : Int,
#SerializedName("SpareColumn1") val spareColumn1 : String,
#SerializedName("SpareColumn2") val spareColumn2 : String,
#SerializedName("SpareColumn3") val spareColumn3 : String,
#SerializedName("SpareColumn4") val spareColumn4 : String,
#SerializedName("SpareColumn5") val spareColumn5 : String,
#SerializedName("SpareColumn6") val spareColumn6 : String,
#SerializedName("SpareColumn7") val spareColumn7 : String,
#SerializedName("SpareColumn8") val spareColumn8 : String,
#SerializedName("SpareColumn9") val spareColumn9 : String,
#SerializedName("SpareColumn10") val spareColumn10 : String,
#SerializedName("EstimatedDeliveryDateTime") val estimatedDeliveryDateTime : String,
#SerializedName("IpAddress") val ipAddress : String,
#SerializedName("Active") val active : Int,
#SerializedName("ParentAreaGuid") val parentAreaGuid : String,
#SerializedName("AreaGuid") val areaGuid : String,
#SerializedName("TariffGroupGuid") val tariffGroupGuid : String,
#SerializedName("DeviceType") val deviceType : String,
#SerializedName("EstimateArticleName") val estimateArticleName : String,
#SerializedName("EstimateArticleGuid") val estimateArticleGuid : String,
#SerializedName("lastCoinboxExchange") val lastCoinboxExchange : String,
#SerializedName("lastStatusUpdateTime") val lastStatusUpdateTime : String,
#SerializedName("reportDateTime") val reportDateTime : String,
#SerializedName("hasFinancialInfo") val hasFinancialInfo : Boolean,
#SerializedName("ticketsSold") val ticketsSold : Int,
#SerializedName("cash") val cash : Int,
#SerializedName("cashless") val cashless : Int,
#SerializedName("hasStockLevel") val hasStockLevel : Boolean,
#SerializedName("hasArticlePrices") val hasArticlePrices : Boolean,
#SerializedName("EstDeliveryDays") val estDeliveryDays : String,
#SerializedName("hasOther") val hasOther : Boolean,
#SerializedName("hasOutOfOrder") val hasOutOfOrder : Boolean,
#SerializedName("hasWarning") val hasWarning : Boolean,
#SerializedName("hasError") val hasError : Boolean,
#SerializedName("flags") val flags : List<Flags>,
#SerializedName("actionState") val actionState : String,
#SerializedName("spareColumns") val spareColumns : List<SpareColumns>
)
data class Flags (
#SerializedName("ErrorLevel") val errorLevel : Int,
#SerializedName("ErrorFlag") val errorFlag : Int,
#SerializedName("Translation") val translation : String,
#SerializedName("BitPosition") val bitPosition : Int
)
data class SpareColumns (
#SerializedName("key") val key : String,
#SerializedName("value") val value : String
)
}
And I'm processing the response like this:
fun getStatusService(mandatorGuid: String, #Nullable statusCallbacks: ChartsCallbacks.StatsCallbacks) {
val mandatorItem = MandatorItem(mandatorGuid)
val guid: MutableMap<String, String> = HashMap()
guid["guid"] = mandatorItem.guid
var statusData: StatsModel.Stats
val call: Call<StatsModel> = createAPI().getStatus(mandatorItem.guid)
call.enqueue(object : Callback<StatsModel> {
override fun onFailure(call: Call<StatsModel>, t: Throwable) {
Log.i("TEST", "Status failure: $t")
}
override fun onResponse(call: Call<StatsModel>, response: Response<StatsModel>) {
if (response.isSuccessful) {
Log.i("TEST", "ITEM " + response.headers().toString())
statusData = response.body()!!.stats
Log.i("TEST", "ITEM $statusData")
statusCallbacks.onSuccess(statusData)
}
}
})
}
data class ActionRight(
val hasArticlePriceChangeRights: Boolean?=null,
val hasStockLevelViewRights: Boolean?=null
)
this is for state
data class Stats(
val errors:Int? = null,
val warnings? = null,
val outOfOrder? = null,
val running? = null
)
call this model into your retrofit you got only your state
data class StatsModel(
val actionRights: ArrrayList<ActionRight>?=null,
val markers: ArrrayList<Any>?=null,
val stats: Stats? = null)
Your main model:
data class MyModel(
val actionRights: List<ActionRight?>?,
val markers: List<Marker?>?,
val stats: Stats?
)
Your Sub Models:
data class ActionRight(
val hasArticlePriceChangeRights: Boolean?,
val hasStockLevelViewRights: Boolean?
)
data class Marker(
val test1: Int?,
val test2: String?
)
data class Stats(
val errors: Int?,
val outOfOrder: Int?,
val running: Int?,
val warnings: Int?
)
I create a fake Marker class because I don't know your full json data.
You should modelling Marker class for according your json data. You can call MyModel class from retrofit.
Edit Response (Only parse Stats class)
This class represent parse only Stats class, you can call from retrofit. #JsonIgnoreProperties should ignore other data.
#JsonIgnoreProperties(ignoreUnknown = true)
data class MyModel(
#JsonProperty("stats")
val stats: Stats?
)
#JsonIgnoreProperties(ignoreUnknown = true)
data class Stats(
#JsonProperty("errors")
val errors: Int?,
#JsonProperty("outOfOrder")
val outOfOrder: Int?,
#JsonProperty("running")
val running: Int?,
#JsonProperty("warnings")
val warnings: Int?
)
Note: The code use Jackson library.
implementation 'com.squareup.retrofit2:converter-jackson:2.5.0'
I'm trying to get some results from json but i have a problem.When i have only this line of code it works and i can get the temperature
var main: TemperatureData? = null
but i want also to get more values from my json so i insert this line of code
var weather: WeatherDataJson? = null
When i'm adding the second line it doesn't fetch any data(it even stops fetching the var main:... data)
This is my weather data class
class WeatherData {
var main: TemperatureData? = null
var weather: WeatherDataJson? = null
}
here is my interface class
interface ApiInterface {
#GET("data/2.5/weather?q=Prague")
fun getWeatherData(#Query("appid") appId: String)
: Call<WeatherData>
}
My TemperatureData class
data class TemperatureData(var temp: String)
And my WeatherDataJson class
data class WeatherDataJson(var description:String)
In my mainclass i have a function to show the json into my screen but i can't load the "var weather..."
Here is the function
private fun getTemperatureData(repository: Repository) {
repository.getApiInterface()
.getWeatherData("4cf7f6610d941a1ca7583f50e7e41ba3")
.enqueue(object : Callback<WeatherData> {
override fun onFailure(call: Call<WeatherData>?, t: Throwable?) {
t?.printStackTrace()
}
override fun onResponse(call: Call<WeatherData>?, response: Response<WeatherData>?) {
val weatherData: WeatherData? = response?.body()
weatherData?.let {
it.main?.let {
tempText.text = it.temp
}
it.weather?.let{
weatherTextFromApi.text=it.description
}
}
}
})
}
On the description is says unresolved reference description
If you use openweathermap api declare those data classes
data class WeatherData(
#SerializedName("coord") val coord: Coord,
#SerializedName("weather") val weather: List<Weather>,
#SerializedName("base") val base: String,
#SerializedName("main") val main: TemperatureData,
#SerializedName("visibility") val visibility: Int,
#SerializedName("wind") val wind: Wind,
#SerializedName("clouds") val clouds: Clouds,
#SerializedName("dt") val dt: Int,
#SerializedName("sys") val sys: Sys,
#SerializedName("id") val id: Int,
#SerializedName("name") val name: String,
#SerializedName("cod") val cod: Int
)
data class Sys(
#SerializedName("type") val type: Int,
#SerializedName("id") val id: Int,
#SerializedName("message") val message: Double,
#SerializedName("country") val country: String,
#SerializedName("sunrise") val sunrise: Int,
#SerializedName("sunset") val sunset: Int
)
data class Coord(
#SerializedName("lon") val lon: Double,
#SerializedName("lat") val lat: Double
)
data class TemperatureData(
#SerializedName("temp") val temp: Double,
#SerializedName("pressure") val pressure: Int,
#SerializedName("humidity") val humidity: Int,
#SerializedName("temp_min") val tempMin: Double,
#SerializedName("temp_max") val tempMax: Double
)
data class Weather(
#SerializedName("id") val id: Int,
#SerializedName("main") val main: String,
#SerializedName("description") val description: String,
#SerializedName("icon") val icon: String
)
data class Clouds(
#SerializedName("all") val all: Int
)
data class Wind(
#SerializedName("speed") val speed: Double,
#SerializedName("deg") val deg: Int
)
if you goal is get description from weather all you need will be
weatherData.weather.firstOrNull()?. description ?: ""
For getting url of icon
It is kind of tricky, you could get url from response but only url of icon.
val iconId = weatherData.weather.firstOrNull()?. description
val iconUrl = iconId?.let{"http://openweathermap.org/img/w/${it}.png"} ?: ""
After that you should use Glide library or Picasso for loading imageUrl to ImageView
See more about Glide here https://github.com/bumptech/glide
The server response from OpenWeatherMap is returning a JSON array, not a JSON object, for weather. Gson/Retrofit cannot automatically turn an array into a single Object, so you need to change your data class to hold a List (or use a custom Gson deserializer and manually grab the item you want from the array).
Example of what your top level class should be:
class WeatherData {
var main: TemperatureData? = null
var weather: List<WeatherDataJson>? = null
}