====== Chapter 10 ====== ===== 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. The backstack for Fragments isn't much better. 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. So ... ===== 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:'' and/or ''com.android.support:appcompat-v4:'' to work around an Android Studio bug. However, the DrawerLayout that is the main subject of this chapter //requires// the v4 compat libs. {{ https://upload.wikimedia.org/wikipedia/commons/3/3b/Paris_Tuileries_Garden_Facepalm_statue.jpg?300 }} 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: 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 [[http://developer.android.com/reference/android/app/Fragment.html|official docs]] assert that: > 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: 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: 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(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 how the ''ActionBarDrawerToggle'' here is being defined, 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: 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(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); } } ===== pp. 429-434 ===== I think the length of the ActivityMain class definition is an indication of how poorly designed the Android features introduced in this and the previous chapter are. But you be the judge. Note when you add code that references the FragmentManager class to your code, you'll be offered two different class options to import. Use ''android.app.FragmentManager''. package com.hfad.bitsandpizzas; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; 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 int currentPosition = 0; private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { selectItem(position); } } @Override 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(this, android.R.layout.simple_list_item_activated_1, titles)); drawerList.setOnItemClickListener(new DrawerItemClickListener()); // Display the correct fragment if (savedInstanceState != null) { currentPosition = savedInstanceState.getInt("position"); setActionBarTitle(currentPosition); } else { // 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); // Add a listener for changes in the backstack to the fragment manager, // use that to set titles appropriately and check drawer items. getFragmentManager().addOnBackStackChangedListener( new FragmentManager.OnBackStackChangedListener() { @Override public void onBackStackChanged() { FragmentManager fragMan = getFragmentManager(); Fragment fragment = fragMan.findFragmentByTag("visible_fragment"); if (fragment instanceof TopFragment) { currentPosition = 0; } if (fragment instanceof PizzaFragment) { currentPosition = 1; } if (fragment instanceof PastaFragment) { currentPosition = 2; } if (fragment instanceof StoresFragment) { currentPosition = 3; } setActionBarTitle(currentPosition); drawerList.setItemChecked(currentPosition, true); } } ); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("position", currentPosition); } @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) { currentPosition = 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, "visible_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); } }