FragmentFactory with Dagger and Hilt
In this article, I will talk about how to use FragmentFactory with Dagger, and then we will see how to use it with Hilt.
This article builds on the basics of Dagger, so in case you don’t have any experience with it (which I doubt as it is relatively mainstream nowadays), I’d recommend going through its basic principals before we see how to use it with FragmentFactory.
Same goes for Hilt. If you’ve never used it before, here’s a pretty good step by step article to catch up:
https://medium.com/supercharges-mobile-product-guide/how-to-migrate-your-di-from-dagger-android-to-hilt-2266425046eIf you are using FragmentFactory with your Dagger setup already, and you are only curious about how to use it with Hilt, click here to scroll down.
Dagger recently introduced Hilt, which could be considered as an additional layer on top of Dagger. Hilt promises a much easier and faster dependency injection setup in our code, so I was eager to give it a chance, and I started to migrate my pet project.
The migration process is pretty easy, basically, we need to remove a lot of code, mainly the explicitly written Dagger components. Also, we need some changes in our Dagger modules — actually here is a small conceptual change —, with Dagger we had to tell our components which modules they needed. With Hilt this is reversed, we have to tell our modules to which components they belong to.
Almost everything went well through the migration, except one of the last steps, but I’m going to talk about this later.
In my project, I also use FragmentFactory to inject dependencies into Fragments, and there is a small — but important — difference in the setup in case of Dagger and in case of Hilt. Let’s see what are these.
FragmentFactory with Dagger
Fragments are instantiated by the operating system by default.
The Fragment.java class has an instantiate()
method, and before androidx this method was called by the system.
With androidx, FragmentFactory class was introduced. It helps us to construct our Fragments via constructor injection. Constructor injection is always preferred over field injection, among other things testing purpose is maybe the most important.
As the name suggests, it is a factory class to create Fragments, and it also has an instantiate()
method. FragmentManager is the framework component which uses the FragmentFactory, by default it will create Fragments without dependencies.
So with androidx, Fragments are still instantiated by the system, but it is done through the FragmentFactory class’s default instantiate()
method.
instantiate()
method is still there for compatibility reasons, but unused, the framework is also using FragmentFactory’s instantiate() method.What we are able to do from now, is to define a custom factory implementation, and pass it to the FragmentManager, to tell how to instantiate our Fragment.
The following code is a simple custom factory class, and we can make it injectable with the @Inject
annotation.
So in this class, we can specify our dependencies for the Fragments, in the code example above I use two Fragments, both of them have two dependencies.
We only need to have one factory class, and with the added @Inject
, we can use it by injecting directly. Or an even nicer solution is to create a Dagger module for it and bind it to its parent class. I suggest the latter one.
We can imagine that with adding more dependencies in the constructor and passing them to many more Fragments in the instantiate method, this DefaultFragmentFactory file will be growing nicely:).
At the end of the article I will show an improved solution of creating DefaultFragmentFactory.
One more step left: we would like to use this factory, so we need to pass it to the FragmentManager, the question is where to do that?
Simply saying, it is important to do it before the Activity’s onCreate()
method!
If we don’t do it before Activity’s onCreate()
, that will cause problems.
For example, if we put it right after the super.onCreate()
line, the app will be able to launch, but it will crash at Fragment recreation (like a configuration change) anyway.
With that we are ready to use FragmentFactory with Dagger, let’s see how we can use it with Hilt.
FragmentFactory with Hilt
I start this section with an important fact: in the case of Hilt the injection is happening in the onCreate method(), this is different from Dagger.
The following image is from my Hilt generated Hilt_MainActivity class, where we can see the inject()
method which will start the injection, and it is called in the onCreate()
.
This means our dependencies are only available after the onCreate()
method.
(If we debug the code, we would see the FragmentFactory property is null before the onCreate()
.)
Here comes our problem: as we have discussed before, FragmentFactory should be set before onCreate()
to work as expected, but dependencies with Hilt can be used after the onCreate()
, so the two things contradict each other.
In the following, we will see two possible solutions.
So our goal is to set the proper FragmentManager with our custom FragmentFactory.
- If we have one Fragment with multiple child Fragments, then we should set the FragmentFactory in our container Fragment’s child FragmentManager (so in the Fragment which holds other Fragments).
Actually in this case the mentioned problem above is not even present. - If we have one activity with multiple Fragments in it, then we should set the FragmentFactory in the activity’s FragmentManager.
Fragment with multiple Fragments
If you use the androidx navigation component for example in your app, then you can use the second solution easily. With the navigation component you certainly use a NavHostFragment, in your xml
layout, it is like the following line.
android:name="androidx.navigation.fragment.NavHostFragment"
This Fragment is for handling other Fragments inside. So with extending this class, we can create our own host Fragment, and we will be able to set our factory class. Since we are on a Fragment now, the FragmentFactory should be set in the childFragmentManager
As you can see, I set the FragmentFactory in the onAttach()
method, but that is before onCreate()
. 🤔
The injection works differently with Fragments, it is triggered in the onAttach()
method.
To be sure about it you can check the generated class, in my case this is the Hilt_BaseNavHostFragment class.
This means we can set the FragmentFactory indeed before the onCreate()
method, as it is needed to be.
And finally, we have to replace the NavHostFragment to our custom BaseNavHostFragment.
android:name="...BaseNavHostFragment"
One Activity with multiple fragments
Now let’s see the other case when we have one activity with multiple Fragments. In this case, we would like to set the FragmentManager in our activity, and this should be done before onCreate()
.
To work around this issue we can turn to the Hilt EntryPoint.
EntryPoint comes handy when we would like to use dependencies in classes, which are not supported by Hilt. Actually, our case is a little bit different, because our class supports injection, but we would like to get access to a dependency earlier.
Let’s see the following code. We will tell Hilt that we would like to have access to a dependency, it is the DefaultFragmentFactory.
What Hilt will do is generate the needed implementation for this interface.
If we look into the generated class, we will see it inherits from the Hilt component (so it has access to the dependency graph), and it implements our method with returning the dependency from the dependency graph.
To use this generated entry point Hilt provides for us an accessor class, the EntryPointAccessors.
The following code snippet shows its usage.
When we use dependency injection we have a “closed” component tree/graph. Our dependencies can be used inside the dependency graph with injection.
EntryPoint interface is like an early access or a leak point to our dependency graph, so we can access a dependency from outside of the graph. What we tell with these lines in the entry point interface, that we will need a given dependency, that we would like to access from outside, non-injection-way.
Improved FragmentFactory
As we have seen above, implementing a custom FragmentFactory is pretty easy, but with more Fragments and their dependencies, the class will grow quickly.
The other disadvantage is that actually, we have to define the Fragments constructor parameters two times, once in the Fragments constructors itself, and one more time in the FragmentFactory, when we explicitly call the constructor with the parameters.
In this section we have a way better solution, and we will be able to skip the explicitly called constructor part.
We can use FragmentFactory like how ViewModels are created by ViewModelFactory. Basically what ViewModelFactory does, is that it has a map of ViewModels stored by their class names.
When retrieving viewmodels in Fragments with the following line, the viewModels() lazy function will use our explicitly given viewModel type.
private val viewModel: FirstScreenViewModel by viewModels()
We can do a similar approach with Fragments as well.
For the first step that we need to be able to inject our Fragments, so we need to make them injectable, by adding @Inject
.
class FirstScreenFragment @Inject constructor(...)
We would like to collect our Fragments into a Dagger map, and with the help of the FragmentFactory we want to retrieve them via their class name.
To do that we have to use the @IntoMap
. To use this annotation we will need to specify a map key, and in our case the will be the KClass of the Fragments.
Then the following module is needed, this is where we actually create a Dagger map of Fragments. In this module, we can inject our Fragments (since they are injectable thanks to the @Inject
) and we bind them to their parent class.
And finally here is how our new FragmentFactory looks like.
The content of this class is pretty similar to how ViewModelFactory is. There is one additional step. Since we are storing Fragments in the map, the loadFragmentClass()
static method helps us to retrieve the specific Fragment Class from a given name.
With this improved solution of FragmentFactory we have to add some new code lines to the project, but once we are done with the mentioned steps, adding new Fragments with dependencies will become much easier and maintainable.
Summary
In this article, I assumed that you have experience with Dagger, and adding FragmentFactory to it is pretty simple. When you switch to using Hilt, luckily you don’t have to give up on using FragmentFactory.
I’ve gone through two kinds of implementation of FragmentFactory, both of them can be used both with Dagger and Hilt.
FragmentFactory makes managing Fragments easier, especially if we look at it from the testing point of view. With its help, we can use constructor injection, which was a big lack of the framework.
Sample app
I have created a sample application to demonstrate what we have seen, check it out if you’d like to dig deeper! It is available on Github, the repository contains 4 branches.
- Basic setup with Hilt
- Set FragmentFactory on the Fragment’s ChildFragmentManager
- Set FragmentFactory on activity with EntryPoint
- Improved FragmentFactory
https://github.com/team-supercharge/android-hilt-fragmentfactory