Creating a Fragment: constructor vs newInstance()

from stack overflow

and another chapter

I recently grew tired of constantly having to know String keys to pass arguments into Bundles when creating my Fragments. So I decided to make constructors for my Fragments that would take the parameters I wanted to set, and put those variables into the Bundles with the correct String keys, therefore eliminating the need for other Fragments and Activities needing to know those keys.

public ImageRotatorFragment() {
    super();
    Log.v(TAG, "ImageRotatorFragment()");
}

public ImageRotatorFragment(int imageResourceId) {
    Log.v(TAG, "ImageRotatorFragment(int imageResourceId)");

    // Get arguments passed in, if any
    Bundle args = getArguments();
    if (args == null) {
        args = new Bundle();
    }
    // Add parameters to the argument bundle
    args.putInt(KEY_ARG_IMAGE_RES_ID, imageResourceId);
    setArguments(args);
}

And then I pull out those arguments like normal.

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.v(TAG, "onCreate");

    // Set incoming parameters
    Bundle args = getArguments();
    if (args != null) {
        mImageResourceId = args.getInt(KEY_ARG_IMAGE_RES_ID, StaticData.getImageIds()[0]);
    }
    else {
        // Default image resource to the first image
        mImageResourceId = StaticData.getImageIds()[0];
    }
}

However, Lint took issue with this, saying not to have subclasses of Fragment with constructors with other parameters, requiring me to use @SuppressLint("ValidFragment") to even run the app. The thing is, this code works perfectly fine. I can use ImageRotatorFragment(int imageResourceId) or the old school method ImageRotatorFragment() and call setArguments() manually on it. When Android needs to recreate the Fragment (orientation change or low memory), it calls the ImageRotatorFragment() constructor and then passes the same argument Bundle with my values, which get set correctly.

So I have been searching for the "suggested" approach and see a lot of examples using newInstance() to create Fragments with parameters, which seems to do the same thing my constructor is. So I made my own to test it, and it works just as flawlessly as before, minus Lint whining about it.

public static ImageRotatorFragment newInstance(int imageResourceId) {
    Log.v(TAG, "newInstance(int imageResourceId)");

    ImageRotatorFragment imageRotatorFragment = new ImageRotatorFragment();

    // Get arguments passed in, if any
    Bundle args = imageRotatorFragment.getArguments();
    if (args == null) {
        args = new Bundle();
    }
    // Add parameters to the argument bundle
    args.putInt(KEY_ARG_IMAGE_RES_ID, imageResourceId);
    imageRotatorFragment.setArguments(args);

    return imageRotatorFragment;
}

I personally find that using constructors is a much more common practice than knowing to use newInstance() and passing parameters. I believe you can use this same constructor technique with Activities and Lint will not complain about it. So basically my question is, why does Google not want you to use constructors with parameters for Fragments?

My only guess is so you don't try to set an instance variable without using the Bundle, which won't get set when the Fragment gets recreated. By using a static newInstance() method, the compiler won't let you access an instance variable.

public ImageRotatorFragment(int imageResourceId) {
    Log.v(TAG, "ImageRotatorFragment(int imageResourceId)");

    mImageResourceId = imageResourceId;
}

I still don't feel like this is enough reason to disallow the use of parameters in constructors. Anyone else have insight into this?

1#

I personally find that using constructors is a much more common practice than knowing to use newInstance() and passing parameters.

The factory method pattern is used fairly frequently in modern software development.

So basically my question is, why does Google not want you to use constructors with parameters for Fragments?

You answered your own question:

My only guess is so you don't try to set an instance variable without using the Bundle, which won't get set when the Fragment gets recreated.

Correct.

I still don't feel like this is enough reason to disallow the use of parameters in constructors.

You are welcome to your opinion. You are welcome to disable this Lint check, either on a per-constructor or per-workspace fashion.

#2

EDIT: Although this works and can be useful, it is risky as private fields once set using this pattern maybe will not be retained when changing orientation and the references, usually from a previously viewed fragment, may be cleared by the system reclaiming memory. Using a bundle and making your objects parseable is advised.

I have come across a nice instantiation pattern using newInstance that means you don't have to call setArguments or getArguments which means you are not limited to just the data types you can set in a bundle.

Fragments need to have empty constructors to enable the system to re-instantiate and fragment constructors with parameters won't get called when re-instantiating the fragment. Hence using newInstance is a much better idea to actually add object references and data to your fragment instance.

I also don't really like using setArguments, getArguments. It is extra code and null checks and just seems restrictive as you can only set Parcelable's and primitive types. You also need extra boiler plate code to set up static key name strings etc etc...

So, you can achieve the same result but with viewer restrictions using newInstance like this:

private int resourceId;
private SomeView someView; 
private OtherObject otherObject;

// empty constructor to allow fragment re-instantiation
public ImageRotatorFragment() {}

public static ImageRotatorFragment newInstance
(int resourceId, SomeView someView, OtherObject otherObject) {
    ImageRotatorFragment fragment = new ImageRotatorFragment();
    fragment.resourceId = resourceId;
    fragment.someView = someView;
    fragment.otherObject = otherObject;
    return fragment;
}

If done this way, you can access the private member variables/fields of your fragment anywhere in your code with out have to call getArguments etc....

Hopefully this helps with writing cleaner more flexible code.

1.But... your private fields will be lost if the screen is rotated. Calling setArguments internally avoids that. The point of the question was what's the difference between using a constructor and a static newInstance. (Not much as far as I can see). Writing directly to private fields could equally be done using a constructor. –  Stuart Whitehouse

Ah, didn't realise that would happen. How then can you pass objects into fragments that are not primitive or parcelable and keep references to them? –  domji84 Sep 10 '13 at 11:28

2.I'm the wrong person to ask - that's why I found this question ;-) But AFAIK you'll need to bundle them somehow to either add them to the arguments or stored state of the fragment. In my case all I needed to actually do was store the ID of the object (a Guid which can be converted to/from a string) and that lets me recover the object from the data store if the activity is re-created. –  Stuart Whitehouse Sep 10 '13 at 14:03

3.@domji84 while this code will work when you first instantiate the Fragment. If the screen rotates or the Fragment is destroyed for memory needs, when it's recreated it will have it's empty constructor called and it will not set those instance variables again. This solution would only work if you also saved all of this state in onSaveInstanceState() and restored it in onCreate(), which means you still would have to use a Bundle. –  Steven Byle Sep 19 '13 at 13:32

public class TestFragment extends Fragment {
    private static final String TAG = "TestFragment";
    private String hello;// = "hello android";
    private String defaultHello = "default value";

    static TestFragment newInstance(String s) {
        TestFragment newFragment = new TestFragment();
        Bundle bundle = new Bundle();
        bundle.putString("hello", s);
        newFragment.setArguments(bundle);
        return newFragment;

    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "TestFragment-----onCreate");
        Bundle args = getArguments();
        hello = args != null ? args.getString("hello") : defaultHello;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
        Log.d(TAG, "TestFragment-----onCreateView");
        View view = inflater.inflate(R.layout.lay1, container, false);
        TextView viewhello = (TextView) view.findViewById(R.id.tv_hello);
        viewhello.setText(hello);
        return view;

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "TestFragment-----onDestroy");
    }

}

 

 

posted on 2014-01-14 16:03  洛易  阅读(5216)  评论(0编辑  收藏  举报