populate spinner with retrofit and moshi - android

i am beginner in android dev (both java or kotlin). I am trying to populate spinner from json with retrofit and moshi but I have no idea how to populate it into spinner. to be honest I dont know whether the return of Json data is correct or not, since Log.d() return is not detail as dump() laravel or php.
script in activity onCreate (Please read the comment of the script, i put debug result of Log.d() there):
val task = object : AsyncTask<Void, Void, Response<List<ProductTypeResponse>>>() {
override fun doInBackground(vararg params: Void): Response<List<ProductTypeResponse>> {
val typeAPI = RestAPI()
val callResponse = typeAPI.getNews()
val response = callResponse.execute()
return response
}
override fun onPostExecute(response: Response<List<ProductTypeResponse>>) {
if (response.isSuccessful) {
val news = response.body()
Log.d("test:", news!![0].data.toString()) // method 'java.lang.String com.example.mockie.tigaer.api.TypeDataResponse.toString()' on a null object reference
Log.d("test:", news!!.size.toString()) // it says 67 but the data from the url is 63 array of json object
Log.d("test:", news!![0].toString()) // com.example.mockie.tigaer.api.ProductTypeResponse#f17fd5e
}
}
RestApi.kt
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
class RestAPI() {
private val tigaerApi: TigaerApi
init {
val retrofit = Retrofit.Builder()
.baseUrl("http://app.tigaer.id/laravel/")
.addConverterFactory(MoshiConverterFactory.create())
.build()
tigaerApi = retrofit.create(TigaerApi::class.java)
}
fun getNews(): Call<List<ProductTypeResponse>> {
return tigaerApi.getTop()
}
}
ApiModel.kt
package com.example.mockie.tigaer.api
class ProductTypeResponse(val data: TypeDataResponse)
class TypeDataResponse(
val children: List<ProductTypeChildrenResponse>
)
class ProductTypeChildrenResponse(val data: ProductTypeDataResponse)
class ProductTypeDataResponse(
val productType: String,
val readable: String
)
TigaerApi.kt
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query
interface TigaerApi {
#GET("api/type")
fun getTop(): Call<List<ProductTypeResponse>>
}
return Json: https://jsoneditoronline.org/?id=ce90c41b859218e746e41d64eddb4c30
so my questions are :
is there any function to debug object/array as detail as in laravel ?
how to populate my json return data into spinner?

Here is code for same, I modified and integrate in your code only:
"MainActivity.kt" class:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var spinner: Spinner = findViewById(R.id.spinner)
val task = object : AsyncTask<Void, Void, Response<List<ProductTypeDataResponse>>>() {
override fun doInBackground(vararg params: Void): Response<List<ProductTypeDataResponse>> {
val typeAPI = RestAPI()
val callResponse = typeAPI.getNews()
val response = callResponse.execute()
return response
}
override fun onPostExecute(response: Response<List<ProductTypeDataResponse>>) {
if (response.isSuccessful) {
val news: List<ProductTypeDataResponse>? = response.body()
var adapter: SpinnerAdapter = SpinnerAdapter(this#MainActivity, news!!);
spinner.adapter=adapter
}
}
}.execute()
}
}
Now Layout "activity_main":
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.ankitpatidar.checkkotlin.MainActivity">
<Spinner
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/spinner"></Spinner>
</LinearLayout>
Now Spinner Adapter as "SpinnerAdapter":
class SpinnerAdapter internal constructor(internal var context: Context, internal var list: List<ProductTypeDataResponse>) : BaseAdapter() {
override fun getCount(): Int {
return list.size
}
override fun getItem(i: Int): Any? {
return null
}
override fun getItemId(i: Int): Long {
return 0
}
override fun getView(i: Int, view: View?, viewGroup: ViewGroup): View {
var view = view
if (view == null) {
val inflater = LayoutInflater.from(context)
view = inflater.inflate(R.layout.item, viewGroup, false)
}
val textView = view!!.findViewById<TextView>(R.id.textView)
textView.text = list[i].productType + " " + list[i].readable
return textView
}
}
Spinner item layout as "item":
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/textView"/>
</LinearLayout>
Now some changes in your existing files:
"ApiModel.kt":
class TypeDataResponse(
val children: List<ProductTypeChildrenResponse>
)
class ProductTypeChildrenResponse(val data: ProductTypeDataResponse)
class ProductTypeDataResponse(
val productType: String,
val readable: String
)
"RestAPI.kt"
class RestAPI() {
private val tigaerApi: TigaerApi
init {
val retrofit = Retrofit.Builder()
.baseUrl("http://app.tigaer.id/laravel/")
.addConverterFactory(MoshiConverterFactory.create())
.build()
tigaerApi = retrofit.create(TigaerApi::class.java)
}
fun getNews(): Call<List<ProductTypeDataResponse>> {
return tigaerApi.getTop()
}
}
Hence it will work for you.

