FragmentFactory and Dependency Injection Revolution in Android Apps

By | 2019-02-04T19:59:05+00:00 February 4th, 2019|Android development|7 Comments

Not long ago I wrote an article about dependency injection in modularized Android applications. If you read that article, you might remember that towards the end I said that Google works on special factory for Fragments which will open some new exciting possibilities in the context of multi-module dependency injection. Several months later, FragmentFactory landed as part of AndroidX Fragments infrastructure.

In this post I’ll review the usage of FragmentFactory and then show you how it revolutionizes dependency injection in multi-module Android projects.

FragmentFactory

FragmentFactory API was introduced in AndroidX 1.1.0-alpha01. As far as I can tell, this addition was motivated by a need to support FragmentScenario class which is part of the new set of tools for integration testing on Android. I’m not a big fan of integration testing, but as you’ll see in a moment the value of FragmentFactory pertains beyond just testing.

So, what does it do?

Well, until now we had to use Fragments with default no-args constructors exclusively. This restriction was motivated by the fact that Fragments are re-created on config changes and save&restore by the system, but the system wouldn’t know how to satisfy their constructor dependencies. Therefore, when re-creating the Fragments the system always used no-args constructor and we had to do the same to avoid inconsistencies and partially initialized Fragments after re-creation.

FragmentFactory removes the above restriction by allowing you to “help” the system handle Fragments with non-default constructors. Once the system can handle non-default constructors, you can start using them too.

FragmentFactory Basic Tutorial

Let’s say you’ve got just one single Fragment in your app that you want to instantiate using FragmentFactory. The first step is to extend that class with your own implementation:


public class FragmentFactoryImpl extends FragmentFactory {

    private final Dep1Factory mDep1Factory;
    private final Dep2Factory mDep2Factory;

    public FragmentFactoryImpl(Dep1Factory dep1Factory,
                               Dep2Factory dep2Factory) {
        mDep1Factory= dep1Factory;
        mDep2Factory= dep2Factory;
    }


    @NonNull
    @Override
    public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className, @Nullable Bundle args) {
        Class clazz = loadFragmentClass(classLoader, className);

        Fragment fragment = null;
        if (clazz == MyFragment.class) {
            fragment = new MyFragment(mDep1Factory.newInstance(), mDep2Factory.newInstance());
        } else {
            return super.instantiate(classLoader, className, args);
        }

        if (args != null) {
            fragment.setArguments(args);
        }

        return fragment;
    }
}

As you can see, FragmentFactory instantiates MyFragment and satisfies its two dependencies. If this factory will be asked to instantiate any other class of Fragment, it will simply delegate this responsibility to its superclass (which implements the “legacy” approach). This way you can migrate your Fragments to this approach one-by-one.

To force the system to actually use this factory, you need to set it as a default factory on a specific FragmentManager. For example, you can do the following in Activity’s onCreate():

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getSupportFragmentManager().setFragmentFactory(mFragmentFactory);
        super.onCreate(savedInstanceState);
    }

This setting applies to all “child” FragmentManagers transitively. Therefore, if you do that in your Activities you don’t need to repeat the process in your Fragments.

Note that it’s crucially important that you set FragmentFactory before the call to super.onCreate(). This is required because the call to super.onCreate() will lead to re-construction of Fragments, so if the factory won’t be set you’ll get crash after config change or save&restore.

Lastly, when you want to switch Fragments manually, you can use your FragmentFactory in the following way:

        Bundle args = new Bundle();
        args.putString(MyFragment.ARG_SOMETHING, "something");
        getSupportFragmentManager().beginTransaction().replace(
                R.id.content,
                mFragmentFactory.instantiate(getClassLoader(), MyFragment.class.getName(), args)
        );

So, that’s how you’d use this new API in your own applications.

FragmentFactory in Single Module Application

Note that this new approach to Fragments’ instantiation is more complicated than what we had before. Especially if you had already used a mature dependency injection library like Dagger 2.

MyFragment looked like this before refactoring:

public class MyFragment extends RequestsListBaseFragment {

    @Inject Dep1 mDep1;
    @Inject Dep2 mDep2;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        getDaggerComponent().inject(this);
        super.onCreate(savedInstanceState);
    }
}

This code is simple and I can easily remove or add dependencies with a single line of code.

