O'Reilly logo

Android Application Development by G. Blake Meike, Zigurd Mednieks, John Lombardo, Rick Rogers

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Under the Covers: Startup Code and Resources in the MJAndroid Application

Chapter 3 introduced the major application we use in this book to illustrate basic Android concepts. That chapter explained which files make up the source code, but it didn’t actually cover any source code in the application. We’ll start looking at source code in this chapter. And to allow you to get started developing an application quickly, we’ll begin with the first task every standalone application has to perform: initialization.

The events covered in this chapter occur between your selecting “Run As Android Application” from the Eclipse menu and seeing the map that MJAndroid displays at startup. This chapter shows how Android makes it easy to create relatively complex applications. In just 80 lines of code and some associated XML resource files, MJAndroid manages to:

  • Display an interactive map

  • Track the current location of the Android phone and update the map

  • Create a local database of information and load user preferences into it

  • Provide a dynamically changing menu

  • Display user interface elements such as labels, buttons, and spinners

  • Run a new Activity to display a supporting screen

The Java code in an Android application interacts tightly with XML resource files, so we’ll bounce back and forth between them in this chapter. As we point out repeatedly, XML files are easier to tweak during development and maintain over the life of an application. The design of Android encourages you to specify the look and behavior of the application in the resource files.

Initialization Parameters in AndroidManifest.xml

As Chapter 3 explained, we told Android to launch Microjobs.java as the first Activity for MJAndroid. We defined that on the Application tab of the AndroidManifest.xml editor. The first part of the XML code that results from that choice is shown here:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.microjobsinc.mjandroid" android:versionCode="1" 
      android:versionName="1.0">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name=
      "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
    <uses-permission android:name="android.permission.CALL_PHONE" />  
    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application android:icon="@drawable/icon2">
    <uses-library android:name="com.google.android.maps" />
        <activity android:name=".MicroJobs" android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

This section of the chapter focuses on the XML in this file. The MicroJobs Activity is identified in the manifest at the beginning of the file. This part of the file is normally created in Eclipse when you first create the Project that you use to write your application.

Like all good XML files, line 1 has the standard declaration of the XML version and the character encoding used. Before we get into the Activities that make up the MJAndroid application, we define a few parameters and declare needed permissions for the whole application:

package="com.microjobsinc.mjandroid"

This is just the package name we gave when we created the application in Eclipse. It’s also the default package for all the modules in the application.

android:versionCode

This is an integer that should always increment with each new version of the application. Every application should include a version code, and it should always be a monotonically increasing integer from version to version. This lets other programs (such as Android Market, installers, and launchers) easily figure out which is the latest version of an application. The filename of your .apk file should include this same version number, so it is obvious which version it contains.

android:versionName

This version identifier is a string, and it is intended to be more like the version numbers you usually see for applications. The naming convention is up to you, but generally the idea is to use a scheme like m.n.o (for as many numbers as you want to use), to identify successive levels of change to the application. The idea is that this is the version identifier that would be displayed to a user (either by your application or another application).

<uses-permission android:name=...

There are four of these in MJAndroid, and they declare that the application intends to use features of Android that require explicit permission from the user of the mobile device running the application. The permission is requested when the application is installed, and from then on Android remembers that the user said it was OK (or not) to run this application and access the secure features. There are many permissions already defined in Android, all described in the Android documentation (search for android.Manifest.permission). You can also define your own permissions and use them to restrict other applications’ access to functions in your application, unless the user grants the other application that permission. The permissions requested here are:

  • ACCESS_FINE_LOCATION, which is required to obtain location information from a GPS sensor.

  • ACCESS_LOCATION_EXTRA_COMMANDS. The Android documentation doesn’t tell us which location commands are “extra,” so we’ll ask for all of them.

  • CALL_PHONE. This allows MJAndroid to request that the Dialer place a mobile phone call on its behalf.

  • ACCESS_MOCK_LOCATION, so we can get fake location information when we’re running under the emulator.

  • INTERNET, so we can retrieve map tiles over an Internet connection.

android:icon="@drawable/icon2"

This is the filename for a PNG file that contains the icon you’d like to use for your application. In this case we’re telling the Android SDK to look for the icon file in the drawable subdirectory of the res (resources) directory under MJAndroid. Android will use this icon for your application in the Android Desktop.

Turning our attention to the definition for the first (and main) Activity, MicroJobs, we first define a few attributes for the Activity:

android:name

