I'm trying to get news from Guardian API. I'm getting null response, everything is below. I'm using Kotlin, Retrofit and RxJava. I know that there are some miscalled variables/objects but I will change them when I will get rid of that problem.
Retrofit interface
#get:GET("search?api-key=test")
val news:Observable<News>
Retrofit client
val instance : Retrofit
get() {
if (myInstance == null) {
myInstance = Retrofit.Builder()
.baseUrl("https://content.guardianapis.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
return myInstance!!
}
And function where I'm loading data
private fun loadUrlData() {
compositeDisposable.add(jsonApi.news
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe{news -> displayData(news)})
}
JSON example
{
response:{
status:"ok",
userTier:"developer",
total:2063064,
startIndex:1,
pageSize:10,
currentPage:1,
pages:206307,
orderBy:"newest",
results:[
{
id:"politics/2018/sep/24/keir-starmer-labour-does-not-rule-out-remaining-in-eu",
type:"article",
sectionId:"politics",
sectionName:"Politics",
webPublicationDate:"2018-09-24T18:57:48Z",
webTitle:"Keir Starmer: Labour does not rule out remaining in EU as option",
webUrl:"https://www.theguardian.com/politics/2018/sep/24/keir-starmer-labour-does-not-rule-out-remaining-in-eu",
apiUrl:"https://content.guardianapis.com/politics/2018/sep/24/keir-starmer-labour-does-not-rule-out-remaining-in-eu",
isHosted:false,
pillarId:"pillar/news",
pillarName:"News"
}
]
}
}
Model class
data class News( val status: String, val userTier: String, val total: Int, val startIndex: Int, val pageSize: Int, val currentPage: Int, val pages: Int, val orderBy: String, val results: List<Result>)
I suppose that the problem is with the last function or with the interface but I can't find the solution.
The issues lies within your data model class.
Your JSON has an outer node (response) and if you're trying to return a News you won't get it, because Retrofit can't map the JSON to the News class. Add an outer class called Response that holds a field called response that is of type News, that should fix it.
Like so:
class Response(val response: News)
Note: I haven't added data in front of the class as you don't necessarily need it. The data keyword just adds some extra things for you automatically, like toString(), equals() and hashCode(), but unless you're actually using them for anything I wouldn't recommend adding the data keyword as it's pretty useless then.
Related
I am working with an api that displays currency prices. Although I tried many times, I could not find a way to follow the JSON map. So I can't access the data.
My JSON format below
{"USD":{"satis":"18.6391","alis":"18.6268","degisim":"0.07"},"EUR":{"satis":"19.2998","alis":"19.2894","degisim":"0.57"}
my api interface code below:
interface CurrencyAPI {
#GET("embed/para-birimleri.json")
fun getData(): Call<List<CurrencyModel>>
}
my main class
private fun loadData()
{
val retrofit=Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val service=retrofit.create(CurrencyAPI::class.java)
val call=service.getData()
call.enqueue(object :Callback<List<CurrencyModel>>
{
override fun onResponse(
call: Call<List<CurrencyModel>>,
response: Response<List<CurrencyModel>>
) {
if (response.isSuccessful)
{
response.body()?.let {
currencyModels=ArrayList(it)
}
}
}
override fun onFailure(call: Call<List<CurrencyModel>>, t: Throwable) {
t.printStackTrace()
}
})
}
my model class
import com.google.gson.annotations.SerializedName
data class CurrencyModel(
#SerializedName("satis")
val selling:String,
#SerializedName("alis")
val buying:String,
#SerializedName("degisim")
val change:String
)
I tried data via list but I Could not get data.
Each time you see { in JSON it represents the start of a new object, which means that you have a top level object that has multiple values (in your case USD and EUR) that are each objects themselves. You've created a class representing these inner objects correctly, but you are incorrectly trying to deserialize the entire JSON body as a list/array rather than an object. Now, there are a few things to consider when deciding how to deserialize the entire JSON body:
Do you know all of the possible keys ahead of time?
Will these keys stay the same for the foreseeable future?
Are these keys static or dynamic?
If you answered no to either of the first 2 questions, or answered dynamic to the last one, then you won't want to make a class representing the object and use a Map<String, CurrencyModel> instead. If you answered yes to both of the first 2 questions, and the answer to the last one was static, then you can make a class to represent the entire body, where each property of the class has type CurrencyModel, though you can still use the map above.
I have 3 classes:
GameResultList which is basically ArrayList with some helper methods in it
GameResult with an abstract value gameMode
GameMode
public class GameResultList extends ArrayList<GameResult> {
...
}
class GameResult(
val gameMode: GameMode,
val score: Int,
timeSpentInSeconds: Int,
val completionDateTime: Date
) {
...
}
GameMode class:
abstract class GameMode(
val suggestionsActivated: Boolean,
val screenOrientation: ScreenOrientation // enum: PORTRAIT, HORIZONTAL
) {
...
}
I need to serialize GameResultList into JSON.
Since the parameter gameMode is abstract, Gson throws an exception. After some research, I decided to give Moshi a try. I have added PolymorphicJsonAdapterFactory and KotlinJsonAdapterFactory, but the result is always empty ({}).
How I set up Moshi:
private val moshi =
Moshi.Builder().add(PolymorphicJsonAdapterFactory.of(GameMode::class.java, "GameMode")
.withSubtype(GameOnTime::class.java, "GameOnTime")
.withSubtype(GameOnCount::class.java, "GameOnCount"))
.add(KotlinJsonAdapterFactory())
.build()
private val jsonAdapter: JsonAdapter<GameResultList> = moshi.adapter(GameResultList::class.java)
This returns empty JSON response:
jsonAdapter.toJson(gameResultList)
So how can I serialize the GameResultList? Is there an easy way? Also, it's not necessary to use Moshi, it can be anything else for the sake of easiness.
After some investigation, I found out that the main problem is that array lists require explicit converters.
class GameResultListToJsonAdapter {
#ToJson
fun arrayListToJson(list: GameResultList): List<GameResult> = list
#FromJson
fun arrayListFromJson(list: List<GameResult>): GameResultList = GameResultList(list)
}
Also, there is a problem with handling the Date type, I have replaced it with Long to not make another explicit converter.
I'm new in Kotlin and I'm trying to parse a simple JSON, but I'm getting an
" Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter vouchers"
result.products is always null, but I can see in the logs that retrofit is getting correctly the json with a 200 ok request. So I suppose that could be a problem when I'm trying to parse the json
How can I solve this?
I have add my code below
disposable = ApiServe.getVouchers()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> processVouchers(result.products) },
{ error -> error(error.message)}
)
fun processVouchers(vouchers : List<Product>){
mCallback?.onResponseVouchers(vouchers)
}
GET VOUCHERES in ApiServe class
#GET(Api.ENDPOINT.VOUCHER_ENDPOIN)
fun getVoucher(): Observable<Response<Vouchers>>
MODEL
data class Voucher(val products: List<Product>)
data class Product(val code: String, val name: String, val price: Double)
JSON
{"products":[{"code":"Voucher","name":"Voucher","price":3},{"code":"Ball","name":"Voucher Ball","price":10},{"code":"Milk","name":"Voucher Milk","price":8.5}]}
I think it's because you're wrapping your return type with Response in your Retrofit services interface. Just try to change like this:
#GET(Api.ENDPOINT.VOUCHER_ENDPOIN)
fun getVoucher(): Observable<Voucher>
I believe that the issue here might be that
fun getVouchers(): Observable<Voucher.Vouchers>
Are you sure that getVoucher returns the correct type? Shouldn't it be Observable<Voucher> ?
Edit:
It turned out that author was using excludeFieldsWithModifiers for his GsonConverterFactory, which was causing issues with parsing to model.
I am using retrofit to get data from thing speak. And response that I am getting is containing JSON objects like "field1", "field2" etc. Is it possible to parse this data by Retrofit to get list containing of this elements?
For now I have parsing made like this:
#SerializedName("field1")
val field1: Float?,
#SerializedName("field2")
val field2: Float?,
#SerializedName("field3")
val field3: Float?,
#SerializedName("field4")
val field4: Float?,
#SerializedName("field5")
val field5: Float?,
#SerializedName("field6")
val field6: Float?,
#SerializedName("field7")
val field7: Float?,
#SerializedName("field8")
val field8: Float?
Response that I am getting:
{"created_at":"2019-05-24T06:11:43Z","entry_id":15419693,"field1":"370","field2":"56.390658174097666"}
And I would like to get something like this:
#SerializedName("field[]")
val fields List<Float>
Is that possible somehow?
You could create a custom JSON parser, but it isn't recommended. It's much better to use Retrofit parser.
You can't parse as you described, but it would be much better if you could update server response, so it looks like this:
{"created_at":"2019-05-24T06:11:43Z","entry_id":15419693,"fields":["370",:"56.390658174097666", "45"]}
And then in your class define
#SerializedName("fields")
var fields: List<Float>?
EDIT:
If you can't update server response, everything you can do is to define helper function:
public fun getFields() : ArrayList<Float?> {
var list = ArrayList<Float?>()
list.add(field1)
list.add(field2)
list.add(field3)
list.add(field4)
list.add(field5)
list.add(field6)
list.add(field7)
list.add(field8)
list.removeIf { it == null }
return list;
}
New to using API's (and kotlin for that matter) and I'm having some trouble figuring out how to pull data from an API into model objects and was hoping someone could point me in the right direction. Sample code below.
val request = Request.Builder().header("X-Mashape-Key", keyVal).url(url).build()
//make request client
val client = OkHttpClient()
//create request
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call?, response: Response?) {
//grab as string, works fine
val body = response?.body()?.string()
//make my builder, works fine
val gson = GsonBuilder().create()
// to pass type of class to kotlin ::
val cardFeed = gson.fromJson(body, CardFeed::class.java)
}
override fun onFailure(call: Call?, e: IOException?) {
println("Failed to execute request")
}
})
}
All of that seems to work as intended in debug, I get the string, and can see it but using the following it still dumps a null into the cardFeed/cards object.
class CardFeed(val cards: List<Card>)
class Card(val cardId: String, val name: String, val text: String, val flavor: String)
The body string I'm getting from the API reads as follows
body: "{Basic":[{"key":"value", etc
I'm assuming the [ is what's tripping me up, but not sure how to correct it.
Any ideas or nudges in the correct direction would be greatly appreciated and thanks ahead of time.
According to your class structure, the JSON object that you should get from the API should be
{
"cards":[
{
"cardId":"<cardID>",
"name":"<name>",
"flavor":"<flavor>"
},
{
"cardId":"<cardID>",
"name":"<name>",
"flavor":"<flavor>"
}
]
}
with the correct keys because when you use gson.fromJson(body, CardFeed::class.java), it looks for the variable names in the class assigned (CardFeed.java). If you don't have the correct keys ("cards", "cardId", "name", "flavor"), gson would return null for the values
okHttp is a low level library. In order to consume JSON based APIs, Retrofit is a much more suitable library, as it already have converters that use libraries like GSON or Moshi to do all the heavy lifting for you.