dcsimg

Building a Ticket Responder for Android, Part 2

Dig into the code as we continue down the path of creating a real-world Android application

Project Motivation

Last time we examined the motivation for our project, which we entitled “TicketResponder”. The purpose of the application is to enable the user to acknowledge a new service ticket created by either an automated monitoring tool or a manually created ticket, entered by a customer directly. The objective is to allow a network services support engineer the ability to easily and quickly acknowledge these new service requests to keep the customer (and teammates) informed of progress, improve overall response times, and ultimately customer satisfaction.

In this article we will take a tour of the application structure and look at some code that makes up this simple application. Over time we’ll be adding new features, digging into the code, and perhaps even re-writing some aspects of how we tackle some features. The actual direction we go in will be a factor of reader-feedback – where do you want to go, and my own distraction level with perhaps shinier objects that are more interesting. So, please, let me know where you would like to see the application go! Let’s get started.

Getting Started

Developing Android applications can be done in either Eclipse or from the “command-line”. I would highly encourage the newcomer to use Eclipse as it includes not only helpful tools for creation of applications, but it also includes a very capable debugging environment as well. I don’t want to spend time here re-hashing the installation of the development tools as it is covered very well in a number of places. Here are a few resources that can help in getting started with Android development:

Bird’s Eye View of the Project

In cooking show style, here is what the application will look like when we’re done with our first run-through today:

enterticketnumber.png

And here is a snapshot of the project as seen in Eclipse:

projectexpanded.png

Here are some observations and explanation of the components of this Android project.

  • The name of the project is TicketResponder which was created with the new project wizard from within Eclipse.
  • There is a single source file, named TicketResponder.java, contained in the package named com.msi.linuxmagazine.ticketing.
  • The API-level used for this project include the core Android 1.5 plus the Google APIs. Note that at present we’re not using any mapping specific functionality. That will come in future enhancements of the project.
  • The res folder contains “resources”. These are images and layout files used to control the user interface elements.
    • drawable: images, icons, etc.
    • layout: screen layouts. Similar to *.rc files in windows or other environments where the UI elements are described and positioned. These files can be edited in either text (xml) form, or in a WYSWIG editor.
      layouteditor.png
    • strings.xml: textual values should (ideally) be externalized to this file, though this often takes some discipline to do consistently.
  • The gen folder contains automagically created files — don’t mess with them because they are over-written every time the application is built. To make a change to one of those files, you will want to change the underlying file — which is typically in the res folder.
  • Android.xml: This is the manifest file that controls many aspects of the application and deserves an article unto itself at a later point in time. For now, we’ll be fine with the default settings.
  • default.properties: this is another automagically generated file related to project settings

Creating your project

NOTE: At this point, I am assuming you’ve got the Android development tools installed. If not, please review some of the resources mentioned earlier, ask a friend or send me a note and I’ll try to get you pointed in the right direction. If enough readers are interested, we can go into this in further depth — please let me know!

There are two ways to get this project in place in your development environment. You can either create the project step by step yourself, which is the best way to learn and/or you can import the project from our project page. To import the project you will need to have an SVN plugin for Eclipse. I use the Subclipse plugin.

To create the new application in Eclipse:

newprojectwizard1.png

Fill out the wizard fields as shown in the image below. You will want to select the Build Target of “Google APIs”. At this point,we could get away with the Android 1.5 target, however in the future we may want to add mapping to the application and for that we will require the Google APIs. Note that this target is essentially a combination of “Android 1.5″ plus the Google mapping apis. I expect that future releases of the Android SDK will follow a similar pattern of splitting out the core, open source Android elements and the Google-specific elements.

newprojectwizard2.png

Note that your project should now look similar to the expanded project view shown earlier. If not, stop here, go back and make sure your project was created properly. Assuming you’ve got the project in place, let’s take a quick look at the source code.

The Application

The entirety of the application’s logic is contained in the single TicketResponder.java file. This file implements the TiecketResponder class which extends the Activity class. The Activity is a core class within the Android SDK and understanding it is essential for Android development. Fortunately for simple applications, we can just use it and not worry too much about the details. In its most simplistic view, an Activity is a screen of your application, encompassing both the user interaction and logic aspects. Again, this is a topic we will likely dive into in more depth in future articles. For now, you can peruse the SDK reference or perhaps read this pdf for more info on fundamental Android application architecture, including the Activity.

Here is the code listing for TicketResponder.java

package com.msi.linuxmagazine.ticketing;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.view.View;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.ImageView;
import android.util.Log;
import android.app.AlertDialog;

public class TicketResponder extends Activity {

	final String tag = "TicketResponder";

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        final EditText ticketNumber = (EditText) findViewById(R.id.TicketNumber);
        final Spinner ticketStatus = (Spinner) findViewById(R.id.TicketStatus);
        final Button gobutton = (Button) findViewById(R.id.GoButton);
        gobutton.setOnClickListener(new Button.OnClickListener() {

            public void onClick(View v) {
                try {
                	String ticket = ticketNumber.getText().toString();
                	String status = ticketStatus.getSelectedItem().toString();
                	Log.i(tag,"Ticket Number is [" + ticket + "]");
                	if (ticket.trim().length() == 0)
                	{
                		AlertDialog.Builder adb = new AlertDialog.Builder(TicketResponder.this);
                        AlertDialog ad = adb.create();
                        ad.setMessage("Ticket number required.");
                        ad.show();
                	}
                	else
                	{
                		AlertDialog.Builder adb = new AlertDialog.Builder(TicketResponder.this);
                        AlertDialog ad = adb.create();
                        ad.setMessage("Updating ticket (" + ticket.trim() + ") to (" + status + ")");
                        ad.show();
                	}

                } catch (Exception e) {
                    Log.i(tag, "Failed to process request [" + e.getMessage() + "]");
                }
            }
        });

