I am currently trying to fetch a JSONArray from a server using Retrofit in Kotlin. Here is the interface I am using:
interface TripsService {
#GET("/coordsOfTrip{id}")
fun getTripCoord(
#Header("Authorization") token: String,
#Query("id") id: Int
): Deferred<JSONArray>
companion object{
operator fun invoke(
connectivityInterceptor: ConnectivityInterceptor
):TripsService{
val okHttpClient = OkHttpClient.Builder().addInterceptor(connectivityInterceptor).build()
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://someurl.com/")
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(TripsService::class.java)
}
}
}
the desired url is: https://someurl.com/coordsOfTrip?id=201
I am getting the following error message:
retrofit2.HttpException: HTTP 405 Method Not Allowed
I know the URL is working because I can access it via a browser.
Can someone please help me identify what I am doing wrong?
Just change the parameter from
#GET("/coordsOfTrip{id}")
to
#GET("/coordsOfTrip") // remove {id} part that's it
And you'd get the desired URL https://someurl.com/coordsOfTrip?id=201
If you want to use {id} in GET() then you've to use it like below
#GET("/coordsOfTrip{id}")
fun getTripCoord(
#Header("Authorization") token: String,
#Path("id") id: Int // use #Path() instead of #Query()
): Deferred<JSONArray>
But in your case it doesn't require. Follow the first method I mentioned.
For more check Retorfit's official documentation URL Manipulation part
Replace
#GET("/coordsOfTrip{id}")
with:
#GET("/coordsOfTrip?id={id}")
Related
I have to make a post request using retrofit, but the URL for this request comes from another request (GET), and the URL comes as a complete endpoint (i.e: https://pod-000-1005-03.backblaze.com/b2api/v2/b2_upload_file?cvt=c001_v0001005_t0027&bucket=4a48fe8875c6214145260818).
How can i make a retrofit request directly to this endpoint?
How im creating the retrofit instance:
fun getUploadApi(uploadUrl: String): B2UploadApi {
return Retrofit.Builder()
.baseUrl(uploadUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(B2UploadApi::class.java)
}
this throws an exception since the url doesnt end with '/'
And the POST request:
#POST
suspend fun uploadFile(
#Header("Authorization") authToken: String,
#Header("X-Bz-File-Name") fileName: String,
#Header("Content-Length") fileSize: Int,
#Header("Content-Type") mimeType: String,
#Header("X-Bz-Content-Sha1") sha1: String,
#Body byteArray: ByteArray
): Response<UploadResponse>
As mentioned in documentation , #url will override base url which you have mentioned at time of retrofit object creation
So you just need to use #url annotation along with method in retrofit service
documentation - https://square.github.io/retrofit/2.x/retrofit/retrofit2/http/Url.html
example -
https://futurestud.io/tutorials/retrofit-2-how-to-use-dynamic-urls-for-requests
I'm calling an API in my Android app to receive this ByteArray from the server Picture of ByteArray data from server
I'm using Retrofit2 in order to call the API, and in doing so I've had to specify the Response type. When I specify the Response type to be ByteArray,
#GET("api/getImage")
suspend fun getImage(#Query("user_id") user_id: String
): Response<ByteArray> // This is in my API Class
suspend fun getImage(user_id: String): Response<ByteArray> {
return RetrofitInstance.api.getImage(user_id)
}` // This is in my Repository accessed through ViewModels
I encounter this error " java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING at line 1 column 1 path $".
So then I tried to receive the Retrofit2 Response as a String type
#GET("api/getImage")
suspend fun getImage(#Query("user_id") user_id: String
): Response<String> // This is in my API Class
suspend fun getImage(user_id: String): Response<String> {
return RetrofitInstance.api.getImage(user_id)
}` // This is in my Repository accessed through ViewModels
instead and then convert it to Bytes, but I encounter a "com.google.gson.stream.MalformedJsonException: Expected value at line 1 column 56 path $" instead.
If it helps, here's my Retrofit Object I refer to in api calls:
object RetrofitInstance {
private val client = OkHttpClient.Builder().apply {
addInterceptor(MyInterceptor())
addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
}.build()
private val retrofit by lazy {
val gson = GsonBuilder().setLenient().create()
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
val api : ApplicationAPI by lazy {
retrofit.create(ApplicationAPI::class.java)
}
}
Would really appreciate the help in debugging this error
Please check this api: https://api.github.com/emojis
This is part of the response:
{
"+1": "https://github.githubassets.com/images/icons/emoji/unicode/1f44d.png?v8",
"-1": "https://github.githubassets.com/images/icons/emoji/unicode/1f44e.png?v8",
"100": "https://github.githubassets.com/images/icons/emoji/unicode/1f4af.png?v8",
"1234": "https://github.githubassets.com/images/icons/emoji/unicode/1f522.png?v8",
"1st_place_medal": "https://github.githubassets.com/images/icons/emoji/unicode/1f947.png?v8",
"2nd_place_medal": "https://github.githubassets.com/images/icons/emoji/unicode/1f948.png?v8",
"3rd_place_medal": "https://github.githubassets.com/images/icons/emoji/unicode/1f949.png?v8",
"8ball": "https://github.githubassets.com/images/icons/emoji/unicode/1f3b1.png?v8",
"a": "https://github.githubassets.com/images/icons/emoji/unicode/1f170.png?v8",
"ab": "https://github.githubassets.com/images/icons/emoji/unicode/1f18e.png?v8",
"abacus": "https://github.githubassets.com/images/icons/emoji/unicode/1f9ee.png?v8",
"abc": "https://github.githubassets.com/images/icons/emoji/unicode/1f524.png?v8",
"abcd": "https://github.githubassets.com/images/icons/emoji/unicode/1f521.png?v8",
}
I'd like to convert this response to a list of Emoji.
data class Emoji(
val name: String,
val url: String,
)
Note that the response is a big object and I need a list.
This is how I'm instantiating Retrofit:
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
How could I achieve it?
You didn't attach you Api interface but based on your descriptions you've put List<Emoji> in you api interface which ia going to raise a MalformedJSONException
Use a Map<String, String> instead and if you need a list use responseMap.map{ Emoji(it.key, it.valie) }
I'm trying to post some data with retrofit 2 but I'm gettins some problems... and don't find any example like this...
This is the body that I have to send:
{
"birthday": "12-01-1987",
"name": bob,
"activity": {
"activity_preferences": {
"user_subjects": [4,7,8],
"user_allergies": [1,6,10],
}
}
}
This is my data class:
data class GenericFormDataEntity(
var birthday: String,
var name: String,
#SerializedName("activity")
var food: ActivityEntity?
)
data class ActivityEntity(#SerializedName("activity_preferences")val activityPreferences: ActivityPreferencesEntity)
data class ActivityPreferencesEntity(#SerializedName("user_Subjects")var userSubjects:List<Int>?,#SerializedName("user_allergies")var userAllergies: List<Int>?)
This is the method that I'm trying to build the json:
fun getUserFormEntity(): String{
val paramObject = JSONObject()
paramObject.put("birthday", birthday)
paramObject.put("name", name)
paramObject.put("activity", getActivityEntity())
return paramObject.toString()
}
private fun getActivityEntity(): ActivityEntity{
return ActivityEntity(ActivityPreferencesEntity(selectedSubjectList, selecteAllergiesList))
}
And this is the json that is returning me:
{\"birthday\":\"23-12-2019\",\"name\":Bob,"activity\":\"ActivityEntity(activity_preferences=ActivityPreferencesEntity(user_Subjects=[4,7,8], user_allergies=[1,6,10])"}"
My question is, how can I get the correct json that I have to send as a body:
#Headers("Accept: application/json")
#POST("xxxxxxxx")
suspend fun saveUserData(#Body userFormData: String)
You need to stringify getActivityEntity using Gson.
Gson.toJson(getActivityEntity())
Also, from your API I infer that you are using retrofit why not pass along the entire instance of GenericFormDataEntity as the body for your API.
For enabling this you need to follow by adding GsonConverterFactory.create(gson) to your retrofit.
Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create(gson))
.callFactory(okHttpClient)
.build()
I am using the new Retrofit2 with suspending coroutines, and with GET requests everything works fine.
But I now have to implement a POST request, and just can't get it to work
I have a CURL example that looks like this:
curl -X POST -H "Content-Type: application/json;charsets: utf-8" -d '{"tx_guapptokenlist_tokenitem":{"tokenchar":"my-token-string","platform":"android"}}' https://www.example-url.com/tokens?type=56427890283537921
This works fine, and returns this response: {"errors":false,"success":true}%
So here's what my request looks like in my Api class right now:
#Headers( "Content-Type: application/json" )
#POST("/tokens?type=56427890283537921")
suspend fun sendFirebaseToken(#Body tokenRequest: RequestBody) : Call<TokenResponse>
This is my TokenResponse class:
#JsonClass(generateAdapter = true)
data class TokenResponse(
#Json(name="errors")
val errors: Boolean,
#Json(name="success")
val success: Boolean)
and the ApiClient class I'm using:
object ApiClient {
private const val BASE_URL = "https://myExampleUrl.com"
private var retrofit: Retrofit? = null
var moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val client: Retrofit?
get() {
if (retrofit == null) {
retrofit = Retrofit.Builder().baseUrl(
BASE_URL
).client(getOkHttpClient())
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
return retrofit
}
fun getOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().addInterceptor(getLoggingInterceptor())
.connectTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS).writeTimeout(90, TimeUnit.SECONDS).build()
}
private fun getLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().setLevel(
if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.HEADERS
else HttpLoggingInterceptor.Level.NONE
)
}
}
The first odd thing I noticed: Even with the #POST annotation, if my suspend fun has no return type, I get no error, but okhttp will always send a GET request (at least the endpoint always receives a GET). Not sure if that is supposed to be like that?
Anyway: I need the return values, so I'm returning Call<TokenResponse>.
This leads me to my main problem, that I can't solve: If now I execute my code, it crashes with this log:
java.lang.IllegalArgumentException: Unable to create converter for retrofit2.Call<myapp.communication.TokenResponse>
for method TokenApi.sendToken
at retrofit2.Utils.methodError(Utils.java:52)
To try and deal with this I have used moshi-kotlin-codegen to generate the proper adapter (hence the annotations in the data class), but to no avail. The class is generated, but not used. I have tried to pass a Moshi with JsonAdapterFactory like this var moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()to my ConverterFactory but that doesn't work either.
Tried to add the generated adapter maually to moshi but that also did not work.
I've also tried returning different types in my request. The Retrofit docs state that without a converter one could only return a ResponseBody, but same result: Retrofit complains it has no converter. The same for returning Call<Void>
I feel like I'm missing something here? Who can help? Happy to provide more details, please request what's needed.
Your request function should look like this.
#Headers( "Content-Type: application/json" )
#POST("/tokens?type=56427890283537921")
suspend fun sendFirebaseToken(#Body tokenRequest: RequestBody): TokenResponse
You don't use Call<...> since you have marked it as suspend.
Think the annotation should be:
#JsonClass(generateAdapter = true)
data class TokenResponse(
#field:Json(name = "errors") val errors: Integer,
#field:Json(name = "success") val success: Boolean
)
And try to remove the suspend keyword once, which might clash with generateAdapter = true.
I've got it working now, this is what I learned:
First of all: #Dominic Fischer here is right, Call is wrong, and with everything set up correctly, there is no need to wrap the result object at all (I noticed by the way the #Headers annotation looks to be not necessary, Retrofit seems to just take care of it).
The second and biggest problem is that the client object in my ApiClient class was not used correctly. See the new version:
fun getRetrofitService(): ApiService {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getOkHttpClient())
.addConverterFactory(MoshiConverterFactory.create())
.build().create(ApiService::class.java)
}
See that now the 'create()' step is added, which before I handled outside of this class. There I used my Retrofit object to create the service just like here, but I accidentally passed ApiClient::class.java. Interestingly that compiles and runs just fine, but of course this must mess up somewhere - it's unable to properly build the JSON adapters.
As a result I pulled this step into my ApiClientin order to prevent such accidents in the future.
If anybody has suggestions as to meking this question + answer more useful for future readers, please let me know!