User Tools

Site Tools


android_learning:headfirst_android_development_notes:chapter_10

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:<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.

paris_tuileries_garden_facepalm_statue.jpg

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 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:

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 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:

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);
    }
}

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.

ActivityMain.java
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<String>(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);
    }
}
android_learning/headfirst_android_development_notes/chapter_10.txt · Last modified: 2016/03/16 04:49 by mithat

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki