Fragment constructor injection has been supported for a while in Android thanks to the FragmentFactory. In this article, I explain what FragmentFactory is, how and when to use it, and how it behaves with embedded fragments.

Fragments in Android

A fragment (an instance of the Fragment class) represents a behavior or part of the user interface in an activity (an instance of the Activity class). A developer can combine multiple fragments into one operation to build a multi-pane user interface and reuse the fragment in multiple operations. A fragment can be thought of as a modular part of an operation. This part has its own lifecycle and handles input events on its own. In addition, it can be added or removed directly during the operation. It’s kind of like a nested operation that can be reused across different operations.

A fragment must always be embedded in an activity, and its lifecycle is directly affected by that activity’s lifecycle. For example, when an operation is suspended, all fragments within it are also suspended, and when an operation is destroyed, all its fragments are destroyed. However, while an operation is in progress (which corresponds to the resumed state of the lifecycle), you can manipulate each fragment independently and can add and remove fragments.

When developers perform such fragment transactions, they can also add these transactions to the back stack that’s managed by the operation. Each item in the back stack of an operation is a record of a completed transaction with a fragment. The back stack allows the user to reverse a fragment transaction (navigate in the opposite direction) by clicking the Back button.

When a fragment is added as part of an activity layout, it sits in a ViewGroup within the activity’s view hierarchy and defines its own view layout. There are two ways a developer can insert a fragment into an activity layout: by declaring the fragment in the activity layout file as a <fragment> element or by adding it to an existing ViewGroup object in application code. However, the fragment does not have to be part of the activity layout. You can use a fragment without an interface as an invisible workflow for an activity.

FragmentFactory in Android

Fragments in Android must have an empty constructor (without arguments). This is necessary in case the operating system needs to recreate the fragment itself.
The operating system does not use any constructors with arguments when reinstantiating, since during fragment recreation it cannot know which constructor to use. Therefore, any arguments should be included by passing them using the setArguments method.
To do this, in a fragment, we create a static method getInstance (String str) in which we create a Fragment using the default constructor. Then we set the arguments for the Bundle and call the setArguments method:


public class MainFragment extends BaseFragment {

   private static final String MY_ARG = "my_arg";
   private String arg = "";

   public static MainFragment getInstance(String str) {
       MainFragment fragment = new MainFragment();
       Bundle bundle = new Bundle();
       bundle.putString(MY_ARG, str);
       fragment.setArguments(bundle);
       return fragment;
   }

   @Override
   public void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       if (getArguments() != null) {
           arg = getArguments().getString(MY_ARG);
       }
   }
}

Next, we use code like this to get the fragment instance:

MainFragment fragment = MainFragment.getInstance("Hello world!!");

When creating a fragment, the Fragment class calls lifecycle methods: one of them is the instantiate method (called between the onCreate and onActivityCreated methods). Also, if it’s necessary to recreate a fragment, the system will call the instantiate method of the Fragment class:

