ChatGPT解决这个技术问题 Extra ChatGPT

Best practice for instantiating a new Android Fragment

I have seen two general practices to instantiate a new Fragment in an application:

Fragment newFragment = new MyFragment();

and

Fragment newFragment = MyFragment.newInstance();

The second option makes use of a static method newInstance() and generally contains the following method.

public static Fragment newInstance() 
{
    MyFragment myFragment = new MyFragment();
    return myFragment;
}

At first, I thought the main benefit was the fact that I could overload the newInstance() method to give flexibility when creating new instances of a Fragment - but I could also do this by creating an overloaded constructor for the Fragment.

Did I miss something?

What are the benefits of one approach over the other? Or is it just good practice?

When there are parameters, there is no choice, and this is extensively answered here. Still, the question remains for no-argument construction of the fragment.
After learning about factory pattern's and how a calling class not instantiating an object itself helps in decoupling them, I thought this would be a strong point for the newInstance() method. Am I wrong on this? I haven't seen this specific argument spoken of as a benefit.

V
Vasily Kabunov

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


@Vlasto unfortunately static methods can't be overridden.
@yydl I think I'm missing something here, couldn't you use a constructor here anyway, one that creates the Bundle and calls setArguments() still since it will only be called by your code (and not when Android re-creates your fragment)?
@mgibson You must use a bundle if you want the data to be available when the fragment is later recreated.
Being FORCED to create a no-argument constructor for fragments is potentially the single biggest gotcha in all of programming, anywhere. It forces a complete paradigm shift in object creation and initialization. If you're new to Android and have stumbled on this thread, please read the answer above over and over and over again.
I would argue with that assertion. First, type-safeness is a language concern, not a framework concern. Secondly, IMO, the framework is stepping into the area of "things your API must never do". If I want to pass the library of congress into my fragment constructor, then I should be allowed to. The "no-args" constructor contract basically kills the use of dependency injection in fragments - major yuck.
S
Sufian

The only benefit in using the newInstance() that I see are the following:

You will have a single place where all the arguments used by the fragment could be bundled up and you don't have to write the code below everytime you instantiate a fragment. Bundle args = new Bundle(); args.putInt("someInt", someInt); args.putString("someString", someString); // Put any other arguments myFragment.setArguments(args); Its a good way to tell other classes what arguments it expects to work faithfully(though you should be able to handle cases if no arguments are bundled in the fragment instance).

So, my take is that using a static newInstance() to instantiate a fragment is a good practice.


1) How is this any different than putting the logic in a constructor? Both are single places where you include this logic. 2) How are parameters on a static factory any different than parameters on a constructor? Both tell what arguments are expected. My point being it's a different paradigm, sure, but there's no clear benefit to this over using constructors.
You cannot use custom constructors for fragment. Framework uses the no argument constructor for restoring fragments.
Yes, I agree with you there. I'm saying conceptually there is no benefit in using the static factory pattern instead of using overloaded constructors, and vice versa. Both of your points are valid in both patterns; there is no benefit of using one over the other. Android forces you to use the static factory pattern - but there is no benefit to using one or the other.
@RJCuthbertson A possible benefit would be the ability to create and return subclasses of the static factory method's class i.e. to return an appropriate subclass for the situation.
T
Taryn

There is also another way:

Fragment.instantiate(context, MyFragment.class.getName(), myBundle)

Tried this with the support library, but in onCreateView (in my fragment), the bundle passed was null, so I went with the setArguments/getArguments option and it worked (for anyone reading this).
Interesting, I have not seen this approach before. Does it have any advantages over other approaches to instantiating a Fragment?
From the developer docs, instantiate() Creates a new instance of a Fragment with the given class name. This is the same as calling its empty constructor.
Although they mentioned same as calling empty constructor. "args.setClassLoader(f.getClass().getClassLoader());" is called underneath for Bundle arguments
instantiate(...) methods are deprecated in API 28. Also, I don't think, it is good approach to create fragments.
H
Heath Borders

While @yydl gives a compelling reason on why the newInstance method is better:

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.

it's still quite possible to use a constructor. To see why this is, first we need to see why the above workaround is used by Android.

