1

I read many articles and StackOverflow answers but still cannot understand why we need a factory method to create an instance of a Fragment.

The following Fragment classes both work fine.

Fragment with two constructors:

public class CtorFragment extends Fragment {
    private static final String KEY = "the_key";

    public CtorFragment() {
        // Android calls the default constructor so default constructor must be explicitly defined.
        // As we have another constructor, Android won't create a default constructor for us.
    }

    public CtorFragment(String s) {
        // Set the arguments.
        Bundle bundle = new Bundle();
        bundle.putString(KEY, s);

        setArguments(bundle);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View fragment = inflater.inflate(R.layout.fragment_my, container, false);
        TextView textView = (TextView) fragment.findViewById(R.id.textView);

        // Use getArguments() to get the String argument set by the constructor with parameter.
        textView.setText(getArguments().getString(KEY));
        return fragment;
    }
}

Fragment with a static factory method:

public class StaticFragment extends Fragment {
    private static final String KEY = "the_key";

    public static StaticFragment newInstance(String s) {
        StaticFragment fragment = new StaticFragment();
        // Set the arguments.
        Bundle bundle = new Bundle();
        bundle.putString(KEY, s);

        fragment.setArguments(bundle);
        return fragment;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        View fragment = inflater.inflate(R.layout.fragment_my, container, false);
        TextView textView = (TextView) fragment.findViewById(R.id.textView);

        // Use getArguments() to get the String argument set by the constructor with parameter.
        textView.setText(getArguments().getString(KEY));
        return fragment;
    }
}

Can you please explain why everyone (including Google) "strongly recommends" the one with the static factory method? Is there something critical that me and others coming from a non-Android background missing?

Is it that we have to define two methods (constructors) instead of one (static factory method) which causes all the fuss?

Mert Akcakaya
  • 3,109
  • 2
  • 31
  • 42
  • as with any factory method, it easily provides an instance to the client – Ramanlfc Aug 23 '17 at 12:32
  • @Ramanlfc How is it easier to write `StaticFragment.newInstance("text")` than to write `new CtorFragment("text")`? – Mert Akcakaya Aug 23 '17 at 12:35
  • i didn't mean typing , i mean it provides a good abstraction – Ramanlfc Aug 23 '17 at 12:36
  • @Ramanlfc I cannot see how the static method provides better abstraction over the constructor. Can you please elaborate more? Also what is special about Fragments so that we should "strongly" consider using the static factory method? – Mert Akcakaya Aug 23 '17 at 12:42
  • you may find something here http://www.androiddesignpatterns.com/2012/05/using-newinstance-to-instantiate.html. Read post comments too. – Bharatesh Aug 23 '17 at 12:44
  • `NumberFormat.getInstance()` doesn't this provide good abstraction – Ramanlfc Aug 23 '17 at 12:44
  • @Ramanlfc It is good, it is not better. – Mert Akcakaya Aug 23 '17 at 12:50
  • @skadoosh I read that post. It claims static factory methods provide good abstraction. So do constructors! I cannot understand why factory method is "better". Factory method pattern has no advantage in this context unless I am missing something special about Fragment. – Mert Akcakaya Aug 23 '17 at 12:54

2 Answers2

6

still cannot understand why we need a factory method to create an instance of a Fragment

"Need" is a strong word. You do not "need" a factory method. You do need:

  • A public zero-argument constructor, ideally empty; and

  • An organized means to set up a fragment, in a way that survives configuration changes

Can you please explain why everyone (including Google) "strongly recommends" the one with the static factory method?

If you have no explicit constructors on a Java class, you automatically get an empty public zero-argument constructor, which is what the framework needs to create a fragment.

If you create a constructor that takes arguments (e.g., CtorFragment(String s)), then you also have to remember to create the public zero-argument constructor (CtorFragment()). You might remember to do this. Many inexperienced programmers will not.

Writing a factory method achieves the same objective as the non-zero-arguments constructor, without clobbering the automatically-created zero-argument constructor.

If you are an experienced Java programmer, and you do not like factory methods, and you can read stack traces and otherwise remember to add the public zero-argument constructor, you are welcome to create additional constructors and use them.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
3

The purpose of the newInstance is mainly to set initial value for the Fragment. If we using a default constructor for the fragment, we can't set the arguments as a means for sending the initial value.


Here is the more details explanation, quoting from https://stackoverflow.com/a/9245510/4758255:

If Android decides to recreate your Fragment later, it's going to call the no-argument constructor of your fragment. So overloading the constructor is not a solution.

With that being said, the way to pass stuff to your Fragment so that they are available after a Fragment is recreated by Android is to pass a bundle to the setArguments method.

So, for example, if we wanted to pass an integer to the fragment we would use something like:

public static MyFragment newInstance(int someInt) {
    MyFragment myFragment = new MyFragment();

    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    myFragment.setArguments(args);

    return myFragment;
}

And later in the Fragment onCreate() you can access that integer by using:

getArguments().getInt("someInt", 0);

This Bundle will be available even if the Fragment is somehow recreated by Android.

Also note: setArguments can only be called before the Fragment is attached to the Activity.

This approach is also documented in the android developer reference: https://developer.android.com/reference/android/app/Fragment.html

ישו אוהב אותך
  • 28,609
  • 11
  • 78
  • 96
  • Did you see the `CtorFragment` class in the question? How is it that it can't set the arguments? – Mert Akcakaya Aug 23 '17 at 12:33
  • @MertAkcakaya, when the fragment needs to be recreated, the OS calls the default constructor and and then passes the arguments as bundle to the fragment. Please see the explanation [Do fragments really need an empty constructor?](https://stackoverflow.com/a/10450535/1449010) – Victor B Aug 23 '17 at 12:43