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

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.

Related

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.

Android MVVM: how can I update data in a Repository from another Repository, when same data is part of response from different server endpoints?

I'm trying to move Android project to MVVM. Two different repositories get same data response, when calling different server endpoints. What is the right way to keep both up-to-date?
There are different server endpoints returning "UserData": "/get_user_data" and "/get_user_statistics". I need to update data in "UserRepository", when making request in "UserStatisticsRepository" (which returns UserData as well).
Should I inject both Repositories in a ViewModel and then set the "UserData" via ViewModel to "UserRepository"? It just feels to me not really right way to set data to a Repository from a ViewModel...
Let's say, I have:
data class UserData(
#SerializedName("id") val id: Int,
#SerializedName("name") val name: String
)
and
data class UserStatisticData(
#SerializedName("id") val id: Int,
#SerializedName("active_users_count") val activeUsersCount: Int,
#SerializedName("users") val users: List<UserData>
)
and
class UserStatisticRepository(...) {
...
suspend fun makeUserStatisticDataRequest(){
withContext(Dispatchers.IO) {
val networkResponse = myAppAPI?.getUserStatisticData(params)
try {
if (networkResponse!!.isSuccessful) {
_userStatisticData.value = networkResponse.body()?.userStatisticData
// TODO: Should I set UserData (networkResponse.body()?.userData) to UserRepository here???
// How can I access UserRepository?
} else {
throw UserStatisticDataResponseError(Exception("Response body error..."))
}
} catch (e: Exception) {
throw UserStatisticDataResponseError(Exception("Network error..."))
}
}
}
...
}
I am not sure how your projwct is set up but If I was in this kind of situation I would :
Either have a single state that is mutated by both of the repository, make it reactive. Both of the repostitory can take that state as argument when being constructed and they can also mutate the state.
A reactive database that is mutated by both the calls. i.e. whenever any new UserData entity is fetched, its written to databse. When we need UserData we listen it directly from Reactive Database like "Room" instead of those API endpoints directly.
When to do what?
I would choose the first option if I did not have to persist the fetched data & I am cool with losing all the data once app is closed where as If I wanted the data from those endpoints to survive App restart the I would go for the second options.
For the first option the code may look like this :
data class UserDataSource(val userDataList : List<UserData>)
class UserDataRepo(val subject : PublishSubject<UserDataSource>){
UserdataService.getUserData(userID).subscribe{
// Do calculations to find the old list of user and edit or update the new user
subject.onNext(UserDataSource(newUserList))
}
}
class UserStatRepo(subject : PublishSubject<UserDataSource>){
UserdataService.getUserStat().subscribe{
// Do calculations to find the old list of user and edit or update the new user
subject.onNext(UserDataSource(newUserList))
}}
In Ui subscribe to the userDataSource subject..
class UserDataViewModel{
val userDataSource = PublishSubject.create<userDataSource>()
val userDataRepository = UserDataRepo (userDataSource)
val userDataRepository = UserStatRepo (userDataSource)
}

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.

Class inheritance and parametrized types in Kotlin: Is Subtype Polymorphism possible with generics?

