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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With