What is actual usage of “HasFragmentInjector” in Dagger 2

后端 未结 2 455
囚心锁ツ
囚心锁ツ 2021-02-02 14:37

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 methodolo

相关标签:
2条回答
  • 2021-02-02 15:10

    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!!!!";
        }
    }
    
    0 讨论(0)
  • 2021-02-02 15:10

    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);
    
    1. Less boilerplate, easier maintainability.

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

    0 讨论(0)
提交回复
热议问题