This is an old revision of the document!
Table of Contents
Chapter 10
Under development.
Foreword
I think Android's action bars, action items, and navigation drawers are implemented pretty terribly—the last of these being especially painful to write code for. It's as though thousands of years of desktop app development framework development never happened.
If you get frustrated in this chapter, I feel your pain. It's not you, it's Android. If there's good news, it's that Android Studio's “Blank” activity template sets up some of necessary scaffolding and boilerplate code for you. But you still need to know the inner workings.
p. 398: Creating the app
If you are creating a new app from scratch rather than expanding on what you build for Chapter 9, be sure you specify an Empty app template in the new app wizard.
p 401: "Fragment name is not a valid class name"
There is a subtlety in creating new Android fragments in Android Studio that Android Studio does a horrible job of negotiating.
If you try to create a new Blank fragment (File > New > Fragment > Fragment (Blank)) without initially proving some context regarding in what package the fragment should be generated, you may/will get a “Fragment name is not a valid class name” error. The simplest solution is to select the package in the left had project navigator pane and then create the fragment.
p 401: Proper inheritance
Android Studio might generate code that makes your new fragment derive from android.support.v4.app.Fragment
rather than android.app.Fragment
. That's not what we want.
Make sure the fragment's java file imports android.app.Fragment
rather than android.support.v4.app.Fragment
and that the class extends Fragment.
The above applies to all the fragments you'll create in this chapter.
p 401: Project dependencies
In the preceding chapter, you were instructed to remove the project's dependency on com.android.support:appcompat-v7:<whatever>
and/or com.android.support:appcompat-v4:<whatever>
to work around an Android Studio bug. However, the DrawerLayout that is the main subject of this chapter requires the v4 compat libs.
So, leave com.android.support:appcompat-v7
as a project dependency or add it back if you removed it—and hope the Android Studio bugs stay in their holes. A dependence on com.android.support:appcompat-v7
will automatically bring in v4
.
p 401: Code blocks
Here's what I ended up with:
- TopFragment.java
package com.hfad.bitsandpizzas; import android.os.Bundle; import android.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; /** * A simple {@link Fragment} subclass. */ public class TopFragment extends Fragment { public TopFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_top, container, false); } }
Notice the required empty public constructor not included in the book's code.
The official docs say “All subclasses of Fragment must include a public no-argument constructor.” The book doesn't follow this practice, but you should.
And here's the layout:
- fragment_top.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/title_top" /> </RelativeLayout>
Android studio might set tools:context
to some value other than .MainActivity
. Be sure to make the change.
p. 408-415: Code block
The exposition here gets a little squirrelly and the “updated MainActivity.java” isn't all quite there, so here's what MainActivity.java should look like at the end of p. 415:
- MainActivity.java
package com.hfad.bitsandpizzas; import android.app.Activity; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.Intent; import android.os.Bundle; import android.support.v4.widget.DrawerLayout; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.ShareActionProvider; import android.widget.Toast; public class MainActivity extends Activity { private ShareActionProvider shareActionProvider; private String[] titles; // local list of categories private ListView drawerList; private DrawerLayout drawerLayout; private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectItem(position); } } protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // populate local list of categories titles = getResources().getStringArray(R.array.titles); drawerList = (ListView) findViewById(R.id.drawer); drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); // Populate the list view drawerList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_activated_1, titles)); drawerList.setOnItemClickListener(new DrawerItemClickListener()); if (savedInstanceState == null) { // if MainActivity newly created ... selectItem(0); } } private void selectItem(int position) { // update the main content by replacing fragments Fragment fragment; switch (position) { case 1: fragment = new PizzaFragment(); break; case 2: fragment = new PastaFragment(); break; case 3: fragment = new StoresFragment(); break; default: fragment = new TopFragment(); } FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.content_frame, fragment) .addToBackStack(null) .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) .commit(); //Set the action bar title setActionBarTitle(position); //Close drawer drawerLayout.closeDrawer(drawerList); } private void setActionBarTitle(int position) { String title; if (position == 0) { title = getResources().getString(R.string.app_name); } else { title = titles[position]; } getActionBar().setTitle(title); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Add menu_main items to the action bar (if present): getMenuInflater().inflate(R.menu.menu_main, menu); MenuItem menuItem = menu.findItem(R.id.action_share); shareActionProvider = (ShareActionProvider) menuItem.getActionProvider(); setIntent("This is example text."); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_create_order: //Code to run when the Create Order item is clicked Intent intent = new Intent(this, OrderActivity.class); startActivity(intent); return true; case R.id.action_settings: //Code to run when the settings item is clicked Toast.makeText(MainActivity.this, "Settings!", Toast.LENGTH_SHORT).show(); return true; default: return super.onOptionsItemSelected(item); } } private void setIntent(String text) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, text); shareActionProvider.setShareIntent(intent); } }
If you run the app at this point, you will not see the drawer.
p. 417: Anonymous classes
In case you're not familiar with the construct of the ActionBarDrawerToggle
definition code, it's using Java's anonymous inner class syntax. Anonymous inner classes are typically used as shortcuts to creating a derived class that you'll only use once.
p. 422: Code block
At this point, you should have a properly working Drawer.
Here is the MainActivity.java that I ended up with:
- MainActivity.java
package com.hfad.bitsandpizzas; import android.app.Activity; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.ShareActionProvider; import android.widget.Toast; public class MainActivity extends Activity { private ShareActionProvider shareActionProvider; private String[] titles; // local list of categories private ListView drawerList; private DrawerLayout drawerLayout; private ActionBarDrawerToggle drawerToggle; private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectItem(position); } } protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // populate local list of categories titles = getResources().getStringArray(R.array.titles); drawerList = (ListView) findViewById(R.id.drawer); drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); // Populate the list view drawerList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_activated_1, titles)); drawerList.setOnItemClickListener(new DrawerItemClickListener()); if (savedInstanceState == null) { // if MainActivity newly created ... selectItem(0); } // Create the ActionBarToggle ... drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.open_drawer, R.string.close_drawer) { @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); invalidateOptionsMenu(); } @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); invalidateOptionsMenu(); } }; // ... and set the ActionBarToggle as the drawer's listener drawerLayout.setDrawerListener(drawerToggle); getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setHomeButtonEnabled(true); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // sync toggle state (after onRestoreInstanceState has occurred). drawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); drawerToggle.onConfigurationChanged(newConfig); } @Override public boolean onPrepareOptionsMenu(Menu menu) { // If the drawer is open, hide action items related to the content view. boolean drawerOpen = drawerLayout.isDrawerOpen(drawerList); menu.findItem(R.id.action_share).setVisible(!drawerOpen); return super.onPrepareOptionsMenu(menu); } private void selectItem(int position) { // update the main content by replacing fragments Fragment fragment; switch (position) { case 1: fragment = new PizzaFragment(); break; case 2: fragment = new PastaFragment(); break; case 3: fragment = new StoresFragment(); break; default: fragment = new TopFragment(); } FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.content_frame, fragment) .addToBackStack(null) .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) .commit(); //Set the action bar title setActionBarTitle(position); //Close drawer drawerLayout.closeDrawer(drawerList); } private void setActionBarTitle(int position) { String title; if (position == 0) { title = getResources().getString(R.string.app_name); } else { title = titles[position]; } getActionBar().setTitle(title); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Add menu_main items to the action bar (if present): getMenuInflater().inflate(R.menu.menu_main, menu); MenuItem menuItem = menu.findItem(R.id.action_share); shareActionProvider = (ShareActionProvider) menuItem.getActionProvider(); setIntent("This is example text."); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { // If a drawer option has been clicked ... if (drawerToggle.onOptionsItemSelected(item)) { // do nothing and return. return true; } // ... otherwise ... switch (item.getItemId()) { case R.id.action_create_order: //Code to run when the Create Order item is clicked Intent intent = new Intent(this, OrderActivity.class); startActivity(intent); return true; case R.id.action_settings: //Code to run when the settings item is clicked Toast.makeText(MainActivity.this, "Settings!", Toast.LENGTH_SHORT).show(); return true; default: return super.onOptionsItemSelected(item); } } private void setIntent(String text) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, text); shareActionProvider.setShareIntent(intent); } }