0

My app dynamically creates (up to) 8 buttons each time the floatactionbutton is clicked but when the screen rotates, all the buttons disappear. I know the reason behind this and that's because I haven't set up the onSaveInstanceState on the generated views. I tried setting up the onSaveInstanceState with outState.putInt (because I felt that was appropriate since button ids are of type int) but it didn't work.

This is my Java.

public class MainActivity extends AppCompatActivity {

    int counter = 0;

    FloatingActionButton addingSemester;
    Button semesterButton;
    LinearLayout semesterLayout;
    GridLayout semesterGridLayout;

    LinearLayout.LayoutParams portraitLayoutParams = new LinearLayout.LayoutParams(
            AppBarLayout.LayoutParams.MATCH_PARENT,
            AppBarLayout.LayoutParams.WRAP_CONTENT);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        addingSemester = (FloatingActionButton) findViewById(R.id.addActionButton);
        semesterLayout = (LinearLayout) findViewById(R.id.main_layout);

        semesterGridLayout = (GridLayout)findViewById(R.id.semester_grid_layout);

        semesterButton = new Button(MainActivity.this);

    }

    @Override
    public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
        outState.putInt("semesterButton", semesterButton.getId());
        super.onSaveInstanceState(outState, outPersistentState);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        savedInstanceState.getInt("semesterButton", semesterButton.getId());
        super.onRestoreInstanceState(savedInstanceState);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater menuInflater = getMenuInflater();
        menuInflater.inflate(R.menu.main, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

            if (id == R.id.delete) {
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("Delete entry")
                        .setMessage("Are you sure you want to delete everything?")
                        .setCancelable(true)
                        .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                if (MainActivity.this.getResources().getBoolean(R.bool.is_landscape)) {
                                    semesterGridLayout.removeAllViews();
                                } else if (!MainActivity.this.getResources().getBoolean(R.bool.is_landscape)) {
                                    semesterLayout.removeAllViews();
                                }
                                counter = 0;
                            }
                        })
                        .setNegativeButton("No", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.cancel();
                            }
                        })
                        .show();
                return true;
            }


        return super.onOptionsItemSelected(item);

    }

    public void onFloatActionButtonClick(View view) {
        Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);

        double width = (size.x)/3;

        semesterButton = new Button(MainActivity.this);
        if (counter < 8) {
            semesterButton.setId(counter + 1);
            semesterButton.setText("Semester " + (counter + 1));
            semesterButton.setBackgroundColor(getColor(R.color.colorPrimary));
            semesterButton.setTextColor(Color.WHITE);
            portraitLayoutParams.setMargins(24, 24, 24, 24);

            if (MainActivity.this.getResources().getBoolean(R.bool.is_landscape)) {
                GridLayout.LayoutParams params = new GridLayout.LayoutParams();
                params.setMargins(24, 24, 24, 24);
                params.width = (int) width;
                params.height = GridLayout.LayoutParams.WRAP_CONTENT;
                semesterButton.setLayoutParams(params);
                semesterGridLayout.addView(semesterButton);
            } else if (!MainActivity.this.getResources().getBoolean(R.bool.is_landscape)) {
                semesterLayout.addView(semesterButton);
                semesterButton.setLayoutParams(portraitLayoutParams);
            }

            // these lines moved outside the if statement blocks
            counter++;
            setOnLongClickListenerForSemesterButton();

        } else if (counter == 8) {
            Toast.makeText(MainActivity.this, "You cannot add more than 8 semesters", Toast.LENGTH_SHORT).show();
        }
    }

    private void setOnLongClickListenerForSemesterButton() {
        semesterButton.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                final Button b = (Button) v;
                b.setTag(b.getText().toString());
                b.setBackgroundColor(Color.RED);
                b.setText("Delete");

                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                        builder.setTitle("Delete entry");
                        builder.setMessage("Are you sure you want to delete this entry?");
                        builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                if (MainActivity.this.getResources().getBoolean(R.bool.is_landscape)) {
                                    semesterGridLayout.removeView(b);
                                    for (int i = 0; i < semesterGridLayout.getChildCount(); i++) {
                                        ((Button) semesterGridLayout.getChildAt(i)).setText("Semester " + (i + 1));
                                    }
                                } else if (!MainActivity.this.getResources().getBoolean(R.bool.is_landscape)) {
                                    semesterLayout.removeView(b);
                                    for (int i = 0; i < semesterLayout.getChildCount(); i++) {
                                        ((Button) semesterLayout.getChildAt(i)).setText("Semester " + (i + 1));
                                    }
                                }
                                counter--;
                            }
                        });
                        builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                b.cancelLongPress();
                                b.setBackgroundColor(ContextCompat.getColor(MainActivity.this, R.color.colorPrimary));
                                b.setText(b.getTag().toString());
                                dialog.cancel();

                            }
                        });
                        builder.show();
                return true;
            }
        });
    }
}

UPDATE: For my onSaveInstaceState, I did this:

@Override
    public void onSaveInstanceState(Bundle outsave) {
        super.onSaveInstanceState(outsave);

        if (MainActivity.this.getResources().getBoolean(R.bool.is_landscape)) {
            outsave.putInt("NUMBEROFBUTTONS", semesterGridLayout.getChildCount());
        } else if (!MainActivity.this.getResources().getBoolean(R.bool.is_landscape)) {
            outsave.putInt("NUMBEROFBUTTONS", semesterLayout.getChildCount());
        }

    }