@NonNull
public static Fragment instantiate(@NonNull Context context, @NonNull String fname,
       @Nullable Bundle args) {
   try {
       Class extends Fragment> clazz = FragmentFactory.loadFragmentClass(
               context.getClassLoader(), fname);
       Fragment f = clazz.getConstructor().newInstance();
       if (args != null) {
           args.setClassLoader(f.getClass().getClassLoader());
           f.setArguments(args);
       }
       return f;
   } catch (java.lang.InstantiationException e) {

To create an object, we use the getInstance (String str) method and, because the arguments are stored in setArguments during the recreation of the fragment, all variables will be initialized in the method onCreate(@Nullable Bundle savedInstanceState).

All of this was true until the release of the AndroidX ver. 1.1.0-alpha01 library. AndroidX libraries released by Google are part of Android Jetpack, a collection of libraries and tools for Android development. AndroidX was announced at Google I/O in 2018, and the first stable release was available later that year.

In AndroidX, the Fragment class and the method public static Fragment instantiate(@NonNull Context context, @NonNull String fname, @Nullable Bundle args) were declared as @Deprecated, and going forward, the reference states were supposed to use FragmentManager.getFragmentFactory and FragmentFactory.instantiate (ClassLoader, String). Now, Google recommends using FragmentFactory — but what is it? FragmentFactory is a new API that removes the restriction on using constructors with arguments, allowing you to help the system to create fragments with non-default constructors.

Mobile and Web App Development

Are you planning to expand your business online? We will translate your ideas into intelligent and powerful solutions.

Implementing the FragmentFactory API

So what do we need to start creating fragments using FragmentFactory?

Step 1

The first step is to create a FragmentFactory. Let’s suppose our MainFragment fragment needs two arguments. To implement this, we create a class that inherits from the FragmentFactory class. In this class, we override a single method:

 public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className)

Our factory will look like this:

Note that Bundle is missing in this method

class MyFragmentFactory extends FragmentFactory {

   private final AnyArg anyArg1;
   private final AnyArg anyArg2;

   public MyFragmentFactory(AnyArg arg1, AnyArg arg2) {
       this.anyArg1 = arg1;
       this.anyArg2 = arg2;
   }

   @NonNull
   @Override
   public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
       Class extends Fragment> clazz = loadFragmentClass(classLoader, className);
       if (clazz == MainFragment.class) {
          return new MainFragment(anyArg1, anyArg2);
       } else {
           return super.instantiate(classLoader, className);
       }
   }
}

Instead of the static initialization method MainFragment getInstance (String str), the fragment will have a normal constructor:

protected MainFragment(AnyArg arg1, AnyArg arg2) {
    this.arg1 = arg1;
    this.arg2 = arg2;
}

As you can see from the code, the factory stores the variables necessary to create a fragment with a constructor. If the system needs to recreate the fragment, it will call the overridden instantiate method. This method will create a fragment with the required arguments or will create the default method without arguments.

Note that when using FragmentFactory, using the old approach with setArguments is impossible.

Step 2

The second step is to tell the FragmentManager of our activity that it should use the default MyFragmentFactory factory and call the FragmentManager methods to create the fragment:

MyFragmentFactory fragmentFactory = new MyFragmentFactory( someObject1,  someObject2);

@Override
public void onCreate(Bundle savedInstanceState) {
   getSupportFragmentManager().setFragmentFactory(fragmentFactory);
   super.onCreate(savedInstanceState);
…

FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
        .replace(
            R.id.fragment_container,
            fragmentManager.getFragmentFactory().instantiate(
               this.getClassLoader(), 
               MainFragment.class.getName()), 
            tag);
if (addToBackStack) {
   fragmentTransaction.addToBackStack(tag);
}
fragmentTransaction.commit();

Note that you must set the default FragmentFactory before calling super.onCreate.

This is necessary so that when fragments are recreated, the factory is set by default before calling the AppCompatActivity super.onCreate lifecycle method, which will be called to recreate the fragment.
Remember to set it before super.onCreate.

Now, if the system recreates the fragment, the FragmentManager will survive the deletion of the fragment and, when the fragment is recreated, will use the custom FrаgmentFactory, recreating the fragment using the constructor with arguments.

Conclusion

Do you need to use FragmentFactory? No, it’s not required! But in certain cases, it might be the best architecture for your application. So far, you’ve probably created your fragments using their default constructors and then either injected dependencies using a library like Dagger or Koin or just initialized them inside the fragment at some point before they’re used. If your fragment has an empty default constructor, there’s no need to use FragmentFactory.

However, if your fragment takes arguments in its constructor, you must use a FragmentFactory; otherwise, a Fragment.InstantiationException will be thrown since the default FragmentFactory will not know how to instantiate your fragment.

Mobile and Web App Development

Are you planning to expand your business online? We will translate your ideas into intelligent and powerful solutions.

Share your project with us

×

Write a comment

avatar