My apiPath is fully dynamic. I am having items containing fields such us "ipAddress" and "SSLprotocol". Based on them I can build my url:
private String urlBuilder(Server server) {
String protocol;
String address = "";
if (AppTools.isDeviceOnWifi(activity)) {
address = serverToConnect.getExternalIp();
} else if (AppTools.isDeviceOnGSM(activity)) {
address = serverToConnect.getInternalIp();
}
if (server.isShouldUseSSL()) {
protocol = "https://";
} else {
protocol = "http://";
}
return protocol + address;
}
So my protocol + address can be: http:// + 192.168.0.01:8010 = http://192.168.0.01:8010
And I would like to use it like that:
#FormUrlEncoded
#POST("{fullyGeneratedPath}/json/token.php")
Observable<AuthenticationResponse> authenticateUser(
#Path("fullyGeneratedPath") String fullyGeneratedPath,
#Field("login") String login,
#Field("psw") String password,
#Field("mobile") String mobile);
So full path for authenticateUser would be http://192.168.0.01:8010/json/token.php - for example.
That means I don't need any basePath because I create whole basePath myself depending on server I want to connect to.
My retrofit setup is:
#Provides
#Singleton
Retrofit provideRetrofit(OkHttpClient okHttpClient,
Converter.Factory converterFactory,
AppConfig appConfig) {
Retrofit.Builder builder = new Retrofit.Builder();
builder.client(okHttpClient)
.baseUrl(appConfig.getApiBasePath())
.addConverterFactory(converterFactory)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create());
return builder.build();
}
If I remove baseUrl then I get error that this parameter is required. So I set my apiBasePath to:
public String getApiBasePath() {
return "";
}
And then I get error instantly after I create retrofit instance:
java.lang.IllegalArgumentException: Illegal URL:
How to solve it?
From source (New URL resolving concept) you can simply specify whole path in post request.
Moreover we also can declare a full URL in #Post in Retrofit 2.0:
public interface APIService {
#POST("http://api.nuuneoi.com/special/user/list")
Call<Users> loadSpecialUsers();
}
Base URL will be ignored for this case.
just use like this
public interface UserService {
#GET
public Call<ResponseBody> profilePicture(#Url String url);
}
source
With Retrofit 2 you have to put base url anyway. If it is not known then you just can put any url, usually, it is good to use http://localhost/.
retrofit version :2.9.0 | gson version / okhttp : auto-set by retrofit
The original solution did not worked for me, as my query was using GET . But here's something that might work for you :
interface DownloaderApi {
#GET(".")
fun getData(): Call<List<TeacherDto>>
}
and
class TeacherDataDownloader {
private val downloaderApi: DownloaderApi
init {
val convertor = Gson()
val convertorFactory = GsonConverterFactory.create(convertor)
val retrofit =
Retrofit.Builder().baseUrl(FULL_URL).addConverterFactory(convertorFactory).build()
downloaderApi = retrofit.create(DownloaderApi::class.java)
}
fun getListOfTeachersSync(): List<TeacherDto>? = downloaderApi.getData().execute().body()
}
This solution works for me partially , because some of the links that I use ends with / (eg: const val FULL_URL = "https://jsonplaceholder.typicode.com/users/" ). However this solution only works for APIs ending with a /
For URLS not ending with a / (eg : github gists) , you have to use the default methods and pass the end-path as parameter.
If you're okay with creating a new RestAdapter instance each time you want to make an api call, you could create a new adapter with the base url as a paramter.
Using absolute URLs with Retrofit
Yes you can do. I have done like this:
public APIHelper(BaseActivity activity) {
String url = Common.getAPIUrl();
RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(url)
.build();
methods = restAdapter.create(IAppService.class);
}
Basically you need to create object of retrofit once you have known the endpoint url. I guess no more explanation is needed as code is self explanatory.
Assuming that only base url is getting changed.
My way to use retrofit
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.okhttp3:okhttp:3.10.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.10.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.1.8'
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
And here i have a generic class and inside i have my method
public static AppServiceApi setMyRetrofit(){
Retrofit retrofit = null;
AppServiceApi appServices = null;
if (retrofit == null){
Gson gson = new GsonBuilder()
.setLenient()
.create(); //
retrofit = new Retrofit.Builder().baseUrl(BASE_URL).client(getClient())
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
appServices = retrofit.create(AppServiceApi.class);
}
return appServices;
}
public static OkHttpClient getClient(){
HttpLoggingInterceptor interceptor=new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
/*.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)*/.build();
return client;
}
where AppServiceApi interface have all my end urls and method type
Related
so basically am using retrofit to get data from an api called calorieNinja and for some reason i keep getting an unsuccessful response
here is the retrofit code :
retrofit = new Retrofit.Builder().baseUrl("https://api.calorieninjas.com/v1/")
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiCalorieNinjas apiCalorieNinjas = retrofit.create(ApiCalorieNinjas.class);
Call<MealCalories> call = apiCalorieNinjas.getMeal("nutrition?query= 5 eggs");
call.enqueue(new Callback<MealCalories>() {
#Override
public void onResponse(Call<MealCalories> call, Response<MealCalories> response) {
if(!response.isSuccessful()){
Toast.makeText(getContext(),"Not Found",Toast.LENGTH_SHORT).show();
return;
}
mealEaten = response.body();
Meal meal1 = new Meal(mealEaten.getName(),mealEaten.getCalories(),mealEaten.getProtein_g(),mealEaten.getCarbohydrates_total_g());
mealViewModel.insertMeal(meal1);
}
#Override
public void onFailure(Call<MealCalories> call, Throwable t) {
}
});
}
});
btw am using 2 different types of meal objects because one is responsible of getting the data from the api and one is used as an entity for Room databse and they dont have the same parameters so instead of just adding #Ignore i decided to use two different objects while i try fixing this problem.
and here is the interface of it :
public interface ApiCalorieNinjas {
#Headers("X-Api-Key: PiQfBb0CZy2GfOZWjWyj6Tg==EdGjoESjqxQh1q4M")
#GET("{meal}")
public Call<MealCalories> getMeal(#Path("meal") String meal);
the api key isnt real!
if additional code is needed please let me know!
Try to add an interceptor so you can see all calls logs (headers, body, URLs, etc...) and see what it's the error that the API is sending.
Add OkHtpp to your grade dependencies:
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2"
implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2"
And after that, when you create your Retrofit instance, add the interceptor, should look something like this:
val httpClient = OkHttpClient.Builder()
val interceptor = HttpLoggingInterceptor()
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
httpClient.addInterceptor(interceptor)
httpClient.addInterceptor(Interceptor { chain: Interceptor.Chain ->
val original: Request = chain.request()
val request: Request = original.newBuilder()
.header("Content-Type", "application/json")
.method(original.method, original.body)
.build()
chain.proceed(request)
})
val okHttpClient = httpClient.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.calorieninjas.com/v1/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
I am learnng how to use retrofit library,however I come across a little problem.
So I set everything and run my project but first I git this error:
Caused by: java.lang.IllegalArgumentException: baseUrl must end in /:
So I added the "/" but then I realized that its more than that, and I should leave just the baseUrl and add the api to the interface I created.
I tried to add the api in diffrent ways but I didn't manage to do it.
Here are some codes:
Retrofit BaseUrl:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.nytimes.com/svc/movies/v2/critics/full-time.json?api-key=abcdefghijklmnop")
.addConverterFactory(GsonConverterFactory.create())
.build();
At first I used it like above,but after some research I discovered that I have to leave the baseUrl and pass the other half of the address through the interface.
Interface -
public interface ConnectAPI {
#GET("results")
Call<List<Reviewers>> getReviewers();
}
I will be glad for some help,
Thanks !
As Gabe Sechan said, when working with Retrofit you need to set a base url that will be the same for all api calls and then append the rest of the url on a per endpoint basis. If you change your code to the below you should be good
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.nytimes.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
and getReviewers() to
public interface ConnectAPI {
#GET("svc/movies/v2/critics/full-time.json?api-key=abcdefghijklmnop")
Call<List<Reviewers>> getReviewers();
}
Base URL should be the root of all the queries you want to make on that interface. The rest should be part of the URL on the actual API interface. So for your example, base URL should probably be "https://api.nytimes.com/svc/movies/v2/". Although any subset of that, such as "https://api.nytimes.com/" would also work as long as the interface has all the rest of the path. Basically when the actual HTTP request is made, the URL of the query is concatenated to the end of the base url.
//ApiClient class for BaseUrl(Retrofit)
public class ApiClient {
private static String BASE_URL="";
private static OkHttpClient getOkHttpClient(){
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(logging).addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
HttpUrl originalHttpUrl = original.url();
HttpUrl url = originalHttpUrl.newBuilder().build();
Request.Builder requestBuilder = chain.request().newBuilder()
.addHeader("Content-Type","application/json")
.addHeader("timezone", TimeZone.getDefault().getID())
.url(url);
Request request = requestBuilder.build();
Response response = chain.proceed(request);
return response;
}
});
return httpClient.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
}
private static Retrofit.Builder retrofitBuilder=new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit=retrofitBuilder.build();
private static ApiService apiService=retrofit.create(ApiService.class);
public static ApiService getApiService(){
return apiService;
}
}
I'm wondering if there is a way for Dagger to know that it should recreate an object when new data is available.
The instance I am speaking of is with the request headers I have for retrofit. At some point (when the user logs in) I get a token that I need to add to the headers of retrofit to make authenticated requests. The issue is, I'm left with the same unauthenticated version of retrofit. Here's my injection code:
#Provides
#Singleton
OkHttpClient provideOkHttpClient(Cache cache) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.cache(cache).build();
client
.newBuilder()
.addInterceptor(
chain -> {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.addHeader("Accept", "Application/JSON");
Request request = requestBuilder.build();
return chain.proceed(request);
}).build();
return client;
}
#Provides
#Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create())
.baseUrl(mBaseUrl)
.client(okHttpClient)
.build();
return retrofit;
}
#Provides
#Singleton
public NetworkService providesNetworkService(Retrofit retrofit) {
return retrofit.create(NetworkService.class);
}
Any ideas on how to make this work?
I personally created an okhttp3.Interceptor that does that for me, which I update once I have the required token. It looks something like:
#Singleton
public class MyServiceInterceptor implements Interceptor {
private String sessionToken;
#Inject public MyServiceInterceptor() {
}
public void setSessionToken(String sessionToken) {
this.sessionToken = sessionToken;
}
#Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder requestBuilder = request.newBuilder();
if (request.header(NO_AUTH_HEADER_KEY) == null) {
// needs credentials
if (sessionToken == null) {
throw new RuntimeException("Session token should be defined for auth apis");
} else {
requestBuilder.addHeader("Cookie", sessionToken);
}
}
return chain.proceed(requestBuilder.build());
}
}
In the corresponding dagger component, I expose this interceptor so I can set the sessionToken when I need to.
That is some stuff that Jake talked about it his talk Making Retrofit Work For You.
Please consider using the approach mentioned by #oldergod as it is the "official" and much better way, whereas the approaches mentioned below are not advised, they may be considered as workarounds.
You have a couple of options.
As soon as you get the token, you have to null out the component that provided you the Retrofit instance, create a new component and ask for a new Retrofit instance, which will be instantiated with necessary okhttp instance.
A fast and bad one - Save the token in SharedPreferences, create okHttp header, which will apply token reading from SharedPreferences. If there is none - send no token header.
Even uglier solution - declare a static volatile String field, and do the same thing like in step 2.
Why the second option is bad? Because on each request you would be polling disk and fetch data from there.
Created custom RequestInterceptor with #Inject constructor
RequestInterceptor
#Singleton
class
RequestInterceptor #Inject constructor(
private val preferencesHelper: PreferencesHelper,
) : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
var newRequest: Request = chain.request()
newRequest = newRequest.newBuilder()
.addHeader(
"AccessToken",
preferencesHelper.getAccessTokenFromPreference()
)
.build()
Log.d(
"OkHttp", String.format(
"--> Sending request %s on %s%n%s",
newRequest.url(),
chain.connection(),
newRequest.headers()
)
);
return chain.proceed(newRequest)
}
ApplicationModule
#Module(includes = [AppUtilityModule::class])
class ApplicationModule(private val application: AppController) {
#Provides
#Singleton
fun provideApplicationContext(): Context = application
#Singleton
#Provides
fun provideSharedPreferences(): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(application.applicationContext)
}
PreferencesHelper
#Singleton
class PreferencesHelper
#Inject constructor(
private val context: Context,
private val sharedPreferences: SharedPreferences
) {
private val PREF_KEY_ACCESS_TOKEN = "PREF_KEY_ACCESS_TOKEN"
fun getAccessTokenFromPreference(): String? {
return sharedPreferences.getString(PREF_KEY_ACCESS_TOKEN, null)
}
}
Well tested and working
public OkHttpClient getHttpClient(Context context) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
return new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.callTimeout(60,TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.addInterceptor(logging)
.addInterceptor(chain -> {
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + Utility.getSharedPreferencesString(context, API.AUTHORIZATION))
.build();
return chain.proceed(newRequest);
})
.build();
}
Earlier I was wondering, if session expires and user login again, will this interceptor replace the existing auth, but fortunately it is working fine.
I'm just doing a GET request, but I'm getting this error:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.yomac_000.chargingpoint/com.example.yomac_000.chargingpoint.AllStores}: java.lang.IllegalArgumentException: Unable to create converter for java.util.List
And it's because of this line of code:
Call<List<Store>> call = subpriseAPI.listStores(response);
So I had tried with this line of code to see what type it is:
System.out.println(subpriseAPI.listStores(response).getClass().toString());
But then I get the same error so it doesn't let me know what type it is. Here below you can see my code.
StoreService.java:
public class StoreService {
public static final String BASE_URL = "http://getairport.com/subprise/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.build();
SubpriseAPI subpriseAPI = retrofit.create(SubpriseAPI.class);
String response = "";
public List<Store> getSubprises() {
Call<List<Store>> call = subpriseAPI.listStores(response);
try {
List<Store> listStores = call.execute().body();
System.out.println("liststore "+ listStores.iterator().next());
return listStores;
} catch (IOException e) {
// handle errors
}
return null;
}
}
SubpriseAPI.java:
public interface SubpriseAPI {
#GET("api/locations/get")
Call<List<Store>> listStores(#Path("store") String store);
}
Store.java:
public class Store {
String name;
}
I'm using Retrofit version 2.0.0-beta2.
In the 2+ version you need to inform the Converter
CONVERTERS
By default, Retrofit can only deserialize HTTP bodies into OkHttp's
ResponseBody type and it can only accept its RequestBody type for
#Body.
Converters can be added to support other types. Six sibling modules
adapt popular serialization libraries for your convenience.
Gson: com.squareup.retrofit:converter-gson Jackson: com.squareup.retrofit:converter-jackson
Moshi: com.squareup.retrofit:converter-moshi
Protobuf: com.squareup.retrofit:converter-protobuf
Wire: com.squareup.retrofit:converter-wire
Simple XML: com.squareup.retrofit:converter-simplexml
// Square libs, consume Rest API
compile 'com.squareup.retrofit:retrofit:2.0.0-beta1'
compile 'com.squareup.okhttp:okhttp:2.4.0'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta1'
So,
String baseUrl = "" ;
Retrofit client = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
public interface SubpriseAPI {
#GET("api/locations/get")
Call<List<Store>> listStores(#Path("store") String store);
}
you declared a #Path called store, so in your #GET annotation retrofit is expecting to find the placeholder for the substitution. E.g.
#GET("api/locations/{store}")
Call<List<Store>> listStores(#Path("store") String store);
I am using OAuth and I need to put the OAuth token in my header every time I make a request. I see the #Header annotation, but is there a way to make it parameterized so i can pass in at run time?
Here is the concept
#Header({Authorization:'OAuth {var}', api_version={var} })
Can you pass them in at Runtime?
#GET("/users")
void getUsers(
#Header("Authorization") String auth,
#Header("X-Api-Version") String version,
Callback<User> callback
)
Besides using #Header parameter, I'd rather use RequestInterceptor to update all your request without changing your interface. Using something like:
RestAdapter.Builder builder = new RestAdapter.Builder()
.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
request.addHeader("Accept", "application/json;versions=1");
if (isUserLoggedIn()) {
request.addHeader("Authorization", getToken());
}
}
});
p/s : If you are using Retrofit2, you should use Interceptor instead of RequestInterceptor
Since RequestInterceptor is not longer available in Retrofit 2.0
Yes, you can pass them in runtime. As a matter of fact, pretty much exactly as you typed it out. This would be in your API interface class, named say SecretApiInterface.java
public interface SecretApiInterface {
#GET("/secret_things")
SecretThing.List getSecretThings(#Header("Authorization") String token)
}
Then you pass the parameters to this interface from your request, something along those lines: (this file would be for example SecretThingRequest.java)
public class SecretThingRequest extends RetrofitSpiceRequest<SecretThing.List, SecretApiInteface>{
private String token;
public SecretThingRequest(String token) {
super(SecretThing.List.class, SecretApiInterface.class);
this.token = token;
}
#Override
public SecretThing.List loadDataFromNetwork() {
SecretApiInterface service = getService();
return service.getSecretThings(Somehow.Magically.getToken());
}
}
Where Somehow.Magically.getToken() is a method call that returns a token, it is up to you where and how you define it.
You can of course have more than one #Header("Blah") String blah annotations in the interface implementation, as in your case!
I found it confusing too, the documentation clearly says it replaces the header, but it DOESN'T!
It is in fact added as with #Headers("hardcoded_string_of_liited_use") annotation
Hope this helps ;)
The accepted answer is for an older version of Retrofit. For future viewers the way to do this with Retrofit 2.0 is using a custom OkHttp client:
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Builder ongoing = chain.request().newBuilder();
ongoing.addHeader("Accept", "application/json;versions=1");
if (isUserLoggedIn()) {
ongoing.addHeader("Authorization", getToken());
}
return chain.proceed(ongoing.build());
}
})
.build();
Retrofit retrofit = new Retrofit.Builder()
// ... extra config
.client(httpClient)
.build();
Hope it helps someone. :)
Retrofit 2.3.0
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
okHttpClientBuilder
.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder newRequest = request.newBuilder().header("Authorization", accessToken);
return chain.proceed(newRequest.build());
}
});
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(GithubService.BASE_URL)
.client(okHttpClientBuilder.build())
.addConverterFactory(GsonConverterFactory.create())
.build();
I am using this to connect to GitHub.