Lars Nielsen
Lars Nielsen

Reputation: 150

Save state on NavigationView

I'm working with the new NavigationView, trying to implement it into a DrawerLayout, and I want to save the currently selected menu item in the menu within the NavigationView. I know that I can save/restore the selected item "manually" by saving/restoring my instance state:

public class MyActivity extends AppCompatActivity {
    private static String STATE_SELECTED_POSITION = "state_selected_position";
    private int mCurrentSelectedPosition;

    private NavigationView mNavigationView;
    private FrameLayout mContentFrame;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mNavigationView = (NavigationView) findViewById(R.id.navigation_view);
        mContentFrame = (FrameLayout) findViewById(R.id.content_frame);

        mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(MenuItem menuItem) {
                menuItem.setChecked(true);

                switch(menuItem.getItemId()) {
                    case R.id.navigation_item_1:
                        Snackbar.make(contentFrame, "Item One", Snackbar.LENGTH_SHORT).show();
                        mCurrentSelectedPosition = 0;
                        return true;
                    case R.id.navigation_item_2:
                        Snackbar.make(contentFrame, "Item Two", Snackbar.LENGTH_SHORT).show();
                        mCurrentSelectedPosition = 1;
                        return true;
                    default:
                        return false;
                }
            }
        });
    }

    // Saving the currently selected menu item (index).
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
    }

    // Restoring selected menu item.
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION, 0);
        mNavigationView.getMenu().getItem(mCurrentSelectedPosition).setChecked(true);
    }

}

I stumbled upon the NavigationView.SavedState, hoping this would be a better solution, but I'm having trouble figuring out how to implement this into my activity. Anyone who can help me figure out how to implement this? Thanks.

Upvotes: 3

Views: 2183

Answers (1)

Mauro Panzeri
Mauro Panzeri

Reputation: 106

update: as of 22.2.1 of the support library the issue has been fixed and there is no more need to save/restore the view state with the workaround mentioned below


Don't use NavigationView.SavedState

NavigationView is a view with saveEnabled set to true. This means that SavedState should be used internally by the view itself to save/restore its state automatically (E.g: on device rotation) Unfortunately the view has a confirmed bug and the state is not saved/restored correctly: https://code.google.com/p/android/issues/detail?id=175224

here you'll find a more general workaround for that issue:

public void onSaveInstance(Bundle outState) {
    ArrayList positions = findSelectedPosition();
    if(positions.size()>0) {
        outState.putIntegerArrayList(STATE_SELECTED_POSITION, positions);
    }
}

private ArrayList findSelectedPosition() {
    Menu menu = navDrawerFirstPart.getMenu();
    int count = menu.size();
    ArrayList result = new ArrayList<>();
    for (int i = 0; i < count; i++) {
        if(menu.getItem(i).isChecked()){
            result.add(i);
        }
    }
    return result;
}

public void onRestoreInstanceState(Bundle savedInstanceState) {
    if(savedInstanceState.containsKey(STATE_SELECTED_POSITION)){
        restoreSelectedPosition(savedInstanceState.getIntegerArrayList(STATE_SELECTED_POSITION));
    }
}

private void restoreSelectedPosition(ArrayList<Integer> positions) {
    Menu menu = navDrawerFirstPart.getMenu();
    for(int i=0; i<positions.size(); i++){
        menu.getItem(positions.get(i)).setChecked(true);
    }
}

Using a NavigationView.SavedState would have no pros over this solution, because the state stored in it, it's a simple Bundle where the NavigationView stores a SparseBooleanArray of the check states from the items. Besides that, the function that start all the process of saving the instance inside the NavigationView is this:

protected Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    NavigationView.SavedState state = new NavigationView.SavedState(superState);
    state.menuState = new Bundle();
    this.mMenu.savePresenterStates(state.menuState);
    return state;
}

note that savePresenterStates is part of an internal class MenuBuider: is public so it's visible, but then again as it is an internal class, should not get accessed outside of the package of the support library. And besides that, tha bundle that comes out of that function is wrong cause it's the root of the issue.

Upvotes: 4

Related Questions