Chapter 1. Getting Started
1.1. Introduction: Getting Started
Discussion
The famous “Hello, World” pattern came about when Kernighan and Plaugher wanted to write a “recipe” on how to get started in any new programming language and environment. This chapter is affectionately dedicated to these fine gentlemen, and to everyone who has ever struggled to get started in a new programming paradigm.
1.2. Learning the Java Language
Problem
Android apps are written in the Java programming language before they are converted into Android’s own class file format, DEX. If you don’t know how to program in Java you will find it hard to write Android apps.
Solution
Lots of resources are available for learning Java. Most of them will teach you what you need, but will also mention some API classes that are not available for Android development. Avoid any sections in any resource that talk about topics listed in the lefthand column of Table 1-1.
Java API | Android equivalent |
Swing, applets | Android’s GUI; see Chapter 7. |
Application entry point main() | See Recipe 1.6. |
J2ME/Java ME | Most of android.* replaces Java ME
API. |
Servlets/JSP, J2EE/Java EE | Designed for server-side use. |
Discussion
Here are some books and resources on Java programming:
Java in a Nutshell by David Flanagan (O’Reilly) is a good introduction for programmers, particularly those who are coming from C/C++. This book has grown from an acorn to a coconut in size, to keep up with the growth of Java SE over its lifetime.
Head First Java by Kathy Sierra and Bert Bates (O’Reilly). This provides a great visual-learner-oriented introduction to the language.
Thinking in Java by Bruce Eckel (Prentice-Hall).
Learning Java by Patrick Niemeyer and Jonathan Knudsen (O’Reilly).
“Great Java: Level 1”, a video by Brett McLaughlin (O’Reilly). This provides a visual introduction to the language.
Java: The Good Parts by Jim Waldo (O’Reilly).
Java Cookbook, which I wrote and which O’Reilly published. This is regarded as a good second book for Java developers. It has entire chapters on strings, regular expressions, numbers, dates and time, structuring data, I/O and directories, internationalization, threading, and networking, all of which apply to Android. It also has a number of chapters that are specific to Swing and to some EE-based technologies.
Please understand that this list will probably never be completely up-to-date. You should also refer to O’Reilly’s freely downloadable (with registration) Android Development Bibliography, a compilation of all the books from the various publishers whose books are in the online Safari service. This book is also distributed without charge at relevant conferences where O’Reilly has a booth.
See Also
This book’s primary author maintains a list of Java resources online at http://www.darwinsys.com/java/.
O’Reilly has many of the best Java books around; there’s a complete list at http://oreilly.com/pub/topic/java.
1.3. Creating a “Hello, World” Application from the Command Line
Solution
Use the Android Development Kit (ADK) tool android
with the create project
argument and some additional
arguments to configure your project.
Discussion
In addition to being the name of the platform,
android
is also the name of a command-line tool for
creating, updating, and managing projects. You can either navigate into
the android-sdk-xxx directory, or you can set your
PATH
variable to include its
tools subdirectory.
Then, to create a new project, give the command android create project
with
some arguments. Example 1-1 is an example run under
MS-DOS.
C:> PATH=%PATH%;"C:\Documents and Settings\Ian\My Documents\android-sdk-windows\tools"; \ "C:\Documents and Settings\Ian\My Documents\android-sdk-windows\platform-tools" C:> android create project --target android-7 --package com.example.foo --name Foo --activity FooActivity --path .\MyAndroid Created project directory: C:\Documents and Settings\Ian\My Documents\MyAndroid Created directory C:\Documents and Settings\Ian\My Documents\MyAndroid\src\com\example\foo Added file C:\Documents and Settings\Ian\My Documents\MyAndroid\src\com\example\foo\FooActivity.java Created directory C:\Documents and Settings\Ian\My Documents\MyAndroid\res Created directory C:\Documents and Settings\Ian\My Documents\MyAndroid\bin Created directory C:\Documents and Settings\Ian\My Documents\MyAndroid\libs Created directory C:\Documents and Settings\Ian\My Documents\MyAndroid\res\values Added file C:\Documents and Settings\Ian\My Documents\MyAndroid\res\values\strings.xml Created directory C:\Documents and Settings\Ian\My Documents\MyAndroid\res\layout Added file C:\Documents and Settings\Ian\My Documents\MyAndroid\res\layout\main.xml Added file C:\Documents and Settings\Ian\My Documents\MyAndroid\AndroidManifest.xml Added file C:\Documents and Settings\Ian\My Documents\MyAndroid\build.xml C:>
Table 1-2 lists the arguments for the create project
code.
Name | Meaning | Example |
--activity | Name of your “main class” and default name for the generated .apk file. | --activity HelloActivity |
--name | Name of the project and the generated .apk file. | --name MyProject |
--package | Name of the Java package for your classes. | --package com.example.hello |
--path | Path to create the project in (does not create a
subdirectory under this, so don’t use
/home/ ,
but rather
/home/ ). | --path /home/ian/workspace/MyProject
(see above for Windows
example) |
--target | API level of the Android platform to target; use android list targets to see list of
targets. A number is an “ID,” not an API level; for that, use
android- with the API level you want. | --target android-10 |
If it cannot complete the requested operation, the android
command presents a voluminous “command
usage” message listing all the operations it can do and the arguments
for them. If successful, the android create
project
command creates the files and directories listed in
Table 1-3.
It is a normal and recommended Android practice to create your user interface
in XML using the layout file created under res/layout
, but it is certainly possible to
write all the code in Java. To keep this example self-contained, we’ll
do it the “wrong” way for now. Use your favorite text editor to replace
the contents of the file HelloWorld.java with the
contents of Example 1-2.
import android.app.Activity; import android.widget.*; public class Hello extends Activity { /** * This method gets invoked when the activity is instantiated in * response to e.g., you clicked on the app's Icon in the Home Screen. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create a TextView for the current Activity TextView view = new TextView(this); // Make it say something view.setText("Hello World"); // Put this newly created view into the Activity, // sort of like JFrame.getContentPane().add(view) setContentView(view); } }
Assuming you have the Apache Software Foundation Ant Build Tool installed (and it is
included with recent versions of the Android SDK), you can now (in a
command-line window) change to the project directory (...MyDocuments\MyAndroid
in Example 1-1) and issue the command:
ant debug
This will create an archive file named, for example,
MyAndroid.apk (with “apk” standing for Android
Package) in the bin
directory.
If this is your first time here, you may need to create an Android Virtual Device (AVD), which is just a named configuration for the Android emulator specifying target resolution, API level, and so on. You can create an emulator using:
android create avd -n my_droid -t 7
For more details on creating an AVD, see Recipe 3.3.
You can then start the Android Debug Bridge (ADB) server and the emulator:
adb start-server emulator -avd my_droid -t 5
Assuming you now have either the emulator running or your device plugged in and recognized via USB, you can then do:
adb -e install -r bin/MyAndroid.apk
The -e
flag is for the emulator; use
-d
for a real device.
If you are handy with shell scripts or batch files, you’ll want to
create one called, say, download, to avoid typing
the adb
invocation on every build
cycle.
Finally you can start your app! You can use the Application list: tap the little icon that looks like a 5×5 row of dots, scroll to your application by name, and tap its icon.
You will probably find it convenient to create an icon for your
app on the home screen of the device or emulator; this icon will survive
multiple install -r
cycles, so it’s the easiest way
to test the running of your application.
See Also
Recipe 1.4. The blog “a little madness” has a more detailed formulation. The official Android reference site has a page on developing without Eclipse.
1.4. Creating a “Hello, World” Application in Eclipse
Solution
Install Eclipse, the Android SDK, and the ADT plug-in. Create your project and start writing your app. Build it, and test it under the emulator, from within Eclipse.
Discussion
Once you have these items installed, you are ready to begin:
If you want a more detailed exposition of installing these three items, please refer to Recipe 1.5.
To get started, create a new project from the File→New menu (see Figure 1-1).
Click Next. Give your new project a name, and click Next (see Figure 1-2).
Select an SDK version to target. Version 2.1 gives you almost all the devices in use today; version 3.x or 4.x gives you the latest features (see Figure 1-3). You decide.
Figure 1-4 shows the project structure
expanded in the Project panel on the right. It also shows the extent to
which you can use Eclipse auto-completion within Android—I added the
gravity
attribute for the label, and Eclipse is
offering a full list of possible attribute values. I chose
center-horizontal
, so the label should be centered
when we get the application running.
In fact, if you set gravity
to center_vertical
on the LinearLayout
and set it to center_horizontal
on the TextView
, the text will be centered
both vertically and horizontally. Example 1-3 is the
layout file main.xml (located under
res/layout) which achieves this.
<?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" android:gravity="center_vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" android:gravity="center_horizontal" /> </LinearLayout>
As always, Eclipse generates a compiled version whenever you save a source file. Also, in an Android project, it also runs an Ant build to create the compiled, packaged APK that is ready to run. So you only need to run it. Right-click on the project itself and select Run As → Android Project. (See Figure 1-5.)
This will start the Android emulator if it’s not already running. The emulator will start with the word Android in typewriter text, then switch to the fancier Android font with a moving white patch over blue lettering—remember the Microsoft Windows 95 startup? See Figure 1-6.
After a little longer, your application should start up (Figure 1-5 only shows a screenshot of the application itself, since the rest of the emulator view is redundant). See Figure 1-7.
1.5. Setting Up an IDE on Windows to Develop for Android
Problem
You want to develop your Android applications using a Windows PC, so a concise guide to setting up an IDE for that platform is useful.
Solution
The use of the Eclipse IDE is recommended when developing Android apps. Configuring Eclipse on Windows is not a single-shot install; several stages need to be completed. This recipe provides details on those stages.
Discussion
To develop applications for Android, the Eclipse Integrated Development Environment (IDE) for Java is recommended. An Android Development Tools (ADT) plug-in is available to enhance Eclipse. The ADT plug-in uses the Android Software Development Kit (SDK) which provides essential programs for developing Android software. To set up a development system you will need to download and install the following:
Java Standard Edition Development Kit
Eclipse for Java Development
Android Software Development Kit
Android Development Tools plug-in (from within Eclipse)
In the subsections that follow, we will cover these stages in detail for a PC running Windows (tested on XP, Vista, and Windows 7).
Installing the JDK (Java Development Kit)
Go to the Java download page at http://www.oracle.com/technetwork/java/javase/downloads/index.html.
Select the Java icon to access the JDK downloads:
The list of JDK downloads will be shown. Click the Accept License Agreement radio button; otherwise, you will not be allowed to continue. Download and run the latest JDKs present; as of this writing, they are jdk-7u2-windows-i586.exe (or jdk-7u2-windows-x64.exe for 64-bit Windows). You may need to select the location of the download site. Accept any security warnings that appear, but only if you are downloading from the official Java download web page.
When the download has completed and is run you will need to go through the install screens, clicking Next until the JDK installer has finished. You should not need to change any options presented. When the JDK installer has completed, click the Finish button. A product registration web page may load; you can close this or you can choose to register your installation.
Installing Eclipse for Java development
The Eclipse Downloads web page is at http://www.eclipse.org/downloads/.
Windows needs to be selected in the Packages drop down; select the relevant Eclipse IDE for Java Developers download link (see Figure 1-8).
Download and open the ZIP file. In the file there will be an eclipse directory containing several files and subdirectories. Copy the eclipse directory and all its contents as it comes (Figure 1-9). The usual place to copy the files to is either the root of the C drive or under C:\Program Files. You may need to select Continue when Windows asks permission for the copy.
Make a desktop shortcut to eclipse.exe.
Run Eclipse so that it sets up a workspace; this will also check that both Java and Eclipse were installed correctly. When you run Eclipse a security warning may be displayed; select Run to continue. Accept the default workspace location or use a different directory.
Installing the Android SDK (software development kit)
Go to the Android Software Development Kit download page at http://developer.android.com/sdk/index.html.
Choose the latest Windows EXE package (currently installer_r16-windows.exe) and select Run. Accept the security warning only if you are downloading from the official Android SDK website. The Android SDK Tools installer will show some screens. Select the Next button on each screen; you should not need to change any options. Since C:\Program Files is a protected directory, you can either get permission to install there or, as some developers do, install to your user folder or another directory—for example, C:\Android\android-sdk.
When the Install button is clicked, a progress screen will briefly display while the Android files are copied. Click the final Next button and the Finish button at the end of the installation. If you left the Start SDK Manager checkbox ticked the SDK Manager will run. Otherwise, select SDK Manager from the Android SDK Tools program group (Start→All Programs→Android SDK Tools→SDK Manager). When the SDK Manager starts the Android packages available to download are checked. Then a list of all available packages is shown with some preselected for download. A Status column shows whether a package is installed or not. In Figure 1-10, you can see that the Android SDK Tools have just been installed and this is reflected in the Status column.
Check each package that needs to be installed. Multiple packages are available. These include SDK platform packages for each application programming interface (API) level, application samples for most API levels, Google Maps APIs, manufacturer-device-specific APIs, documentation, source code, and the following Google extra packages:
- Android Support
Used to support later Android APIs on older devices
- AdMob Ads SDK
For incorporating advertising into apps
- Analytics SDK
To support analysis of customers’ purchases
- Market Billing
Adds support for in-app purchases
- Market Licensing
Helps protect apps from being illegally copied
- USB Driver
For debugging on physical devices (or using a manufacturer’s driver)
- Webdriver
Helps test a website’s compatibility with the Android browser
It is recommended that you download several SDK platforms to allow testing of apps against various device configurations. It is worth noting that older computers will struggle to run the virtual device emulators for the later Android APIs; therefore, develop with the earlier SDK platforms on such computers. If in doubt about what to download, either accept the initial choices and rerun the SDK Manager to get other packages as and when required; or check all packages to download everything (the download may take a while). Click the “Install packages” button.
The selected packages will be shown in a list; if a package has licensing terms that require acceptance, it is shown with a question mark. Highlight each package that has a question mark to read the licensing terms. You can accept or reject the package using the radio buttons. Rejected packages are marked with a red ×. Alternatively, click Accept All to accept everything that is available. Click the Install button and a progress log will show the packages being installed, as well as any errors that occur. On Windows a common error occurs when the SDK Manager is unable to access or rename directories. Rerun the SDK Manager as administrator and check that the directory does not have any read-only flags or files; see Recipe 1.12 for further details. When complete close the SDK Manager by clicking the × button in the top corner of the window.
Installing the Android Development Tools (ADT) plug-in
You install the ADT plug-in via Eclipse, but to do so you must run Eclipse from the administrator account. Use the shortcut created earlier or eclipse.exe from the eclipse folder. In either case, bring up the context menu (usually via a right-click), select “Run as administrator,” and accept any security warnings. When Eclipse has loaded open the Help menu item and select Install New Software….
On the Install screen enter the following address in the “Work with” box:
https://dl-ssl.google.com/android/eclipse/
Click the Add button. An Add Repository screen appears; in the Name box type something meaningful, such as “ADT plug-in” (the aforementioned web address will be displayed in the Location box); see Figure 1-11.
Click the OK button. The screen will update after briefly showing Pending in the Name column of the table.
Check the box next to Developer Tools. Then select the Next button at the bottom of the screen (see Figure 1-12).
A list of the items to be installed will be displayed. If you get an error message check that Eclipse has been run under the administrator account. Select Next again. A screen displays the licenses; ensure that each license has been accepted (select the “I accept the terms of the license agreements” radio button). Then click the Finish button. A security warning will need to be accepted to complete the installation; select OK to this warning (the address entered earlier is a secure address). Eclipse will ask you for a restart. Select the Restart Now button and Eclipse will close and reload. A Welcome to Android Development dialog will appear. Set the SDK location in the Existing Location box (since the SDK Manager will have already run), browse to the Android SDK folder (by default, C:\Program Files\Android\android-sdk), and click Next (see Figure 1-13).
A Google Android SDK usage monitoring question will appear; change the option if required and click Finish. Eclipse is now configured to build and debug Android apps. See Recipe 3.3 to configure an Android emulator; then try Recipe 1.4 as a sanity check. Plug a physical device into the computer and use its settings to turn on USB Debugging (under Development in Applications).
1.6. Understanding the Android Life Cycle
Problem
Android apps do not have a “main” method; you need to learn how they get started and how they stop or get stopped.
Solution
The class android.Activity
provides a number of well-defined life-cycle methods that
are called when an application is started, suspended, restarted, and so
on, as well as a method you can call to mark an activity as
finished.
Discussion
Your Android application runs in its own Unix process, so in general it cannot directly affect any other running application. The Dalvik VM interfaces with the operating system to call you when your application starts, when the user switches to another application, and so on. There is a well-defined life cycle for Android applications.
An Android application has three states it can be in:
Active, in which the app is visible to the user and is running
Paused, in which the app is partly obscured and has lost the input focus
Stopped, in which the app is completely hidden from view
Your app will be transitioned among these states by Android calling the following methods on the current activity at the appropriate time:
void onCreate(Bundle savedInstanceState) void onStart() void onResume() void onRestart() void onPause() void onStop() void onDestroy()
You can see the state diagram for this life cycle in Figure 1-14.
For an application’s first activity, onCreate()
is how you know that the application has been started. This
is where you normally do constructor-like work such as setting up the
“main window” with setContentView()
, adding listeners
to buttons to do work (including starting additional activities), and so
on. This is the one method that even the simplest Android app
needs.
You can see the effects of the various life cycle methods by creating a dummy project in Eclipse and overriding all the methods with log “debug” statements.
1.7. Installing .apk Files onto an Emulator via the ADB
Problem
You have an application’s .apk file, and you want to install it on the emulator to check out the application, or because an application you are developing requires it.
Solution
Use the ADB command-line tool to install the .apk file onto the running emulator; you can also use this tool to install an .apk file onto a connected Android device.
Discussion
To install the .apk file, follow these steps:
Find the location on your machine where you have installed the Android SDK. In the Android SDK directory, go to the tools directory.
Look for an executable named adb in the tools directory. If it is present that is the location of the adb file; otherwise, there should be a .txt file named “adb has moved.” The contents of the file merely direct you to the location of the adb binary; the file states that adb is present in the platform-tools directory instead of the tools directory.
Once you have located the adb program,
cd
to that location in a terminal (Linux) or command prompt (Windows).Use the command
adb install
. If you get “command not found” on Linux, try using “./adb” instead of just “adb”.location of the .apk you want to install
This should start the installation on the device that is currently running (either an emulator that is running on your desktop, or a physical Android device that is connected).
After the installation finishes, in the menu of the Android device/emulator you should see the icon of the application you just installed (see Figure 1-15).
1.8. Installing Apps onto an Emulator via SlideME
Problem
App stores are a huge element of the attraction of modern smartphones. Google’s Android Market is the official app store, but you may want to use others as well.
Solution
SlideMe LLC offers an alternative app store. The SlideME app store allows you to install other apps (perhaps you want to integrate with other apps), as well as test the experience of publishing and downloading your own apps on your emulated Android device. SlideME also reaches many Android users who are locked out of the Google Android Market, including people with unsupported devices and those who don’t live in a country that is supported by the Android Market.
Discussion
An alternative to the official Android Market is Slide ME, an alternative app store. SlideME may not have as many apps as Google’s Android Market, but it has some advantages, including that it works easily on an emulated Android device.
Go to the SlideME website using your emulated Android device, browse or search through the apps, and click on a free one. After a pause to download the file, open the download (the little arrow on the top left), review the license, and launch the .apk file you’ve downloaded to install the app. During the installation, you will be asked to review and accept the license for the software.
Once the SlideME app is installed, you can go through the catalog and install more apps without using the browser. This is much easier than using a web browser to download the apps, since the presentation is designed for the Android device; simply choose a category, scroll through it, and choose an app to install. I have had some stability problems using the app on my emulator—it freezes on occasion—but I was able to install some basic free apps, like Grocery List.
I noticed in the Android Invasion discussion forum on Linkedin.com that some Android users are disappointed to find that many cell phone providers do not include the official Android Market in their Android cell phone offerings, and unless you’re comfortable rooting and flashing your Android phone there’s no way to get it. Most consumers are not comfortable rooting and flashing their phones, and for them SlideME offers an alternative way to find free and inexpensive apps for their phones.
See Also
SlideME also allows you to publish your apps to its app store; see the Applications page on the SlideME website.
For information on developing apps for SlideME, see http://slideme.org/developers.
1.9. Sharing Java Classes from Another Eclipse Project
Discussion
You often need to reuse classes from another project. In my JPSTrack GPS tracking program, the Android version borrows classes such as the file I/O module from the Java SE version. You surely do not want to copy and paste classes willy-nilly from one project into another, because this makes maintenance improbable.
In the simplest case, when the library project contains the source of the classes you want to import, all you have to do is declare the project containing the needed classes (the Java SE version in this case) as a referenced project on the build path. Select Project→Properties→Java Build Path, select Projects, and click Add. In Figure 1-16, I am adding the SE project “jpstrack” as a dependency on the Android project “jpstrack.android.”
Mobile developers who create apps for other platforms as well should note that this technique does not work if you also have the current (late 2011) BlackBerry Java plug-in installed in your Eclipse installation. This is a bug in the BlackBerry Java plug-in; it incorrectly flags even non-BlackBerry projects as depending on non-BlackBerry-library projects, and marks the project as having an error, which will prevent correct code generation and execution. Remove the buggy plug-in, or put it in its own Eclipse installation.
Alternatively, create a JAR file using either Ant or the Eclipse wizard. Have the other project refer to it as an external JAR in the classpath settings. Or physically copy it into the libs directory and refer to it from there.
A newer method that is often more reliable and is now officially recommended, but is only useful if both projects are Android projects, is to declare the library one as a library project, under Project→Properties→Android→Library tab, and use the Add button on the other project on the same screen to list the library project as a dependency on the main project (see Figure 1-17).
For command-line fans, the first method involves editing the .classpath file, while the second method simply creates entries in the project.properties file, for example:
# Project target target=android-7 android.library=false android.library.reference.1=../wheel
Since you are probably keeping both projects under source control (and if these are programs you ever intend to ship, you should!), remember to “tag” both projects when you release the Android project—one of the points in favor of source control is that you are able to re-create exactly what you shipped.
See Also
1.10. Referencing Libraries to Implement External Functionality
Solution
Obtain the JAR file for the library that you require and add it to your project.
Discussion
As an example, you might need to use AndroidPlot, a library for plotting charts and graphs in your application, or OpenStreetMap, a wiki project that creates and provides free geographic data and mapping. If so, your application needs to reference these libraries. You can do this in Eclipse in a few simple steps:
Download the JAR file corresponding to the library you wish to use.
After creating your Android project in Eclipse, right-click on the project name and select Properties in the menu (Figure 1-18).
From the list on the left side, select Java Build Path and click on the Libraries tab.
Click the Add External JARs button.
Provide the location where you downloaded the JAR file for the library you wish to use.
At this point you will see a Referenced Libraries directory in your project. The JARs you added will appear (see Figure 1-19).
An alternative approach is to create a lib folder in your project, physically copy the JAR files there, and add them individually as you did earlier, but instead clicking the Add JARs button. This keeps everything in one place (especially if your project is shared via a version control system with others who might use a different operating system and be unable to locate the external JARs in the same place). However, it does raise the burden of responsibility for licensing issues on the included JAR files. See Figure 1-20.
In either case, if you also build with Ant, be sure to update your build.xml file.
Whichever way you do it, it’s pretty easy to add libraries to your project.
1.11. Using SDK Samples to Help Avoid Head Scratching
Problem
Sometimes it is a struggle to code up some functionality, especially when the documentation is sketchy or does not provide any examples.
Solution
Looking at existing working code will help. The Android SDK has sample programs that you can pick apart to see how they work.
Discussion
The Android SDK comes with several sample applications that can be useful when trying to code up some functionality. Looking through the sample code can be insightful. Once you have installed the Android SDK, several samples become available:
Accelerometer Play
Accessibility Service
API Demos
Backup and Restore
Bluetooth Chat
Business Card
Contact Manager
Cube Live Wallpaper
Home
Honeycomb Gallery
JetBoy
Lunar Lander
Multiple Resolutions
Near Field Communication
Note Pad
RenderScript
Sample Sync Adapter
Searchable Dictionary
Session Initiation Protocol
Snake
Soft Keyboard
Spinner
SpinnerTest
StackView Widget
TicTacToeLib
TicTacToeMain
USB
Wiktionary
Wiktionary (Simplified)
Weather List Widget
XML Adapters
To open a sample project from Eclipse open the File menu and then select Android Project. See Figure 1-21.
On the New Android Project dialog, select the “Create project from existing sample” option. Click Next and select the Build Target. A list of available samples for the selected target is shown. If the required sample is not shown, go back and select another Build Target. (The sample may not be installed; the SDK Manager can be used to install additional samples if they were missed during the SDK setup.) Choose the sample to load, click Finish, and the sample is copied to the Workspace and built (with progress shown on the status bar).
After a short time, the sample will be ready to run and you will be able to browse the source code to see how it is all done.
If the samples have been moved from the SDK samples directory, use the “Create project from existing source” option on the New Android Project dialog to open the sample.
When the sample is first run select Android Application in the Run As dialog that may appear. It may also be necessary to configure an appropriate AVD to run the sample (see Recipe 3.3). See Figure 1-22.
See Also
The Android Developers website at http://developer.android.com/index.html; this cookbook, of course.
You can also search the Web for additional programs or examples. If you still can’t find what you need, you can seek help from Stack Overflow (http://www.stackoverflow.com; use “android” as the tag) or from the Internet Relay Chat (IRC) channel #android-dev on freenode.
1.12. Keeping the Android SDK Updated
Problem
The SDK must be kept updated to allow app developers to work with the latest APIs on the evolving Android platform.
Solution
Use the Android SDK Manager program to update the existing installed SDK packages and to install new SDK packages. This includes third-party packages for device-specific functionality.
Discussion
The Android operating system (OS) is constantly evolving, and therefore, so is the Android SDK. The ongoing development of Android is driven by:
Google’s research and development
Phone manufacturers developing new and improved handsets
Addressing security issues and possible exploits
The need to support new devices (e.g., support for tablet devices was added with version 3.0)
Support for new hardware interfaces (e.g., support for near field communication was added in version 2.3).
Fixing bugs
Improvements in functionality (e.g., a new JavaScript engine)
Changes in the underlying Linux kernel
Deprecation of redundant programming interfaces
New uses (e.g., Google TV)
The wider Android development community
We covered Android SDK installation elsewhere (see Recipe 1.5 or http://developer.android.com/sdk/installing.html). After the SDK is installed on the development machine and the programming environment is running smoothly, once in a while developers will need to check for updates to the SDK.
You can keep the SDK up-to-date by running the SDK Manager program. (On a Windows machine run SDK Manager.exe in the folder C:\Program Files\Android\android-sdk, or use the Start button, then select All Programs→Android SDK Tools, and click SDK Manager). You can also run it from within Eclipse (using the Window menu and selecting Android SDK Manager). See Figure 1-23. The Android SDK is divided into several packages. The SDK Manager automatically scans for updates to existing packages and will list new packages and those provided by device manufacturers.
Available updates will be shown in a list (as will available optional packages). If an update or package has licensing terms that require acceptance it is shown with a question mark. Highlight each package that has a question mark to read the licensing terms. You can accept or reject the package using the radio buttons. Rejected packages are marked with a red ×. See Figure 1-24.
Alternatively, click on Accept All to accept everything that is available. All packages and updates that are ready to download and install will be shown with a green tick. Click the Install button to begin the download and installation; when complete click the Close button. See Figure 1-25.
If the SDK Manager program has itself been updated, you will see a message asking you to restart the program (see Figure 1-26).
The SDK Manager is also used to download additional packages that are not part of the standard platform. This mechanism is used by device manufacturers to provide support for their own hardware. For example, LG Electronics provides a 3D device, and to support 3D capability in applications an additional package is provided. It is also used by Google to allow the download of optional APIs.
In the SDK Manager dialog, expand and tick the required packages in the left-hand list, and then click the Install button (see Figure 1-27). If a third-party package is not listed, the URL to a respository.xml file, provided by the package publisher, will need to be entered via the Tools menu.
Possible update errors on Windows
In a system this complex, there are many things that might go wrong. This section discusses some of these and their solutions.
Run SDK Manager as admin
On a Windows machine, the default location for the SDK is under the C:\Program Files\Android\android-sdk directory. This is a restricted directory and can cause the SDK installation to fail. A message dialog with the title “SDK Manager: failed to install” can appear (see Figure 1-28).
To overcome this error there are a few things to check:
Unplug any Android devices (this may prevent adb.exe from closing).
Browse to C:\Program Files\Android\Android-sdk and bring up the Properties for the tools folder (select the context menu, and then Properties). Ensure that the “Read-only (Only applies to files in folder)” checkbox is cleared (see Figure 1-29).
You may need to give permission to change the attributes (see Figure 1-30).
A Confirm Attribute Changes dialog will appear; ensure the option “Apply changes to this folder, subfolders and files” is selected and click OK. Then do the following:
Restart the computer.
Ensure that all other programs are closed, especially any copies of File Explorer.
Run SDK Manager.exe under the administrator account. Bring up the context menu and select “Run as administrator. (See Figure 1-31.)
Close ADB before updating
A message asking you to restart ADB (the Android Debugger) may appear (Figure 1-32).
Ideally, it is best to run the SDK Manager without ADB running, and it should not be running if Windows has just been started. Alternatively, you can use the Windows Task Manager to stop adb.exe. Answer No to this prompt if ADB was not running; otherwise, answer Yes.
SDK Manager cannot update itself
During the SDK update installation there may be an error related to the SDK Manager program (see Figure 1-33).
To resolve this error ensure that all programs are closed (including adb.exe). Then copy SDK Manager.exe from C:\Program Files\Android\android-sdk\tools\lib to C:\Program Files\Android\android-sdk (or wherever the SDK is installed). Then run the SDK Manager again. (See Figure 1-32.)
Updating Eclipse
After you update the SDK and open Eclipse a warning message may appear (see Figure 1-34).
In Eclipse, select Help and then select Check for Updates. Wait for the progress dialog to finish and the Android Eclipse updates will be shown. Click Next twice, and accept the licensing terms. Then click Finish to start the download and update process. A warning message about unsigned content may appear. Click OK to accept the warning (only do so if you are updating via Eclipse). Restart Eclipse once the update has completed (a message to do so will appear).
Further information on troubleshooting the SDK Manager and Android Eclipse plug-in is available on the Android Developers website.
1.13. Taking a Screenshot from the Emulator/Android Device
Solution
Use the Device Screen Capture feature of the Dalvik Debug Monitor Server (DDMS) view in Eclipse.
Discussion
To use the Device Screen Capture feature follow these steps:
Run the application in Eclipse and go to the DDMS view (Window menu→Open Perspective→Other→DDMS) or Window menu→Show View→Other→Android→Devices; the former is shown in Figure 1-36).
Note that the line that reads “Resource…does not exist” appears in Figure 1-35 only because another Eclipse project has been closed, and does not affect the steps listed here.
In the DDMS view, select the device or emulator whose screen you want to capture.
In the DDMS view, click the Screen Capture icon. See Figure 1-36.
A window showing the current screen of the emulator/Android device will pop up. It should look like Figure 1-37. You can save the screenshot and use it to describe the app!
See Also
Some distributions provide alternative ways of taking screenshots. CyanogenMod 7.x provides a screenshot in the menu you get when you long-press the power button. Some HTC tablets with pen support offer screen grabs in the Pen menu. Ice Cream Sandwich (Android 4.0) provides a built-in mechanism for taking screenshots on real devices: just press the Volume Down control at the same time as the Power button, and the image will be saved to your device and can be viewed in the Gallery application.
1.14. Program: A Simple CountDownTimer Example
Problem
You want a simple countdown timer, a program that will count down a given number of seconds until it reaches zero.
Solution
Android comes with a built-in class for constructing CountDownTimer
s. It’s easy
to use, it’s efficient, and it works (that goes without saying!).
Discussion
The steps to provide a countdown timer are as follows:
Create a subclass of
CountDownTimer
. This class’s constructor takes two arguments,CountDownTimer(long millisInFuture, long countDownInterval)
. The first is the number of milliseconds from now when the interval should be done; at this point the subclass’sonFinish()
method will be called. The second is the frequency in milliseconds of how often you want to get notified that the timer is still running, typically to update a progress monitor or otherwise communicate with the user. Your subclass’sonTick()
method will be called with each passage of this many milliseconds.Override the
onTick()
andonFinish()
methods.Instantiate a new instance in your Android Activity.
Call the start() method on the new instance created!
The example Countdown Timer program consists of an XML Layout (shown in Example 1-4) and some Java code (shown in Example 1-5). When run, it should look something like Figure 1-38, though the times will probably be different.
<?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"> <Button android:id="@+id/button" android:text="Start" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TableLayout android:padding="10dip" android:layout_gravity="center" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TableRow> <TextView android:id="@+id/timer" android:text="Time: " android:paddingRight="10dip" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/timeElapsed" android:text="Time elapsed: " android:paddingRight="10dip" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </TableRow> </TableLayout> </LinearLayout>
package com.examples; import android.app.Activity; import android.os.Bundle; import android.os.CountDownTimer; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; public class Main extends Activity implements OnClickListener { private MalibuCountDownTimer countDownTimer; private long timeElapsed; private boolean timerHasStarted = false; private Button startB; private TextView text; private TextView timeElapsedView; private final long startTime = 50 * 1000; private final long interval = 1 * 1000; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); startB = (Button) this.findViewById(R.id.button); startB.setOnClickListener(this); text = (TextView) this.findViewById(R.id.timer); timeElapsedView = (TextView) this.findViewById(R.id.timeElapsed); countDownTimer = new MalibuCountDownTimer(startTime, interval); text.setText(text.getText() + String.valueOf(startTime)); } @Override public void onClick(View v) { if (!timerHasStarted) { countDownTimer.start(); timerHasStarted = true; startB.setText("Start"); } else { countDownTimer.cancel(); timerHasStarted = false; startB.setText("RESET"); } } // CountDownTimer class public class MalibuCountDownTimer extends CountDownTimer { public MalibuCountDownTimer(long startTime, long interval) { super(startTime, interval); } @Override public void onFinish() { text.setText("Time's up!"); timeElapsedView.setText("Time Elapsed: " + String.valueOf(startTime)); } @Override public void onTick(long millisUntilFinished) { text.setText("Time remain:" + millisUntilFinished); timeElapsed = startTime - millisUntilFinished; timeElapsedView.setText("Time Elapsed: " + String.valueOf(timeElapsed)); } } }
Source Download URL
The source code for this example is in the Android Cookbook repository at http://github.com/AndroidCook/Android-Cookbook-Examples, in the subdirectory CountDownTimerExample (see Getting and Using the Code Examples).
1.15. Program: Tipster, a Tip Calculator for the Android OS
Problem
When you go with friends to a restaurant and wish to divide the check and tip, you can get into a lot of manual calculations and disagreements. Instead, you want to use an app that lets you simply add the tip percentage to the total and divide by the number of diners. Tipster is an implementation of this in Android, to show a complete application.
Solution
This is a simple exercise that uses the basic GUI elements in Android and then pieces them together with some simple calculations and some event-driven UI code to tie it all together. We will use the following GUI components:
TableLayout
This provides a good control over screen layout. This layout allows you to use the HTML
Table
tag paradigm to lay out widgets.TableRow
This defines a row in the
TableLayout
. It’s like the HTMLTR
andTD
tags combined.TextView
This
View
provides a label for displaying static text on the screen.EditText
This
View
provides a text field for entering values.RadioGroup
This groups together radio buttons.
RadioButton
This provides a radio button.
Button
This is the regular button.
View
We will use a
View
to create a visual separator with certain height and color attributes.
Discussion
Android uses XML files for the layout of widgets. In our example project, the Android plug-in for Eclipse generates a main.xml file for the layout. This file has the XML-based definitions of the different widgets and their containers.
There is a strings.xml file which has all the string resources used in the application. A default icon.png file is provided for the application icon.
Then there is the R.java file which is automatically generated (and updated when any changes are made to main.xml). This file has the constants defined for each layout and widget. Do not edit this file manually; the plug-in does it for you when you make any changes to your XML files.
In our example we have Tipster.java as the
main Java file for the Activity
.
Recipe 1.4 as well as various Google tutorials highlight how to use the plug-in. Using the Eclipse plug-in, create an Android project named Tipster. The end result will be a project layout that looks like the one shown in Figure 1-39.
Creating the layout and placing the widgets
The end goal is to create a layout similar to the one shown in Figure 1-39.
For this screen layout we will use the following layouts and widgets:
TableLayout
Provides good control over screen layout. This layout allows you to use the HTML
Table
tag paradigm to lay out widgets.TableRow
This defines a row in the
TableLayout
. It’s like the HTMLTR
andTD
tags combined.TextView
This
View
provides a label for displaying static text on the screen.EditText
RadioGroup
RadioButton
Button
View
We will use a View to create a visual separator with certain height and color attributes.
Familiarize yourself with these widgets as you will be using these quite a lot in applications you build. When you go to the Javadocs for layout and widget, look up the XML attributes. This will help you correlate the usage in the main.xml layout file and the Java code (Tipster.java and R.java) where these are accessed.
Also available is a visual layout editor in the Eclipse ADT, as well as a standalone UI tool called DroidDraw, both of which let you create a layout by dragging and dropping widgets from a palette, like any form designer tool. However, I recommend that you create the layout by hand in XML, at least in your initial stages of learning Android. Later on, as you learn all the nuances of the XML layout API, you can delegate the task to such tools.
The layout file, main.xml, has the layout
information (see Example 1-6). A
TableRow
widget creates a single row inside the
TableLayout
. So you use as many
TableRow
s as the number of rows you want. In this
tutorial we will use eight TableRow
s—five for the
widgets up to the visual separator below the buttons, and three for
the results area below the buttons and separator.
<?xml version="1.0" encoding="utf-8"?> <!-- Using table layout to have HTML table like control over layout --> <TableLayout android:id="@+id/TableLayout01" android:layout_width="fill_parent" android:layout_height="fill_parent" android:stretchColumns="1" xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Row 1: Text label placed in column zero, text field placed in column two and allowed to span two columns. So a total of 4 columns in this row --> <TableRow> <TextView android:id="@+id/txtLbl1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="0" android:text="@string/textLbl1"/> <EditText android:id="@+id/txtAmount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:numeric="decimal" android:layout_column="2" android:layout_span="2" /> </TableRow> <!-- Row 2: Text label placed in column zero, text field placed in column two and allowed to span two columns. So a total of 4 columns in this row --> <TableRow> <TextView android:id="@+id/txtLbl2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="0" android:text="@string/textLbl2"/> <EditText android:id="@+id/txtPeople" android:layout_width="wrap_content" android:layout_height="wrap_content" android:numeric="integer" android:layout_column="2" android:layout_span="3"/> </TableRow> <!-- Row 3: This has just one text label placed in column zero --> <TableRow> <TextView android:id="@+id/txtLbl3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/textLbl3"/> </TableRow> <!-- Row 4: RadioGroup for RadioButtons placed at column zero with column span of three, thus creating one radio button per cell of the table row. Last cell number 4 has the textfield to enter a custom tip percentage --> <TableRow> <RadioGroup android:id="@+id/RadioGroupTips" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="0" android:layout_span="3" android:checkedButton="@+id/radioFifteen"> <RadioButton android:id="@+id/radioFifteen" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/rdoTxt15" android:textSize="15sp" /> <RadioButton android:id="@+id/radioTwenty" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/rdoTxt20" android:textSize="15sp" /> <RadioButton android:id="@+id/radioOther" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/rdoTxtOther" android:textSize="15sp" /> </RadioGroup> <EditText android:id="@+id/txtTipOther" android:layout_width="fill_parent" android:layout_height="wrap_content" android:numeric="decimal"/> </TableRow> <!-- Row for the Calculate and Rest buttons. The Calculate button is placed at column two, and Reset at column three --> <TableRow> <Button android:id="@+id/btnReset" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="2" android:text="@string/btnReset"/> <Button android:id="@+id/btnCalculate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="3" android:text="@string/btnCalculate"/> </TableRow> <!-- TableLayout allows any other views to be inserted between the TableRow elements. So insert a blank view to create a line separator. This separator view is used to separate the area below the buttons which will display the calculation results --> <View android:layout_height="2px" android:background="#DDFFDD" android:layout_marginTop="5dip" android:layout_marginBottom="5dip"/> <!-- Again table row is used to place the result textviews at column zero and the result in textviews at column two --> <TableRow android:paddingBottom="10dip" android:paddingTop="5dip"> <TextView android:id="@+id/txtLbl4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="0" android:text="@string/textLbl4"/> <TextView android:id="@+id/txtTipAmount" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="2" android:layout_span="2"/> </TableRow> <TableRow android:paddingBottom="10dip" android:paddingTop="5dip"> <TextView android:id="@+id/txtLbl5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="0" android:text="@string/textLbl5"/> <TextView android:id="@+id/txtTotalToPay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="2" android:layout_span="2"/> </TableRow> <TableRow android:paddingBottom="10dip" android:paddingTop="5dip"> <TextView android:id="@+id/txtLbl6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="0" android:text="@string/textLbl6"/> <TextView android:id="@+id/txtTipPerPerson" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_column="2" android:layout_span="2"/> </TableRow> <!-- End of all rows and widgets --> </TableLayout>
TableLayout and TableRow
After examining main.xml, you can gather
that the TableLayout
and
TableRow
are straightforward to use. You create the
TableLayout
once, then insert a
TableRow
. Now you are free to insert any other
widgets, such as TextView
,
EditView
, and so on, inside this
TableRow
.
Do look at the attributes, especially android:stretchColumns
,
android:layout_column
, and
android:layout_span
, which allow you to place widgets the same way you would use a
regular HTML table. I recommend that you follow the links to these
attributes and read up on how they work for a
TableLayout
.
Controlling input values
Controlling input values: Look at the EditText
widget in the
main.xml file at . This
is the first text field for entering the “Total Amount” of the check.
We want only numbers here. We can accept decimal numbers because real
restaurant checks can be for dollars and cents, and not just dollars.
So we use the android:numeric
attribute with a
value of decimal
. This will allow whole values like
10 and decimal values like 10.12, but will prevent any other type of
entry.
This is a simple and concise way to control input values, thus
saving us the trouble of writing validation code in the
Tipster.java file, and ensuring that the user
does not enter erroneous values. This XML-based constraints feature of
Android is quite powerful and useful. You should explore all possible
attributes that go with a particular widget to extract maximum
benefits from this XML shorthand way of setting constraints. In a
future release, unless I have missed it completely in this release, I
hope that Android allows for entering ranges for the
android:numeric
attribute so that we can define
what range of numbers we wish to accept.
Since ranges are not currently available (to the best of my knowledge), you will see later on that we do have to check for certain values like zero or empty values to ensure that our tip calculation arithmetic does not fail.
Examining Tipster.java
Now we will look at the Tipster.java file which controls our application. This is the main class that does the layout, the event handling, and the application logic.
The Android Eclipse plug-in creates the Tipster.java file in our project with the default code shown in Example 1-7.
package com.examples.tipcalc; import android.app.Activity; public class Tipster extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
The Tipster
class extends the
android.app.Activity
class. An activity is a
single, focused thing that the user can do. The
Activity
class takes care of creating the window
and then laying out the UI. You have to call the setContentView(View view)
method to
put your UI in the Activity
. So think of
Activity
as an outer frame that is empty, and that
you populate with your UI.
Now look at the snippet of the Tipster.java class shown in Example 1-8. First we define the widgets as class members. Look at through in particular for reference.
Then we use the findViewById(int id)
method to
locate the widgets. The ID of each widget, defined in your
main.xml file, is automatically defined in the
R.java file when you clean and build the project
in Eclipse. (If you have set up Eclipse to build automatically, the
R.java file is instantaneously updated when you
update main.xml.)
Each widget is derived from the View
class,
and provides special GUI features. So a TextView
provides a way to put
labels on the UI, while the EditText
provides a text field. Look
at through in Example 1-8. You can see
how findViewById()
is used to locate the
widgets.
public class Tipster extends Activity { // Widgets in the application private EditText txtAmount; private EditText txtPeople; private EditText txtTipOther; private RadioGroup rdoGroupTips; private Button btnCalculate; private Button btnReset; private TextView txtTipAmount; private TextView txtTotalToPay; private TextView txtTipPerPerson; // For the id of radio button selected private int radioCheckedId = -1; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Access the various widgets by their id in R.java txtAmount = (EditText) findViewById(R.id.txtAmount); //On app load, the cursor should be in the Amount field txtAmount.requestFocus(); txtPeople = (EditText) findViewById(R.id.txtPeople); txtTipOther = (EditText) findViewById(R.id.txtTipOther); rdoGroupTips = (RadioGroup) findViewById(R.id.RadioGroupTips); btnCalculate = (Button) findViewById(R.id.btnCalculate); //On app load, the Calculate button is disabled btnCalculate.setEnabled(false); btnReset = (Button) findViewById(R.id.btnReset); txtTipAmount = (TextView) findViewById(R.id.txtTipAmount); txtTotalToPay = (TextView) findViewById(R.id.txtTotalToPay); txtTipPerPerson = (TextView) findViewById(R.id.txtTipPerPerson); // On app load, disable the Other Tip Percentage text field txtTipOther.setEnabled(false);
Addressing ease of use or usability concerns
Our application must try to be as usable as any other established application or web page. In short, adding usability features will result in a good user experience. To address these concerns look at Example 1-8 again.
Look at where we use the
requestFocus()
method of the View
class. Since the
EditText
widget is derived from the
View
class, this method is applicable to it. This
is done so that when our application loads the Total Amount text field
will receive focus and the cursor will be placed in it. This is
similar to popular web application login screens where the cursor is
present in the username text field.
Now look at where the Calculate
button is disabled by calling the setEnabled(boolean enabled)
method on the Button
widget. This is done so that
the user cannot click on it before entering values in the required
fields. If we allowed the user to click Calculate without entering
values in the Total Amount and No. of People fields, we would have to
write validation code to catch these conditions. This would entail
showing an alert pop up warning the user about the empty values. This
adds unnecessary code and user interaction. When the user sees the
Calculate button disabled, it’s quite obvious that unless all values
are entered, the tip cannot be calculated.
Look at in Example 1-8. Here the Other Tip Percentage text field is disabled. This is done because the “15% tip” radio button is selected by default when the application loads. This default selection on application load is done via the main.xml file. Look at the line of main.xml where the following statement selects the “15% tip” radio button:
android:checkedButton="@+id/radioFifteen"
The RadioGroup
attribute
android:checkedButton
allows you to select one of the RadioButton
widgets in the group by default.
Most users who have used popular applications on the desktop as well as the Web are familiar with the “disabled widgets enabled on certain conditions” paradigm. Adding such small conveniences always makes an application more usable and the user experience richer.
Processing UI events
Like popular Windows, Java Swing, Flex, and other UI frameworks, Android also provides an event model which allows you to listen to certain events in the UI caused by user interaction. Let’s see how we can use the Android event model in our application.
First let’s focus on the radio buttons in the UI. We want to
know which radio button the user selected, as this will allow us to
determine the tip percentage in our calculations. To “listen” to radio
buttons, we use the static interface OnCheckedChangeListener()
. This will
notify us when the selection state of a radio button changes.
In our application, we want to enable the Other Tip Percentage text field only when the Other radio button is selected. When the “15% tip” and “20% tip” buttons are selected we want to disable this text field. Besides this, we want to add some more logic for the sake of usability. As we discussed before, we should not enable the Calculate button until all the required fields have valid values. In terms of the three radio buttons, we want to ensure that the Calculate button gets enabled for the following two conditions:
The Other radio button is selected and the Other Tip Percentage text field has valid values.
The “15% tip” or “20% tip” radio button is selected and the Total Amount and No. of People text fields have valid values
Look at Example 1-9, which deals with the radio buttons. The source code comments are quite self-explanatory.
/* * Attach an OnCheckedChangeListener to the * radio group to monitor radio buttons selected by user */ rdoGroupTips.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { // Enable/disable Other Tip Percentage field if (checkedId == R.id.radioFifteen || checkedId == R.id.radioTwenty) { txtTipOther.setEnabled(false); /* * Enable the calculate button if Total Amount and No. of * People fields have valid values. */ btnCalculate.setEnabled(txtAmount.getText().length() > 0 && txtPeople.getText().length() > 0); } if (checkedId == R.id.radioOther) { // enable the Other Tip Percentage field txtTipOther.setEnabled(true); // set the focus to this field txtTipOther.requestFocus(); /* * Enable the calculate button if Total Amount and No. of * People fields have valid values. Also ensure that user * has entered an Other Tip Percentage value before enabling * the Calculate button. */ btnCalculate.setEnabled(txtAmount.getText().length() > 0 && txtPeople.getText().length() > 0 && txtTipOther.getText().length() > 0); } // To determine the tip percentage choice made by user radioCheckedId = checkedId; } });
Monitoring key activity in text fields
As I mentioned earlier, the Calculate button must not be enabled unless the text fields have valid values. So we have to ensure that the Calculate button will be enabled only if the Total Amount, No. of People, and Other Tip Percentage text fields have valid values. The Other Tip Percentage text field is enabled only if the Other Tip Percentage radio button is selected.
We do not have to worry about the type of values, that is,
whether the user entered negative values or letters because the
android:numeric
attribute has been defined for the
text fields, thus limiting the types of values that the user can
enter. We have to just ensure that the values are present.
So we use the static interface
OnKeyListener()
. This will notify us when a key is pressed. The
notification reaches us before the actual key pressed is sent to the
EditText
widget.
Look at the code in Examples 1-10 and 1-11 which deal with key events in the text fields. As in Example 1-9, the source code comments are quite self-explanatory.
/* * Attach a KeyListener to the Tip Amount, No. of People and Other Tip * Percentage text fields */ txtAmount.setOnKeyListener(mKeyListener); txtPeople.setOnKeyListener(mKeyListener); txtTipOther.setOnKeyListener(mKeyListener);
Notice that we create just one listener instead of creating anonymous/inner listeners for each text field. I am not sure if my style is better or recommended, but I always write in this style if the listeners are going to perform some common actions. Here the common concern for all the text fields is that they should not be empty, and only when they have values should the Calculate button be enabled.
/* * KeyListener for the Total Amount, No of People and Other Tip Percentage * text fields. We need to apply this key listener to check for the following * conditions: * * 1) If the user selects Other Tip Percentage, then the Other Tip Percentage text field * should have a valid tip percentage entered by the user. Enable the * Calculate button only when the user enters a valid value. * * 2) If the user does not enter values in the Total Amount and No. of People fields, * we cannot perform the calculations. Hence we enable the Calculate button * only when the user enters valid values. */ private OnKeyListener mKeyListener = new OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { switch (v.getId()) { case R.id.txtAmount: case R.id.txtPeople: btnCalculate.setEnabled(txtAmount.getText().length() > 0 && txtPeople.getText().length() > 0); break; case R.id.txtTipOther: btnCalculate.setEnabled(txtAmount.getText().length() > 0 && txtPeople.getText().length() > 0 && txtTipOther.getText().length() > 0); break; } return false; } };
At in Example 1-11, we examine the ID of the
View
. Remember that each widget has a unique ID as
we define it in the main.xml file. These values
are then defined in the generated R.java
class.
At and , if the key event occurred in the Total Amount or No. of People fields, we check for the value entered in the field. We are ensuring that the user has not left both fields blank.
At we check if the user has selected the Other radio button, and then we ensure that the Other text field is not empty. We also check once again if the Total Amount and No. of People fields are empty.
So the purpose of our KeyListener
is now
clear: ensure that all text fields are not empty and only then enable
the Calculate button.
Listening to button clicks
Now we will look at the Calculate and Reset buttons. When the
user clicks these buttons, we use the static interface
OnClickListener()
which will let us know when a button is clicked.
As we did with the text fields, we create just one listener and
within it we detect which button was clicked. Depending on the button
that was clicked, the calculate()
or
reset()
method is called.
Example 1-12 shows how the click listener is added to the buttons.
/* Attach listener to the Calculate and Reset buttons */ btnCalculate.setOnClickListener(mClickListener); btnReset.setOnClickListener(mClickListener);
Example 1-13 shows how to detect which button
is clicked by checking for the ID of the View
that
receives the click event.
/** * ClickListener for the Calculate and Reset buttons. * Depending on the button clicked, the corresponding * method is called. */ private OnClickListener mClickListener = new OnClickListener() { @Override public void onClick(View v) { if (v.getId() == R.id.btnCalculate) { calculate(); } else { reset(); } } };
Resetting the application
When the user clicks the Reset button, the text fields should be cleared, the default “15% tip” radio button should be selected, and any results calculated should be cleared.
Example 1-14 shows the
reset()
method.
/** * Resets the results text views at the bottom of the screen as well as * resets the text fields and radio buttons. */ private void reset() { txtTipAmount.setText(""); txtTotalToPay.setText(""); txtTipPerPerson.setText(""); txtAmount.setText(""); txtPeople.setText(""); txtTipOther.setText(""); rdoGroupTips.clearCheck(); rdoGroupTips.check(R.id.radioFifteen); // set focus on the first field txtAmount.requestFocus(); }
Validating the input to calculate the tip
As I said before, we are limiting what type of values the user can enter in the text fields. However, the user could still enter a value of zero in the Total Amount, No. of People, and Other Tip Percentage text fields, thus causing error conditions like divide by zero in our tip calculations.
If the user enters zero we must show an alert pop up asking the
user to enter non-zero values. We handle this with a method called
showErrorAlert(String errorMessage, final int
fieldId)
, but we will discuss this in more detail
later.
First, look at Example 1-15 which shows the
calculate()
method. Notice how the values entered
by the user are parsed as double values.
Now notice and where we check for zero values. If the user enters zero, we show an alert pop up to warn the user. Next, look at , where the Other Tip Percentage text field is enabled because the user selected the Other radio button. Here, too, we must check for the tip percentage being zero.
When the application loads, the “15% tip” radio button is
selected by default. If the user changes the selection, we assign the
ID of the selected radio button to the member variable
radioCheckedId
, as we saw in Example 1-9, in
OnCheckedChangeListener
.
But if the user accepts the default selection, the
radioCheckedId
will have the default value of
–1
. In short, we will never know which radio button
was selected. Of course, we know which one is selected by default and
could have coded the logic slightly differently, to assume 15% if
radioCheckedId
has the value –1
.
But if you refer to the API, you will see that we can call the method
getCheckedRadioButtonId()
on the RadioGroup
and not on individual
radio buttons. This is because
OnCheckedChangeListener
readily provides us with
the ID of the radio button selected.
Showing the results
Calculating the tip is simple. If there are no validation
errors, the boolean flag isError
will be
false
. Look at
through in Example 1-15 for the simple tip calculations. Next, the
calculated values are set to the TextView
widgets
from to .
/** * Calculate the tip as per data entered by the user. */ private void calculate() { Double billAmount = Double.parseDouble( txtAmount.getText().toString()); Double totalPeople = Double.parseDouble( txtPeople.getText().toString()); Double percentage = null; boolean isError = false; if (billAmount < 1.0) { showErrorAlert("Enter a valid Total Amount.", txtAmount.getId()); isError = true; } if (totalPeople < 1.0) { showErrorAlert("Enter a valid value for No. of People.", txtPeople.getId()); isError = true; } /* * If the user never changes his radio selection, then it means * the default selection of 15% is in effect. But it's * safer to verify */ if (radioCheckedId == -1) { radioCheckedId = rdoGroupTips.getCheckedRadioButtonId(); } if (radioCheckedId == R.id.radioFifteen) { percentage = 15.00; } else if (radioCheckedId == R.id.radioTwenty) { percentage = 20.00; } else if (radioCheckedId == R.id.radioOther) { percentage = Double.parseDouble( txtTipOther.getText().toString()); if (percentage < 1.0) { showErrorAlert("Enter a valid Tip percentage", txtTipOther.getId()); isError = true; } } /* * If all fields are populated with valid values, then proceed to * calculate the tips */ if (!isError) { Double tipAmount = ((billAmount * percentage) / 100); Double totalToPay = billAmount + tipAmount; Double perPersonPays = totalToPay / totalPeople; txtTipAmount.setText(tipAmount.toString()); txtTotalToPay.setText(totalToPay.toString()); txtTipPerPerson.setText(perPersonPays.toString()); } }
Showing the alerts
Showing the alerts Android provides the
AlertDialog
class to show alert pop ups. This lets us show a dialog with
up to three buttons and a message.
Example 1-16 shows the
showErrorAlert
method which uses this
AlertDialog
to show the error messages. Notice that
we pass two arguments to this method: String
errorMessage
and int fieldId
. The first
argument is the error message we want to show to the user. The
fieldId
is the ID of the field which caused the
error condition. After the user dismisses the alert dialog, this
fieldId
will allow us to request the focus on that
field, so the user knows which field has the error.
/** * Shows the error message in an alert dialog * * @param errorMessage * String for the error message to show * @param fieldId * the Id of the field which caused the error. * This is required so that the focus can be * set on that field once the dialog is * dismissed. */ private void showErrorAlert(String errorMessage, final int fieldId) { new AlertDialog.Builder(this).setTitle("Error") .setMessage(errorMessage).setNeutralButton("Close", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { findViewById(fieldId).requestFocus(); } }).show(); }
When all this is put together, it should look like Figure 1-39.
Conclusion
Developing for the Android OS is not too different from developing for any other UI toolkit, including Microsoft Windows, X Windows, Java Swing, or Adobe Flex. Of course Android has its differences and, overall, a very good design. The XML layout paradigm is quite cool and useful for building complex UIs using simple XML. In addition, the event handling model is simple, feature-rich, and intuitive to use in code.
Source Download URL
You can download the source code for this example from http://www.vidyut.com/sunit/android/tipster.zip.
Binary Download URL
You can download the executable code for this example from http://www.vidyut.com/sunit/android/tipster.zip.
Get Android Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.