简体   繁体   中英

Android - How to stop fragment from automatically restoring its state?

I have created a whole new project to demonstrate my problem. Basically, my fragment has two RadioButton . When onCreateView, I always use radioButtonFirst.setChecked(true); . But if I check the radioButtonSecond then navigate out by pressing Back button, next time opening it, it automatically checks the radioButtonSecond . That behavior is breaking my logic.

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private WeirdFragment weirdFragment;
    private FragmentManager fragmentManager;
    private FragmentTransaction fragmentTransaction;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        weirdFragment = new WeirdFragment();
        fragmentManager = getSupportFragmentManager();
    }
    public void openFragment(View v){
        fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.container, weirdFragment);
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.commit();
    }
}

WeirdFragment.java

public class WeirdFragment extends Fragment {
    private RadioButton radioButtonFirst;
    private RadioButton radioButtonSecond;
    private Button buttonReturn;
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_weird, container, false);
        radioButtonFirst = view.findViewById(R.id.radio_button_first);
        radioButtonSecond = view.findViewById(R.id.radio_button_second);
        buttonReturn = view.findViewById(R.id.button_return);

        radioButtonFirst.setChecked(true);

        // I decide to set a listener right here, to see what happened to my radio button. So this listener will be removed in my real project.
        radioButtonSecond.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Log.v("weird", "i got here");
            }
        });
        buttonReturn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager().beginTransaction().remove(WeirdFragment.this).commit();
            }
        });
        return view;
    }
}

activity_main.xml

<Button
    android:id="@+id/button_open"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="openFragment"
    android:text="Open"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<FrameLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintTop_toBottomOf="@id/button_open" />

fragment_weird.xml

<RadioGroup
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintTop_toTopOf="parent">

    <RadioButton
        android:id="@+id/radio_button_first"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:checked="true"
        android:text="First" />

    <RadioButton
        android:id="@+id/radio_button_second"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Second" />
</RadioGroup>

<Button
    android:id="@+id/button_return"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Return"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent" />

I set a breakpoint at the log statement. That breakpoint, when triggered by user action, the stacktrace will look like this:

onCheckedChanged:31, WeirdFragment$1 (com.peanut.myweirdradiobutton)
setChecked:154, CompoundButton (android.widget)
toggle:113, CompoundButton (android.widget)
toggle:78, RadioButton (android.widget)
performClick:118, CompoundButton (android.widget)
run:19866, View$PerformClick (android.view)
handleCallback:739, Handler (android.os)
dispatchMessage:95, Handler (android.os)
loop:135, Looper (android.os)
main:5254, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
invoke:372, Method (java.lang.reflect)
run:903, ZygoteInit$MethodAndArgsCaller (com.android.internal.os)
main:698, ZygoteInit (com.android.internal.os)

But when that breakpoint is triggered from out of nowhere, the stracktrace looks like this:

onCheckedChanged:31, WeirdFragment$1 (com.peanut.myweirdradiobutton)
setChecked:154, CompoundButton (android.widget)
onRestoreInstanceState:522, CompoundButton (android.widget)
dispatchRestoreInstanceState:13740, View (android.view)
dispatchRestoreInstanceState:2893, ViewGroup (android.view)
dispatchRestoreInstanceState:2893, ViewGroup (android.view)
restoreHierarchyState:13718, View (android.view)
restoreViewState:494, Fragment (android.support.v4.app)
moveToState:1486, FragmentManagerImpl (android.support.v4.app)
moveFragmentToExpectedState:1784, FragmentManagerImpl (android.support.v4.app)
moveToState:1852, FragmentManagerImpl (android.support.v4.app)
executeOps:802, BackStackRecord (android.support.v4.app)
executeOps:2625, FragmentManagerImpl (android.support.v4.app)
executeOpsTogether:2411, FragmentManagerImpl (android.support.v4.app)
removeRedundantOperationsAndExecute:2366, FragmentManagerImpl (android.support.v4.app)
execPendingActions:2273, FragmentManagerImpl (android.support.v4.app)
run:733, FragmentManagerImpl$1 (android.support.v4.app)
handleCallback:739, Handler (android.os)
dispatchMessage:95, Handler (android.os)
loop:135, Looper (android.os)
main:5254, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
invoke:372, Method (java.lang.reflect)
run:903, ZygoteInit$MethodAndArgsCaller (com.android.internal.os)
main:698, ZygoteInit (com.android.internal.os)

So I can see that it is the onRestoreInstanceState checking my radioButtonSecond . But I don't know how to prevent it. Things I am considering:

  1. Doing my logic in onViewStateRestored. But that is rather a not clean solution.
  2. Re-create my fragment everytime I need to open it. But my initializing phase when creating a new fragment is costly, I shouldn't do that.

In my real project, this fragment is like a setting for something in my app, it has a Cancel and an OK button. So, I want the fragment is in its default state every time it is opened because I think, for example, the user might configure many many things, but then press Cancel, then it is reasonable to see all of the views are in their default states, rather than the configured one (maybe this thought is causing my problem, is it a good user experience?)

So, how to prevent my fragment from restoring its state without my permission?

If I understand you correctly, you want to reset the state of the RadioButton s before the users are leaving the Fragment in order to prepare for the case that the Fragment is reentered at some later time.

Since you want to have the first RadioButton checked each time the users reenter the Fragment after leaving it but (I suppose) not when the users just left your app for half an hour and then returned to continue right where they left off, you should use the savedInstanceState Bundle to make sure the RadioButton s have the correct state depending on the situation:

Let's introduce a field private boolean isFirstButtonChecked = true; .

Keep the OnCheckedChangeListener to track the checked state of the RadioButton s using

isFirstButtonChecked = radioButtonFirst.isChecked();

In addition to that, set the boolean to true in the OnClickListener

buttonReturn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            isFirstButtonChecked = true;
            getFragmentManager().beginTransaction().remove(WeirdFragment.this).commit();
        }
});

Now you can save the desired state in onSaveInstanceState() :

     @Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean("FIRST_RB_STATE", isFirstButtonChecked);
}

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);
    isFirstButtonChecked = savedInstanceState == null 
            ? true 
            : savedInstanceState.getBoolean("FIRST_RB_STATE");
    radioButtonFirst.setChecked(isFirstButtonChecked);
}

I'm aware that this means adding quite a lot of code. But since you are reluctant to recreate your Fragment from scratch each time I suppose it contains more than just two RadioButton s. So letting the runtime work for you instead of struggling against it makes this approach cleaner and safer than just calling super.onViewStateRestored(null); in the corresponding method.

public class MainActivity extends AppCompatActivity {
private WeirdFragment weirdFragment;
private FragmentManager fragmentManager;
private FragmentTransaction fragmentTransaction;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    fragmentManager = getSupportFragmentManager();
}
public void openFragment(View v){
    weirdFragment = new WeirdFragment();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.container, weirdFragment);
    fragmentTransaction.addToBackStack(null);
    fragmentTransaction.commit();
}

}

Problem: You are calling the same weirdFragment object always. so if you select second radiobutton last time and return , next time it will show second button second selected.

Solution: Create a new object weirdfragment object always in openFragment function.

Change these lines in your fragment:

    @Override
    public void onCreate(Bundle savedInstanceState) {
//      super.onCreate(savedInstanceState);
        super.onCreate(null); // <- to prevent the states to be restaured

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM