Trying to inject to a Job class - android

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);

Related

Dependency injection and mutable dependencies [duplicate]

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();
}
}

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(); }
}

Retrofit 2.0 + Dagger 2.0 multiple dispatchers

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

Injecting OkHttpClient using Dagger2

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.

How to address multiple API end points using Retrofit?

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.

Categories

Resources