Before a fragment can be used, an instance is needed. Android calls YourFragment() (the no arguments constructor) to construct an instance of the fragment. Here any overloaded constructor that you write will be ignored, as Android can't know which one to use.

In the lifetime of an Activity the fragment gets created as above and destroyed multiple times by Android. This means that if you put data in the fragment object itself, it will be lost once the fragment is destroyed.

To workaround, android asks that you store data using a Bundle (calling setArguments()), which can then be accessed from YourFragment. Argument bundles are protected by Android, and hence are guaranteed to be persistent.

One way to set this bundle is by using a static newInstance method:

public static YourFragment newInstance (int data) {
    YourFragment yf = new YourFragment()
    /* See this code gets executed immediately on your object construction */
    Bundle args = new Bundle();
    args.putInt("data", data);
    yf.setArguments(args);
    return yf;
}

However, a constructor:

public YourFragment(int data) {
    Bundle args = new Bundle();
    args.putInt("data", data);
    setArguments(args);
}

can do exactly the same thing as the newInstance method.

Naturally, this would fail, and is one of the reasons Android wants you to use the newInstance method:

public YourFragment(int data) {
    this.data = data; // Don't do this
}

As further explaination, here's Android's Fragment Class:

/**
 * Supply the construction arguments for this fragment.  This can only
 * be called before the fragment has been attached to its activity; that
 * is, you should call it immediately after constructing the fragment.  The
 * arguments supplied here will be retained across fragment destroy and
 * creation.
 */
public void setArguments(Bundle args) {
    if (mIndex >= 0) {
        throw new IllegalStateException("Fragment already active");
    }
    mArguments = args;
}

Note that Android asks that the arguments be set only at construction, and guarantees that these will be retained.

EDIT: As pointed out in the comments by @JHH, if you are providing a custom constructor that requires some arguments, then Java won't provide your fragment with a no arg default constructor. So this would require you to define a no arg constructor, which is code that you could avoid with the newInstance factory method.

EDIT: Android doesn't allow using an overloaded constructor for fragments anymore. You must use the newInstance method.


When would one justify using android:configChanges="orientation|keyboardHidden|screenSize" ?
Android Studio now throws an error for all non-default constructors in fragments, so this no longer works.
Holy man, I wonder how many droid developers have ever written code outside of droid. This is insane that we cannot use the approach you describe. There are NO compelling arguments from any comment as to why we HAVE to use static factory method. It's even more disturbing that they would raise an error when compiling. This is definitely the best answer provided and shows that there is no benefit to sfm.
Well, there is one subtle reason. You are free to create your own constructor with arguments, but there still needs to be a no-arg constructor as well. Since classes always have an implicit no-arg constructor unless a constructor with args is explicitly defined, this means that you would have to define both your arg-constructor and a no-arg constructor explicitly, or the system would not be able to invoke any no-arg constructor. I believe this is why the recommendation is to instead use a static factory method - it simply reduces the risk of forgetting to define a no-arg constructor.
@JHH that will fail at compile time itself, so not that big of a risk. However, the issue here is that constructor overloading, a key programming paradigm, is being denied by Android.
J
Jemshit Iskenderov

Some kotlin code:

companion object {
    fun newInstance(first: String, second: String) : SampleFragment {
        return SampleFragment().apply {
            arguments = Bundle().apply {
                putString("firstString", first)
                putString("secondString", second)
            }
        }
    }
}

And you can get arguments with this:

val first: String by lazy { arguments?.getString("firstString") ?: "default"}
val second: String by lazy { arguments?.getString("secondString") ?: "default"}

Is it best practice to use the @JvmStatic annotation? @JvmStatic fun newInstance(bundle: Bundle) = SomeFragment().apply { arguments = bundle }
Z
Ziem

I disagree with yydi answer saying:

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.

I think it is a solution and a good one, this is exactly the reason it been developed by Java core language.

Its true that Android system can destroy and recreate your Fragment. So you can do this:

public MyFragment() {
//  An empty constructor for Android System to use, otherwise exception may occur.
}

public MyFragment(int someInt) {
    Bundle args = new Bundle();
    args.putInt("someInt", someInt);
    setArguments(args);
}

It will allow you to pull someInt from getArguments() latter on, even if the Fragment been recreated by the system. This is more elegant solution than static constructor.

