An Alternative Action Item

In this section, you will use what you have learned about menu resources to add an action item that lets users show and hide a subtitle displaying the number of crimes from CrimeListActivity’s toolbar.

In res/menu/fragment_crime_list.xml, add an action item that will read SHOW SUBTITLE and will appear in the toolbar if there is room. (The all-caps formatting is courtesy of the toolbar’s inherent styles; you have seen similar formatting on buttons.)

Listing 13.12  Adding SHOW SUBTITLE action item (res/menu/fragment_crime_list.xml)

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/new_crime"
        android:icon="@android:drawable/ic_menu_add"
        android:title="@string/new_crime"
        app:showAsAction="ifRoom|withText"/>

    <item
        android:id="@+id/show_subtitle"
        android:title="@string/show_subtitle"
        app:showAsAction="ifRoom"/>
</menu>

Create a new method, updateSubtitle(), that will set the subtitle of the toolbar to display the number of crimes.

Listing 13.13  Setting the toolbar’s subtitle (CrimeListFragment.java)

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    ...
}

private void updateSubtitle() {
    CrimeLab crimeLab = CrimeLab.get(getActivity());
    int crimeCount = crimeLab.getCrimes().size();
    String subtitle = getString(R.string.subtitle_format, crimeCount);

    AppCompatActivity activity = (AppCompatActivity) getActivity();
    activity.getSupportActionBar().setSubtitle(subtitle);
}
    ...

updateSubtitle() first generates the subtitle string using the getString(int resId, Object… formatArgs) method, which accepts replacement values for the placeholders in the string resource.

Next, the activity that is hosting the CrimeListFragment is cast to an AppCompatActivity. Recall that because CriminalIntent uses the AppCompat library, all activities are a subclass of AppCompatActivity, which allows you to access the toolbar. (For legacy reasons, the toolbar is still referred to as action bar in many places within the AppCompat library.)

Now that updateSubtitle() is defined, call the method when the user presses on the new action item.

Listing 13.14  Responding to SHOW SUBTITLE action item (CrimeListFragment.java)

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.new_crime:
            ...
            return true;
        case R.id.show_subtitle:
            updateSubtitle();
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

Run CriminalIntent, press the SHOW SUBTITLE item, and confirm that you can see the number of crimes in the subtitle.

Toggling the action item title

Now the subtitle is visible, but the action item still reads SHOW SUBTITLE. It would be better if the action item toggled its title and function to show or hide the subtitle.

When onOptionsItemSelected(MenuItem) is called, you are given the MenuItem that the user pressed as a parameter. You could update the text of the SHOW SUBTITLE item in this method, but the subtitle change would be lost as you rotate the device and the toolbar is re-created.

A better solution is to update the SHOW SUBTITLE MenuItem in onCreateOptionsMenu(…) and trigger a re-creation of the toolbar when the user presses on the subtitle item. This allows you to share the code for updating the action item in the case that the user selects an action item or the toolbar is re-created.

First, add a member variable to keep track of the subtitle visibility.

Listing 13.15  Keeping subtitle visibility state (CrimeListFragment.java)

public class CrimeListFragment extends Fragment {

    private RecyclerView mCrimeRecyclerView;
    private CrimeAdapter mAdapter;
    private boolean mSubtitleVisible;
    ...

Next, modify the subtitle in onCreateOptionsMenu(…) and trigger a re-creation of the action items when the user presses on the SHOW SUBTITLE action item.

Listing 13.16  Updating a MenuItem (CrimeListFragment.java)

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
    inflater.inflate(R.menu.fragment_crime_list, menu);

