Retrofit GET without a value in Android - android

I use Retrofit for most of my calls but in one of the cases, I have the full path provided in arguments. My URL is like this http://www.example.com/android.json. This URL is provided in full so I have to path it at runtime. I implement endpoint as suggested here
https://medium.com/#kevintcoughlin/dynamic-endpoints-with-retrofit-a1f4229f4a8d
but in the #GET I need to be able to put #GET(""). This does not work as I get an error saying I should provide at least one "/".
If I add the slash the URL becomes http://www.example.com/android.json/ and it does not work, the server returns forbidden. I also tried creating a custom GET interface similar to here https://github.com/square/retrofit/issues/458 but with GET and without providing a value method in the interface. Then I get another error saying value is missing.
Basically I need to be able to provide an empty or null value but retrofit does not allow that. How could I solve this problem? For now I am doing the JSON request manually but is there a way I could use retrofit for this case? I need to pass the full URL there is no way I can do endpoint http://www.example.com and #GET("/android.json").
Thanks

You can use #GET(".") to indicate that your url is the same as the base url.
#GET(".")
Observable<Result> getData(#Query("param") String parameter);

I've tried this approach, however didn't work for me.
Workaround for this issue is:
//Retrofit interface
public interface TestResourceClient {
#GET
Observable<Something> getSomething(#Url String anEmptyString);
}
//client call
Retrofit.Builder().baseUrl("absolute URL").build()
.create(TestResourceClient.class).getSomething("");
The downside of this solution is that you have to supply empty string in getSomething("") method call.

I face the same problem with Retrofit 2. Using #GET, #GET("") and #GET(".") not solved my problem.
According to the official document you can the same baseUrl and #GET argument.
Endpoint values may be a full URL.
Values that have a host replace the host of baseUrl and values also with a scheme replace the scheme of baseUrl.
Base URL: http://example.com/
Endpoint: https://github.com/square/retrofit/
Result: https://github.com/square/retrofit/
So in my case:
interface MyAPI {
#GET("http://www.omdbapi.com/")
suspend fun getMovies(
#Query("apikey") apikey: String,
#Query("s") s: String
): Response<MoviesResponse>
companion object {
operator fun invoke(): MyAPI {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://www.omdbapi.com/")
.build()
.create(MyAPI::class.java)
}
}
}

Related

How can i transcribe a URL to android studio? [duplicate]

I use Retrofit for most of my calls but in one of the cases, I have the full path provided in arguments. My URL is like this http://www.example.com/android.json. This URL is provided in full so I have to path it at runtime. I implement endpoint as suggested here
https://medium.com/#kevintcoughlin/dynamic-endpoints-with-retrofit-a1f4229f4a8d
but in the #GET I need to be able to put #GET(""). This does not work as I get an error saying I should provide at least one "/".
If I add the slash the URL becomes http://www.example.com/android.json/ and it does not work, the server returns forbidden. I also tried creating a custom GET interface similar to here https://github.com/square/retrofit/issues/458 but with GET and without providing a value method in the interface. Then I get another error saying value is missing.
Basically I need to be able to provide an empty or null value but retrofit does not allow that. How could I solve this problem? For now I am doing the JSON request manually but is there a way I could use retrofit for this case? I need to pass the full URL there is no way I can do endpoint http://www.example.com and #GET("/android.json").
Thanks
You can use #GET(".") to indicate that your url is the same as the base url.
#GET(".")
Observable<Result> getData(#Query("param") String parameter);
I've tried this approach, however didn't work for me.
Workaround for this issue is:
//Retrofit interface
public interface TestResourceClient {
#GET
Observable<Something> getSomething(#Url String anEmptyString);
}
//client call
Retrofit.Builder().baseUrl("absolute URL").build()
.create(TestResourceClient.class).getSomething("");
The downside of this solution is that you have to supply empty string in getSomething("") method call.
I face the same problem with Retrofit 2. Using #GET, #GET("") and #GET(".") not solved my problem.
According to the official document you can the same baseUrl and #GET argument.
Endpoint values may be a full URL.
Values that have a host replace the host of baseUrl and values also with a scheme replace the scheme of baseUrl.
Base URL: http://example.com/
Endpoint: https://github.com/square/retrofit/
Result: https://github.com/square/retrofit/
So in my case:
interface MyAPI {
#GET("http://www.omdbapi.com/")
suspend fun getMovies(
#Query("apikey") apikey: String,
#Query("s") s: String
): Response<MoviesResponse>
companion object {
operator fun invoke(): MyAPI {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://www.omdbapi.com/")
.build()
.create(MyAPI::class.java)
}
}
}

Inject retrofit using dagger using `base_url` from firebase remote config

I know that we can inject retrofit using dagger in the following manner when the BASE_URL is fixed:
#Provides
#Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson)).build();
return retrofit;
}
#Provides
#Singleton
ApiCallInterface provideApiCallInterface(Retrofit retrofit) {
return retrofit.create(ApiCallInterface.class);
}
But, the problem here is my BASE_URL comes from firebase remote config, and I can obtain that only after runtime which is too late as dagger will initialize retrofit before that. How can this problem be solved? All the tutorials and examples that I see, work with static base urls and hence dont encounter this problem.
I am using remoteconfig for my base url so that I can control the app environment without pushing in app updates.
Thanks in advance.
According to Retrofit docs
About base_URL:
Endpoint values may be a full URL.
Values which have a host replace the host of baseUrl and values also
with a scheme replace the scheme of baseUrl.
Examples:
Base URL: http://example.com/
Endpoint: https://github.com/square/retrofit/
Result: https://github.com/square/retrofit/
So if endpoint's URL holds full URL, base URL will be replaced|ignored
About GET-annotation (the same with POST):
A relative or absolute path, or full URL of the endpoint. This value
is optional if the first parameter of the method is annotated with
#Url.
So with #URL parameter of your endpoint and empty GET parameter you can set full URL in runtime with parameter #URL of your endpoint
So try with something like this in your endpoint:
#GET()
fun getData(#Url url : String): Call<SomeResult>
and set parameter of method (full URL) in runtime
I found a workaround by leaving the Firebase Remote Config version to 17.0.0 and using the FirebaseRemoteConfig#setDefaults function.
build.gradle
implementation 'com.google.firebase:firebase-config:17.0.0'
Class. Note the setConfigSettingsAsync is still async, setDefaults stays sync.
private final FirebaseRemoteConfig mFirebaseRemoteConfig;
mFirebaseRemoteConfig.setConfigSettingsAsync(new FirebaseRemoteConfigSettings.Builder...);
mFirebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults);
mFirebaseRemoteConfig.fetchAndActivate().addOnCompleteListener(...)
In addition to the answer above by sergiy tikhonov you can also use an interceptor to intercept your requests and swap the host with your firebase remote config's BASE_URL.
See this gist for an example.

How to resolve "#Url cannot be used with #PUT URL (parameter #1)"

I want to use #PUT and #Url together, but it throws an IllegalArgumentException
Edit
#PUT
fun editPost(
#Url() url: String = "xxxx/threads/{tid}",
#Path("tid") postId:Long,
#Body x: X
)
This answer is based on the assumption of you using Retrofit Library to make a API call. If that's not the case, my apologies and let me know so I can modify/remove the answer.
From what I've researched, you've probably implemented your interface method like the following:
#PUT("")
Call...
With this call, you should encounter java.lang.IllegalArgumentException: Missing either #GET URL or #Url parameteras you've not provided the addition parameter which is needed to complete the API call.
Therefore, you must keep the base URL in the mainActivity where you'll be making the call and route of the API within the bracket of the interface
#PUT("user/id")
or if you want to keep it blank as original, you must use #PUT(".") as this will declare that your final URL is the same as the Base URL provided in the mainActivity.
You were very close...
#PUT("xxxx/threads/{tid}"
fun editPost(
#Path("tid") postId: Long,
#Body x: X
)

Using #Path and #Url paramers together with retrofit2

I'm using retrofit2 to comunicate with a webapi.
I need to set the URL of the webapi dynamically beacuase the user can change it, so i use the #Url annotation to specify it like this:
#POST
Call<LoginResponse> login(#Url String url, #Body LoginRequest user);
In one of the operations, i need to send some path parameters to the server, so i do this:
#GET
Call<DataResponse> getOrder(#Url String url,
#Header(WEBAPI_EMAIL_HEDER) String email,
#Header(WEBAPI_TOKEN_ID_HEDER) String token,
#Path("id") int id);
When i call the operation getOrder(...), an exception is rised by retrofit because i am not suppoused to use #Url and #Path parameters in the same operation.
This is the exception:
java.lang.IllegalArgumentException: #Path parameters may not be used with #Url. (parameter #4)
One solution is to replace the id parameter on the url and use only the #Url parameter in the invokation. But i think this is not the best way, beacuase i will be doing this with all the operations with #Path parameters.
Is there any other cleaner solution? Maybe using another retrofit2 annotation?
Thanks in advance.
As described in the post Retrofit 2 - Dynamic URL, the #Url notation assumes that the String is a fully defined URL and does not check whether it contains #Path variables.
To use the #Path annotation, you have to configure an endpoint URL and include the #Path variable inside the #GET() annotation.
There is a workaround. Incase of a dynamic Url with some variable path, we can define a string format with paths denoted by %s arguments.
E.g:
Suppose the dynamic url with path is : https://www.example.com/users/{id}/whoami
Here we can just replace {id} with %s. So now it becomes,
val formatString = https://www.example.com/users/%s/whoami
Now we can use it as a format string and replace it with required id.
val url = formatString.format(id)
and in the retrofit interface, use #Url parameter in the function.
interface AnyService {
fun whoAmI(#Url url:String): Call<ResponseBody>
}
Incase you are using MVVM architecture, you can call the formatting code in the concerned repository.

Why is Retrofit adding a trailing slash to all URLs?

Editing question with more details :
I understand the use of service interfaces in Retrofit. I want to make a call to a URL like this :
http://a.com/b/c (and later append query parameters using a service interface).
My limitations are :
I cannot use /b/c as a part of service interface (as path parameter). I need it as a part of base url. I have detailed the reason below.
I cannot afford to have a resultant call being made to http://a.com/b/c/?key=val. What I need is http://a.com/b/c?key=val (the trailing slash after "c" is creating problems for my API). More details below.
My Server API changes pretty frequently, and I am facing trouble on the client side using Retrofit. The main problem is that we cannot have dynamic values (non final) passed to #GET or #POST annotations for Path Parameters (like it is possible for query parameters). For example, even the number of path parameters change when the API changes. We cannot afford to have different interfaces everytime the API changes.
One workaround to this is by forming the complete URLs, that is, an Endpoint with Base_Url + Path_Parameters.
But I am wondering why is Retrofit forcibly adding a trailing slash ("/") to the base url :
String API_URL = "https://api.github.com/repos/square/retrofit/contributors";
if (API_URL.endsWith("/")) {
API_URL = API_URL.substring(0, API_URL.length() - 1);
}
System.out.println(API_URL); //prints without trailing "/"
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(API_URL)
.build();
API_URL is always being reset to https://api.github.com/repos/square/retrofit/contributors/ by Retrofit internally (confirmed this by logging the request)
One workaround to this is by manually adding a "?" in the end to prevent "/" to be added: https://api.github.com/repos/square/retrofit/contributors?
Unfortunately, such request won't be accepted by our API.
Why is Retrofit forcing this behavior ?
Is there a solution for people like me who don't want a trailing slash ?
Can we have variable parameters (non final) being passed to Retrofit #GET or #POST annotations ?
You're expected to pass the base URL to the setEndpoint(...) and define /repos/... in your service interface.
A quick demo:
class Contributor {
String login;
#Override
public String toString() {
return String.format("{login='%s'}", this.login);
}
}
interface GitHubService {
#GET("/repos/{organization}/{repository}/contributors")
List<Contributor> getContributors(#Path("organization") String organization,
#Path("repository") String repository);
}
and then in your code, you do:
GitHubService service = new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.build()
.create(GitHubService.class);
List<Contributor> contributors = service.getContributors("square", "retrofit");
System.out.println(contributors);
which will print:
[{login='JakeWharton'}, {login='pforhan'}, {login='edenman'}, {login='eburke'}, {login='swankjesse'}, {login='dnkoutso'}, {login='loganj'}, {login='rcdickerson'}, {login='rjrjr'}, {login='kryali'}, {login='holmes'}, {login='adriancole'}, {login='swanson'}, {login='crazybob'}, {login='danrice-square'}, {login='Turbo87'}, {login='ransombriggs'}, {login='jjNford'}, {login='icastell'}, {login='codebutler'}, {login='koalahamlet'}, {login='austynmahoney'}, {login='mironov-nsk'}, {login='kaiwaldron'}, {login='matthewmichihara'}, {login='nbauernfeind'}, {login='hongrich'}, {login='thuss'}, {login='xian'}, {login='jacobtabak'}]
Can we have variable parameters (non final) being passed to Retrofit #GET or #POST annotations ?
No, values inside (Java) annotations must be declared final. However, you can define variable paths, as I showed in the demo.
EDIT:
Note Jake's remark in the comments:
Worth noting, the code linked in the original question deals with the case when you pass https://api.github.com/ (note the trailing slash) and it gets joined to /repos/... (note the leading slash). Retrofit forces leading slashes on the relative URL annotation parameters so it de-dupes if there's a trailing slash on the API url.

Categories

Resources