I have come across some very unexpected (and incredibly frustrating) functionality while trying to restore the state of a list of CheckBox
es after a screen rotation. I figured I first would try to give a textual explanation without the code, in case someone is able to determine a solution without all the gory details. If anyone needs more details I can post the code.
I have a scrolling list of complex View
s that contain CheckBox
es. I have been unsuccessful in restoring the state of these check boxes after a screen rotation. I have implemented onSaveInstanceState
and have successfully transferred the list of selected check boxes to the onCreate
method. This is handled by passing a long[]
of database ids to the Bundle
.
In onCreate()
I check the Bundle
for the array of ids. If the array is there I use it to determine which check boxes to check when the list is being built. I have created a number of test methods and have confirmed that the check boxes are being set correctly, based on the id array. As a last check I am checking the states of all check boxes at the very end of onCreate(
). Everything looks good... unless I rotate the screen.
When I rotate the screen, one of two things happens: 1) If any number of the check boxes are selected, except for the last one, all check boxes are off after a rotation. 2) If the last check box is checked before rotation, then all check boxes are checked after rotation.
Like I said, I check the state of the boxes at the very end of my onCreate()
. The thing is, the state of the boxes at the end of onCreate
is correct based on what I selected before the rotation. However, the state of the boxes on the screen does not reflect this.
In addition, I have implemented each check box's setOnCheckChangedListener()
and I have confirmed that my check boxes' state's are being altered after my onCreate
method returns.
Anyone have an idea of what is going on? Why would the state of my check boxes change after my onCreate
method returns?
Thanks in advance for your help. I have been trying to degub this for a couple days now. After I found that my check boxes were apparently changing somewhere outside my own code I figured it was time to ask around.
I had similar problem. App had several checkboxes on screen.
After rotating phone app was 'manually' setting value for all checkboxes.
This code was executed in onStart().
But on screen all checkboxes were set with value of 'last checkbox' on screen.
Android was calling onRestoreInstanceState(..) and somehow was treating all checkboxes as 'one' [last from screen].
Solution were to disable 'restoring instance state':
<RadioButton
...
android:saveEnabled="false" />
everyone. Looks like I figured this out. The state of the check boxes is being altered in onRestoreInstanceState(Bundle). This method is called after onCreate() (more precisely, after onStart()), and is another place Android recommends restoring state.
Now, I have no idea why my check boxes are being altered within onRestoreInstanceState, but at least I know that is where the problem is occurring. Surprisingly, when I override onRestoreInstanceState and do absolutely nothing (no call to super.onRestoreInstanceState) the entire Activity works perfectly.
If anyone can tell me why the check boxes' selected state is being changed in this method I would very much like to know. In my opinion this looks like a bug within the Android code itself.
If you ever find anything else more about this problem, please let me know. I faced essentially this exact same problem, and only overriding onRestoreInstanceState()
worked. Very odd.
Rpond, I have not overriden onResume, so I don't think that is the issue. Here is the Activity and associated layouts for everyone to see. In the code you'll see many Log.d statements (there were even more at one point.) With these Log statements I was able to verify that the code works exactly as I expect it to. Also, notice the onCheckChangedListener I add to each CheckBox. All it does is print a Log statement telling me the state of one of my CheckBoxes changed. It was through this that I was able to determine the state of the CheckBoxes were being altered after my onCreate returns. You'll see how I call examineCheckboxes() at the end of my onCreate. The Log statements produced from this are not what is shown on my screen after a rotation, and I can see the state of my boxes being altered afterwards (because of the onCheckChangedListener.)
SelectItems.java:
public class SelectItems extends Activity {
public static final int SUCCESS = 95485839;
public static final String ITEM_LIST = "item list";
public static final String ITEM_NAME = "item name";
// Save state constants
private final String SAVE_SELECTED = "save selected";
private DbHelper db;
private ArrayList<Long> selectedIDs;
ArrayList<CheckBox> cboxes = new ArrayList<CheckBox>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.select_items);
// Create database helper
db = new DbHelper(this);
db.open();
initViews(savedInstanceState);
examineCheckboxes();
}
private void initViews(Bundle savedState) {
initButtons();
initHeading();
initList(savedState);
}
private void initButtons() {
// Setup event for done button
Button doneButton = (Button) findViewById(R.id.done_button);
doneButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
done();
}
});
// Setup event for cancel button
Button cancelButton = (Button) findViewById(R.id.cancel_button);
cancelButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
cancel();
}
});
}
private void initHeading() {
String itemName = getIntent().getExtras().getString(ITEM_NAME);
TextView headingField = (TextView) findViewById(R.id.heading_field);
if (itemName.equals("")) {
headingField.setText("No item name!");
} else {
headingField.setText("Item name: " + itemName);
}
}
private void initList(Bundle savedState) {
// Init selected id list
if (savedState != null && savedState.containsKey(SAVE_SELECTED)) {
long[] array = savedState.getLongArray(SAVE_SELECTED);
selectedIDs = new ArrayList<Long>();
for (long id : array) {
selectedIDs.add(id);
}
Log.d("initList", "restoring from saved state");
logIDList();
} else {
selectedIDs = (ArrayList<Long>) getIntent().getExtras().get(
ITEM_LIST);
Log.d("initList", "using database values");
logIDList();
}
// Begin building item list
LayoutInflater li = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout scrollContent = (LinearLayout) findViewById(R.id.scroll_content);
Cursor cursor = db.getAllItems();
startManagingCursor(cursor);
cursor.moveToFirst();
// For each Item entry, create a list element
while (!cursor.isAfterLast()) {
View view = li.inflate(R.layout.item_element, null);
TextView name = (TextView) view.findViewById(R.id.item_name);
TextView id = (TextView) view.findViewById(R.id.item_id);
CheckBox cbox = (CheckBox) view.findViewById(R.id.checkbox);
name.setText(cursor.getString(cursor
.getColumnIndexOrThrow(DbHelper.COL_ITEM_NAME)));
final long itemID = cursor.getLong(cursor
.getColumnIndexOrThrow(DbHelper.COL_ID));
id.setText(String.valueOf(itemID));
// Set check box states based on selectedIDs array
if (selectedIDs.contains(itemID)) {
Log.d("set check state", "setting check to true for " + itemID);
cbox.setChecked(true);
} else {
Log.d("set check state", "setting check to false for " + itemID);
cbox.setChecked(false);
}
cbox.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Log.d("onClick", "id: " + itemID + ". button ref: "
+ ((CheckBox) v));
checkChanged(itemID);
}
});
//I implemented this listener just so I could see when my
//CheckBoxes were changing. Through this I was able to determine
//that my CheckBoxes were being altered outside my own code.
cbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton arg0, boolean arg1) {
Log.d("check changed", "button: " + arg0 + " changed to: "
+ arg1);
}
});
cboxes.add(cbox);
scrollContent.addView(view);
cursor.moveToNext();
}
cursor.close();
examineCheckboxes();
}
private void done() {
Intent i = new Intent();
i.putExtra(ITEM_LIST, selectedIDs);
setResult(SUCCESS, i);
this.finish();
}
private void cancel() {
db.close();
finish();
}
private void checkChanged(long itemID) {
Log.d("checkChaged", "checkChanged for: "+itemID);
if (selectedIDs.contains(itemID)) {
selectedIDs.remove(itemID);
} else {
selectedIDs.add(itemID);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
long[] array = new long[selectedIDs.size()];
for (int i = 0; i < array.length; i++) {
array[i] = selectedIDs.get(i);
}
outState.putLongArray(SAVE_SELECTED, array);
}
//Debugging method used to see what is in selectedIDs at any point in time.
private void logIDList() {
String list = "";
for (long id : selectedIDs) {
list += id + " ";
}
Log.d("ID List", list);
}
//Debugging method used to check the state of all CheckBoxes.
private void examineCheckboxes(){
for(CheckBox cbox : cboxes){
Log.d("Check Cbox", "obj: "+cbox+" checked: "+cbox.isChecked());
}
}
}
select_items.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView android:id="@+id/heading_field"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:layout_marginBottom="10dip" android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout android:id="@+id/button_layout"
android:orientation="horizontal" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_alignParentBottom="true">
<Button android:id="@+id/done_button" android:layout_weight="1"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Done" />
<Button android:id="@+id/cancel_button" android:layout_weight="1"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Cancel" />
</LinearLayout>
<ScrollView android:orientation="vertical"
android:layout_below="@id/heading_field" android:layout_above="@id/button_layout"
android:layout_width="fill_parent" android:layout_height="wrap_content">
<LinearLayout android:id="@+id/scroll_content"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
</LinearLayout>
</ScrollView>
item_element.xml:
<?xml version="1.0" encoding="utf-8"?>
<CheckBox android:id="@+id/checkbox" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_alignParentRight="true"
android:layout_marginRight="5dip" />
<TextView android:id="@+id/item_name" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:textSize="18sp"
android:layout_centerVertical="true" android:layout_toLeftOf="@id/checkbox"
android:layout_alignParentLeft="true" />
<TextView android:id="@+id/item_id" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:visibility="invisible" />
Probably the issue is that whenever onRestoreInstanceState(Bundle)
is called it resets some or all of the "settings" of your app to the start-up "defaults". The best way to solve this is through app lifecycle method calls and management. Additionally, any time something is changed in your app that you don't want to loose, save the change to the saveInstanceState(Bundle)
or create your own Bundle()
to hold the changes temporarily until you can persist the changes in a file or something, Its easy enough to pass a bundle through an Intent between Activities. How you save whatever you need to save depending on how long you need to retain the "settings".
I've run into this too. You should restore checkbox state in onRestoreInstanceState() instead of onCreate().
The activity is destroyed when screen orientation changes and onRestoreInstanceState() is called after onCreate(). Since the parent/default implementation of onRestoreInstanceState() automatically restores state in Views with IDs, it's restoring your checkboxes after onCreate() and clobbering them -- apparently due to them having the same ID (framework bug?).
http://developer.android.com/reference/android/app/Activity.html
http://developer.android.com/reference/android/app/Activity.html#onRestoreInstanceState(android.os.Bundle)
You can also use onSaveInstanceState(Bundle savedInstanceState)
and onRestoreInstanceState(Bundle savedInstanceState)
example
@Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
// EditTexts text
savedInstanceState.putString("first_et",((EditText)findViewById(R.id.et_first)).getText().toString());
// EditTexts text
savedInstanceState.putInt("first_et_v", ((EditText)findViewById(R.id.et_first)).getVisibility());
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
// EditTexts text
((EditText)findViewById(R.id.et_first)).setText(savedInstanceState.getString("first_et"));
// EditText visibility
((EditText)findViewById(R.id.et_first)).setVisibility(savedInstanceState.getInt("first_et_v"));
}
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