How to address multiple API end points using Retrofit? - android

In my Android project I am using the following Retrofit ApiModule for one API end point. Please note, I use Dagger for injecting dependencies.
#Module(
complete = false,
library = true
)
public final class ApiModule {
public static final String PRODUCTS_BASE_URL = "https://products.com";
#Provides
#Singleton
Endpoint provideEndpoint() {
return Endpoints.newFixedEndpoint(PRODUCTS_BASE_URL);
}
#Provides
#Singleton
ObjectMapper provideObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
return objectMapper;
}
#Provides
#Singleton
RestAdapter provideRestAdapter(
Endpoint endpoint, ObjectMapper objectMapper) {
return new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.NONE)
.setEndpoint(endpoint)
.setConverter(new JacksonConverter(objectMapper))
.build();
}
#Provides
#Singleton
ProductsService provideProductsService(RestAdapter restAdapter) {
return restAdapter.create(ProductsService.class);
}
}
Now, there is another API (e.g. "http://subsidiaries.com") which I want to communicate with. Is it possible to extend the given ApiModule while reusing the ObjectMapper and the RestAdapter? Or should I not extend it? I already tried to duplicate the module. But this involves that I have to duplicate the Endpoint, ObjectMapper and ... the RestAdapter has a private contructor - so I can't.

I guess you could work with Named annotations:
#Module(
complete = false,
library = true
)
public final class ApiModule {
public static final String PRODUCTS_BASE_URL = "https://products.com";
public static final String SUBSIDIARIES_BASE_URL = "https://subsidiaries.com";
public static final String PRODUCTS = "products";
public static final String SUBSIDIARIES = "subsidiaries";
#Provides
#Singleton
#Named(PRODUCTS)
Endpoint provideProductsEndpoint() {
return Endpoints.newFixedEndpoint(PRODUCTS_BASE_URL);
}
#Provides
#Singleton
#Named(SUBSIDIARIES)
Endpoint provideSubsidiariesEndpoint() {
return Endpoints.newFixedEndpoint(SUBSIDIARIES_BASE_URL);
}
#Provides
#Singleton
ObjectMapper provideObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
return objectMapper;
}
#Provides
#Singleton
#Named(PRODUCTS)
RestAdapter provideProductsRestAdapter(#Named(PRODUCTS) Endpoint endpoint, ObjectMapper objectMapper) {
return newRestAdapterBuilder(objectMapper)
.setEndpoint(endpoint)
.build();
}
#Provides
#Singleton
#Named(SUBSIDIARIES)
RestAdapter provideSubsidiariesRestAdapter(#Named(SUBSIDIARIES) Endpoint endpoint, ObjectMapper objectMapper) {
return newRestAdapterBuilder(objectMapper)
.setEndpoint(endpoint)
.build();
}
#Provides
#Singleton
#Named(PRODUCTS)
ProductsService provideProductsService(#Named(PRODUCTS) RestAdapter restAdapter) {
return restAdapter.create(ProductsService.class);
}
#Provides
#Singleton
#Named(SUBSIDIARIES)
ProductsService provideSubsidiariesService(#Named(SUBSIDIARIES) RestAdapter restAdapter) {
return restAdapter.create(ProductsService.class);
}
private RestAdapter.Builder newRestAdapterBuilder(ObjectMapper objectMapper) {
return new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.NONE)
.setConverter(new JacksonConverter(objectMapper));
}
}
Now everywhere where you inject ProductsService you need to either annotate the dependency with #Named(PRODUCTS) or #Named(SUBSIDIARIES), depending on which variant you need. Of course instead of the #Named annotations you could also create your own, custom annotations and use them. See here under "Qualifiers".
To flatten your module a bit you could move the creation of the RestAdapters into the provide*Service() methods and get rid of the provide*RestAdapter() methods. Unless you need the RestAdapters as a dependency outside of the module, of course.

Related

Adding JWT authentication token to the OkHttp , Dagger 2 & Retrofit

