FragmentFactory is relatively recent addition to Android toolbox which allows you to use non-default constructors  (i.e. constructors with arguments) to instantiate your Fragments.

While this might sound like a small nuance, it is, in fact, a major step forward. Especially for bigger Android applications which combine extensive modularization schemes with dependency injection architectural pattern. So, in this post, I’ll show you how to use FragmentFactory and then explain how it affects dependency injection in modularized Android projects.

FragmentFactory

FragmentFactory is a new API that was introduced in AndroidX 1.1.0-alpha01.¬† Until then, you’d have to instantiate Fragments in your application using default no-args constructors exclusively. This restriction was motivated by the fact that Fragments are re-created on configuration changes and process death 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. Consequently, 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 to create Fragments with non-default constructors. Once the system can handle non-default constructors, you can start using them too.

FragmentFactory 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 subclass of Fragment, it will simply delegate this responsibility to the FragmentFactory superclass, which implements the “legacy” approach. Therefore, you can migrate your Fragments to this approach one-by-one.

To allow 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 for FragmentManager before the call to super.onCreate() because that call will lead to re-construction of Fragments. Therefore, if the factory won’t be set before super.onCreate(), the system will fall back to “legacy” instantiating mechanism after configuration changes and process death.

Once you’ve got your FragmentFactory set up and you want to switch Fragments, you can do that in the following manner:

        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 by changing 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 didn’t become simpler either. Taking into account the need to implement and use a 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. To be honest, even though I used Robolectric in the past, nowadays I’m not a big fan of integration testing in Android. So, for the sake of this discussion, let’s assume that you don’t do that either. Are there any other use cases?

I can’t see any additional benefits in using FragmentFactory in single-module application. However, it’s not the case in modularized projects.

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 or Fragments in library modules).

However, sometimes, you do need to modularize the presentation layer logic too. It might be required due to organizational structure, or, maybe, you have complex screens shared among multiple applications, or any other special reason. In the aforementioned post I listed several alternatives that you can use to inject dependencies into Activities and Fragments in child modules. However, all of them are hacky, with dagger.android being the culmination of excessively complex hacks. The reason why you can’t do proper dependency injection in this case is 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, but I’m not sure that it’s better. I can’t put my finger on it right now, but I’ve got a gut feeling that it’s better for long-term maintainability to avoid @Inject annotations in child modules.

Summary

In this post I shared with you my excitement about the new FragmentFactory API. Its introduction is a 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 in form of Fragments which you can then integrate into your application without additional ugly hacks.

If you’ve been using dagger.android just to work around the limitation on non-default constructors in Fragments, 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.

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

If you liked this post, then you'll surely like my courses