Now, MyFragment looks like this:

public class MyFragment extends RequestsListBaseFragment {

    private final Dep1 mDep1;
    private final Dep2 mDep2;

    public MyFragment(Dep1 dep1, Dep2 dep2) {
        mDep1 = dep1;
        mDep2 = dep2;
    }
}

While it didn’t become more complex, it also didn’t become much simpler. Taking into account the need to implement and use the custom FragmentFactory, the net result is probably increased complexity, which is not good. So, why would I want to use this approach then?

Well, the first use case is integration testing using FragmentScenario. As I already said, after some extensive experience with Robolectric in the past, nowadays I’m not a big fan of integration testing in Android. For the sake of discussion, let’s assume that you don’t do that either. Are there any other use cases?

In a single module application I’m not aware of any additional benefits of this new approach. However, it’s not the case in modularized Android apps.

FragmentFactory in Modularized Application

In my previous post about dependency injection in modularized Android apps I tried to convince you that the best modularization strategy is to have pure Java library modules. The second-best strategy is to have Android library modules, but without presentation layer logic in them (i.e. no Activities and Fragments in library modules).

However, sometimes, you need to put presentation layer logic into modules. It might be required due to organizational structure, or, maybe, you have complex screens shared among multiple applications, or any other special reason. How can you inject dependencies into Activities and Fragments in library modules then?

Well, in the previous post I listed several alternatives that you can use. However, all of them are hacky, with dagger.android being the culmination of excessively complex hacks. You can’t do proper dependency injection in this case because Activities and Fragments don’t support constructor injection.

Now the situation changed. At least for Fragments.

With the help of FragmentFactory, you can have Fragments in library modules and use proper constructor injection to provide their dependencies. No need for hacks anymore. No need for dagger.android.

To test how it all works in real application, I refactored my own pet project that uses Dagger to AndroidX Fragments and then modularized it.

That’s how my subclass of FragmentFactory looks like:

public class FragmentFactoryImpl extends FragmentFactory {

    private final Provider<RequestDetailsFragment> mRequestDetailsFragmentProvider;
    private final Provider<RequestsAllFragment> mRequestAllFragmentProvider;
    private final Provider<RequestsMyFragment> mRequestsMyFragmentProvider;
    private final Provider<CloseRequestFragment> mCloseRequestFragmentProvider;
    private final Provider<NewRequestFragment> mNewRequestFragmentProvider;
    private final Provider<InfoDialog> mInfoDialogProvider;
    private final Provider<PromptDialog> mPromptDialogProvider;

    @Inject
    public FragmentFactoryImpl(Provider<RequestDetailsFragment> requestDetailsFragmentProvider,
                               Provider<RequestsAllFragment> requestAllFragmentProvider,
                               Provider<RequestsMyFragment> requestsMyFragmentProvider,
                               Provider<CloseRequestFragment> closeRequestFragmentProvider,
                               Provider<NewRequestFragment> newRequestFragmentProvider,
                               Provider<InfoDialog> infoDialogProvider,
                               Provider<PromptDialog> promptDialogProvider) {
        mRequestDetailsFragmentProvider = requestDetailsFragmentProvider;
        mRequestAllFragmentProvider = requestAllFragmentProvider;
        mRequestsMyFragmentProvider = requestsMyFragmentProvider;
        mCloseRequestFragmentProvider = closeRequestFragmentProvider;
        mNewRequestFragmentProvider = newRequestFragmentProvider;
        mInfoDialogProvider = infoDialogProvider;
        mPromptDialogProvider = promptDialogProvider;
    }


    @NonNull
    @Override
    public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className, @Nullable Bundle args) {
        Class clazz = loadFragmentClass(classLoader, className);

        Fragment fragment = null;
        if (clazz == RequestDetailsFragment.class) {
            fragment = mRequestDetailsFragmentProvider.get();
        }
        else if (clazz == RequestsAllFragment.class) {
            fragment = mRequestAllFragmentProvider.get();
        }
        else if (clazz == RequestsMyFragment.class) {
            fragment = mRequestsMyFragmentProvider.get();
        }
        else if (clazz == NewRequestFragment.class) {
            fragment = mNewRequestFragmentProvider.get();
        }
        else if (clazz == CloseRequestFragment.class) {
            fragment = mCloseRequestFragmentProvider.get();
        }
        else if (clazz == InfoDialog.class) {
            fragment = mInfoDialogProvider.get();
        }
        else if (clazz == PromptDialog.class) {
            fragment = mPromptDialogProvider.get();
        }
        else {
            return super.instantiate(classLoader, className, args);
        }

        if (args != null) {
            fragment.setArguments(args);
        }

        return fragment;
    }
}