is there any function to debug object/array as detail as in laravel ?
Go through this to run your app in debug mode. https://developer.android.com/studio/debug/index.html
You can always use breakpoints to evaluate expressions while the app is running. Instead of logging put a breakpoint at this line.
val news = response.body()
so when you'll receive a response from server, app will stop at this point and you can check what you are getting in response in detail.
how to populate my json return data into spinner?
If you are getting response from server in Json format as shown in provided link, you'll have to parse the data into a list of objects(POJO).
And then you have to forward this data(maybe you'll have to iterate over list to get the required data because you have two fields in each object) into an adapter and set that adapter to your spinner. It is explained very clearly at following link.
https://developer.android.com/guide/topics/ui/controls/spinner.html

I got an idea in this post, maybe my logic can help anyone in here
this is my JSON
{
"success": 1,
"dataset": [
{
"id": "3",
"nama": "Rush"
},
{
"id": "5",
"nama": "Avanza"
},
{
"id": "6",
"nama": "Innova"
},
{
"id": "14",
"nama": "Sienta"
},
{
"id": "15",
"nama": "Alphard"
},
{
"id": "16",
"nama": "Calya"
}
],
"sql_duration": 0.0013179779052734375,
"auth_duration": 1.9073486328125e-6,
"req_duration": 0.004480123519897461,
"debug_duration": []
}
this is my API Service
ApiMain.kt
class ApiMain : Application(){
private var BASE_URL = "your url in here"
private val client = OkHttpClient().newBuilder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
})
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
val services: ApiServices = retrofit.create(ApiServices::class.java)
}
and this is my API service
ApiServices.kt
interface ApiServices {
#GET("yourlink")
fun MerkKendaraan():Call<MerkKendaraan>
}
and this is my model
kendaraan.kt
data class MerkKendaraan(
#SerializedName("success")
#Expose
var success: Int? = null,
#SerializedName("dataset")
#Expose
var dataset: List<MerkMobil>? = null,
#SerializedName("sql_duration")
#Expose
var sql_duration: String? = null,
#SerializedName("auth_duration")
#Expose
var auth_duration: String? = null,
#SerializedName("req_duration")
#Expose
var req_duration: String? = null
)
data class MerkMobil(
#SerializedName("id")
#Expose
var id: String? = null,
#SerializedName("nama")
#Expose
var nama: String? = null
)
and this is my main activity
AddKendaraan
class AddKendaraan : AppCompatActivity() {
private var merk : ArrayList<MerkMobil> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_kendaraan)
configspinnermobil()
}
private fun configspinnermobil() {
val spinner: Spinner = findViewById(R.id.spinnerMerk)
ApiMain().services.MerkKendaraan().enqueue(object :
Callback<MerkKendaraan> {
override fun onResponse(call: Call<MerkKendaraan>, response: Response<MerkKendaraan>) {
//Tulis code jika response sukses
Log.d("data api", "data masuk")
if(response.code() == 200){
merk = response.body()?.dataset as ArrayList<MerkMobil>
var data : MutableList<String> = ArrayList()
merk.forEach {
data.add(0,it.nama.toString())
}
spinner.adapter = ArrayAdapter<String>(this#AddKendaraan, R.layout.support_simple_spinner_dropdown_item, data)
}else{
}
}
override fun onFailure(call: Call<MerkKendaraan>, t: Throwable){
//Tulis code jika response fail
Toast.makeText(applicationContext, t.message, Toast.LENGTH_LONG).show()
Log.d("data api", "data tidak masuk")
}
})
}

Related

Handle dynamic response sometimes object / array on same key on android kotlin

I have a response from this api, and there is different response on
...
"value": [
{
"#unit": "C",
"#text": "28"
}
]
sometimes
"value":
{
"#unit": "C",
"#text": "28"
}
I have try create this json adapter & model class from the answer
object WeatherResponse {
open class CuacaResponse{
#SerializedName("Success")
val success : Boolean = false
val row : RowBean? = null
}
data class RowBean(
val data : DataBean? = null
)
data class DataBean (
val forecast : ForecastBean? = null
)
data class ForecastBean(
val area : List<Area>? = null
)
data class Area(
#SerializedName("#id")
val id :String?="",
#SerializedName("#description")
val nama :String?="",
val parameter : List<DataMain>?=null
)
data class DataMain(
#SerializedName("#description")
val namaData :String?="",
#SerializedName("#id")
val id :String?="",
#SerializedName("timerange")
val timeRange : List<TimeRangeItem>
)
data class TimeRangeItem(
// sample data : 202107241800 => 2021-07-24-18:00
#SerializedName("#datetime")
val datetime : String,
#JsonAdapter(ValueClassTypeAdapter::class)
val value : ArrayList<ValueData>? = null,
)
data class ValueData(
#SerializedName("#unit")
val unit :String?="",
#SerializedName("#text")
val value :String?="",
)
class ValueClassTypeAdapter :
JsonDeserializer<ArrayList<ValueData?>?> {
override fun deserialize(
json: JsonElement,
typeOfT: Type?,
ctx: JsonDeserializationContext
): ArrayList<ValueData?> {
return getJSONArray(json, ValueData::class.java, ctx)
}
private fun <T> getJSONArray(json: JsonElement, type: Type, ctx:
JsonDeserializationContext): ArrayList<T> {
val list = ArrayList<T>()
if (json.isJsonArray) {
for (e in json.asJsonArray) {
list.add(ctx.deserialize<Any>(e, type) as T)
}
} else if (json.isJsonObject) {
list.add(ctx.deserialize<Any>(json, type) as T)
} else {
throw RuntimeException("Unexpected JSON type: " + json.javaClass)
}
return list
}
}
}
my retrofit service :
interface WeatherService {
#GET("/api/cuaca/DigitalForecast-{province}.xml?format=json")
suspend fun getWeather(
#Path("province") provinceName: String? = "JawaTengah"
) : WeatherResponse.CuacaResponse?
companion object {
private const val URL = "https://cuaca.umkt.ac.id"
fun client(context: Context): WeatherService {
val httpClient = OkHttpClient.Builder()
httpClient.apply {
addNetworkInterceptor(
ChuckerInterceptor(
context = context,
alwaysReadResponseBody = true
)
)
addInterceptor { chain ->
val req = chain.request()
.newBuilder()
.build()
return#addInterceptor chain.proceed(req)
}
cache(null)
}
val gsonConverterFactory = GsonConverterFactory.create()
return Retrofit.Builder()
.baseUrl(URL)
.client(httpClient.build())
.addConverterFactory(gsonConverterFactory)
.build()
.create(WeatherService::class.java)
}
}
}
But the result that i got, from log request :
...
{
"#datetime": "202108070000",
"value": {
"size": 4
}
},
{
"#datetime": "202108070600",
"value": {
"size": 4
}
},
{
"#datetime": "202108071200",
"value": {
"size": 4
}
}
...
the value return size that IDK from where, it should return array of unit & text from the api.
Please anyone help me from this stuck, thanks in advance!
Finaly, i have solved this by change JsonDeserializer to TypeAdapterFactory as mentioned on this answer

Unable to invoke no-args constructor for retrofit2.Call MVVM Coroutines Retrofit

I want to use coroutines in my project only when I use coroutines I get the error :Unable to invoke no-args constructor. I don't know why it's given this error. I am also new to coroutines.
here is my apiclient class:
class ApiClient {
val retro = Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
Here is my endpoint class:
#GET("v2/venues/search")
suspend fun get(
#Query("near") city: String,
#Query("limit") limit: String = Constants.limit,
#Query("radius") radius: String = Constants.radius,
#Query("client_id") id: String = Constants.clientId,
#Query("client_secret") secret: String = Constants.clientSecret,
#Query("v") date: String
): Call<VenuesMainResponse>
my Repository class:
class VenuesRepository() {
private val _data: MutableLiveData<VenuesMainResponse?> = MutableLiveData(null)
val data: LiveData<VenuesMainResponse?> get() = _data
suspend fun fetch(city: String, date: String) {
val retrofit = ApiClient()
val api = retrofit.retro.create(VenuesEndpoint::class.java)
api.get(
city = city,
date = date
).enqueue(object : Callback<VenuesMainResponse>{
override fun onResponse(call: Call<VenuesMainResponse>, response: Response<VenuesMainResponse>) {
val res = response.body()
if (response.code() == 200 && res != null) {
_data.value = res
} else {
_data.value = null
}
}
override fun onFailure(call: Call<VenuesMainResponse>, t: Throwable) {
_data.value = null
}
})
}
}
my ViewModel class:
class VenueViewModel( ) : ViewModel() {
private val repository = VenuesRepository()
fun getData(city: String, date: String): LiveData<VenuesMainResponse?> {
viewModelScope.launch {
try {
repository.fetch(city, date)
} catch (e: Exception) {
Log.d("Hallo", "Exception: " + e.message)
}
}
return repository.data
}
}
part of activity class:
class MainActivity : AppCompatActivity(){
private lateinit var venuesViewModel: VenueViewModel
private lateinit var adapter: HomeAdapter
private var searchData: List<Venue>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val editText = findViewById<EditText>(R.id.main_search)
venuesViewModel = ViewModelProvider(this)[VenueViewModel::class.java]
venuesViewModel.getData(
city = "",
date = ""
).observe(this, Observer {
it?.let { res ->
initAdapter()
rv_home.visibility = View.VISIBLE
adapter.setData(it.response.venues)
searchData = it.response.venues
println(it.response.venues)
}
})
this is my VenuesMainResponse data class
data class VenuesMainResponse(
val response: VenuesResponse
)
I think the no-args constructor warning should be related to your VenuesMainResponse, is it a data class? You should add the code for it as well and the complete Log details
Also, with Coroutines you should the change return value of the get() from Call<VenuesMainResponse> to VenuesMainResponse. You can then use a try-catch block to get the value instead of using enqueue on the Call.
Check this answer for knowing about it and feel free to ask if this doesn't solve the issue yet :)
UPDATE
Ok so I just noticed that it seems that you are trying to use the foursquare API. I recently helped out someone on StackOverFlow with the foursquare API so I kinda recognize those Query parameters and the Venue response in the code you provided above.
I guided the person on how to fetch the Venues from the Response using the MVVM architecture as well. You can find the complete code for getting the response after the UPDATE block in the answer here.
This answer by me has code with detailed explanation for ViewModel, Repository, MainActivity, and all the Model classes that you will need for fetching Venues from the foursquare API.
Let me know if you are unable to understand it, I'll help you out! :)
RE: UPDATE
So here is the change that will allow you to use this code with Coroutines as well.
Repository.kt
class Repository {
private val _data: MutableLiveData<mainResponse?> = MutableLiveData(null)
val data: LiveData<mainResponse?> get() = _data
suspend fun fetch(longlat: String, date: String) {
val retrofit = Retro()
val api = retrofit.retro.create(api::class.java)
try {
val response = api.get(
longLat = longlat,
date = date
)
_data.value = response
} catch (e: Exception) {
_data.value = null
}
}
}
ViewModel.kt
class ViewModel : ViewModel() {
private val repository = Repository()
val data: LiveData<mainResponse?> = repository.data
fun getData(longLat: String, date: String) {
viewModelScope.launch {
repository.fetch(longLat, date)
}
}
}
api.kt
interface api {
#GET("v2/venues/search")
suspend fun get(
#Query("ll") longLat: String,
#Query("client_id") id: String = Const.clientId,
#Query("client_secret") secret: String = Const.clientSecret,
#Query("v") date: String
): mainResponse
}
MainActivity.kt
private val viewModel by viewModels<ViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.getData(
longLat = "40.7,-74",
date = "20210718" // date format is: YYYYMMDD
)
viewModel.data
.observe(this, Observer {
it?.let { res ->
res.response.venues.forEach { venue ->
val name = venue.name
Log.d("name ",name)
}
}
})
}
}

Android Kotlin Retrofit MVVM return Null

I'm currently making a sample project about diagrams. I'm starting to use MVVM architecture recently, and I got stuck when the response is null. I also checked the Mutable Live Data to make sure that it is calling the API. Here's some of my code and the error-tag:
Model.kt
data class Model(
#SerializedName("FID") val FID: Int,
#SerializedName("region") val region: String,
#SerializedName("positive") val positive: Float
) {
}
ModelWrap.kt
data class ModelWrap(#SerializedName("samplesAPI") val attributes: Model){
}
ApiClient.kt
object ApiClient {
var retrofitService: ApiInterface? = null
const val BASE_URL = "https://sampleapi.../"
fun getApiSample() : ApiInterface {
if (retrofitService == null){
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofitService = retrofit.create(ApiInterface::class.java)
}
return retrofitService!!
}
}
ApiInterface.kt
interface ApiInterface {
#GET("samples")
fun getSampleData(): Call<List<ModelWrap>>
}
MainViewModel.kt
class MainViewModelconstructor(private val repository: ModelRepository) : ViewModel(){
val sampleList= MutableLiveData<List<ModelWrap>>()
val errorMessage = MutableLiveData<String>()
fun getSampleData(pieChart: PieChart){
val response = repository.getSampleData()
response.enqueue(object : Callback<List<ModelWrap>> {
override fun onResponse(
call: Call<List<ModelWrap>>,
response: Response<List<ModelWrap>>
) {
sampleList.postValue(response.body())
}
override fun onFailure(call: Call<List<ModelWrap>>, t: Throwable) {
errorMessage.postValue(t.message)
}
})
}
}
MainViewModelFactory.kt
class MainViewModelFactoryconstructor(private val repository: MainRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(MainViewModel::class.java)){
MainViewModel(this.repository) as T
} else {
throw IllegalArgumentException("Sample ViewModel Not Found")
}
}
}
MainRepository.kt
class MainRepository constructor(private val retrofitService: ApiInterface){
fun getSampleData() = retrofitService.getSampleData()
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var pieChart: PieChart
lateinit var sampleViewModel: MainViewModel
private val sampleService = ApiClient.getApiSample()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pieChart = findViewById(R.id.PieChart)
sampleViewModel= ViewModelProvider(this, MainViewModelFactory(MainRepository(sampleService))).get(MainViewModel::class.java)
getPieChart(pieChart)
}
private fun getPieChart(pieCharts: PieChart) {
mainViewModel.mainList.observe(this, Observer {
Log.d("TAG sample" , "onCreate PieChart: $it")
Log.d("Tag Samples Response" , response.body().toString())
if (it != null) {
val sampleEntries: List<PieEntry> = ArrayList()
for ((attributes) in it!!) {
sampleEntries.toMutableList()
.add(PieEntry(attributes.positive, attributes.region))
//........................................................................
val description = Description()
description.text = "Samples Data"
pieChart.description = description
pieChart.invalidate()
}
}
})
mainViewModel.errorMessage.observe(this, Observer { })
mainViewModel.getSampleData(pieCharts)
}
}
and Lastly, here's some or log message:
V/InputMethodManager: Starting input: tba=android.view.inputmethod.EditorInfo#8b795c0 nm : com.example.diargram ic=null
D/Tag Sample Response: null
D/TAG Sample: onCreate PieChart: null
E/libc: Access denied finding property "ro.serialno"
V/StudioTransport: Agent command stream started.
V/StudioTransport: Transport agent connected to daemon.
I would appreciate it if someone can help me :D, Thank you
Finally, I found a solution for my problem:
I type the wrong endpoint inside the interface class and it should be like this:
interface ApiInterface {
#GET("sample")
fun getSampleData(): Call<List> }
When it comes to assigning the livedata to the view, based on my JSON I should call ArrayList instead of List
List item
Before :
val sampleEntries: List = ArrayList()
After :
val sampleEntries: ArrayList<PieEntry> = ArrayList()