The name of the Activity. The full name of the Activity includes the package name (which in our application is “com.microjobsinc.mjandroid.MicroJobs”), but since this file is always used in the package’s namespace, we don’t need to include the leading package names. The Android SDK strips the package name down to “.MicroJobs” when it creates this part of AndroidManifest.xml, and even the leading period is optional.

android:label

The label that we want to appear at the top of the Android screen when the Activity is on the screen. We saw this before in HelloWorld, where we changed the string in strings.xml to match our application.

We then declare an intent filter that tells Android when this Activity should be run. We talked briefly about Intents in Chapter 1, and now we see them in use. As you’ll recall, when Android encounters an Intent to fulfill, it looks among the available Activities and Services to find something that can service the Intent. We set two attributes:

action

Right now Android is trying to launch this application, so it’s looking for an Activity that declares itself ready to resolve the MAIN action. Any application that is going to be launched by the Launcher needs to have exactly one Activity or Service that makes this assertion.

category

The Intent resolver in Android uses this attribute to further qualify the Intent that it’s looking for. In this case, the qualification is that we’d like for this Activity to be displayed in the User Menu so the user can select it to start this application. Specifying the LAUNCHER category accomplishes this. You can have a perfectly valid application without this attribute—you just won’t be able to launch it from the Android user interface. Normally, again, you’ll have exactly one LAUNCHER per application, and it will appear in the same intent filter as the opening Activity of your application.

Initialization in MicroJobs.java

Having seen the XML resources that Android uses to launch the application, we can turn to some Java code that initializes the application. Use Eclipse to open MicroJobs.java in the Java editor.

After the package declaration and the import statements, the MicroJobs class is defined. Most Activities (and the other activities in this application) extend the Activity class. Because we want to display a map in this application, and we want to take advantage of the powerful mapping features built into Android, we declare that MicroJobs will extend MapActivity, as shown in the following code segment. If you look in the Android documentation for MapActivity, you will see that it is a subclass of Activity, and so inherits all the Activity methods and variables:

/**
 * MicroJobs
 */