and for my onCreate, this is what I did:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        addingSemester = (FloatingActionButton) findViewById(R.id.addActionButton);
        semesterLayout = (LinearLayout) findViewById(R.id.main_layout);

        semesterGridLayout = (GridLayout)findViewById(R.id.semester_grid_layout);

        semesterButton = new Button(MainActivity.this);

        if(savedInstanceState!=null) {
            int numberofbuttons = savedInstanceState.getInt("NUMBEROFBUTTONS");

            if (MainActivity.this.getResources().getBoolean(R.bool.is_landscape)) {
                for(int i = 0; i < numberofbuttons; i++){
                    semesterLayout.addView(semesterButton);
                }
            } else if (!MainActivity.this.getResources().getBoolean(R.bool.is_landscape)) {
                for(int i = 0; i < numberofbuttons; i++){
                    semesterGridLayout.addView(semesterButton);

                }
            }
        }

    }

And this is the issue I had on my stacktrace

FATAL EXCEPTION: main
                                                                                 Process: myapp.onur.journeygpacalculator, PID: 10463
                                                                                 java.lang.NullPointerException: Attempt to invoke virtual method 'int android.widget.GridLayout.getChildCount()' on a null object reference
                                                                                     at myapp.onur.journeygpacalculator.MainActivity.onSaveInstanceState(MainActivity.java:68)

Basically the issue is that I can't get getChildCount() for my GridLayout object.

1 Answers1

0

You cannot save UI related variables between configuration changes (that's what happen when you rotate screen).

However, you have multiple options to do what you want:

You can use a fragment. If you use "setRetainInstance" on the button fragments, it should stay same between configuration changes, but such method does not allow you to deal with backstack (for reverting changes with back button). Pro : easiest way to deal. cons: meh, setRetainInstance is made to save fragment without UI elements.

You can save the number of buttons in onSaveInstanceState as you wrote it, but then you need to check the bundle on "onCreate", and if not null, get the number, and add buttons accordingly. Pro : easy. Cons: if you swap from buttons to anything or if buttons depends on some external data, doing so may change your buttons apparence.

You can manage screen rotation by yourself. I don't know how to do that but i don't recommend that option because the problem will still be there if the user swap language, region, or anything that leads to a configuration change. However, this option needs to be mentionned even if that's the worst (IMO).

Edit: as requested more details on second->

First, override onSaveInstanceState:

public void onSaveInstanceState(Bundle outsave) {
    super.onSaveInstanceState(outsave);
    outsave.putInt("NUMBEROFBUTTONS", number_of_buttons);
}

Then, during onCreate :

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if(savedInstanceState!=null) {
        int number_of_button = savedInstanceState.getInt("NUMBEROFBUTTONS");
        display_X_buttons(number_of_button); /*sum up : display number_of_button buttons as you wish display them.*/
    }
}
Community
  • 1
  • 1
Feuby
  • 708
  • 4
  • 7
  • How would you go about the second option? –  Apr 21 '17 at 00:29
  • see : http://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it That's not the same problem at all.. – Feuby Apr 21 '17 at 14:25
  • So null is when it asks for childcount when there are no children. Should I implement if (x != null) to my code? And even after that, how would that solve the overall problem? Even with views in the layout, the rotation causes the app to crash. –  Apr 21 '17 at 14:34
  • yes you should do that, or save 0 as button number in that case. It's not guaranteed that it will fully solve your problem, i'm just providing ways to deal with your problem, your implementation mail fail (like with NPE), or fail inserting buttons, or anything different, but my job here is to help you answering the first question, not programming the app for you :) – Feuby Apr 21 '17 at 14:37
  • I'm not asking you to program the app for me. There must be a way to store the instance of dynamically generated views. There are tons of apps on play store that do just that. –  Apr 21 '17 at 14:42
  • you cannot save instance of UI elements because they are destroyed on configuration change then recreated. You may thing that some apps are doing that but they are RECREATING the views and saving elements needing to be saved before destroying the view. That's what i'm trying to say you to do. If you save UI elements (or activities) through configuration changes, it will lead to memory leaks. – Feuby Apr 21 '17 at 14:53
  • Ok so I found that my problem could easily be solved by overriding the onConfigurationChanged() method but the only problem is this. The global variable counter is 0 by default and it only increments within the onFloatActionButtonClick method and keeps a track of the number of buttons I have. However, from the onConfigurationChanged method, I can't reference the current value of counter to recreate the amount of views that were lost when the orientation changed. Any idea how I can come across this? –  Apr 23 '17 at 00:39
  • onConfigurationChanged is called before or after "onCreate" ? if after, just reload the global variable as i said in 2. If before, i don't know, i'd suggest make the global variable static, but i'm not sure if static variable are keep through config change. Other possibility is a fragment without UI with setReainInstance(true); to store the variable, and resinstanciate the fragment if needed in onConfigurationChanged. I could provide you some code to do that but it's a horrible answer, and i feel like your problem would not be solved correctly doing that. – Feuby Apr 23 '17 at 01:49
  • After. The counter variable is incremented within the onFloatActionButtonClick method so how do I reach that current value? –  Apr 23 '17 at 15:31
  • i told you, this variable is global, store it in "onsaveinstancestate" and restore it in oncreate using the bundle provided. – Feuby Apr 23 '17 at 16:10