Getting null value in response

I am trying to get the response from https://www.reddit.com/r/popular/.rss and map to Kotlin POJO class in Android. But when I am logging that category's label value, getting null. For the title I am getting response value as popular links.
Here is entity class FeedX:-
#Root(name = "feed", strict = false)
class FeedX {
#set: Element(name = "category")
#get: Element(name = "category")
var category: Category? = null
val entry: List<Entry>? = null
val id: String? = null
val link: List<LinkX>? = null
#set: Element(name = "title")
#get: Element(name = "title")
var title: String? = null
val updated: String? = null
}
Category class:-
#Root(name = "category", strict = false)
class Category {
#set: Element(required = false, name = "_label")
#get: Element(required = false, name = "_label")
var _label: String? = null
val _term: String? = null
}
Here is Api Interface:-
interface FeedApi {
#GET("{type}/.rss")
fun getPopularFeeds(
#Path("type") type: String?
): Call<FeedX>?
}
Here is MainActivity:-
class MainActivity : AppCompatActivity() {
private val BASE_URL = "https://www.reddit.com/r/"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(SimpleXmlConverterFactory.create())
.build()
val service = retrofit.create(FeedApi::class.java)
service.getPopularFeeds("popular")?.enqueue(object : Callback<FeedX> {
override fun onFailure(call: Call<FeedX>, t: Throwable) {
Log.d("Response Failed", "${t.localizedMessage}")
}
override fun onResponse(call: Call<FeedX>, response: Response<FeedX>) {
if (response.isSuccessful) {
Log.d("Response Success", "${response.body()!!.title}") // for this I am getting value
Log.d("Response Success", "${response.body()!!.category?._label}") // always getting null value
} else {
Log.d("Response Failed jg", "${response.errorBody()}")
}
}
})
}
}
This is because title contains a value where category tag doesn't. See the difference below.
<title>popular links</title>
<category term="AskReddit" label="r/AskReddit"/>
As you can see category tag is self closing.

