I'd like to be able to inject a new OkHttpClient using Dagger2 but am having a hard time. Most of the tutorials and instructions are written for applications and I'm building an aar (android library).
here's my Component:
#Singleton
#Component(modules = {NetworkModule.class})
public interface AndroidSdkComponent {
void inject(OkHttpClient httpClient);
}
here's my module:
#Module
public class NetworkModule {
#Provides
#Singleton
OkHttpClient provideOkHttpClient(Context context) {
final OkHttpClient client = new OkHttpClient();
if (Configuration.isAlphaBuild(context)) {
client.networkInterceptors().add(new StethoInterceptor());
}
return client;
}
}
At an entry point of my library I build the component like so:
DaggerAndroidSdkComponent.builder().networkModule(new NetworkModule())
.build();
but later when I'm trying to #Inject OkHttpClient okHttpClient it appears to always be null. What am I doing wrong?
What I'm I doing wrong?
void inject(OkHttpClient httpClient);
This will inject an OkHttpClient with the dependencies it needs...But since you neither call it, nor have access to the fields of the okhttp client anyways this method is redundant and useless.
DaggerAndroidSdkComponent.builder().networkModule(new NetworkModule())
.build();
This will build a component. It will build it. You still need to use it. e.g. call inject() and inject the object you want to inject.
What you need to do is update your interface to actually inject the object where you want the OkHttpClient in. You don't provide any information of where you want that client to be used, but you should have SomeClass like the following, where you already build the component with the code you provided above:
class SomeClass {
#Inject
OkHttpClient mClient(); // THIS is how you mark a field for injection
void someMethod() {
AndroidSdkComponent component = DaggerAndroidSdkComponent.builder().networkModule(new NetworkModule())
.build();
component.inject(this); // THIS is how you inject a field (okhttp!)
}
}
Then you need to update the component to know about SomeClass that you want to be injected:
#Singleton
#Component(modules = {NetworkModule.class})
public interface AndroidSdkComponent {
void inject(SomeClass someclass); // THIS will inject okhttp to your someclass object, as seen above
}
Please read some more tutorials about dagger and try playing with it, since it seems that you don't really understand how it works or what the different classes do. The User's Guide is a good place to start.
Related
I have created below module in my android application.
val appNetworkModule = module {
// Dependency: OkHttpClient
single {
var okHttpClient=OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.addInterceptor(get<Interceptor>("LOGGING_INTERCEPTOR"))
.addInterceptor(get<Interceptor>("OK_HTTP_INTERCEPTOR"))
.build()
}
}
Now, I have to clear all the previous api calls while doing Logout from the app.
So, I need to access the variable used in here as above named : okHttpClient
I am trying to access it as below to cancel all the previous api calls in my main activity:
appNetworkModule.okHttpClient.dispatcher.cancelAll()
But, okHttpClient is not accessible.
What might be the issue?
I might be late here, but the correct answer is that since it is defined in the module (Provided that you are using koin as a DI library). We need to inject it in our required class (where we need to call the okHttpClient) either using a constructor injection or using a field injection.
So our code will be something like this.
Using Construction Injection: Make sure your Class TestClass gets the injected okHttpClient for example define the TestClass in the module like this single { TestClass(get()) }
class TestClass(private val okHttpClient: OkHttpClient) {
fun clearAllApis(){
okHttpClient.dispatcher.cancelAll()
}
}
Using field injection: If you need to use field injection instead
class TestClass {
private val okHttpClient : OkHttpClient by inject() //If it is extended from Android platform class like AppCompatActivity, Fragment etc. or a KoinComponent extended class
private val okHttpClient : OkHttpClient by inject(OkHttpClient::class.java) //Otherwise
fun clearAllApis(){
okHttpClient.dispatcher.cancelAll()
}
}
Make sure .inject() is from org.koin.android.ext.android.inject
I have a set of #Singleton and #Provides method in my module class for the purpose of creating Singleton instance throughout the application. Everything works fine except few bottle neck scenarios like as follows:
STEP 1. I am creating a Retrofit instance from OKHttpClient with Auth token in it to make a authenticated api calls each time (Auth token retrieval and insertion is handled through SharedPreferences). But the problem starts at the time of relaunching the activity after when i logout the application by clearing databases and shared preferences values.
STEP 2. After logout, am making an another request to fetch auth tokens and inserting into SharedPreferences again for future use.
STEP 3: Now if i proceed with the rest of api calls, the previous instance of the Dagger #Singleton and #Provides method remains same unless and until if i relaunch the app by clearing it from the recent task. (New auth token is not updated)
Fixes Needed:
How to trigger the Dagger provider methods forcibly to trigger or revoke it again?
Is there any method to refresh the application class data as similar behaviour like when the app relaunches.?
Please find my Dagger 2 architecture used in my project:
NetworkModule.java (Dagger Module class)
#Module
public class NetworkModule {
private Context context;
public NetworkModule(Application app) {
this.context = app;
}
#Provides
#Named("network.context")
Context providesContext() {
return context;
}
#Singleton
#Provides
OkHttpClient providesOkHttpClient(#Named("network.context")final Context context) {
final UserProfile userProfile = GsonUtils.createPojo(SharedPrefsUtils.getString(Constants.SHARED_PREFS.USERS_PROFILE, "",context), UserProfile.class);
Logger.i(userProfile != null && !TextUtils.isEmpty(userProfile.getAuth_token()) ? userProfile.getAuth_token() : "----OAuth token empty---");
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.header("Api-Version", "application/vnd.addo-v1+json")
.header("Access-Token", userProfile != null && !TextUtils.isEmpty(userProfile.getAuth_token()) ? userProfile.getAuth_token() : "")
.header("App-Version", Utils.getVersionName(context))
.header("Device-Platform","android")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
});
return httpClient.build();
}
#Provides
#Named(Constants.INJECTION.BASE_URL)
String providebaseURL() {
return Constants.URL.BASE_URL;
}
#Singleton
#Provides
Retrofit providesRetrofit(#Named("network.context")Context context, #Named(Constants.INJECTION.BASE_URL) String baseURL, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseURL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(okHttpClient)
.build();
return retrofit;
}
#Singleton
#Provides
NetworkApiService providesNetworkApiService(Retrofit retrofit){
return retrofit.create(NetworkApiService.class);
}
#Singleton
#Provides
ProjectPresenter providesProjectPresenter(NetworkApiService networkApiService){
return new ProjectPresenterImpl(networkApiService);
}
}
AppComponent.java (Dagger component class)
#Singleton
#Component(modules = {NetworkModule.class})
public interface AppComponent {
//ACtivity
void inject(AuthenticationActivity authenticationActivity);
void inject(MainActivity mainActivity);
//Fragments
void inject(ProjectsListFragment projectsListFragment);
}
Application.java (Class used to create Dagger component)
public class Application extends Application {
private AppComponent appComponent ;
#Override
public void onCreate() {
super.onCreate();
Realm.init(this);
ButterKnife.setDebug(BuildConfig.DEBUG);
appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).networkModule(new NetworkModule(this)).build();
}
public AppComponent getAppComponent() {
return appComponent;
}
}
Kindly help me with your suggestions or tips to resolve this weird behaviour of Dagger 2. Any kind of solutions will be much helpful to me since I am completely stuck up with this for the past 6 days. I am clueless and perplexed because my complete architecture is built on top of this. Please forgive me for typos and corrections. Ping me if there are any clarifications required regarding the same. Thanks in advance.
How to trigger the Dagger provider methods forcibly to trigger or revoke it again?
Is there any method to refresh the application class data as similar behaviour like when the app relaunches?
Nope, there isn't such a trigger. Component is responsible for providing you a dependency. If you are done with one Component and you want to invalidate it (i.e. your dependencies to be created again) you have to dispose from it (null out) and create a new Component. Now all your dependencies will be created again.
Your problem is #Singleton. #Singleton tells Dagger that you want Dagger to cache and manage the instance state, and you don't get a lot of control to refresh instances when you do so. However, you're welcome to drop #Singleton from the #Provides method and manage that instance yourself. Without a scope, Dagger will call your #Provides method for every single injection request, which will let you return whichever instance you wish and invalidate it when appropriate.
See this answer from yesterday, which incidentally is also about a Retrofit-serving NetworkModule and the scope troubles with refreshing instances on an AppComponent. (You two aren't on the same team, are you?)
/* Module fields */
OkHttpClient myClient;
String lastToken;
/** Not #Singleton */
#Provides
OkHttpClient providesOkHttpClient(
#Named("network.context") final Context context, TokenManager tokenManager) {
String currentToken = getToken(); // gets token from UserProfile
if (myInstance == null || !lastToken.equals(currentToken)) {
lastToken = currentToken;
myInstance = createInstance(currentToken); // As you have it above
}
return myInstance;
}
There is not a way to automatically refresh shared preferences, but with the above create-on-demand structure, you could easily write it to a data holder whenever the current token changes. At that point, it may make sense to extract a NetworkManager as in the other answer.
As per the azizbekian solution I modified the code a bit and it worked like a charm. Thanks a lot!
If the use clicks logout button, I am clearing SharedPreference and assigning dagger component as null through custom created method in application clearComponent() and then navigating the user to the another Authentication screen. Please find the complete code as below. Hope it will help some one!
#OnClick(R.id.img_logout)
void logout() {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(getActivity());
alertDialog
.setMessage("Do you really want to logout?")
.setCancelable(false)
.setPositiveButton("Logout", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogBox, int id) {
// ToDo get user input here
SharedPrefsUtils.remove(KEY_USERPROFILE, getActivity());
((Application) getActivity().getApplication()).clearComponent();
ActivityUtils.launchActivity(getActivity(), AuthenticationActivity.class, true);
}
})
.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogBox, int id) {
dialogBox.cancel();
}
});
AlertDialog alertDialogAndroid = alertDialog.create();
alertDialogAndroid.show();
}
Application.java
public class Application extends Application {
private AppComponent appComponent ;
#Override
public void onCreate() {
super.onCreate();
Realm.init(this);
ButterKnife.setDebug(BuildConfig.DEBUG);
appComponent = createDaggerComponent();
}
public AppComponent getAppComponent() {
return appComponent == null ? createDaggerComponent() : appComponent;
}
public void clearComponent() {
appComponent = null;
}
private AppComponent createDaggerComponent() {
return DaggerAppComponent.builder().appModule(new AppModule(this)).networkModule(new NetworkModule(this)).build();
}
}
I've been wanting to adopt Dagger 2 in conjugation with Retrofit 2. All seems to work nicely except for GET requests; they doesn't seem to have any headers attached with them.
Below is my NetworkModule which provides all networking-related dependencies for the whole app (note the #ForApplication scope annotation sprinkled there):
#Module
public class NetworkModule {
// …
#ForApplication
#Provides
OkHttpClient provideOkHttp(
HttpLoggingInterceptor loggingInterceptor,
#Named(PREFERENCE_CUR_LOGIN_SESSION) Preference<LoginSession> loginSessionPreference,
DeviceCredentials deviceCredentials
) {
final OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
builder.addNetworkInterceptor(chain -> {
if (loginSessionPreference.isSet()) {
return chain.proceed(
chain.request().newBuilder()
.addHeader("token", loginSessionPreference.get().getTokenId())
.addHeader("device-id", deviceCredentials.getDeviceId())
.addHeader("Content-Type", "application/json")
.build()
);
} else {
return chain.proceed(
chain.request().newBuilder().build()
);
}
});
return builder.build();
}
#ForApplication
#Provides
Retrofit provideRetrofit(Gson gson, OkHttpClient client) {
return new Retrofit.Builder()
.baseUrl("http://xxx.xxx.xxx/api/1.0/")
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
#ForApplication
#Provides
XxxApi provideApi(Retrofit retrofit) {
return retrofit.create(XxxApi.class);
}
}
This module is supplied as a dependency for my ApplicationComponent (among other modules):
#ForApplication
#Component(
modules = {
ApplicationModule.class,
RuntimeModule.class,
DateFormatModule.class,
PreferenceModule.class,
NetworkModule.class
}
)
public interface ApplicationComponent {
// …
}
I've ran a debug session and confirmed that loginSessionPreference.isSet() is evaluated as true but nevertheless my request still showed up without any headers:
11-16 16:55:22.748 21747-22569/xxx.xxx.xxx D/OkHttp: --> GET http://xxx.xxx.xxx/api/1.0/public/get-all-data/site http/1.1
11-16 16:55:22.748 21747-22569/xxx.xxx.xxx D/OkHttp: --> END GET
Did I miss something?
Use .addInterceptor instead of .addNetworkInterceptor()
First of all use addInterceptor() like Alex Shutov suggested.
Secondly, ensure in debug mode that methods addHeader() are called.
If you use the same Retrofit instance (same injection) it could not be used because loginSessionPreference.isSet() always returns false.
Your method provideOkHttp() is called while OkHttpClient is necessary to provide Retrofit instance. Method provideOkHttp() requires Preference and it is injected while object OkHttpClient is created. You can treat it as a final variable (compiler even makes it final I think).
Please delete loginSessionPreference logic and hardcode some headers - it will gonna tell us if it is the issue.
In my opinion you will need to change this architecture a little.
I currently have a Retrofit client class to make network calls. The same client is used for unit testing as well. What i want is to keep using the same class but if unit testing the app simply switch the Dispatcher so as to execute all of the network calls synchronously and monitor the results.
The easiest way around that would be to simply create a couple of provide methods like below for the Dispatcher using Dagger 2.0:
#Provides
#Named("RealDispatcher")
#Singleton
public Dispatcher provideDispatcher() {
dispatcher = new Dispatcher();
return dispatcher;
}
#Provides
#Named("TestDispatcher")
#Singleton
public Dispatcher provideTestDispatcher() {
dispatcher = new Dispatcher(newSynchronousExecutorService());
return dispatcher;
}
The component class:
#Singleton
#Component(modules = {RetrofitModule.class})
public interface RetrofitComponent {
void inject(RestClient restClient);
}
The conundrum being, how do i dynamically switch the Dispatcher based on if its in the "default" mode or "unit testing" mode. Since the type association in Dagger 2.0 for injection is a strong one, how can i keep changing it around.
Retrofit client setup:
public class RestClient {
private static ApiCall REST_CLIENT;
#Inject
#Named("RealDispatcher")
static Dispatcher dispatcher; ---> need to switch this dynamically
private static void setupRestClient() {
retrofitComponent.inject(this);
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(Level.BODY);
OkHttpClient httpClient = new OkHttpClient();
httpClient.setDispatcher(dispatcher); ---->add dispatcher based on "default" or "unit testing" mode.
httpClient.interceptors().add(logging);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(MainActivity.URL)
.client(httpClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
REST_CLIENT = retrofit.create(ApiCall.class);
}
}
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);
}