Setting arguments to fragments vs saving in a static variable

from the CommonsWare Community archives

At June 3, 2018, 9:03pm, Shahood asked:

Hi,

I can’t say if the issue I’m facing relates to fragments in general or viewpager in specific.

I’m gonna take ViewPager\Fragments sample project as my example here. I just tried to save the position via a static variable (pos) instead of using the setArguments() menthod in the following manner. I was testing if sending the data to a fragment without using setArguments(), stands as an option or not, because it might not be possible to use setArguments() if something that we want to send to a fragment, is not serializable.

public class EditorFragment extends Fragment {
    private static final String KEY_POSITION = "position";
    static int pos;

    static EditorFragment newInstance(int position) {
        EditorFragment frag = new EditorFragment();
        pos = position;
//        Bundle args = new Bundle();
//        args.putInt(KEY_POSITION, position);
//        frag.setArguments(args);
        return (frag);
    }

What happens is that the positions get messed up this way. Somehow, the static variable is creating this problem but I can’t understand why.

Can you pl explain why this is happening?


At June 3, 2018, 9:19pm, mmurphy replied:

I was testing if sending the data to a fragment without using setArguments(), stands as an option or not, because it might not be possible to use setArguments() if something that we want to send to a fragment, is not serializable.

Use ViewModel (see Android’s Architecture Components).

What happens is that the positions get messed up this way. Somehow, the static variable is creating this problem but I can’t understand why.

There are N fragments, one per page in the ViewPager. There is one static field. That static field can only hold one position, not N. So, if N>1, the pos field will be wrong for N-1 fragments.


At June 3, 2018, 10:04pm, Shahood replied:

I was testing if sending the data to a fragment without using setArguments(), stands as an option or not, because it might not be possible to use setArguments() if something that we want to send to a fragment, is not serializable.

Use ViewModel (see Android’s Architecture Components).

Actually, the problem arose when I actually wanted to send a Word object to the fragment.
Following are the changes I made to the same sample project:

Word.class:

public class Word implements Serializable {
    public SpannableString text;

    public Word() {
    }
}

EditorFragment.class:

public class EditorFragment extends Fragment {
    private static final String KEY_POSITION = "position";
    static int pos;

    static EditorFragment newInstance(int position, Word word) {
        EditorFragment frag = new EditorFragment();
//        pos = position;
        Bundle args = new Bundle();
        args.putInt(KEY_POSITION, position);
        args.putSerializable("word", word);
        frag.setArguments(args);

        return (frag);
    }

    @Override
    public View onCreateView(LayoutInflater inflater,
                             ViewGroup container,
                             Bundle savedInstanceState) {
        View result = inflater.inflate(R.layout.editor, container, false);
        EditText editor = (EditText) result.findViewById(R.id.editor);
        int position = getArguments().getInt(KEY_POSITION, -1);
        editor.setText(String.format(getString(R.string.hint), position + 1));
        editor.append("\n");
        editor.append(((Word) getArguments().getSerializable("word")).text);
        return (result);
    }
}

SampleAdapter.class:

public class SampleAdapter extends FragmentPagerAdapter {

    Word word;

    public SampleAdapter(FragmentManager mgr) {
        super(mgr);
        word = new Word();
        SpannableString string = new SpannableString("Test Text");
        StyleSpan span = new StyleSpan(Typeface.BOLD);
        string.setSpan(span, 0, string.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        word.text = string;
    }

    @Override
    public int getCount() {
        return (10);
    }

    @Override
    public Fragment getItem(int position) {
        return (EditorFragment.newInstance(position, word));
    }
}

It does show the “Test Text” as bold, as desired but if i tap on Home button, it throws a

java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = com.commonsware.android.pager.Word)

which is

Caused by: java.io.NotSerializableException: android.text.SpannableString

So, probably since SpannableString is not Serializable, I am not able to send a Word object, although Serializable. OTOH, if i save the object as a static variable, it causes the other issue as reported earlier and already explained by you.

So, what choice are we left with in this case? Do you still think ViewModel would be helpful in this regard?


At June 3, 2018, 10:29pm, mmurphy replied:

You have three separate-but-related issues:

  1. How do you get the Word to the fragment?
  2. How do you hold onto the Word across configuration changes?
  3. How do you get the Word back if Android terminates your process (after the user moves your app to the background), but the user returns to you while you still have a live task (within ~30 minutes)

If all you care about is #1, just call a method on the fragment.

If all you care about is #1 and #2, consider a ViewModel.

If you want to handle all three, you will need to decide what to do in the process-termination scenarios. Perhaps this Word is coming from disk, and so you’ll just reload it. Perhaps you want the Word to be part of your saved instance state Bundle (e.g., convert it to/from HTML, since a String can go in a Bundle). Perhaps there’s a server involved. That part really depends a lot on what your app is.


At June 3, 2018, 11:29pm, Shahood replied:

If all you care about is #1, just call a method on the fragment.

How do I go about it if I want to get the Word to the fragment at the time of creating it? onCreateView() gets called before any other method.

Also, is making Word Parcelable an option, considering that it contains a SpannableString which ain’t Parcelable or Serializable?


At June 4, 2018, 11:29am, mmurphy replied:

How do I go about it if I want to get the Word to the fragment at the time of creating it?

That is not a recommended pattern. At most, pass in short identifiers, and have the fragment pull the data from wherever the data is coming from.

Also, is making Word Parcelable an option, considering that it contains a SpannableString which ain’t Parcelable or Serializable?

Spans themselves are Parcelable, at least most of the stock ones. Presumably there are recipes for treating a SpannedString as a Parcelable, though I have not investigated it recently. The only thing I know of “off the top of my head” is converting the SpannedString to and from HTML, using the Html class, but that only works for span patterns supported by the Html parser and serializer.


At June 4, 2018, 12:18pm, Shahood replied:

Now I’m thinking on the lines of saving the raw data in Word and other classes and applying the spans just before presenting the data in the views. However, it would be really challenging to find out where in a sentence I need to apply which of the spans. For that, I’m planning to append some flags/tags with the relevant piece of string while saving it in Word and other classes, searching for the relevant flag in the adapter and applying the relevant span. Sounds like an uphill task but let’s see.
Would appreciate suggestions if any.

Thanks for all the help though!