        final ImageView logo = (ImageView) findViewById(R.id.ImageView02);
        logo.setOnLongClickListener(new ImageView.OnLongClickListener() {
        	public boolean onLongClick(View v)
        	{

                Intent launchLM = new Intent("android.intent.action.VIEW", android.net.Uri.parse("http://linux-mag.com/blogs/fableson"));
                startActivity(launchLM);

        		return true;
        	}
        }
        );

    }
}

This application (at present) is entirely user-driven meaning that no actions take place without a user tapping on a button or an image on the primary screen. This means that we need to see what the user interface elements look like. Recall earlier the image of the WYSIWIG tool used to create the application layout. This tool generates an XML file. In this case, the primary layout is main.xml.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

<TextView android:id="@+id/Instructions" android:layout_height="wrap_content" android:layout_width="fill_parent" android:width="90px" android:text="Please enter Ticket # and Desired Status" android:minWidth="200px"></TextView>

<TableLayout android:id="@+id/TableLayout01" android:layout_width="fill_parent" android:layout_height="wrap_content">
<TableRow android:id="@+id/TableRow01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20px">
<EditText android:id="@+id/TicketNumber" android:layout_height="wrap_content" android:layout_width="wrap_content" android:width="100px"/>
<Spinner android:id="@+id/TicketStatus" android:layout_width="wrap_content" android:layout_height="wrap_content" android:entries="@array/Ticket_Options"></Spinner>
</TableRow>
</TableLayout>
<Button android:id="@+id/GoButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Update Ticket" android:layout_marginTop="10px" android:layout_gravity="center_vertical|center_horizontal"></Button>
<ImageView android:id="@+id/ImageView02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/lmlogo" android:layout_marginTop="10px" android:layout_gravity="center_vertical|center_horizontal" android:longClickable="true"></ImageView>
</LinearLayout>

When the application is built, the resources defined in the layout files are converted to a run-time variant of this xml, an odd “binary” xml file as well as compiled into the “R” class. R.java lives in the “gen” folder because it is automatically created by the Android tools. Note that when application code (as found in TicketResponder.java) needs to refer to a user interface element, it makes references the R class. For example, virtually the first thing we do in an Activity’s implementation is to setup the user interface elements — to accomplish this we call the setContentView() method, passing in R.layout.main to refer to our layout contained in main.xml. Then we associate layout-defined widgets with widget instances in code to handle events and manipulate them at run-time. The findViewById method returns an object, which we then cast to the appropriate class.

        setContentView(R.layout.main);
        final EditText ticketNumber = (EditText) findViewById(R.id.TicketNumber);
        final Spinner ticketStatus = (Spinner) findViewById(R.id.TicketStatus);
        final Button gobutton = (Button) findViewById(R.id.GoButton);

The next thing to be done is to handle events. The most straight-forward manner of accomplishing this is to assign a “listener” to the objects of interest. The listener may be implemented in the Activity class itself by declaring the class to “implements XYZListener”, another class entirely or as in this example, as an anonymous class.

        gobutton.setOnClickListener(new Button.OnClickListener() {

            public void onClick(View v) {
                try {
                	String ticket = ticketNumber.getText().toString();
                	String status = ticketStatus.getSelectedItem().toString();
                	Log.i(tag,"Ticket Number is [" + ticket + "]");
                	if (ticket.trim().length() == 0)
                	{
                		AlertDialog.Builder adb = new AlertDialog.Builder(TicketResponder.this);
                        AlertDialog ad = adb.create();
                        ad.setMessage("Ticket number required.");
                        ad.show();
                	}
                	else
                	{
                		AlertDialog.Builder adb = new AlertDialog.Builder(TicketResponder.this);
                        AlertDialog ad = adb.create();
                        ad.setMessage("Updating ticket (" + ticket.trim() + ") to (" + status + ")");
                        ad.show();
                	}

                } catch (Exception e) {
                    Log.i(tag, "Failed to process request [" + e.getMessage() + "]");
                }
            }
        });

We setup an ImageView widget in a similar manner and respond to a “long click”. When a long click is detected we launch the built-in web browser to launch a page from the Linux Magazine website. We do this using the Intent. If there is something more important than the Activity in Android, it is the Intent. We’ll be covering it more in a future article, but if you’re anxious to learn more, I would recommend both the SDK references and again the Chapter 1 pdf. Understand Intents, understand Android! Here’s the code:

        final ImageView logo = (ImageView) findViewById(R.id.ImageView02);
        logo.setOnLongClickListener(new ImageView.OnLongClickListener() {
        	public boolean onLongClick(View v)
        	{

                Intent launchLM = new Intent("android.intent.action.VIEW", android.net.Uri.parse("http://linux-mag.com/blogs/fableson"));
                startActivity(launchLM);

        		return true;
        	}
        }
        );

Running the Application

The easiest way to run the Android application is to use the RunAs menu. This will launch the Android Emulator and attempt to run the application.

runas.png

You will likely want to create a Run Configuration in Eclipse so you can save your work over time and generally speaking, have more control over the emulator environment. You can do this in the Run Configuration menu — we’ll cover this and application debugging in more detail next time.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62