I have a working Dagger setup using the @ContributesAndroidInjector annotation (https://google.github.io/dagger/android.html).
Component Application
/ \
Subcomponent DrawerActivity Subcomponent SubActivity
|
Subcomponent DrawerFragment
In SubActivity and DrawerActivity I am using the same repository instance which is marked as @Singleton.
@Singleton
@Component(modules = {
AndroidInjectionModule.class,
AppModule.class,
ActivityBuilderModule.class
})
public interface AppComponent {
@Component.Builder
interface Builder{
@BindsInstance Builder application(Application application);
AppComponent build();
}
void inject(App app);
}
@Module
public abstract class ActivityBuilderModule {
@PerActivity
@ContributesAndroidInjector(modules = {DrawerActivityModule.class,
FragmentBuilderModule.class})
abstract DrawerActivity bindDrawerActivity();
@PerActivity
@ContributesAndroidInjector(modules = {DrawerActivityModule.class})
abstract SubActivity bindSubActivity();
}
@Module
public abstract class FragmentBuilderModule {
@PerFragment
@ContributesAndroidInjector(modules = DrawerFragmentModule.class)
abstract DrawerFragment provideDrawerFragment();
}
@Singleton
public class Repository{
private SomeClass mSomeClass;
@Inject
public VehicleRepositoryImpl(SomeClass someClass) {
mSomeClass = someClass;
}
}
public class App extends Application implements HasActivityInjector{
@Inject
DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
}
AppComponent component = DaggerAppComponent.builder().application(this)
.build();
component.inject(this);
}
@Override
public AndroidInjector<Activity> activityInjector() {
return dispatchingAndroidInjector;
}
}
public class DrawerActivity extends AppCompatActivity implements HasSupportFragmentInjector{
@Inject
DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;
@Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return fragmentDispatchingAndroidInjector;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
}
public class DrawerFragment extends Fragment {
@Inject
ViewModelFactory mViewModelFactory; //repository gets injected into factory
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
AndroidSupportInjection.inject(this);
super.onCreate(savedInstanceState);
}
}
public class SubActivity extends AppCompatActivity{
@Inject
Repository mRepository;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
}
I have now the need to add a user management. This requires a LoginActivity. I don't want the repository to be avaiblable in LoginActivity. So I'd like to create a local singleton scope "UserScope" for DrawerActivity/Subactivity/DrawerFragement
Component Application
/ \
@UserScope @LoginScope
Subcomponent DrawerActivity SubComponent LoginActivity
| \
Subcomponent DrawerFragment Subcomponent SubActivity
How can I achieve this still using the new @ContributesAndroidInjector annotation?
I want it to work like in this blogpost: http://frogermcs.github.io/building-userscope-with-dagger2/
I solved my problem by doing it like in this repo:
https://github.com/ragdroid/Dahaka
Many thanks to its contributor!
Update 1: Code example added.
This graph gives a rough idea of the code example.
Component Application
/ \
@UserScope @LoginScope
Subcomponent UserComponent SubComponent LoginActivity
| \
Subcomponent DrawerActivity Subcomponent SubActivity
|
SubComponent DrawerFragment
Code Example (If somethings missing please let me know in the comments):
1. Dagger Setup
AppComponent is the root of Dagger graph:
@Singleton
@Component(modules = {
AndroidSupportInjectionModule.class,
AppModule.class,
AppBindingModule.class
})
public interface AppComponent extends AndroidInjector<DaggerApplication> {
@Component.Builder
interface Builder{
@BindsInstance Builder application(Application application);
AppComponent build();
}
void inject(MyApp app);
UserComponent.Builder userBuilder();
UserManager getUserManager();
}
Module which binds its SubComponents:
@Module(subcomponents = UserComponent.class)
public abstract class AppBindingModule {
@ContributesAndroidInjector(modules = LoginModule.class)
@LoginScope
abstract LoginActivity loginActivity();
}
UserComponent holds instances of classes that are used only when user is logged in. All classes provided in UserModule.class are available as "LocalSingletons" in further subcomponents like activities and fragment components.
@UserScope
@Subcomponent(modules = {
UserBindingModule.class,
UserModule.class,
AndroidSupportInjectionModule.class
})
public interface UserComponent extends AndroidInjector<DaggerApplication> {
void inject(UserManager userManager);
@Subcomponent.Builder
interface Builder{
UserComponent build();
}
}
UserBindingModule defines which activity-subcomponents belong to UserComponent.
@Module
public abstract class UserBindingModule {
@ContributesAndroidInjector(modules = {DrawerBindingModule.class, AndroidSupportInjectionModule.class})
abstract DrawerActivity bindDrawerActivity();
@ContributesAndroidInjector
abstract SubActivity bindSubActivity();
}
DrawerBindingModule defines which fragment-subcomponents belong to DrawerActivityComponent.
@Module
public abstract class DrawerBindingModule {
@DrawerFragmentScope
@ContributesAndroidInjector(modules = DrawerFragmentModule.class)
abstract DrawerFragment provideDrawerFragment();
}
The UserManager handles user login/logout and all further activity injections.
@Singleton
public class UserManager implements HasActivityInjector {
private final UserComponent.Builder userComponentBuilder;
@Inject
DispatchingAndroidInjector<Activity> activityInjector;
private UserComponent userComponent;
@Inject
public UserManager(UserComponent.Builder builder) {
this.userComponentBuilder = builder;
}
public void logIn(){
createUserSession();
}
private void createUserSession() {
userComponent = userComponentBuilder.build();
userComponent.inject(this);
}
public boolean isLoggedIn() {
return userComponent != null;
}
public void logOut() {
userComponent = null;
}
@Override
public AndroidInjector<Activity> activityInjector() {
return activityInjector;
}
}
2. App+Activity+Fragment classes
public class MyApp extends Application implements HasActivityInjector{
@Inject
DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
@Inject
UserManager mUserManager;
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
}
AppComponent component = DaggerAppComponent.builder().application(this)
.build();
component.inject(this);
}
@Override
public AndroidInjector<Activity> activityInjector() {
return mUserManager.activityInjector();
}
}
public class LoginActivity extends AppCompatActivity {
Intent mOpenDrawerActivity;
private ActivityLoginBinding binding;
@Inject
UserManager mUserManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
...
}
void openDrawerActivity(){
mUserManager.logIn();
mOpenDrawerActivity = new Intent(this, DrawerActivity.class);
startActivity(mOpenDrawerActivity);
finish();
}
}
public class DrawerActivity extends BaseUserActivity implements HasSupportFragmentInjector{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
private void onLogout(){
logoutUser();
}
}
public abstract class BaseUserActivity extends BaseActivity {
@Inject
UserManager userManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!userManager.isLoggedIn()) {
finishView();
}
}
@Override
protected void androidInject() {
AndroidInjection.inject(this);
}
protected void logoutUser() {
userManager.logOut();
finishView();
}
}
public abstract class BaseActivity extends AppCompatActivity implements HasSupportFragmentInjector {
@Inject
DispatchingAndroidInjector<Fragment> injector;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
androidInject();
super.onCreate(savedInstanceState);
}
protected void androidInject() {
AndroidInjection.inject(this);
}
public void finishView() {
startActivity(new Intent(this, LoginActivity.class));
finish();
}
@Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return injector;
}
}
Application
(there are default scope with annotation @Singleton
already available), Activity
, BroadcastReceiver
, Fragment
, Service
, ContentProvider
(this list I took from DaggerApplication
). So depends on what object it will be based you'll get reusable dependency inside lifecycle of this object (it will be local singleton for it, e.g. for activity/fragment)@ContributesAndroidInjector
for generating @Subcomponent
. This subcomponent should be scoped so that's why we put @FragmentScope
(created annotation by ourselves) on it. Two child fragments will belong to this subcomponent and that is the reason why we creating standalone module for child fragments and add it into generated Subcomponent by adding argument modules
to @ContributesAndroidInjector
of parent fragment subcomponent.
@Module(includes = [AndroidInjectionModule::class])
abstract class AppBindingModule {
//there are a lot of other android stuff binding here
//activities, fragments, etc.
@FragmentScope
@ContributesAndroidInjector(modules = [ParentFragmentModule::class])
abstract fun bindParentFragment(): ParentFragment
}
@Module
abstract class ParentFragmentModule {
//we should not annotate this by any scope annotation
@ContributesAndroidInjector
abstract fun bindFirstChildFragment(): FirstChildFragment
@ContributesAndroidInjector
abstract fun bindSecondChildFragment(): SecondChildFragment
}
@FragmentScope
class CommonInteractor @Inject constructor() {
//we can inject this interactor into presenters of those two child fragments
//it will be the same instance for both presenters
}
class FirstChildPresenter @Inject constructor(
private val commonInteractor: CommonInteractor
) : Presenter<FirstView>()
class SecondChildPresenter @Inject constructor(
private val commonInteractor: CommonInteractor
) : Presenter<SecondView>()
class FirstChildFragment: Fragment(), FirstView {
@Inject
lateinit var firstChildPresenter: FirstChildPresenter
}
class SecondChildFragment: Fragment(), SecondView {
@Inject
lateinit var secondChildPresenter: SecondChildPresenter
}
I omit some details like describing AppComponent and adding AppBindingModule into it and showing how to inject with AndroidSupportInjection.inject(this) cause it is out of the topic. But if clarification is necessary feel free to ask questions in comment.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With