following post Dagger + Retrofit. Adding auth headers at runtime i'm trying to configure the okHttp & adding the jwt auth key to the okHttp by adding interceptor,for this i have created separate interceptor & added it to the Dagger's component so that it can be exposed anywhere.
Now once i hit the login i get the token,setting it using the setJwtToken() method of JwtAuthenticationInterceptor class & when i'm trying with the next endpoints i'm getting 401 error since jwtToken is coming null even though i have setted it.
Below i'm attaching my interceptor,component & module code snaps.
Module
#Provides
#Singleton
OkHttpClient provideOkhttpClient(Cache cache) {
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.addInterceptor(provideHeaderInterceptor());
client.cache(cache);
return client.build();
}
#Provides
#Singleton
Retrofit provideRetrofit(OkHttpClient okHttpClient) {
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(mBaseUrl)
.client(okHttpClient)
.build();
}
#Provides
#Singleton
JwtAuthenticationInterceptor provideHeaderInterceptor(){
return new JwtAuthenticationInterceptor();
}
Component
#Component(modules = {AppModule.class, ApiModule.class, StorageModule.class})
#Singleton
public interface NetComponent {
Retrofit retrofit();
OkHttpClient okHttpClient();
SharedPreferences sharedPreferences();
Gson gson();
Cache cache();
KRITILog log();
JwtAuthenticationInterceptor headerInterceptor();
}
JwtAuthenticationInterceptor.java
#Singleton
public class JwtAuthenticationInterceptor implements Interceptor {
private String jwtToken;
#Inject
public JwtAuthenticationInterceptor() { }
public void setJwtToken(String jwtToken) {
this.jwtToken = jwtToken;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder builder = original.newBuilder()
.header("Authorization","Bearer " +jwtToken);
//String.format("Bearer %s", jwtToken));
Request request = builder.build();
return chain.proceed(request);
}
}
problem is this line
client.addInterceptor(provideHeaderInterceptor());
there you are creating a new instance of the JwtAuthenticationInterceptor, different from the one provided by dagger. JwtAuthenticationInterceptor should be a dependency of that method. Eg
#Provides
#Singleton
OkHttpClient provideOkhttpClient(Cache cache, JwtAuthenticationInterceptor interceptor) {
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.addInterceptor(interceptor);
client.cache(cache);
return client.build();
}

How to read hostname from meta-data when providing Retrofit in DI Modules?

I am using Dagger 2 + Retrofit to implement my interfaces which sends/receives data to/from my web service
I am referring Philippe BOISNEY's AppModule.java as below
private static String BASE_URL = "https://api.github.com/";
#Provides
Gson provideGson() { return new GsonBuilder().create(); }
#Provides
Retrofit provideRetrofit(Gson gson) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(BASE_URL)
.build();
return retrofit;
}
However I have a question that what if I have multiple hosts of my web services, such as Production, Staging and Development?
I already setup those different hosts connected to Build Config in my AndroidManifest.xml, but I don't have an idea how to read meta-data in AppModule, in order to replace BASE_URL with corresponding build config
Please kindly advice me, thank you
You can define in build.gradle several flavor types like dev, prod and stage and for each flavor define build config variable
productFlavors {
dev {
buildConfigField "String", "SERVER_URL", "\"your dev url\""
}
stage {
buildConfigField "String", "SERVER_URL", "\"your stage url\""
}
prod {
buildConfigField "String", "SERVER_URL", "\"your prod url\""
}
}
And after that use it
private static String BASE_URL = BuildConfig.SERVER_URL;
If you like to provide it dynamically using dagger, you can do it in that way
#Module
public class AppModule {
#Named("server_url")
#Provides
String provideServerUrl() {
return "https://api.github.com/";
}
#Provides
Retrofit provideRetrofit(Gson gson, #Named("server_url") String url) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(url)
.build();
return retrofit;
}
}
Another way of dynamically providing server url using dagger - using builder. For example,
#Component(AppModule.class)
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
Builder serverUrl(#Named("server_url") String url);
AppComponent build();
}
}
#Module
public class AppModule {
#Provides
Retrofit provideRetrofit(Gson gson, #Named("server_url") String url) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(url)
.build();
return retrofit;
}
}
DaggerAppComponent.Builder()
.appModule(new AppModule())
.serverUrl("https://api.github.com/")
.build();
I give up reading corresponding host from meta-data in AndroidManefist, instead, I switch to another approach by referring #ConstOrVar's suggestion
Here is my app/build.gradle, I have 2 flavors which describes my staging and production host respectively
productFlavors {
production {
buildConfigField("String", "SERVER_HOST", "\"myproductionost.com\"")
}
staging {
buildConfigField("String", "SERVER_HOST", "\"mystaginghost.com\"")
}
}
Then here is my codes in my AppModule.java
#Module
public class AppModule {
#Provides
Retrofit provideRetrofit(Gson gson) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("https://" + BuildConfig.SERVER_HOST)
.build();
return retrofit;
}
}
By doing so, Retrofit will tells which host of webservice it shall talk with based on BuildVariant automatically