Similarly to injection of ViewModels with ViewModelFactory, explicit Providers could be replaced with multibindings in this implementation. I don’t like multibindings due to their complexity and try to avoid this convention as much as possible, so I stayed with plain Providers.

As for adding the Fragments to the objects graph, you’ve got two options:

Evidently, the second option is simpler. However, I’m not sure how it affects incremental compilation and incremental annotation processing. Therefore, it might be the case that the second option incurs build-time penalties. I don’t have time to benchmark this hypothesis at this point, but I’ll be very glad to get readers’ input on this subject.

Summary

In this post I shared with you my excitement about the new FragmentFactory API. Its introduction is a true revolution in Android framework that opens new options for modularization of Android projects. With the help of FragmentFactory, you can finally have library modules that contain presentation layer logic and integrate easily into the complete application.

If you’ve been using dagger.android just because you wanted to use dependency injection in library modules that contained screens, you should definitely look into FragmentFactory.

One big question with respect to modularized dependency injection using FragmentFactory is what will be the impact of using @Inject annotation in library modules. Currently I recommend not using it and instantiating the Fragments from library modules manually. It doesn’t take that much effort and the gains in build times might be significant. Hopefully we’ll get more information from more informed community members soon.

As usual, you can leave your comments and questions below, and don’t forget to subscribe if you liked this post.

Check out my Android Development courses on Udemy

Subscribe for new posts!

7 Comments

  1. Guilherme February 6, 2019 at 9:18 pm - Reply

    One thing to consider when migrating to fragment factory, is that the fragment scope in Dagger will be the activity scope now. So we cannot provide in the DI graph objects obtained from fragments, like fragment’s layoutinflater. For this we need to use factories and pass them at runtime.

    • Vasiliy February 7, 2019 at 1:13 pm - Reply

      Hello Guilherme,
      I’m not sure I understand what you mean. Can you elaborate a bit?
      For example, I didn’t do any restructuring of my DI approach following refactoring to FragmentFactory, so I wonder if I missed some important aspect.
      Thanks

      • Guilherme February 7, 2019 at 7:33 pm - Reply

        Hmm, sorry, I think I made a mistake here.

        After following your courses (which are great BTW!), I restructured my app to use a PresentationModule; but I was passing a Fragment in the constructor, and not an Activity. Now I don’t remember exactly why I did this (maybe it was just an overlook on my side), but now by using the FragmentFactory I only have access to the Activity in the dependency graph in the PresentationComponent.

        Maybe I did this to get the layout inflater directly from the Fragment, but I don’t know if there is any difference on getting one via the Activity.

        • Vasiliy February 7, 2019 at 8:01 pm - Reply

          I see.
          The only case where I’d consider having Fragment specific bootstrapping dependencies in PresentationComponent (I also call it ControllerComponent) is if I’d use nested Fragments. In this situation, I would probably need childFragmentManager on the objects graph. That said, I’ve been burned with nested Fragments years ago and I would hesitate to use them even today.
          Maybe that was your use case?

          • Guilherme February 7, 2019 at 10:11 pm

            Actually no, in that particular project I have also tried to avoid nested fragments. Anyway, I have already changed the PresentationComponent to use activities. Thanks Vasiliy!

  2. Igor Ganapolsky May 2, 2019 at 10:28 pm - Reply

    Thanks for this writeup. I’m curious, have you looked at Toothpick DI? It is similar to Dagger, yet less boilerplate.

    • Vasiliy May 4, 2019 at 6:01 am - Reply

      Hi Igor,
      Glad you liked the article.
      I read about Toothpick out of curiosity two years ago, but never actually tried it. Don’t see a reason to.
      In general, IMHO, boilerplate argument shouldn’t be a primary factor in any technical decisions. Usually there are much more important aspects to consider. In case of DI frameworks, I’d say that the main factor to consider on real projects is community adoption.

Leave A Comment