Pulling data from an API with okHTTP & GSON - android

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.

Related

How I can access JSON data for two brackets. - Retrofit2

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.

Default argument not passed in body request

I got a request (used with kotlinx.serialization.Serializable):
#Serializable
data class Request(
val a: String,
val b: String,
val c: String = "version"
)
Part of retrofit / API code where I use this:
#PUT(value = "path/to/endpoint")
suspend fun sendRequest(#Body request: Request): Response
override fun sendRequest(request: Request) =
flow<Resource<Response, NetworkError>> {
emit(Resource.Success(network.sendRequest(request)))
}.catch {
emit(Resource.Error(it.parseNetworkError()))
}
The default parameter val c: String = "version" does not follow in the request.
However, if I pass it in while creating the Request it works. Or if I put a breakpoint inspecting it before the Request is sent.
So this works:
Request("test", "test", "version")
But not this:
Request("test", "test")
Is this the compiler being smart and omitting it or something? How can I prevent it from doing so?
This appears to be by design. According to the kotlinx.serialization documentation...
Default values are not encoded by default in JSON.
This behavior is motivated by the fact that in most real-life
scenarios such configuration reduces visual clutter, and saves the
amount of data being serialized.
If you want to turn it on for a property, you can annotate it with #EncodeDefault
#Serializable
data class Request(
val a: String,
val b: String,
#EncodeDefault val c: String = "version"
)

Need help Kotlin Coroutines, Architecture Component and Retrofit

I'm trying to wrap my head around the mentioned components and I can't get it right. I want to do something very simple: Fetch data from the network and present it to the user. Currently am not yet caching it as am still learning new Coroutine features in Architecture components. Every time app loads I get an empty model posted, which seems weird.
My API is get hit fine and response is 200 which is OK.
Below is what I have attempted:
POJO
data class Profile(#SerializedName("fullname") val fullName : String.....)
Repository
class UserRepo(val context: Context, val api: Api) {
suspend fun getProfile(): Profile
{
val accessToken = ....
return api.getUserProfile(accessToken)
}
}
API
interface GatewayApi {
#GET("users/profile")
suspend fun getUserProfile(#Query("access-token") accessToken: String?): Profile
}
ViewModel
class UserViewModel(application: Application) : AndroidViewModel(application) {
private val usersRepo = UserRepo(application.applicationContext, Apifactory.Api)
val userProfileData = liveData{
emit(usersRepo.getProfile())
}
fun getProfile() = viewModelScope.launch {
usersRepo.getProfile()
}
}
Finally my fragment's relevant code
val viewModel = ViewModelProviders.of(activity!!).get(UserViewModel::class.java)
viewModel.userProfileData.observe(this, Observer<UserProfile> {
//it is having nulls
})
//trigger change
viewModel.getProfile()
So I added HTTP requests and responses (thanks to #CommonsWare for pointing that out) and it happened I had used a different model than I was supposed to use. The correct model that mapped the JSON response was ProfileResponse and as you can see in my posted code, I used Profile instead. So all fields were empty as Gson could not correctly serialize JSON into Profile object.
All the credit goes to #CommonsWare for pointing that out in comment.

Retrofit Parameter specified as non-null is null

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.

Kotlin - Retrofit request using RxJava gives null response

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.

Categories

Resources