Issue
According to tutorials like this one: in Dagger2 we are able to provide local singleton objects using a custom scope.
I already have a global appComponent and my goal is to create a activity scope that allows a subcomponent to have local singleton providers.
My issue is when i create a custom scope and inject my activity twice, i see that the object being provided is not the same one, even though i tagged it with the custom scope. for the appComponent which uses the @Singleton annotation it works fine though. Lets take a look at how i did this:
Here is the custom activity scope:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
And here is how i define the subcomponent:
@ActivityScope
@Subcomponent(modules = {PresenterModule.class, UseCaseModule.class})
public interface ActivitySubComponent {
void inject(LoginActivity target);
void inject(LoginPresenter target);
void inject(UCGetFireBaseAccount target);
}
Notice here that i've tagged this subcomponent with @ActivityScope annotation that i custom made above. Now lets see the appComponent which is the parent component and global:
@Singleton
@Component(modules = {AppModule.class, NetworkModule.class, RepositoryModule.class})
public interface AppComponent {
void inject(myappCloudRepo target);
void inject (FireBaseCloudRepo target);
ActivitySubComponent plus(PresenterModule presenterModule, UseCaseModule useCaseModule);
}
Notice ActivitySubComponent is defined here as a subcomponent.
Finally lets look at the presenterModule class to see what i wanted to be provided as a singleton:
@Module
public class PresenterModule {
@Provides
@ActivityScope
LoginPresenter provideLoginPresenter(Context context) {
return new LoginPresenter(context);
}
}
Now when i actually use this i set it up in my application class like this:
public class MyApplication extends Application {
private AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = initDagger(this);
}
public AppComponent getAppComponent() {
return appComponent;
}
protected AppComponent initDagger(MyApplication application) {
return DaggerAppComponent.builder()
.appModule(new AppModule(application))
.build();
}
public ActivitySubComponent getActivityComponent() {
return appComponent.plus(new PresenterModule(),new UseCaseModule());
}
}
Then lastly in any activity i do this:
public class LoginActivity extends AppCompatActivity{
ActivitySubComponent activityComponent;
//this loginpresenter is from the activitysubcomponent
@Inject
LoginPresenter loginPresenter;
//this retrofit object is from the appcomponent
@Inject
Retrofit retrofit;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityComponent= ((MyApplication)getApplication()).getActivityComponent();
activityComponent.inject(this);
Log.d("j2emanue",loginPresenter.toString());
Log.d("j2emanue",retrofit.toString());
activityComponent= ((MyApplication)getApplication()).getActivityComponent();
activityComponent.inject(this);
Log.d("j2emanue2",loginPresenter.toString());
Log.d("j2emanue2",retrofit.toString());
}
}
When i take a look at the logs after injecting TWICE i am expecting all objects to be the same but instead the loginPresenter is another instance but the retrofit is the same so that works. Here is the log:
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue: com.myapp.mobile.myappfashion.UI.Presenters.LoginPresenter@1c27a460
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue: retrofit2.Retrofit@13366d19
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue2: com.myapp.mobile.myappfashion.UI.Presenters.LoginPresenter@3dc041de
05-23 23:33:35.617 31298-31298/com.myapp.mobile.myappfashion D/j2emanue2: retrofit2.Retrofit@13366d19
Notice the loginpresenter is another object so its not making a local singleton for me that would only be destroyed when i dereference the activitysubcomponent. what am i missing here ? The reason i want it to be a local singleton is for configuration changes. This way the presenter can be maintained during a config change.
Solution
In your application class you create and return a new subcomponent.
public ActivitySubComponent getActivityComponent() {
return appComponent.plus(new PresenterModule(),new UseCaseModule());
}
Now scopes mean that within a scope an object exists only one time. But in your case, you create 2 different components. Those 2 components will share everything in their parent component (they are subcomponents), but anything within their scope will be recreated, but unique to their scope.
Anything within your component will use the same @ActivityScope
annotated objects, but if you create 2 components you will have 2 copies of everything.
If you take @Singleton
as an example, it does not mean the object will be an actual Singleton. It is the name of the scope that you are supposed to have for your root component, which should only be created once and be kept during the lifetime of the application.
If you were to create 2 AppComponents that are @Singleton
you could observe that same behavior—two different instances of your object.
In your example Retrofit
is the same, because you use the same AppComponent
both times, but LoginPresenter
gets recreated along with every ActivitySubComponent
that you create.
With Dagger you should try that your components follow the same lifecycle as what they scope, thus your app should hold an AppComponent
, and every Activity should have their own ActivityComponent
(keep the component as a member variable!). When you create a new Activity you should create a new @ActivityScope
scoped component, but not more often than that.
You should remove your getActivityComponent()
from the Application and keep a reference to your ActivitySubComponent
, because injecting scoped dependencies with the same component will give you the same objects.
activityComponent.inject(this);
activityComponent.inject(this);
// call it as many times as you'd like.
activityComponent.inject(this);
Just don't recreate your component.
Answered By - David Medenjak
Answer Checked By - Terry (JavaFixing Volunteer)