12

I am attempting to save and restore a view hierarchy consisting of a table of buttons. The number of table rows and buttons required in the table is not known until runtime, and are added programmatically to an inflated xml layout in my Activity's onCreate(Bundle) method. My question is: can the final table be saved and restored using Android's default view saving/restoring implementation?

An example of my current attempt is below. On the initial run, the table builds as expected. When the activity is destroyed (by rotating the device), the rebuilt view shows only an empty TableLayout with no children.

The xml file referenced in setContentView(int) includes, among other things, the empty TableLayout that the buttons are added to.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Setup this activity's view.
    setContentView(R.layout.game_board);

    TableLayout table = (TableLayout) findViewById(R.id.table);

    // If this is the first time building this view, programmatically
    // add table rows and buttons.
    if (savedInstanceState == null) {
        int gridSize = 5;
        // Create the table elements and add to the table.
        int uniqueId = 1;
        for (int i = 0; i < gridSize; i++) {
            // Create table rows.
            TableRow row = new TableRow(this);
            row.setId(uniqueId++);
            for (int j = 0; j < gridSize; j++) {
                // Create buttons.
                Button button = new Button(this);
                button.setId(uniqueId++);
                row.addView(button);
            }
            // Add row to the table.
            table.addView(row);
       }
    }
}

My understanding is that Android saves the state of views as long as they have an ID assigned to them, and restores the views when the activity is recreated, but right now it seems to reinflate the xml layout and nothing more. When debugging the code, I can confirm that onSaveInstanceState() is called on each Button in the table, but onRestoreInstanceState(Parcelable) is not.

Cœur
  • 37,241
  • 25
  • 195
  • 267
happydude
  • 3,869
  • 2
  • 23
  • 41

3 Answers3

12

After searching through the source code for Activity, View and ViewGroup, I learned that the programmatically added views must be programmatically added and assigned the same ID each time onCreate(Bundle) is called. This is true regardless of whether it is creating the view for the first time, or recreating the view after destroying the activity. The saved instance state of the programmatically added views is then restored during the call to Activity.onRestoreInstanceState(Bundle). The easiest answer to the code above is simply to remove the check for savedInstanceState == null.

protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    // Setup this activity's view. 
    setContentView(R.layout.game_board); 

    TableLayout table = (TableLayout) findViewById(R.id.table); 

    int gridSize = 5;
    // Create the table elements and add to the table. 
    int uniqueId = 1; 
    for (int i = 0; i < gridSize; i++) { 
        // Create table rows. 
        TableRow row = new TableRow(this); 
        row.setId(uniqueId++);
        for (int j = 0; j < gridSize; j++) {
            // Create buttons.
            Button button = new Button(this);
            button.setId(uniqueId++);
            row.addView(button);
        }
        // Add row to the table.
        table.addView(row);
   }
}
happydude
  • 3,869
  • 2
  • 23
  • 41
  • 4
    That's right: the framework saves and restores the state of each view, but it doesn't save what views exist. That's why you have to call `setContentView()` regardless of whether `savedInstanceState` is null, and in the same way you have to create any dynamic views yourself inside `onCreate()` (or `onCreateView()` for fragments). Note that if you add views later (say, in `onStart()`), that's too late for their contents to be restored. – Dan Hulme Sep 27 '12 at 09:12
  • 1
    So basically, if my Activity has several views and fragments generated via user interaction, I need to remember each ID, each view and each type of Fragment? What is the reason of this choce? Why don't they simply reinstate everything as it were when the app was killed? – Antonio Sesto Mar 11 '15 at 15:34
  • 1
    What if the views cannot be added during onCreate? – Antonio Sesto Mar 11 '15 at 15:35
  • @AntonioSesto Excellent explanation. Only I want to correct that view state is restored after onStart(). Then it's safe to create view there (https://developer.android.com/reference/android/app/Activity.html#onRestoreInstanceState(android.os.Bundle)) When it is not safe is later . – lujop Oct 05 '16 at 11:34
0

If it works with Views with an ID, why not give each button an ID in the table when you are creating them? See if that works.

How can I assign an ID to a view programmatically?

Community
  • 1
  • 1
AJcodez
  • 31,780
  • 20
  • 84
  • 118
0

This line recreates an empty layout:

setContentView(R.layout.game_board);

Besides, you assign equal id's to rows and buttons. You should have used one counter for both the rows and buttons:

    int idCounter = 1;
    for (int i = 0; i < gridSize; i++) {
        TableRow row = new TableRow(this);
        row.setId(idCounter++);
        for (int j = 0; j < gridSize; j++) {
            Button button = new Button(this);
            button.setId(idCounter++);
            row.addView(button);
        }
        ...
    }
a.ch.
  • 8,285
  • 5
  • 40
  • 53
  • Thank you for the response, the code above has been edited to show that assigning a unique ID to all programmatically added views has the same result. How should I handle setting the content view on the first and subsequent onCreate() calls? – happydude Mar 15 '12 at 14:40