For my opinion static constructors are useless and should not be used. Also they will limit you if in the future you would like to extend this Fragment and add more functionality to the constructor. With static constructor you can't do this.

Update:

Android added inspection that flag all non-default constructors with an error. I recommend to disable it, for the reasons mentioned above.


Another benefit of having a static method, which I did not mention above is that you cannot accidentally set properties from it.
Additionally, with regards to your point about "extend this fragment", this method would actually be really bad if you ever extend the class. Calling super will cause the setArguments() call to only be effective for either the child or parent, but not both!
@yydle you can avoid this situation by calling get arguments to initialize the child Bundle. Java way always better.
True, but that's another reason to encourage people to use the pattern Google has suggested. Of course, we all agree that your solution is 100% technically feasible. Much the same way there are many ways to do lots of things. The question, though, is whether it's the best. And I strongly feel that using the constructor does not represent the true nature of how this is supposed to work.
I agree with @yydl that static creation is better. One other benefit is dependency injection of future new dependencies - constructor is ill fitted for that and will likely cause more code change (or more constructors to be added).
Z
Zain

I'm lately here. But somethings I just known that might help you a bit.

If you are using Java, there is nothing much to change. But for Kotlin developers, here is some following snippet I think that can make you a basement to run on:

Parent fragment:

inline fun <reified T : SampleFragment> newInstance(text: String): T {
    return T::class.java.newInstance().apply {
        arguments = Bundle().also { it.putString("key_text_arg", text) }
    }
}

Normal call

val f: SampleFragment = SampleFragment.newInstance("ABC")
// or val f = SampleFragment.newInstance<SampleFragment>("ABC")

You can extend the parent init operation in child fragment class by:

fun newInstance(): ChildSampleFragment {
    val child = UserProfileFragment.newInstance<ChildSampleFragment>("XYZ")
    // Do anything with the current initialized args bundle here
    // with child.arguments = ....
    return child
}

Happy coding.


G
Gunhan

Best practice to instance fragments with arguments in android is to have static factory method in your fragment.

public static MyFragment newInstance(String name, int age) {
    Bundle bundle = new Bundle();
    bundle.putString("name", name);
    bundle.putInt("age", age);

    MyFragment fragment = new MyFragment();
    fragment.setArguments(bundle);

    return fragment;
}

You should avoid setting your fields with the instance of a fragment. Because whenever android system recreate your fragment, if it feels that the system needs more memory, than it will recreate your fragment by using constructor with no arguments.

You can find more info about best practice to instantiate fragments with arguments here.


T
Tigra

Since the questions about best practice, I would add, that very often good idea to use hybrid approach for creating fragment when working with some REST web services

We can't pass complex objects, for example some User model, for case of displaying user fragment

But what we can do, is to check in onCreate that user!=null and if not - then bring him from data layer, otherwise - use existing.

This way we gain both ability to recreate by userId in case of fragment recreation by Android and snappiness for user actions, as well as ability to create fragments by holding to object itself or only it's id

Something likes this:

public class UserFragment extends Fragment {
    public final static String USER_ID="user_id";
    private User user;
    private long userId;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        userId = getArguments().getLong(USER_ID);
        if(user==null){
            //
            // Recreating here user from user id(i.e requesting from your data model,
            // which could be services, direct request to rest, or data layer sitting
            // on application model
            //
             user = bringUser();
        }
    }

    public static UserFragment newInstance(User user, long user_id){
        UserFragment userFragment = new UserFragment();
        Bundle args = new Bundle();
        args.putLong(USER_ID,user_id);
        if(user!=null){
            userFragment.user=user;
        }
        userFragment.setArguments(args);
        return userFragment;

    }

    public static UserFragment newInstance(long user_id){
        return newInstance(null,user_id);
    }

    public static UserFragment newInstance(User user){
        return newInstance(user,user.id);
    }
}

You said: "We can't pass complex objects, for example some User model, " - It is not true, we can. Like this: User user = /*...*/ put the user to the bundle: Bundle bundle = new Bundle(); bundle.putParcelable("some_user", user); and get the user from arguments: User user = getArguments().getParcelable("some_user"); The object must be implement Parcelable interface. link
Well, yes, but when class is complex and contains referrers to another objects... I personally prefer to keep it simple, either I have object, or I don't and then need to get it
A
Amin Emadi

