8

I have written android app now for a long time but now I'm facing a problem that I have never thought about. It is about the android lifecycle of Activitys and Fragments in in relation to configuration changes. For this I have create a small application with this necessary code:

public class MainActivity extends FragmentActivity {

    private final String TAG = "TestFragment";
    private TestFragment fragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FragmentManager fm = getSupportFragmentManager();
        fragment = (TestFragment) fm.findFragmentByTag(TAG);

        if (fragment == null) {
            fragment = new TestFragment();
            fm.beginTransaction().add(R.id.fragment_container, fragment, TAG).commit();
        }
    }
}

And here is my code for the TestFragment. Note that I'm calling setRetainInstance(true); in the onCreate method so the fragment is not recrated after a configuration change.

public class TestFragment extends Fragment implements View.OnClickListener {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public View onCreateView(LayoutInflater li, ViewGroup parent, Bundle bundle) {
        View rootView = li.inflate(R.layout.fragment_test, parent, false);
        Button button = (Button) rootView.findViewById(R.id.toggleButton);

        button.setOnClickListener(this);
        return rootView;
    }

    @Override
    public void onClick(View v) {
        Button button = (Button) v;
        String enable = getString(R.string.enable);

        if(button.getText().toString().equals(enable)) {
            button.setText(getString(R.string.disable));
        } else {
            button.setText(enable);
        }
    }
}

And here is the layout that my fragment is using:

<LinearLayout
    ...>

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/toggleButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/enable"/>

</LinearLayout>

My problem is that if I rotate the device the text of the Button change back to the default value. Of course the View of the Fragment is new created and inflated but the saved instance for the Views should be restored. I also have an EditText in my layout and there the text and other properties remains after the rotation. So why is the Button not restore from the Bundle by default? I have read on the developer site:

By default, the system uses the Bundle instance state to save information about each View object in your activity layout (such as the text value entered into an EditText object). So, if your activity instance is destroyed and recreated, the state of the layout is restored to its previous state with no code required by you.

I've also read a lot of answers the last days but I do not know how actual they are anymore. Please do not leave a comment or an answer with android:configChanges=... this is very bad practice. I hope someone can bring light into my lack of understanding.

MatBos
  • 2,380
  • 24
  • 34
Cilenco
  • 6,951
  • 17
  • 72
  • 152

5 Answers5

5

You should save state of your fragment in the onSaveInstanceState(Bundle outState) and restore it in the onViewCreated(View view, Bundle savedState) method. This way you will end up with the UI just as it was before configuration change.

MatBos
  • 2,380
  • 24
  • 34
  • But as I said default views should save and restore their properties automaticly – Cilenco Aug 13 '15 at 20:48
  • 1
    @Cilenco I think your assumption is wrong here - things like TextView and Buttons have `freezesText` false by default - probably for performance reasons - these components are typically read-only. Things like EditText or ScrollViews do save and restore their state - because it is expected the user will manipulate them. – jt-gilkeson Aug 23 '15 at 09:01
  • When you give a `TextView` (Button is a subclass of it) a layoutId the state will be restored by default (look [here](http://stackoverflow.com/a/6097177/2047987) for the last two comments) In my case the Button has an Id so it should restore its state. – Cilenco Aug 23 '15 at 13:00
4

TextView subclasses don't save their text by default. You need to enable freezesText="true" in the layout, or setFreezesText(true) at runtime for it to save its state.

Kevin Coppock
  • 133,643
  • 45
  • 263
  • 274
4

As per documentation, views should maintain their state without using setRetainInstance(true). Try to remove it from your onCreate, this should force the fragment to be recreated on screen rotation, hence all of it's views should be saved before rotation and restored after.

Ivan V
  • 3,024
  • 4
  • 26
  • 36
4

This stack overflow should answer your question: setRetainInstance not retaining the instance

setRetainInstance does tell the fragment to save all of its data, and for ui elements where the user has manipulated the state (EditText, ScrollView, ListView, etc) the state gets restored. That being said, normal read-only UI components get reinflated from scratch in onCreateView and have to be set again - my guess would be that their properties are not considered "data" that needs to be retained and restored - Google probably does this for performance reasons. So things like a normal Button, ImageView, or TextView need their contents set manually when they are reinflated if it differs from the initial state in the XML. (TextView's android:freezesText basically puts the TextView in a mode that uses an implementation to save it's state and restore it).

PS: According to this stack overflow Save and restore a ButtonText when screen orientation is switched, you may be able to set android:freezesText on the button to have it keep the text you set - I haven't tried it, but it makes sense.

Community
  • 1
  • 1
jt-gilkeson
  • 2,661
  • 1
  • 30
  • 40
2

Edit after Op feedback

Although this fails to answer the question, after consultation with the OP, I've decided to leave it here as a reference point of information on the topic for people who land here. Hope you find it helpful.


Try putting your setRetainInstance in onCreateView. See here

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // setRetainInstance(true);
}

@Override
public View onCreateView(LayoutInflater li, ViewGroup parent, Bundle bundle) {
    setRetainInstance(true);
    View rootView = li.inflate(R.layout.fragment_test, parent, false);
    Button button = (Button) rootView.findViewById(R.id.toggleButton);

    button.setOnClickListener(this);
    return rootView;
}

Called when the fragment's activity has been created and this fragment's view hierarchy instantiated. It can be used to do final initialization once these pieces are in place, such as retrieving views or restoring state. It is also useful for fragments that use setRetainInstance(boolean) to retain their instance, as this callback tells the fragment when it is fully associated with the new activity instance. This is called after onCreateView(LayoutInflater, ViewGroup, Bundle) and before onViewStateRestored(Bundle).

developer.android.com/reference/android/app/Fragment.html#onActivityCreated

Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:

onDestroy() will not be called (but onDetach() still will be, because the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being re-created.
onAttach(Activity) and onActivityCreated(Bundle) will still be called.

developer.android.com/reference/android/app/Fragment.html#setRetainInstance

And taken from here:

onCreate : It is called on initial creation of the fragment. You do your non graphical initializations here. It finishes even before the layout is inflated and the fragment is visible.

onCreateView : It is called to inflate the layout of the fragment i.e graphical initialization usually takes place here. It is always called sometimes after the onCreate method.

onActivityCreated : If your view is static, then moving any code to the onActivityCreated method is not necessary. But when you - for instance, fill some lists from the adapter, then you should do it in the onActivityCreated method as well as restoring the view state when setRetainInstance used to do so. Also accessing the view hierarchy of the parent activity must be done in the onActivityCreated, not sooner.

Let me know if this helps.

Community
  • 1
  • 1
  • Thank you for this answer and all the quotes. These are really helpful. But at last it changes nothing if I put `setRetainInstance()` in `onCreateView`. – Cilenco Aug 18 '15 at 06:26
  • I'm also not sure about that. On the one hand there are really helpful information in it but on the other hand it could confuse some people. Maybe you can short it a bit and set a headtitle so it stays here as additional information – Cilenco Aug 18 '15 at 17:12