Menus

The top-right area of the app bar is reserved for the app bar’s menu. The menu consists of action items (sometimes also referred to as menu items), which can perform an action on the current screen or on the app as a whole. You will add an action item to allow the user to create a new crime.

Your new action item will require a string resource for its label. Open res/values/strings.xml and add a string label describing your new action.

Listing 14.1  Adding a string for menu (res/values/strings.xml)

<resources>
    ...
    <string name="crime_solved_label">Solved</string>
    <string name="new_crime">New Crime</string>

</resources>

Defining a menu in XML

Menus are a type of resource, similar to layouts. You create an XML description of a menu and place the file in the res/menu directory of your project. Android generates a resource ID for the menu file that you then use to inflate the menu in code.

In the project tool window, right-click the res directory and select NewAndroid resource file. Name the menu resource fragment_crime_list, change the Resource type to Menu, and click OK (Figure 14.3).

Figure 14.3  Creating a menu file

Creating a menu file

Here, you use the same naming convention for menu files as you do for layout files. Android Studio will generate res/menu/fragment_crime_list.xml, which has the same name as your CrimeListFragment’s layout file but lives in the menu folder. In the new file, switch to the text view and add an item element, as shown in Listing 14.2.

Listing 14.2  Creating a menu resource for CrimeListFragment (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"/>
</menu>

The showAsAction attribute refers to whether the item will appear in the app bar itself or in the overflow menu. You have piped together two values, ifRoom and withText, so the item’s icon and text will appear in the app bar if there is room. If there is room for the icon but not the text, then only the icon will be visible. If there is no room for either, then the item will be relegated to the overflow menu.

If you have items in the overflow menu, those items will be represented by the three dots on the far-right side of the app bar, as shown in Figure 14.4.

Figure 14.4  Overflow menu in the app bar

Overflow menu in the app bar

Other options for showAsAction include always and never. Using always is not recommended; it is better to use ifRoom and let the OS decide. Using never is a good choice for less-common actions. In general, you should only put action items that users will access frequently in the app bar to avoid cluttering the screen.

The app namespace

Notice that fragment_crime_list.xml uses the xmlns tag to define a new namespace, app, which is separate from the usual android namespace declaration. This app namespace is then used to specify the showAsAction attribute.

This unusual namespace declaration exists for legacy reasons with the AppCompat library. The app bar APIs (called action bar at the time) were first added in Android 3.0. Originally, the AppCompat library was created to bundle a compatibility version of the action bar into apps supporting earlier versions of Android, so that the action bar would exist on any device, even those that did not support the native action bar. On devices running Android 2.3 or older, menus and their corresponding XML did exist, but the android:showAsAction attribute was only added with the release of the action bar.

The AppCompat library defines its own custom showAsAction attribute and does not look for the native showAsAction attribute.

Creating the menu

In code, menus are managed by callbacks from the Activity class. When the menu is needed, Android calls the Activity function onCreateOptionsMenu(Menu).

However, your design calls for code to be implemented in a fragment, not an activity. Fragment comes with its own set of menu callbacks, which you will implement in CrimeListFragment. The functions for creating the menu and responding to the selection of an action item are:

    onCreateOptionsMenu(menu: Menu, inflater: MenuInflater)
    onOptionsItemSelected(item: MenuItem): Boolean

In CrimeListFragment.kt, override onCreateOptionsMenu(Menu, MenuInflater) to inflate the menu defined in fragment_crime_list.xml.

Listing 14.3  Inflating a menu resource (CrimeListFragment.kt)

class CrimeListFragment : Fragment() {
    ...
    override fun onDetach() {
        super.onDetach()
        callbacks = null
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.fragment_crime_list, menu)
    }
    ...
}

Within this function, you call MenuInflater.inflate(Int, Menu) and pass in the resource ID of your menu file. This populates the Menu instance with the items defined in your file.

Notice that you call through to the superclass implementation of onCreateOptionsMenu(…). This is not required, but we recommend calling through as a matter of convention. That way, any menu functionality defined by the superclass will still work. However, it is only a convention – the base Fragment implementation of this function does nothing.

