21

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:

  1. How to trigger the Dagger provider methods forcibly to trigger or revoke it again?

  2. 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.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
Chandru
  • 5,954
  • 11
  • 45
  • 85

3 Answers3

13

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.

Community
  • 1
  • 1
azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • 6
    can you explain more, how to dispose the component? – Tefa Apr 18 '19 at 14:49
  • 1
    @Tefa, assume you've had `AppComponent a = DaggerAppComponent.build()` and now you want to recreate all dependencies: `a = DaggerAppComponent.build()` basically you build a new component and forget about the previous one. – azizbekian Apr 14 '20 at 17:38
  • 1
    @azizbekian I created a new component but still the old one exists I am getting two dagger instances in the application. Why is it so? how can I avoid it? – keshav kowshik Oct 19 '20 at 16:13
10

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.

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • Thanks @Jefff Bowman for your solution. Apparently we both are not from the team :-P – Chandru Apr 02 '17 at 05:55
  • @Rachit Sure; this is a good reason to engineer your solution to not need to recreate them, but I thought it was still appropriate to answer the original question (which could be about any instance that you have to manually recreate within the same scope). Of course, if multiple objects share this lifetime, it may make sense to simply create a custom scope, which is also general advice that would specifically create more OkHttpClients than you might want in your app. – Jeff Bowman Dec 27 '17 at 11:50
  • Thanks for the quick update. I went with the second approach where I have a single session manager for the application. – Rachit Mishra Dec 27 '17 at 12:45
9

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();
    }
            }
Chandru
  • 5,954
  • 11
  • 45
  • 85
  • what if you already have an existing injected component relying on the shared preferences that needs to be recreated ? – Louis Dec 01 '17 at 17:27
  • It will be recreated once if you call clearComponent() method. – Chandru Dec 04 '17 at 07:06
  • Looks like appComponent is not getting recreated for me. After logout, am clearing it but it doesn't get recreated when I call Application class getAppComponent or createAppcomponent from the LoginActivity. Both methods return appComponents as null. Any help would be appreciated – saintjab Mar 11 '19 at 12:06