Next   Previous section   Next section

Mobile Phone Handheld Hardware Hardware Rick Rogers John Lombardo O'Reilly Media, Inc. O'Reilly Media Android Application Development, 1st Edition

4.2. 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).

Colors in Android

This is a good time to talk briefly about defining colors in Android; we'll discuss it further when we talk about graphics in Chapter 12. The color specification will be familiar to you if you've worked with web pages (although on a web page, the Alpha value is the last item instead of the first). Colors in Android are defined by a pound sign (#) followed by four 8-bit integers in hexadecimal:


Alpha

The transparency of the resulting color, FF being completely opaque and 0 being completely transparent.


Red

Red's contribution to the resulting color, FF being fully on and 0 meaning no red.


Green

Green's contribution to the resulting color, FF being fully on and 0 meaning no green.


Blue

Blue's contribution to the resulting color, FF being fully on and 0 meaning no blue.

Common colors are also defined as global constants for use in Java.


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 Chapter 7 and Chapter 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.

Dimensions in Android

Often you will need to specify a dimension for some element of the user interface. In the example here we generally used scaled pixels (abbreviated "sp"), but Android actually offers a rich set of dimensions to choose from:


px (pixels)

If a dimension is set at 10px, it will be exactly 10 pixels long, no matter what the physical size (and physical density) of the pixels on the display. 10px will therefore be different sizes on handset displays with different pixel densities. On a QVGA display, for example (320x240 pixels), it will be 1/24th of the height of the display. The same 10px running on a VGA display (640x480 pixels) will be 1/64th of the height of the display.


dip or dp (device-independent pixels)

In an effort to make it easier to adapt applications to different pixel densities, dimensions can be expressed in device-independent pixels (sometimes also called "density-independent pixels"). When you specify a dimension of 10dpi, Android will scale the resulting object so it appears the same size it would appear on a 160dpi screen. For example, if a 640x480 display is 4"x3", its pixel density is 160 dots per inch (640/4, or 480/3). On that screen, dp's are the same as px's. But if we run the same application on a tablet-size VGA screen—say, 8"x6"—the pixel density is 80dpi, and a dimension given as 10dp will be twice as large as a dimension given as 10px. The scaling factor for dp's is approximate—Android doesn't try to make dp's come out exactly right.


sp (scaled pixels)

Scaled pixels are a lot like dp's, but they are intended for elements that need finer control over the density scaling factor, such as text.


pts (points)

This is used to express text size in points, just as you would in any text editor. Points are a fixed dimension (roughly 1/72nd of an inch), so the text will appear the same size on any display.


in (inches)

This is just what it says: the dimension in inches.


mm (millimeters)

This is also just what it says, only metric this time.


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

          
    Next   Previous section   Next section
     


    Bionaire air purifier for room.