Unable to parse JSON using Retrofit in Android

I am successfully able to hit the API and get the json result. I can see the success result in the logs by printing Retrofit response body. and also using Stetho as the network interceptor.
However, I am not able to understand why is the api response still "null" in the onResponse() method in the repository. I believe, I am not passing the correct model maybe for the JSON to be parsed properly ? Can anybody help me to find out what's the issue here?
Following is the json:
{
"photos": {
"page": 1,
"pages": 2864,
"perpage": 100,
"total": "286373",
"photo": [
{
"id": "49570734898",
"owner": "165034061#N07",
"secret": "f3cb2c2590",
"server": "65535",
"farm": 66,
"title": "Hello",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
}
],
"photo": [
{
"id": "12344",
"owner": "23444#N07",
"secret": "f233edd",
"server": "65535",
"farm": 66,
"title": "Hey",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
}
]
},
"stat": "ok"
}
My Pojo Class :
data class Photos(
#SerializedName("page")
val page: Int,
#SerializedName("pages")
val pages: Int,
#SerializedName("perpage")
val perpage: Int,
#SerializedName("photo")
val photos: List<Photo>,
#SerializedName("total")
val total: String
)
data class Photo(
#SerializedName("farm")
val farm: Int,
#SerializedName("id")
val id: String,
#SerializedName("isfamily")
val isFamily: Int,
#SerializedName("isfriend")
val isFriend: Int,
#SerializedName("ispublic")
val isPublic: Int,
#SerializedName("owner")
val owner: String,
#SerializedName("secret")
val secret: String,
#SerializedName("server")
val server: String,
#SerializedName("title")
val title: String
)
RetrofitClient:
object ApiClient {
private val API_BASE_URL = "https://api.flickr.com/"
private var servicesApiInterface: ServicesApiInterface? = null
fun build(): ServicesApiInterface? {
val builder: Retrofit.Builder = Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
val httpClient: OkHttpClient.Builder = OkHttpClient.Builder()
httpClient.addInterceptor(interceptor()).addNetworkInterceptor(StethoInterceptor())
val retrofit: Retrofit = builder
.client(httpClient.build()).build()
servicesApiInterface = retrofit.create(
ServicesApiInterface::class.java
)
return servicesApiInterface as ServicesApiInterface
}
private fun interceptor(): HttpLoggingInterceptor {
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return httpLoggingInterceptor
}
interface ServicesApiInterface {
#GET("/services/rest/?method=flickr.photos.search")
fun getImageResults(
#Query("api_key") apiKey: String,
#Query("text") text: String,
#Query("format") format: String,
#Query("nojsoncallback") noJsonCallback: Boolean
): Call<PhotoResponse>
}
}
OperationCallback:
interface OperationCallback<T> {
fun onSuccess(data:List<T>?)
fun onError(error:String?)
}
PhotoDataSource:
interface PhotoDataSource {
fun retrievePhotos(callback: OperationCallback<Photo>, searchText: String)
fun cancel()
}
PhotoRepository:
class PhotoRepository : PhotoDataSource {
private var call: Call<PhotoResponse>? = null
private val API_KEY = "eff9XXXXXXXXXXXXX"
val FORMAT = "json"
companion object {
val TAG = PhotoRepository::class.java.simpleName
}
override fun retrievePhotos(callback: OperationCallback<Photo>, searchText: String) {
call = ApiClient.build()
?.getImageResults(
apiKey = API_KEY,
text = searchText,
format = FORMAT,
noJsonCallback = true
)
call?.enqueue(object : Callback<PhotoResponse> {
override fun onFailure(call: Call<PhotoResponse>, t: Throwable) {
callback.onError(t.message)
}
override fun onResponse(
call: Call<PhotoResponse>,
response: Response<PhotoResponse>
) {
response?.body()?.let {
Log.d(TAG, "got api response total pics are :${it.data?.size}")
if (response.isSuccessful && (it.isSuccess())) {
callback.onSuccess(it.data)
} else {
callback.onError(it.msg)
}
}
}
})
}
override fun cancel() {
call?.let {
it.cancel()
}
}
}
PhotoResponse:
data class PhotoResponse(val status: Int?, val msg: String?, val data: List<Photo>?) {
fun isSuccess(): Boolean = (status == 200)
}
Try to change your PhotoResponse to match with your json response.
data class PhotoResponse(
#SerializedName("stat")
val status: String?,
#SerializedName("photos")
val photos: Photos?
) {
fun isSuccess(): Boolean = status.equals("ok", true)
}
And then inside onResponse, You can get List<Photo> like below:
override fun onResponse(
call: Call<PhotoResponse>,
response: Response<PhotoResponse>
) {
response?.body()?.let {
//This should be your list of photos
it.photos.photos
}
}
The issue is with your data class. You need one extra data class here.
So if you look at your JSON response closely, then you will understand whats going wrong.
Your photos data class should not be the first class. Instead it should be inside one more class lets say PhotoApiResponse.
Your first class will contain both photos and stat.
And then rest can be the same.

Categories

Resources