I have implemented dagger2 v2.2 previously but as now they have added dagger.android part also. so I am creating sample project with that.
I am aware about old methodology of @Provide and @Modules and @Components etc annotations but from Dagger 2.8+ they have added this android-support library also which have some new Injections like @ActivityKey, @ContributesAndroidInjector, @Subcomponent.Builder etc.
So my question is what benefits it brings to the table.
Does it resolve problems like Inject method with base class can work for all child class ? or Any other benefits ?
2nd question - HasFragmentInjector is just to load fragment inside activity like we used to do using fragment manager ? or I am missing some thing ?
Please don't downvote its more informative question for all library users as documentation of library don't provide such answers.
First question
In Dagger 2.8+ they have added this android-support library also which have some new annotations like @ActivityKey
, @ContributesAndroidInjector
, @Subcomponent.Builder
etc. So my question is what benefits it brings to the table.
This has already been answered in What are the advantages of using DispatchingAndroidInjector and the other dagger-android classes?
Does it resolve problems like not having an inject method for a base class that can work for all child class?
Dagger 2 uses code generation at compile time for dependency injection. In this, it is different from other dependency injection frameworks like Guice that inspect injection sites at runtime. In order for Dagger 2 to work, you must at some point specify the invariant of the injection site. Therefore it will never be possible to write something like:
void inject(Activity activity);
inside a Dagger 2 component and have it inject all activities.
However, there are many improvements with the new classes available in dagger-android. Whereas before you would have to write:
void inject(MainActivity mainActivity);
and so on for each different injection site you can now write the following code:
@Module(subcomponents = MainActivitySubcomponent.class)
public abstract class MainActivityModule {
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity> mainActivityInjectorFactory(MainActivitySubcomponent.Builder builder);
}
and then:
AndroidInjection.inject(this);
inside your MainActivity at the appropriate point.
Second question
HasFragmentInjector is just to load Fragment inside Activity like we used to do using FragmentManager? or I am missing something?
HasFragmentInjector
simply marks the class where the Fragment should get its AndroidInjector
from. You can see for yourself in the code on GitHub for AndroidInjection#inject(Fragment fragment)
:
public static void inject(Fragment fragment) {
checkNotNull(fragment, "fragment");
HasFragmentInjector hasFragmentInjector = findHasFragmentInjector(fragment);
Log.d(TAG, String.format(
"An injector for %s was found in %s",
fragment.getClass().getCanonicalName(),
hasFragmentInjector.getClass().getCanonicalName()));
AndroidInjector<Fragment> fragmentInjector = hasFragmentInjector.fragmentInjector();
checkNotNull(fragmentInjector,"%s.fragmentInjector() returned null",
hasFragmentInjector.getClass().getCanonicalName());
fragmentInjector.inject(fragment);
}
From the javadoc, this method walks first the parent-fragment, then the Activity, then finally the Application to find HasFragmentInjector
and uses the AndroidInjector<Fragment>
to inject the fields of the Fragment.
However, the presence of HasFragmentInjector
does not mean that you should start managing Fragments using Dagger 2:
public class MainActivity {
@Inject CoffeeFragment coffeeFragment; //no! don't do this
@Inject TeaFragment teaFragment; //no!
You should still use the idiomatic way of instantiating Fragments which is using static factory methods. Dagger 2 will perform injection for the fields inside the Fragments when their onAttach(Context context)
is invoked when, say, you add the Fragment using a transaction or you delegate to a ViewPager. Instead of the above example, the following code is a very simple Activity with a ViewPager and two Fragments:
public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
@Inject
DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;
ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BeveragesPagerAdapter beveragesPagerAdapter = new BeveragesPagerAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.viewpager);
mViewPager.setAdapter(beveragesPagerAdapter);
}
class BeveragesPagerAdapter extends FragmentStatePagerAdapter {
public BeveragesPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
switch (i) {
case 0:
return TeaFragment.instantiate(new Bundle());
case 1:
return CoffeeFragment.instantiate(new Bundle());
default:
throw new IllegalStateException();
}
}
@Override
public int getCount() {
return 2;
}
@Override
public CharSequence getPageTitle(int position) {
return "tab " + (position + 1);
}
}
@Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return fragmentDispatchingAndroidInjector;
}
}
The FragmentStatePagerAdapter correctly handles the management of the Fragments and we do not inject as fields inside MainActivity.
The Fragments themselves look like this:
in CoffeeFragment.java:
public class CoffeeFragment extends Fragment {
public static CoffeeFragment instantiate(@Nullable Bundle arguments) {
CoffeeFragment coffeeFragment = new CoffeeFragment();
coffeeFragment.setArguments(arguments);
return coffeeFragment;
}
@Inject
@Named("Coffee")
Repository repository;
TextView textView;
@Override
public void onAttach(Context context) {
AndroidSupportInjection.inject(this);
super.onAttach(context);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_coffee, container, false);
textView = (TextView) v.findViewById(R.id.coffee_textview);
return v;
}
@Override
public void onResume() {
textView.setText(repository.retrieve());
}
}
in CoffeeFragmentModule.java:
@Module(subcomponents = CoffeeFragmentSubcomponent.class )
public abstract class CoffeeFragmentModule {
@Binds
@Named("Coffee")
abstract Repository repository(CoffeeRepository coffeeRepository);
@Binds
@IntoMap
@FragmentKey(CoffeeFragment.class)
abstract AndroidInjector.Factory<? extends Fragment> bindCoffeeFragmentInjectorFactory(CoffeeFragmentSubcomponent.Builder builder);
}
in CoffeeFragmentSubcomponent.java:
@Subcomponent
public interface CoffeeFragmentSubcomponent extends AndroidInjector<CoffeeFragment> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<CoffeeFragment> {}
}
in CoffeeRepository.java:
public class CoffeeRepository implements Repository {
@Inject
public CoffeeRepository() {
}
@Override
public String retrieve() {
return "Coffee!!!!";
}
}
Official Documentation explains this topic quite well in my oppinion.
Anyway the main benefit is that instead of something like this
((SomeApplicationBaseType) getContext().getApplicationContext())
.getApplicationComponent()
.newActivityComponentBuilder()
.activity(this)
.build()
.inject(this);
You can simply write this, which makes life easier for everyone.
AndroidInjection.inject(this);
Less boilerplate, easier maintainability.
The previous approach is kind of breaking the basic concept of dependency injection, a class should not know any details about the way dependencies are being injected.