Can you, please, help me understand how to parse data from nested json file using GSON. I have looked at other similar question posted, but don't see where have i made a mistake. I'm trying to get id, user, technician and an account from my json file.
My JSON file look like this:
{
"operation": {
"result": {
"message": "successful",
"status": "done"
},
"details": [
{
"id": "106818",
"user": "Leona",
"technician": "45441",
"account": "Inc",
"status": "Open"
}
]
}
}
Code:
val url = "https://myURL"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
println("Failed - onFailure")
}
override fun onResponse(call: Call, response: Response) {
val body = response?.body()?.string()
println(body)
val gson = GsonBuilder().create()
gson.fromJson(body, HomeFeed::class.java)
}
})
}
}
class HomeFeed(val details: List<Details>)
class Details(val id: String, val user:String, val technician:String, val account:String)
Error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.kotlinjsontube, PID: 10905
java.lang.NullPointerException: Attempt to invoke interface method 'int java.util.Collection.size()' on a null object reference
at com.example.kotlinjsontube.MainAdapter.getItemCount(MainAdapter.kt:13)
Thank you.
Your response modeled into a POJO class:
Model.kt
data class Model(
val operation: Operation,
val details: List<Detail>
)
data class Operation(
val result: Result
)
data class Result(
val message: String,
val status: String
)
data class Detail(
val id: String,
val user: String,
val technician: String,
val account: String,
val status: String
)
Class where you make the request:
val url = "https://myURL"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
println("Failed - onFailure")
}
override fun onResponse(call: Call, response: Response) {
val body = response?.body()?.string()
println(body)
val gson = GsonBuilder().create()
gson.fromJson(body, Model::class.java)
}
})
Related
i am new to kotlin and i am in learning phase. I have followed many links but didn't able to understand completely.
I want Json response to show in my textview.
Problem: 1
I have tried this code but was unable to get data, but i want to get the items inside data object. Quote and author are coming null.
{
"status": 200,
"message": "Success",
"data": {
"Quote": "The pain you feel today will be the strength you feel tomorrow.",
"Author": ""
},
"time": "0.14 s"
}
Problem: 2
I dont know how to parse this response in textview
object ServiceBuilder {
private val client = OkHttpClient.Builder().build()
private val retrofit = Retrofit.Builder()
.baseUrl("https://url.com.pk/") // change this IP for testing by your actual machine IP
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
fun<T> buildService(service: Class<T>): T{
return retrofit.create(service)
}}
RestApi
interface RestApi{
#Headers("Content-Type: application/json")
#POST("api/getquotes")
abstract fun addUser(#Body userData: UserInfo): Call<UserInfo>}
RestAPiService
class RestApiService
{
fun addUser(userData: UserInfo, onResult: (UserInfo?) -> Unit)
{
val retrofit = ServiceBuilder.buildService(RestApi::class.java)
retrofit.addUser(userData).enqueue(
object : Callback<UserInfo>
{
override fun onFailure(call: Call<UserInfo>, t: Throwable)
{
onResult(null)
}
override fun onResponse( call: Call<UserInfo>, response: Response<UserInfo>)
{
val addedUser = response.body()
Log.d("responsee",""+addedUser)
onResult(addedUser)
}
}
)
}
}
UserInfo
data class UserInfo (
#SerializedName("Quote")
val quote : String,
#SerializedName("Author")
val author : String
)
MainActivity
fun getQuotes() {
val apiService = RestApiService()
val userInfo = UserInfo("","")
apiService.addUser(userInfo) {
Log.d("Error registering user","errter")
/*if ( != null)
{
// it = newly added user parsed as response
// it?.id = newly added user ID
} else {
Log.d("Error registering user","errter")
}*/
}
}
Any help would be appreciated :)
Status, message and data are all part of the response so you need to take care of that. For example this
data class AddUserResponse(
val `data`: UserInfo, //like you defined it
val message: String,
val status: Int,
val time: String
)
This means parameter and response are different so the RestApi needs to be changed to this
abstract fun addUser(#Body userData: UserInfo): Call<AddUserResponse>}
This in turn also change the types in the service like
class RestApiService
{
fun addUser(userData: UserInfo, onResult: (UserInfo?) -> Unit)
{
val retrofit = ServiceBuilder.buildService(RestApi::class.java)
retrofit.addUser(userData).enqueue(
object : Callback<AddUserResponse>
{
override fun onFailure(call: Call<AddUserResponse>, t: Throwable)
{
onResult(null)
}
override fun onResponse( call: Call<AddUserResponse>, response: Response<AddUserResponse>)
{
val addedUser = response.body()
Log.d("responsee",""+addedUser)
onResult(addedUser.data)
}
}
)
}
}
now in getQuotes you will have that it is a UserInfo object
apiService.addUser(userInfo) {
val returnedUserInfo = it
}
just follow my steps :
File->settings->Plugins
search for JSON To Kotlin class and install it
again click on File->New->Kotlin Data class from JSON
paste your json code here and click on generate. It will generate POJO classes and you will good to go.
The first thing I noticed, is that the data in your json is:
"Quote": "The pain you feel today will be the strength you feel tomorrow.",
"Author": ""
While your UserInfo defined #SerializedName("message") for Quote.
I want to show response POST from retrofit. I don't really know how because my response are array but with POST.
this is how my response looks like
{
"message":[
"00",
"Get Success"
],
"result":{
"listProgram":[
{
"banner":"",
"area":"",
"domainlembaga":"",
"domainprogram":"",
"donate":0
{
.
.
.
]
service
interface Service {
#POST("program/list")
fun getProgram(#Body body: Pair<String, Int>, pair: Pair<String, Int>): Call<BaseBersedekahResponse<ListProgramBersedekahResponse>>
data
data class BaseBersedekahResponse<T>(
#SerializedName("message") val message: String?,
#SerializedName("result") val result: T?
)
data class ListProgramBersedekahResponse(
#SerializedName("listProgram") val listProgram: List<ProgramBersedekahResponse>?
)
data class ProgramBersedekahResponse(
#SerializedName("banner") val banner: String?,
#SerializedName("domainlembaga") val domainlembaga: String?,
#SerializedName("domainprogram") val domainprogram: String?,
#SerializedName("donate") val donatur: Int?
)
activity
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(APIUrl.BASEURL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService: Service = retrofit.create(Service::class.java)
val call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>> = apiService.getProgram(
"limit" to 1,
"group" to 4)
call.enqueue(object : Callback<BaseBersedekahResponse<ListProgramBersedekahResponse>> {
override fun onResponse(call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>, response: Response<BaseBersedekahResponse<ListProgramBersedekahResponse>>) {
val getProgram: List<ProgramBersedekahResponse?>? = response.body().result.listProgram
Toast.makeText(applicationContext, "success", Toast.LENGTH_SHORT).show()
}
override fun onFailure(call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>>, t: Throwable) {
Toast.makeText(applicationContext, "Failure", Toast.LENGTH_SHORT).show()
}
})
I actually planning it to show in RecyclerView, but I still don't know how to show it the response from POST. please help.
Your signatures should match each other. You should make your signatures same as the signature in your interface.
For your request body:
data class BersedekahRequest(
#SerializedName("limint") val limint: Int,
#SerializedName("group") val group: Int
)
then your interface:
#POST("program/list")
fun getProgram(#Body body: BersedekahRequest): Call<BaseBersedekahResponse<ListProgramBersedekahResponse>>
your rest service call:
val requestBody = BersedekahRequest(10,20)
val call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>> = apiService.getProgram(requestBody)
call.enqueue(object : Callback<BaseBersedekahResponse<ListProgramBersedekahResponse>> {
override fun onResponse(call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>, response: Response<BaseBersedekahResponse<ListProgramBersedekahResponse>>) {
val GetApps2: List<ProgramBersedekahResponse?>? = response.body().result.listProgram
Toast.makeText(applicationContext, "success", Toast.LENGTH_SHORT).show()
}
override fun onFailure(call: Call<ListProgramBersedekahResponse>, t: Throwable) {
Toast.makeText(applicationContext, "Failure", Toast.LENGTH_SHORT).show()
}
})
And I think one more problem you have.
fun getProgram(#Body body: ProgramBersedekahResponse?) this #Body is the body you will send via POST request, not your response body. If you do not send anything inside post body, make it empty, or give the appropriate request body since it seems like it is your response object right now.
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.
How can I solve this problem?
class InformationActivity : AppCompatActivity() {
private val _tag = SplashActivity::class.java.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_information)
val uniqueId = SharedPreference.getidInfo(this)
val token = SharedPreference.getUserInfo(this)
Client.retrofitService.profile(uniqueId, token)
.enqueue(object : Callback<LoginResponse> {
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
}
override fun onResponse(
call: Call<LoginResponse>,
response: Response<LoginResponse>
) {
if (response?.isSuccessful == false) { val er = Gson().fromJson(response.errorBody()?.charStream(), ErrorResponse::class.java)
Log.d(_tag, "${er.code}:${er.message}")
if (er.code == 60203) {
Toast.makeText(this#InformationActivity, "", Toast.LENGTH_SHORT).show()
}
} else if (response?.isSuccessful == true) {
Glide.with(applicationContext).asBitmap().load("https://s3.amazonaws.com/appsdeveloperblog/micky.gif").into(imageView)
Toast.makeText(this#InformationActivity, "", Toast.LENGTH_LONG).show()
val file=File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),"micky.gif")
var fileName="micky.gif"
val token = SharedPreference.getUserInfo(applicationContext)
val uniqueId= SharedPreference.getidInfo(applicationContext)
var requestBody: RequestBody = RequestBody.create(MediaType.parse("image/*"), file)
val body: MultipartBody.Part = MultipartBody.Part.createFormData("profile",fileName,requestBody)
if (uniqueId != null) {
Client.retrofitService.updateProfile(token,uniqueId,body)
.enqueue(object : Callback<List<LoginResponse>> {
override fun onFailure(
call: Call<List<LoginResponse>>,
t: Throwable) { Log.d("", t.message) }
override fun onResponse(
call: Call<List<LoginResponse>>,
response: Response<List<LoginResponse>>) { if (response?.isSuccessful)
{ Toast.makeText(this#InformationActivity, "File Uploaded Successfully...", Toast.LENGTH_LONG).show()
Log.d("", "" + response?.body().toString())
} else {
Toast.makeText(this#InformationActivity, "Some error occurred...", Toast.LENGTH_LONG).show()
}
} }) }
}
}
}) }
}
interface API {
#Headers("Content-Type: application/json", "Authorization:token:String")
#Multipart
#PUT("/user/profile/{userId}")
fun updateProfile(#Header("Authorization") token: String?, #Path("userId") userID: String, #Part file: MultipartBody.Part): Call<List<Loginresponse>>
}
object Client {
var retrofitService: API
init {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val logger: OkHttpClient = OkHttpClient.Builder().addInterceptor(interceptor).readTimeout(20, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).build()
val retrofit = Retrofit.Builder()
.baseUrl("myurl")
.addConverterFactory(GsonConverterFactory.create())
.client(logger)
.build()
retrofitService = retrofit.create(API::class.java)
}
}
#SerializedName("uniqueId")
val user:String?=null
#SerializedName("nickname")
val nickname: String?=null
#SerializedName("birth")
val birth: String?=null
#SerializedName("profileImage")
val profileImage: String?=null
#SerializedName("profileThumbnail")
val profileThumbnails: String?=null
#SerializedName("gender")
val gender: Int?=null
#SerializedName("token")
val token: String? = null
}
Your json return as JSON object. But you are trying to convert into Json array
Call<List<Loginresponse>> - you try to convert result as JSON Array (list)
Solution
Get the raw json result & convert the pojo using http://www.jsonschema2pojo.org/ and try again
You are trying to store json object in list that's why you are getting error .
check your JSON response start with { curly bracket it means it is object not an array . array start with [ square bracket .
#PUT("/user/profile/{userId}")
fun updateProfile(#Header("Authorization") token: String?, #Path("userId") userID: String, #Part file: MultipartBody.Part): Call<List<Loginresponse>>
replace Call<List<Loginresponse>> with Call<Loginresponse> all over where you using updateProfile method
I have parse the data from this link
https://api.androidhive.info/contacts/
But I am getting error as
E/onĀ FailureĀ :: retrofit errorjava.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
Below is the code which I done.
class RetrofitService {
val liveUserResponse:MutableLiveData<List<ContactBase>> = MutableLiveData()
companion object Factory {
var gson = GsonBuilder().setLenient().create()
fun create(): ApiInterface {
Log.e("retrofit","create")
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("https://api.androidhive.info/")
.build()
return retrofit.create(ApiInterface::class.java)
}
}
fun loadContactsData(): MutableLiveData<List<ContactBase>>? {
Log.e("loadAndroidData","yes")
val retrofitCall = create().getContacts()
retrofitCall.enqueue(object : Callback<List<ContactBase>> {
override fun onFailure(call: Call<List<ContactBase>>, t: Throwable?) {
Log.e("on Failure :", "retrofit error"+t)
Log.e("on Failure :", "retrofit error"+call)
}
override fun onResponse(call: Call<List<ContactBase>>, response: retrofit2.Response<List<ContactBase>>) {
val list = response.body()
for (i in list.orEmpty()){
Log.e("on response 1:", ""+i)
}
liveUserResponse.value = list
Log.e("hasActiveObservers 1", liveUserResponse.hasActiveObservers().toString()+" check")
Log.e("on response 2 :", liveUserResponse.toString()+" check")
}
})
return liveUserResponse
}
}
But it's always going to Failure state.
data class ContactBase (val contacts : List<Contacts>)
data class Contacts (
val id : String,
val name : String,
val email : String,
val address : String,
val gender : String,
val phone : Phone
)
data class Phone (
val mobile : String,
val home : String,
val office : String
)
interface ApiInterface{
#GET("contacts/")
fun getContacts(): Call<List<ContactBase>>
}
class AndroidViewModel:ViewModel(){
private val retrofitService = RetrofitService()
fun getContactsData(): MutableLiveData<List<ContactBase>>?{
return retrofitService.loadContactsData()
}
}
I cross verified the url too and pojo class. But it always go to failure case in retrofit.
The error says that the incomming JSON starts with a { and not with a [ which means it's an object and not an array of objects.
So you should be having a class that has an array of Contact in order to make that call successful.
A small heads up: Since you are using GSON, your model classes would need the implementation of #SerializedName(string) annotation above the variables.