use this code 100% fix your problem

enter this code in firstFragment

public static yourNameParentFragment newInstance() {

    Bundle args = new Bundle();
    args.putBoolean("yourKey",yourValue);
    YourFragment fragment = new YourFragment();
    fragment.setArguments(args);
    return fragment;
}

this sample send boolean data

and in SecendFragment

yourNameParentFragment name =yourNameParentFragment.newInstance();
   Bundle bundle;
   bundle=sellDiamondFragments2.getArguments();
  boolean a= bundle.getBoolean("yourKey");

must value in first fragment is static

happy code


M
Mahesh

Best way to instantiate the fragment is use default Fragment.instantiate method or create factory method to instantiate the the fragment Caution: always create one empty constructor in fragment other while restoring fragment memory will throw run-time exception.


M
Morozov

You can use smth like this:

val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, YourFragment::class.java.name)

because this answer now is Deprecated


K
Kelvin Schoofs

Ideally we shouldn't pass anything in the fragment constructor, fragment constructor should be empty or default. Now the second question is, what if we want to pass interface variable or parameters- We should use Bundle to pass data. For Interface we can putParceble in bundle and make that interface implement parceble If possible we can implement that interface in activity and in fragment we can initialize listener in OnAttach where we have context[ (context) Listener].

So that during configuration change (e.g. Font change) the Activity recreation listener won't go uninitialize and we can avoid a null pointer exception.


V
Vadim Star

setArguments() is useless. It only brings a mess.

public class MyFragment extends Fragment {

    public String mTitle;
    public String mInitialTitle;

    public static MyFragment newInstance(String param1) {
        MyFragment f = new MyFragment();
        f.mInitialTitle = param1;
        f.mTitle = param1;
        return f;
    }

    @Override
    public void onSaveInstanceState(Bundle state) {
        state.putString("mInitialTitle", mInitialTitle);
        state.putString("mTitle", mTitle);
        super.onSaveInstanceState(state);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state) {
        if (state != null) {
            mInitialTitle = state.getString("mInitialTitle");
            mTitle = state.getString("mTitle");
        } 
        ...
    }
}

Except you have just been forced to override one more method and make a field that could have been isolated to the onViewCreated scope. It's convenience I guess, many ways to do the same thing. Also it's an easy way to check for updates done by the user (compare the bundles of getArguments and the bundle from onSaveInstanceState)
@Asagen, I like your comment about compare initial and user values. I edited code and consider that it is still uniform and clear witout getArguments stuff. What about onViewCreated scope... We can restore state bundle there. But I just prefer make onCreateView light and fast and do all heavy initialization inside a onActivityCreated because Fragment.getActivity() sometimes like to return null and because of onAttach() changes in new version of API 23.
All you did here was move set and get Arguments into saveInstanceState. You're essentially doing the same thing that is done "under the hood"
@cricket_007, or just opposite. Using saveInstanceState is "under the hood". And using of Arguments is duplication of functionality that make you double check: first Arguments values and then saveInstanceState values. Because you have to use saveInstanceState any way. What about Arguments... they are not necessary.
Arguments are the equivalent of Intent extras for fragments. They are not useless, they contain the initial parameters which is different from the current state.
S
Stefan Bogaard

I believe I have a much simpeler solution for this.

public class MyFragment extends Fragment{

   private String mTitle;
   private List<MyObject> mObjects;

   public static MyFragment newInstance(String title, List<MyObject> objects)
   MyFragment myFrag = new MyFragment();
   myFrag.mTitle = title;
   myFrag.mObjects = objects;
   return myFrag;
   }

mObjects will be cleared if MyFragment happens to be recreated (the user goes to the device home screen and later opens up the app which left off at MyFragment). You can retain mObjects by sending MyFragment a bundle as arguments.
Also, how is the static method accessing non-static member variables?
@ynnadkrap You are correct, using a bundle is the way to go here.
@OrhanC1 According to this example code, static method is not accessing to the member variables. The instance of MyFragment is accessing to its members. There is no error here. However, I do not recommend this answer to anyone because when your fragment is removed from the memory to open up some space by the android os then after restarting the activity and this fragment will be created with default empty constructor without assigning ant variables.