    MenuItem subtitleItem = menu.findItem(R.id.show_subtitle);
    if (mSubtitleVisible) {
        subtitleItem.setTitle(R.string.hide_subtitle);
    } else {
        subtitleItem.setTitle(R.string.show_subtitle);
    }
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.new_crime:
            ...
        case R.id.show_subtitle:
            mSubtitleVisible = !mSubtitleVisible;
            getActivity().invalidateOptionsMenu();
            updateSubtitle();
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

Finally, respect the mSubtitleVisible member variable when showing or hiding the subtitle in the toolbar.

Listing 13.17  Showing or hiding the subtitle (CrimeListFragment.java)

private void updateSubtitle() {
    CrimeLab crimeLab = CrimeLab.get(getActivity());
    int crimeCount = crimeLab.getCrimes().size();
    String subtitle = getString(R.string.subtitle_format, crimeCount);

    if (!mSubtitleVisible) {
        subtitle = null;
    }

    AppCompatActivity activity = (AppCompatActivity) getActivity();
    activity.getSupportActionBar().setSubtitle(subtitle);
}

Run CriminalIntent and modify the subtitle visibility in the toolbar. Notice that the action item text reflects the existence of the subtitle.

Just one more thing...

Programming in Android is often like being questioned by the TV detective Columbo. You think you have the angles covered and are home free. But Android always turns at the door and says, Just one more thing...

Here, there are actually two more things. First, when creating a new crime and then returning to CrimeListActivity with the Back button, the number of crimes in the subtitle will not update to reflect the new number of crimes. Second, the visibility of the subtitle is lost across rotation.

Tackle the update issue first. The solution to this problem is to update the subtitle text when returning to CrimeListActivity. Trigger a call to updateSubtitle() in onResume(). Your updateUI() method is already called in onResume() and onCreateView(…). Add a call to updateSubtitle() to the updateUI() method.

Listing 13.18  Showing the most recent state (CrimeListFragment.java)

private void updateUI() {
    CrimeLab crimeLab = CrimeLab.get(getActivity());
    List<Crime> crimes = crimeLab.getCrimes();

    if (mAdapter == null) {
        mAdapter = new CrimeAdapter(crimes);
        mCrimeRecyclerView.setAdapter(mAdapter);
    } else {
        mAdapter.notifyDataSetChanged();
    }

    updateSubtitle();
}

Run CriminalIntent, show the subtitle, create a new crime, and press the Back button on the device to return to CrimeListActivity. The number of crimes in the toolbar will be correct.

Now repeat these steps, but instead of using the Back button, use the Up button in the toolbar. The visibility of the subtitle will be reset. Why does this happen?

An unfortunate side effect of the way hierarchical navigation is implemented in Android is that the activity that you navigate up to will be completely re-created from scratch. This means that any instance variables will be lost, and it also means that any saved instance state will be lost as well. This parent activity is seen as a completely new activity.

There is not an easy way to ensure that the subtitle stays visible when navigating up. One option is to override the mechanism that navigates up. You could call finish() on the CrimePagerActivity to pop back to the previous activity. This would work perfectly well in CriminalIntent but would not work in apps with a more realistic hierarchy, as this would only pop back one activity.

Another option is to pass information about the subtitle visibility as an extra to CrimePagerActivity when it is started. Then, override the getParentActivityIntent() method in CrimePagerActivity to add an extra to the intent that is used to re-create the CrimeListActivity. This solution requires CrimePagerActivity to know the details of how its parent works.

Both of these solutions are less than ideal, and there is not a great alternative. For this reason, you are going to let this issue ride; making the user click SHOW SUBTITLE is not a terrible burden.

Now that the subtitle always displays the correct number of crimes, solve the rotation issue. To fix this problem, save the mSubtitleVisible instance variable across rotation with the saved instance state mechanism.

Listing 13.19  Saving subtitle visibility (CrimeListFragment.java)

public class CrimeListFragment extends Fragment {

    private static final String SAVED_SUBTITLE_VISIBLE = "subtitle";
    ...
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        ...
        if (savedInstanceState != null) {
            mSubtitleVisible = savedInstanceState.getBoolean(SAVED_SUBTITLE_VISIBLE);
        }

        updateUI();

        return view;
    }

    @Override
    public void onResume() {
        ...
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(SAVED_SUBTITLE_VISIBLE, mSubtitleVisible);
    }
}

Run CriminalIntent. Show the subtitle and then rotate. The subtitle should appear as expected in the re-created view (Figure 13.13).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.104.248