Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is actual usage of "HasFragmentInjector" in Dagger 2

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?

like image 228
Wasim K. Memon Avatar asked Jun 07 '17 09:06

Wasim K. Memon


1 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!!!!";
    }
}
like image 70
David Rawson Avatar answered Oct 02 '22 18:10

David Rawson