How to call REST API with Retrofit #Query - android

I can't get data base on idCafe.
This is the url from webservice :
https://admin-services-dot-annular-bucksaw-167705.appspot.com/_ah/api/meja?idCafe=123445
Api Service Interface :
#GET ("meja?idCafe")
fun getTable(#Query("idCafe") idCafe: String): Call<List<Table>>
I call api with this function :
private fun getTableList(idCafe:String){
val apiService : Service = Client.getClient()!!.create(Service::class.java)
apiService.getTable(idCafe).enqueue(object : Callback<List<Table>>{
override fun onResponse(call: Call<List<Table>>?, response: Response<List<Table>>?) {
Log.i("IdMeja", "id : " + response?.body())
if (response != null && response.isSuccessful) {
val listTable = response.body()
if (listTable == null || listTable.isEmpty()) {
Toast.makeText(this#MainActivity, "Tidak ada meja", Toast.LENGTH_SHORT).show()
}
else{
tableList = ArrayList(listTable)
// update list table
dataAdapter.updateData(tableList)
}
}
else{
Log.i("idCafe", " $idCafe")
Toast.makeText(this#MainActivity, "Gagal dapat meja", Toast.LENGTH_SHORT).show()
}
}
override fun onFailure(call: Call<List<Table>>?, t: Throwable?) {
Log.i("fail",t.toString() )
Toast.makeText(this#MainActivity, "Gagal", Toast.LENGTH_SHORT).show()
}
})
}
But the response unsuccessful. I can get idCafe, but can't get data of Table.
Please help me to fix this.

use like this
#GET ("meja")
fun getTable(#Query("idCafe") idCafe: Int): Call<List<Table>>
You dont have to include ? and query variable inside your URL.

When you use #Query Retrofit will build the query parameter for you. No need to specify it in the URL.
This means that you should just declare the interface like:
#GET ("meja")
fun getTable(#Query("idCafe") idCafe: String): Call<List<Table>>
Retrofit will then use the name inside the annotation - idCafe - and build the parameter with it.
You can keep using string, since retrofit can easily deal with it. However, if idCafe is better modeled as an integer in your application, then I'd suggest you use Int instead:
#GET ("meja")
fun getTable(#Query("idCafe") idCafe: Int): Call<List<Table>>
Note that this is only possible because in the URL idCafe is an integer.

Call your api like this
#GET ("meja")
fun getTable(#Query("idCafe") idCafe: String): Call<TableResponse>
And the table response call will be as follows -
class TableResponse {
#SerializedName("daftarMeja")
List<Table> list;
public List<Table> getList() {
return list;
}
public void setList(List<Table> list) {
this.list = list;
}
}
You can not use Call<List<Table>> as an result in the api call as error says "BEGIN_ARRAY but was BEGIN_OBJECT"

Related

How to Parse Json in Kotlin Using Retrofit?

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.

How to parse response with retrofit2 Kotlin

I'm stuck with parsing the response. In Swift I can make a codable to help parsing the json response. I'm new to Kotlin and I'm working on someone else existing project. I made a data class for string and boolean but I don't know the syntax to parse it. Please help and thank you.
The responseBody json
{
"bearerToken": "########",
"staySignIn": false
}
//Interface
interface PostInterface {
class User(
val email: String,
val password: String
)
#POST("signIn")
fun signIn(#Body user: User): Call<ResponseBody>
//Network handler
fun signIn(email: String, password: String): MutableLiveData<Resource> {
val status: MutableLiveData<Resource> = MutableLiveData()
status.value = Resource.loading(null)
val retrofit = ServiceBuilder.buildService(PostInterface::class.java)
retrofit.signIn(PostInterface.User(email, password)).enqueue(object : Callback<ResponseBody> {
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
errorMessage(status)
}
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
if (response.code() == 200) {
try {
status.value = //how to parse using the model??
} catch (ex: Exception) {
parseError(400, response.body().toString(), status)
}
} else {
//do something...
}
}
})
return status
}
//Model
data class SignInModel(
#field:SerializedName("bearerToken")
val bearerToken: String? = null,
#field:SerializedName("staySignIn")
val staySignIn: Boolean? = null
)
//Storing value class
class RrefManager constructor(var applicationContext: Context) {
private fun getSharedPrefEditor(): sharedPrefEditor.Editor {
return applicationContext.getSharedPrefEditor(prefStorageName, Context.MODE_PRIVATE).edit()
}
public fun setBearerToken(token: String) {
getSharedPrefEditor().putString("bearerToken", token).apply()
}
public fun setStaySignIn(enabled: Boolean) {
getSharedPrefEditor().putBoolean("staySignIn", enabled).apply()
}
}
//SignIn Button
viewModel.signIn().observe(viewLifecycleOwner, androidx.lifecycle.Observer { v ->
if (v.status == Resource.Status.SUCCESS) {
val model = v.data as SignInModel
pref.setToken(model.token as String) //storing value
pref.setTwoFactorEnabled(model.twoFactorEnabled as Boolean) //storing value
} else if (v.status == Resource.Status.ERROR) {
//do something...
}
})
I think your best option to achieve something like the codable in swift is to use Gson library for parsing api responses.
When you create the retrofit instance you pass the gson converter to the builder like:
val retrofit = Retrofit.Builder()
.baseUrl(BaseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
After you have done that you can make the api return the response you have as the data class, like:
//Interface
interface PostInterface {
#POST("signIn")
fun signIn(#Body user: User): Call<SignInModel>
}
To read the answer from the callback on your class, the response inside the network call is already parsed into your model in the callback. All the retrofit callback should be changed to receive Callback and then you can access directly like status.value = response.body()
For more info you can consult the retrofit library page where it gives all the details and explanations on how to use it correctly.
https://square.github.io/retrofit/

Android get data from dynamic url from APi

I found some Api about Pokemon(https://pokeapi.co). And I try get data from this Api and it's work.
The data look like this
But I try to use name of data from Api to get some image with this path "api/v2/pokemon/{name}"
The problem is how can get name out of onResponse or do the other way to get name and image of Pokemon
MainActivity
val retrofit = Retrofit.Builder()
.baseUrl("https://pokeapi.co/")
.addConverterFactory(GsonConverterFactory.create())
.client(HTTPLogger.getLogger())
.build()
val jsonPlaceholderApi = retrofit.create(pokemonService::class.java)
val myCall: Call<PokemonInGen> = jsonPlaceholderApi.getGen(1)
myCall.enqueue(object : Callback<PokemonInGen> {
override fun onResponse(
call: Call<PokemonInGen>,
response: Response<PokemonInGen>
) {
val DataResponse: PokemonInGen = response.body()!!
Timber.i("on do Respon %s", DataResponse)
}
override fun onFailure(call: Call<PokemonInGen>, t: Throwable) {
Timber.i("on do ERROR")
}
})
My Service
interface pokemonService {
#GET("api/v2/generation/{id}")
fun getGen(
#Path("id") id: Int,
): Call<PokemonInGen>
#GET("api/v2/pokemon/{name}")
fun getArtwork(
#Path("name") name: String,
): Call<PokemonArtwork>
}
My Model Data class
data class PokemonInGen(
val pokemon_species: List<PokemonList>)
data class PokemonList(
val name: String,
val url: String,
)
To fetch Pokemon image you should create additional function in your MainActivity class
fun fetchPokemonArtwork(name: String) {
jsonPlaceholderApi.getArtwork(name).enqueue(object : Callback<PokemonArtwork> {
override fun onResponse(
call: Call<PokemonArtwork>,
response: Response<PokemonArtwork>
) {
// An artwork is successful fetched
val artwork = response.body()!!
}
override fun onFailure(call: Call<PokemonArtwork>, t: Throwable) {
// Handle a failure
}
})
}
You should call this function right after you fetched a Pokemon in generation
val myCall: Call<PokemonInGen> = jsonPlaceholderApi.getGen(1)
myCall.enqueue(object : Callback<PokemonInGen> {
override fun onResponse(
call: Call<PokemonInGen>,
response: Response<PokemonInGen>
) {
val DataResponse: PokemonInGen = response.body()!!
Timber.i("on do Respon %s", DataResponse)
// Extract name
val name = DataResponse.pokemon_species.first().name
// Fetch an artwork
fetchPokemonArtwork(name)
}
override fun onFailure(call: Call<PokemonInGen>, t: Throwable) {
Timber.i("on do ERROR")
}
})
P.S. I proceeded from the assumption that you've implemented PokemonArtwork class. Please let me know if you are facing difficulties in comments below.
P.S.S. It's not recommended to make network calls in Activity or Fragment classes. This guide to app architecture should help you to select correct app structure in your future releases.
You can using #Url to support dynamic link in retrofit. Example below:
interface pokemonService {
#GET
Call<PokemonResponse> getListPokemon(#Url String url);
}
And paste your url to browser to see data format.

Retrofit and Kotlin Post Request 400 error

I am trying to make a simple post request to googles dialogflow in retrofit using kotlin. I am modeling my code off of this site. However, I keep getting 400 errors when trying to make a search so there must be something wrong with my interface creating the message body I believe. I have working python code that does the same functionality as shown here:
url = "https://api.dialogflow.com/v1/query?v=20170712"
headers = {
'Authorization': 'Bearer ' + my_key ,
'Content-Type' : 'application/json'
}
body = {
'lang': 'en',
'query': 'id like to fix my wire c1000 stocks',
'sessionId': 'me'
}
resp = r.post(url,headers=headers,data=json.dumps(body))
I have set this up in android studio as 3 classes:
1) Message.kt
The body of the post request
object Message {
data class MsgBody(val lang: String, val query: String, val sesId: String)
}
2) Model.kt
The response from dialogflow
object Model {
data class Response(val resp: Result)
data class Result(val fulfillment: Fulfillment)
data class Fulfillment(val speech: String)
}
3) DialogFlowService.kt
The interface that has the post request enpoint
interface DialogFlowService {
#Headers(
"Authorization: Bearer {MY API KEY}",
"Content-Type: application/json"
)
#POST("query")
fun getAiMessage(#Body msg: Message.MsgBody,
#Query("v") v: String): Observable<Model1.Response>
companion object {
fun create(): DialogFlowService {
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.dialogflow.com/v1/")
.build()
return retrofit.create(DialogFlowService::class.java)
}
}
}
All of this is then used in my main activity as seen below:
class MainActivity : AppCompatActivity() {
private var disposable: Disposable? = null
private val dialogFlowService by lazy {
DialogFlowService.create()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
if (editText.text.toString().isNotEmpty()) {
sendMessage(editText.text.toString())
}
}
}
private fun sendMessage(msg: String){
disposable = dialogFlowService.getAiMessage(Message.MsgBody("en",msg,"me"),"20170712")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> textView.text = "DialogFlow says: ${result.resp.fulfillment.speech}" },
{ error -> Toast.makeText(this, error.message, Toast.LENGTH_SHORT).show() }
)
}
override fun onPause() {
super.onPause()
disposable?.dispose()
}
}
I tried to follow the tutorial as close as possible and am very confused as to what I did wrong. Like I said above I think this is related to my DialogFlowService.kt file. Thanks for any help in advance.
Error in post request since Model variable name sesId did not equal the actual key sessionId. As Raghunandan said a logging interceptor is very useful

Kotlin JSON Data not arranging into arraylist properly with Retrofit

I'm working on a kotlin android app with Retrofit. I'm making an API call to IEX stock data using this link:
https://api.iextrading.com/1.0/stock/market/batch?symbols=aapl,fb,ge&types=quote
The JSON data doesn't seem to arrange itself into an arraylist naturally. When I plug the data into jsonschema2pojo, it tells me that I should create class names for each of the stocks like this:
public class GE {
#SerializedName("quote")
#Expose
public Quote__ quote;
}
Naturally, I want the stock names to be variable so I can plug any list into there. Is there something wrong with the JSON data, or am I missing a step??
My Methods in case you wanted to see them (They're generic):
private fun getStock(stock: String) {
Timber.d("Start Retrofit Get Stocks")
val service = initiateRetrofit()
val call = service.queryStock("GE")
Timber.d("Url: " + call.request().url())
call.enqueue(object : retrofit2.Callback<StockModel> {
override fun onResponse(call: Call<StockModel>, response: retrofit2.Response<StockModel>) {
Timber.d("Successful Query. Message: " + response.message())
val stocklist : StockModel = response.body()
Timber.d("See what you get in the stock model")
}
override fun onFailure(call: Call<StockModel>, t: Throwable) {
Timber.d("Failed Call: " + t)
}
})
}
private fun initiateRetrofit(): RetrofitService {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder().baseUrl(RetrofitService.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson)).build()
return retrofit.create(RetrofitService::class.java)
}
There's a really clean and simple way of solving for this problem.
To get at the "quote" JSON object, you'll want to create a custom JSON deserializer. The JsonDeserializer is an interface that you implement from the Gson library.
First, we need our response object to use for deserialization.
// PortfolioResponse.kt
class PortfolioResponse {
var quotes: List<Quote>? = null
}
Next, we'll setup our ApiService class to make a call for a PortfolioResponse object.
// ApiService.kt
interface ApiService {
#GET("stock/market/batch")
abstract fun queryStockList(#Query("symbols") stocks: String, #Query("types") types: String): Call<PortfolioResponse>
}
Then, setup the deserializer. This is where we'll strip the unnecessary JSON object keys, and get the "quote" JSON objects we're looking for.
// PortfolioDeserializer.kt
class PortfolioDeserializer : JsonDeserializer<PortfolioResponse> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): PortfolioResponse {
val portfolioResponse = PortfolioResponse()
json?.let {
val jsonObject = it.asJsonObject
val symbolSet = jsonObject.entrySet()
val quoteElements = ArrayList<JsonObject>()
val quotes = ArrayList<Quote>()
val gson = Gson()
// this will give us a list of JSON elements that look like ""Quote": {}"
symbolSet.mapTo(quoteElements) { it.value.asJsonObject }
// this will take each quote JSON element, and only grab the JSON that resembles a Quote
// object, and add it to our list of Quotes
quoteElements.mapTo(quotes) { gson.fromJson(it.entrySet().first().value, Quote::class.java) }
portfolioResponse.quotes = quotes
}
return portfolioResponse
}
}
Finally, update your existing network call in your Activity, and it's done.
// MainActivity.kt
call.enqueue(object : retrofit2.Callback<PortfolioResponse> {
override fun onResponse(call: Call<PortfolioResponse>, response: retrofit2.Response<PortfolioResponse>) {
Timber.d("Successful Market Batch Query. Response.body=${response.body()}")
}
override fun onFailure(call: Call<PortfolioResponse>, t: Throwable) {
Timber.d("Failed Call: " + t)
}
})
The data is in a Map, not an array. Looks like the automated converter is trying to make it an object with field names. Try making your return value in your retrofit interface Call<Map<String, Quote__>>.
You will need to update the rest of your code to pull the key and values out of the map for processing.

Categories

Resources