Trying to inject to a Job class

I have a network component in my app that allows me to inject retrofit to my activities & fragments , i wanted to inject it to my Job class , here's what i did
NetComponent interface :
#Singleton
#Component(modules={AppModule.class, NetModule.class})
public interface NetComponent {
void inject(MainActivity activity);
void inject(SplashActivity activity);
void inject(RegisterActivity activity);
void inject(SettingsFragment fragment);
void inject(Context cont); // also tried void inject(Job job);
}
And in my Job Class i inject it like this :
public class LogUploader extends Job {
public static final String TAG = "UPLOAD_LOGS" ;
#Inject
Retrofit mRetrofitClient;
#Override
#NonNull
protected Result onRunJob(Params params) {
((MyApp) getContext()).getNetComponent().inject(getContext());
// run your job here
Log.e("LogFile", " "+ TAG);
//// TODO: 10/18/2017 send log
checklogs(this.getContext());
//// TODO: 10/18/2017 get phone db update
return Result.SUCCESS;
}
}
And the crash :
ClassCastException: com.evernote.android.job.v21.PlatformJobService cannot be cast to com.**.**.Application.MyApp
Any ideas what should i do differently ?
Thanks to all the helpers !
UPDATE
The first crash (CCE) was because I did getContext and cast it to MyApp , I changed it to
((MyApp) this.getContext().getApplicationContext()).getNetComponent().inject(getContext());
Now the crash makes more sense :
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object retrofit2.Retrofit.create(java.lang.Class)' on a null object reference
I checked with the debug , the inject line doesn't inject mRetrofitClient
Any ideas ?
NetModule class :
#Module
public class NetModule {
String mBaseUrl;
// Constructor needs one parameter to instantiate.
public NetModule(String baseUrl) {
this.mBaseUrl = baseUrl;
}
// Dagger will only look for methods annotated with #Provides
#Provides
#Singleton
// Application reference must come from AppModule.class
SharedPreferences providesSharedPreferences(Application application) {
return PreferenceManager.getDefaultSharedPreferences(application);
}
#Provides
#Singleton
Cache provideOkHttpCache(Application application) {
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(application.getCacheDir(), cacheSize);
return cache;
}
#Provides
#Singleton
Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE);
return gsonBuilder.create();
}
#Provides
#Singleton
OkHttpClient provideOkHttpClient(Cache cache) {
OkHttpClient client = new OkHttpClient().newBuilder().cache(cache).build();
return client;
}
#Provides
#Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(mBaseUrl)
.client(okHttpClient)
.build();
return retrofit;
}
}
Managed to solve it by changing
void inject(Context cont);
to
void inject(LogUploader lp);
and in the LogUploader to
((MyApp) this.getContext().getApplicationContext()).getNetComponent().inject(this);
I tried it before without the getApplicationContext which was the first crash , after the changes it works .
Basically the inject needs to get the class that you want to inject doesn't matter if it's Activity Fragment or any other.
The accepted answer didn't work for me, since the Job has not been created yet in the constructor, you'll get an error.
You'll need to place this code snippet in onRunJob() method before any other code.
((MyApp) this.getContext().getApplicationContext()).getNetComponent().inject(this);