I'm struggling to understand and/or make Kotlin generics & polymorphism work for me. Consider this code:
class Item<T: BaseAttributes> {
var id: Long = -1L
lateinit var type: String
lateinit var attributes: T
}
open class BaseAttributes {
lateinit var createdAt: String
lateinit var updatedAt: String
}
open class BaseResponseList<T : BaseAttributes> {
lateinit var items: List<Item<T>> // the collection of items fetched from an API
}
class FruitAttributes(val id: Long, val color: String /* ... */) : BaseAttributes()
class FruitResponseList: BaseResponseList<FruitAttributes>()
// base service for all types of items
interface ApiService {
fun getItems(): BaseResponseList<BaseAttributes>
// fun getItemById(itemId: Long): BaseResponse<BaseAttributes>
/* other CRUD functions here ... */
}
// service for fruits
interface FruitService: ApiService {
override fun getItems(): FruitResponseList // get fruit items
}
I'm stumped by this compiler error that suggests FruitResponseList is not a subtype of the parametrized base class (BaseResponseList<FruitAttributes>):
Return type of 'getItems' is not a subtype of the return type of the overridden member 'public abstract fun getItems(): BaseResponseList<BaseAttributes> defined in ApiService'
I try to use declaration-site covariance in BaseAttributes to tell the compiler my intention that a FruitResponseList is a subclass of the base response list like this:
open class BaseResponseList<out T : BaseAttributes> {
lateinit var items: List<Item<T>> // the collection of items fetched from an API
}
leads to this error:
Type parameter T is declared as 'out' but occurs in 'invariant' position in type List<Item<T>>
How can I achieve the type-subtype relation between Fruit & Base response lists?
The Context
I'm implementing the networking code to perform CRUD operations against an API that's based on the JSON API spec format, thus I created the attributes and data (Item) classes to represent the json response objects.
My goal is to reduce the amount of duplicated code so that I only have to write the API service declarations once for every entity in my application (fruits, vendors, buyers, etc). I also want to avoid writing duplicated/boilerplate implementations of the data repository layers for every entity in my app (in the context of clean architecture). I should be able to just specify the business entity-specific types (models/entities) and let the one generic implementation do the work of fetching network data.
I thought it would make sense to use generics and inheritance to achieve this. In this particular example, the idea is that the fruit-specific GET would return a fruit response list, which is a subtype of the base response list. Will greatly appreciate any guidance on this, or alternative approaches to this problem
I'm stumped by this compiler error that suggests FruitResponseList is not a subtype of the parametrized base class (BaseResponseList<FruitAttributes>):
It is a subtype of BaseResponseList<FruitAttributes>, which isn't a subtype of BaseResponseList<BaseAttributes>.
I try to use declaration-site covariance in BaseAttributes to tell the compiler my intention that a FruitResponseList is a subclass of the base response list like this:...
This could be a correct approach, but the problem is that Item is not covariant (and it can't be because attributes is a var and its setter takes a T parameter). If Item can be modified to avoid this, good.
Another approach would be to add a type parameter to ApiService:
// base service for all types of items
interface ApiService<T: BaseAttributes> {
fun getItems(): BaseResponseList<T>
// fun getItemById(itemId: Long): BaseResponse<T>
/* other CRUD functions here ... */
}
// service for fruits
interface FruitService: ApiService<FruitAttributes> {
override fun getItems(): FruitResponseList // get fruit items
}
Why not something like this :
// base service for all types of items
interface ApiService<T> {
fun getItems(): T
// fun getItemById(itemId: Long): BaseResponse<BaseAttributes>
/* other CRUD functions here ... */
}
// service for fruits
interface FruitService: ApiService<FruitResponseList> {
override fun getItems(): FruitResponseList
}
or this :
// base service for all types of items
interface ApiService<T : BaseAttributes> {
fun getItems() : BaseResponseList<T>
// fun getItemById(itemId: Long): BaseResponse<BaseAttributes>
/* other CRUD functions here ... */
}
// service for fruits
interface FruitService: ApiService<FruitAttributes> {
override fun getItems(): FruitResponseList
}
You will be bounded to BaseResponseList<BaseAttributes> in your current implementation.
The answers provided by Mark and Alexey were very helpful. Particularly, parametrizing the ApiService on the BaseAttributes was the more flexible option since it allowed functions to take or return both BaseResponseList and BaseResponse [sub]types.
However, for what it's worth, it turns out that the Retrofit library does not allow its service declarations to extend other interfaces or even to be parametrized, supposedly in favor of composition over inheritance. There's plenty of debate about their decision on this issue here.
So I ended up creating separate interfaces for every one of my models :/

Pulling data from an API with okHTTP & GSON

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.

Categories

Resources