public class MicroJobs extends MapActivity {

Skip over the first few variables and the definition of the MJOverlay class for the moment, to get to the definition of the onCreate method, as shown in the code block that follows. This is the method called by Android when it first launches an application, so that’s where we’ll put our initialization code. Let’s take a look at it, section by section:

MapView mvMap;
MicroJobsDatabase db;
MyLocationOverlay mMyLocationOverlay;
double latitude, longitude;

/**
 * Called when the activity is first created.
 *
 * @see com.google.android.maps.MapActivity#onCreate(android.os.Bundle)
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

The first thing to note is that onCreate receives an argument when it runs: a Bundle that will be referred to as savedInstanceStte. Note also that the first thing onCreate does is call the onCreate method of its superclass. That makes sense because we want the chain of superclasses to initialize themselves appropriately. But what is this Bundle thing?

A Bundle is one of the mechanisms used by Android to pass structured data between Activities. It’s just a parcel of key/object pairs, and you’ll see later when we start another Activity that we have the option of passing that Activity a Bundle. In the case of MicroJobs, we aren’t going to make use of any of the resources in the savedInstanceState Bundle, but we faithfully pass it on to the onCreate method of our superclass.

The very last line in this section of code sets our Content View. A view, as we explained in Chapter 1, describes how an application window appears and interacts with the user. So the setContentView call tells Android that we want to use the layout information in R.layout.main.java to lay out the screen for the Activity. As Chapter 2 explained, the R.* resource files are generated by the Android SDK from your own XML resource files when you compile your application (as a result of selecting Run); in this case, the parameters come from our res/layout/main.xml file. Android “inflates” these parameters when layouts are created, using them to determine how the layout looks.

So let’s digress for a minute and take a look at the first part of the XML version of that file:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#ffc5d1d4" 
    >
  <com.google.android.maps.MapView
    android:id="@+id/mapmain"
      android:layout_width="fill_parent" 
      android:layout_height="fill_parent"
      android:clickable="true"
      android:apiKey="0P18K0TAE0dO2GifdtbuScgEGLWe3p4CYUQngMg"
    />
  <TextView  
      android:id="@+id/lblMicroJobsToday" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:text="MicroJobs for You Today Near:"
    android:textSize="20dp" 
    android:textColor="#FF000000"
    android:layout_centerHorizontal="true" 
    android:gravity="top" 
    />
  <Spinner 
    android:id="@+id/spnLocations" 
    android:layout_width="250dp" 
    android:layout_height="wrap_content" 
    android:layout_centerHorizontal="true" 
    android:layout_marginTop="2dp" 
    android:layout_below="@+id/lblMicroJobsToday" 
  /> 
  <Button 
    android:id="@+id/btnShowList" 
    android:layout_width="150dp" 
    android:layout_height="wrap_content" 
    android:text="List Jobs" 
    android:textSize="20sp" 
    android:gravity="center_vertical" 
    android:layout_centerInParent="true" 
    android:layout_alignParentBottom="true" 
  /> 
</RelativeLayout>

First, we say that we are going to use a Relative Layout for this screen. Android offers a variety of Layout types, and though it’s beyond the scope of this book, you can even define your own Layout types. A Relative Layout says that we are going to define the positions of the different user interface elements by relating their positions to each other and to the overall screen. That may sound a little vague right now, but it will be clear when we go into some of the attributes in detail. We go into much more depth on the process of screen layout later in this book in Chapter 12.

The first few lines of code define overall attributes for the screen layout:

android:orientation

This tells Android which way we want “gravity” to work in determining the screen layout.

android:layout_width and android:layout_height

These tell Android that we want to make use of the whole screen; we aren’t trying to leave room for other Activities to be partially visible.

android:background

This defines the color of the background for the application (which isn’t really visible in our case, since the map covers the whole screen).

The rest of the file defines each of the visual elements of the screen, and tells Android where we’d like it placed.

The following elements of the application are defined in the file:

Section starting <com.google.android.maps.MapView

This is the main View for this Activity:a Map that consumes most of the screen and shows the locations of jobs that might be of interest to the user. You’ll see that most Views can be described in a layout file by just writing the name of the View, but this holds only for Views that are part of Android’s default libraries. MapViews are not included, so we create an XML element for it. The MapView View is defined in the maps library, so the full pathname is com.google.android.maps.MapView. We assign it the following attributes:

android:id

This defines an identifier that we can use to refer to this View, either from other places in this XML file or from our Java code. You’ll see later in the Java initialization code that we connect the Java source code with this XML source through these IDs.

android:layout_width and android:layout_height

These are the same attributes defined earlier for the application, but here they apply to the MapView alone, not the whole application. The fill_parent value, as its name suggests, asks for permission to fill all the space within the parent. In this case the parent happens to be the whole screen, but it is important to keep in mind that this attribute affects only the relationship between the MapView and its parent.

android:clickable

This tells Android that we want an interactive MapView that the user can click on using the touchscreen on the Android phone (simulated by mouse clicks on the emulated Android phone).

android:apiKey

This is an attribute unique to MapViews. You need an API Key from Google to use a Map View, just as you do when you add a Google map to your web page. You’ll see how to obtain and use Map API Keys in Chapters 7 and 9.

Section starting <TextView

This will display a Label telling the user what he’s looking at. The attributes defined here are typical of what needs to be defined for a TextView. In addition to attributes we already saw under MapView, this element has:

android:text

This contains the text we’d like to display in the TextView.

android:textSize

This says how big Android should display the text—in this case, 20 scaled pixels high (see the upcoming sidebar for a description of Android dimensions).

android:textColor

This defines the color of the text.

android:layout_centerHorizontal

This tells Android that we want it to center the displayed text horizontally.

android:gravity

This tells the Android layout manager where to position the element vertically relative to its container, when the element is smaller. Gravity can be defined as top, center_vertical, or bottom. Note that gravity and attributes like layout_centerHorizontal are layout hints that the layout manager uses to lay out the children of a container. There is no guarantee that the hints will be followed, but the layout manager attempts to satisfy the combined requests from the container, the children it contains, and any global layout hints from the user interface.

There are many other attributes we could define for our TextView, and they are all described in the Android documentation that accompanies the SDK.

Section starting <Spinner

This is a standard Android control that allows the user to select from the current location or any of several “favorite” locations that are recorded in the user’s profile. In addition to the attributes we’ve seen already, the android:layout_below attribute controls the placement of the Spinner. This is the first attribute we’ve seen that applies specifically to the Relative Layout we chose at the top of the file. It tells Android that it should position this Spinner just below the interface element whose id is lblMicroJobsToday.

Section starting <Button

The final segment of main.xml defines a Button widget, which is just what it sounds like—a button that the user can press to initiate some action. In this case, we want a button that takes us to the listing of jobs.

android:layout_width and android:layout_height

These are the same attributes used for the other views, but we don’t want the Button to take up the whole width of the screen, so we give it a defined width. Vertically, we just tell it to wrap the text that it is displaying.

android:text

This places a label on the Button.

android:textSize

This tells Android how large we’d like that text drawn—in this case, 20 scaled pixels.

android:layout_centerInParent

Since the button is not as wide as the parent (the screen), we need to tell the layout manager where to put the Button horizontally. This says “put it in the middle.”

android:layout_alignParentBottom

The Button is only tall enough to wrap the label that it displays, so we also need to tell the layout manager where to place it vertically on the screen. This says “put it at the bottom.” Note that we could also have said android:gravity=bottom. Android provides multiple ways of expressing our layout requests.

More Initialization of MicroJobs.java

The previous section was a rather long digression into XML Layout files, but as you can see, that is where a lot of the initialization of the application’s user interface takes place: where views are defined, named, and given attributes; where the screen is layed out; and where hints are given to the layout manager describing the way we would like the screen to look. Let’s get back to the Java code that brings up the application, starting where we left off in MicroJobs.java:

db = new MicroJobsDatabase(this);

// Get current position
final Location myLocation
   = getCurrentLocation((LocationManager) getSystemService(Context.LOCATION_SERVICE));

Spinner spnLocations = (Spinner) findViewById(R.id.spnLocations);
mvMap = (MapView) findViewById(R.id.mapmain);

// get the map controller
final MapController mc = mvMap.getController();

mMyLocationOverlay = new MyLocationOverlay(this, mvMap);
mMyLocationOverlay.runOnFirstFix(
    new Runnable() {
        public void run() {
            mc.animateTo(mMyLocationOverlay.getMyLocation());
            mc.setZoom(16);
        }
    });
Create the database object

We said before that we are going to use a small SQLite database to hold the job, worker, and employer information. The first line initializes that database by asking Android to create a new MicroJobsDatabase object (and initialize it). The Java code for this is in the file MicroJobsDatabase.java, and we’ll look at it in detail later in Chapter 8.

Get our location

We’ll need to know our current location to do things like finding jobs that are close by, so we get it here by calling getCurrentLocation, which is a method defined later and that accepts the name of our LocationManager as its argument. The LocationManager is a special class that Android instantiates for you, and you can retrieve the instance for your application through the call to getSystemService.

Initialize the Spinner

As explained in the previous section, we place a Spinner widget at the top of the screen to help users quickly go to one of their favorite locations and look for jobs. This is the first time we encounter the findViewById method, which is the way we access the IDs we defined in the XML layout file. If you recall, we identified the Spinner in main.xml as spnLocations. When we built the application, Android compiled that XML into a Java identifier that it placed in R.layout.main.java and linked it into the application. So now we can use findViewById to connect our Java Spinner to the XML attributes we defined.

Initialize the MapView and MapController

Similarly,we connect the Java MapView to the attributes defined for it in main.xml, and then attach a MapController to it. You’ll see much more about the controller in Chapter 9, but for now think of it as a handle to get to all the methods you need to control the MapView.

Initialize the LocationOverlay

We want to create a LocationOverlay that will build and draw the Map in our MapView when we want to view a map of our local area. Again, Maps are covered in much more detail later, but you can see here that we use the constructor to create a new overlay and tell it to run when it gets its first fix from the LocationManager, so that it displays our current location. We also set the zoom level so it’s about right for a metropolitan area.

We’ll skip over the map overlay initialization, because that will be covered in more detail in Chapter 9, where we talk about mapping. We still need to initialize the remaining Views on this screen: the Button and the Spinner. The code for these follows:

// Create a button click listener for the List Jobs button.
Button btnList = (Button) findViewById(R.id.btnShowList);
btnList.setOnClickListener(new Button.OnClickListener() {
    public void onClick(View v) {
        Intent intent = new Intent(MicroJobs.this.getApplication(), 
          MicroJobsList.class);
        startActivity(intent);
    }
});

// Load a HashMap with locations and positions
List<String> lsLocations = new ArrayList<String>();
final HashMap<String, GeoPoint> hmLocations = new HashMap<String, GeoPoint>();
hmLocations.put("Current Location", new GeoPoint((int) latitude, (int) longitude));
lsLocations.add("Current Location");

// Add favorite locations from this user's record in workers table
worker = db.getWorker();
hmLocations.put(worker.getColLoc1Name(), new GeoPoint((int)worker.getColLoc1Lat(), 
  (int)worker.getColLoc1Long()));
lsLocations.add(worker.getColLoc1Name());
hmLocations.put(worker.getColLoc2Name(), new GeoPoint((int)worker.getColLoc2Lat(), 
  (int)worker.getColLoc2Long()));
lsLocations.add(worker.getColLoc2Name());
hmLocations.put(worker.getColLoc3Name(), new GeoPoint((int)worker.getColLoc3Lat(), 
  (int)worker.getColLoc3Long()));
lsLocations.add(worker.getColLoc3Name());
        
ArrayAdapter<String> aspnLocations
    = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, 
     lsLocations);
aspnLocations.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spnLocations.setAdapter(aspnLocations);
Create a callback for the btnList Button View

We first get a handle on the Button View by doing a lookup on its ID, just as we did before for the Spinner and MapView. We then set the behavior of the Button, which uses a construct known as a listener to respond to external events.

When a user clicks a button, Android sends an event to its OnClickListener listener. In this code, we set the Button’s behavior by setting its OnClickListener to the method that we immediately define, onClick.

When the user clicks on btnList, we want to display a list of available MicroJobs. To do that, we have to launch a new Activity, MicroJobsList.java, which contains the screen that displays the list. We can do that by calling the startActivity method with an Intent that describes the new Activity. The first statement in onClick() creates the Intent, using the constructor for Intents that allows us to explicitly name the Activity. This constructor takes two arguments: a pointer to the context of the current application, and the name of the Class to start. The next statement in onClick() then uses that Intent to start an instantiation of MicroJobsList.

Initialize the list of entries in the Spinner View

We need two data structures to pass to our Spinner: a list of favorite locations that the Spinner will display (and the user can select), and a hash map connecting location names to geographical locations (latitude and longitude). Don’t confuse the HashMap with a geographical Map; the HashMap uses the term “map” in the way many programmers use it, to mean an associative array.

We first create the list of location names (lsLocations), and then the HashMap that we’ll use to map names to GeoPoints (hmLocations). We then put the first entry, Current Location, into the list and the HashMap. This entry will always return the user to the current location. This item is special because it can be a moving target. For example, the user may be consulting our application on a fast-moving train or an airplane, so we have to dynamically retrieve the location of the device whenever the current location is selected.

We then add three entries for the user’s “favorite locations,” recorded in the user’s record in the workers table in the MJAndroid database. We’ll dive into the details of how the database works and how it’s set up later. For now, we’ll just say that the code immediately following worker = db.getWorker(); loads the location names and positions (latitudes and longitudes) into the lsLocations and hmLocations lists.

Spinner Views require an ArrayAdapter to feed them the list, so we create one named aspnLocations, attaching it to the list of location names in its constructor. Then, we attach the adapter to the Spinner by calling setAdapter. The statement "aspnLocations.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);" provides the Spinner with the drop-down layout necessary for the user to display the whole list of locations.

Now that we have initialized the lists, we can add the following code, which enables the appropriate action when the user clicks on an item with the Spinner:

    // Set up a callback for the spinner
    spnLocations.setOnItemSelectedListener(
        new OnItemSelectedListener() {
            public void onNothingSelected(AdapterView<?> arg0) { }

            public void onItemSelected(AdapterView<?> parent, View v, int position, 
              long id)  {
                TextView vt = (TextView) v;
                if ("Current Location".equals(vt.getText())) {
                    latitude = myLocation.getLatitude();
                    longitude = myLocation.getLongitude();
                    mc.animateTo(new GeoPoint((int) latitude, (int) longitude));
                } else {
                    mc.animateTo(hmLocations.get(vt.getText()));
                }
                mvMap.invalidate();
            }
        });
}
Initialize the Spinner callback

Just as we did with the Button View, we create a method named onItemSelected and set it to be called when the user selects an item using the Spinner. The onNothingSelected method is also required, but we leave it empty (not used).

As mentioned earlier, Current Location is a special case because we retrieve the device’s location dynamically when the user selects that item. The if block handles that case: we look to see whether the selection is Current Location and if it is, we get the current location and go there. Otherwise, we go to the selected location.

Then, in the final statement, we invalidate the map so it will redraw itself.

Summary

With these explanations (skipping over a few advanced features covered later in the book), we’ve finished initializing the application—at least as far as the main Activity, MicroJobs, is concerned. We’ve seen how the Activity gets started, how it gets its layout information from its associated layout XML file (main.xml), how it initializes the Views it contains, and how it causes the initialization of other Activities or Services (either by invoking a constructor, as when creating the SQL database instance, or by asking Android to start another Activity, as with MicroJobsList).

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required