Trevor Giddings
Trevor Giddings

Reputation: 397

Android Fragment's view is duplicated on Activity Recreation

When an activity spawns a fragment and is later recreated (eg. by rotating the screen), the view associated with the fragment is duplicated with only one being destroyed when the fragment is later destroyed.

This happens if and only if the activity calls super.onSaveInstanceState either directly in its override of onSaveInstanceState or by simply not overriding the callback.

minimum code to reproduce: MainActivity.java:

package com.example.trevor.test;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.CheckBox;
import android.widget.CompoundButton;

/**
 * Created by trevor on 11/11/16.
 */

public class MainActivity extends Activity {
    MainFragment fragment = new MainFragment();
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CheckBox checkbox = (CheckBox)findViewById(R.id.checkBox);
        checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                if(b)
                {
                    getFragmentManager().beginTransaction().add(R.id.container,fragment).commit();
                }
                else
                {
                    getFragmentManager().beginTransaction().remove(fragment).commit();
                }
            }
        });
    }
}

MainFragment.java:

package com.example.trevor.test;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by trevor on 11/11/16.
 */

public class MainFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        return inflater.inflate(R.layout.fragment_main,container,false);
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <CheckBox
        android:text="CheckBox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/checkBox" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/container">

    </FrameLayout>
</LinearLayout>

fragment_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:text="Open"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/textView" />
</LinearLayout>

AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.trevor.test">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

    </application>

</manifest>

Expected behavior: checking the box causes the word "open" to appear below. unchecking causes the word to dissapear.

Actual behavour: checking the box causes the word "open" to appear below. if the screen is then rotated, the word "open" becomes darker and unchecking the box causes the word to become its normal shade.

Upvotes: 0

Views: 656

Answers (1)

Mike M.
Mike M.

Reputation: 39191

The Fragment you're adding initially is being restored automatically when the Activity is recreated. That's standard behavior for Fragments. Additionally, the CheckBox's checked state is being restored after the Activity recreation, so its onCheckedChanged() method is firing again, and loading another instance of the Fragment. If you were to continue to change the orientation with the CheckBox checked, more and more Fragment instances would just keep piling up. You need to check if a Fragment instance already exists before adding one.

Since the Fragment is going to be re-added automatically, adding and removing it in the OnCheckedChangeListener is going to be cumbersome, as you'd first need to check if it's attached to the FragmentManager, and then determine if it's showing. It would probably be simpler to just hide() and show() it as needed, after ensuring that it's instantiated and added.

For example:

fragment = (MainFragment) getFragmentManager().findFragmentById(R.id.container);

checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
        if(b)
        {
            if(fragment == null) {
                fragment = new MainFragment();
                getFragmentManager().beginTransaction().add(R.id.container, fragment).commit();
            }
            else {
                getFragmentManager().beginTransaction().show(fragment).commit();
            }
        }
        else
        {
            if (fragment != null) {
                getFragmentManager().beginTransaction().hide(fragment).commit();
            }
        }
    }
});

You can then remove the initialization from MainFragment's declaration.

MainFragment fragment;

Upvotes: 2

Related Questions