Can I send parameter to the Interface in kotlin? - android

I use interface for #Get in retrofit API request, instead of creating #Get functions for each call I want to send a parameter to the #Get function, How can I do that? Like below example
interface SomeApiService(catId: String) {
#GET(EN_PRODUCTLIST+catId)
fun getProductByCatId():
Deferred<SomeProduct>
}
object SomeApi {
val retrofitService : SomeApiService by lazy { retrofit.create(SomeApiService::class.java) }
}
than when I call SomeApi(catId).getProductByCatId(), it will return that specific product by catId.
how to do that?

you would have to do this
#GET("EN_PRODUCTLIST{catId}")
fun getProductByCatId(#Path(catId) catId:String):Deferred<SomeProduct>
the #Path puts the parameter into the url path of the request.
Please read through the documentation again https://square.github.io/retrofit/

Related

#FormUrlEncoded #Field enum not using a custom Moshi adapter

I use Retrofit (v2.9.0) and Moshi (v1.11.0) in my app. I try to call an endpoint this way:
#FormUrlEncoded
#PATCH("anime/{anime_id}/my_list_status")
fun updateListStatus(
#Path("anime_id") animeId: Long,
#Field("num_watched_episodes") nbWatchedEpisodes: Int,
#Field("score") score: Double,
#Field("status") watchStatus: WatchStatus,
): Single<MyListStatus>
But the WatchStatus->Json conversion is not working as expect. WatchStatus is a simple enum class:
enum class WatchStatus {
COMPLETED,
DROPPED,
ON_HOLD,
PLAN_TO_WATCH,
WATCHING,
}
and I created a custom adapter because my app uses uppercase enum names while the back-end uses lowercase names:
class AnimeMoshiAdapters {
/* Others adapters */
#ToJson
fun watchStatusToJson(watchStatus: WatchStatus): String =
watchStatus.toString().toLowerCase(Locale.getDefault())
#FromJson
fun watchStatusFromJson(watchStatus: String): WatchStatus =
WatchStatus.valueOf(watchStatus.toUpperCase(Locale.getDefault()))
}
I create my Moshi instance this way:
Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.add(AnimeMoshiAdapters())
.build()
and my Retrofit instance uses it (with Koin injection):
Retrofit.Builder()
.baseUrl(get<String>(named("baseUrl")))
.client(get(named("default")))
.addConverterFactory(MoshiConverterFactory.create(get()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
When parsing a Json to create a WatchStatus enum the adapter is used. It is noticeable because the call fails with an error "com.squareup.moshi.JsonDataException: Expected one of [COMPLETED, DROPPED, ON_HOLD, PLAN_TO_WATCH, WATCHING]" if I remove my custom adapter.
When I try to call the endpoint specified above the transformation of a WatchStatus in Json is wrong and the enum name stay in Uppercase, meaning my custom adapter is not used. If I check the Retrofit logs I can see that it send "num_watched_episodes=12&score=6.0&status=ON_HOLD", so the status is not converted in lowercase.
If I try to manually convert a WatchStatus in Json using the same Moshi instance it works as expected, so I believe my custom adapter implementation is correct.
How can I make Retrofit uses my custom Moshi adapter in this call?
Moshi adapters' toJson apply to the retrofit requests' body (== params annotated with #BODY), not query parameters (== params annotated with #FIELD). And it is correct and expected behavior, as query parameters are by standards not expected to be JSON formatted strings (eventhough in your case it's just a String). If your server expects the status field as query parameter, then there is no other way than to provide it lowerCased by yourself:
#FormUrlEncoded
#PATCH("anime/{anime_id}/my_list_status")
fun updateListStatus(
...
#Field("status") watchStatus: String
): Single<MyListStatus>
and feed your updateListStatus with already lowerCased value:
updateListStatus(..., COMPLETED.name.toLowerCase())
If you have no influence on the server's implementation, then skip the rest of this post.
If you want to utilize your custom adapter's toJson function, your server needs to change the request to accept JSON body instead of query params, say like this:
PUT: anime/{anime_id}/my_list_status
BODY:
{
"anime_id" : Long,
"num_watched_episodes" : Int,
"score" : Double,
"status" : WatchStatus
}
Then you would create a data class for the body, say named RequestBody, then you could change your request to:
#PUT("anime/{anime_id}/my_list_status")
fun updateListStatus(
...
#BODY body: RequestBody
): Single<MyListStatus>
in which case your custom adapter will take effect and transform the WatchStatus inside the RequestBody by its defined toJson logic.

Build a url with retrofit

I'm using retrofit to access data about tv channels.
The url that i have is this:
http://ott.online.meo.pt/catalog/v7/Channels?UserAgent=AND&$filter=substringof(%27MEO_Mobile%27,AvailableOnChannels)%20and%20IsAdult%20eq%20false&$orderby=ChannelPosition%20asc&$inlinecount=allpages
In Retrofit.Builder() i put "the main url" (http://ott.online.meo.pt) and in interface Endpoint the rest of url.
I do this, but i don't kwon how put the complet url
interface Endpoint {
#Headers("User-Agent: AND")
#GET("catalog/v7/Channels" )
fun getChannels() : Call<SerializeChannels>
}
Your retrofit client :
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://ott.online.meo.pt/")
.build();
You can define endpoint in multiple way:
Following is hard codded way:
interface Endpoint {
#GET("catalog/v7/Channels?UserAgent=AND&filter=substringof('MEO_Mobile',AvailableOnChannels)&IsAdult=false&orderby=ChannelPosition asc&inlinecount=allpages" )
fun getChannels() : Call<SerializeChannels>
}
You can also use Query parameter as follows:
interface Endpoint {
#GET("catalog/v7/Channels")
fun getChannels( #Query("UserAgent") String agent, #Query("filter") String filters,#Query("IsAdult") String isAdult,#Query("orderby") String sort,#Query("inlinecount") String count) : Call<SerializeChannels>
}

How to add URL parameter in a Retrofit #GET request in Kotlin

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}")

Retrofit request without body

I am using the retrofit. But I don't understand how to send a request without a body and I can't find anything about this in the internet... Please, write an example of the request without the body (with an url and header only)
Everything is explained in details in the retrofit docs.
In the very first example you have GET call without any body - just to fetch a list of github repos.
public interface GitHubService {
#GET("users/{user}/repos")
Call<List<Repo>> listRepos(#Path("user") String user);
}
Just for understanding:
For GET method use:
public interface GitHubService {
#GET("users/{user}/repos")
Call<List<Repo>> listRepos(#Path("user") String user);
}
For POST method use:
public interface GitHubService {
#POST("users/user/repos")
Call<List<Repo>> listRepos(#Field("user") String user, ......<your_parametres>);
}
If you still have any doubt, please comment.

Retrofit 2 : End of input at line 1 column 1 path $

Have made a thorough searching to this particular problem but while implementing answers under each question I encountered, am still getting the same output:
End of input at line 1 column 1 path $
I perfomed my Request on PostMan and I got expected output:
Here is the Screenshot of the Postman Request
Interfaces
#POST(Constant.API_REQUEST)
Observable<ServerResponse> postToWinnersList(#Body ServerRequest serverRequest);
ApiClient
public class ApiClient {
public static Retrofit retrofit;
private static OkHttpClient.Builder okHttpClientBuilder;
private static HttpLoggingInterceptor loggingInterceptor;
public static Retrofit getApiClient(){
if (retrofit == null){
// create instance of Httpclient
okHttpClientBuilder = new OkHttpClient.Builder();
loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
if(BuildConfig.DEBUG){
okHttpClientBuilder.addInterceptor(loggingInterceptor);
}
// instance of retrofit
retrofit = new Retrofit.Builder().baseUrl(Constant.BASE_URL).
addCallAdapterFactory(RxJava2CallAdapterFactory.create()).
addConverterFactory(GsonConverterFactory.create())
.client(okHttpClientBuilder.build())
.build();
}
return retrofit;
}
}
Retrofit/RxJava Request Code:
Observable<ServerResponse> response = apiInterface.postToWinnersList(serverRequest);
response.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribeWith(new DisposableObserver<ServerResponse>() {
#Override
public void onNext(ServerResponse serverResponse) {
AVLoadingIndicatorView1.setVisibility(View.GONE);
txtSubmitWinner.setVisibility(View.VISIBLE);
}
#Override
public void onError(Throwable e) {
AVLoadingIndicatorView1.setVisibility(View.GONE);
txtSubmitWinner.setVisibility(View.VISIBLE);
Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
#Override
public void onComplete() {
showShortMsg(getString(R.string.submit_success));
}
});
Kindly help, thanks in Advance.
You can get this error when you are expecting an object in the response, but the API doesn't return anything other than result codes (200,...). Options:
- Check that the API really returns a ServerResponse.
- If you don't really need it to return anything, use Observable<Response<Void>> instead of Observable<ServerResponse>
You only get that error when something is wrong with your json response, Check again to make sure both the error response and correct response are well formatted.
Enter wrong credentials using postman and see what the output looks like.
Not sure but I think in Constant.API_REQUEST you are not appending "/".Let me know if it is right.
ex.#POST("index.php") // wrong
#POST("/index.php") //correct way
This error happens when an answer is void.
To correct this error make sure that in the return of the request there will be void.
Eg.
Interface whit kotlin
#POST("endPoint/")
fun relatarProblema(#Body serverRequest: ServerRequest ): Call<Void>
Don't forget to override the return type in the api call.
example whit Kotlin
call.enqueue (object: Callback <Void> {
override fun onResponse(call: Call<Void>, response: Response<Void>) {
}
override fun onFailure(call: Call<Void>, t: Throwable) {
}
})
When I used Coroutines what helped was to basically not return anything from the method:
#GET
#Headers("X-Requested-With:XMLHttpRequest")
suspend fun methodCall(
#Url url: String
)
I was getting the same exception yesterday and figured out that I was using a LogOutResponse data class as the expected response for the API, but then I got to know that the API doesn't return any JSON response corresponding to that data class, in fact, it didn't return anything in the body and only returned result code (200,300,400, etc). So I changed my implementation from:
#POST("logout")
suspend fun logout(): LogOutResponse
to
#POST("logout")
suspend fun logout(): Response<Unit>
Earlier retrofit tried to parse a JSON response at line 1 when there was no JSON there, that's why it threw that exception.
After the changes, the code worked fine as Response is a default retrofit class and Unit type inside it is of void type meaning we don't expect anything in return (no JSON body in response).

Categories

Resources