I'm having an issue with a simple GET request in Retrofit.
I'm getting data from API that returns many pairs that I don't really need, so I just focused on the elements that I need to parse when it comes to this specific request.
To get an idea, this is how the response looks:
[
{
"buildingID": 0,
"buildingName": "string",
"description": "string",
"address": "string",
"type": 0,
"rooms": [
{
...
}
],
"reservations": [
{
...
}
]
...
}
I'd just like to pull a few pairs out of it, so I created a following data class:
#JsonClass(generateAdapter = true)
data class BuildingJson(#field:Json(name = "buildingID") val buildingID: Int,
#field:Json(name = "buildingName") val name: String,
#field:Json(name = "description") val description: String,
#field:Json(name = "address") val address: String,
#field:Json(name = "type") val type: Int)
and this is how my retrofit&moshi instance looks like (Singleton.kt):
object Singleton {
val okhttp = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
val moshi = Moshi.Builder().build()
const val BASE_URL = "https://localhost:5001/api/"
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okhttp)
.addConverterFactory(MoshiConverterFactory.create())
.build()
val service = retrofit.create(BookitAPI::class.java)
}
And this is how my Retrofit interface for a call looks like (wrapped in BookitAPI.kt):
interface BookitAPI {
#GET("v1/BuildingApi")
fun getBuildings(): Call<List<BuildingJson>>
}
This all gets called in repository with the following code:
val buildingResponse = service.getBuildings().enqueue(object : Callback<List<BuildingJson>> {
override fun onFailure(call: Call<List<BuildingJson>>, t: Throwable) {
TODO("Not yet implemented")
}
override fun onResponse(
call: Call<List<BuildingJson>>,
response: Response<List<BuildingJson>>
) {
TODO("Not yet implemented")
}
})
The problem is that when I run this code, I get the following error:
java.lang.IllegalArgumentException: Unable to create converter for java.util.List<mylab.bookitphoneapp.models.BuildingJson>
for method BookitAPI.getBuildings
Does anyone have an idea how should I implement this converter that fails to be created? I've checked many resources but I still couldn't find an issue that's similar to this one.
Thanks!
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'm using retrofit for web requests and then moshi for JSON parsing,this is api
#POST("/verify_code/send_email")
suspend fun sendEmail(#Body sendEmailRequest: SendEmailRequest): BaseResponse<Unit>
the BaseResponse
#JsonClass(generateAdapter = true)
open class BaseResponse<T> {
#Json(name = "code")
var code: Int? = null
#Json(name = "message")
var message: String? = null
#Json(name = "data")
var data: T? = null
}
JSON String
{
"code": 200,
"message": "Some Message",
"data": null
}
and error log
2021-11-26 09:59:24.166 14288-14288/com.gow E/FlowKtxKt$next: java.lang.IllegalArgumentException: Unable to create converter for com.gow.base.BaseResponse<kotlin.Unit>
for method VerifyApi.sendEmail
I tried adding the following, but it didn't work
object UnitConverterFactory : Converter.Factory() {
override fun responseBodyConverter(
type: Type, annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *>? {
return if (type == Unit::class.java) UnitConverter else null
}
private object UnitConverter : Converter<ResponseBody, Unit> {
override fun convert(value: ResponseBody) {
value.close()
}
}
}
it`s the Moshi bug.
I solved my problem by using Any instead of Unit.
like this:
#POST("/verify_code/send_email")
suspend fun sendEmail(#Body sendEmailRequest: SendEmailRequest): BaseResponse<Any>
I had the same issue.
I fixed it by following the comment on this link.
I do not see how you are adding your UnitConverterFactory but in my case, the order you add it is important.
I am also using MoshiConverterFactory and ApiResultConverterFactory from EitherNet, so my UnitConverterFactory had to be placed after ApiResultConverterFactory and before MoshiConverterFactory:
Retrofit.Builder()
...
.addCallAdapterFactory(ApiResultCallAdapterFactory)
//the order of the converters matters.
.addConverterFactory(ApiResultConverterFactory)
.addConverterFactory(UnitConverterFactory)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
help me for this issue please, I want to get data from API
{
"status": true,
"data": [
{
"id_pelanggan": "456",
"nama_pelanggan": "ahmad",
"alamat": "taliwang"
},
{
"id_pelanggan": "457",
"nama_pelanggan": "ahmad",
"alamat": "taliwang"
}
]}
this is my API object for setup for dynamic class, retrofit2 and gson
object Api {
private val BASE_URL: String = BuildConfig.API_SRAPP
private var gson = GsonBuilder().setLenient().create()
private val httpClient = OkHttpClient.Builder()
fun <T>service(java: Class<T>): T{
val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(BASE_URL)
.client(httpClient.build())
.build()
return retrofit.create(java)
}
}
this is my class BaseRespon for handle all data from API and with dynamic class
data class BaseResponse<T>(
val status: Boolean,
val data: T?
)
this is my class Customer
data class Customer (
#SerializedName("id_pelanggan")
#Expose
val idPelanggan: String,
#SerializedName("nama_pelanggan")
#Expose
val namaPelanggan: String,
#SerializedName("alamat")
#Expose
val alamat: String
)
this is my API service
interface CustomerServices {
#GET("customer")
fun getAllCustomer(#Header("Authorization") auth: String): Call<BaseResponse<ArrayList<Customer>>>
#GET("customer")
fun getCustomerbyID(#Header("Authorization") auth: String, #Query("id") id: String): Call<BaseResponse<Customer>>
}
and this is my class for using API
class CustomerPresenter {
fun loadAllCustomer(){
apiCustomer.getAllCustomer(OfflineHelper.getToken())
.enqueue(object : Callback<BaseResponse<ArrayList<Customer>>>{
override fun onFailure(call: Call<BaseResponse<ArrayList<Customer>>>, t: Throwable) {
Log.e("allCustomer", "${t.message}")
}
override fun onResponse(
call: Call<BaseResponse<ArrayList<Customer>>>,
response: Response<BaseResponse<ArrayList<Customer>>>
) {
saveLocalCustomer(response.body())
}
})
}
fun saveLocalCustomer(data: BaseResponse<ArrayList<Customer>>?){
Log.w("loadedAll", "${data?.status}")
}
and I have log failure in loadAllCustomer
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 2 column 1 path $
I don't understand why it's failure, my endpoint is not typo, and I have tested it on postman
You need to use a List as your return type for the data field. try this:
data class BaseResponseList<T>(
val status: Boolean,
val data: List<T>?
)
and then using BaseResponseList in your call as following:
#GET("customer")
fun getAllCustomer(#Header("Authorization") auth: String): Call<BaseResponseList<Customer>>
Use BaseResponseList for any endpoint that returns a List, and BaseResponse for Objects
Update your response class and api interface like below
data class BaseResponse(
val status: Boolean,
val data: List<Customer>?
)
interface CustomerServices {
#GET("customer")
fun getAllCustomer(#Header("Authorization") auth: String): Call<BaseResponse>
#GET("customer")
fun getCustomerbyID(#Header("Authorization") auth: String, #Query("id") id: String): Call<Customer>
}
your model not same with you object json
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.
I'm new to Kotlin, Android and OOP in general (Natural-ADABAS background, never did Java, C++, etc) so I'm pretty desperate.
I have an API whose data looks like this, an array of book details:
API data sample
I'm confused about data models. I know it's supposed to look like how the data in the API and return an array but how exactly do I code it in Kotlin? And then how do I parse it? I've read some tutorials but they all differ. Some use an object, and some use a class.
I'm also probably breaking some standard by putting everything in the main activity but I haven't gotten to that part yet.
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query
class MainActivity : AppCompatActivity()
{
private val api: RestAPI = RestAPI()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val apiGetBooks = api.getBooksList("token123123123")
val response = apiGetBooks.execute()
if (response.isSuccessful) {
val books = response.body()?.title
println(books)
} else {
println("error on API") // What do I do?
}
}
object Model {
val ResultArray : MutableList<BookProperties>? = null
}
data class BookProperties (val id: Int,val title: String, val coverURI: String, val pageURI: String, val pageCount: Int, val languageId: Int,val description: String, val isFree: Boolean) {
}
private val buriApi: MainActivity.BooksAPI? = null
class RestAPI {
private val buriApi: BooksAPI
init {
val retrofit = Retrofit.Builder()
.baseUrl("https://api.someurl.com")
.addConverterFactory(MoshiConverterFactory.create())
.build()
buriApi = retrofit.create(BooksAPI::class.java)
}
fun getBooksList(token: String): Call<BookProperties>{
return buriApi.getBooks(token)
}
}
fun getBooksList(token: String): Call<MainActivity.BookProperties> {
return buriApi!!.getBooks(token)
}
interface BooksAPI {
#GET("/v1/books")
fun getBooks (#Query("token")token: String) : Call<BookProperties>
}
}
After much googling, I finally solved my problem thanks to How to Quickly Fetch Parse JSON with OkHttp and Gson on YouTube.
fun fetchBooks () {
println("fetching books")
val url = "https://api.someurl.com/v1/books?"
val request = Request.Builder().url(url).build()
println(request)
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call?, response: Response?) {
val body = response?.body()?.string()
println(body)
}
override fun onFailure(call: Call?, e: IOException?) {
println("Failed to execute request")
e?.printStackTrace()
}
})
}
Still need to format the data and figure out how to turn on wifi in my Android emulator but at least I can consume the JSON.
Let's start with a sample and I guess you can map it accordingly to your requirement.
I don't have your JSON as text so I am giving an example of mine.
sample JSON response
{
"status": true,
"message": "User created Successfully.",
"response": {
"user": {
"id": 12,
"email": "testmail#gmailtest.com"
},
"token": "eyJlbWFpbCI6ImVzaGFudHNhaHUxMTBAZ21hc2kyMmwuY29tIiwidXNlcklkIjoxNSwiaWF0IjoxNTIxNTYyNjkxfQ"
}
}
so create a new class and name it something like this
CreateResponse.kt
and just map those objects and arrays from json to data classes and list here.
data class CreateUserResponse(override val status: Boolean? = null,
override val message: String? = null,
val response: Response? = null)
data class Response(val user: User?, val token: String)
data class User(val id: Int, val email: String)
easy right, now with Kotlin you can declare your data classes without creating separate files each time for each object just create one file and declare all of them at once.
I'm attaching some of the resources here which may help you understand the things better.
https://antonioleiva.com/retrofit-android-kotlin/
https://segunfamisa.com/posts/using-retrofit-on-android-with-kotlin