Update request header when access token updates Dagger and Retrofit

I want to update access token in network request.But there is some difficulty using Dagger and Retrofit.
๐Ÿ˜ขSorry,my English is not good , so give you an example may be much clear.Starting from scratch, my idea is like this:
provide an access token saved in shared preference
#Provides
#ForOauth
Preference<String> provideAccessToken(RxSharedPreferences prefs) {
return prefs.getString(PrefsUtils.KEY_ACCESS_TOKEN);
}
use access token to create an interceptor and added into okhttp client
#Provides
#Singleton
#Named("Cached")
public OkHttpClient provideOkHttpClientWithCache(Application application, #ForOauth OauthInterceptor oauthInterceptor) {
...
builder.addInterceptor(oauthInterceptor);
...
}
and I provide the OauthInterceptor instance by its constructor
#Inject
public OauthInterceptor(#ForOauth Preference<String> accessToken) {
this.accessToken = accessToken;
Timber.tag("OauthInterceptor");
}
But cause the okhttp client is a singleton,it won't change when the access token in prefs updates.An alternative way I thought that may work is to use a custom scope like #ForOauth or something, but it's just a rough sketch...
By the way, I have another idea like this:
get the access token from prefs in the intercept() method , so every time I can have a request header which contains the latest access token.
#Override
public Response intercept(Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
if (accessToken.isSet()) {
// Preference<String> accessToken
builder.header("Authorization", ACCESS_TYPE + accessToken.get());
} else {
builder.header("Authorization", "Bearer xxxxxx");
}
return chain.proceed(builder.build());
}
But I haven't really experimented with this idea,and I think it's not right ๐Ÿ˜‚
I wonder whether I have to create a new okhttp client instance every time or I can just update the access token then the okhttp client singleton can refresh its interceptor...
So could you please give me some advice , or a simple working example.
Thanks in advance ๐Ÿ˜Š
Hmmmm, I've done this many times and never noticed any issues with the access token refresh not making its way down the chain to OkHttp. Here's a typical setup I use in apps:
#Provides #Singleton
SharedPreferences providePreferences(Context ctx) {
return new SharedPreferences(ctx);
}
#Provides #Singleton
HttpLoggingInterceptor provideLoggingInterceptor(){
return new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
}
#Provides #Singleton
OkHttpClient provideClient(HttpLoggingInterceptor interceptor, SharedPreferences prefs){
return new OkHttpClient.Builder()
.addNetworkInterceptor(chain -> {
// Add Auth Header
String token = prefs.accessToken().get();
if(token == null) token = "";
Request request = chain.request().newBuilder().addHeader("Authorization", token).build();
return chain.proceed(request);
})
.addInterceptor(interceptor)
.build();
}
#Provides #Singleton
Retrofit provideRetrofit(#ApiUrl String url, OkHttpClient client){
return new Retrofit.Builder()
.baseUrl(url)
.client(client)
.addConverterFactory(LoganSquareConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}
SharedPreferences is just a class I've abstracted some of the RxSharedPreferences logic into. Can also just #Inject it wherever you need it in the app that way too, which is nice. Here's a simple version of that class just for fun:
public class SharedPreferences {
// Constants and variables
private static final String PREFERENCE_FILENAME = BuildConfig.APPLICATION_ID + ".prefs";
private static final String PREF_ACCESS_TOKEN= "pref_access_token";
private RxSharedPreferences mRxSharedPrefs;
// Constructor
public SharedPreferences(Context context) {
mRxSharedPrefs = RxSharedPreferences.create(context.getSharedPreferences(PREFERENCE_FILENAME, Context.MODE_PRIVATE));
}
// Helper methods
public Preference<String> accessToken() { return mRxSharedPrefs.getString(PREF_ACCESS_TOKEN, ""); }
public void logout() { accessToken().delete(); }
}

What is the reasoning for separating the RestAdapter.build() and .create() methods when using Dagger?

I have been using Dagger/Retrofit for the past few months and have seen a common pattern of implementing an ApiModule class for an api. These ApiModules typically look something like this:
#Provides #Singleton Client provideClient(OkHttpClient client) {
return new OkClient(client);
}
#Provides #Singleton Endpoint provideEndpoint() {
return "release".equalsIgnoreCase(BuildConfig.BUILD_TYPE)
? Endpoints.newFixedEndpoint(PRODUCTION_URL, "Foo Production Url")
: Endpoints.newFixedEndpoint(STAGING_URL, "Foo Staging Url");
}
#Provides #Singleton Converter provideConverter(Gson gson) {
return new GsonConverter(gson);
}
#Provides #Singleton RestAdapter provideRestAdapter(Endpoint endpoint, Client client,
Converter converter) {
return new RestAdapter.Builder()
.setClient(client)
.setEndpoint(endpoint)
.setConverter(converter)
.setLogLevel(BuildConfig.DEBUG
? RestAdapter.LogLevel.FULL
: RestAdapter.LogLevel.NONE)
.build();
}
#Provides #Singleton FooApi provideFooApi(RestAdapter restAdapter) {
return restAdapter.create(FooApi.class);
}
But to clean this up why not do this:
#Provides #Singleton Client provideClient(OkHttpClient client) {
return new OkClient(client);
}
#Provides #Singleton Converter provideConverter(Gson gson) {
return new GsonConverter(gson);
}
#Provides #Singleton FooApi provideFooApi(Client client, Converter converter) {
return new RestAdapter.Builder()
.setClient(client)
.setEndpoint("release".equalsIgnoreCase(BuildConfig.BUILD_TYPE)
? Endpoints.newFixedEndpoint(PRODUCTION_URL, "Foo Production Url")
: Endpoints.newFixedEndpoint(STAGING_URL, "Foo Staging Url"))
.setConverter(converter)
.setLogLevel(BuildConfig.DEBUG
? RestAdapter.LogLevel.FULL
: RestAdapter.LogLevel.NONE)
.build()
.create(FooApi.class);
}
Is there any cons to doing it this way or am I violating some Dagger contract? I ask because there have been cases where I need to use multiple APIs within a project...setting it up like the second example above, makes that possible.
There's three reasons to do this:
By splitting the two you are creating a separation of concerns. How the RestAdapter is instantiated and put into the graph is completely separate from how the instance our your service interface is put into the graph. Here you happen to have them in the same module but there's no reason they couldn't be in separate modules or even have one in a library from a different component.
Separate providers allow you override one or both in an override module to customize the behavior without have to know about how the other is used or where it comes from.
For example, if you wanted to enable different behavior when you are running an integration test you could provide a different RestAdapter instance.
#Provides #Singleton RestAdapter provideTestRestAdapter() {
return new RestAdapter.Builder()
.setEndpoint(Endpoints.newFixedEndpoint("http://mycomputer.local/api"))
.setLogLevel(FULL)
.build();
}
Having this in an override module means you don't have to change where the service instances are being created.
Finally, and most simply, maybe you have multiple service interfaces. You shouldn't be creating multiple RestAdapter instances (unless they are for different endpoints).
#Provides #Singleton AccountService provideAccountService(RestAdapter ra) {
return ra.create(AccountService.class);
}
#Provides #Singleton TweetService provideTweetService(RestAdapter ra) {
return ra.create(TweetService.class);
}
#Provides #Singleton DirectMessageService provideDirectMessageService(RestAdapter ra) {
return ra.create(DirectMessageService.class);
}

Categories

Resources