The FragmentManager is responsible for calling Fragment.onCreateOptionsMenu(Menu, MenuInflater) when the activity receives its onCreateOptionsMenu(…) callback from the OS. You must explicitly tell the FragmentManager that your fragment should receive a call to onCreateOptionsMenu(…). You do this by calling the following Fragment function:

    setHasOptionsMenu(hasMenu: Boolean)

Define CrimeListFragment.onCreate(Bundle?) and let the FragmentManager know that CrimeListFragment needs to receive menu callbacks.

Listing 14.4  Receiving menu callbacks (CrimeListFragment.kt)

class CrimeListFragment : Fragment() {
    ...
    override fun onAttach(context: Context) {
        ...
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setHasOptionsMenu(true)
    }
    ...
}

You can run CriminalIntent now to see your menu (Figure 14.5).

Figure 14.5  Icon for the New Crime action item in the app bar

Icon for the New Crime action item in the app bar

Where is the action item’s text? Most phones only have enough room for the icon in portrait orientation. You can long-press an icon in the app bar to reveal its title (Figure 14.6).

Figure 14.6  Long-pressing an icon in the app bar shows the title

Long-pressing an icon in the app bar shows the title

In landscape orientation, there is room in the app bar for the icon and the text (Figure 14.7).

Figure 14.7  Icon and text in the landscape app bar

Icon and text in the landscape app bar

Responding to menu selections

To respond to the user pressing the New Crime action item, you need a way for CrimeListFragment to add a new crime to the database. Add a function to CrimeListViewModel to wrap a call to the repository’s addCrime(Crime) function.

Listing 14.5  Adding a new crime (CrimeListViewModel.kt)

class CrimeListViewModel : ViewModel() {

    private val crimeRepository = CrimeRepository.get()
    val crimeListLiveData = crimeRepository.getCrimes()

    fun addCrime(crime: Crime) {
        crimeRepository.addCrime(crime)
    }
}

When the user presses an action item, your fragment receives a callback to the function onOptionsItemSelected(MenuItem). This function receives an instance of MenuItem that describes the user’s selection.

Although your menu only contains one action item, menus often have more than one. You can determine which action item has been selected by checking the ID of the MenuItem and then respond appropriately. This ID corresponds to the ID you assigned to the MenuItem in your menu file.

In CrimeListFragment.kt, implement onOptionsItemSelected(MenuItem) to respond to MenuItem selection by creating a new Crime, saving it to the database, and then notifying the parent activity that the new crime has been selected.

Listing 14.6  Responding to menu selection (CrimeListFragment.kt)

class CrimeListFragment : Fragment() {
    ...
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.fragment_crime_list, menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.new_crime -> {
                val crime = Crime()
                crimeListViewModel.addCrime(crime)
                callbacks?.onCrimeSelected(crime.id)
                true
            }
            else -> return super.onOptionsItemSelected(item)
        }
    }
    ...
}

Notice that this function returns a Boolean value. Once you have handled the MenuItem, you should return true to indicate that no further processing is necessary. If you return false, menu processing will continue by calling the hosting activity’s onOptionsItemSelected(MenuItem) function (or, if the activity hosts other fragments, the onOptionsItemSelected function will get called on those fragments). The default case calls the superclass implementation if the item ID is not in your implementation.

In this brave new world where you can add crimes yourself, the seed database data you uploaded is no longer necessary. Remove the database files to start fresh with an empty list of crimes: Open the Device File Explorer tool window. Expand the data/data folder. Locate and expand the folder with your package name. Right-click the databases folder and choose Delete from the menu that appears. When you are done, the database folder will be gone (Figure 14.8).

Figure 14.8  No more database files

No more database files

Note that the folders remaining in the data/data/your.package.name folder may look slightly different from the ones in Figure 14.8. That is OK, so long as the databases folder is gone.

Run CriminalIntent. You should see an empty list to start with. Try out your new menu item to add a new crime. You should see the new crime appear in the crime list (Figure 14.9).

Figure 14.9  New crime flow

New crime flow

The empty list that you see before you add any crimes can be disconcerting. If you tackle the challenge in the section called Challenge: An Empty View for the RecyclerView at the end of this chapter, you will present a helpful clue when the list is empty.

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

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