Android App Development
Android App Development
APP DEVELOPMENT
Hervé J. Franceschi
Loyola University Maryland
Jones & Bartlett Learning books and products are available through most
bookstores and online booksellers. To contact Jones & Bartlett Learning
directly, call 800-832-0034, fax 978-443-8000, or visit our website,
www.jblearning.com.
All rights reserved. No part of the material protected by this copyright may
be reproduced or utilized in any form, electronic or mechanical, including
photocopying, recording, or by any information storage and retrieval system,
without written permission from the copyright owner.
The content, statements, views, and opinions herein are the sole expression
of the respective authors and not that of Jones & Bartlett Learning, LLC.
Reference herein to any specific commercial product, process, or service by
trade name, trademark, manufacturer, or otherwise does not constitute or
imply its endorsement or recommendation by Jones & Bartlett Learning, LLC,
and such reference shall not be used for advertising or product endorsement
purposes. All trademarks displayed are the trademarks of the parties noted
herein. Android App Development is an independent publication and has not
been authorized, sponsored, or otherwise approved by the owners of the
trademarks or service marks referenced in this product.
There may be images in this book that feature models; these models do not
necessarily endorse, represent, or participate in the activities represented in
the images. Any screenshots in this product are for educational and
instructive purposes only. Any individuals and scenarios featured in the case
studies throughout this product may be real or fictitious, but are used for
instructional purposes only.
09365-0
Production Credits
VP, Executive Publisher: David D. Cella
Executive Editor: Matt Kane
Acquisitions Editor: Laura Pagluica
Associate Editor: Taylor Ferracane
Associate Production Editor: Rebekah Linga
Director of Marketing: Andrea DeFronzo
Marketing Manager: Amy Langlais
VP, Manufacturing and Inventory Control: Therese Connell
Composition: S4Carlisle Publishing Services
Cover Design: Kristin E. Parker
Rights & Media Specialist: Merideth Tumasz
Media Development Editor: Shannon Sheehan
Cover Image: © Fon_nongkran/Shutterstock
Printing and Binding: RR Donnelley
Cover Printing: RR Donnelley
Library of Congress Cataloging-in-Publication Data
Names: Franceschi, Hervé, author.
Title: Android app development / Herve J. Franceschi, Loyola University
Maryland.
Description: Burlington, Massachusetts : Jones & Bartlett Learning, [2017] |
Includes bibliographical references and index.
Identifiers: LCCN 2016038890 | ISBN 9781284092127
Subjects: LCSH: Android (Electronic resource) | Mobile computing. |
Application software—Development.
Classification: LCC QA76.76.A65 F73 2017 | DDC 005.3—dc23
LC record available at https://lccn.loc.gov/2016038890
6048
Printed in the United States of America
20 19 18 17 16 10 9 8 7 6 5 4 3 2 1
DEDICATION
Pedagogy
Within each chapter, and therefore within each app, the instructor can pick
what topics he or she chooses to cover and in what depth as well as at what
pace. The Model, which typically uses basic Java classes and is Android
independent, can be downloaded and briefly explained. That allows the
instructor to concentrate on the View and the Controller, which include
Android-specific topics. Screenshots, examples, and tables are included
throughout each chapter to illustrate the concepts at hand and the current
state of the chapter app. Software Engineering and Common Error boxes
highlight software engineering tips and common errors throughout the
chapters. Chapter summaries are provided at the end of each chapter
followed by exercises, problems, and projects to test students’ knowledge.
Each chapter includes extensive multiple-choice, fill in the code, and write an
app exercises.
SOFTWARE ENGINEERING: When defining an EditText , choose the
appropriate value for android:inputType in order to match the data we
expect the user will input.
▸ View: activity_main.xml
android:textSize void setTextSize( int unit, float size ) Sets the size of the text
android:hint void setHint( int resource ) Sets the hint text to display
Chapter-by-Chapter Overview
The first nine chapters cover essential concepts of Android app development.
The last seven chapters cover more specific topics.
We explain the Model View Controller architecture and make our first app, a
tip calculator, using the MVC. We define a GUI using a RelativeLayout and
TextViews, EditTexts and a Button. We explain how to separate the various
styles used in an app from the contents of the app (similar to CSS in web
design). We cover event handling: clicking on a button and typing on a soft
keyboard.
Chapter 3: Coding the GUI Programmatically, Layout
Managers
Continuing with the MVC architecture, we explain how to define a GUI and
set up event handling programmatically for a tic-tac-toe app. We explain how
to use inner classes, layout parameters, and alert dialogs.
Chapter 4: Multiple Activities, Passing Data between
Activities, Transitions, Persistent Data
We cover how to include multiple activities within an app, and explain how to
pass data between activities. We explore the life-cycle methods of an activity
and how they can be used, particularly in the context of an app with multiple
activities. We demonstrate how to set up an animated transition between one
activity and another. We explore the RelativeLayout class in greater depth as
well as the TableLayout class. Finally, we show how to deal with persistent
data. We use a mortgage app as the vehicle for the concepts presented: the
first screen displays mortgage data, including the monthly payment, and the
second screen enables the user to edit the mortgage characteristics
(amount, interest rate, and number of years).
Chapter 5: Menus, SQLite
We show how to include a menu in an app and how to use SQLite to handle
persistent data. We use a ScrollView when generating a GUI
programmatically. The app is a candy store manager that enables the user to
add, update, and delete candies, as well as ring the cash register for a
customer using the Toast class.
In this chapter, we show how to detect a change in the device orientation and
explore various ways of coding the GUI so that an app works in both vertical
and horizontal orientations. In particular, we dynamically retrieve the
dimensions of the device so that we can size and position the GUI
components accordingly. There is no meaningful app in this chapter, we
simply explore the various ways of managing the GUI with orientation
changes.
Chapter 7: Touches and Swipes
We first show how to detect and handle a touch or a swipe event. More
generally, we also show how to detect and handle a gesture, single tap, or
double tap event. We use touch and gesture handling to build a puzzle game
app: the user can solve a puzzle by touching and moving the puzzle pieces.
The puzzle colors are generated randomly and feedback is given to the user
when he or she solves the puzzle.
Chapter 8: Graphics, Animations, Sounds, and
Gaming
We explore how to create a custom View, how to draw shapes and bitmaps,
how to create a game by animating objects on the screen, handling events,
and making sounds. We use the Timer and TimerTask classes to create a
game loop and update the state of the game at a defined frequency. In this
duck hunting app, a duck flies from the right of the screen in an animated
fashion, using four png frames. The user can control a cannon, a drawn
shape, located at the bottom left of the screen by moving its barrel with
touches. A double tap fires a bullet, a drawn shape, and we detect collision
with the duck. If the duck is shot, a sound is played, we stop animating the
duck, and it falls down vertically.
Chapter 9: Fragments
We use the Google maps activity template to display a map with annotations,
and use speech recognition to change the map. We also explain how to use
Google Play Services. In this app, the user can display a map centered on a
city (London, Paris, Rome, or Washington, DC) by saying its name, and then
move the map by saying north, south, west, or east.
We explain how to use Google Play Services, and how to use a device’s
GPS within an app. The app manages the distance and time left to a
destination, first enabling the user to update them, and eventually updating
them both periodically and automatically.
Chapter 12: Using Another App within the App: Taking
a Photo, Graying It, and Sending an Email
We explain how to use another app, such as a camera app, within an app,
how to store files in external storage, and how to use an email app within an
app. We also explore seek bars (sliders). The app opens a camera app and
when the user takes a picture, enables the user to gray the picture using
three seek bars. In the Model, we explain how to access each individual pixel
of a Bitmap, both for reading and writing. After the user is done graying the
image, the user can send an email to a friend and we automatically attach
the grayed picture.
Chapter 13: XML and Content Apps
We parse local and remote XML documents using a SAX parser. We show
how we can use the AsyncTask class to perform a task in the background of
an app. We display results in a ListView and open a web browser inside the
app. The app is a content app that retrieves data from a remote URL
(http://blogs.jblearning.com/computer-science/feed/), parses its XML content
into an ArrayList of items, and displays the items in a ListView. When the
user clicks on one of the items, we open the browser at the URL
corresponding to that item.
Chapter 14: Making an Android Widget
We cover the various steps to make a widget: we first make a very simple
widget, then make it dynamic, then retrieve temperature data from a remoter
website, and finally make the widget customizable by the user. We start with
hard coded city and temperature, then give the widget a style, then add
some dynamic data retrieving the date and time from the device, and enable
the user to update the widget data by clicking on it. Next, we retrieve the
temperature data from a remote website using the AsyncTask class. The
data comes as a JSON string so we access the data we want within the
JSON string (the temperature) and display it inside the widget. Finally, we
make the widget customizable by enabling the user to set the city and state
for which we retrieve the temperature data.
TicTacToe (Chapter 3)
Appendices
▸ A micro-chapter on how to retrieve the height of the action bar and the
status bar dynamically (including a micro-app for it): this is important if
we intend to code the GUI programmatically and make the app device
independent.
▸ How to use Google Play Services: this is important for apps involving
maps, GPS, ads, and other services from Google.
▸ Test bank including sample midterm and final exams with mini-apps
Acknowledgments
First and foremost, we would like to thank our publisher, Jones & Bartlett
Learning, especially Laura Pagluica, Acquisitions Editor, Taylor Ferracane,
Associate Editor, Bharathi Sanjeev, Project Manager, and Rebekah Linga,
Associate Production Editor.
Georgia Brown, MS
Computer Science Department
Northern Illinois University
Roy Kravitz
Portland State University
Preface
1.1.1 Smartphones
2.5 GUI Components and More XML Attributes: Tip Calculator, Version 1
4.2 Using a TableLayout for the Front Screen GUI: Mortgage Calculator App, Version 0
5 Menus, SQLite
5.1 Menus and Menu Items: Candy Store App, Version 0
5.3 SQLite: Creating a Database, a Table, and Inserting Data: Candy Store App, Version 2
6.4 Strategy 2: One Layout XML File for Both Orientations, Modify the Layout by Code
8.4 Animating an Object: Flying the Duck, Duck Hunting App, Version 1
8.5 Handling Touch Events: Moving the Cannon and Shooting, Duck Hunting App, Version 2
8.6 Playing a Sound: Shooting, Collision Detection, Duck Hunting App, Version 3
9 Fragments
9.1 The Model
9.2 Fragments
9.3 Defining and Adding a Fragment to an Activity Using a Layout XML File, Hangman, App
Version 0
9.4 Adding GUI Components, Styles, Strings, and Colors, Hangman, App Version 1
9.5 Defining a Fragment Using a Layout XML File and Adding the Fragment to an Activity by
9.6 Defining and Adding a Fragment to an Activity by Code, Hangman, App Version 3
9.7 Communication between Fragments and Their Activity: Enabling Play, Hangman, App
Version 4
9.10 Improving the GUI: Processing the Keyboard Input Directly, Hangman, App Version 7
10.8 Voice Recognition Part B, Moving the Map with Voice Once, App Version 5
10.9 Voice Recognition Part C, Moving the Map with Voice Continuously, App Version 6
11.2 Using the GPS to Retrieve Our Location, GPS App, Version 1
12 Using Another App within the App: Taking a Photo, Graying It, and
Sending an Email
12.1 Accessing the Camera App and Taking a Picture, Photo App, Version 0
12.6 Using the Email App: Sending the Grayed Picture to a Friend, Photo App, Version 5
13.5 Opening a Web Browser Inside the App, Web Content App, Version 4
14.4 Updating the Data in the Widget by Clicking on It, Temperature Widget, Version 3
14.5 Retrieving the Temperature Data from a Remote Source, Temperature Widget, Version 4
14.7 Hosting the Widget in the Lock Screen, Temperature Widget, Version 6
15 In App Advertising
15.1 The View, Stopwatch App, Version 0
15.6 Managing the Life Cycle of the AdView, Stopwatch App, Version 5
16.4 Asymmetric Encryption: Adding RSA to the Model, Encryption App, Version 2
16.5 Symmetric and Asymmetric Encryption: Modifying the View, Encryption App, Version 3
APPENDIX A Retrieving the Height of the Status Bar and the Action
Bar Dynamically
INDEX
CHAPTER ONE: Basics of Android,
First App: HelloAndroid
1.1.1 Smartphones
Chapter Summary
1.1.1 Smartphones
A smartphone is an intelligent cellular phone, or a cellular phone with a
computer inside, as well as some extra hardware. Thus, programmers can
write application software for them; such application software is called an
app. A smartphone has the typical components of a regular computer: a
CPU, memory, a storage device, an operating system, and other devices,
such as a camera, an accelerometer, or a GPS.
The two most well-known operating systems are the Android operating
system, from Google, and iOS, from Apple. Other popular operating systems
for smartphones include BlackBerry, Windows, and Symbian. The annual
number of smartphones sold worldwide is now over one billion. Furthermore,
smartphones comprise an increasing percentage of the mobile phones sold.
1.1.2 Android Phones
There are over 100 different varieties of Android phones or tablets. They can
have a different CPU, a different screen resolution, and a different amount of
memory available. This makes it difficult for developers to test their apps on
actual devices. The dimensions of the various components of the user
interface of an app can be different depending on the Android phone or
tablet. Furthermore, in complex games where speed is an issue, an app can
run differently on an old and slow Android device as compared to a more
recent one. This is something to keep in mind as we develop apps for the
Android market.
There are now over one million apps in Google Play, and the vast majority of
those apps are free. Top categories include entertainment (games),
personalization, tools, books, and references. Most of the downloaded apps
are free apps. One should also be aware that the Android operating system
is open, so anyone can easily copy an app from one Android device to
another. There is little protection for intellectual property.
1.2 Development Environment for Android Apps
▸ Android Studio
▸ Download and install the latest Java SDK (if we have not done it yet),
which we can find at
http://www.oracle.com/technetwork/java/javase/downloads/index.html.
▸ Download and install Android Studio, including the IDE, SDK tools, and
the emulator system, which we can find at
http://developer.android.com/sdk/index.html
In the dialog box shown in FIGURE 1.3, we type in our project name (we
choose HelloAndroid) and our domain (we chose jblearning.com; if we do not
have a domain name, we can choose any name); the other two fields
(package name and project location) are automatically filled. If we prefer a
different project location, we can edit that field. Note that the package name
is our domain name in reverse. It is typical for developers to name their
package with their domain name in reverse; it insures that it is unique. When
we are done, we click Next.
The next dialog box (FIGURE 1.4) is used to specify the minimum SDK for
this project; depending on our app, this can be important. For example, if we
incorporate advertising, this may require a higher SDK level than the default
level; however, the more recent the SDK level we specify, the more we
restrict the potential number of users for our app. For this app, we keep the
default SDK level and click Next.
FIGURE 1.1 Downloading components
FIGURE 1.2 Welcome screen
In the next dialog box (FIGURE 1.5), we can choose among several
templates; a template creates skeleton code with some predefined user-
interface functionality; often, it provides a user interface that is similar to
what can be found in native apps. For this app, we choose the Empty Activity
template—it creates a minimum skeleton code.
FIGURE 1.5 Dialog box to choose the type of activity
The project directory structure has been created along with many source
files, which we can see on the left pane of the Android Studio development
environment (shown in FIGURE 1.7).
▸ The java directory contains the Java source files. We can add more
Java source files as our app gets more complex.
▸ Inside the res directory, the mipmap directory contains the icon for the
app. As needed, we can add resources to this directory.
▸ Inside the res directory, the layout directory contains XML files defining
screen layouts. At this point, it contains the activity_main.xml file, an
automatically generated layout file for our empty activity. We can edit
this file to define the Graphical User Interface (GUI) for this app.
▸ Inside the res directory, the values directory contains XML files
defining various resources such as colors (in the colors.xml file),
dimensions (in the dimens.xml file), styles (in the styles.xml file), or
strings (in the strings.xml file). We can edit these files to define more
color, dimension, style, or string resources.
▸ The gradle scripts directory contains the scripts used to build the app.
FIGURE 1.11 Choosing a theme for the preview (after selecting Holo
Dark)
We can find the full XML documentation at http://www.w3.org/XML/.
Although the actual specification is a bit more complex, we will use the
following simplified syntax to define a non-empty XML element:
Element Content
</tagName>
<price>46.00</price>
<app language=”Java” version=”7.0”>Hello Android</app>
If an element has no content, then we can use an empty element tag with the
following syntax:
<website name=”twitter”/>
There are rules for naming tags; we use only tag names that start with a
letter or an underscore and are followed by 0 or more letters, underscores,
or digits. The official specification for tag names is more complex than the
earlier, and we can find it at http://www.w3.org/XML/Core/#Publications.
The file activity_main.xml defines how the GUI should look like. The
RelativeLayout element defines that the various graphical elements will be
In the activity_main.xml file the element RelativeLayout uses the first syntax
and includes attributes android:layout_width and android:layout_height ,
which both have value match_parent (lines 5–6). This means that the
RelativeLayout element will be as big as its parent element, which in this
padding on the left side of the screen equal to the value of the dimen element
named activity_vertical_margin (in this case 16 px) in the dimens.xml file
located in the values directory of the res directory.
Inside the dimens.xml file, the syntax for defining a dimen resource is:
Similarly, the String whose value is HelloAndroid (see the blue title of the
app in Figure 1.13) is defined at line 2 of the strings.xml file (EXAMPLE 1.4),
located in the values directory of the res directory. We can create String
constants in our Java files, but it is recommended to store String constants
in the strings.xml file as much as possible. If we want to modify one or
several String values later, it is easier to edit that file than to edit our Java
code.
Inside the strings.xml file, the syntax for defining a String resource is:
<string name=”stringName”>valueOfString</string>
The file styles.xml, located in the values directory of the res directory, defines
the various styles used in the app. EXAMPLE 1.6 shows its automatically
generated contents.
Inside the styles.xml file, we can modify a style by adding an item element
using this syntax:
<item name=”styleAttribute”>valueOfItem</item>
The name of the style attribute that specifies the text size inside a TextView
is android:-textSize . In EXAMPLE 11.7, we change the default text size to
40 at line 6. The environment updates its preview as shown in FIGURE 1.15.
Note that the style defined in styles.xml consists of three color constants
using the syntax @color/color_name . These constants are defined in the
colors.xml file, shown in EXAMPLE 1.8, and also located in the values
directory of the res directory. Its contents are automatically generated.
Inside the colors.xml file, the syntax for defining a color resource is:
<color name=”colorName”>valueOfColor</color>
FIGURE 1.15 A preview of the app in the environment after updating the
styles.xml file
FIGURE 1.16 A preview of the app in the environment after updating the
colors.xml file
The color values are defined using the syntax æRRGGBB where R , G , and B
are hexadecimal digits representing the amount of red, blue, and green,
respectively.
If we modify the value of the color named colorPrimary to æFF0000 (full red),
the preview is updated as shown in FIGURE 1.16.
EXAMPLE 1.9).
The source code for an Android app uses Java syntax. Line 1 is the package
declaration. When this project was created, we told Android Studio to place
the code in the package com.jblearning.helloandroid . Lines 3–4 import the
classes used in this class. We need to import AppCompat-Activity because
MainActivity extends it (line 6) and thus inherits from it. AppCompatActivity
inherits (indirectly) from the Activity class and includes an action bar (in red
on Figure 1.16) above the activity screen, where the app title shows.
The onCreate method (lines 8–12) is automatically called when the activity
starts. We should override this method and if needed, create the components
of the activity inside that method. At line 10, we call the onCreate method
inherited from Activity via AppCompatActivity . At line 11, we define the
layout of the app by calling the setContentView method, shown in TABLE 1.1.
The layout is defined in the activity_main.xml file. The argument of the
setContentView method is an integer that represents the id of a layout
class is public and static and contains one or many int constants. Each
int constant is accessible via the syntax:
R.className.constantName
Method Description
void setContentView( int Sets the content of this Activity using the resource with id
layoutResID ) layoutResID, which will be inflated
Android Studio includes an emulator so that we can run and test an app
inside the environment before testing it on a device. We first run the app in
the emulator. In order for the emulator to work, we may need to do three
things:
The emulator runs inside an Android Virtual Device (AVD). We can create
AVDs with the AVD Manager. To open the AVD Manager, choose Window,
AVD Manager, or click on the AVD Manager icon, as shown in FIGURE 1.17.
This opens the AVD Manager, as shown in FIGURE 1.18. To create a new
AVD, we click on the + Create Virtual Device. . . button. That opens a new
panel (FIGURE 1.19) that allows us to select among a list of premade AVDs.
After selecting one from the list and clicking on Next, it is displayed in a new
panel (FIGURE 1.20). We select the version we want and click on next. The
characteristics of the AVD are displayed, as shown in FIGURE 1.21. We can
edit them if we want; in particular, we can edit the Scale attribute if we want
to change the resolution of the AVD (and the size of the emulator as a result)
when we run. After we click on Finish, the new AVD has been added, as
shown in FIGURE 1.22.
In order to run the app, click on the Run icon in the icon bar, as shown in
FIGURE 1.23.
A dialog box opens (FIGURE 1.24) and we choose the AVD we want to use
by selecting one from the list; then, we click on OK. We can also access the
AVD Manager by clicking on the Create New Emulator button. Note that if the
emulator is already running or an actual device is connected to our computer,
it will show in the panel under Connected Devices. After a couple of minutes,
the app should be running in the emulator. In order to save time, we
recommend that you leave the emulator open as long as Android Studio is
open.
FIGURE 1.23 Run configurations dialog box
FIGURE 1.24 Deployment target dialog box, after clicking on the Run
icon
FIGURE 1.25 shows the app running in the emulator. By default, the emulator
runs in vertical, also called portrait, orientation. It includes a toolbar on its
right that enables us to control some of its features. In particular, we can
rotate the emulator using the tool shown in FIGURE 1.26. We can also click
on Ctrl+F11 to rotate the emulator to the horizontal, or landscape,
orientation (see FIGURE 1.27; note that we did not include the toolbar in this
screenshot). Ctrl+F12 brings the emulator back to the vertical orientation.
We can move the emulator by clicking on the top, white part of its frame and
dragging it. TABLE 1.2 summarizes useful information about the emulator.
Turn on virtualization in the BIOS (if necessary) Enable the emulator to run
Install and run the HAXM executable (if necessary) Enable the emulator to run
Just like with a regular Java program, we can send output to the console,
which can be very useful for debugging purposes. In order to do that, we can
use one of the static methods of the Log class shown in TABLE 1.3. The
Log class is part of the android.util package. This can be very useful at
There can be a lot of output inside Logcat. We can filter the output inside
Logcat by using a filter so it only shows the output that we select from the
app. To set up a filter, click on the drop-down list located on the top right
corner inside the lower left panel; select Edit Filter Configuration (shown on
the right side of FIGURE 1.30). Inside the dialog box shown on FIGURE
1.31, give the filter a tag. We choose MainActivity : in this way, it identifies
that this output was generated by some code inside the MainActivity class.
When using the methods of the Log class to output data, we use
MainActivity as the first argument (the tag) of those methods. In turn, when
Method Description
static int d( String This method sends a debug message; tag identifies the source of the log
tag, String msg ) message; it can be used to filter messages in Logcat.
As we run the app, we see the result of our output statement in Figure 1.29.
As the figure shows, the Logcat tab is selected (top left), and the
MainActivity filter is selected (top right). We can verify that the id of the
The Android Studio includes many features that improve the programmer’s
experience, saves time by automatically generating code and prevents
errors. Here are a few of these features:
▸ If we use a class that has not been imported yet, it will underline it and
suggests to import it by typing Alt+Enter, saving us some time.
To run the app in debug mode, we click on the debug icon on the toolbar,
shown in Figure 1.23. The app runs and stops at the first breakpoint. The
debugger tab is selected in the panel at the bottom of the screen and we can
see some debugging information and tools. Under Frames, we can see
where in the code we are currently executing. Under Variables, we can check
the values of the various variables, for example MA , which has value
MainActivity . To resume the app, we click on the green Resume icon at the
As we resume, stop at breakpoints, and resume the app a few times, the
values of the various variables in our app are displayed under Variables.
FIGURE 1.36 shows that count and i both have a value of 2 at that point.
If there are too many variables showing up under Variables and we only want
to look at a few of them, we can make a list of variables in the right bottom
panel, under Watches. We click on the + sign to add a variable to the list and
click on the – sign to take a variable off the list. FIGURE 1.37 shows the
value of count after we have added it to the list of watched variables.
The driver for the Android device can be downloaded from the device
manufacturer’s website. For example, for Samsung’s Android devices, the
website is http://www.samsung.com/us/support/downloads#.
Once we have done that, Android Studio will detect the connected device
and display its name as well as its API level at the top left corner of the
lower panel (assuming the lower panel is open, i.e., the Android tab is
selected), as shown in FIGURE 1.38. We use a Samsung Galaxy Tab 2 7.0
tablet.
After we click on the Run or Debug button, a dialog box appears (FIGURE
1.39). The device appears in the list of devices or emulators we can run on.
Select the device and click on OK. A few seconds later, the app runs (and is
installed) on the device. Testing an app on an actual device is easier and
faster than using the emulator. FIGURE 1.40 shows the app running in the
tablet. Note that we can still see the output in Logcat if we have output
statements.
FIGURE 1.40 HelloAndroid app running inside the tablet
We should supply a launcher icon for our app. This is the visual
representation of our app on the Home screen or the apps screen. A
launcher icon for a mobile device should be 48 × 48 dp. Various devices can
have different screen densities, thus, we can supply several launcher icons,
one for each density. When doing that, we should follow the 2/3/4/6/8 scaling
ratios between the various densities from medium (2) to xxx-high (8). If we
only supply one icon, Android Studio will use that icon and expand its density
as necessary. TABLE 1.4 shows an example of possible dimensions using
that rule. If we intend to publish, we should provide a 512 × 512 launcher
icon for display in Google Play. We can find more information at
https://www.google.com/design/spec/style/icons.html. FIGURE 1.41
shows the mipmap directory after we added a file named hi.png whose
dimensions are 48 × 48.
To set the launch icon for the app to hi.png, we assign the String @mipmap/hi
to the android:icon attribute of the application element in the
AndroidManifest.xml file. The @mipmap/hi expression defines the resource in
the mipmap directory (of the res directory) whose name is hi (note that we
do not include the extension). Line 7 of Example 1.11 becomes:
TABLE 1.4 Launcher icon ratios and possible dimensions
Scaling ratio 2 3 4 6 8
Dots per inch 160 dpi 240 dpi 320 dpi 480 dpi 640 dpi
android:icon=”@mipmap/hi”
One thing we have to worry about is that some users will use the app in
vertical (portrait) orientation, and others will use the app in horizontal
(landscape) orientation. The default behavior for an app is to rotate the
screen as the user rotates the device, thus, our current app works in both
orientations. FIGURE 1.42 shows the app running inside the tablet in
horizontal orientation. As an app gets more complex, this becomes an
important issue. Later in the book, we discuss alternatives and strategies to
manage orientations. Sometimes, we want the app to run in only one
orientation, vertical for example, and therefore we do not want the app to
rotate when the user rotates the device. Inside the activity element, we can
add the android:screenOrientation attribute and specify either portrait or
landscape as its value. For example, if we want our app to run in vertical
android:screenOrientation=”portrait”
Note that we can control the behavior of the app on a per activity basis. In
this app, there is only one activity, but there could be several. EXAMPLE
1.12 shows the updated AndroidManifest.xml file. If we run the app on a
device, the screen does not rotate as we rotate the device; it stays in portrait
orientation. FIGURE 1.43 shows the icon for the app in the emulator (among
native apps).
Apk files are built using the gradle build system, which is integrated in the
Android Studio environment. When we start an app, the gradle build scripts
are automatically created. They can be modified to build apps that require
custom building, for example:
▸ Create multiple apks for the app, each with different features, using
the resources of the project. For example, different versions can be
made for different audiences (paid versus free, consumer versus
corporation).
Chapter Summary
Android Studio is the official development environment for Android apps.
In addition to the JDK, an Android SDK is available for development.
Android Studio is free and available from Google.
Android development uses XML files to define the Graphical User
Interface (GUI), strings, styles, dimensions, etc.
The res directory stores some of the app resources, such as
activity_main.xml in the layout directory, and strings.xml, styles.xml,
colors.xml, and dimens.xml in the values directory.
The layout and GUI can be defined in XML files; the starting default file
is activity_main.xml.
Strings can be defined in the strings.xml file.
</b>Hello<b>
<c digit=”6”></c>
<d>He there</e>
<f letter='Z”/>
<1 digit=”8”>one</1>
<h><i name=”Chris”></i></h>
<j><k name=”Jane”></j>
<l><m name=”Mary”></l></m>
4. What is the name of the string that is defined by the following XML
snippet inside strings.xml?
<string name=”abc”>Hello</string>
string
name
abc
Hello
5. What is the value of the string that is defined by the following XML
snippet inside strings.xml?
<string name=”abc”>Hello</string>
string
name
abc
Hello
6. What will be the text displayed inside the TextView widget defined by the
following XML snippet inside activity_main.xml?
<TextView
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:text=”@string/hi” />
@string/hi
hi
android:text
android.app
android.Activity
android.support.v7.app
false
Fill in the Code
9. Inside the XML snippet next activity_main.xml, add a line of XML so that
the text displayed in the text field is the value of the string book (assume
that the string book has been defined in strings.xml)
<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
/>
10. Inside the onCreate method, fill in the code so that we set the layout and
GUI defined in activity_main.xml
Write an App
Chapter Summary
Introduction
Good apps provide useful functionality and an easy to use interface. The
user interface is made of various Graphical User Interface (GUI) components
and typically waits for user interaction. Some of these GUI components
enable user input, such as clicking on a button, entering data from the
keyboard, selecting an item from a list, and spinning a wheel. . . Those user
interactions are called events. When an event happens, the app processes
the event and updates the screen; this is called handling the event. In this
chapter, we learn about various GUI components, their associated events,
and how to handle events on those components. We also learn how to style
the look and feel of those components. Later, we will learn how to better
arrange those components on the screen with layout managers.
▸ activity_main.xml, which defined the GUI: this is called the View part of
the app.
▸ The MainActivity class, which displayed the GUI (more generally, the
Activity class manages the GUI); this is called the Controller part of
the app.
Our first app only displayed a label, so it did not have any functionality; the
functionality of an app is called the Model.
It is good design practice to separate these three parts: the Model, the View,
and the Controller. It makes the app much easier to design, code, maintain,
and modify later on. We could also reuse parts of the app, for example the
Model, to make another app.
In this chapter, we make a simple Tip Calculator app using the Model View
Controller architecture; in order to do that, we code three important files:
More generally, we can use the Model View Controller Store architecture.
This is useful when we have persistent data; we can store and retrieve data
in and from the Store.
The Model is typically straightforward Java code, and does not include any
GUI-related code. It is generally a good place to start.
Constructor
Methods
float getTip( )
float getBill( )
float tipAmount( )
float totalAmount( )
2.2 The Model
The Model encapsulates the Tip Calculator functionality. The Model should be
platform independent. It should be coded so that it could be used to build not
just this mobile app or another mobile app, but also a desktop application or
even an Internet website.
For this app, the Model is simple and is only composed of one class that
encapsulates a tip calculator, the TipCalculator class. It is a regular Java
class. In fact, we can even code this class outside the Android development
environment and test it before we bring it into our project. We choose to
place our class in the com.jblearning.tipcalculatorv0 package (and directory)
of our project, inside the Java directory. We select that directory, then select
File → New → Java Class, write the name of the class, and click OK. The
class skeleton appears. TABLE 2.1 shows the API for the TipCalculator
class.
EXAMPLE 2.1 shows the class code. In order to code the TipCalculator
class and a Model in general, we do not need to know anything about
Android programming. Furthermore, we can test the Model with a simple
Java program that tests all the methods of the class; this is left as an
exercise. We only have one class in this example, but in general, a Model
can include many classes and can be placed in its own directory. The most
useful methods for this app are the constructor (lines 7–10), and the
tipAmount (lines 30–32) and totalAmount (lines 34–36) methods.
We now turn our attention to the View and the capabilities of the Android
development environment for designing a GUI.
EXAMPLE 2.1 The TipCalculator class
In this chapter, we use the activity_main.xml file to create and define the GUI
components that are displayed on the screen. We can also define and
manipulate GUI components entirely by code, which we will do later in the
book.
GUI Components are objects that have a graphical representation; they are
also called widgets. The term widget also has another meaning for Android
users—it refers to a mini-app that is displayed and is running on the
Android’s Home or Lock screen, typically showing the app’s most relevant
information. GUI components can:
▸ display information
▸ allow the user to interact with them and trigger a call to a method.
A lot of GUI components share some functionality, and inherit from each
other. FIGURE 2.1 gives a summary of the inheritance hierarchy for some of
them.
A View represents the basic building block for GUI components; the View
class is in the android.view package. It occupies a rectangular area of the
screen, is drawable, and can respond to events. View is the superclass for
GUI components such as buttons, checkboxes, and lists. ViewGroup is the
superclass for layout classes and is also in the android.view package;
layouts are invisible containers that can contain other Views or other
ViewGroups . Most other classes shown in Figure 2.1, such as ImageView ,
View View A drawable rectangular area on the screen; can respond to events
Text field EditText Editable text field where user can enter data
Radio button RadioButton Two-state button that can be checked by the user; when used in a
group, checking a radio button automatically unchecks the other
radio buttons in the group
Checkbox CheckBox Two-state button that can be checked or unchecked by the user
Two-state ToggleButton Two-state switch button that can select between two options
toggle
button
Two-state Switch Similar to a ToggleButton; user can also slide between the two
toggle states
button
FIGURE 2.1 Inheritance hierarchy for selected GUI components
In order to keep things simple, we only allow the app to work in vertical
orientation, which we can specify in the AndroidManifest.xml file. We do that
by assigning the value portrait to the android:screenOrientation attribute of
the activity tag at line 13 in EXAMPLE 2.2.
We edit the activity_main.xml file to define the user interface. If the GUI is
static and does not change as the user interacts with the app,
activity_main.xml is a good way to define it. If the GUI is dynamic and
changes as the app is running, then we can create and define the user
interface by code. For example, if the app retrieves a list of links from a file
and displays them, we do not know in advance how many links we will
retrieve and that we will need to display. Typically, whatever we can do via
XML attributes and values in the activity_main.xml file, we can also do
programmatically.
Before editing the activity_main.xml file of our Tip Calculator app, let’s look at
the skeleton that is provided when we create a project. The TextView
element has the following two attributes and values:
android:layout_width="match_parent"
android:layout_height="wrap_content"
can be specified with regular View attributes, but are instead parsed by the
View ’s parent. They belong to the static class ViewGroup.LayoutParams ,
which Views use to tell their parents how they want to be laid out.
ViewGroup.LayoutParams is a static class nested inside the ViewGroup class.
The ViewGroup.LayoutParams class enables us to specify the width and height
of a View relative to its parent. We can use its android:layout_width and
android:layout_height XML attributes to specify the width and height of the
View , not only with absolute values, but also relative to the View’ s parent.
its parent, minus the padding. The wrap_content value, which corresponds to
the constant WRAP_CONTENT (whose value is –2) of the ViewGroup.LayoutParams
class, means that the View should just be dimensioned big enough to enclose
its contents, plus the padding.
Constant Value
MATCH_PARENT –1
WRAP_CONTENT –2
We can also dimension the View with absolute coordinates. If we want to
specify absolute dimensions, several units are available: px (pixels), dp
(density-independent-pixels), sp (scaled pixels based on preferred font size),
in (inches), mm (millimeters).
For example:
android:layout_width=”200dp”
android:layout_height=”50dp”
However, an app will eventually be run on many different devices that can
have a variety of screen sizes. Thus, we recommend using relative
positioning rather than absolute positioning. If we choose to use absolute
dimensions, we recommend the dp units because they are independent of
the screen density of the device that the app is installed on.
As shown in Figure 2.1, EditT ext and Button are subclasses of TextView ,
itself a subclass of View ; therefore, all these three classes inherit the
attributes of View , in addition to having their own. TABLE 2.4 shows some
XML attributes of View , along with the corresponding methods and their
descriptions. We can see the full list at
http://www.developer.android.com/reference/android/view/View.html.
TABLE 2.4 Selected XML attributes and methods of View
android:id setId( int ) Sets an id for the View; the View can then be retrieved
by code using its id
For a value to be the id of a View , that View must have been given an id.
using its id. The id must be a resource reference and is set using the @+
syntax as follows:
android:id="@+id/idValue"
TABLE 2.5 Useful XML attributes of RelativeLayout.LayoutParams
android:layout_alignLeft View’s left edge matches the value view’s left edge
android:layout_alignRight View’s right edge matches the value view’s right edge
android:layout_alignBottom View’s bottom edge matches the value view’s bottom edge
android:layout_alignTop View’s top edge matches the value view’s top edge
android:layout_alignParentLeft If true, view’s left edge matches its parent’s left edge
android:layout_alignParentRight If true, view’s right edge matches its parent’s right edge
android:layout_alignParentBottom If true, view’s bottom edge matches its parent’s bottom edge
android:layout_alignParentTop If true, view’s top edge matches its parent’s top edge
android:textSize void setTextSize( int unit, float size ) Sets the size of the text
android:hint void setHint( int resource ) Sets the hint text to display
We give all of them an id (lines 14, 22, 33, 43) so that we can refer to them
when we position them relative to each other. We use the first TextView ,
storing the bill label, as the anchor element at the top left of the screen. At
line 17, we give it a minimum width of 120 pixels; we choose that value so
that the text of all the labels that we will position below the bill label will easily
fit.
At line 25, we specify that the first EditText , where the user enters the bill, is
to the right of the bill label. At line 26, we specify that its bottom edge is on
the same horizontal line as the bottom edge of the bill label. At line 27, we
specify that its right edge lines up with its parent’s right edge, so that it
extends to the right edge of the screen.
At line 36, we specify that the label for the tip percentage is below the bill
label, whose id is label_bill . At lines 37 and 38, we specify that its left and
right edges are lined up with the bill label.
At line 46, we specify that the first EditText , where the user enters the bill, is
to the right of the bill label. At line 47, we specify that its bottom edge is on
the same horizontal line as the bottom edge of the bill label. At line 48, we
specify that its right edge lines up with the right edge of the EditText above
it, whose id is amount_bill .
We assign hint values to both EditTexts at lines 29 and 50; both hint values
are defined in the strings.xml file. TABLE 2.6 shows the android:textSize
and android:hint XML attributes of TextView , along with its corresponding
method and description. Since EditText is a subclass of TextView , EditText
inherits these two attributes. The default font size is small, so we set the font
size of the TextViews and EditTexts to 28. By specifying sp units, we insure
that the font size is screen-density independent. The size of the font has a
direct impact on the size of the TextViews since they wrap around their
contents. If we specify a large font size, then the components may not fit on
the screen of small devices. In this app, do not worry about that issue. We
will learn how to address that issue later in the book.
and run the app again. We would see that every character we enter is hidden
and shows as a small plain circle.
TABLE 2.7 Selected possible values for the android:inputType XML attribute
of TextView
FIGURE 2.3 Tip Calculator GUI, Version 0, shown in the Android Studio
environment
FIGURE 2.3 shows the current GUI in the Android Studio environment. We
can see the two TextViews and EditTexts including the two hints: there is not
enough space at the top of the screen to see Enter Bill properly. In Version
1, we fix that, add more GUI elements, and improve the look and feel.
In Version 1, we complete the GUI, adding two labels and two TextViews to
display the tip amount and the total amount, as well as one Button . In
Version 2, we will update the tip and total amount when the user clicks on the
Button . We also add colors, center the text in the EditTexts , and add
TABLE 2.8 shows more XML View attributes, along with the corresponding
methods and their descriptions. There may not be a one-to-one mapping of
each XML attribute to a method and vice versa. For example, a single
method, such as setPadding , is used to set the values of several attributes
( paddingBottom , paddingLeft , paddingRight , paddingTop ). The padding
attributes relate to extra spacing inside a GUI component, between the
border of the component and its content. If we want to add extra space
between GUI components (i.e., on the outside ), we can use the margin
attributes of the ViewGroup.MarginLayoutParams class, as shown in TABLE
2.9.
We can add some padding and margin to the first TextView of Example 2.3,
as shown in the following code sequence:
<TextView
android:id=”@+id/label_bill”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginTop=”70dp”
android:layout_marginLeft=”50dp”
android:padding=”10dp”
android:minWidth=”120dp”
android:textSize=”28sp”
android:text=”@string/label_bill”/>
android:paddingBottom setPadding( int, int, int, int Set the padding, in pixels, of the View’s
) bottom edge
android:paddingLeft setPadding( int, int, int, int Set the padding, in pixels, of the View’s left
) edge
android:paddingRight setPadding( int, int, int, int Set the padding, in pixels, of the View’s right
) edge
android:paddingTop setPadding( int, int, int, int Set the padding, in pixels, of the View’s top
) edge
TABLE 2.9 Selected XML attributes and methods of
ViewGroup.MarginLayoutParams
android:layout_marginBottom setMargins( int, int, int, Set extra space at the bottom of this
int ) View
android:layout_marginLeft setMargins( int, int, int, Set extra space at the left of this View
int )
android:layout_marginRight setMargins( int, int, int, Set extra space at the right of this View
int )
android:layout_marginTop setMargins( int, int, int, Set extra space at the top of this View
int )
We can also set more text attributes of a TextView , such as its color, style,
or alignment. TABLE 2.10 shows the android:textColor , android:textStyle,
and android:gravity XML attributes of TextView . The android:gravity
attribute relates to the alignment of the text within the TextView ; the default is
left-aligned.
TABLE 2.11 shows the possible formats for the value of the
android:textColor XML attribute of TextView (or any GUI component that
▸ Reference a resource
6 digits color value "#rrggbb" r, g, b are hex digits, representing the amount of
red, green, and blue in the color
To specify a yellow color (e.g., full red, full green, no blue) for the text, we
can directly give a value to the android:textColor attribute in four different
ways:
android:textColor=”#FFFF00”
android:textColor=”#FFFFFF00”
android:textColor=”#FF0”
android:textColor=”#FFF0”
If there are only three or four characters in the color string, then each
character is duplicated to obtain an equivalent six- or eight-character color
string. If there are six characters only, the first two characters specify the
amount of red, the next two characters the amount of green, and the last two
characters the amount of blue. If there are eight characters, the first two
characters specify the opacity of the color, and the last six characters
specify the various amounts of red, green, and blue as before. FF means
fully opaque and 00 means fully transparent. If the opacity is not specified (if
there are three or six characters), the default is fully opaque.
"@[+][package:]type:name"
The brackets, [], in the syntax expression mean that the field is optional.
In order to use this syntax, we externalize some colors in a file, the resource,
named colors.xml. The name of the file does not matter, but it is good
practice to choose a file name that is meaningful; the file should have the .xml
extension and must be located in the res/values/ directory of our project so
that the Android framework can find it. Although not using the .xml extension
will not prevent our app from running, we recommend that we use the .xml
extension for these files.
TABLE 2.12 shows the supported directories inside the res directory along
with their descriptions.
To specify a color that is defined by the string named lightGreen in a newly
created resource file named colors.xml, we write this code:
android:textColor=”@color/lightGreen"
In the colors.xml file, we included the following code (line 8 of EXAMPLE 2.5)
in order to define lightGreen:
<color name="lightGreen">#40F0</color>
color XML files defining a state list of colors (that can be used to specify different colors for
different states of a button, for example)
drawable Bitmap files or XML files defining drawable resources (place drawable resources for the
app here)
mipmap Bitmap files or XML files defining drawable resources (place the icon for the app here)
values XML files defining strings, integers, colors, and other simple values
Color is the resource type and lightGreen is its name. The name of the file,
If we delete the resources start and end tags, we will get an error message
that our XML document is not well formed. Any error in our XML documents
will abort the build when we try to compile or run our app.
We separate the input related elements, at the top, from the computed
related elements, at the bottom, by a red line (lines 63–72). The red line is a
View element (line 64) whose height is 5 pixels (line 68). We use the same
positioning attributes that we use earlier in order to position the five new
elements in relation to themselves and the existing four elements:
android:layout_below , android:layout_alignLeft , android:layout_alignRight ,
At lines 13–15, we add margins and padding to the bill label, our anchor
element.
We set the background of the four TextViews on the left to light gray at lines
18, 46, 84, 109. We set the background of the computed tip and total to light
green at lines 95 and 120. We set the text color of the two EditText
elements to dark blue at lines 32 and 59. We left the default left-alignment
for the four TextViews on the left. We center-align the two EditTexts and two
TextViews on the right (lines 31, 58, 96, 121). As FIGURE 2.5 shows,
At line 129, we center the button horizontally. At this point, the button does
not respond to a user click; we will implement that feature in Version 3.
We can see the various component sizes, colors, font sizes, and font styles
in FIGURE 2.6, which shows the GUI in the Android Studio environment.
FIGURE 2.5 The Android Studio environment, showing suggested
values for the android:gravity attribute
In Version 1, many GUI components have the same attributes with the same
values. For example, all the nine elements in the activity_main.xml file have a
wrap_content value for both the android:layout_width and
contents.
We can define styles in a file named styles.xml in one of the res/values
directories. In it, we can define several styles, each with a list of XML
attributes and values; each style has a name that we can use to reference it.
</style>
A style can inherit from another style. Inheritance is defined using the parent
attribute, and is optional. The parent style can be a native Android style, or it
can be a user-defined style.
By defining styles this way, we can build an inheritance hierarchy; not only
several GUI elements can use the same style, but a style can reuse another
style’s attributes via inheritance. A style can also override the value of an
attribute that it inherits. We define six styles as shown in FIGURE 2.7. We
use InputStyle for the two EditTexts : it inherits from CenteredTextStyle ,
which itself inherits from TextStyle , which inherits from TextAppearance , an
existing style from the Android Standard Development Kit (Android SDK).
Thus, the text inside the EditTexts will be centered, have size 28, and be
dark blue. Note that the default text color inherited from Text Appearance is
black but is overridden. The two EditTexts will be sized based on their
contents.
EXAMPLE 2.8 shows the styles.xml file. TextStyle is defined at lines 11–16;
it inherits from the Android native style TextAppearance using the syntax
parent=”@android:style/nameOfParentStyle” . Note that the contents for each
item element, for example wrap_content or 28sp , are not inside double
quotes.
At lines 18–20, LabelStyle inherits from our own TextStyle using the syntax
parent="nameOfParentStyle” . Thus, it inherits the four attributes and their
the light gray color defined in colors.xml for the android:background attribute
at line 19. Thus, any element styled with LabelStyle will have a light gray
background, 10 pixels of padding around its text, which will have size 28. The
size of that element will be set based on the text inside it.
EXAMPLE 2.8 The styles.xml file
Not only does an XML style file provide a centralized location to define styles,
but it also makes our code better organized and easier to maintain and
modify. Often, we will want to have the same styling themes for GUI
components of the same type. If all our TextView components are styled red
and we decide to change them to purple, we only have one line of code to
change at a single location. We can also reuse styles in many apps.
Furthermore, the expertise needed to create and edit a style file is different
from the expertise needed to write Java code. If the app is complex and
developed by a group of developers, it makes it easier to separate the work
and distribute it among the team members based on their respective
expertise.
style=”@style/nameOfStyle”
At lines 11, 29, 58, and 74, we style the four TextView elements on the left
side of the screen with LabelStyle . At lines 19 and 38, we style the two
EditText elements with InputStyle . At lines 67 and 83, we style the two
we will see that change taking place as we run the app again.
A style is typically applied to one or several screens. Often, we will want the
Views in a screen to have the same look and feel. Or we might want all the
screens in the app to have the same look and feel. Using a style, we can give
an activity, or give the whole app, a theme.
EXAMPLE 2.9 The activity_main.xml file, Tip Calculator app, Version 2
android:theme=”@style/nameOfStyle”
The AndroidManifest.xml skeleton already includes the preceding syntax
using the style AppTheme . AppTheme is already declared in the styles.xml
skeleton, and we can edit AppTheme inside styles.xml. We can also use a
style from styles.xml, and change line 10 of Example 2.2 as follows:
android:theme=”@style/LabelStyle”
android:theme=”@style/nameOfStyle”
For example, we could insert a line between lines 12 and 13 of Example 2.2
as follows:
android:theme=”@style/OutputStyle”
There is only one activity in our current app, but in a more complex app,
there could be many activities, and therefore there could be many activity
elements in the AndroidManifest.xml file. Each one can be themed
separately.
Now that we have a Model and a View, we can edit the Controller so that our
app calculates and displays the tip and the total when the user enters a new
amount for either the bill or the tip percentage rate and clicks on the
Calculate button.
android:onClick=”methodName”
The method should be in the Activity class that is controlling the View, in
this case MainActivity . The Button class inherits from View , so we can use
the android:onClick XML attribute within the Button element in our
activity_main.xml file. If we want a method named calculate to execute
when the user clicks on the Button , we write:
android:onClick=”calculate”
Inside the method, the View parameter v is a reference to the View that
originated the event.
Thus, inside the MainActivity class, we need to code the calculate method,
and it has the following header:
Inside that method, we need to access the EditText elements to retrieve the
data input by the user and some of the TextView elements to update them
accordingly.
If a View had been given an id, we can get a reference to that View using the
findViewById method of the Activity class (inherited by the
method call:
findViewById(R.id.idValue)
Now that the activity_main.xml file is defined, we can code the MainActivity
class, shown in EXAMPLE 2.11, and its new method calculate .
The calculate method needs to retrieve the amount entered in the two
EditText components, calculate the corresponding tip and total amounts, and
Class Method
Activity View findViewById( int id )Finds and returns the View that is identified by the id attribute
from the XML inflated in the onCreate method (for example, activity_main.xml)
TextView CharSequence getText( )Returns the text displayed in the TextView widget
TextView void setText( CharSequence text )Sets the text to be displayed in the TextView widget
At lines 28–29, we retrieve the data input by the user in the two EditTexts
and assign the two values to the billString and tipString Strings ; we use
the getText method (TABLE 2.13) of the TextView class, inherited by the
EditText class. GetText returns a CharSequence , so we make an extra
good practice to use try and catch blocks in order to prevent a runtime
exception from being thrown in case the data retrieved is not a valid number.
At lines 39–41, we update the Model: we set the bill and tip of tipCalc by
calling setBill and setTip . At lines 42–44, we ask the Model to call
tipAmount and totalAmount to compute the tip and total amounts. At lines
45–47, we use the results to update the View: we place the formatted tip
and total amounts in the TextViews using the setText method from Table
2.13. In this way, the Controller, the MainActivity class, gathers input from
the View to update the Model, and retrieves updated data from the Model to
update the View.
EXAMPLE 2.11 The MainActivity class of the Tip Calculator app,
Version 3
We should never get in the catch block (lines 48–50); indeed, the
numberDecimal and number input types of the two EditText specified in
activity_main.xml guarantee that the values of the bill and the tip percentage
will be a valid floating point number and a valid integer. The numberDecimal
value for android:inputType only allows the user to enter digits and a
maximum of one dot. The number value for android:inputType only allows the
user to enter digits. However, it is good practice to trap potential errors and
validate data even if we are reasonably sure that they will not happen. This
practice is called defensive programming. Inside the catch block (line 49),
we could pop up an alert view to inform the user to enter correct data.
method must be public , void , and take one and only parameter, a
View ; otherwise, we will have a runtime exception when the user
v = android.support.v7.widget.AppCompatButton{fe17b58
VFED..C.....P.... 280,1063-799,1235}
This shows that the View argument is indeed a Button (the only one, clicked
by the user; AppCompatButton is a class that extends Button and supports
features of older versions); fe17b58 is a hexadecimal number representing
its memory address.
FIGURE 2.8 shows our app running in the emulator after the user enters
154.50 in the bill EditText and 18 in the tip percentage EditText and clicks
on the Calculate button. By default, when we have at least one EditText on
the screen, the soft keyboard is open.
One may think that we do not really need a complete model, the
TipCalculator class, for this very simple app. However, even for a simple
Our app now has some basic functionality, but we should update the tip and
total amount any time the user changes the data (i.e., any time the user is
typing on the keyboard) even if the user does not click on the Calculate
button. In fact, we do not even need the Calculate button. In order to
implement that, we have to edit the View (delete the Button element in the
activity_main.xml file) and change the Controller (process any change in the
text of the two EditTexts ); there is no change needed in the Model.
EXAMPLE 2.12 shows the end of the updated activity_main.xml file. It no
longer includes the Button element. Since we no longer use the Button , we
can also delete the button_calculate String in strings.xml, the button style in
styles.xml, and the dark green color element in colors.xml.
The static OnKeyListener interface, nested inside the View class, includes a
method, onKey , that is called when a key event is dispatched to a View .
However, key presses in software keyboards will generally not trigger a call
to this method. We want our app to be as universal as possible and work
equally well with mobile devices that have either a hardware keyboard or a
software keyboard. For that reason, we do not use the OnKeyListener
interface in this app.
The TextWatcher interface, from the android.text package, provides three
methods that are called when the text inside a GUI component (a TextView
or an object of a subclass of TextView , such as EditText ) changes,
assuming that a TextWatcher object is registered on the TextView . TABLE
2.14 shows these methods. As in any interface, these three methods are
abstract .
▸ Register that object on the TextView (in this app the two EditTexts )
void beforeTextChanged ( CharSequence Within cs, the count characters beginning at start are
cs, int start, int count, int after ) about to be replaced with after characters
void onTextChanged ( CharSequence cs, Within cs, the count characters beginning at start have
int start, int before, int count ) just replaced before characters
EXAMPLE 2.13 shows the complete code for the MainActivity class. We
made one important change to the overall class design. We have added
instance variables for the two EditTexts (lines 14–15). This is not necessary
but it is convenient to have direct references to GUI components if we need
to access them at various places (when we register the handler and when
we process the text changes). This is easier than to use the findViewById
method every time we want to access them. They are instantiated at lines
23–24.
which updates the Model, and then updates the View accordingly.
COMMON ERROR: Be sure that all the methods of the handler class
are coded. If one or more abstract methods of the interface is not
implemented, there will be a compiler error.
EXAMPLE 2.13 The MainActivity class of the Tip Calculator app,
Version 4
As we run the app, the tip amount is updated after any key is pressed when
either of the two EditText components is in focus (except when one of the
EditTexts is empty; in that case, we execute inside the catch block and the
components are not updated). FIGURE 2.9 shows the app running after the
user enters 125.59 for the bill value and starts entering the tip percentage.
As soon as the user types 2, the tip and the total amounts are updated.
Instead of coding a separate class for the handler, then instantiate an object
of that class and register that object on one or more GUI components, as we
did in Example 2.13 (lines 57–69, line 26, and lines 27–28 respectively),
some developers like to use anonymous objects instead and write code as
follows:
tipEditText.addTextChangedListener( new TextWatcher( ) {
} );
While that code looks more compact at first, we feel that it is less readable
and less reusable, particularly if we need to have several handler objects or
several GUI components that the handlers need to be registered on.
If we look at the overall code for this app, we can start seeing the benefits of
the Model-View-Controller architecture and the resulting organization of the
code as follows:
▸ View: activity_main.xml
As the apps that we develop get more complex, the benefits of the Model-
View-Controller architecture will be more obvious.
Chapter Summary
A sound design for an app involves using the Model-View-Controller
architecture.
The Model is a set of classes that encapsulate the functionality of the
app.
The View is the Graphical User Interface (GUI).
The Controller is a middleman between the Model and the View: it takes
some inputs from the View, updates the Model, asks the Model to
perform some calculations, and updates the View accordingly.
The Android framework provides a variety of components for building a
GUI such as labels, buttons, text fields, checkboxes, radio buttons, and
lists.
We can use the activity_main.xml file to set up our GUI, including setting
up some events.
We can define styles and apply them to views.
We can apply a style, as a theme, to the whole app or to an activity.
A RelativeLayout enables us to position GUI components in relation to
the position of other GUI components.
We can assign an id to a View in order to retrieve it later using its id with
the findViewById method from the Activity class. We can also refer to
a component using its id in a layout XML file, which is useful if we use a
RelativeLayout .
Event-driven programming consists of using interactive components and
event handlers to respond to events generated by user interaction with
these components.
If we want to handle the action of clicking on a View , we can assign the
name of a method to the android:onClick attribute of that View in the
XML layout file. We should then code that method as a void method
accepting a View parameter in the corresponding Activity class.
Generally, to respond to a user’s interaction with a component, we need
to write a handler class, instantiate an object of that class, and register
that object on one or more components.
Event handler classes implement a listener interface and need to
override all their abstract method(s).
Event handlers can be implemented as private inner classes; this
enables the methods of that class to directly access the instance
variables of its outer class. Event handlers can also be implemented with
anonymous objects or as separate public classes.
To listen to text changing inside a TextView , we can implement the
TextWatcher interface from the android.text package.
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
/>
11. Add a line of XML so that the text displayed is the value of a string
named book (defined in strings.xml).
12. Add a line of XML so that the background color of the TextView is red.
13. Add a line of XML so that the text displayed is centered inside the
TextView element.
14. Add a line of XML so that the TextView is centered on a vertical axis
within the View containing that TextView element.
15. Add a line of XML so that TextView uses a style named myStyle defined
in styles.xml (using the XML element style).
16. Inside colors.xml, write the code to define a color named myColor that is
green.
17. Inside styles.xml, define a style named myStyle so that paddingBottom
is 10dp, and the background color is red.
18. Inside MainActivity.java, write the code to retrieve and assign to a
TextView object reference a TextView element defined in
activity_main.xml and whose id is look.
19. We have coded a class named MyWatcher extending the TextWatcher
interface and declared an object of that class as follows:
20. Modify this chapter’s app. Allow the user to input the number of guests
and calculate the tip and total per guest.
21. Write an app that performs an addition. It has three labels and one
button. The first two labels are filled with two randomly generated
integers between 1 and 10. When the user clicks on the button, the two
integers are added and the result is displayed in the third label. The
three labels should be styled using the same style. The button should
have a red background and a blue text. All four components should be
centered vertically. Include a Model.
22. Write an app that performs an addition. It has two text fields, one label
and one button. The user can enter two integers in the text fields. When
the user clicks on the button, the two integers are added and the result
is displayed in the label. The two text fields should be styled using the
same style. The label and the button should also be styled with the same
style, but different from the other style. Each style should have a
minimum of four attributes. All four components should be centered
vertically. Include a Model.
23. Write an app that simulates a traffic light. It displays a label and a
button. The label represents the current color of a traffic light and can be
red, green, or yellow. When the user clicks on the button, the label
cycles to the next color and simulates running the traffic light. You should
look at the documentation of the View class to figure out how to change
the color of a View programmatically. The two components should be
centered vertically. Include a Model.
24. Write an app that lets the user create a color using the RGB color
model. It displays three text fields and one label. We expect the user to
enter integers between 0 and 255 included in the three text fields; if a
value is negative, it should be converted to 0; if it is greater than 255, it
should be converted to 255. The value of the text fields represents the
amount of red, green, and blue of the background color in the label.
When the user modifies the number in any text field, the background
color of the label should be updated. You should look at the
documentation of the View class to figure out how to change the color of
a View programmatically. The three text fields should have the same
style with a minimum of four attributes. All four components should be
centered vertically. Include a Model.
25. Write an app that checks if two passwords match. It has two text fields,
one label and one button. The user can enter two passwords in the text
fields, which should be styled as such. When the user clicks on the
button, the two passwords are compared for equality and either THANK
YOU (if the passwords match) or PASSWORDS MUST MATCH (if the
passwords do not match) is displayed in the label. The text fields and
the label should have their own separate style, each with a minimum of
four attributes. All four components should be centered vertically. Include
a Model.
26. Write an app that evaluates if an email is valid. The app includes an edit
text field, a label and a button. The user can enter his or her email in the
edit text field. When the user clicks on the button, the app checks if the
email entered contains the @ character. If it does, the label displays
VALID, otherwise it displays INVALID. The text field and the label should
have their own separate style, each with a minimum of four attributes.
The text field and the label should be on the same horizontal axis; the
button should be below them and centered. Include a Model.
27. Write an app that evaluates the strength of a password. It has a text
field and a label; the user can enter his or her password. The label
displays, as the user types, WEAK or STRONG. To keep things simple,
we define a weak password as a string of eight characters or fewer; a
strong password is defined as a string of nine characters or more. The
text field and the label should have their own separate style, each with a
minimum of four attributes. The two components should be centered
vertically. Include a Model.
28. Write an app that asks the user to enter a password with a minimum
strength level. It has a text field and a label. As the user enters his or
her password, the label displays VALID or INVALID. For the purpose of
this app, we define a valid password as a string of eight characters or
more with at least one uppercase letter, one lowercase letter, and one
digit. The text field and the label should have their own separate style,
each with a minimum of four attributes. The two components should be
centered vertically. The label displaying VALID or INVALID should be
updated as the user types. Include a Model.
29. Write an app that keeps track of the calories for a meal. Present the
user with a choice of several foods; assign a number of calories to each
food choice. After the user enters the number of servings for each food,
the total number of calories is updated. The text fields and the labels
should have their own separate style, each with a minimum of four
attributes. Include a Model.
30. Write an app that keeps track of the calories burned during a workout
session. Present the user with a choice of workouts; assign a number of
calories to each workout. After the user enters a number for each
workout, the total number of calories burned is updated. The text fields
and the labels should have their own separate style, each with a
minimum of four attributes. Include a Model.
CHAPTER THREE: Coding the GUI
Programmatically, Layout
Managers
Chapter Summary
Introduction
Sometimes the number of buttons (or other components) we want to display
is dynamic—it could be different every time we run the app, such as in a
content app. A content app is an app that gets its data from an external
source, for example a website such as Facebook or Twitter, displays the
data, and enables the user to interact with it. We do not know in advance
how much data will be retrieved. It is also possible that an app lends itself
well to a particular data structure, like a 3 × 3 two-dimensional array in a tic-
tac-toe game. We can implement the GUI part of a tic-tac-toe game with
nine buttons and we could define the nine buttons in the activity_main.xml file,
but it is easier to manipulate a 3 × 3 two-dimensional array of buttons by
code.
In this chapter, we build a Tic-Tac-Toe app, and create the GUI and handle
the events programmatically. We leave the automatically generated
activity_main.xml file as it is, and do everything by code.
Although we define the View by code rather than in the activity_main.xml file,
we still use the Model-View-Controller architecture. In order to define the
View, we need to understand what will be in it. That depends on how the
TicTacToe game is defined and played; that part is encapsulated in the
Model. Thus, we define the Model first.
EXAMPLE 3.1 shows the class code. The TicTacToe constructor (lines 8–11)
calls reset-Game at line 10 after instantiating the two-dimensional array game
as a 3 × 3 array. Having a separate resetGame method enables us to reuse
the same object when playing back-to-back games. ResetGame (lines 80–85)
clears the board by setting all the cells in game to 0; it also sets turn to 1 so
that player 1 starts.
The method play (lines 13–26) first checks if the play is legal (lines 15–16).
If it is not, it returns 0 (line 25). If it is, it updates game at line 17 and turn at
lines 18–21. It then returns the value of currentTurn (line 22), which holds the
old value of turn (line 14).
The method whoWon (lines 28–39) checks if a player has won the game and
returns the player number if that is true; otherwise, it returns 0. We break
down that logic using three protected methods: checkRows (lines 41–47),
checkColumns (lines 49–55), and checkDiagonals (lines 57–65). There is no
reason for a client of that class to access these methods so we declare them
protected .
Instance Variables
private int turn 1 = player 1’s turn to play; 2 = player 2’s turn to play
Constructor
TicTacToe( ) Constructs a TicTacToe object; clears the board and prepares to play by
setting turn to 1
Public Methods
int play( int row, int If the play is legal and the board cell is not taken, plays and returns the old
column ) value of turn
boolean canStillPlay( ) Returns true if there is still at least one cell not taken on the board;
otherwise, returns false
The method isGameOver (lines 76–78) returns true if the game is over (i.e.,
somebody has won or no one can play); otherwise, it returns false .
In Version 0 of our TicTacToe app, we use the empty activity template and
only setup the GUI. We use a 3 × 3 two-dimensional array of Buttons , in
order to mirror the 3 × 3 two-dimensional array game in our Model, the
TicTacToe class. In order to keep things simple, we first place the View
inside the Activity class, so the View and the Controller are in the same
class. Later in the chapter, we separate the View from the Controller and
place them in two different classes.
TABLE 3.2 shows a few layout classes along with their descriptions.
FIGURE 3.1 shows the hierarchy of these classes.
As we know, there are many types of Android phones and tablets, and they
all can have different screen dimensions. Thus, it is bad practice to hardcode
widget dimensions and coordinates because a user interface could look good
on some Android devices but poorly on others. In order to keep this example
simple, we will assume that the user will only play in vertical orientation; thus,
we assume that the width of the device is smaller than its height.
Class Description
LinearLayout A layout that arranges its children in a single direction (horizontally or vertically).
FrameLayout A layout designed to block out an area of the screen to display a single item.
RelativeLayout A layout where the positions of the GUI components can be described in relation to
each other or to their parent.
TableRow A layout that arranges its children horizontally. A TableRow should always be used
as a child of a TableLayout.
We can dynamically retrieve, by code, the width and height of the screen of
the device that the app is currently running on. Using that information, we can
then size the GUI components so that they fit within the device’s screen no
matter what the brand and model of the device are. In setting up the View,
we perform the following steps:
▸ Define and instantiate a GridLayout with three rows and three columns
▸ Set the GridLayout as the layout manager of the view managed by this
activity
At lines 20–23, we retrieve the width of the screen. At line 21, we declare a
Point variable, size . The Point class, imported at line 3, has two public
current display and can provide information about its size and its density.
With it, we call the getSize method of the Display class; getSize is a void
method but takes a Point object reference as a parameter. When that
method executes, it modifies the Point parameter object and assigns to it
the width and height of the display as its x and y instance variables. At line
23, we retrieve the width of the screen ( size.x ) and assign one third of it to
the variable w . We later use the value of w to dimension the Buttons so that
we can place three of them across the screen. TABLE 3.3 summarizes the
resources involved in retrieving the size of the current device’s screen.
The Buttons are created and added to the layout at lines 30–37 by looping
through the array buttons . We first instantiate buttons at line 31 as a 3 × 3
two-dimensional array of Buttons . The double loop at lines 32–37
instantiates each Button and adds it to gridLayout . The Button constructor
called at line 34 takes a Context parameter representing the app
environment. Again, we pass this , representing the current Context , as the
argument. It is important to instantiate a GUI component before either calling
a method with it or adding it to a View . Otherwise, we will have a
NullPointerException at runtime and the app will stop. At line 35, we add the
current button to the layout using the addView method and specify its width
and height as equal to w . The GridLayout class inherits addView from
ViewGroup .
TABLE 3.4 GridLayout constructor and methods
Constructor
GridLayout( Context Constructs a GridLayout within the app environment defined by context.
context )
Public Methods
setRowCount( int rows ) Sets the number of rows in the grid to rows.
addView( View child, int Method inherited from ViewGroup; adds child to this ViewGroup using
w, int h ) width w and height h .
Finally, at line 40, we set the contents of the view managed by this activity to
gridLayout by calling the setContentView method from the Activity class.
Remember that GridLayout inherits from ViewGroup , which inherits from View ;
therefore, gridLayout “is a” View .
The buildGuiByCode method is called at line 16, inside the onCreate method,
which is called automatically when the app starts.
FIGURE 3.2 Inheritance hierarchy showing the Context and Activity
classes
In order to keep things simple, we only allow the app to work in vertical
orientation. Inside the AndroidManifest.xml file, we assign the value portrait
to the android:screenOrientation attribute of the activity tag.
At this point, we have coded two important components of our app: the
Model (the TicTacToe class), and the View (the buildGuiByCode method of
the MainActivity class). Next, we build the Controller.
FIGURE 3.3 TicTacToe app, Version 0, running inside the emulator
The type of event that we want to capture determines the listener interface
that we implement. View.OnClickListener is the listener interface that we
should implement in order to capture and handle a click event on a View .
Since it is defined inside the View class, importing the View class (line 7)
automatically imports View.OnClickListener . TABLE 3.5 lists the only
abstract method of that interface, onClick . Our event handler class,
line 55, we add an output statement that gives feedback on the View
parameter of the onClick method; we expect that it is a Button . At lines 56–
59, we loop through the array buttons in order to identify the row and column
values of the button that was clicked. We then call the update method,
passing these row and column values at line 59.
public abstract void onClick( Called when a View has been clicked; the parameter v is a
View v ) reference to that View.
The update method is coded at lines 48–51, but does not do much in this
version. It outputs some feedback on the row and column of the button that
was clicked (for debugging purposes), and writes an X inside it; this will
change in Version 2.
FIGURE 3.4 shows the app after the user clicked successively on three
buttons. FIGURE 3.5 shows a partial output of Logcat, resulting from the
output statements at lines 55 and 49. It reveals that the user clicked on the
button in the top right corner first, then on the left button in the middle row,
and then on the middle button in the bottom row. Figure 3.5 also shows the
three different memory addresses of the three View ( Buttons in this case)
arguments of onClick .
MainActivity class. Note that in this app, the View and the Controller are in
the same class. Later in the chapter, we put them in separate classes to
make the code more reusable. The next step is to finish coding the Controller
in order to enable game play.
We are assuming that two users will be playing on the same device against
each other. Enabling game play does not just mean placing an X or an O on
the grid of buttons at each turn. It also means enforcing the rules, such as
not allowing someone to play twice at the same position on the grid, checking
if one player won, indicating if the game is over. Our Model, the TicTacToe
class, provides that functionality. In order to enable play, we add a TicTacToe
object as an instance variable of our Activity class, and we call the
methods of the TicTacToe class as and when needed. Play is happening
inside the update method so we have to modify it. We also need to check if
the game is over and, in that case, disable all the buttons.
EXAMPLE 3.4 shows the updated MainActivity class. At line 11, we declare
a TicTacToe object, tttGame ; we instantiate it at line 17.
Inside the update method (lines 48–56), we first call the play method of the
TicTacToe class with tttGame at line 49. We know that it returns the player
number (1 or 2) if the play is legal, 0 if the play is not legal. If the play is legal
and the player is player 1, we write X inside the button (line 51). If the play is
legal and the player is player 2, we write O inside the button (line 53). If the
play is not legal, we do not do anything.
When the game is over, we want to disable all the buttons. At lines 58–62,
the enableButtons method enables all the buttons if its parameter, enabled , is
true . If it is false , it disables all the buttons. We can enable or disable a
Button by calling the setEnabled method of the Button class using the
argument true to enable the Button , false to disable it. We test if the game
is over at line 54 and disable all the buttons if it is (line 55).
EXAMPLE 3.4 The MainActivity class, TicTacToe app, Version 2
In Version 3, we will add a status label giving some feedback on the current
state of the game.
FIGURE 3.6 shows the app running in the emulator. Player 1 just won and
the buttons are disabled.
A lot of the Android GUI classes are inner classes or inner interfaces. For
example, OnClick-Listener is an inner interface of the View class. The layout
manager classes have inner classes that are used to specify the position of a
View child inside its parent layout. TABLE 3.6 shows some of them. The dot
notation used in the class name indicates that a class is an inner class of
another class. For example, GridLayout.LayoutParams means that
LayoutParams is an inner class of GridLayout .
Class Description
EXAMPLE 3.6 shows a very simple example showing how we can use class
B inside another class, Test . To reference an inner class, we use the
following syntax:
OuterClassName.InnerClassName
number: 20
And if B was not declared static , we could not use the syntax A.B .
A a = new A( );
A.C c = a.new C( );
The feedback message depends on the state of the game. It makes sense
to let the TicTacToe class, our Model, define the message. EXAMPLE 3.7
shows the result method (lines 87–94) of the TicTacToe class. It returns a
String that reflects the state of the game.
The GridLayout used in Version 2 suits the layout of TicTacToe or any View
that requires a two-dimensional grid of GUI components very well.
Sometimes we want to use a grid and we want to have the flexibility of
combining several cells of the grid and place a single component there. The
GridLayout class enables us to span widgets over several rows or columns,
We can use the static spec methods of the GridLayout.Spec class to create
and define a GridLayout.Spec object, which defines a span. Once we have
defined two GridLayout.Spec objects, we can use them to define a
GridLayout.LayoutParams object. TABLE 3.7 shows these methods.
Returns a GridLayout.Spec object where start is the starting index and size is the size.
public static GridLayout.Spec spec( int start, int size, GridLayout.Alignment alignment )
Returns a GridLayout.Spec object where start is the starting index and size is the size.
FIGURE 3.7 shows a 4 × 6 grid of cells. The shaded area can be defined as
follows:
// horizontal span
import android.widget.GridLayout.Spec;
in addition to:
import android.widget.GridLayout;
FIGURE 3.8 shows a 4 × 3 grid of cells like the one in our app. The shaded
area can be defined as follows:
GridLayout.LayoutParams lp
We use code similar to the one above in EXAMPLE 3.8 at lines 48–53 to
define the layout parameters of status . At line 54, we set them by calling
setLayoutParams . At line 34, we set the number of rows of our GridLayout to
four so that we can place status in the fourth row. At lines 57–58, we set
the height and width of status so that it fills completely the area of the grid
defined by its layout parameters. Note that it is possible to set the height and
width of a component to be different from its layout parameters. We can then
use the spec method with three parameters to set the alignment of the
component with respect to its defined layout parameters. Table 3.7 shows
some possible values for the third parameter of the spec method.
At lines 59–62, we center the text in status , set its background color to
green, set its text font size, and set its text content based on the state of
tttGame , respectively.
We add code to the update method at lines 77 and 79 to change the color
and text of status when the game is over.
EXAMPLE 3.8 The MainActivity class, TicTacToe app, Version 3
FIGURE 3.9 TicTacToe app, Version 3, running inside the emulator
FIGURE 3.9 shows the app running inside the emulator, including the status
of the game at the bottom of the screen.
In Version 4, we enable the player to play another game after the current one
is over. When the game is over, we want a dialog box asking the user if he or
she wants to play again to pop up. If the answer is yes, he or she can play
again. If the answer is no, we exit the activity (in this case the app since
there is only one activity).
The AlertDialog.Builder class, part of the android.app package, provides
the functionality of a pop-up dialog box. It offers several choices to the user
and captures the user’s answer. A dialog box of type AlertDialog.Builder
can contain up to three buttons: negative, neutral, and positive buttons. In this
app, we only use two of them: the positive and negative buttons. Typically,
these two buttons correspond to yes or no answers from the user, although
that is not required. TABLE 3.8 shows the AlertDialog.Builder constructor
and other methods of that class.
TABLE 3.8 Selected constructor and methods of the AlertDialog.Builder
class
Sets the message to display to message; returns this AlertDialog.Builder object, which
allows chaining if desired.
Sets the title of the alert box to title; returns this AlertDialog.Builder object, which allows
method call chaining if desired.
Sets listener to be invoked when the user clicks on the positive button; sets the text of
the positive button to text.
Sets listener to be invoked when the user clicks on the negative button; sets the text of
the negative button to text.
Sets listener to be invoked when the user clicks on the neutral button; sets the text of the
neutral button to text.
EXAMPLE 3.9 shows the updated MainActivity class. Inside the method
showNewGame-Dialog (lines 98–106), we declare and instantiate alert , an
AlertDialog.Builder object at line 99, passing this , the current MainActivity
does not show. However, showing a dialog box without buttons enabling
interaction with the user results in a dialog box with a title and a message
that cannot be closed. In this app, we want to ask the user if he or she wants
to play another game, so we include only the positive and negative buttons.
the Buttons and the TextView to their original state. The private class
PlayDialog (lines 117–128) implements DialogInterface.OnClickListener .
EXAMPLE 3.9 The MainActivity class, TicTacToe app, Version 4
At line 119, we test if the value of id is –1. If it is, we reset the instance
variables of tttGame to their starting values by calling resetGame with tttGame
(line 120), we enable the buttons at line 121, clear them of any text at line
122, and update the background color and text in status at lines 123–124.
The enableButtons and resetButtons methods, coded at lines 86–90 and 92–
96, enable or disable the nine buttons and reset their text content to the
empty String , respectively. If the value of id is –2, we exit the activity by
calling the finish method at line 126. Note that the expression this.finish()
would be incorrect, because this would refer to the current PlayDialog object
since we are inside that class. Because we want to call the finish method
with the current object of the MainActivity class, we use MainActivity.this
to access it.
FIGURE 3.10 shows the app at the end of the game with the status
reflecting that player 1 won, and asking the user to play again.
In Version 5, we split the View and the Controller. In this way, we make the
View reusable. The Controller is the middleman between the View and the
Model, so we keep the View independent from the Model.
In the View, in addition to the code creating the View, we also provide
methods to:
This is similar to the Model class, which provides methods to retrieve its
state and update it.
In the Controller, in addition to an instance variable from the Model, we add
an instance variable from the View. With it, we can call the various methods
of the View to update it and get user input from it.
The array of buttons and the TextView status are now inside the View.
Updating the View means updating the buttons and the TextView status. To
that end, we provide the following methods:
▸ A method to reset the text of all the buttons to the empty String (we
need it when we start a new game)
▸ A method to enable or disable all the buttons (we also need it when we
start a new game)
FIGURE 3.10 TicTacToe app, Version 4, running inside the emulator,
showing the alert dialog box at the end of a game
User input for this View is clicking on one of the buttons. Typically, event
handling is performed in the Controller. Thus, we provide a method to check
if a particular button was clicked.
EXAMPLE 3.10 shows the ButtonGridAndTextView class, the View for Version
5 of this app. In Version 4, our View was a GridLayout , thus, the
ButtonGridAndTextView class extends GridLayout (line 10), and therefore, it
Because we want to keep the View independent from the model, we do not
use the SIDE constant of the TicTacToe class to determine the number of
rows and columns in buttons . Instead, the side instance variable stores that
value. The constructor includes a parameter, newSide , that is assigned to
side (lines 15–16 and 18). When we create the View from the Controller, we
have access to the Model, thus, we will pass the SIDE constant of the
TicTacToe class so that it is assigned to side . The ButtonGridAndTextView
widgets of the View (the Buttons and the TextView ). Since the Activity
class inherits from Context , an Activity “is a” Context . Thus, when we
create the ButtonGridAndTextView from the Controller, we can pass this for
the Context parameter. We pass that Context parameter to the Button and
TextView constructors at lines 27 and 35. The int parameter represents the
width of the View. By having the width as a parameter, we let the Activity
client determine the dimensions of the View. We assign the newSide
parameter to side at line 18. Finally, the View.OnClickListener parameter
enables us to set up event handling. We want to handle events in the
Controller but the Buttons are in the View, so event handling needs to be set
up in the View. We do that at line 29. The constructor code can be made
more robust by testing if newSide and width are positive. This is left as an
exercise.
At lines 30 and 49, we add each Button and the TextView to this
ButtonGridAndTextView .
instantiated at line 24. Inside onClick (lines 40–58), we first identify which
button was clicked at line 43, and call setButtonText to update the View at
lines 46 and 48 depending on whose turn it is to play. If the game is over (line
49), we update the View accordingly by calling the setStatusBackgroundColor
and setStatusText methods at lines 50 and 52. We also disable the buttons
at line 51.
EXAMPLE 3.11 The MainActivity class, TicTacToe app, Version 5
By separating the View from the Controller, we make the View reusable.
Chapter Summary
To create a GUI, we can either use XML or do it programmatically. For
some apps, the number of widgets is dynamic and we have to do it
programmatically. For some other apps, although we could do it using
XML, it may be more convenient to do it programmatically.
The Android framework provides layout managers to help us organize a
View .
13. Assign the width of the current screen to a variable name width.
14. This code creates a GridLayout within the current context and sets its
number of rows to four and its number of columns to two.
17. This code adds a Button object named b, specifying its width and height
as 200 pixels each, to an already created GridLayout object named gl.
20. This code sets up an alert view for the current activity. It sets its title to
HELLO and its message to HAVE FUN
AlertDialog.Builder alert = new AlertDialog.Builder( this );
alert.setPositiveButton( ”YES”, pd );
alert.setNegativeButton( ”NO”, pd );
21. This code checks if the button that was clicked is a button named b. If it
is, it outputs to Logcat YES, otherwise, it outputs to Logcat NO.
}
}
Write an App
22. Write an app that displays one label and one button. Every time the user
clicks on the button, the label toggles between being visible and not
being visible. Do not use XML.
23. Write an app that has three labels and one button. The three labels
represent a traffic light. When the app starts, only the label with the red
background shows. When the user clicks on the button, only the label
with the yellow background shows. When the user clicks again on the
button, only the label with the green background shows. When the user
clicks again on the button, only the label with the red background shows
. . . and the cycle continues. Do not use XML.
24. Write an app that displays one label and one button. The label
represents the current color of a traffic light and can be red, yellow, or
green. When the user clicks on the button, the label cycles to the next
color and simulates running the traffic light. Do not use XML.
25. Write an app that displays one label and one button. Every time the user
clicks on the button, the label moves down by 10 pixels. When the label
reaches the bottom of the screen, it no longer moves when the user
clicks on the button. Do not use XML.
26. Write an app that displays two labels and one button. The first label
should display a randomly generated integer between 50 and 100. When
the user clicks on the button, the second label moves to a position
whose y-coordinate is the integer displayed in the first label. Do not use
XML.
27. Write an app that has one text field, one label, and one button. The user
can enter his or her email in the text field. When the user clicks on the
button, the app checks if the email entered contains the @ character
and a dot somewhere after the @ character. If it does, the label
displays VALID, otherwise it displays INVALID in the label. The text field
and the label should have their own separate style, each with a minimum
of four attributes. Include a Model. Do not use XML.
28. Write an app that has one text field and one label. The user can enter
his or her password. The label displays, as the user types, WEAK or
STRONG. For the purpose of this app, we define a weak password as
having eight or fewer characters. A strong password is defined as
having nine or more characters. Include a Model. Do not use XML.
29. Write an app that displays a chessboard on a grid with black and white
pieces. You can represent each piece by a letter or two, for example, P
for pawn, Q for queen, R for rook, K for knight, B for bishop, and KG for
king. When the user clicks on a knight, color in green the cells where the
knight can move. Include a Model. Do not use XML.
30. Write an app that displays four labels and a button. The first two labels
represent two cards in a simplified blackjack game and are filled with
two randomly generated integers with values between 1 and 11
inclusive. The total of the two is displayed inside the fourth label. When
the user clicks on the button, if the current total shown inside the fourth
label is 15 or less, the third label is filled with a randomly generated
number between 1 and 11 inclusive and the total inside the fourth label is
updated to equal the sum of the three numbers in labels one, two, and
three. If the current total shown inside the fourth label is greater than 15,
nothing happens. Include a Model. Do not use XML.
CHAPTER FOUR: Multiple
Activities, Passing Data between
Activities, Transitions, Persistent
Data
Chapter Summary
Introduction
Most apps involve the use of several screens. In this chapter, we learn how
to code several activities, how to go from one to another and back, how to
share data between activities, how to set up transitions between them, and
how to save the state of an app and retrieve it whenever the user starts the
app again (i.e., how to make the data persistent). We build a mortgage
calculator app as a vehicle to learn all these concepts.
We explore two layout managers: a TableLayout for the first screen and a
Relative-Layout for the second screen. In the second screen, we explore
more GUI components: radio buttons, which are used to display mutually
exclusive choices.
The Model for this app is the Mortgage class, which encapsulates a
mortgage. A typical mortgage has three parameters: the mortgage amount,
the interest rate, and the number of years for the mortgage. For simplicity,
we assume that the interest rate parameter is the annual rate, and is
compounded monthly; we also assume that the monthly payment is constant.
TABLE 4.1 shows the flow of money. M is the mortgage amount that we
receive at time 0 from the bank. P is the monthly payment that we pay every
month to the bank, starting at month 1, and ending at month n*12, where n is
the number of years.
If r is annual interest rate, then mR = r/12 is the monthly interest rate. The
present value of all the monthly payments discounted using the monthly
interest rate of mR is equal to M, the mortgage amount. Thus, we have the
following equation between M and P, mR, and n.
Mortgage M
Payment P P P P . . . P P
Thus, M = P(1 – a n*12)/mR; our formula for the monthly payment is:
EXAMPLE 4.1 shows the Mortgage class, including its three instance
variables, amount , years , and rate (lines 9–11), constructor (lines 13–17),
mutators (lines 19–32), and accessors (lines 34–36, 42–44, and 46–48). The
monthlyPayment method is at lines 50–54. We have included a DecimalFormat
constant MONEY (lines 6–7) so that we can format the mortgage amount, the
monthly payment, and the total payment with a dollar sign and two digits
after the decimal point. The methods getFormattedAmount (lines 38–40),
formattedMonthlyPayment (lines 56-58), and formattedTotalPayment (lines 64–
66) return string representations of amount , the monthly payment, and the
total payment respectively. The DecimalFormat class, part of the java.text
package is imported at line 3. We choose not to provide a formatting method
for the interest rate because we want to show its exact value in the app.
EXAMPLE 4.1 The Mortgage class
We use the empty activity template for this app. The View part of the Model-
View-Controller for this app consists of two screens. The first screen
displays the characteristics of a mortgage: the amount, the number of years,
the interest rate, the monthly payment, and the total payments over the
course of the mortgage. The user does not interact with this screen, it is
read-only. We want to display all this information as a table of six rows and
two columns. For each of the first five rows, the first column is a label
describing the data displayed in the second column. The last row is a button
that will enable the user to go to another screen to update the data for the
mortgage amount, number of years, and interest rate. FIGURE 4.1 shows a
preview of Version 0 of the app inside the environment.
its own, other than those inherited from its ancestor classes, such as
LinearLayout , ViewGroup , and View . FIGURE 4.2 shows the inheritance
hierarchy.
FIGURE 4.1 Preview of the Mortgage Calculator app, Version 0
At lines 43–46, we insert a View element that is 5 pixels high and red. This
defines a thin red rectangle that spans the width of the screen, showing as a
red line. This enables us to separate the top area of the screen displaying
the mortgage parameters from the bottom area of the screen showing the
calculated data (i.e., monthly payment and total payment). These are shown
in the next two rows. The XML code for these two rows is similar to the code
for the first three rows, using the same padding, some Strings from
strings.xml, and ids.
The last row shows a button. Since there is only one element in that row, we
center the row at line 73 using the android:gravity attribute with value
center . We specify 50 dip for the padding above the row (line 74) to better
separate the button from the row above it. At line 77, we specify modifyData
as the method that will be called when the user clicks on the button.
We want the font size for all elements to be larger than the default font size,
thus, we specify a text size of 22sp at line 3 of the file styles.xml (EXAMPLE
4.5). This font size may work well for some Android devices and not as well
for others, but we do not worry about this issue in this app—we cover that
topic later in the book. The AppTheme style, at line 2, is the default style
specified in the AndroidManifest.xml file as the theme for the app.
In order to keep this app simple, we only allow the app to run in vertical
orientation. Thus, inside the AndroidManifest.xml file, we add an
android:screenOrientation attribute to the activity element and set its value
to portrait .
4.3 Using a RelativeLayout for the Second Screen
GUI
The second component of the View part of the app is the second screen. It
enables the user to change the three mortgage parameters: the amount, the
number of years, and the interest rate. We use this opportunity to explore
radio buttons, which we use to select the number of years for the mortgage.
COMMON ERROR: We should not use upper case letters in our XML
file names. Android Studio does not allow them. File names for
resources must start with a lowercase letter and only contain
lowercase letters, digits, and underscores.
For the GUI of this second screen, we use the RelativeLayout class, a
subclass of View-Group , as shown in Figure 4.2. It enables us to position
components relative to other components.
View an id and use that id to reference it. We can also position a View with
respect to its parent View , in which case we do not need to reference the
parent View with its id. Some of these attributes are listed in TABLE 4.2.
The first eight attributes listed ( android:layout_alignLeft , to
android:layout_toRightOf ) expect their value to be the id of a View .
android:layout_toRightOf=”@+id/label_years”
in order to specify that the radio buttons group is positioned to the right of
the View whose id is label_years . That View , a TextView , is defined at lines
8–12, and its id is specified at line 9.
android:layout_alignLeft=”@+id/data_rate”
to further define the position of the radio buttons group. This specifies that it
is left-aligned with the View whose id is data_rate . That View , an EditText ,
is defined at lines 67–76, and its id is defined at line 68.
We can use the last four attributes listed in Table 4.2 to position a View
relative to its parent View . The attributes android:layout_alignParentLeft and
android:layout_align-ParentRight relate to vertical alignment, while
to horizontal alignment.
TABLE 4.2 Useful XML attributes of RelativeLayout.LayoutParams
android:layout_alignLeft View’s left edge matches the value of the view’s left edge.
android:layout_alignRight View’s right edge matches the value of the view’s right edge.
android:layout_alignBottom View’s bottom edge matches the value of the view’s bottom
edge.
android:layout_alignTop View’s top edge matches the value of the view’s top edge.
android:layout_alignParentLeft If true, view’s left edge matches its parent’s left edge.
android:layout_alignParentRight If true, view’s right edge matches its parent’s right edge.
android:layout_alignParentBottom If true, view’s bottom edge matches its parent’s bottom edge.
android:layout_alignParentTop If true, view’s top edge matches its parent’s top edge.
EXAMPLE 4.6 The activity_data.xml file
android:layout_alignParentRight=”true”
to specify that the EditText should be right aligned with its parent, the
RelativeLayout , which is a View , and in this case encompasses the whole
screen; so that means that the EditText ’s right edge should be vertically
aligned with the right edge of the screen.
At line 81, we center the button element horizontally. At line 82, we specify
that it should be positioned below the View whose id is data_rate and that it
should be 50dp below it (line 83). We also specify that the method goBack
will execute when the user clicks on the button (line 84).
At this point, when we run the app, we can only see the first screen, because
we have no way to go to the second screen, yet. However, we can
temporarily modify the statement in MainActivity.java that sets the resource
to be used for the first screen in MainActivity.java and show the second
screen instead. We show that at lines 11–12 of EXAMPLE 4.8.
We have now defined and coded the Model and the View part of the app, for
which we have two XML files defining two Views. Next, we code the
Controller part of the app. We want to be able to navigate back and forth
between the two Views that we have created. For this, we need to complete
the following steps:
FIGURE 4.3 Preview of the second screen of our Mortgage Calculator
app, Version 0
▸ Add some code in the first Activity class so that we can go to the
second View via some user interaction, in this case when the user clicks
on the Modify Data button.
Intent Constructor
Closes this activity and pops it off the stack; the screen for the prior activity is shown.
TABLE 4.4 shows the startActivity and finish methods of the Activity
class. StartActivity is typically called by the current Activity object
reference to execute its Intent argument.
18, we call startActivity with myIntent , and thus start a new activity of type
DataActivity .
syntax is:
android:name=”ActivityClassName”
The value must be specified (there is no default value) and can be a fully
qualified class name such as com.jblearning.mortgagev1.MainActivity . If the
value starts with a. (dot) as at lines 11 and 21 (. MainActivity
and .DataActivity ), then the value is appended to the package name listed
as the package attribute value of the manifest element (lines 2–3).
COMMON ERROR: We need to add an activity element in the
AndroidManifest.xml file whenever we add an activity to our app.
Otherwise, the app will crash when we try to go to that activity.
We can now run the app and go back and forth between the first and second
Views (Figures 4.1 and 4.3). We can also edit the mortgage amount,
interest rate, and number of years in the second screen. However, the values
in the first View are unchanged at this point. This will change in Version 2.
An activity goes through a life cycle, and methods are called automatically as
an activity is started, paused, stopped, or closed. TABLE 4.5 lists these
methods.
To illustrate which methods are called and when as the user runs the app, we
include all the methods of Table 4.5 in our MainActivity and DataActivity
classes. Each method calls its super method and outputs something to
Logcat. EXAMPLE 4.12 and EXAMPLE 4.13 show the two classes. Note
that if we do not call the super methods, the app will crash.
Method Description
onCreate(Bundle) Called when the activity is created; the Bundle argument stores the activity’s
previously frozen state, if there is one.
onResume() Called after onStart, when the user starts interacting with the activity.
onDestroy() Called when the activity has ended or is being destroyed by the system
because the system is running out of memory and needs to free some
memory.
EXAMPLE 4.12 The MainActivity class with its life cycle methods
EXAMPLE 4.13 The DataActivity class with its life cycle methods
new activity is started, it goes to the top of the stack. When an activity is
destroyed, it is popped off the stack.
TABLE 4.6 shows the state of the output and the activity stack as the user
starts the app and interacts with the app on the device.
When the app starts, the onCreate , onStart , and onResume methods of
MainActivity , the starting activity, are called in that order. When the user
of MainActivity are called, and then the onStop and onDestroy methods of
DataActivity are called. The call to onDestroy shows that the DataActivity
instance, previously at the top of the stack, is popped off the stack and is no
longer in memory. The call to the onRestart method of MainActivity shows
that the MainActivity instance, now at the top of the stack (the only one on
the stack at this point), is restarted. Note that the onCreate method is not
called because the MainActivity instance was created before and is still in
memory.
At that point, if the user just waits and stops interacting with the app, the app
goes to the background and is no longer visible; the onPause and onStop
methods of MainActivity , the current activity, are called. Then, when the
user touches the Power button and swipes the screen, the onRestart ,
onStart , and onResume methods of MainActivity are called as the current
TABLE 4.6 Output and state of Activity stack as the user interacts with the
app
User waits a while, app goes to the background, is Inside Main activity
no longer visible MainActivity:onPause
Inside
MainActivity:onStop
User touches the device’s Power button, then Inside Main activity
swipes the screen MainActivity:onRestart
Inside
MainActivity:onStart
Inside
MainActivity:onResume
User hits the device’s Home Key button MainActivity:onPause Main activity
MainActivity:onStop
Then, if the user touches the Home button, onPause and onStop are called.
When the user touches the app icon on the screen, the app restarts, thus,
onRestart , onStart , and onResume are called again.
Finally, if the user touches the Back Key button, onPause , onStop, and
onDestroy are called and we exit the current activity. The activity stack is
There are several ways that we can pass data from one activity to another,
including:
▸ Pass data using the putExtra methods of the Intent class. Data must
be either primitive data types or Strings .
▸ Declare a public static instance of a class of the Model (in this app,
the Mortgage class) in one Activity class. That makes that instance
globally accessible by any other Activity class.
In this app, we want to share a Mortgage object between the two screens,
rather than sharing primitive data types or Strings . Thus, we will not use the
putExtra methods of Intent . Later in the book, we show how to use the
A singleton class is a class from which only one object can be instantiated.
We can declare several object references of that class, but after
instantiation, they will all point to the same object in memory. Thus, activities
can share that same object, reading data from it and writing data to it. We
could recode the Mortgage class so that it is a singleton class, but if we write
other apps, we may want to be able to instantiate more than one Mortgage
object. Thus, we decide not to implement the Mortgage class as a singleton.
We implement the second strategy, the most simple for this app. We declare
a public static variable of type Mortgage in the MainActivity class and we
access it from the DataActivity class. In MainActivity , we have the
following declaration:
MainActivity.mortgage
In this way, the same Mortgage object can be referenced from both Activity
classes. This is what we want for this app, only one Mortgage object rather
than two identical Mortgage objects.
The onStart method (lines 18–21) is called automatically when we start the
app, when we come back from the data activity, or when we bring back the
main activity to the foreground after it went to the background. We want the
data to be updated every time those events happen, so we call the
updateView method at line 20. The updateView method, coded at lines 23–34,
updates the five TextView elements with current mortgage data. We retrieve
each TextView element using the findViewById method and typecast the
returned View to a TextView . Then we call methods from the Mortgage class
with the mortgage object in order to set the text of each TextView element
with current mortgage data. For example, at line 31, we set the text inside
the TextView displaying the monthly payment. We call the
formattedMonthlyPayment method of the Mortgage class with the mortgage
object in order to retrieve the monthly payment value. We then call the
setText method with monthlyTV and pass that value.
EXAMPLE 4.14 The MainActivity class, Mortgage Calculator app,
Version 2
The goBack method (lines 57–60), executes when the user clicks on the Done
button. Before the user leaves this activity (line 59) and returns to the main
activity, we want to update the state of the mortgage object based on the
values the user inputs. We call the method updateMortgageObject at line 58.
As in the updateView method, the first thing we do inside the
updateMortgageObject method (lines 32–55) is get a reference to the
mortgage object. Then, we update the values of its instance variables amount ,
years , and rate . At lines 34–41, we update years based on the current
state of the three radio buttons. We call the method isChecked , inherited by
RadioButton from CompoundButton , to check if a radio button is on or off. At
line 42, we get a reference to the EditText element displaying the mortgage
amount and retrieve its text value and assign it to the String variable
amountString at line 43. We do the same for the interest rate value and
assign the value retrieved to the String variable rateString at line 45.
Because the amount and rate instance variable of the mortgage object are
floats , we need to convert the two Strings to floats . In the
FIGURE 4.4 and FIGURE 4.5 show the two screens after the user has
updated the mortgage parameters on the second screen.
FIGURE 4.4 The Mortgage Calculator app running inside the emulator,
Version 2 (second screen)
FIGURE 4.5 The Mortgage Calculator app running inside the emulator,
Version 2 (first screen after coming back from the second screen)
When looking for resources, the Android framework looks inside the res
directory. We create a directory named anim in the res directory, and add
two XML files, slide_from_left.xml and fade_in_and_scale.xml in it. FIGURE
4.6 shows the directory structure. R represents the res directory, and we
access these two resources using the expressions R.anim.fade_in_and_scale
and R.anim.slide_from_left . The Android framework automatically creates
fade_in_and_scale and slide_from_left as public static int constants in
the anim class, itself a public static inner class of the R class.
The abstract class Animation is the root class for animation classes. It
defines some XML attributes that we can use to define an animation using an
XML file. It also defines some methods we can use to define the animation
by code. It has five direct subclasses: AnimationSet , Alpha-Animation ,
RotateAnimation , ScaleAnimation , and TranslateAnimation . TABLE 4.7 shows
other elements inside it and define several animations that run concurrently.
TABLE 4.8 shows some selected XML attributes and their meaning for the
XML elements in Table 4.7. The android:duration and the
android:interpolator attributes are common to all animations. The
FIGURE 4.6 The directory structure showing the transition XML files
TABLE 4.7 Selected animation XML elements and their corresponding
classes
When assigning values to these attributes, we can use either absolute values
or relative values. A relative value can be relative to the element itself using
the syntax value%, for example, 30% , or can be relative to its parent using
the syntax value%p, for example, 50%p .
TABLE 4.8 Selected XML attributes of the various Animation classes
EXAMPLE 4.16 shows the sliding from the left side of the screen transition.
For a horizontal sliding transition, we define the starting x-coordinate using
the attribute android:fromXDelta and the ending x-coordinate using the
attribute android:toXDelta , which should be set to 0. The android:fromXDelta
value should be negative if the screen comes in left to right (and positive if
the screen comes in right to left). The two values are defined at lines 5 and
6. The time of the transition is defined using the attribute android:duration ;
its value is in milliseconds. Line 7 defines a transition lasting 4 seconds.
EXAMPLE 4.17 shows a fade in and scaling transitions that run concurrently.
They are both defined inside a set element.
For the fade animation (lines 4–7), we use an alpha element and we define
the starting opacity using the android:fromAlpha attribute and the ending
opacity using the android:toAlpha attribute. For a full fade in, the starting
opacity is 0 and the ending opacity is 1. They are defined at lines 5 and 6.
Line 7 defines a transition lasting 3 seconds.
For the scaling animation (lines 9–16), we use a scale element and we
define the starting and ending x and y scaling values using the
android:fromXScale , android:toXScale , android:-fromYScale , and
Method Description
In the DataActivity class, the method goBack (EXAMPLE 4.19) includes the
code to go back to the first screen. We call overridePendingTransition at line
60 and specify the fade_in_and_scale resource to use to transition to the first
screen and no transition from the current screen.
This file should not be modified. Among other things, it includes public static
classes containing constants for the transitions, the ids, the layouts, the
strings, etc.
If we run the app, we can see the sliding to the left transition going to the
second screen, and the fade in and scaling transition coming back to the first
screen (shown in FIGURE 4.7).
FIGURE 4.7 The Mortgage Calculator app in the middle of the
fade_in_and_scale transition, Version 3
In Version 4 of the app, we want to make the data chosen by the user
persistent. When the user uses the app for the first time, we show the
default values for the three mortgage parameters, the mortgage amount, the
interest rate, and the number of years. But when the user uses the app
again, we want to show the values that were used the last time the user
used the app.
In order to implement that functionality, we write to a file on the device the
mortgage parameters every time they are changed. When we start the app
the first time, the file does not exist and we use the default parameters for
the mortgage. When we run the app afterward, we read the mortgage
parameters from the file. Although we could use the openFileOutput and
openFileInput methods of the ContextWrapper class to open a file for writing
and reading, it is easier to use the user preferences system in order to store
and retrieve persistent data. Preferences for an app are organized as a set
of key/value pairs, like a hashtable. In this app, since we have three values
for a mortgage, we have three key/value pairs.
Method Description
value )
// editor is a SharedPreferences.Editor
editor.putInt( ”rating”, 10 );
some of them. The getDataType methods have this general method header:
The return value is the value that was previously associated with key when
user references were written to. If the key does not exist, defaultValue is
returned. Assuming we have a Shared-Preferences reference named pref , in
order to retrieve the value that was previously associated with the key
rating and written to the preferences, we write:
// pref is a SharedPreferences
Method Description
int getInt(String key, int Returns the int value associated with key in this SharedPreferences
defaultValue) object. Returns defaultValue if the key is not found.
float getFloat(String Returns the float value associated with key in this
key, float defaultValue) SharedPreferences object. Returns defaultValue if the key is not
found.
Method Description
we can write:
SharedPreferences pref =
ReferenceManager.getDefaultSharedPreferences( this );
The View components of our app are still the same. Most of the changes
take place in the Model. We modify the Mortgage class, so that it includes a
method to write mortgage data to the user preferences system and a
constructor to read data from it. In both the MainActivity and DataActivity
classes, which make up the Controller parts of the app, we use these
methods to either load or write the mortgage parameters from and to the
user preferences system.
EXAMPLE 4.21 shows the updated parts of the Mortgage class. The
SharedPreferences interface and the PreferenceManager class are imported at
lines 5–6. Lines 11–13 define three String constants that hold the
preferences key names for amount, years, and rate.
lines 89–91 using the three keys defined at lines 11–13. At line 92, we call
commit to actually write to the preferences.
EXAMPLE 4.21 The Mortgage class, Mortgage Calculator app, Version 4
There is also only one line of code to add to the DataActivity class: a
statement that writes the data in mortgage to the user preferences for this
app. We do this toward the end of the updateMortgage-Object method by
calling the setPreferences method with mortgage , once again passing this
as its argument (line 51 of EXAMPLE 4.23). The updateMortgageObject
method is called right after the user has updated the mortgage parameters
on the second screen and before going back to the first screen.
When an app writes to the user preferences, it writes to the device file
system. Generally, when we release an app to Google Play that requires
interaction with a device, we may need to include a uses-permission element
in the AndroidManifest.xml file so that the app operates correctly.
Furthermore, before somebody downloads the app, he or she is informed
that the app writes to the device’s file system. The syntax for such an
element is:
The android:name attribute is the name of the permission: its value relates to
the fact that the app wants to use a function or service of the device, for
example, its camera, its list of contacts, or its ability to read or send SMS
messages. There are many values that can be assigned to this attribute, for
example android.permission.CAMERA , android.permission.READ_CONTACTS ,
android.permission.FLASHLIGHT , or in this app’s case android.permission.
WRITE_EXTERNAL_STORAGE .
For this app, since we are writing on the device file system, we need to
include the following inside the manifest element in AndroidManifest.xml
(note that this is not necessary when we run the app in the emulator):
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_
STORAGE” />
When we run the app the second time, the data that we entered on the
second screen the first time we ran the app is now shown on the first screen.
The app is pulling data from the preferences that were written into the first
time we ran the app.
Chapter Summary
The Android framework provides layouts to help us organize a View.
Layouts are subclasses of ViewGroup .
A TableLayout arranges its children in rows and columns.
A RelativeLayout positions components relative to other components.
We can call the startActivity method of the Activity class, passing an
Intent argument, to start a new Activity for that Intent .
15. Inside a TableLayout element, this code adds a row that contains an
EditText and a TextView whose ids are game and player.
<TableRow
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” >
<!--Your code goes here -->
</TableRow>
<EditText
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
<!-- Your code goes here -->
android:inputType=”numberDecimal” />
18. Inside the AndroidManifest.xml file, add an activity element of the type
MyActivity class
19. Inside an activity, when the user clicks on a button, the method
goToSecondActivity executes. Write the code to start a new activity from
the SecondActivity class.
20. When the user comes back to this activity from another activity, we want
the method modifyThisActivity to execute. Override the appropriate
method and make the call to the modifyThisActivity method inside it.
<scale android:fromXScale=”0.0”
android:fromYScale=”0.0”
<!--Your code goes here -->
</set>
22. This XML file defines a resource for a transition rotating 180 degrees
clockwise around the top left corner, finishing in the normal position and
lasting five seconds.
<set xmlns:android=”http://schemas.android.com/apk/res/android”>
<rotate
</set>
23. This code writes the values 45 and ”Hello” to user preferences using the
keys number and hi.
getDefaultSharedPreferences( );
24. This code reads the integer value associated with the key grade and the
String value associated with the key course from the user preferences
and assigns them to two variables. If the keys do not exist, the default
values 80 and CS3 should be assigned to the two variables.
SharedPreferences preferences = PreferenceManager.
getDefaultSharedPreferences();
Write an app
25. Write an app using two activities: one activity plays TicTacToe, and the
other activity asks the user to choose who plays first (X or O) and the
colors for the Xs and Os. Include a Model. Include transitions between
the two activities.
26. Write an app using two activities: one activity asks the user to give the
answer to a simple math problem—addition, subtraction, or multiplication
—the other activity asks the user to choose the arithmetic operation.
Include a Model. Include transitions between the two activities.
27. Write an app using two activities: one activity performs a unit conversion
from Celsius to Fahrenheit or Fahrenheit to Celsius, and the other
activity asks the user to choose which way to make that conversion.
Include a Model. Include transitions between the two activities.
28. Write an app using two activities: one activity performs a unit conversion
from miles to kilometers or kilometers to miles, and the other activity
asks the user to choose which way to make that conversion. Include a
Model. Include transitions between the two activities.
29. Write an app using two activities: one activity performs the translation
from English to another language of the sentence Hello World , and the
other activity asks the user to choose one of five languages for the
translation. Include a Model. Include transitions between the two
activities.
30. Write an app using two activities: one activity performs a unit conversion
from pounds to kilograms or kilograms to pounds, and the other activity
asks the user to choose which way to make that conversion. Include a
Model. Include transitions between the two activities. Make the user
choice persistent so that next time the user runs the app, his or her
previous choice is the default.
31. Write an app using two activities: one activity performs a currency
conversion from dollars to another currency, and the other activity asks
the user to choose which currency to use among five currencies. Include
a Model. Include transitions between the two activities. Make the user
choice persistent so that next time the user runs the app, his or her
previous choice is the default.
32. Write an app using two activities: one activity calculates the monthly
payment for a car lease, and the other activity asks the user for the car
lease parameters—duration in months, down payment, lease rate, and
car value at the end of the lease. Include a Model. Include transitions
between the two activities. Make the user choice persistent so that next
time the user runs the app, his or her previous choices are the default
values when the app starts.
33. Write an app using two activities: one activity encrypts with a fixed shift
(using a Caesar cipher) a text that the user types in a text field, and the
other activity asks the user to define the shift, an integer between 1 and
25 (if the shift value is 3, then the word the will be encrypted into wkh . If
the word is zoo , the encrypted word is crr ). Assume that only
lowercase letters from a to z will be used. Include a Model. Include
transitions between the two activities. Make the user choice persistent
so that next time the user runs the app, his or her previous shift value is
the default value when the app starts.
CHAPTER FIVE: Menus, SQLite
CHAPTER CONTENTS
Introduction
Chapter Summary
When we start an app using the Basic Activity template, Android Studio
generates two layout XML files and one menu XML file: activity_main.xml,
content_main.xml, and menu_main.xml. This is different from the Empty
Activity template, which only generates the activity_main.xml file.
Inside the onCreate method of the MainActivity class, there is existing code
to handle user interaction with the floating action button, as shown in
EXAMPLE 5.2. Since we do not use the floating action button for this app,
we delete that code inside the MainActivity class.
EXAMPLE 5.1 The automatically generated activity_main.xml file when
using the Basic Activity template
EXAMPLE 5.3 shows the menu_main.xml file. It defines a menu with one
item. Menu items are shown in the action bar, starting from the right.
However, if we run the skeleton app, no menu item shows. This is because
the only menu item has the value never for the attribute app:showAsAction
(line 9).
data type), whereas the other setTitle method accepts an int parameter
representing a resource. Menu items can be given an id so that we can get a
reference to them in the corresponding Activity class. TABLE 5.2 shows
some constants of MenuItem that are possible arguments of the
setShowAsAction and the corresponding values for the showAsAction XML
attribute.
android:title setTitle( int ), Sets the title for the menu item
setTitle(CharSequence )
app:showAsAction setShowAsAction( int ) Defines how this item displays within the
action bar
android:icon setIcon( int ), setIcon( Drawable Sets the icon for the menu item
)
If there are too many items to fit in the action bar, the ones on the far right
will not be immediately visible, but they are still accessible via a sub menu.
If we select the menu_main.xml file, the menu items are visible in the preview
pane, as shown in FIGURE 5.1. When we run the app inside the emulator or
a device, the UPDATE menu item may or may not be visible depending on
the available space. If there are items that are not shown, there is a . . . that
will show instead and the non-visible items will become visible when the user
clicks on . . . If we click on the various menu items, the corresponding output
will show in Logcat.
EXAMPLE 5.6 The MainActivity class, Candy Store app, Version 0
TABLE 5.3 The setSupportActionBar of the AppCompatActivity class
Method Description
void Sets toolbar to act as the action bar for this Activity; the toolbar’s menu
setSupportActionBar( will be populated with the activity’s options menu
Toolbar toolbar )
MenuInflater void inflate( int menuRes, Menu menu ) Inflates the menuRes resource and
creates menu with it.
In Version 1, we use icons instead of Strings for the menu items and we
provide an activity with its layout for the add menu item. To include an icon
for a menu item, we use the android:icon XML attribute shown in Table 5.1.
We can use existing icons available in the Android library or create our own
icons. Existing icons can be referenced using the following syntax and
pattern:
@android:drawable/name_of_icon
For add, edit (update), and delete, the names of the icon resources are
ic_menu_add , ic_menu_edit , and ic_menu_delete .
EXAMPLE 5.7 shows the updated menu_main.xml file, with the android:icon
attributes added at lines 7, 12, and 17. Note that although the icons show in
the action bar and the titles do not, we still specify titles for the three items
(lines 6, 11, 16). Indeed, if the user long presses on an icon, the title shows.
This can be very helpful for visually impaired users.
FIGURE 5.2 shows a preview of the app in the Android Studio environment:
the three icons appear on the right side of the action bar.
When the user clicks on the add icon, we want to enable the user to add a
candy to the database. We keep things simple and our database only
contains one table storing candies: a candy has an id, a name, and a price.
Ids are expected to be integers starting at 0 and being automatically
incremented by 1 as we add candies. Thus, our second screen, where the
user can add a candy to the database, only includes two widgets for user
input: one for the name and one for the price of the candy. EXAMPLE 5.8
shows the XML layout file for it, activity_insert.xml.
The additional String constants used in the activity_insert.xml file are defined
in strings.xml, shown in EXAMPLE 5.9.
EXAMPLE 5.11 shows the InsertActivity class. We inflate the XML layout
defined in the insert_activity.xml file (line 11), and we include the insert and
goBack methods. The goBack method (lines 28–30) executes when the user
clicks on the BACK button; it pops the current activity off the activity stack,
returning the app to the previous activity (i.e., the first screen). Inside the
insert method (lines 14–26), we retrieve the user input at lines 15–19, plan
to insert a new candy in the database using user input (line 21), and clear the
two EditTexts (lines 23–25) in case the user wants to add another candy.
Finally, in styles.xml, we specify that the text size in the various views is 24,
as shown at line 5 of EXAMPLE 5.13.
EXAMPLE 5.13 The styles.xml file, Candy Store app, Version 1
FIGURE 5.3 The add a candy screen of the Candy Store app, Version 1
FIGURE 5.3 shows a preview of the insert screen of the app inside the
Android Studio environment.
When creating a SQLite table, we are restricted to the following data types:
null , integer , real , text , and blob . We use real for floats and doubles ,
and text for strings. SQLite includes support for date and time using the
integer , real , or text data types.
Class Description
SQLiteOpenHelper Extend this abstract class to manage a database and its version .
We must override the onCreate and onUpgrade methods.
id Name Price
We include in the Model a class that mirrors the columns of a SQL table. We
intend to store names and prices for candies as in TABLE 5.6, where id is
an int , name is a String , and price is a double .
The Candy class, shown in EXAMPLE 5.14, mirrors the type of data we have
in Table 5.6. It is a straightforward Java class with constructor, accessors,
and mutators. We included a toString method, which is always very useful
for debugging and feedback purposes.
Method Description
void execSQL( String sql ) Executes sql, a SQL query that does not return data. Can be used
for create, insert, update, delete, but not for select queries.
Cursor rawQuery( String Executes sql and returns a Cursor; selectionArgs can be provided to
sql, String [ ] selectionArgs ) match ?s in the where clause of the query.
TABLE 5.8 Selected Methods of the Cursor class
Method Description
boolean moveToNext( Move this Cursor to the next row when processing results.
)
DataType Returns the value for the current row at column index column. DataType
getDataType( int can be a basic data type, String or Blob.
column )
EXAMPLE 5.15 The DatabaseManager class, Candy Store app, Version 2
The constructor (lines 17–19) calls the super constructor shown in Table 5.9.
The onCreate method (lines 21–28) is automatically called when the
database is first created. Inside it, we should create the tables that we need.
We define a String representing an SQL statement to create the candy
table at lines 22–25 and actually create the table at line 27. Note that a
database is specific to the app that uses it. If we have two different apps,
we have two different databases.
The insert , deleteById , and updateById methods (lines 38–46, 48–55, 57–
67) share the same pattern: we get a SQLiteDatabase reference by calling
the getWritableDatabase of the SQLiteOpenHelper class, build an SQL query,
execute it by calling the execSQL method, and close the database.
TABLE 5.9 Selected Methods of the SQLiteOpenHelper class
Method Description
abstract void onCreate( Called when the database is created for the first time. We must
SQLiteDatabase db ) implement that method.
abstract void onUpgrade( Called when the database needs to be upgraded. We must
SQLiteDatabase db, int implement that method.
oldVersion, int newVersion )
SQLiteDatabase Creates and/or opens a database that we will use for reading and
getWritableDatabase( ) writing. Triggers a call to onCreate the first time it is called.
Returns a SQLiteDatabase reference, which we can use to
perform SQL operations.
The selectAll and selectById methods (lines 69–84 and 86–98) get a
SQLiteDatabase reference, build a select SQL query, execute it by calling the
rawQuery method, process the results, close the database, and return an
Now that our Model is ready, we can use it in the Controller, the
InsertActivity class, in order to add a candy in our database, as shown in
Method Description
static Toast makeText( Context context, Creates a Toast within context with content text and
CharSequence text, int duration ) a duration specified by duration.
Constant Value
Toast class to specify the duration of the Toast . We then can show the
Toast by calling the show method. The following code sequence illustrates
toast.show( );
When we run the app, enter some data and click on the ADD icon, the Toast
message appears. If we want to check that a new row is added to the candy
table, we can call the selectAll method of the DatabaseManager class and
loop through the resulting ArrayList of Candy objects. We can write these
statements at the end of the insert method and check the output in Logcat:
▸ Modify MainActivity so that when the user clicks on the DELETE icon,
the user goes to the delete activity.
5.17) so that when the user clicks on the delete icon (line 36), we create an
intent for a DeleteActivity (line 37), and then start that activity (line 38).
EXAMPLE 5.17 The MainActivity class, Candy Store app, Version 3
Because the number of records in the candy table varies over time, the
number of radio buttons varies as well. Thus, we need to create the GUI
programmatically. At line 21 of the onCreate method, we call the updateView
method, which creates the GUI. We code updateView at lines 24–64: we
retrieve all the records from the candy table and create one radio button per
record. In order to do this, we call the selectAll method of the
DatabaseManager class at line 26, using the instance variable dbManager (line
Although the user can use the device’s back button to go back to the
previous activity, we add a button to do that as a convenience (lines 40–42;
we use a string named button_back from strings.xml). We set up event
handling for the button at lines 44–48 using an anonymous object of type
View.OnClickListener . When the user clicks on the button, the current activity
is popped off the stack and we go back to the previous activity, the first
screen. However, a ScrollView does not allow more than one ViewGroup
inside it. Thus, we cannot add the button inside the ScrollView . We place the
ScrollView inside a RelativeLayout (line 51) and place the button at the
We create the RadioGroup at line 29. At lines 30–35, we loop through the
ArrayList of Candy objects: we create one RadioButton per Candy (line 31),
set its text to the String representation of that Candy object (line 33), and
add it to the RadioGroup at line 34. At line 32, set the id of the RadioButton
to the id of the corresponding Candy object. In this way, when the user
selects a radio button, we can access the correct Candy object.
At lines 36–38, we set up event handling for the group of radio buttons.
TABLE 5.11 shows the setOnCheckedChangeListener method of the
RadioGroup class that we use to handle the selection of a radio button within
delete the candy selected from the candy table. Because we assigned the
Candy id to the corresponding RadioButton , we know that the checkedId
parameter is not only the id of the RadioButton that is selected, but also the
id of the correct Candy to delete. At lines 71–72, using a Toast , we provide
some visual feedback that a candy was deleted. At lines 74–75, we call
updateView to update the list of radio buttons, reflecting that the deleted
Method Description
Figure 5.4 shows the delete screen of the app after the user selects the
DELETE icon. All candies are displayed as radio buttons. Clicking on one
deletes it and refreshes the screen. Note that the database in Version 3 is
different from the database in Version 2. Thus, if we run Version 3 without
inserting candies, the candy table is empty and no candy will show when we
click on the DELETE icon.
▸ Modify MainActivity so that when the user clicks on the UPDATE icon,
the user goes to the update activity.
button to update the name and price of that candy in the database. As for the
delete activity, clicking on a button updates the candy and refreshes the
screen.
As in the delete activity, we need to create the GUI by code and will wrap a
ScrollView in the list of candies. To keep this example simple, we do not
provide a BACK button to go back to the previous activity. The user can go
back using the device’s BACK button. We organize the components in a grid
with four columns: we place the id in the first column in a TextView , the name
and the price in the second and third columns in EditTexts , and a button in
the fourth column. As before, the updateView method creates the GUI, and is
called by onCreate and after the user updates a candy.
The ScrollView and GridLayout are created at lines 32–36. We create
arrays for the TextViews , EditTexts , and Buttons at lines 38–41.
EXAMPLE 5.22 The UpdateActivity class, Candy Store app, Version 4
FIGURE 5.5 The update screen of the Candy Store app, Version 4
We distribute the width of the screen among the four components across it
as follows:
We add the grid to the ScrollView at line 87 and assign the ScrollView as
the content View of this activity at line 88.
Figure 5.5 shows the update screen. Clicking on an UPDATE button updates
the corresponding candy and refreshes the screen. The user is updating the
price of the walnut chocolate candy. Once again, if we run Version 4 without
inserting candies first, the candy table is empty and no candy will show when
we click on the UPDATE icon.
In Version 5, we enable the user to run the app as a cash register using the
first screen. We provide a grid of buttons, one button per candy. The store
employee can use the buttons to compute the total amount of money due by
a customer who buys candies. Each time the user clicks on a button, we add
the price of the corresponding candy to the total for that customer and show
the total in a Toast.
When the user clicks on a button, we need to access the price of the candy
associated with that button. An easy way to solve that problem is to create a
new class, CandyButton , which extends the Button class and has a Candy
instance variable. It is shown in EXAMPLE 5.23. We include a getPrice
method (lines 14–16) that returns the price of the Candy instance variable. In
this way, a CandyButton ”knows” the price of its associated Candy .
The app needs to work for more than one customer. Thus, we need to have
a way to reset the running total to 0 whenever we are done with the current
customer and are ready for the next one. An easy way to do that is to
provide an additional item in the menu and reset the total to 0 when the user
clicks on that item. Thus, we add an item in the menu as shown in EXAMPLE
5.24 at lines 5–8. We add a String named reset with value RESET in the
strings.xml file (not shown). We also add our own icon, stored in the
ic_reset.png file, which we place in the drawable directory. We access that
icon at line 7 using the expression @drawable/ic_reset.
As we did in the delete and update activities, we need to place our buttons
inside a ScrollView because we do not know how many there are each time
we run the app . An easy way to do this is to replace the RelativeLayout
inside content_main.xml with a ScrollView element as shown in EXAMPLE
5.25. We give it an id (line 12) so that we can retrieve it inside the
MainActivity class. We also eliminate the padding inside the ScrollView .
EXAMPLE 5.24 The menu_main.xml file, Candy Store app, Version 5
EXAMPLE 5.26 shows the updated MainActivity class. It has four instance
variables (lines 19–22): A DatabaseManager , dbManager , so that we can query
the database to retrieve the candies; total , a double , to keep track of the
running total for the current customer; scrollView , a reference to the
ScrollView defined in content_main.xml; and buttonWidth , the width of each
button. We instantiate dbManager at line 30, initialize total to 0.0 at line 31,
instantiate scrollView at line 32, and calculate buttonWidth at lines 33–35.
We want to size the buttons so that their width is half the width of the screen.
Thus, we assign half the width of the screen to the variable buttonWidth .
When the user has finished processing the current customer and clicks on the
reset icon (line 104), we reset the running total to 0.0 (line 105) so that the
app is ready to compute the total for the next customer.
EXAMPLE 5.26 The MainActivity class, Candy Store app, Version 5
The updateView method (lines 44–77) creates the GUI and sets up event
handling. We call updateView inside onCreate (line 36) when the app starts
and also inside onResume (line 41) when the user comes back from a
secondary activity such as add, delete, or update. Indeed, the contents of
the candy table may have changed when the user comes back from a
secondary activity so it is necessary to update the first screen by calling
updateView . The onResume method is automatically called when the user
The updateView method has similarities with the updateView method in the
UpdateActivity class. It is possible that the user added, deleted, or updated
one or more candies before coming back to this View. Thus, we first remove
all the buttons inside scrollView at lines 47–48 before rebuilding scrollView .
We create a grid of buttons dynamically, one button per candy. We include
two buttons per row (line 53). We size the number of rows to guarantee to
have enough room for all the buttons (line 52). As in the delete and update
activities, we put the grid inside a ScrollView so that we have automatic
scrolling if needed (line 75).
Inside each button, we put the name and the price of the candy, each on one
line (lines 62–65). Long candy names may require two lines of their own. We
could set the font size of each button dynamically to make each candy name
fit on one line. This is beyond the scope of this chapter, but we explain it in
Appendix A. We set up event handling at lines 67–68.
using the CandyButton class is illustrated at line 115, where we cast the View
parameter v , which represents the button clicked, to a CandyButton and call
getPrice to retrieve the price of the corresponding candy.
FIGURE 5.6 shows all the candies and a current running total of $4.48 after
the user selected chocolate cookie and walnut chocolate. Note the three
vertical dots showing on the right of the menu because there is not enough
space for all the icons. Touching the three dots opens a submenu showing
the missing items.
FIGURE 5.6 Running the cash register of the Candy Store app, Version
5
Chapter Summary
We can place menu items in the action bar.
A menu item can be either text or an icon.
A menu can be defined in an XML file, such as the automatically
generated menu_main.xml, using item elements inside a menu element.
The onCreateOptionsMenu method of the Activity class is automatically
called when the activity starts.
Clicking on an item in a menu triggers a call to the
onOptionsItemSelected method.
We can retrieve which item was selected if we give each item an id.
SQLite is available on every Android device.
SQLite allows us to organize our data in a relational database manner
and perform SQL queries.
In order to perform database operations, we extend the
SQLiteOpenHelper class.
10. Inside an item element of a menu, this line of code makes the item
visible in the action bar if there is room for it.
<item
/>
11. Inside an item element of a menu, this line of code specifies that the title
of the item is the value of the string test, defined in strings.xml.
<item
…
/>
12. Inside an item element of a menu, this line of code specifies that the icon
of the item is the file named image.png, which is stored in the drawable
directory.
<item
…
/>
13. Inside the AndroidManifest.xml file, add an activity element of the type
SecondActivity class.
</activity>
<!-- Your code goes here -->
</application>
14. When the user selects from the menu an item whose id is second, write
the code to go to another activity of type SecondActivity. Otherwise, we
do nothing.
15. Write the class header of the MyDBManager class. Inside that class, we
intend to create a database and perform SQL operations.
16. Inside a class that extends SQLiteOpenHelper, write the code for the
constructor. The name of the database we want to create is FRIENDS,
and its version is 3.
public DatabaseManager( Context context ) {
17. Inside a class that extends SQLiteOpenHelper, write the code for the
onCreate method. We want to create the table emails defined as
follows: the key is email, a string; it has two other columns, first and
last, both strings.
18. Inside a class that extends SQLiteOpenHelper , write the code for the
insert method below. We want to insert one record in the emails table
from question 17 using the three parameter values of the insert method
below.
19. Inside a class that extends SQLiteOpenHelper, write the code for the
delete method below. We want to delete the records in the emails table
from question 17 with a last value equal to the value of the parameter of
the method.
20. Inside a class that extends SQLiteOpenHelper, write the code for the
update method below. We want to update the records in the emails
table from question 17 whose email value is email. We want to change
the first and last names of that record to first and last.
Write an app
21. Modify the app of this chapter. Add a BACK button to the update
activity.
22. Write an app similar to the chapter’s app to manage a group of friends:
a friend is defined as having a first name, a last name, and an email
address. The friends should be stored in a database: a friend can be
added or deleted; his/her data can be modified.
23. Write an app similar to the chapter’s app to manage a group of friends:
a friend is defined as having a first name, a last name, and an email
address. The friends should be stored in a database: a friend can be
added, updated, or deleted; his/her data can be modified. On the first
screen, you should display a list of all the friends.
24. Same as æ 22 with the following feature: on the first screen, provide a
search engine where the user enters an email; the app searches the
database and returns the corresponding first and last names if the email
exists.
25. Same as æ24 with an autocomplete feature for the search engine text
field: as the user types, a drop-down list suggests possible matching
emails retrieved from the database. You should look at the
AutoCompleteTextView to implement this feature.
26. Write an app that does error correction. Build a database with one table.
That table has two columns: one stores misspelled words and the other
stores their corresponding correct words (e.g., the is the correct word
for teh ). The contents of that table can be hard coded and should
include at least five pairs of words. The app shows an EditText: as the
user types, the app corrects misspelled words based on the contents of
the table.
27. Same as æ 26 with the following feature: the user can add pairs of
misspelled and correct words in the table.
28. Same as æ 26 with the following feature: the user can delete and
update misspelled and correct word pairs.
29. Write a quiz app where the questions and the answers are stored in a
table of a database. The contents of the table can be hard coded.
30. Same as æ 29 with the following feature: the user can add pairs of
questions and answers in the table.
31. Same as æ 29 with the following feature: the user can delete and
update question and answer pairs.
32. Write an app that maintains a TO DO list stored in a database. The user
can add items to the list and delete them.
33. Same as æ 32 with the following feature: the TO DO list is displayed on
the first screen.
34. Same as æ 32 with the following feature: along with each item on the
TO DO list, we store a deadline in the database. The first screen should
display all the items in the TO DO list, and the past due items should be
colored in red.
CHAPTER SIX: Managing the
Device Orientation
CHAPTER CONTENTS
Introduction
Chapter Summary
<activity
android:screenOrientation=”landscape”
In this chapter, we want the app to run in both orientations. We use the
Empty Activity template so that our View has nothing in it when we start.
public int orientation An integer value representing the orientation of the screen.
public int screenHeightDp Height of the screen, not including the status bar, in dp units
(density independent pixels).
TABLE 6.2 Constant values for the orientation field of the Configuration
class
Constant Value
ORIENTATION_LANDSCAPE 2
ORIENTATION_PORTRAIT 1
TABLE 6.3 Constant values of the Configuration class related to a device’s
screen size
Constant Value
SCREENLAYOUT_SIZE_UNDEFINED 0
SCREENLAYOUT_SIZE_SMALL 1
SCREENLAYOUT_SIZE_NORMAL 2
SCREENLAYOUT_SIZE_LARGE 3
SCREENLAYOUT_SIZE_XLARGE 4
Class Method
Using these resources, we can detect information about the device that our
app is running on when the app starts. In order to use these resources, we
need a Configuration reference. Inside the Activity class, we can call the
getResources method from the Context class (that Activity inherits from) to
two methods.
Thus, inside an Activity class, we can use these two methods in order to
obtain a Configuration object as follows:
At lines 22–27, we retrieve the size of the screen in actual pixels and output
its width and height. At lines 29–32, we retrieve and output the pixel density
of the screen. Appendix A gives us a detailed explanation.
At lines 34–47, we test for the relative size of the screen and output it. Note
that we test for the relative sizes in descending order. If we tested for “at
least” a small screen first, it would always evaluate to true . We output the
two constants ORIENTATION_LANDSCAPE and ORIENTATION_PORTRAIT of the
Configuration class (2 and 1, respectively) at lines 49–52. At line 53, we
FIGURE 6.1 shows the various parts of a device’s screen. In yellow is the
device’s status bar. It typically includes some icons for system and
application notifications, including the clock. In red is the app’s action bar,
which typically includes the app name on the left and optional menu items on
the right, although it can be different depending on the app. In blue is the app
content View. This is where the contents of our app go. The visible display
frame is made up of the app’s action bar (in red) and the app content View
(in blue).
FIGURE 6.2 shows the output of Example 6.1 in Logcat when running using
the emulator for the Nexus 5 started in vertical position. The output confirms
that the device is in vertical position, that the screen of the device is 568 dp ×
360 dp in that position and is considered a normal size screen. The
orientation value is 1, equal to the portrait constant. The height dimension
(568 dp) includes the action bar height but does not include the status bar
height. The logical pixel density is 3; 568 dp is equivalent to 1,704 pixels (568
× 3), 72 pixels less than the 1,776 pixels screen height. The 72 pixels are
equivalent to 24 dp, the height of the status bar.
If we leave the emulator in horizontal position and restart the app, the output
shows that we detect that the device is in the horizontal position.
If we run the app in the Nexus 4 emulator, we can see that the logical pixel
density for the Nexus 4 is only 2, and the height and width are 568 dp and
384 dp, respectively.
FIGURE 6.1 View components of the screen
Inside the activity element for which we want to be notified if the device
configuration changes, we need to add the android:configChanges attribute
and assign to it the appropriate value or values. We can assign several
values, separated by the | character. TABLE 6.6 shows selected values. If
we want to be notified whenever the user rotates the device, we need to
assign the value orientation to android:configChanges . However, starting
with API level 13, we also need to add the value screenSize . Indeed, the
system considers that the screen size changes when the user rotates the
device. Thus, we need to specify the value orientation | screenSize for the
android:configChanges attribute as follows (note that there is no space
<activity
android:name=”.MainActivity”
android:configChanges=”orientation|screenSize” >
Using another modified HelloAndroid app, we demonstrate how we execute
inside the onConfigurationChanged method when the user rotates the device
in EXAMPLE 6.2. We can use the newConfig parameter of that method (lines
16–28) to detect what orientation the device is in and code the appropriate
changes for the app. In this simple app, we output screen size information at
lines 18–19 and orientation information at lines 21–27.
Method Description
locale The locale has changed (the user has selected a new language).
If we run the app starting in vertical position, there is no output until we rotate
the device (Ctrl+F11) to the horizontal position. When we do, the first four
lines of FIGURE 6.3 are output. When we rotate the device back (Ctrl+F12)
to the vertical position, the next four lines of Figure 6.2 are output. We can
see that the dimensions of the screen differ depending on the device’s
orientation.
▸ Have one layout XML file per orientation and inflate it whenever the
user rotates the device.
▸ Have the same layout XML file for both orientations and modify the
characteristics of some of the GUI components whenever the user
rotates the device.
Now that the resource files are set, we code the MainActivity class. If the
device is in the vertical position, we use the activity_main.xml file for the
layout. If the device is in the horizontal position, we use the
activity_main_landscape.xml file for the layout. Not only do we need to do
this inside the onConfigurationChanged method, but we must also do this
inside the onCreate method so that the proper layout file is used when the
app starts. Indeed, the device could either be in horizontal or vertical position
when the app starts. Since the same code will be run two times, we code a
new method, modifyLayout (lines 20–25 of EXAMPLE 6.7), which we call
from onCreate (line 12) and onConfigurationChanged (line 17). The
modifyLayout method tests what the orientation of the device is, and sets the
EXAMPLE 6.7 Managing orientation changes using two XML layout files
FIGURE 6.4 shows the app running inside the emulator when the device is in
vertical position, and FIGURE 6.5 shows the app after we rotate the device
to the horizontal position. If we place output statements to Logcat inside
onCreate and onConfigurationChanged , we see that onCreate is only called
when the app starts and is no longer called afterward when we rotate the
device. Instead, onConfigurationChanged is called. As discussed earlier, this
is because we added
android:configChanges=”orientation|screenSize”
inside the activity element in the AndroidManifest.xml file.
of its elements (line 6). If we keep the spacing constant between the buttons
no matter the orientation of the screen, for example 50 pixels, it is possible
that the app looks good in one orientation (vertical for example) but looks
bad in the other orientation (horizontal). Instead, we can set the spacing
between the buttons by code, depending on orientation. For this, we need to
access the second and third buttons at run time, so we give them an id at
lines 16 and 22. Lines 13, 19, and 25 specify the text of the three buttons
using the view1 , view2 , and view3 Strings defined in the strings.xml file,
shown in EXAMPLE 6.9. We use the same styles.xml file as the one in
Example 6.6.
In order to make the app look good in both positions, we need to set the
spacing between the buttons by code in the MainActivity class. If the device
is in the vertical position, we set the spacing to 50 pixels, and if the device is
in the horizontal position, we set the spacing to 25 pixels.
EXAMPLE 6.10 shows the new MainActivity class. This time, we inflate the
XML inside the onCreate method. Like before, both onCreate and
onConfigurationChanged call modifyLayout , which sets the spacing
The modifyLayout method (lines 25–41) retrieves the second and third
buttons at lines 26 and 29, and then retrieves their associated margin layout
parameters at lines 27–28 and 30–31.
EXAMPLE 6.10 Managing orientation changes using two XML layout
files
The Android framework includes layout parameter classes that we can use
to set the layout parameters of the GUI components contained in a layout.
The root class for those is ViewGroup.LayoutParams . It has many specialized
subclasses relating to specialized ViewGroups , including
TableLayout.LayoutParams , RelativeLayout.LayoutParams ,
android:layout_marginBottom setMargins( int left, int top, int right, int bottom )
android: layout_marginTop setMargins( int left, int top, int right, int bottom )
android: layout_marginLeft setMargins( int left, int top, int right, int bottom )
android: layout_marginRight setMargins( int left, int top, int right, int bottom )
FIGURE 6.6 App running in vertical position using 50 pixels between
buttons
FIGURE 6.7 App running in horizontal position using 25 pixels between
buttons
We test for orientation at lines 33 and 36–37 and set the top margin of the
layout parameters of the two buttons at lines 34–35 or 38–39 depending on
orientation. We use the two constants SPACING_VERTICAL and
SPACING_HORIZONTAL defined at lines 10–11. The MarginLayoutParams, params2
android:configChanges=”orientation|screenSize”
FIGURES 6.6 and 6.7 show the app running inside the emulator when the
device is in vertical position and horizontal position, respectively.
In this example, we hard coded the spacing between the buttons. As a result,
the app may not look as good in some devices. In the next example, we set
the spacing dynamically, relative to the dimensions of the device that the app
is running on.
In the previous example, we could have set up some dimen elements in the
dimens.xml file to define several spacing values to go with different screen
sizes. This strategy would work for most current devices, but may or may not
work well for future devices. In order to have maximum flexibility and tailor
the GUI to any screen size, we can control everything by code, without using
any layout XML file. Instead of inflating a layout XML file, we call a method
that sets up the layout programmatically. Additionally, we call another method
that sets the layout parameters for the GUI components of the View. The
basic principles of the previous example apply as far as managing
orientations: each time the user rotates the device, onConfigurationChanged is
called, and in turn it calls modifyLayout . This app is more complex, however,
because we need to capture information related to the screen dimensions
when the app starts in order to display the GUI components properly based
on the starting orientation. Overall, we replace the layout XML file and the
call to setContentView with a method setting up the GUI programmatically
and a call to that method. Again, we use the same styles.xml file as the one
in Example 6.6.
We need to display our buttons inside the app content View, the blue area of
the screen in Figure 6.1. And we want our buttons to be equally distributed,
whatever the orientation of the device, as shown in FIGURE 6.8.
FIGURE 6.8 Three buttons with equal spacing
If h is the height of the screen, a is the space between buttons, and b is the
height of a button, we have (assuming we are using three buttons):
h = 4 * a + 3 * b
Therefore,
a = (h – 3 * b)/4
We can measure the dimensions of a View that has not been displayed yet
by calling its measure method, and then its getMeasuredHeight method, shown
in TABLE 6.8. If button is a Button reference, we can obtain its height as
follows:
button.measure( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT );
the action bar as shown next. Once we have it, we can easily compute the
height of the app content View. Appendix A provides detailed explanations.
))
actionBarHeight = TypedValue.complexToDimensionPixelSize( tv.data,
getResources( ).getDisplayMetrics( )
);
TABLE 6.8 The measure and getMeasuredHeight methods of the View class
public void measure( int The two parameters are dimensional constraint
widthMeasureSpec, int information supplied by the parent of this View.
heightMeasureSpec )
In Example 6.1, we learned how to retrieve the height of the screen minus
the status bar. Thus, to compute the height of the content view, we do the
following:
Retrieve the height of the screen minus the status bar: appScreenHeight
Retrieve the height of the action bar: actionBarHeight
that we only set all these variables once and not every time the device
changes orientation. Some of these variables are declared as public and
static so they can be easily accessed from other activities if necessary—
most likely, if we have other activities in the app, we will need to access
these values in other activities for the same reason that we need to access
them in this activity, and we do not want to rewrite the same code to retrieve
them.
EXAMPLE 6.11 Managing orientation by code only
The onCreate method (lines 31–40) calls setUpGui at line 33. The setUpGui
method is coded at lines 70–94: it sets up the layout manager for the View,
creates the buttons, and adds them to the View. In order to retrieve the
action bar height, the screen dimensions, and to calculate the space between
the buttons, we call a separate method, checkDimensions , at line 38. At line
39, we call modifyLayout , which displays the buttons properly depending on
the orientation of the device. Since we do not know what orientation the
device is in when the app starts, we assign the current configuration to the
variable config at line 37 and pass config to checkDimensions and
modifyLayout .
parameter, the space between two buttons. Depending on the position of the
device, we pass a different value for that parameter when calling that
method.
The checkDimensions method (lines 42–68) retrieves the action bar height and
assigns values to the various dimension-related instance variables. We only
want to do that twice: when the app starts, and after the user has rotated
the device the first time. Thus, inside onConfigurationChanged , we only call
checkDimensions if either verticalDimensionsSet or horizontalDimensionsSet
is false (line 98). The checkDimensions method retrieves the height of the
action bar, and the height of the screen in both orientations. We assign them
to the instance variables actionBarHeight , screenHeightInHP, and
screenHeightInVP . We also retrieve the height of the buttons (lines 52–54)
and calculate the values of spacingInHP and spacingInVP at lines 59–60 and
64–65. We output the value of the action bar height at line 50 for feedback
purposes. FIGURE 6.9 shows the output after we start the app in vertical
orientation and rotate the device. The height of the action bar is 168 pixels
(56 dp) and 144 pixels (48 dp) in vertical and horizontal orientations,
respectively.
Earlier in the chapter, we showed that the height of the screen, not including
the status bar, is 1,704 pixels (568 dp × 3) and 1,008 pixels (336 dp × 3) in
vertical and horizontal orientations, respectively. Thus, the height of the app
content view for our emulator is 1,536 pixels (1,704 – 168) in vertical
orientation, and 864 pixels (1,008 – 144) in horizontal orientation as shown in
FIGURES 6.10 and 6.11.
rotates the device. Currently, the action bar height is the same in both
orientations. If this changes in the future, the code in this Example will need
to be modified. At line 100, we call modifyLayout so that the buttons are
displayed according to the device orientation.
each button. Remember that the params1 , params2 , and params3 are object
references, so when we modify them, we modify the layout parameters of
the three buttons.
androidManifest.xml file:
<activity
android:name=”.MainActivity”
android:configChanges=”orientation|screenSize” >
If we run the app and rotate the device, the vertical and horizontal Views look
like the ones in Figures 6.6 and 6.7, with the buttons evenly spaced in both
orientations.
At the time of this writing, the height of the status bar is 24 dp in both
orientations. If necessary, it can be retrieved by code as follows (Appendix A
provides detailed explanations):
int resourceId =
/* Assume that the TextView label has been instantiated and has
20. Write the code to set the margins of a button to 30 pixels on the left and
50 pixels on the right.
/* Assume that the Button myButton has been instantiated and has
MarginLayoutParams params =
( MarginLayoutParams ) myButton.getLayoutParams( );
Write an App
21. Write an app that works in both vertical and horizontal orientations using
two layout XML files: the app asks the user to enter the result of an
addition and checks the answer. Use randomly generated integers
between 0 and 20 for the operands of the addition. Include a Model. The
GUI should look nice in both orientations.
22. Write an app that works in both vertical and horizontal orientations. The
app is a variety of the game of nim. Two players take turns removing
identical objects from a set of objects. A player can remove one, two, or
three objects at a time. The player who takes the last object loses. You
can store the current number of objects in a TextView and get user input
via an EditText. Include a Model. Generate randomly the starting number
of objects, an integer between 10 and 20. The GUI should look nice in
both orientations.
23. Write an app that works in both vertical and horizontal orientations using
two layout XML files. The app is a simple calculator: the user can click
on buttons showing digits from 1 to 9, click on buttons displaying +, -,
and *, and the app shows the result. Include a Model. The GUI should
look nice in both orientations. Use one color theme for an orientation,
and another color theme for the other orientation.
24. Write an app that works in both vertical and horizontal orientations using
two layout XML files. The app is a tip calculator: the user can enter a
restaurant bill, a number of guests, and a tip percentage, and the app
shows the total tip, the total amount, the tip per guest, and the total per
guest. Include a Model. The GUI should look nice in both orientations.
25. Write an app that works in both vertical and horizontal orientations. The
app is a tic-tac-toe game. Include a Model. The GUI should look nice in
both orientations. Use Xs and Os in one orientation, and As and Zs in
the other orientation.
26. Write an app that works in both vertical and horizontal orientations. The
app displays a chessboard in its starting position. Include a Model. The
GUI should look nice in both orientations. Use black and white colors in
one orientation, and two other colors in the other orientation. In vertical
position, position the chessboard at the top of the screen, using the full
width of the screen. In horizontal position, position the chessboard in the
middle of the screen, using the full height of the screen. Use chars to
represent the various pieces ( K for King, Q for Queen, etc.).
CHAPTER SEVEN: Touches and
Swipes
Chapter Summary
Introduction
Many mobile games use screen touching, tapping, or swiping as a way to
interact with the user. In most map apps, the user can zoom in and out of the
map by doing some gesture such as pinching or spreading his or her fingers.
All these things are events and follow the same rules as event handling in
general:
In this chapter, we learn how to detect and handle touches, swipes, and
taps. We also build a simple puzzle app that lets the user move a piece of
the puzzle by touching it and dragging it to another position in the puzzle.
FIGURE 7.1 shows the puzzle, which uses five TextViews whose order has
been scrambled and need to be placed in the correct order.
FIGURE 7.1 The Puzzle app, Version 2, running inside the emulator
We can define that class as a private class of the current activity class:
events
v.setOnTouchListener( th );
View.OnTouchListener {
v.setOnTouchListener( this );
Method Description
boolean Called when a touch event occurs. v is the View where the event occurred
onTouch( (assuming that this listener is registered on v). If this method returns true, the event
View v, is consumed. If it returns false, the event is propagated to the Views that are
MotionEvent underneath v in the View stack.
event )
TABLE 7.2 The setOnTouchListener method of the View class
Method Description
void setOnTouchListener( Registers listener on this View. When a touch event is sent to this
View.OnTouchListener listener ) View, the onTouch method of listener will be called.
The first parameter of the onTouch method is a View where the event
occurred, assuming that the listener is registered on that View . The second
parameter is a MotionEvent reference. It contains information about the
event. When the user touches the screen, moves his or her finger, and then
lifts it up, there is actually a series of events that happen in sequence. The
onTouch method is called many times, each time with a different value for the
event parameter. This allows us to test that event parameter and process
case MotionEvent.SOME_OTHER_ACTION:
// some other action happened; process that information
break;
...
}
...
}
Method Description
float getRawX( ) Returns the x-coordinate of the touch within the screen.
float getRawY( ) Returns the y-coordinate of the touch within the screen.
float getX( ) Returns the x-coordinate of the touch within the View where it happened.
float getY( ) Returns the y-coordinate of the touch within the View where it happened.
int getAction( ) Returns the type of action that occurred within the touch event.
TABLE 7.4 Selected constants of the MotionEvent class that can be
compared to the return value of the getAction method
Constant Description
interacts with the screen via touches, the onTouch method executes and its
View parameter is the content View .
EXAMPLE 7.1 The MainActivity class, tracking touch events, Touches
app, Version 0
Inside the onTouch method (at lines 22–36), we first retrieve the action
performed at line 23. We compare its value to the ACTION_DOWN , ACTION_MOVE ,
and ACTION_UP constants of the MotionEvent class using a switch construct.
For each case, we output to Logcat information about v , the View
parameter, and event , the MotionEvent parameter.
FIGURE 7.2 Logcat output from Example 7.1
FIGURE 7.2 shows the output in Logcat from Example 7.1 as the user
makes a small swipe somewhere on the screen. We can observe several
things:
▸ The method is called first with a DOWN action, then many times with a
MOVE action, then once with an UP action.
▸ The View parameter v is always the same and it is the content view
for the activity.
▸ The parameter event contains information such as the action, x- and y-
coordinates, time, etc. We can see that each time the method is called
on a MOVE action, the x- and y-coordinate values are different. Together,
they form a discrete set of values that mirrors the swipe. Note that we
do not get continuous, pixel by pixel information on the swipe.
In the first example, we register the touch listener on the content View . In this
second example, we add a TextView to the screen and we move it as the
user touches it and moves his or her finger on the screen. This time, we
register the listener on the TextView . This illustrates the difference between
the getX and getY methods on one hand, and the getRawX and getRawY
methods on the other hand, shown in Table 7.3. The getX and getY methods
return the x- and y-coordinates relative to the View where the event
happened, which is the View v passed to onTouch as the first parameter.
The getRawX and getRawY return the x- and y-coordinates within the screen,
including the screen decorations such as the status bar and the action bar. If
we use the getRawX and getRawY methods and we want the x- and y-
coordinates relative to the content View , we can use the following code
inside onTouch to convert them:
view.getLocationInWindow( location );
the top left corner of the View calling that method within the window. We can
then subtract their values from the values returned by getRawX and getRawY
to obtain the relative x- and y-coordinates of the touch event within the
content view of the activity.
By contrast, if the listener has been registered on a TextView and the first
touch is at the top left corner of the TextView , then the View v parameter of
the onTouch method is that TextView , and the getX and getY method return
0 and 0 (or close to it) independently of where the TextView is located on
the screen.
EXAMPLE 7.2 shows the MainActivity class. We build the GUI by code and
include one TextView in it. We want to move that TextView based on user
touches, so we need to use absolute coordinates. The AbsoluteLayout class,
originally intended to work with absolute coordinates, is deprecated. We use
the RelativeLayout class instead. A RelativeLayout allows us to position
components either relative to each other or using absolute coordinates. Each
component’s dimension and position can be defined and controlled inside that
RelativeLayout by associating it with it a RelativeLayout.LayoutParams object
We add more instance variables at lines 14–17 to keep track of the original
position of the TextView and the location of the touch when the swipe starts.
We use them inside the onTouch methods to recalculate the left corner
position of the TextView as the user moves his or her finger on the screen.
Inside the onTouch method (lines 39–55), we initialize these four variables
when the user first touches the screen (lines 43–46), which is captured by
the DOWN action (line 42). When the user moves his or her finger ( MOVE action,
line 48), we update the leftMargin and topMargin parameters of params at
lines 49–50 and update the position of tv accordingly at line 51 by calling the
setLayoutParams method, shown in TABLE 7.6, passing the updated value of
params . There is nothing to do when the user lifts up his or her finger from
Method Description
Method Description
Note that in this example, we register the listener on tv at line 36, and not on
the content view. Thus, the calls to onTouch are triggered only when the
touch starts inside tv , but no call to onTouch is triggered when the touch
starts outside tv .
As we run this practice app, if we touch the red rectangle and move our
finger, the red rectangle moves along.
Now that we understand how touch events work and how to move a
TextView , we can start building our Puzzle app. As shown in Figure 7.1, it
presents the user with several TextViews that need to be reordered. The first
step is to design our Model, the Puzzle class, which includes the following:
▸ A method returning the number of pieces in the puzzle (i.e., the number
of elements in parts ).
EXAMPLE 7.3 shows the Puzzle class. To keep things simple, we hard code
the contents of the parts array in the default constructor (lines 10–17). We
could envision a more complex model, with a constructor reading a puzzle
data from a file or a database. In this case, we would not know the number
of pieces in the puzzle before we read the data. For this reason, we provide
an accessor for the number of pieces in the puzzle, getNumberOfParts , at
lines 47–49. In this way, whenever the Controller needs to know how many
pieces are in the puzzle, it can call that method, rather than use the constant
NUMBER_PARTS , whose value is hard coded. We could also have many puzzles
The solved method, at lines 19–29, returns true if its parameter array has
the same content as parts, false otherwise. The scramble method, at lines
31–45, returns an array of Strings containing all the elements of the puzzle
but in a different order. To do this, we first create a new array, scrambled , at
line 32, and initialize it with the elements of parts at lines 33–34. At lines 36–
43, we use a while loop to shuffle the elements of scrambled in place until
the scrambled elements are in a different order as compared to the parts
elements. To test for that, we use the solved method (line 36).
EXAMPLE 7.3 The Puzzle class, Puzzle app, Version 0
The for loop at lines 37–42 shuffle the elements of scrambled in place going
from left to right, starting at index 0 . At each iteration of the loop, the
elements between indexes 0 and i – 1 have already been shuffled. The
current iteration of the loop swaps the element at index i with an element at
a random index between i and scrambled.length – 1 . At line 38, we
generate a random index between i and scrambled.length – 1 included.
Then, we swap the element at that index with the element at index i (lines
39–41).
Now that we understand touch events and we have a Model, we can start
building a functional puzzle app. In this section, we build the View part of the
app. To keep things simple, we only allow the app to display in vertical
orientation. in the AndroidManifest.xml file, we specify this as follows:
<activity
android:screenOrientation=”portrait”
Inside the styles.xml file, we set the font size for TextViews to be 32 as
follows:
<item name=”android:textSize”>32sp</item>
EXAMPLE 7.4 shows the PuzzleView class. Rather than extend View and
manage the View with a RelativeLayout inside, we extend RelativeLayout
instead. Since RelativeLayout inherits from View , it is a View and can be
assigned to an Activity as its View . We use the RelativeLayout class to
organize the TextViews because it can manage its children Views using
absolute coordinates.
We build the entire GUI by code using the buildGuiByCode method, coded at
lines 23–41, and called from the PuzzleView constructor at line 20, passing
all the constructor’s parameters to buildGuiByCode . Each component’s
dimension and position can be defined and controlled by associating with it a
RelativeLayout.LayoutParams object when we add it to the RelativeLayout .
▸ tvs , an array of TextViews (line 11). There is one TextView for each
puzzle piece.
The three arrays above are parallel arrays. We use the params array to
position and size the TextViews in tvs , and the colors array to color them.
They are instantiated at lines 25–27. At lines 30–40, we loop through the
elements in the array tvs . Each TextView is colored, positioned, and sized
using the corresponding values in the three arrays above. At lines 33–34, we
generate three random integers between 0 and 255 included and pass them
to the rgb static method of the Color class in order to generate a random
color for the current TextView . Note that we are using the Color class from
the android.graphics package (imported at line 8), not the Color class from
the traditional java.awt package.
We want all the TextViews to have the same height, which we store in
labelHeight . At line 29, we assign labelHeight the height parameter
The fillGui method, coded at lines 43–46, provides code to fill the
TextViews with an array of Strings representing the scrambled pieces of the
puzzle.
puzzleView at line 48. We retrieve the height and width of the screen at lines
20–23. While the width of the screen is also the width of the puzzle, the
height of the screen includes the height of the status bar and the action bar,
which should not be included in the puzzle. At the time of this writing, and
according to Google’s design guidelines, the height of the status bar is 24 dp
and the height of the action bar is 56 dp. Thus, we declare two constants
defining default values for the status bar and action bar heights at lines 11–
12.
At line 42, we subtract the heights of the status bar and the action bar from
the height of the screen in order to compute the height of the puzzle.
At this point, there is no event handling so the user cannot move any piece of
the puzzle, but the app runs and shows the unsolved puzzle, as shown in
Figure 7.1.
We now start building the Controller part of the app, in order to give the app
its functionality. In Version 1, we enable the user to move the various pieces
of the puzzle (i.e., the TextViews ). At this point, we do not care if the pieces
are well positioned and whether the puzzle has been solved.
FIGURE 7.3 The red View is higher than the blue View in the stacking
order. It partially hides the blue View
Whenever the user is moving a piece of the puzzle, we bring that piece on
top of the others so the user can see it at all times. Views are arranged in a
stack using a stacking order. The first View added to a ViewGroup is at the
bottom of the stack and the last View added is at the top of the stack.
FIGURE 7.3 illustrates the stacking order concept. The red View, which was
added after the blue View, is higher in the stacking order and partially hides
the blue View . TABLE 7.7 shows the bringToFront method of the View class,
which we can use to bring a View to the top of the stack so that it is not
hidden by its sibling Views . To force a View named view to be at the top of
the stack, we call the bringToFront method with it as in the following
statement:
view.bringToFront( );
Move the piece of the puzzle as the user moves his or her finger
Method Description
void bringToFront( ) Brings this View to the top of the stack so that it is on top of all its siblings.
All of the preceding involves managing the TextViews in the PuzzleView class.
Thus, we provide methods in the PuzzleView class to perform these actions,
and call these methods from the MainActivity class. EXAMPLE 7.6 shows
the MainActivity class. It implements View.OnTouchListener (lines 12–13)
and overrides the onTouch method at lines 54–70. At line 50, inside the
onCreate method, we call the enableListener method of the PuzzleView class
with puzzleView . That method registers this listener, MainActivity , on all the
TextViews .
within the touch event at line 56. At this point, we only care about the DOWN
and MOVE actions. We do not care about the UP action yet. If it is a DOWN
action (line 58), the user is picking up a piece of the puzzle and is getting
ready to move it. We call updateStartPositions with puzzleView (lines 59–60)
in order to update the y position of the touched TextView and the y position
of the touch (we need these two y values to calculate the new location of the
TextView if the action is MOVE ). We bring the touched TextView to the front at
lines 61–62.
EXAMPLE 7.6 The MainActivity class, Puzzle app, Version 1
When the user moves his or her finger (move touch action, line 64), we call
moveTextViewVertically with puzzleView (lines 65–66) in order to move the
touched TextView , following the user’s finger along the vertical axis.
EXAMPLE 7.7 shows the updated PuzzleView class. It includes all the
methods called by the PuzzleView instance variable in the MainActivity
class: indexOfTextView , tvPosition , enableListener , updateStartPositions ,
and moveTextViewVertically .
but we test for it at line 53. We loop through the elements of tvs and
compare the element at index i to tv (line 56) and return i if they are equal
(line 57). If tv is not a TextView or we do not find it, we return –1 (lines 54
and 59).
TextView for that piece, and startTouchY stores the y-coordinate of the touch
within the TextView . We need both of them to update the y-coordinate of the
TextView on a MOVE touch action.
FIGURE 7.4 shows the puzzle after the user has moved a few pieces.
We now continue to build the Controller part of the app, in order to give the
app its functionality. When the user moves a piece of the puzzle, a TextView ,
and releases it, we swap its position with the piece of the puzzle that is under
it. We consider that a piece of the puzzle is over another one if at least half
of the piece of the puzzle is over the other one, as shown in FIGURE 7.5.
The green piece is more than halfway over the blue piece. If the user
releases it, the green piece will take the blue piece’s place, and the blue
piece will be placed where the green piece was when the touch event
started. After each move, we check if the user has solved the puzzle. If the
user has, we disable the touch events.
FIGURE 7.5 The green piece is closer to the blue piece than the red
piece
Store the y position of the piece of the puzzle touched Store the y
position of the touch
Bring the piece of the puzzle at the top of the stacking order
If the action is MOVE
Move the piece of the puzzle as the user moves his or her finger
If the action is UP
Swap the piece of the puzzle with the piece of the puzzle that is
under it
When the user lifts up his or her finger (up touch action, line 68), we swap
the touched TextView with the TextView currently underneath the touch: we
first call tvPosition with puzzleView at line 70 in order to retrieve the new
position index of the touched TextView , and then call placeTextViewAtPosition
at line 71 with puzzleView in order to swap the two TextViews . Finally, we
check if the user solved the puzzle at line 73. From a Model-View-Controller
perspective, the Controller retrieves the state of the puzzle from the View,
and asks the Model to check if this is the correct ordering of the puzzle. If it
is, the Controller asks the View to disable touch event listening by calling
disableListener at line 74 in order to disable touch listening. In this way, we
The disableListener method (lines 84–87) sets the listener of all the
TextViews to null , effectively disabling touch listening.
yet.
The tvPosition method, coded at lines 89–93, returns the position (0, 1, 2,
3, or 4 in this example) of the array element of tvs at index tvIndex , the
parameter of the method. The expression params[tvIndex].topMargin returns
the y-coordinate of the top of the TextView . We add half the height of a
TextView and then divide by the height of a TextView in order to compute its
FIGURE 7.8 shows the various states of the TextViews and those variables
as the user picks up the I LOVE piece of the puzzle and moves it to where
the PROGRAMMING piece of the puzzle is. At that stage, the user is moving
the TextView at index 4 (the value of tvIndex ) and position 3 (the value of
emptyPosition ) to the position 0 (the value of toPosition ), where the
The tvs and params arrays parallel each other. At line 98, we set the top
margin of the params array element at index tvIndex to the y-coordinate
where that tvs array element is moving to. At line 99, we force a re-layout of
the TextView array element at tvIndex based on the value of its
corresponding params array element, effectively placing the TextView in its
new slot. At lines 101–104, we do the same for the TextView element that
was at position toPosition . It is moving to the position emptyPosition . At
lines 106–108, we update the positions array to reflect that the two
TextViews have been moved.
inside the TextView at position i within the PuzzleView using the expression
tvs[positions[i]].getText( ). Because the getText method returns a
CharSequence , we call toString to convert the returned value to a String .
Method Description
boolean onDown( MotionEvent e ) Called on the DOWN action when a touch occurs.
boolean onFling( MotionEvent e1, Called when a swipe occurs. Velocity is measured in
MotionEvent e2, float velocityX, float pixels per second in both directions.
velocityY )
boolean onScroll( MotionEvent e1, Called when a scroll occurs. Distances are measured
MotionEvent e2, float distanceX, float between this onScroll method call and the previous one.
distanceY )
void onShowPress( MotionEvent e ) Called when the user touches the screen and does not
move or lift up his or her finger.
Method Description
boolean onDoubleTap( Called on the DOWN action when a double tap occurs.
MotionEvent e )
boolean Called on the DOWN, possibly MOVE, and UP actions when a double
onDoubleTapEvent( tap occurs.
MotionEvent e )
boolean Called on the DOWN action when a confirmed single tap occurs (i.e.,
onSingleTapConfirmed( the single tap is not the first tap of a double tap).
MotionEvent e )
TABLE 7.10 Selected methods of the GestureDetector class
Method Description
GestureDetector( Context context, Creates a GestureDetector object for the context and
GestureDetector.OnGestureListener using gestureListener as the listener called for gesture
gestureListener ) events. We must use this constructor from a User-
Interface thread.
boolean onTouchEvent( MotionEvent e Called when a touch event occurs; triggers a call to the
) appropriate callback methods of the
GestureDetector.OnGestureListener interface.
In order to handle a touch event using the GestureDetector class and the
Gesture-Detector.OnDoubleTapListener and
methods.
▸ Set the handler object as the listener handling the tap events (if
necessary).
We can define the handler class as a private class of the current activity
class. The following code sequences would implement the preceding:
GestureDetector.OnDoubleTapListener {
// The GestureAndTapHandler class needs to override all 9 methods
implement them. Then, we would need to override the nine methods inside
the Activity class and we would use this instead of gth in the preceding
code.
interfaces based on the nature of the event that just happened. For some
apps, it is possible that all we need is to execute inside the onTouchEvent
method. For other apps, we may be interested in capturing double taps and,
therefore, we will want to place our code inside the onDoubleTapEvent
method. Or we may be interested in capturing the speed of a swipe and
therefore we will want to place our code inside the onFling method.
Depending on what action we are interested in capturing and processing, we
place our code in the corresponding method. Note that when we override
onTouchEvent , we need to call the onTouchEvent method of the
line 17, passing this as the first and second argument. As the first
argument, this represents the application’s context (an Activity object “is
a” Context object). As the second argument, it represents a
GestureDetector.OnGestureListener ( MainActivity inherits from
onTouchEvent method at line 23, gestures events will trigger calls to some of
onTouchEvent method at line 23, tap events will trigger calls to some of the
The onTouchEvent method is coded at lines 21–25. Detector calls its own
onTouchEvent method at line 23, passing the MotionEvent event , setting up
the dispatching discussed previously. All the nine overridden methods of the
two listeners interface output a feedback statement to Logcat.
FIGURE 7.10 Possible Logcat output when the user taps on the screen
once, Touches app, Version 2
FIGURE 7.10 shows the output inside Logcat of Example 7.10 when the
user taps on the screen once on the emulator. It shows that the onTouchEvent
method triggers calls to the onDown , onSingleTapUp , and
onSingleTapConfirmed , successively. When onSingleTapUp is called, we do not
know yet that this was a single tap event, because a double tap event would
also trigger a call to onSingleTapUp . Thus, if we want to execute some code
based on a single tap event only, we should place that code inside the
onSingleTapConfirmed method.
FIGURE 7.11 Possible Logcat output when the user double taps on the
screen, Touches app, Version 2
FIGURE 7.12 Possible Logcat output when the user swipes the screen,
Touches app, Version 2
FIGURE 7.11 shows the output inside Logcat of Example 7.10 when the
user taps on the screen twice quickly (i.e., double taps on the emulator). It
shows that the onTouchEvent method triggers calls to the onDown ,
onSingleTapUp , onDoubleTap , onDoubleTapEvent , and the onDown and
onDoubleTapEvent again. It is possible that we get a slightly different output,
action and the second time on an UP action. It can even be called three
times, if a (slight) MOVE action is detected in between the DOWN and UP
actions.
FIGURE 7.12 shows a possible output inside Logcat of Example 7.10 when
the user swipes the screen. The onScroll method is called several times, at
various points within the swipe. The onFling method is called once and last.
It is possible that we get a slightly different output depending on the swipe or
if we use the emulator and the computer mouse.
Once we have decided what method we want to execute in, we can use its
parameter or parameters to retrieve values relative to the action that is
taking place. For example, if we want to capture the point of origin and the
velocity of a swipe, we can place our code inside the onFling method and
retrieve these values as shown in EXAMPLE 7.11.
As we run the modified app and swipe the screen, the output looks like the
one in FIGURE 7.13.
EXAMPLE 7.11 Expanded onFling method within the MainActivity class,
Touches app, Version 3
FIGURE 7.13 Logcat output when the user swipes the screen, Touches
app, Version 3
We update the Puzzle class, our Model, and add two methods to it: one that
returns the word to be changed, and one that returns the replacement word.
We keep this change as simple as possible in order to focus more on the
Controller and View parts of the app. EXAMPLE 7.12 shows these two
methods.
EXAMPLE 7.13 shows the new parts of the MainActivity class. The private
class DoubleTapHandler , coded at lines 93–106, only overrides the
onDoubleTapEvent method because we are only interested in processing a
double tap event. In it, we retrieve the index of the TextView that the user
double tapped on, check if its label is MOBILE, and change it to ANDROID if
it is. We first retrieve the y-coordinate of the double tap within the PuzzleView
at line 96. We then call the indexOfTextView method (which we add to the
PuzzleView class) of PuzzleView to obtain the index of the TextView where
the double tap happened (lines 99–100). Because we need to subtract the
heights of the status bar and action bar from the y-coordinate of the double
tap, we declare statusBarHeight and actionBarHeight as instance variables
at lines 19–20. In this way, we do not have to compute their value twice.
EXAMPLE 7.13 Additions to the MainActivity class, Puzzle app, Version
3
At lines 101–102, we retrieve the text inside that TextView by calling the
getTextViewText method (which we also add to the PuzzleView class) and
test if it matches the word to change in the Puzzle class. If it does, we call
the setTextViewText method (which we also add to the PuzzleView class) to
change the text of the TextView to the new word retrieved from the Puzzle
class. From the Model-View-Controller perspective, the Controller asks the
View to retrieve the text of the TextView and compares it to the word to
change retrieved from the Model. If they match, the Controller asks the
Model for the replacement word and tells the view to place it inside the
TextView at line 103.
At line 57, we declare and instantiate dth , a DoubleTapHandler object. The
instance variable detector , a GestureDetector , is declared at line 21 and
instantiated at line 58. The second argument passed to the constructor is
dth . That means that the gesture-related methods of the
EXAMPLE 7.14 shows the three methods added to the PuzzleView class.
Inside the indexOfTextView method (lines 120–124), we divide the y -
coordinate of the tap by the height of a TextView and assign it to position at
line 122. The index of the TextView located at position is positions[position].
We return it at line 123.
If we run the app and double tap on MOBILE after we solve the puzzle, it
changes to ANDROID. Note that if we double tap on either I LOVE,
PROGRAMMING, USING, or JAVA, nothing happens. The onTouch method,
which is part of the MainActivity class and is shown at lines 54–78 of
Example 7.8, returns true . That means that we stop propagating the touch
event. As we run the app, if we double tap on MOBILE before the puzzle is
solved, nothing happens because the onDoubleTapEvent method does not
execute. Once the puzzle is solved, we disable listening on the TextViews (at
line 74 of Example 7.8). There is no longer a touch listener registered on the
TextViews , so the touch event’s target is now the content View for the
FIGURE 7.14 The Puzzle app, Version 3, after the user solves the
puzzle and double taps on MOBILE
When we publish an app, we do not know what device it will run on, in
particular what the screen width and height dimensions will be. In Version 4,
we use our DynamicFontSizing class in EXAMPLE 7.15 so that the size of the
font is optimal for the device on which the app runs. Given a TextView , the
setFontSizeToFitInView static method (lines 11–32) sizes the text font
inside the TextView so that it is maximal and the text fits on one line—it
returns that maximal font size. Appendix B explains in detail the
DynamicSizing class.
EXAMPLE 7.16 shows the updated fillGui method, the only change in the
PuzzleView class, in addition to importing the TypedValue class from the
android.util package. We optimize the font size for the TextViews in the
We want to use the same font size for all the TextViews . However, the text
inside each TextView is different. Thus, we compute the minimum font size
returned by all the calls to setFontSizeToFitInView for each TextView . After
initializing minFontSize at line 51 with MAX_FONT_SIZE , we update it as
necessary at lines 59–62. Once minFontSize is set, we loop again through all
the TextViews and reset their font size with minFontSize at lines 65–67. After
this method executes, all the TextViews have the same font size, and it is the
maximal font size so that their text contents fit on one line.
Before we compute the font size for each TextView , we make sure that their
width is equal to the width of the screen at line 56. We give some padding to
each TextView at line 57 so that there is space around the text. The font size
ends up being 46 when running on the Nexus 5 emulator.
FIGURE 7.15 shows the Puzzle app, Version 4, running inside the emulator.
PROGRAMMING fills up almost the whole TextView .
FIGURE 7.15 The Puzzle app, Version 4, running inside the emulator
Chapter Summary
We can use the View.OnTouchListener interface to capture and handle a
touch event.
To register a View.OnTouchListener on a GUI component, we can use the
setOnTouchListener method of the View class.
order. The View added first is at the bottom of the stack, whereas the
View added last is at the top of the stack.
We can use the bringToFront method to bring a View to the top of the
stack in order to guarantee that that View is not hidden by other Views .
We can use the GestureDetector class, along with its static inner
interfaces, GestureDetector.OnGestureListener and
GestureDetector.OnDoubleTapListener , to capture and handle gesture and
tap events.
Inside the onTouchEvent method of the activity class, a GestureDetector
object is expected to call its onTouchEvent method to set up the
dispatching of the touch event to the appropriate method of the handler
class that implements the GestureDetector.OnGestureListener and/or
GestureDetector.OnDoubleTapListener interfaces.
methods.
Exercises, Problems, and Projects
Multiple-Choice Exercises
variable y2 .
13. Write the code to place a View named view at the top of the stack it
belongs to.
14. We have already coded a class named MyHandler that extends
View.OnTouchListener. Write the code to use an object of that class so
that we can listen to touch events occurring inside a View named
myView.
15. Write the class header of a class named MyActivity that inherits from
Activity, GestureDetector.OnGestureListener, and
GestureDetector.OnDoubleTapListener.
16. Write a private class that inherits from
GestureDetector.SimpleOnGestureListener. We only want to process
single tap events. Every time there is a single tap, we color the
background of a View named myView with a random color.
17. Write a private class that inherits from
GestureDetector.SimpleOnGestureListener. We want to count the
number of taps and accumulate them in an instance variable of an
Activity class named total, which has already been initialized. Every time
there is a tap, total goes up by 1.
The following applies to Questions 18 and 19:
The MyActivity class extends Activity and implements
GestureDetector.OnGestureListener and
GestureDetector.OnDoubleTapListener. We have declared an
instance variable of type GestureDetector named d as follows:
private GestureDetector d;
18. We are coding inside the onCreate method of an Activity class. Write the
code so that the current Activity will handle the gestures and tap events.
protected void onCreate( Bundle savedInstanceState ){
super.onCreate( savedInstanceState );
Write an app
8.1 Graphics
Chapter Summary
Introduction
The Android development framework provides us with a set of classes for
drawing shapes and bitmaps, animating those shapes and bitmaps, and
making sounds. In this chapter, we build a simple game where the user
shoots from a cannon at a duck flying across the screen. We learn how to
draw basic shapes such as a line, a circle, or a rectangle; drawing a bitmap
from a file; playing a sound; and how to capture and respond to touch
events. We also learn how to refresh the screen at a given frequency so we
can animate objects on the screen.
8.1 Graphics
The android.graphics package contains classes to draw and paint. TABLE
8.1 shows some of its selected classes.
...
public void onDraw( Canvas canvas ) {
super.onDraw( canvas );
}
...
TABLE 8.1 Selected classes of the android.graphics package
Class Description
Color Defines constants and methods for defining colors represented by integers.
Method Description
void setARGB( Sets the alpha, red, green, and blue color components of this Paint object to
int a, int r, int g, values a, r, g, and b (all between 0 and 255).
int b )
void setColor( int Sets the color of this Paint object to color (including the alpha component).
color )
void setTextSize( Sets the text size of this Paint object to textSize.
float textSize )
void setStyle( Sets the style of this Paint object to style. Paint.Style is an enum whose
Paint.Style style ) possible values are STROKE, FILL, and FILL_AND_STROKE. The default is
FILL.
void setAntiAlias( Sets this Paint to use anti-alias when drawing shapes if flag is true, default is
boolean flag ) false.
To specify how we draw (i.e., define the drawing style and color), we use the
Paint class. TABLE 8.2 shows some of its methods.
For example, to define a Paint object and set its color to red and its stroke
width to 5 pixels, we can use the following code sequence:
paint.setColor( 0xFFFF0000 );
paint.setStrokeWidth( 5.0f );
To draw, we use the Canvas class. It provides the tools to draw basic shapes
and bitmaps previously generated, for example, from a file containing a
picture, such as a jpeg. TABLE 8.3 shows some of its methods.
The default style for Paint is that shapes are filled when they are drawn. If
we do not want shapes to be filled, we can set the style of the Paint object
to STROKE (see Table 8.2) as in the following statement:
Method Description
void drawLine( float startX, Draws a line between the points (startX, startY) and (endX, endY)
float startY, float endX, float using the style and color defined in paint.
endY, Paint paint )
void drawLines ( float [ ] Draws lines defined in points using the style and color defined in
points, Paint paint ) paint. Each line is defined as four consecutive values in points.
void drawOval( RectF rect, Draws an oval within the rectangle rect using the style and color
Paint paint ) defined in paint. RectF is similar to Rect, but uses floats instead of
ints.
void drawCircle( float Draws a circle whose center is the point (centerX, centerY) and
centerX, float centerY, float whose radius is radius using the style and color defined in paint.
radius, Paint paint )
void drawPicture( Picture Draws picture and stretches it to fit into the dst rectangle.
picture, Rect dst )
void drawBitmap( Bitmap Draws the src rectangle from bitmap inside the dst rectangle using
bitmap, Rect src, Rect dst, paint. If src is null, then the whole bitmap is drawn. The bitmap is
Paint paint ) drawn to the dimensions of dst.
void drawRect( Rect rect, Draws the rectangle rect using the style and color defined in paint.
Paint paint )
void drawText( String text, Draws the String text starting at coordinates (x, y) using the style,
float x, float y, Paint paint ) including alignment, and color defined in paint.
We can also use the Canvas class to draw some graphics from a file. If we
have a file named duck.png , we should place that file in the drawable
directory of our project. Once a file is in the drawable directory, it is a
resource that we can refer to using R.drawable.nameOfFile . In this case, we
can refer to it using R.drawable.duck . Note that we do not include the file
extension.
First, we instantiate a Bitmap object for that file. Then, we draw it with the
canvas parameter of the onDraw method. In order to create a Bitmap , we can
use one of the many static methods of the BitmapFactory class. TABLE 8.4
lists some of them. Once we have a Bitmap reference, we can retrieve or set
some of its characteristics with various methods of the Bitmap class. TABLE
8.5 lists some of them.
R.drawable.duck );
Rect rect = new Rect( 20, 50, 20 + duck.getWidth( ), 50 +
duck.getHeight( ) );
Note that the last two parameters of the Rect constructor used previously
are the right and bottom edges of the rectangle, not its width and height.
Method Description
static Bitmap decodeResource( Creates and returns a Bitmap from the resource whose id is id
Resources res, int id ) that is contained in the Resources object res.
static Bitmap decodeFile( String Creates and returns a Bitmap from the file pathName.
pathName )
static Bitmap decodeStream( Reads is and creates and returns a Bitmap from it.
InputStream is )
Method Description
int getPixel( Returns the color, as an integer, at the (x, y) coordinate of this Bitmap; x and y must
int x, int y ) be greater than or equal to 0 and less than the width and the height of this Bitmap,
respectively.
void setPixel( Sets to color the color of the pixel of this Bitmap located at the (x, y) coordinate.
int x, int y, int The same constraints as in the getPixel method apply.
color )
We use the Empty Activity template for this app. In Version 0, we display the
cannon on the lower left corner of the screen and a duck at the top right
corner of the screen. As in many games, and to keep the example simple,
we only allow the game to be played in horizontal position. Thus, we add the
following inside the activity element of the AndroidManifest.xml file:
android:screenOrientation=”landscape”
To bring a jpeg or png file into our project, we copy it and paste it in the
drawable directory. The name of our file is duck.png .
Constructor Description
View( Context context ) Use this constructor when creating a View by code.
View( Context context, Called when constructing a View by inflating an xml resource; attrs are
AttributeSet attrs ) attributes that are specified in the xml file.
View( Context context, Called when constructing a View by inflating an xml resource; attrs are
AttributeSet attrs, int attributes that are specified in the xml file, and defStyle is the default
defStyle ) style to be applied to the View created.
Method Description
void onFinishInflate( ) Called after a View has finished inflating from its xml resource.
void onMeasure( int Called to measure this View and its contents in order to determine the
widthMeasuredSpec, int width and height of this View.
heightMeasuredSpec )
void onSizeChanged( int Called when the size of this View is changed.
width, int height, int
oldWidth, int oldHeight )
void onLayout( boolean Called when this View assigns positions and dimensions to its children.
changed, int left, int top, The parameters relate to this View relative to its parent.
int right, int bottom )
void onDraw( Canvas Called when the View draws its contents.
canvas )
When a View is set as the content View of an activity, the following methods
of the View class are automatically called, several times for some:
onAttachedToWindow , onMeasure , onSizeChanged , onLayout , onDraw ;
Inside onMeasure
Inside onMeasure
Inside onSizeChanged
Inside onLayout
Inside onMeasure
Inside onLayout
Inside onDraw
EXAMPLE 8.1 shows the GameView class, which inherits from View . Later in
this chapter, we expect the onDraw method to be called often as we redraw
the screen at a high frequency in order to update the game. Thus, we want
to avoid declaring variables inside onDraw so that our code is as efficient as
possible. At lines 12–16, we declare various instance variables to store
resources for the duck: a Paint object to define styles and colors for
drawing, a Bitmap for the duck ( duck ), a rectangle in which we draw the
duck within the View ( duckRect ), and the height of the screen.
Thus,
We assign the preceding value to the scale variable at line 23. Be careful
when performing floating point division: we want the rounding to an integer
dimension to take place at the very end in order to minimize the loss of
precision.
EXAMPLE 8.1 The GameView class, Duck Hunting app, Version 0
We initialize paint at line 27, set its color to black at line 28, the anti-alias
option to true at line 29, and the stroke width of paint to 10 at line 30.
After calling its super method at line 34, we successively draw the cannon
base at lines 35–36, draw the cannon barrel at lines 38–39, and draw the
duck at lines 41–42. We draw the cannon using the drawCircle method of
the Canvas class. The first two parameters are the x- and y-coordinates of
the center of the circle, the third parameter is the radius of the circle, and the
fourth parameter is a Paint object defining the style and color of the circle.
To draw a circle whose center is ( 0 , height ) and whose radius is 50 , we
use this statement:
The coordinate ( 0 , height ) denotes the bottom left corner of the View. The
style attributes of paint are used to draw the circle. Because of the position
of its center, only the top right quarter of the circle is visible.
We actually want to size the radius (50 above) relative to the size of the
View. We use a 10% ratio for this. Thus, we draw the cannon base at line 36
using this statement:
Note that since we only draw a quarter of a circle, we could use the drawArc
method instead. It has the following API:
public RectF( float left, float top, float right, float bottom )
The two angle parameters of drawArc are in degrees: startAngle specifies
the starting angle: 0 specifies three o’clock. If sweepAngle is positive, the arc
is drawn clockwise, otherwise it is drawn counterclockwise. If useCenter is
true , we draw a wedge, going through the center of the oval (the center of
the circle if the RectF argument is a square). Here is how we could draw the
cannon base using drawArc :
At line 39, we draw the cannon barrel using the drawLine method of the
Canvas class. The first two parameters are the x- and y-coordinates of one
end of the line, and the next two parameters are the x- and y-coordinates of
the other end of the line. The fifth parameter is a Paint object defining the
style and color of the line. If we want to draw a line whose end points are
( 0 , height ) and ( 100 , height – 100 ) so that it is at a 45-degree angle, we
use this statement:
Again, we want to size the length of the cannon barrel relative to the size of
the View. We use a 20% ratio for this (note that the 20% ratio is along the x-
and y-axis whereas the cannon barrel is at a 45-degree angle and therefore
longer. Part of the barrel is hidden by the cannon base). Thus, we draw the
cannon barrel at line 39 using this statement:
At line 42, we draw the duck using one of the drawBitmap methods of the
Canvas class. The first parameter is a Bitmap reference, the second
parameter defines the rectangle to draw within the bitmap, the third
parameter defines the rectangle where to perform the drawing on the View,
and the fourth parameter is a Paint object defining the style and color of the
drawn bitmap. We draw the entire Bitmap duck within the rectangle duckRect ,
using this statement:
The value null for the second argument means that we are drawing the
entire bitmap. The duck is drawn within the rectangle represented by the
third argument, duckRect , defined inside the onLayout method.
bar and is a little bigger. Instead of inflating an XML resource, it sets its
content View to a GameView . At line 10, we declare a GameView instance
variable, gameView . It is instantiated inside onCreate at line 26. We set the
content View of the activity to gameView at line 27.
We retrieve the size of the screen at lines 24–25 in order to pass its width
and height to the GameView constructor. However, the height of the screen
includes the height of the status bar. Thus, we retrieve it at lines 16–22 and
subtract it from the height of the screen at line 26 and pass the resulting
value to the GameView constructor. Appendix A explains in greater detail how
to retrieve the heights of the status and action bars.
FIGURE 8.1 shows the Duck Hunting Game app, Version 0, running inside
the emulator. The circle we drew for the cannon is smooth without ragged
edges, as a result of using the anti-alias feature.
In a game, there can be many shapes and objects on the screen, moving,
changing, and colliding. The functionality of a game can be quite complex and
it is easier and cleaner to encapsulate it inside a Model. Our Model will
reflect the state of the game and its rules. We have three objects in this
game: the cannon, the bullet, and the duck. In order to keep things simple,
the user can only shoot one bullet at a time from the cannon. If the bullet hits
the duck or goes outside the screen, then the user can shoot again. Thus,
there is either no or one bullet on the screen at all times. We can define the
state of the game as follows:
The values we use for drawing purposes inside the GameView class, such as
the position of the duck and the cannon, as well as the dimensions of the
game, should be retrieved from the Model.
We also need some functionality to capture what happens during the game:
A lot of the accessor methods are needed because when we need to redraw
the corresponding View , we need to access the data related to the various
objects that we draw. The setCannon method (lines 90–102) sets not only the
position of the cannon, but also the original position of the bullet inside the
cannon.
From lines 137 to 199, we code the various methods that enable us to
change the state of the game, move the duck, move the bullet, test if the
duck or the bullet has gone outside the screen (outside the hunting rectangle
in this Model), reload the bullet in its original position, change the angle at
which the bullet is fired, and test if the bullet hits the duck. The instance
variable duckShot represents whether the duck has been shot. The method
duckHit tests if the bullet hit the duck. If the duck is not shot, the moveDuck
method (lines 161–169) moves the duck from right to left. If the duck is shot,
it moves it down at five times its flying speed.
EXAMPLE 8.3 The Game class, Duck Hunting app, Version 1
TABLE 8.7 Selected intersects method of the Rect class
Method Description
boolean intersects( int left, Returns true if this Rect intersects the rectangle defined by the four
int top, int right, int bottom ) parameters, false if it does not. This Rect is not modified.
In order to test if the bullet hit the duck (method duckHit at lines 195–199),
we compare the rectangle around the duck to the rectangle around the bullet
and test if they intersect. We use one of the intersects methods of the Rect
class, shown in TABLE 8.7. It returns true if the rectangle defined by its four
parameters intersects with the Rect reference calling the method, false if it
does not.
Now we can use the Model to make the duck fly across the screen from right
to left. In order to do that, we need to redraw the View at a certain
frequency. The human eye thinks that things move in continuous motion when
the frame rate is at 20–30 frames per second or more. The higher the frame
rate, the more things will look like they move in continuous motion. However,
the higher the frame rate, the more demand there is on the CPU. If we ask
the CPU to do too much in too short a period of time, movement will start to
be jerky and the quality of our app will deteriorate. Furthermore, the CPU
speed varies from device to device, which complicates things further. For
complex games with a lot of objects moving and interacting with each other, it
is recommended to use OpenGL (Open Graphics Library) so that drawings
are rendered faster. OpenGL defines an API so that developers can interact
directly with the graphics processor. Often, game developers use a game
engine, which uses OpenGL, in order to develop a game. High performance
game programming is beyond the scope of this book. We keep our game
simple so that the CPU can handle it.
TABLE 8.8 Selected methods of the Timer class
Method Description
void schedule( TimerTask task, long Schedules task to run after delay milliseconds and every
delay, long period ) period milliseconds afterward.
Method Description
void run( ) Called automatically with the specified frequency; override this method and execute
the task here.
The Timer class, from the java.util package, provides several schedule
and scheduleAtFixedRate methods to set up a given task, of type TimerTask ,
to be performed either once or at a specified frequency, starting after a
specified delay. We can also cancel tasks that are currently scheduled by a
Timer . TABLE 8.8 shows a schedule method and the cancel method.
We define a GameTimerTask class that extends the TimerTask class. Inside the
run method, we update the state of the game using the Model. Before
exiting the run method, we force the View to be redrawn so that what the
user sees on the screen reflects the state of the game at that time. For this,
we need a reference to both the game and the View inside the
GameTimerTask . There are several methods that can be used to redraw a
View. TABLE 8.10 lists some of them. The methods with four parameters
enable us to redraw the rectangle of the View defined by these four
parameters. If we know that only part of the View changes, this can save
valuable CPU time. The postInvalidate method of the View class
automatically calls onDraw and can be called from a non-user interface
thread, which is the case when we execute inside the run method of the
GameTimerTask . Thus, at the end of the run method, we call postInvalidate in
TABLE 8.10 Methods of the View class that force a call to onDraw
Method Description
void invalidate( ) Refreshes this whole View by calling onDraw. This method must be
called from a user-interface thread.
void postInvalidate( ) Refreshes this whole View by calling onDraw. This method can be called
from outside a user-interface thread.
void invalidate( int top, Refreshes the rectangle within this View defined by the four parameters
int left, int right, int by calling onDraw. This method must be called from a user-interface
bottom ) thread.
void postInvalidate( int Refreshes the rectangle within this View defined by the four parameters
top, int left, int right, int by calling onDraw. This method can be called from outside a user-
bottom ) interface thread.
// Start the task now, run it 10 times per second (every 100
milliseconds)
timer.schedule( new GameTimerTask( this ), 0, 100 );
To summarize:
▸ The run method of the GameTimerTask class asks the Model to update
the state of the game, and then asks the game View to redraw itself.
▸ The onDraw method of the GameView class updates the View based on
the state of the game.
EXAMPLE 8.4 The GameTimerTask class, Duck Hunting app, Version 1
GameView .
but we could easily add more frames to improve the duck animation. Instead
of one Bitmap , we now have an array of Bitmaps , ducks , declared at line 17.
The instance variable duckFrame (line 18) stores the current index within the
array ducks . We use it to access the correct Bitmap to draw inside the
onDraw method. We declare a Game instance variable, game , at line 20.
Usually, we include a reference to the Model inside the Controller (i.e., the
MainActivity class). In this case, we set several parameters of what we
draw inside the onDraw method (cannon base and barrel, duck, bullet) based
on values from the Model. Thus, it is convenient to have a Game reference
inside GameView so that we can access the values of the various instance
variables of Game inside the onDraw method.
EXAMPLE 8.5 The GameView class, Duck Hunting app, Version 1
The array ducks parallels the array TARGETS and is instantiated at line 24 and
filled with Bitmaps generated from the elements in TARGETS at lines 25–27.
We assume that all the png files have the same width and height and use the
first one to construct a Rect at lines 29–30, after computing the scaling
factor at line 28. We pass that Rect to the Game constructor at line 31 when
we instantiate game . We also want to set the duck and bullet speeds
dynamically. Indeed, the duck and the bullet should move faster if the screen
has higher resolution. If the screen is 1,000 pixels wide, then the duck speed
is .03 pixel per second and the bullet speed is 10 times faster, .3 pixel per
second. After setting the game’s hunting rectangle at line 36, we call
setCannon at lines 37–38 in order to set the dimensions of the cannon: we
position the cannon at the left bottom corner of the hunting rectangle and size
it relative to the size of the View in order to be as device-independent as
possible.
We want our screen to be refreshed at about 10 times per second for this
game. Thus, we define the constant DELTA_TIME at line 13 and assign to it the
value 100 . We pass that value to game at line 34 and set the dimension of the
cannon barrel at line 43.
The onDraw method is coded at lines 46–64. We draw the cannon at lines
48–50, retrieving its center coordinates and radius from game . We draw the
cannon barrel at lines 52–59, also retrieving its coordinates, dimensions, and
angle from game . Before drawing the Bitmap for the duck at line 63, we
update the value of duckFrame at line 62, so that we access the next Bitmap
in the array ducks .
FIGURE 8.2 shows the Duck Hunting Game app, Version 1, running inside
the emulator, with the duck flying in the middle of the screen. Note that the
emulator has significantly less power than an actual device, and it is possible
that it would not be able to draw the ducks at the specified frame rate.
FIGURE 8.2 Duck Hunting Game app, Version 1, running inside the
emulator
In Version 2, we allow the user to move the cannon barrel using a single tap
or swiping the screen. Wherever the user touches the screen, we point the
cannon barrel toward that point. We also enable shooting by double tapping
anywhere on the screen. However, we do not want a double tap to change
the angle of the cannon barrel. In order to implement that, we need to
capture the touch event and handle it as either a confirmed single tap or a
swipe, moving the cannon barrel, or as a double tap, shooting. If it is a
confirmed single tap or a swipe, we retrieve the x- and y-coordinates of the
touch, and set the value of the cannon angle in game . The redrawing of the
cannon barrel should happen automatically since we take into account the
value of the cannon barrel angle when we draw it inside the onDraw method.
If it is a double tap, we call the fireBullet method with game .
Generally, events should be handled inside the Controller. Thus, we add all
the event-related code to the MainActivity class. EXAMPLE 8.7 shows the
new parts of the MainActivity class. At lines 7 and 8, we import the
GestureDetector and MotionEvent classes. A GestureDetector instance
code, so we create a separate method, updateCannon , and call it, passing the
MouseEvent parameter. Inside updateCannon , coded at lines 68–73, we
calculate the x- and y-coordinates of the touch relative to the center of the
cannon at lines 69–70, and use the atan2 method of the Math class to
calculate the angle at line 71. We then call the setCannonAngle method with
game and pass that calculated angle at line 72. To retrieve the touch
coordinates, we use the getX and getY methods because they give us the x-
and y-coordinates relative to the View, as opposed to the getRawX and
getRawY methods that give us the absolute x- and y-coordinates.
The only change in the GameView class is to draw the bullet. This is done
inside the onDraw method. EXAMPLE 8.8 shows the updated onDraw method.
If the bullet is not off the screen (line 62), we draw it at lines 63–64 using its
position and radius stored in the instance variable game . As the game is
played, the position of the bullet is updated every 100 ms. This takes place
inside the run method of the GameTimerTask class.
EXAMPLE 8.9 shows the updated GameTimerTask class. The only changes
are at lines 17–20 of the run method. If the bullet is off the screen (line 17),
we load it inside the cannon (line 18). If it is on the screen, we test if it has
been fired (line 19). If it has, we update its position by calling moveBullet with
game at line 20. After the call to postInvalidate at line 23, it is redrawn by
the onDraw method of the GameView class at its new position on the screen. If
the bullet is on the screen but has not been fired, that means that it is inside
the cannon and there is nothing to update in this case.
EXAMPLE 8.8 The onDraw Method of the GameView class, Duck Hunting
app Version 2
EXAMPLE 8.9 The GameTimerTask class, Duck Hunting app, Version 2
FIGURE 8.3 Duck Hunting app, Version 2, running inside the emulator
FIGURE 8.3 shows the Duck Hunting Game app, Version 2, running inside
the emulator, after the user moved the cannon barrel and shot a bullet.
8.6 Playing a Sound: Shooting, Collision Detection,
Duck Hunting App, Version 3
In Version 2, when the bullet hits the duck, nothing happens. In Version 3,
when the duck is hit, we play a small sound and we let the duck fall down to
the ground (in fact, we let the duck go through the ground). We also make a
sound when we fire a bullet.
Method Description
int load( Context context, Loads a sound from context identified by resId as its resource id. The
int resId, int priority ) priority parameter is not used at the time of this writing. Use 1 as a
default value. Returns the sound id.
int play( int soundId, float Plays the sound whose sound id is soundId. If loop is 0, the sound
leftVolume, float plays once, and if loop is –1, the sound plays in a loop. Rate is the
rightVolume, int priority, int playback rate, ranging from 0.5 to 2.0, and 1.0 is the normal playback
loop, float rate ) rate.
void pause( int soundId ) Pauses the sound whose sound id is soundId.
void resume( int soundId ) Resumes playing the sound whose sound id is soundId.
Method Description
SoundPool.Builder Sets the number of max streams that can be played at the
setMaxStreams ( int same time. Returns this SoundPool.Builder.
maxStreams )
The following sequence shows how to create a SoundPool object.
After creating a SoundPool , the next step is to load a sound. Like we inflate a
layout XML file using its id, we can load a sound using its resource id. When
placing a sound resource in the res directory, it is common to create a
directory named raw and place the sound file in it. In Version 3, we play a
sound when a bullet is fired and play another sound when the duck is hit.
Thus, we create the raw directory, and add (using copy and paste) the
cannon_fire.wav and duck_hit.wav sound files in it. FIGURE 8.4 shows the
directory structure after having placed the cannon_fire.wav and duck_hit.wav
files in the just created raw directory.
Inside an Activity class, we can load cannon_fire.wav using one of the load
methods of SoundPool using this statement:
// using a Resource id
// this, this Activity, ”is a” Context
Note that we do not include the extension of the file name when specifying a
resource in the raw directory. That means that we should not have two sound
files with the same name and different extensions in that directory.
Let’s assume that a sound named cannon_fire.wav has been loaded and the
load method returned an integer id that we stored in the int variable
fireSoundId . We can play the sound once (we specify 0 as the next to last
If we have a sound named background.wav that has been loaded and its
sound id is backgroundSoundId , we can play that sound and loop forever (we
specify –1 as the next to last argument of play ) as follows:
// Play the sound at regular speed and loop forever
We need to play sounds whenever we fire a bullet or the duck is hit. The
code to fire a bullet is in the MainActivity class. The code to check if the
duck has been hit is in the GameTimerTask class. Thus, inside the MainActivity
class, we provide a method to play the hit sound that we can call from the
GameTimerTask class.
storing the sound ids of the two sounds we play when a bullet is fired and
when the duck is hit.
EXAMPLE 8.11 The MainActivity class, Duck Hunting app, Version 3
When the user double taps, we execute inside the onDoubleTapEvent method
(lines 66–72) of the TouchHandler class. We play the bullet_fire.wav sound
(line 69) whenever we fire a bullet.
At lines 60–62, we code the playHitSound method. Inside it, we play the
duck_hit.wav sound once. That method is not used in this class, but we call it
EXAMPLE 8.12 shows the changes in the run method of the GameTimerTask
class, the only changes in that class. We update the state of the game when
the duck is shot and when the duck goes off the screen. We also play the hit
sound when the duck is shot. If the duck is off the screen (line 21), not only
do we want to start flying it again (line 23) but we also want to make sure
that its status is that it is not shot (line 22). Indeed, it is possible that it has
gone off the screen vertically after it has been shot (in which case its status
was that it was shot).
If the duck is on the screen, we test if it has been hit (line 24). If it has not,
there is nothing to do. If it has, we set its status to shot (line 25). That, in
turn, impacts the way we move the duck inside the Game class (vertically
from top to bottom and not horizontally from right to left). We also reload the
bullet (line 27) and play the hit sound to indicate that the duck has been shot.
We do that at line 26 by calling the playHitSound method of the MainActivity
class. In order to get a MainActivity reference, we call the getContext
method with gameView , and cast the returned Context to MainActivity .
EXAMPLE 8.12 The run method of the GameTimerTask class, Duck
Hunting app, Version 3
EXAMPLE 8.13 The updated onDraw method of the GameView class, Duck
Hunting app, Version 3
We only need to make one change inside the GameView class: display the
duck properly after it has been shot. If the duck is shot, we stop animating
the duck and only show one frame, the last one (the first one in this example;
we could modify this example using more than four frames to animate the
duck so it makes sense to show the first one). Thus, the only change in the
GameView class is the drawing of the duck, which takes place inside the
We test if the duck is shot at line 68. If it is, we draw the Bitmap that
corresponds to the first animation frame at lines 69–70. If the duck is not
shot, we keep flying it, drawing the current Bitmap within the array ducks
based on the value of duckFrame at lines 72–73.
FIGURE 8.5 shows the Duck Hunting Game app, Version 3, running inside
the emulator, after the user shot the duck.
FIGURE 8.5 Duck Hunting Game app, Version 3, running inside the
emulator, after the user shot the duck
SOFTWARE ENGINEERING: When testing our app, we want to test
all possible situations that can happen. If a situation does not happen
often, we can hard code some of our code during the testing phase
so that it does and we can test it. For example, if we want to test
what happens when we hit the duck twice, we can hard code the
starting position of the duck at the top of the screen so that it is easier
to shoot it twice. After we finish testing, we should delete the hard
coded statements.
Chapter Summary
The android.graphics package includes many classes like Paint ,
Canvas , Bitmap , BitmapFactory that we can use to draw.
1. What class is used to specify what (shapes, bitmaps, etc.) we are going
to draw?
View
Paint
Canvas
Draw
2. What class is used to define how (style, color, etc.) we are going to
draw?
View
Paint
Canvas
Draw
3. If we want to draw inside a View, what method do we override?
draw
paint
onDraw
onPaint
4. What method of the View class can we call to force a redrawing of the
View?
reDraw
rePaint
post
postInvalidate
5. What class has static methods that we can use to create Bitmap
objects?
Bitmap
BitmapFactory
MakeBitmap
CreateBitmap
6. What class can we use to schedule a task to be run at a specified
frequency?
System
Timer
Task
Schedule
7. What class should we extend to define a task that is going to be run at a
specified frequency?
Task
TimerTask
TaskTimer
Scheduler
8. What method of the class in question 7 should be overridden to perform
the task that is scheduled to run at a specified frequency?
start
task
run
thread
9. How many sounds can a SoundPool object play at the same time?
0
1 only
0, 1, or many
10. When we call the play method of the SoundPool class, what identifies
the sound to be played?
The sound name
The SoundPool object
The sound id
The resource id of the sound
Fill in the Code
11. A Paint object named paint has been declared and instantiated. Modify it
so that its color is yellow and its stroke thickness is 20.
12. Inside onDraw, draw a full red circle centered at (100, 200) and with
radius 50.
13. Inside onDraw, draw an outlined green square (not green inside)
centered in the middle of the current View and with side 50.
14. Inside onDraw, draw HELLO ANDROID in blue starting at (50, 200).
15. Inside onDraw, draw a bitmap somewhere on the screen from an image
file named my_image.png located in the drawable directory.
16. Inside some class, you have an instance variable called myView that is a
reference to a View. Write the code to force that View to be redrawn.
17. We have written a class named MyTask that extends TimerTask,
including its run method. Schedule an instance of that class to run 50
times per second, starting in one second.
18. Inside the raw directory of the res directory, there is a sound file named
my_sound.wav. Write the code to play it once.
Write an app
19. Modify the duck hunting app in the chapter so that there are two ducks,
instead of one.
20. Modify the duck hunting app in the chapter so that we do not have to
wait until the bullet is off the screen or the duck is hit in order to shoot
another bullet.
21. Modify the duck hunting app in the chapter to include a nice background
(water, sun, grass, etc.) made up of at least one picture and three
shape drawings.
22. Modify the duck hunting app in the chapter so that we shoot with a
shotgun instead of a cannon. A shotgun shoots a spray of bullets, for
example, three. You can assume that these bullets are located within a
circle and move at the same speed. At least one of these bullets has to
hit the duck for the duck to be shot.
23. Write an app that shows a pool ball (you can use any number from 1 to
15). Use only shapes, do not use bitmaps.
24. Write an app that is a flashlight as follows: the color can go from one
color (color A) to another (color B) in continuous motion. If the screen is
colored with color A, when the user swipes the screen from left to right,
the screen goes from color A to color B in continuous motion. When the
user swipes the screen from right to left, the screen goes from color B
to color A in continuous motion.
25. Write an app with two activities: the first activity presents the user with a
few things to draw with some style or color options, and the second
activity draws them.
26. Write an app with two activities: the first activity displays three buttons
showing web names (for example Yahoo!, Google, or Facebook). When
the user clicks on one, the second activity shows a drawing of its logo.
27. Write a drawing app where the user can draw lines between two points
by swiping the screen between these two points. Offer the user a few
options for the drawing: pick among several colors, pick a thickness for
the line being drawn.
28. Write a drawing app where the user can draw by moving his or her
finger on the screen (Hint: a curve can be made of several lines). Offer
the user a few options for the drawing: pick among several colors, pick
a thickness for what is being drawn.
29. Write an app that displays a chess piece of your choice on the screen.
The user can pick it up and move it to another position on the screen by
touching it, moving his or her finger, and releasing it.
30. Write an app about the following game: Some enemy comes down
vertically from the top of the screen. The player is represented by a
shape, a bitmap, or even a label, at the bottom of the screen. The user
can move the player by touching it and moving his or her finger. Our
objective is to avoid the enemy that is coming down. When the enemy
goes off the screen, another one comes down from a random location at
the top of the screen. The game is over when the enemy touches the
player. Include a Model.
31. Same as problem 30 but we add a sound when an enemy starts at the
top of the screen and another sound when the enemy hits the player.
32. Same as problem 30 but several enemies can come down at the same
time—instead of one enemy, there is an array of enemies.
33. Write an app simulating the game of pong. The user can move a paddle
at the bottom of the screen and a ball moves on the screen. The ball
bounces off the side and top edges of the screen when it hits one of
them. If the ball goes past the paddle, the game is over. Include a
Model.
34. Write a jukebox-like app, where the user can make a selection from at
least five sounds and play it. Offer some options for the selected sound:
one time only or in a loop, different playback rates. Include a Model.
CHAPTER NINE: Fragments
9.2 Fragments
Chapter Summary
Introduction
In order to support larger screen devices like tablets, Google introduced
fragments with API level 11. A fragment is a portion of an activity and can
help manage a portion of the screen. We can think of a fragment as a mini-
activity within an activity. FIGURE 9.1 shows a screen for Version 4 of this
chapter’s app divided into three parts whose backgrounds are blue, red, and
green. We can organize that screen so that each part is a fragment.
Fragments are reusable. They can be reused within the same app or in a
different app. Thus it is desirable to use them.
We provide a default value for the number of allowed guesses at line 6 and
an array of hard coded words at line 7. At line 8, the instance variable word
stores the word to guess. The guessesAllowed and guessesLeft instance
variables keep track of the number of guesses allowed and the number of
guesses that the user still has. Finally, the indexesGuessed array keeps track
of the indexes of the letters in the String word that the user has guessed
correctly so far. A value of true means a correct guess at that index.
FIGURE 9.1 The Hangman app, Version 4, running in the horizontal
position
EXAMPLE 9.1 The Hangman class
The Hangman constructor, coded at lines 13–23, sets the value of the number
of guesses allowed (lines 14–17). It then randomly selects a word within the
array words (lines 19–21), and instantiates the indexesGuessed array. We
could envision a much more complex Model where another constructor could
pull a list of possible words from a file, a database, or even a remote
website, and then selects one at random.
We provide methods to play the game (lines 33–43), retrieve the state of
completion of the word (lines 45–53), and check if the game is over (lines
55–69). We also provide accessors for guessesAllowed and guessesLeft
(lines 25–27 and 29–31).
Figure 9.1 shows an actual game. At the bottom of the left pane, are the
number of guesses that the user still has. Inside the upper right pane, are the
letters that the user has correctly guessed so far, and we provide an
EditText to input the next letter. At the bottom of the right pane, is the status
Since fragments have been available since API level 11, we need to specify
API level 11 or higher when we create the app (at the time of this writing, the
default is API level 15).
▸ How to create and add a fragment for an activity using XML only.
▸ How to create and add a fragment for an activity using XML and code.
▸ How to create and add a fragment for an activity using code only.
EXAMPLE 9.2 shows the activity_main.xml file. There are two elements
inside the top LinearLayout element: a fragment element, defined at lines 9–
15, and a LinearLayout element, defined at lines 17–37. At line 11, we
specify that this fragment is an instance of the GameControlFragment class,
which we code later in this section. The top LinearLayout element’s
orientation is horizontal (line 6), so the fragment is positioned on the left side
of the screen, and the LinearLayout on the right side of the screen. They
both have the same android: layout_weight value of 1 (lines 13 and 20), so
they both occupy 50% (= 1/(1 + 1)) of the screen, as shown in Figure 9.1
(the fragment is in red, the LinearLayout in blue and green) and illustrated in
FIGURE 9.3. The android:layout_weight XML attribute of the LinearLayout.
LayoutParams class enables us to specify how much space each View should
occupy within its parent LinearLayout . We specify the value of 0dp for the
android:layout_width attribute for both the fragment and LinearLayout
on the layout when testing the app. We can change colors later, in the
final version of the app.
1a void onAttach( Context context ) When the fragment is attached to its context.
3a View onCreateView( LayoutInflater inflater, After onCreate; creates and returns the View
ViewGroup container, Bundle associated with this fragment.
savedInstanceState )
4a void onViewCreated( View view, Bundle After onCreateView, but before any saved
savedInstanceState ) state has been restored to the View.
6a void onViewStateRestored( Bundle When the saved state of the fragment’s View
savedInstanceState ) hierarchy has been restored (requires API
level 17).
View inflate( int Resource is an id for an XML resource that is being inflated. Root is an optional
resource, View to be the parent of the inflated layout if attachToRoot is true. If
ViewGroup root, attachToRoot is false, root is only used to set the correct layout parameters for
boolean the root view in the XML resource.
attachToRoot )
XML file is already inserted into container . Specifying true would create a
redundant ViewGroup hierarchy.
We can check what methods of the Activity and Fragment classes are
automatically called and in what order by adding Log statements inside the
various life-cycle methods of both the MainActivity and the
GameControlFragment classes. If we add them to both classes, we would see
the output shown in FIGURE 9.6 as the user starts the app and interacts with
the app and the device. It typically shows two things:
executes afterward.
In Version 1 of the app, we populate the left area of the screen (the
fragment) with the two GUI components, and we start using the Model.
EXAMPLE 9.5 shows the updated activity_main.xml file. We give ids to the
LinearLayout elements (at lines 24 and 32) at the top right and bottom right
of the screen so that we can access them later to place fragments in them.
We change from hard coded colors to colors defined in the colors.xml file
(lines 27 and 35), as shown in EXAMPLE 9.6.
FIGURE 9.6 Output as the user interacts with the HangmanV0LifeCycle
app, including life-cycle methods of the Activity and Fragment classes
EXAMPLE 9.5 The activity_main.xml file, Hangman app, Version 1
shown in EXAMPLE 9.9. The use of styles simplifies the coding of our layout
XML files.
The TextView element (lines 17–19) is styled with the textStyle style at line
19. The TextView element stores how many guesses the user has left. We
retrieve that value from the Model. The button, whose text says PLAY (line
11—the value of the play String in the strings.xml file—line 3 of EXAMPLE
9.8), triggers execution of the play method when the user clicks on it (line
13).
In Version 2, we display the fragment on the top right pane (with the blue
background) of the screen showing the state of completion of the word, and
an EditText for the user to enter a letter. We code the fragment in an XML
file, fragment_game_state.xml, and create the fragment by code.
android:screenOrientation=”landscape”
EXAMPLE 9.11 shows the fragment_game_state.xml file. It includes a
TextView , which shows the state of completion of the word and an EditText ,
where the user can enter a letter. We give ids to these two elements at lines
11 and 17 so that we can later retrieve them using the findViewById method.
They are styled with textSyle and editTextStyle , which are both defined in
the styles.xml file shown in Example 9.9. Like in Example 9.7 for the left
pane fragment, we wrap each element around a LinearLayout element styled
with the wrapCenterWeight1 style, so that the elements are centered and
spaced evenly.
We want to close the soft keyboard when the user touches the Done key. In
order to do that, we set the value of the android:imeOptions attribute,
inherited from TextView , to actionDone at line 19. IME stands for Input
Method Editor, and it enables the user to enter text. It is not restricted to a
keyboard—the user can also enter text by voice. The attribute
android:imeOptions enables us to specify the options that we want to enable.
We need to show the game state fragment when the app starts. We first
need to create the fragment and add it to the activity, then set the text inside
the TextView element of the fragment. For that, we can call the
currentIncompleteWord method of our Model. To incorporate a fragment into
▸ Create a fragment.
Method Description
Method Description
Method Description
FragmentTransaction Shows fragment (i.e., shows its View if it had been hidden before—the
show( Fragment default is that the fragment is shown).
fragment )
FragmentTransaction Similar to the first add method, but instead replaces an existing fragment
replace( int inside the container whose id is containerViewId.
containerViewId,
Fragment fragment,
String tag )
FragmentTransaction Adds this transaction to the back stack of the activity; name is optional and
addToBackStack( stores the name of that back stack state.
String name )
EXAMPLE 9.12 shows the updated MainActivity class. When the app
starts, the onCreate method executes, and we create the fragment and
attach it to the activity. At line 23, we get a reference to the fragment
manager for this activity by calling the getFragmentManager method from the
Activity class. We only want to create a new GameStateFragment and add it
to the activity if it has not been created yet. Using the findFragmentById
method at line 24, we check if there is already a fragment with the id
game_state within this activity. If there is, we do nothing. If there is not, we
incomplete word of the hangman game. To that end, we provide the getGame
method (lines 32–34) in the MainActivity class. From the GameStateFragment
class, we can access the activity by calling the getActivity method (see
TABLE 9.6), inherited from the Fragment class. With a MainActivity
reference, we can access the game by calling getGame , and then access the
incomplete word as follows:
TABLE 9.6 The getActivity and getView methods of the Fragment class
Method Description
Activity getActivity( ) Returns the activity that this fragment belongs to.
in that order. When the onStart method (lines 22–30) executes, the
fragment’s View and the TextView inside it have been instantiated. Thus, we
can set the text inside the TextView at that point.
FIGURE 9.8 shows the app running in the emulator after the user enters the
letter U and hits the Done button on the soft keyboard.
In Version 3, we display the fragment on the bottom right pane (with the
green background) of the screen showing a message about the result of the
game in a TextView . This time, we do not use an XML file to define the
fragment, we define and create it entirely by code.
Defining the fragment’s Graphical User Interface (GUI) is similar to defining a
View by code instead of using an XML file. We do this in the
GameResultFragment class. Creating the fragment is identical to creating a
fragment. It returns the View returned by its super method at lines 18–19.
The setUpFragmentGui method is coded at lines 22–28. Since it is possible
that onCreateView is called several times during the life cycle of the fragment,
we can in turn expect setUpFragmentGui to be called several times too. Thus,
we want to instantiate gameResultTV (line 24) and add it to the ViewGroup that
is the root View of the fragment (line 26) only once, when it is null (line 23).
We center the text inside gameResultTV at line 25.
EXAMPLE 9.14 The MainActivity class, Hangman app, Version 3
EXAMPLE 9.15 The GameResultFragment class, Hangman app, Version 3
value pair to the last LinearLayout element (the one whose id is game_result
and is located in the right bottom pane):
android:gravity=”center”
FIGURE 9.9 The Hangman app, Version 3, running in horizontal position
FIGURE 9.9 shows the app running in the emulator. All three fragments are
now present:
▸ The left pane (game control) fragment in red is defined and created
using XML.
▸ The top right pane (game status) fragment in blue is defined in XML
and created by code.
▸ The bottom right pane (game result) fragment in green is defined and
created by code.
We have now created an activity with three fragments inside, defined and
created in different ways. In Version 4, we complete the app by processing
the user’s letter and enabling the user to play the game. We also illustrate
how fragments can communicate with their activity when processing an
event.
The user enters a letter in the game state fragment, then clicks on the PLAY
button on the left pane, which triggers execution of the play method inside
the MainActivity class. To enable play, we do the following:
▸ Update the number of guesses left in the TextView inside the left pane.
▸ Update the incomplete word in the TextView inside the top right pane.
▸ If the game is over, update the message in the TextView inside the
bottom right pane and clear the hint in the EditText.
EXAMPLE 9.16 shows the play method. We retrieve the EditText using its
id at line 46 and assign it to the variable input . At line 47, we get an
Editable reference for input . If it is not null and contains at least one
character (line 48), we process the first character as the user’s play.
Otherwise, we do nothing.
EXAMPLE 9.16 The play method of the MainActivity class, Hangman
app, Version 4
At lines 49–53, we play and update the number of guesses left. Since we
gave the TextView that displays the number of guesses left an id ( status ) in
the fragment_game_control.xml file, we retrieve it using the findViewById
method at line 52. We call getGuessesLeft with game at line 53 to retrieve the
number of guesses left and display it inside the TextView .
At lines 55–62, we update the state of the incomplete word. We first get a
reference to the GameStateFragment at lines 56–58, then get a reference to
the fragment’s View at line 59. With it, we call findViewById at lines 60–61 in
order to get a reference to the TextView whose id is state_of_game . We call
the currentIncompleteWord of Hangman to set the text of that TextView at line
62. At lines 64–65, we clear the EditText .
Figure 9.1, at the beginning of the chapter, shows the app running at some
point in the game. FIGURE 9.10 shows the app running after the user won.
The TextView that was previously showing either a hint or a letter is no longer
visible because its contents are empty and its android: layout_width and
android:layout_height attributes are set to wrap_content .
FIGURE 9.10 The Hangman app, Version 4, running in horizontal
position, after the user won
We can also use a fragment that can perform some work in the background
of the app, without any visual representation. For this, we use the add
method of the FragmentTransaction class that does not have a View id
parameter. If we want to be able to retrieve the fragment later, we should
provide a tag name. Such a fragment could perform some work in the
background of the app while the user is allowed to interact with the app. It
could, for example, retrieve some data from a file, a database, a remote
URL, or possibly use the device’s GPS to retrieve some live location data.
onCreateView method is not called. Thus, we did not code it. At lines 10–12,
we code the warning method, which we call from the MainActivity class.
class. We use the fragment in the play method (lines 52–99). If the user has
only one guess left (line 75), we display a warning inside the TextView of the
GameResultFragment . We retrieve the BackgroundFragment based on its tag at
FIGURE 9.11 shows the app running in the emulator. The user only has one
guess left.
FIGURE 9.11 The Hangman app, Version 5, running in horizontal
position—the user only has one guess left
Version 5 of the app works, but the GameStateFragment class is not really
reusable. For example, at line 27 of Example 9.13, the use of the
MainActivity reference and the call to getGame at line 28 inside the
an inner interface, which we name Callbacks (lines 15–17), and transfer all
the method calls using a MainActivity reference inside the fragment class to
the MainActivity class via that interface. Inside the fragment class,
whenever we call a MainActivity method, we add a method to the Callbacks
interface and call that method from the fragment class. The MainActivity
class implements the interface and thus must override that method. In this
way, we effectively transfer the MainActivity method calls from the fragment
class to the MainActivity class.
EXAMPLE 9.20 The reusable GameStateFragment class, Hangman app,
Version 6
EXAMPLE 9.23 shows the class header of the updated Hangman class, which
now implements the WordGame interface. There is no other change as
compared to Example 9.1: the currentIncompleteWord method was already
implemented before and is still the same, but it now overrides the
currentIncompleteWord method of the WordGame interface.
EXAMPLE 9.23 The class header of the Hangman class, Hangman app,
Version 6
Note that we implemented Callbacks as an inner interface of
GameStateFragment and WordGame as a separate interface. Either can be
Version 6 works but it is annoying to have to click on the PLAY button every
time. In Version 7, we enable play as soon as the user closes the keyboard.
We can either eliminate or convert the PLAY button to a “Play Another
Game” button, which you can do in the exercises.
The EditText is located inside the game state fragment, so event handling
for Version 7 takes place in that fragment. OnEditorActionListener , a public
static inner interface of the TextView class, provides the functionality to
handle a key event inside an EditText . TABLE 9.7 lists its only method,
onEditorAction .
Method Description
boolean onEditorAction( TextView view is the TextView that was clicked; keyCode is an integer
view, int keyCode, KeyEvent event identifying the key that was pressed; event is the Key event.
)
EXAMPLE 9.24 The GameStateFragment class, Hangman app, Version 7
does exactly the same thing as the existing play method (which we can
leave as a do-nothing method—if we choose to delete it, we also need to
delete the android: onClick attribute in the fragment_game_control.xml file).
We use the same strategy as in Version 6 to transfer the call to the play
method of MainActivity via a play method of the Callbacks interface
(declared at line 20), which we call with the fragment’s play method (lines
71–73). Thus, the call to the fragment’s play method at line 90 triggers a call
to the Callbacks’ play method. In this way, we keep our fragment class
reusable (with a class implementing the Callbacks interface).
Its String parameter represents a service, and TABLE 9.8 lists some of the
possible values. Depending on the service specified, it returns a manager
object reference for that service. For example, if we specify
LOCATION_SERVICE , it returns a LocationManager reference that we can use to
data regarding the device’s Wi-Fi connectivity. Because that method returns
a generic Object , we need to typecast the returned object reference to what
we expect. We want to obtain an InputMethodManager reference, so we pass
the argument Context.INPUT_METHOD_SERVICE and cast the resulting object
reference to an InputMethodManager as follows (and as in lines 83–84):
Method Description
boolean token is the token of the Window making the request as returned by
hideSoftInputFromWindow( the getWindowToken method of the View class; flags specifies
IBinder token, int flags ) additional condition for hiding the soft input.
TABLE 9.10 shows the two constants of the InputMethodManager class that
can be used as values for the second parameter of the
hideSoftInputFromWindow method. Since the user explicitly opens the
keyboard when trying to enter some input, we should not use the
HIDE_IMPLICIT_ONLY constant in this case. We use HIDE_NOT_ALWAYS , so the
InputMethodManager.HIDE_NOT_ALWAYS
When we run the app, play happens as soon as the keyboard closes. We no
longer need to click on the PLAY button.
HIDE_IMPLICIT_ONLY 1 The soft input window should only be hidden if it was not explicitly
open by the user.
HIDE_NOT_ALWAYS 2 The soft input window should normally be hidden unless it was
originally forced open by code.
// fm is a FragmentManager reference
FragmentTransaction transaction = fm.beginTransaction( );
12. Inside an activity class, add a fragment of type MyFragment and place it
inside a ViewGroup element whose id is my_id. Give it the tag my_tag.
// fm is a FragmentManager reference
13. Inside an activity class, add a fragment of type MyFragment that does
not have a user interface. Give it the tag my_tag.
// fm is a FragmentManager reference
14. Inside a fragment class, write the code so that the fragment is inflated
from the my_fragment.xml file.
15. Inside a fragment class, write the code to retrieve the activity that this
fragment belongs to. Assume that the activity type is MyActivity.
16. The following fragment class is not reusable. Change it so that it is.
package com.you.myapp;
import android.app.Fragment;
super.onStart( );
MyActivity activity = ( MyActivity ) getActivity( );
activity.update( );
Write an App
17. Write a mini app containing one activity using two fragments as shown
below:
18. Write a mini app containing one activity using three fragments as shown
below:
19. Write a mini app containing one activity using four fragments as shown
below:
20. Write an app containing one activity using two fragments (one left and
one right) as follows:
The left fragment contains a button and the right fragment contains a
label that simulates a traffic light, red when we start. When the user
clicks on the button, the label changes to green. When the user clicks on
the button again, the label changes to yellow. When the user clicks on
the button again, the label changes to red, and so on. Even though the
functionality is simple, you should include a Model for this app.
21. Write an app containing one activity using several fragments (one at the
top and one or more below) as follows:
The top fragment should occupy one fourth of the screen and contain
three radio buttons, showing 1, 2, and 3. When the user selects one of
them, the bottom of the screen displays the corresponding number of
fragments (use a different color for each), and they all should occupy the
same amount of space. For example, if the user clicks on the radio
button that says 2, then two fragments of equal height are displayed at
the bottom of the screen.
22. Write an app containing one activity using two fragments (one on top
and one at the bottom) as follows:
The app simulates a flashlight. The top fragment comprises 20% of the
screen and contains an on and off switch. You can use the Switch class
for this. The bottom fragment comprises 80% of the screen and is black.
When the user turns the switch on, the bottom fragment turns yellow.
When the user turns the switch off, the bottom fragment reverts back to
black. Even though the functionality is simple, you should include a Model
for this app.
23. Write an app containing one activity using two fragments (one on top
and one at the bottom) as follows:
The app simulates a dimmer. The top fragment comprises 20% of the
screen and contains a slider. You can use the SeekBar class for this.
The bottom fragment comprises 80% of the screen and is yellow. When
the user moves the slider, the yellow fades. You can assume that the
dimmer slider controls the transparency of the yellow color. Even though
the functionality is simple, you should include a Model for this app.
24. Write an app containing one activity using two fragments (one on the left
and one on the right) as follows:
The app simulates a traffic light. The left fragment contains a button and
the right fragment contains three labels, the top one is red when we
start and the others are transparent. When the user clicks on the button,
the top label becomes transparent and the bottom label changes to
green. When the user clicks on the button again, the bottom label
becomes transparent and the middle label changes to yellow. When the
user clicks on the button again, the middle label becomes transparent
and the top label changes to red, and so on. Even though the
functionality is simple, you should include a Model for this app.
25. Write an app containing one activity using two fragments (one at the top
and one below) as follows:
The top fragment should occupy one fourth of the screen and contains a
button, the bottom fragment three fourths of the screen. When the user
clicks on the button, a tic-tac-toe game shows up inside the bottom
fragment. Play should be enabled. You should include a Model for the
tic-tac-toe game.
26. Write an app containing one activity using two fragments (one at the top
occupying 75% of the space and one below occupying 25% of the
space) as follows:
The app generates a random number between 1 and 5 and the user
needs to guess it. The bottom fragment shows an EditText asking the
user to enter a number. The top fragment shows some feedback to the
user (for example, “You won” or “You lost”). Even though the functionality
is simple, you should include a Model for this app.
27. Write the same app as in exercise 26 but add a third fragment, this one
invisible: it provides the functionality of generating a random number.
28. Write an app that improves this chapter’s app. On the left pane, add
another fragment that contains a button so that the user can play
another game by clicking on it.
29. Write the same app as in exercise 28 but add a file that stores a large
number (1,000 or more) of potential words. The Hangman constructor
should pick one at random from the file. Add an invisible fragment to
instantiate a Hangman object and start the game with the word selected.
CHAPTER TEN: Using Libraries and
Their APIs: Speech Recognition and
Maps
Chapter Summary
Introduction
The Android framework provides some powerful libraries for app developers.
In this chapter, we learn how to use some of these libraries. We explore
libraries for speech recognition and maps. We build a simple app including
two activities. The first activity asks the user to say a location and compares
it to a list of locations. If there is a match, the second activity displays a map
with annotations for that location.
10.1 Voice Recognition
FIGURE 10.1 Show A Map app, Version 0, running inside the tablet,
expecting the user to speak
TABLE 10.1 Selected constants of the RecognizerIntent class
Constant Description
ACTION_WEB_SEARCH Use with an activity that searches the web with the spoken
words.
LANGUAGE_MODEL_WEB_SEARCH Value that specifies that spoken words are used as web
search terms.
To create a web search intent that uses speech input, we use the
ACTION_WEB_SEARCH constant of the RecognizerIntent class as the argument of
Once we have created the intent, we can refine it by placing data in it using
the putExtra method, using constants of the RecognizerIntent class as keys
for these values. For example, if we want to display a prompt, we use the
key EXTRA_PROMPT and provide a String value for it as in the following
statement:
does not place any restriction on what the method returns. Thus, we can use
the following pattern to test if the current device supports speech recognition:
Intent intent = new Intent( RecognizerIntent. ACTION_RECOGNIZE_SPEECH
);
} else {
// provide an alternative way for user input
}
In Version 0 of our app, we will only have one activity: we ask the user to say
a word and we provide a list of possible matches for that word in Logcat.
Once again, we choose the Empty Activity template. This will change in
Version 1 when we add a map to our app. EXAMPLE 10.1 shows the
MainActivity class. It is divided into three methods:
▸ In the onCreate method (lines 17–32), we set the content View and
check if speech recognition is supported. If it is, we call the listen
method. If it is not, we display a message to that effect.
line 25, which means that speech recognition is supported by the current
device, and call listen at line 26 if it does. If it does not, we show a
temporary message at lines 28–30. We use the Toast class, shown in
TABLE 10.3, a convenient class to show a quick message on the screen for
a small period of time that still lets the user interact with the current activity.
EXAMPLE 10.1 The MainActivity class, capturing a word from the user,
Version 0
Constant Description
Method Description
static Toast makeText( Context context, Static method to create a Toast in context,
CharSequence message, int duration ) displaying message for duration seconds.
TABLE 10.4 The putExtra and get...Extra methods of the Intent class
Method Description
Intent putExtra( String key, Stores value in this Intent, mapping it to key.
DataType value )
Intent putExtra( String key, Stores the array values in this Intent, mapping it to key.
DataType [ ] values )
DataType getDateTypeExtra( Retrieves the value that was mapped to key. If there is not any,
String key, DataType defaultValue is returned. DataType is either a primitive data type or
defaultValue ) a String.
assign the value What city? to the EXTRA_PROMPT key so that it shows on the
screen next to the microphone. At lines 38–39, we specify that the expected
speech is regular human speech by setting the value of the key
EXTRA_LANGUAGE_MODEL to LANGUAGE_MODEL_FREE_FORM . At line 40, we set the
maximum number of words in the returned list to 5 . We start the activity for
listenIntent at line 41, and we associate it with the request code 1 , the
value defined by the constant CITY_REQUEST (line 15). When it finishes, the
onActivityResult method is automatically called and the value of its first
After the listen method executes, the user is presented with a user
interface showing a microphone and the extra prompt, in this example What
city? , and has a few seconds to say something. After that, the code
Intent parameter of the method, and use the EXTRA_RESULTS key, shown in
We do not need the Hello world! label. Thus, we edit the activity_main.xml file
and remove the TextView element.
FIGURE 10.2 Possible Logcat output from Example 10.1 when the user
says Washington
FIGURE 10.3 Possible Logcat output from Example 10.1 when the user
says Paris
Note that if we failed to speak within a few seconds, the user interface
showing the microphone goes away and it is then too late to speak. If we
want to give more control to the user, we can include a button in the user
interface, which when clicked, triggers a call to the listen method and brings
up the microphone again.
The Google Maps Activity template already includes the skeleton code to
display a map. Furthermore, it makes the necessary Google Play services
libraries available to the project in the build.gradle (Module: app) file, as
shown in Appendix C. When creating the project, instead of choosing the
Empty Activity template as we usually do, we choose the Google Maps
Activity template (see FIGURE 10.4). As FIGURE 10.5 shows, the default
names for the Activity class, the layout XML file, and the app title are
MapsActivity, activity_main.xml, and Map, respectively.
In addition to the usual files, the google_maps_api.xml file is also
automatically generated, and is located in the values directory. EXAMPLE
10.2 shows it.
simplest way to include a map in an app. It occupies its whole parent View
(lines 6–7) and has an id (line 1). Thus, we can retrieve it programmatically
and change the characteristics of the map, for example, move the center of
the map, or add annotations to it.
<uses-permission android:name="permission_name"/>
Constant Description
Each app must declare what permissions are required by the app. When a
user installs an app, the user is told about all the permissions that the app
requires and has the opportunity to cancel the install at that time if he or she
does not feel comfortable granting these permissions.
Class Description
TABLE 10.7 The getMapSync method of the SupportMapFragment class and the
mapReady method of the OnMapReadyCallback interface
The GoogleMap class is the central class for modifying the characteristics of a
map and handling map events. Our starting point is a SupportMapFragment
object, which we can either retrieve through its id from a layout, or can
construct programmatically. In order to modify the map that is contained in
that SupportMapFragment , we need a GoogleMap reference to it. We can get
the parameter by calling the getMapAsync method of the SupportMapFragment
class, shown in TABLE 10.7.
The OnMapReadyCallback interface contains only one method, onMapReady , also
shown in Table 10.7. The onMapReady method accepts one parameter, a
GoogleMap . When the method is called, its GoogleMap parameter is
OnMapReadyCallback interface.
fragment.getMapAsync( this );
public void onMapReady( GoogleMap googleMap ) {
map = googleMap;
In order to modify a map, for example, its center point and its zoom level, we
use the animateCamera and moveCamera methods of the GoogleMap class, which
all take a CameraUpdate parameter. The CameraUpdate class encapsulates a
camera move. A camera shows us the map we are looking at: if we move
the camera, the map moves. We can also add annotations to a map using
addMarker , addCircle , and other methods from the GoogleMap class. TABLE
CameraUpdate object contains position and zoom level values. Zoom levels
vary from 2.0 to 21.0. Zoom level 21.0 is the closest to the earth and shows
the greatest level of details.
TABLE 10.8 Selected methods of the GoogleMap class
Method Description
void animateCamera( Animates the camera position (and modifies the map) from the current
CameraUpdate update ) position to update.
void animateCamera( Animates the camera position (and modifies the map) from the current
CameraUpdate update, position to update, in ms (milliseconds). The onCancel method of
int ms, GoogleMap. callback is called if the task is cancelled. If the task is completed, then
CancelableCallback the onFinish method of the callback is called.
callback )
void moveCamera( Moves, without animation, the camera position (and modifies the map)
CameraUpdate update ) from the current position to update.
void setMapType( int type Sets the type of map to be displayed: the constants
) MAP_TYPE_NORMAL, MAP_TYPE_SATELLITE,
MAP_TYPE_TERRAIN, MAP_TYPE_NONE, and MAP_TYPE_HYBRID
of the GoogleMap class can be used for type.
Marker addMarker( Adds a marker to this Map and returns it; options defines the marker.
MarkerOptions options )
Circle addCircle( Adds a circle to this Map and returns it; options defines the circle.
CircleOptions options )
Polygon addPolygon( Adds a polygon to this Map and returns it; options defines the polygon.
PolygonOptions options )
void Sets a callback method that is automatically called when the user taps
setOnMapClickListener( on the map; listener must be an instance of a class implementing the
GoogleMap. GoogleMap.OnMapClickListener interface and overriding its
OnMapClickListener onMapClick(LatLng) method.
listener )
Often, we want to change the position of the center of the map. If we know
the latitude and longitude coordinates of that point, we can use the LatLng
class to create one using this constructor:
LatLng( double latitude, double longitude )
Method Description
CameraUpdate zoomTo( float level ) Returns a CameraUpdate that zooms to the zoom level
level.
We can then zoom in, using the zoomIn method of the GoogleMap class as
follows:
CameraUpdate update2 = CameraUpdateFactory.zoomIn( );
map.animateCamera( update2 );
If we want to directly zoom to level 10, we can use the zoomTo method of the
GoogleMap class as follows:
classes are imported at lines 6–11. This class inflates the activity_maps.xml
layout file and then places a marker at Sydney, Australia.
The onMapReady method (lines 29–48) is automatically called when the map is
ready. We assign its GoogleMap parameter to our mMap instance variable at
line 41. At line 44, we create a LatLng reference with latitude and longitude
values for Sydney, Australia. At lines 45–46, we add a marker to the map
whose title is “Marker in Sydney” at that location using chained method calls
and an anonymous MarkerOptions object. TABLE 10.10 shows the
constructor and various methods of the MarkerOptions class. This is
equivalent to the following code sequence:
options.position( sydney );
Constructor Description
Method Description
MarkerOptions position( LatLng latLng ) Sets the (latitude, longitude) position for this
MarkerOptions.
MarkerOptions title( String title ) Sets the title for this MarkerOptions.
MarkerOptions snippet( String snippet ) Sets the snippet for this MarkerOptions.
When the user touches the marker, the title is revealed in a callout. Finally, at
line 47, we move the camera to the location on the map defined by the
LatLng reference sydney , so that a map centered on Sydney, Australia,
shows.
FIGURE 10.7 shows the app running inside the tablet. It is possible to run
inside the emulator, but we may have to update Google Play services in
order to do it.
In Version 2, we center the map on the White House in Washington, DC, set
up the zoom level, add a marker, and draw a circle around the center of the
map. The only part of the project impacted is the onMapReady method of the
MapsActivity class. In order to do that, we use the addMarker and addCircle
methods shown in Table 10.8. These two methods are similar: the addMarker
takes a MarkerOptions parameter, and addCircle takes a CircleOptions
parameter. TABLE 10.11 shows selected methods of the CircleOptions
class.
For both, we first use the default constructor to instantiate an object, and
then call the appropriate methods to define the marker or the circle. We must
specify a position, otherwise there will be an exception at run time and the
app will stop. All the methods shown in both tables return a reference to the
object calling the method, so method calls can be chained.
TABLE 10.11 Selected methods of the CircleOptions class
Constructor Description
Method Description
CircleOptions center( LatLng Sets the (latitude, longitude) position of the center for this
latLng ) CircleOptions.
CircleOptions radius( double Sets the radius, in meters, for this CircleOptions.
meters )
CircleOptions strokeColor( int Sets the stroke color for this CircleOptions.
color )
CircleOptions strokeWidth( float Sets the stroke width, in pixels, for this CircleOptions.
width )
To add a circle to a GoogleMap named mMap , we can use the following code
sequence:
options.center( whiteHouse );
options.radius( 500 );
options.strokeWidth( 10.0f );
options.strokeColor( 0xFFFF0000 );
mMap.addCircle( options );
Because all the CircleOptions methods used previously return the
CircleOptions reference that calls them, we can chain all these statements
The first format has the advantage of clarity. The second format is more
compact. Many examples found on the web use the second format.
EXAMPLE 10.6 The MapsActivity class, Show A Map app, Version 2
FIGURE 10.8 The map of the White House with annotations, Show A
Map app, Version 2
At lines 46–49, we place a circle on the map specifying its location, its
radius, its stroke width, and its color. We instantiate a CircleOptions object
and chain calls to the center , radius , strokeWidth, and strokeColor
methods at lines 46–48. The center of the circle is at the White House
location, its radius is 500 meters, its stroke is 10 pixels, and its color is red.
We add it to the map at line 49. Although we prefer the first coding style (the
one we use for the marker), there are many developers who use the second
coding style, chaining method calls in one statement. Thus, we should be
familiar with both.
FIGURE 10.8 shows the app running inside the tablet, after the user touches
the marker. The title and snippet only show if the user touches the marker.
We keep our Model simple: we store (city, attraction) pairs in a hash table.
A hash table is a data structure that maps keys to values. For example, we
can map the integers 1 to 12 to the months of the year (January, February,
March, etc.) and store them in a hash table. We can also store US states in
a hash table: the keys are MD, CA, NJ, etc., and the values are Maryland,
California, New Jersey, etc. In this app, cities (the keys) are New York,
Washington, London, Rome, and Paris. The attractions (the values) are the
Statue of Liberty, the White House, Buckingham Palace, the Colosseum, and
the Eiffel Tower. We want to match a word said by the user to a city in our
Model. In case we do not have a match, we use Washington as the default
city.
EXAMPLE 10.7 shows the Cities class, which encapsulates our Model. At
lines 11–13, we declare three constants to store the String Washington , its
latitude and its longitude. At line 8, we declare a constant, CITY_KEY , to store
the String city . When we go from the first to the second activity, we use
that constant as the key for the name of the city understood by the speech
recognizer in the intent that we use to start the activity. At line 9, we declare
the constant MIN_CONFIDENCE and give it the value 0.5 . It defines a threshold
of confidence level that any word understood by the speech recognizer
needs to have for us to process it as a valid city. At line 14, we declare the
constant MESSAGE . We use it when we add a marker to the map.
time of declaration. The first String data type in <String, String> is the data
type of the keys. The second String is the data type of the values. We code
a constructor that accepts a Hashtable as a parameter and assign it to
places at lines 18–20. At lines 43–45, the getAttraction method returns the
Inside the inner loop, we test if the current word matches a city listed as a
key in the hash table places at line 36. If it does, we return it at line 37.
If we finish processing the outer loop and no match is found with an
acceptable confidence level, we return the value of DEFAULT_CITY at line 40.
Note that we process the parameter words in the outer loop (line 29) and the
keys of places in the inner loop (line 34) and not the other way around
because we expect words to be ordered by decreasing confidence levels.
In the first activity, we ask the user to say the name of a city and we use the
Cities class to convert the list of words returned by the speech recognizer
into a city name that we feel confident the user said. If there is no match, we
use the default city stored in the DEFAULT_CITY constant of the Cities class.
Then, we store that information in the intent that we create before launching
the map activity. Inside the MapsActivity class, we can then access that
intent, retrieve the city name and display the correct map.
EXAMPLE 10.8 shows the updated MainActivity class. After retrieving the
city said by the user, we start a second activity to show a map centered on
that city. In order to analyze the city said by the user, we use the Cities
class. We declare cities , a Cities instance variable at line 16; it represents
our Model. For convenience, because we need to access it from the
MapsActivity class, we declare it as public and static . We instantiate it at
lines 22–28 inside the onCreate method using a list of five cities. At lines 63–
65 of the onActivityResult method, we call the firstMatchWithMinConfidence
method with cities in order to retrieve the name of the city said by the user,
and we assign it to the String variable firstMatch . At lines 68–69, we place
firstMatch in an intent to start a MapsActivity using the putExtra method
(shown in TABLE 10.12), mapping it to the key city . We start the activity at
lines 70–71.
▸ Access the original intent for this activity and retrieve the name of the
city stored in the intent.
To retrieve the name of city stored in the original intent, we use the
getStringExtra method shown in Table 10.12.
TABLE 10.12 The putExtra and getStringExtra methods of the Intent class
Method Description
Intent putExtra( String key, String Puts value in this Intent, associating it with key; returns this
value ) Intent.
String getStringExtra( String key ) Retrieves and returns the value that was placed in this Intent
using key.
The Geocoder class provides methods for both geocoding and reverse
geocoding. TABLE 10.14 lists two of its methods, getFromLocationName , a
geocoding method, and getFromLocation , a reverse geocoding method. They
both returned a List of Address objects, ordered by matching confidence
level, in descending order. For this app, we keep it simple and process the
first element in that list. The Address class, part of the android.location
package, stores an address, including street address, city, state, zip code,
country, etc., as well as latitude and longitude. TABLE 10.15 shows its
getLatitude and getLongitude methods.
Constructor Description
Method Description
List <Address> Returns a list of Address objects given an address. The list has
getFromLocationName( maxResults elements at the most, ordered in descending order of
String address, int matching. Throws an IllegalArgumentException if address is null, and
maxResults ) throws an IOException if the network is not available or if there is any
other IO problem.
List <Address> Returns a list of Address objects given a point on Earth defined by its
getFromLocation( latitude and longitude. The list has maxResults elements at the most,
double latitude, double ordered in descending order of matching. Throws an
longitude, int IllegalArgumentException if latitude or longitude is out of range, and
maxResults ) throws an IOException if the network is not available or if there is any
other IO problem.
Method Description
double getLatitude( ) Returns the latitude of this Address, if it is known at that time.
double getLongitude( ) Returns the longitude of this Address, if it is known at that time.
city is null (line 35), we assign to it the default city defined in the Cities
Inside the onMapReady method, we geocode the address defined by the city
chosen by the user and its attraction to its latitude and longitude coordinates.
Then, we set up the map based on these coordinates, including a circle and
a marker as in Version 2.
At lines 49–51, we declare latitude and longitude , two double variables
that we use to store the latitude and longitude coordinates of city . We
initialize them with the default values stored in the DEFAULT_LATITUDE and
DEFAULT_LONGITUDE constants of the Cities class. At lines 53–55, we retrieve
that we only want 5 or fewer results, and assign the return value to
addresses . We test if addresses is null at line 63 before assigning the
latitude and longitude data of the first element in it to latitude and longitude
at lines 64 and 65. If we execute inside the catch block, that means that we
failed to get latitude and longitude data for city , so we reset city to the
default value.
Afterward, we update the center of the map to the point with coordinates
( latitude , longitude ) and set the zoom level at lines 73–78. We add the
marker at lines 80–84 and add the circle at lines 86–89.
We need to update the AndroidManifest.xml file so that the app starts with
the voice recognition activity and includes the map activity. EXAMPLE 10.10
shows the updated AndroidManifest.xml file. At line 34, we specify
MainActivity as the launcher activity. At lines 43–45, we add a MapsActivity
activity element.
EXAMPLE 10.10 The AndroidManifest.xml file, Show A Map app,
Version 3
Note that if we run the app and we do not say anything, the microphone goes
away and we are stuck on the first activity, which is now a blank screen.
Furthermore, if we go to the second screen and come back to the first
screen, we also have a blank screen. One way to fix this issue is to start the
map activity no matter what. If the user does not say a word, the microphone
goes away but onActivityResult still executes. We can place the code to
start the map activity outside the if block of the onActivityResult method of
Example 10.8. The map shown is a default map.
Another way to fix this issue is to add a button that when clicked, triggers a
call to listen and brings back the microphone. This gives the user a chance
to be in control, although there is the extra step of clicking on a button. We
implement that feature in Version 4. We edit activity_main.xml to add a button
to the first screen and we add a method to the MainActivity class to
process that click.
EXAMPLE 10.13 shows the edited strings.xml file. The click_to_speak string
is defined at line 4.
EXAMPLE 10.13 The strings.xml file, Show A Map app, Version 4
If we run the app again, whenever the first screen goes blank, we can click
on the button and bring back the microphone.
list of words to an enum list rather than a list of keys in a hash table. The
main difference in the code is how we loop through the enum Directions at
line 18. In general, to loop through an enum named EnumName , we use the
following for loop header:
void onReadyForSpeech( The speech recognizer is ready for the user to start speaking.
Bundle bundle )
void onPartialResults( Bundle Partial results are ready. This method may be called 0, 1, or
bundle ) more times.
void onBufferReceived( byte [ ] More sound has just been received. This method may not be
buffer ) called.
void onRmsChanged( float The sound level of the incoming sound has changed. This
rmsdB ) method is called very often.
Method Description
ArrayList getStringArrayList( Returns the ArrayList of Strings associated with key, or null if
String key ) there is none.
float [] getFloatArray( String key ) Returns the float array associated with key, or null if there is
none.
When testing on the tablet, the order of the calls of the various methods of
the RecognitionListener interface, not including the calls to the onRmsChanged
method, is as follows:
We process user input inside the onResults method. Its Bundle parameter
enables us to access the list of possible matching words and their
corresponding confidence scores. TABLE 10.17 lists the two methods of the
Bundle class that we use to retrieve the list of possible matching words and
ArrayList<String> words =
results.getStringArrayList( SpeechRecognizer.RESULTS_RECOGNITION );
float [ ] scores =
results.getFloatArray( SpeechRecognizer.CONFIDENCE_SCORES );
EXAMPLE 10.18 shows the edits in the MapsActivity class. At line 27, we
declare DELTA , a constant that we use to change the latitude or longitude of
the center of the map based on user input. At line 31, we declare a
SpeechRecognizer instance variable, recognizer .
the String equivalent value of one of the constants of the enum Directions .
We assign it to the String match . Note that match could be null at this point.
EXAMPLE 10.18 The MapsActivity class, Show A Map app, Version 5
Based on the value of match , we update the center of the map and refresh it.
At line 139, we retrieve the current center of the map and assign it to the
LatLng variable pos . At line 140, we retrieve the current zoom level of the
latitude component of pos by DELTA at line 144, and so on. Note that in order
to modify the latitude or longitude of the object that pos refers to, we re-
instantiate an object every time. This is because the latitude and longitude
instance variables of the LatLng class, although public , are final . Thus, the
following code does not compile:
Once pos is updated, we call updateMap at line 152 to redraw the map. We
call listen at line 154 to enable the user to enter more voice input; listen is
originally called at lines 97–98, after the map is displayed.
Method Description
void setRecognitionListener( Sets listener as the listener that will receive all the
RecognitionListener listener ) callback method calls.
FIGURE 10.10 shows the app running in the tablet after the user says Paris
and then North . Note that again, if we do not speak within a few seconds,
the onError method is called with a speech timeout error, and speech is no
longer processed. If we want to prevent that situation from happening, we
can add some logic to detect that situation and restart a new speech
recognizer if it occurs. We do that in Version 6.
10.9 Voice Recognition Part C, Moving the Map with
Voice Continuously, App Version 6
In Version 6 of the app, we want to keep listening to the user and keep
updating the map whenever the user tells us to, even if the user does not talk
for a period of time. If that happens, the onError method is called and the
speech recognizer stops listening so we need to restart it. Thus, we call the
listen method (line 129 of EXAMPLE 10.19) inside the onError method in
order to do that. In this way, we can keep talking to update the map.
constructor.
We can use the PackageManager class, from the android.content.pm
package, to retrieve information related to application packages installed
on a device.
The getPackageManager method, from the Context class, returns a
PackageManager instance.
14. You are executing inside the onActivityResult method, write the code to
count how many possible matching words have a confidence level of at
least 25%.
Intent data )
// your code goes here
15. Inside the AndroidManifest.xml file, write one line so that this app is
asking permission to access location.
16. A map is displayed on the screen and we have a GoogleMap reference
to it named myMap. Write the code to set its zoom level to 7.
17. A map is displayed on the screen and we have a GoogleMap reference
to it named myMap. Write the code to move the map so that it is
centered on the point with latitude 75.6 and longitude –34.9.
18. A map is displayed on the screen and we have a GoogleMap reference
to it named myMap. Write the code to move the map so that it is
centered on the point at 1600 Amphitheatre Parkway, Mountain View,
CA 94043.
19. A map is displayed on the screen and we have a GoogleMap reference
to it named myMap. Write the code to display a blue circle of radius 100
meters and centered at the center of the map.
20. A map is displayed on the screen and we have a GoogleMap reference
to it named myMap. Write the code to display an annotation located at
the center of the map. If the user clicks on it, it says WELCOME.
21. A map is displayed on the screen and we have a GoogleMap reference
to it named myMap. Make the map move so that the latitude of its
center increases by 0.2 and the longitude of its center increases by 0.3.
22. You are overriding the onResults method of the RecognitionListener
interface. If one of the matching words is NEW YORK, output USA to
Logcat.
Write an App
24. Write an app that displays the city where you live with zoom level 14 and
add two circles and an annotation to highlight things of interest.
25. Write an app that displays the city where you live and highlight, using
something different from a circle or a pin (for example, use a polygon),
something of interest (a monument, a stadium, etc.).
26. Write an app that asks the user to enter an address in several
TextViews. When the user clicks on a button, the app displays a map
centered on that address.
27. Write an app that asks the user to enter an address in several
TextViews. When the user clicks on a button, the app displays a map
centered on that address. When the user says a number, the map
zooms in or out at that level. If the number is not a valid level, the map
does not change.
28. Write an app that is a game that works as follows: The app generates a
random number between 1 and 10 and the user tries to guess it using
voice input. The user has three tries to guess it and the app gives some
feedback for each guess. If the user wins or loses (after three tries), do
not allow the user to speak again.
29. Write an app that draws circles at some random location on the screen
in a color of your choice. The radius of the circle should be captured
from speech input.
30. Write an app that displays a 3 × 3 tic-tac-toe grid. If the user says X ,
place an X at a random location on the grid. If the user says O , place an
O at a random location on the grid.
31. Write an app that animates something (a shape or an image) based on
speech input. If the user says left , right , up , or down , move that
object to the left, right, up, or down by 10 pixels.
32. Write an app that is a mini-translator from English to another language.
Include at least 10 words that are recognized by the translator. When
the user says a word in English, the app shows the translated word.
33. Write an app that simulates the hangman game. The user is trying to
guess a word one letter at a time by saying that letter. The user will lose
after using seven incorrect letters (for the head, body, two arms, two
legs, and one foot).
34. Write an app that is a math game. The app generates a simple addition
equation using two digits between 1 and 9. The user has to submit the
answer by voice, and the app checks if the answer is correct.
35. Write an app that is a math game. First, we ask the user (via speech)
what type of operation should be used for the game: addition or
multiplication. Then the app generates a simple equation using two digits
between 1 and 9. The user has to submit the answer by voice, and the
app checks if the answer is correct.
36. Write an app that displays a chessboard. When the app starts, all the
squares are black and white with alternating colors. When the user says
C8 (or another square), that square is colored in red.
37. Write an app that enables a user to play the game of tic-tac-toe by
voice. Include a Model.
CHAPTER ELEVEN: Using the GPS
and Location Services
Chapter Summary
Introduction
Many apps today use the location services of the Android device. One such
app is Uber, which people use to find a car to go somewhere for a fee. The
app uses the device’s Global Positioning System (GPS) to locate where
we are and find registered car drivers who are nearby. It uses satellites to
provide location and time information.
In this app, we intend to retrieve the location of the device, ask the user for a
destination, and calculate the distance and time to destination. In order for
the app to retrieve the location of the device using its GPS, it needs to
access Google Play services. We use an Empty Activity template for this
app. In Version 0 of this app, we show how we can check if a device can
access Google Play services. We need to do the following:
▸ Edit the build.gradle file in order to include Google Play services in the
compilation process.
EXAMPLE 11.1 shows the build.gradle (Module: app) file. We edit it in order
to make the Google Play services libraries available to the app (note that
there are two build.gradle files). The only addition is at line 26: we include the
appropriate version of the Google Play services libraries. At the time of this
writing, it is 9.4.0.
After editing the build.gradle file, we should click on the Sync Project with
Gradle Files icon on the tool bar, as shown on FIGURE 11.1.
Method Description
of these methods. These methods, except the build method, return the
GoogleApiClient.Builder reference that calls them, so that method calls can
disconnection.
TABLE 11.2 The ConnectionCallbacks and OnConnectionFailedListener
interfaces and their methods
builder.addConnectionCallbacks( this );
builder.addOnConnectionFailedListener( this );
builder.addApi( LocationServices.API );
.addOnConnectionFailedListener( this )
Constant Description
API A constant that should be used to pass to the addApi method of the
GoogleApi.Builder class during the process of building a GoogleApiClient.
Methods Description
void Establishes a connection with Google Play services. If successful, the onConnected
connect( ) method of ConnectionCallbacks is called. If unsuccessful, onConnectionFailed is
called.
gac.connect( );
false , the issue cannot be resolved and we can exit the app. If that method
returns true , the issue can be resolved and we want to try to resolve it. We
can do so by calling the startResolutionForResult method of
ConnectionResult . TABLE 11.5 shows both methods. This requires user
Method Description
else
exit the app
// inside onActivityResult
if( the problem was resolved )
When running the GPS app, Version 0, the output inside Logcat shows that
we are connected.
although we could hard code latitude and longitude data using the emulator.
To use our current location, we need to run it on a device. Because the app
uses location services from Google, we need to specify that in the
AndroidManifest.xml file. Thus, we need to do the following:
▸ Inside the MainActivity class, capture our current location and display
it.
retrieve the device’s location. Thus, in order to call that method, we need a
FusedLocationProviderApi reference to call it, and a GoogleApiClient
named gac , we can retrieve the current location using the following
sequence:
Method Description
float distanceTo( Location Returns the distance between this Location and destination, in
destination ) meters.
TABLE 11.7 The getLastLocation method of the FusedLocationProviderApi
interface
Method Description
Once we have a Location reference for the current location, we can retrieve
the latitude and longitude of that location by calling the getLatitude and
getLongitude methods as follows:
In Version 2, we ask the user to input an address for his or her destination,
and we provide a button to display the distance and time left to that
destination. Before building the GUI and the controller for it, we add the
TravelManager class, the Model for the app, shown in EXAMPLE 11.7. It
The timeToDestination method (lines 33–54) calculates and returns the time
that it takes to go from its Location parameter and destination, assuming a
speed of 55 miles per hour. At lines 35–36, we compute the distance to the
destination from its Location parameter in meters. At 37–38, we compute
the time in hours, as a float , that it takes to drive that distance at a speed
of 55 miles per hour. We then convert that value to the String result , which
represents the number of hours and the number of minutes at lines 40–52.
Note that if the time is either 0 or negative (line 51), we assign less than a
minute left to result at line 52.
We include the following elements in the GUI, all centered and lined up
vertically:
▸ One Button : when the user clicks on it, we update the distance and
time to destination in the two TextViews.
EXAMPLE 11.8 shows the updated activity_main.xml file. We give all the
elements an id (lines 13, 18, 23, 28) so we can position them and retrieve
them as needed in the MainActivity class. We want to specify the
WRAP_CONTENT value for both the width and height of all the elements. We also
want to have 20 pixels between them and center them horizontally. Thus, we
define the WrappedAndCentered style in styles.xml (lines 11–16 of EXAMPLE
11.9) and use it for all the four elements (lines 15, 20, 25, 32). We specify a
hint, Enter destination , defined in the strings.xml file (line 3 of EXAMPLE
11.10) for the EditText at line 14. At line 30, we define the text inside the
button as the String Update , defined at line 4 of Example 11.10. At line 31,
we specify that the updateTrip method is called when the user clicks on the
Button .
▸ Add instance variables for the EditText and the two TextViews of the
GUI and a String to store the destination address.
▸ Delete the body of the onConnected method: we want to wait for the
user to input a destination address before we do anything.
We first retrieve the destination address entered by the user at line 51. If the
destination address is different from the current destination address (line 53),
we update the current destination address (line 54). We then try to geocode
it into a Location object (lines 57–59). Geocoding is the process of
converting a street address into a (latitude, longitude) coordinate. TABLE
11.8 shows a constructor and a getFromLocationName method of the Geocoder
class. If the geocoding is successful (line 60), we assign the Location to the
destination instance variable of the TravelManager (line 66). We keep track
end up in the catch block at line 69. We retrieve the current location at lines
73–74. If we are successful retrieving the current location and if the
geocoding was successful (line 75), we update the two TextViews displaying
the distance and time left to the destination at lines 76–77. The manager
instance variable calls the milesToDestination and timeToDestination
methods, passing the current location.
Methods Description
List getFromLocationName( String Returns a list of Address objects that match locationName.
locationName, int maxResults ) Throws an IllegalArgumentException and an IOException.
FIGURE 11.3 The GPS app, Version 2, running inside the tablet
FIGURE 11.3 shows the app running inside the tablet after the user enters
the address of the White House and clicks on update.
Method Description
Method Description
Method Description
LocationRequest Sets the interval for location updates in milliseconds. Returns this
setInterval( long ms ) LocationRequest so method calls can be chained.
At that point, we will receive updates and the onLocationChanged method will
be called at the frequency specified by the LocationRequest . Its Location
parameter stores the most recent location, as follows.
public void onLocationChanged( Location location ) {
TABLE 11.11 shows the two most important methods of the LocationRequest
class. We use the setInterval method to specify the desired frequency, in
milliseconds, of location updates. A location update consumes power, and
frequent updates can drain the device’s battery. We use the setPriority
method to specify a desired accuracy level, which in turn is a hint at what
source to use, GPS or Wi-Fi and cell tower. However, other factors, such as
the current location and availability or such sources, as well as the device
itself, can impact the accuracy of the location returned. All the set methods
of the LocationRequest class return the LocationRequest reference that calls
them, so that we can chain method calls. If we do not want updates—if we
have not moved by a minimum distance—we can use the
setSmallestDisplacement method and specify such minimum distance
between two consecutive updates. For example, in a travel app, the user
does not need distance updates if he or she is stuck in traffic and is not
moving.
request.setPriority( LocationRequest.PRIORITY_HIGH_ACCURACY );
request.setSmallestDisplacement(100); // 100 meters minimum
TABLE 11.12 shows the constants of the LocationRequest class that we can
use as the argument of the setPriority method. The higher the accuracy
requested, the higher the battery usage. Without specifically setting the
priority by calling setPriority , the default priority, defined by
PRIORITY_BALANCED_POWER_ACCURACY , is used. If we build a golf app that
measures the distance to the pin, we want accuracy to the nearest yard or
meter, so we specify the highest possible accuracy. For this travel app, the
default accuracy is adequate.
Indeed, if we test the app without moving, we do not want to set a minimum
distance for location updates. It is actually possible that we get disconnected
in the middle of the onConnected method, in which case the app will crash
when we try to set up location updates. Thus, we test that we are connected
(line 104) before we set up location updates (line 105). If we lost the
connection, we try to establish another connection (line 107).
If the app is running and goes in the background, we want to stop requesting
location updates. When that happens, the onPause method (lines 92–96) is
called automatically. We disable location updates at line 95 by calling the
removeLocationUpdates method (shown in Table 11.10). When the app comes
FIGURE 11.4 shows the Logcat output of the app when running on the
author’s tablet. The output shows that the accuracy of the location returned
by the GPS can vary from measurement to measurement. In this instance, it
varies from 30 meters to 64.5 meters. One nanosecond is equal to 10–9
second. The interval frequency of the measurements is always very close to
the 30 seconds specified. It varies between 30.016 seconds (time elapsed
between the last and next to last measurements) and 30.034 seconds (time
elapsed between the third and fourth measurements). When the app starts,
the two TextViews show 0 mile and seconds left . If we enter an address
and wait, the distance and time to that location is automatically updated after
fewer than 30 seconds without clicking on the Update button, illustrating that
the onLocationChanged method has been called.
EXAMPLE 11.12 The MainActivity class, GPS app, Version 3
Method Description
long getElapsedRealtimeNanos( ) Returns the time since the last boot in nanoseconds.
FIGURE 11.4 Logcat output of the GPS app, Version 3, when running
inside the tablet
Accuracy can be device and location dependent too. Further testing on the
author’s tablet from his home shows an accuracy varying from 12 meters to
36 meters when setting the priority using PRIORITY_HIGH_ACCURACY (by un-
commenting line 102 in Example 11.12).
Chapter Summary
The GoogleApiClient class is the entry point to Google Play services.
If Google Play services are not available, it is possible that they can be
made available via user interaction.
The Builder static inner class of GoogleApiClient enables us to build a
GoogleApiClient .
Write an App
16. Expand the app in the chapter by calculating the time left using the
speed between the last two location updates rather than 55 miles per
hour.
17. Expand the app in the chapter by calculating the time left using the
average speed between the last five location updates rather than 55
miles per hour. If there have not been five location updates yet, use 55
miles per hour.
18. Write an app that identifies the closest location from where the user is
among five locations. The five locations can be hard coded inside the
Model of the app.
19. Write an app that identifies how far the user is from five locations. The
five locations can be hard coded inside the Model of the app. As the
user moves, the five distances should be updated at regular intervals.
The app should let the user decide the frequency of the updates and the
accuracy of the locations (priority).
20. Write an app that enables the user to select a friend in a list, retrieve his
or her address, and calculate how far that friend is. The list should be
stored either in a file or can be hard coded. Include a Model.
21. Same as 20 but the list is not hard coded, is persistent and is
expandable. The user can add to the list.
22. Same as 21 but the user can also delete and update a friend’s data from
the list.
23. Write a 1-hole golf app. The app allows the user to enter the location of
the middle of the green by simply clicking on a button. That data should
be stored on the device. The app allows the user to compute the
distance from where the user is to the middle of the green location
(previously stored).
24. Same as 23 but the app is an 18-hole golf app, not a 1-hole golf app.
25. Write an app that stores a list of locations and descriptions on the
device. The user can enter a description for a location (where the user
is) and can click on a button to retrieve the current location and write to
the device’s storage the description and the location. The app also
allows the user to retrieve the list of descriptions and locations and
display them.
26. Write an app that sends an email and includes the latitude and longitude
of the user’s location in the body of the email.
27. Same as 26 but convert the current location to an address and include
the current address in the body of the email.
28. Write an app that helps the user retrieve the parking location of his or
her car. When the user parks his or her car, the user clicks on a button
to store the car’s location. If the user wants to know where his or her
car is, the app displays a map with two circles showing both the car and
the user location.
CHAPTER TWELVE: Using Another
App within the App: Taking a Photo,
Graying It, and Sending an Email
Chapter Summary
Introduction
An app can start another app and process its results. It is more and more
common for apps to communicate with and use other apps on a device. For
example, we might want to access the phone directory of an app, or look for
photos or videos. Photo apps are very popular among smartphone users.
They include features to turn a color picture into a black-and-white picture,
add a frame around a picture, post a picture on some social media website,
etc. In this chapter, we learn how we can use another app: in particular, we
learn how to use the camera and email apps inside another app. We build an
app that uses the camera app (or at least one of them) of a device, takes a
picture, processes that picture, and uses an email app to send it to a friend.
The Android Studio environment does not include a camera so we need an
actual device, phone or tablet, to test the apps in this chapter.
12.1 Accessing the Camera App and Taking a Picture,
Photo App, Version 0
We can test if the device has a given feature by calling the hasSystemFeature
method of the PackageManager class, passing the constant of the
PackageManager class that corresponds to that feature, and that methods
Constant Description
)
// Desired feature is present; use it
else
In order to run the camera app, or more exactly “a” camera app, we first
check that the device has a camera app. Table 12.1 shows two constants
that we can use for that purpose. We can test if the device has a camera
app by calling the getSystemFeature method of the PackageManager class,
passing the FEATURE_CAMERA constant of the PackageManager class, as shown
by the code sequence that follows:
Method Description
void startActivityForResult( Intent Start an Activity for intent. If requestCode is >= 0, it will be
intent, int requestCode ) returned as the first parameter of the onActivityResult method.
Once we know that the device has a feature we want to use, we can launch
an activity to use that feature. In this case, we are interested in capturing the
photo picture and in displaying it on the screen. Generally, if we want to start
an activity and we are interested in accessing the results of that activity, we
can use the startActivityForResult and the onActivityResult methods of the
Activity class, both shown in TABLE 12.2. The onActivityResult executes
In order to create an Intent to use a feature of the device, we can use the
following Intent constructor:
public Intent( String action )
The String action describes the device feature we want to use. Once we
have created the Intent , we can start an activity for it by calling the
startActivityForResult method of the Activity class. The code sequence
that follows illustrates how we can start another app within an app:
( MediaStore.ACTION_IMAGE_CAPTURE );
// actionCode is some integer
startActivityForResult( takePictureIntent, actionCode );
Intent Bundle getExtras( Returns a Bundle object, which encapsulates a map (key/value
) pairs) of what was placed in this Intent.
Base BundleObject get( Returns the Object that was mapped to key.
String key )
We retrieve the Bundle object of the Intent using the getExtras method
shown in TABLE 12.3. That Bundle object stores a map of key/value pairs
that are stored in the Intent . With that Bundle object, we call the get
method, inherited from the BaseBundle class by Bundle and also shown in
Table 12.3, passing the key data in order to retrieve the Bitmap . Since the
get method returns an Object and we expect a Bitmap , we need to typecast
Method Description
void setImageBitmap( Bitmap bitmap ) Sets bitmap as the content of this ImageView.
void setImageDrawable( Drawable drawable ) Sets drawable as the content of this ImageView.
void setImageResource( int resource ) Sets resource as the content of this ImageView.
In this app, we are getting the image dynamically, as the app is running,
when the user takes a picture with the camera app. When the picture is
taken, we retrieve the extras and then the corresponding Bitmap object using
the get method of the BaseBundle class. Thus, we use the setImageBitmap
method in this app to fill an ImageView with a picture that we take with the
camera app. If we already have a Bitmap reference named bitmap , we can
place it inside an ImageView named imageView using the following statement:
imageView.setImageBitmap( bitmap );
We want to notify potential users that this app uses the camera. Thus, we
define a uses-feature element inside the manifest element of the
AndroidManifest.xml file as follows:
To keep things simple, we only allow this app to run in vertical orientation, so
we add this code inside the activity element:
android:screenOrientation="portrait"
CENTER center Centers the image inside the ImageView without doing any
scaling.
CENTER_INSIDE centerInside Scales down the image as necessary so that it fits inside the
ImageView and is centered, keeping the image’s aspect ratio.
and position a picture inside an ImageView . TABLE 12.5 lists some of its
possible values. At line 16, we use the value fitCenter to scale the picture
perfectly so that it fits and position it in the middle of the ImageView . At lines
6–9, we provide some padding as defined in the dimens.xml file.
When the user runs the app and takes a picture, we execute inside the
onActivityResult method, coded at lines 34–42. We retrieve the Bundle from
the Intent parameter data at line 38. Since the Intent was created to take
a picture with a camera app, we expect that the Bundle associated with that
intent contains a Bitmap storing that picture. The get method of the Bundle
class, when passed the String data , returns that Bitmap . We retrieve it at
line 39. At line 40, we set the image inside imageView to that Bitmap .
When we run the app, the camera app starts. FIGURE 12.1 shows our app
after the user takes a picture. FIGURE 12.2 shows our app after the user
clicks on the Save button. The picture shows up on the screen.
FIGURE 12.1 The Photo app, Version 0, after taking the picture
FIGURE 12.2 The Photo app, Version 0, after clicking on Save
In Version 1 of our app, we gray the picture with a hard coded formula. Our
Model for this app is the BitmapGrayer class. It encapsulates a Bitmap and a
graying scheme that we can apply to that Bitmap .
In the RGB color system, there are 256 shades of gray; a gray color has the
same amount of red, green, and blue. Thus, its RGB representation looks
like (x, x, x) where x is an integer between 0 and 255. (0, 0, 0) is black and
(255, 255, 255) is white.
TABLE 12.6 lists some static methods of the Color class of the
android.graphics package. We should not confuse that class with the Color
class of the java.awt package.
Method Description
static int argb( int alpha, int red, int Returns a color integer value defined by the alpha, red,
green, int blue ) green, and blue parameters.
static int alpha( int color ) Returns the alpha component of color as an integer between
0 and 255.
static int red( int color ) Returns the red component of color as an integer between 0
and 255.
static int green( int color ) Returns the green component of color as an integer between
0 and 255.
static int blue( int color ) Returns the blue component of color as an integer between
0 and 255.
▸ Use the red, green, and blue components of the pixel in order to
generate a gray shade for it.
▸ Use the same transformation formula to generate the gray shade for
every pixel.
For example, if red, green, and blue are the amount of red, green, and blue
for a pixel, we could apply the following weighted average formula so that the
new color for that pixel is (x, x, x):
x = red * redCoeff + green * greenCoeff + blue * blueCoeff
where
and
This guarantees that x is between 0 and 255 , that the color of each pixel is
a shade of gray, and that the color transformation process is consistent for
all the pixels in the picture.
color )
Method Description
int getPixel( int x, int y ) Returns the integer representation of the color of the pixel at
coordinate( x, y ) in this Bitmap.
void setPixel( int x, int y, int color ) Sets the color of the pixel at coordinate( x, y ) in this Bitmap to
color.
static Bitmap createBitmap( int w, Returns a mutable Bitmap of width w, height h, and using config
int h, Bitmap.Config config ) to create the Bitmap.
EXAMPLE 12.3 shows the BitmapGrayer class. It has four instance variables
(lines 7–10): originalBitmap , a Bitmap , and redCoeff , greenCoeff ,
blueCoeff , three floats with values between 0 and 1 . They represent the
amounts of red, green, and blue that we take into account in order to
generate a gray shade for each pixel of originalBitmap .
The three mutators, coded at lines 32–39, 41–48, and 50–57, enforce the
following constraints:
logic.
The grayScale method, coded at lines 59–75, returns a Bitmap with the
following characteristics:
▸ If red , green , and blue are the amount of red, green, and blue of a
given pixel in originalBitmap , the color of the pixel in the returned Bitmap
at the same x- and y-coordinates is gray and the value of the gray shade
is red * redCoeff + green * greenCoeff + blue * blueCoeff.
EXAMPLE 12.3 The BitmapGrayer class, Photo app, Version 1
At lines 64–73, we use a double loop to define the color of each pixel in
bitmap . We retrieve the color of the current pixel in originalBitmap at line 66.
We calculate the shade of gray for the corresponding pixel in bitmap at lines
67–69, create a color with it at line 70, and assign that color to that pixel at
line 71. We return bitmap at line 74.
EXAMPLE 12.4 shows the MainActivity class, Version 1. The only change
from Version 0 is that we gray the picture that is inside imageView . We
declare a BitmapGrayer instance variable, grayer , at line 14. Inside
onActivityResult , we instantiate grayer at line 41 with bitmap and three
default values for the red, green, and blue coefficients for the graying
transformation process. Note that it would be too early to instantiate grayer
inside the onCreate method because we need the picture to be taken so that
we have a Bitmap object for that picture. At line 42, we call the grayScale
method with grayer and assign the resulting Bitmap to bitmap .
FIGURE 12.3 shows our app running inside the tablet after the user clicks on
the Save button. The picture is now black-and-white.
In Version 1, the formula to generate the shade of gray for each pixel in the
Bitmap is hard coded with coefficients 0.34, 0.33, and 0.33. In Version 2, we
allow the user to specify the coefficients in the formula. We include three
seek bars, also often called sliders, in the user interface. They allow the user
to define the red, green, and blue coefficients that are used to calculate the
shade of gray for each pixel.
FIGURE 12.4 Inheritance hierarchy of SeekBar
Visually, a seek bar includes a progress bar and a thumb. Its default
representation includes a line (for the progress bar) and a circle (for the
thumb). We can define a seek bar in a layout XML file using the element
SeekBar . TABLE 12.8 shows some of the XML attributes that we can use to
define a seek bar using XML, along with their corresponding methods if we
want to control a seek bar by code. We can set the current value of the seek
bar, called progress, by using the android:progress attribute or by code
using the setProgress method. Its minimum value is 0 , and we can define its
maximum value using the android:max attribute or the setMax method.
The progress bar and the thumb are light blue by default. We can customize
a seek bar by replacing the progress bar or the thumb by a drawable
resource, for example an image from a file or a shape. We can do that inside
an XML layout file or programmatically. In this app, since each seek bar
represents the coefficient of a particular color, we customize the thumb of
each of the three seek bars to reflect the three colors that they represent:
red, green, and blue. We define three drawable resources, one per color,
and use them in the activity_main.xml file.
EXAMPLE 12.5 shows the red_thumb.xml file. It defines a shape that we use
for the thumb of the red seek bar. It defines an oval (line 4) of equal width
and height (lines 5–7), that is, a circle. At lines 8–9, we define the color
inside the circle, a full red. At lines 10–12, we define the thickness (line 11),
and the color of the outline of the circle (line 12), also red but not completely
opaque.
EXAMPLE 12.5 The red_thumb.xml file, Photo app, Version 2
The green_thumb.xml and blue_thumb.xml files are similar, using colors #F0F0
and #F00F , respectively, with the same opacity values as in the
red_thumb.xml .file
We need to add three SeekBar elements to our XML layout file. Other than
their id and their thumb, these SeekBar elements are identical, so we use a
style to define some of their attributes. We edit the styles.xml file and add a
style element for the SeekBar elements. EXAMPLE 12.6 shows the
styles.xml file. The seekBarStyle , defined at lines 11–18, is used in
activity_main.xml to style the seek bars. It defines all the attributes that the
seek bars have in common.
At line 12, we allocate the same amount of horizontal space to all the
elements using seekBarStyle and we center them vertically at line 15. We
initialize any seek bar using seekBarStyle with a progress value of 0 at line
16 and give it a max value of 100 at line 17.
EXAMPLE 12.7 shows the activity_main.xml file. It organizes the screen into
two parts: an ImageView (lines 13–18) at the top that occupies four fifths of
the screen (line 14), and a LinearLayout (lines 20–39) at the bottom that
contains one fifth of the screen (line 22), organizes its components
horizontally (line 21), and contains three seek bars (lines 26–29, 30–33, 34–
37).
We give each seek bar an id (lines 27, 31, 35) so we can retrieve it using the
findViewById method. At lines 28, 32, and 36, we customize the three seek
We expect the user to interact with the seek bars and we need to process
that event. In order to do that, we need to implement the
OnSeekBarChangeListener interface, a public static inner interface of the
SeekBar class. It has the three methods, shown in TABLE 12.9. In this app,
we only care about the value of each slider as the user interacts with it. Thus
we override the onStartTrackingTouch and onStopTrackingTouch methods as
do-nothing methods and place our event handling code inside the
onProgressChanged method. Its first parameter, seekBar , is a reference to the
SeekBar that the user is interacting with, and enables us to determine if the
user is changing the red, green, or blue coefficients of our graying formula.
Its second parameter, progress , stores the progress value of seekBar : we
use it to set the coefficient of the corresponding color. Its third parameter
fromUser , is true if the method was called due to user interaction, or false if
Method Description
void onStartTrackingTouch( SeekBar seekBar ) Called when the user starts touching
the slider.
void onStopTrackingTouch( SeekBar seekBar ) Called when the user stops touching
the slider.
void onProgressChanged( SeekBar seekBar, int progress, Called continuously as the user
boolean fromUser ) moves the slider.
redBar , greenBar , and blueBar , are defined as instance variables at line 18.
Inside the onCreate method, at lines 25–27, we assign the three SeekBars
defined in the activity_main.xml layout file to these three instance variables.
At line 29, we declare and instantiate a GrayChangeHandler object. We
register it on redBar , greenBar , and blueBar , at lines 30–32 as their listener.
which slider triggered the event. If it was the red slider (line 62), we call
setRedCoeff with the value of progress scaled back to between 0 and 1 at
line 63. It is possible that the new redCoeff value is such that the sum of the
three coefficients is 1 or less. In this case, we want to prevent the thumb of
the seek bar to go past the corresponding value of redCoeff (i.e., 100 times
the value of redCoeff ). Either way, we position the thumb based on the value
of redCoeff by calling the setProgress method at line 64. We do the same if
the seek bar is the green or blue seek bar at lines 65–67 and 68–70,
respectively. Finally, we retrieve the Bitmap generated by the new
coefficients at line 72 and reset the picture to that Bitmap at line 73.
EXAMPLE 12.8 The MainActivity class, Photo app, Version 2
FIGURE 12.5 shows our app running after the user clicks on the Save button
and the user interacts with the seek bars.
FIGURE 12.5 The Photo app, Version 2, after clicking on Save and user
interaction
Version 2 provides the user with the ability to customize the formula used to
gray the picture, but the seek bars do not give a precise feedback regarding
the value of each color coefficient. In Version 3, we add three TextViews
below the three SeekBar components to display the color coefficients.
It would be annoying for the user to display numbers with a large number of
digits after the decimal point. We limit the number of digits after the decimal
point to two. In order to implement this, we build a utility class, MathRounding ,
with a static method, keepTwoDigits , that takes a float parameter and
returns the same float with only the first two digits after the decimal point.
EXAMPLE 12.9 shows the MathRounding class. The keepTwoDigits method,
coded at lines 4–7, multiplies the parameter f by 100 and casts the result to
an int at line 5. It then casts that int to a float , divides it by 100 , and
returns the result at line 6. We cast the int to a float in order to perform
floating-point division.
We could use this method inside the BitmapGrayer class or inside the
MainActivity class. Because we think this feature relates to how we display
things rather than the functionality of the app, we use that method inside the
Controller for the app, the MainActivity class, rather than the Model, the
BitmapGrayer class. Thus, the BitmapGrayer class does not change.
We need to add three TextView elements to our XML layout file. Other than
their id, these TextView elements are identical, so we use a style for them.
We edit the styles.xml file and add a style element for the TextView .
EXAMPLE 12.10 shows the styles.xml file.
EXAMPLE 12.10 The styles.xml file, Photo app, Version 3
The textStyle style, at lines 20–28, defines the style we use for the three
TextView elements. We specify a font size of 30 at line 26, which should
work well for most devices, and specify that the text should be centered at
line 24, make it bold at line 25, and initialize it to 0.0 at line 27.
and another LinearLayout (lines 41–57) that also occupies one-tenth of the
screen (line 43). The three SeekBar elements, defined at lines 26–37, now
only take one-tenth of the screen.
EXAMPLE 12.11 The activity_main.xml file, Photo app, Version 3
The three TextView elements are defined at lines 47–55. We give each of
them an id at lines 48, 51, and 54 so that we can access them using the
findViewById method in the MainActivity class. They are styled with
Whenever the user interacts with one of the seek bars, we need to update
the corresponding TextView . We do that at lines 71–72, 76–77, and 81–82,
after we update grayer based on the progress value of the seek bar that the
user is interacting with.
FIGURE 12.6 shows our app running after the user clicks on the Save button
and then interacts with the seek bars.
EXAMPLE 12.12 The MainActivity class, Photo app, Version 3
FIGURE 12.6 The Photo app, Version 3, after clicking on Save and user
interaction
TABLE 12.10 Internal storage, external storage, and methods of the Context
class
When to Use If we want to If we are not concerned about restricting file access.
restrict file
access.
When user App-related files App-related files are deleted only if they are located in a
uninstalls the are deleted. directory obtained using getExternalFilesDir.
app
change the percentage of the screen taken by the picture. At line 14, we set
the android:layout_weight attribute value of the ImageView element to 7 so
that it takes 70% of the screen. The SeekBar elements still take 10% (line
22), the TextView elements still take 10% (line 43), and the button takes 10%
of the screen (line 61).
file in Version 5, when we send the picture to a friend via email, adding that
file as an attachment. In addition to a Bitmap parameter, this method also
takes an Activity parameter, which we use to access a directory for
external storage.
Constant Description
storage on the device and it has read and write access. We can test if there
is external storage with the following code sequence:
We get the storage state at lines 22–23 and test if external storage is
available on the device at line 26. If it is not, we throw an IOException at line
61. If it is, we obtain the path of a directory for the external storage at lines
27–29 by calling the getExternalFilesDir method of the Context class,
inherited by the MainActivity class via the Activity class. TABLE 12.12
shows the getExternalFilesDir method. On the author’s tablet, the directory
path returned if the argument is null is:
/storage/emulated/0/Android/data/com.jblearning.photograyingv4/files
If the argument is MyGrayedPictures , the directory path returned is:
/storage/emulated/0/Android/data/com.jblearning.photograyingv4/files/MyGrayedPictu
Method Description
File Returns the absolute path of an external storage directory where the app can
getExternalFilesDir( place its files. This is app dependent. If type is not null, a subdirectory named
String type ) with the value of type is created.
The Environment class includes some constants we can use for standard
names for directories. Table 12.11 lists some of them. Note that we are not
required to use a directory named Music to store music files, but it is good
practice to do so. At line 29, we use the DIRECTORY_PICTURES constant, whose
value is Pictures .
12.13, returns the number of milliseconds since the last boot. We combine it
with today’s date in order to generate the file name. It is virtually impossible
for the user to modify one seek bar and click on the SAVE PICTURE button
within the same millisecond as the previous click. Thus, our naming strategy
guarantees a unique file name every time we generate one. We generate
that unique file name at lines 30–33. At lines 35–36, we create a file using
that file name and located in the external storage directory.
We retrieve the amount of free space in the directory where that file will be
at line 37 and the number of bytes needed to write the bitmap into that file at
line 38, using the getByteCount method of the Bitmap class, shown in TABLE
12.14. We test if we have enough space in the directory for the number of
bytes we need (times 1.5 , to be safe) at line 39. If we do not, we throw an
IOException at lines 57–58. If we do, we attempt to open that file for writing
at line 42 and try to write bitmap to it at lines 43–45. If for some reason we
are unsuccessful, we execute inside the catch block and we throw an
IOException at line 53. Because the FileOutputStream constructor can throw
In order to write the Bitmap bitmap to our file, we use the compress method
of the Bitmap class, shown in Table 12.14. The first argument of the
compress method is the file compression format. The Bitmap.CompressFormat
enum contains constants that we can use to specify such a format, as listed in
TABLE 12.15. The second argument of the compress method is the quality of
the compression. Some compression algorithms, such as the one for the
PNG format, are lossless, and ignore the value of that parameter. The third
argument of the compress method is the output stream. If we want to write to
a file, we can use a FileOutputStream reference, since FileOutputStream is a
subclass of OutputStream . At line 45, we specify the PNG format and a
compression quality level of 100 (although it does not matter here).
TABLE 12.13 The elapsedRealtime method of the SystemClock class
Method Description
static long Returns the time since the last boot in milliseconds, including sleep
elapsedRealtime( ) time.
TABLE 12.14 The getByteCount and compress methods of the Bitmap class
Method Description
public final int Returns the minimum number of bytes needed to store this Bitmap.
getByteCount( )
public boolean compress( Writes a compressed version of this Bitmap to stream, using the
Bitmap.Compress.Format format format and a compression quality specified by quality—quality is
format, int quality, between 0 (smallest size) and 100 (highest quality). Some formats like
OutputStream stream ) PNG, which is lossless, ignore the value of quality.
Constant Description
After closing the file at line 46, we test if the compress method returned true
at line 47 (i.e., the Bitmap was correctly written to the file). If it does, we
return file at line 48, otherwise, we throw an IOException at lines 50–51.
Since the app writes to external storage, it.is mandatory to notify potential
users and to include a uses-permission element inside the manifest element
of the AndroidManifest.xml file as follows:
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
FIGURE 12.7 shows our app running after the user interacts with the seek
bars. The SAVE PICTURE button enables the user to save the grayed
picture.
When we run the app and save a picture, the app gives a Toast feedback.
We can also open the directory where the pictures stored by the app are
located in the device’s external storage and look for our files. FIGURE 12.8
shows a screenshot of the tablet after we navigate to the data directory,
after successively clicking on the My Files native app, then storage ,
emulated , 0 , Android , and data on the left pane. Next, we click on the
and we run the app again, then navigate to the MyPix directory next to the
Pictures directory, MyPix contains the new grayed pictures generated by the
app.
FIGURE 12.7 The Photo app, Version 4, after clicking on Save and user
interaction
FIGURE 12.8 Screenshot of the tablet as we navigate to find our stored
grayed pictures
Now that we know how to save the picture to a file, we can use that file as
an attachment to an email. In Version 5, we replace the SAVE PICTURE text
inside the button with SEND PICTURE, and we enable the user to share the
picture or send it to a friend as an attachment to an email.
We modify the code for the button in the activity_main.xml file as shown in
EXAMPLE 12.16.
EXAMPLE 12.17 shows the sendEmail method (lines 67–87) and the new
import statements (lines 6 and 15) of the MainActivity class. The sendEmail
method replaces the savePicture method of Example 12.16, Photo app,
Version 4.
At line 70, we write bitmap to external storage, returning the File reference
file . At line 71, we call the fromFile method of the Uri class, shown in
TABLE 12.16, and create uri , a Uri reference for file . We need a Uri
reference in order to attach a file to an email. The Uri class, from the
android.net package, not to be confused with the URI class from the
Method Description
static Uri fromFile( File file ) Creates and returns a Uri from file; encodes characters except /.
At lines 72–79, we create and define an Intent to send some data, using
constants of the Intent class shown in TABLE 12.17. At line 72, we use the
ACTION_SEND constant to create an Intent to send data. We set the
Multipurpose Internet Mail Extensions (MIME) data type of the Intent at line
73 using the setType method shown in TABLE 12.18. We define a subject
line at lines 76–77, mapping it to the value of EXTRA_SUBJECT . We add uri as
an extra to the Intent at line 79, mapping it to the value of EXTRA_STREAM :
that means that the file represented by file becomes the attachment when
we send the data. We add and comment out lines 74–75 and 78. They show
how we can add a list of recipients and email body text, using the
EXTRA_EMAIL and EXTRA_TEXT constants. For this app, it is better to let the
Constant Description
TABLE 12.18 The setType and createChooser methods of the Intent class
Method Description
Intent setType( String Sets the MIME data type of this Intent; returns this Intent so that method
type ) calls can be chained.
Intent putExtra( String Adds data to this Intent. Name is the key for the data, value is the data.
name, dataType value There are many putExtra methods with various data types. It returns this
) Intent so that method calls can be chained.
static Intent Creates an action chooser Intent so that the user can choose among
createChooser( Intent several activities to perform via a user interface. Title is the title of the user
target, CharSequence interface.
title )
At lines 81–82, we call the createChooser method of the Intent class, shown
in Table 12.18, and call startActivity , passing the returned Intent
reference. The createChooser method returns an Intent reference such that
when we start an activity with it, the user is presented with a choice of apps
that match the Intent . Since we use the SEND_ACTION constant to create the
original Intent , apps that send data, such as email , gmail , and other apps,
will be presented to the user.
FIGURE 12.9 shows our app running after the user interacted with the seek
bars. The SEND PICTURE button enables the user to save and send the
grayed picture.
FIGURE 12.10 shows our app running after the user clicks on the SEND
PICTURE button. All the available apps matching the intent to send data
show.
FIGURE 12.9 The Photo app, Version 5, after the user interacted with
the seek bars
FIGURE 12.10 The Photo app, Version 5, after the user clicks on SEND
PICTURE
FIGURE 12.11 The Photo app, Version 5, after selecting the email app
FIGURE 12.11 shows our app running after the user selects email. We are
now inside the email app, and we can edit it as we would edit any email.
Chapter Summary
An app can use another app.
In order to use a feature or another app, we include a uses-feature
element in the AndroidManifest.xml file.
The hasSystemFeature method of the PackageManager class enables us to
check if a feature or an app is installed on an Android device.
We can use the ACTION_IMAGE_CAPTURE constant of the Intent class to
open a camera app.
We can use the ImageView class as a container to display a picture.
The Bitmap class encapsulates an image stored based on a format like
PNG or JPEG.
If a picture is stored in a Bitmap , we can access the color of every pixel
of the picture.
The SeekBar class, from the android.widget package, encapsulates a
slider.
We can capture and process SeekBar events by implementing the
SeekBar.OnSeekBarChangeListener interface.
We can use the compress method of the Bitmap class to write a Bitmap
to a file.
The fromFile method of the Uri class converts a File to a Uri .
We can use the ACTION_SEND constant from the Intent class to create
an Intent to send an email using an existing email app.
We can use the createChooser static method of the Intent class to
display several possible apps matching a given Intent .
Exercises, Problems, and Projects
Multiple-Choice Exercises
try {
catch( Exception e ) {
Write an App
23. Write an app that enables the user to take a picture, display it, and has
a button to eliminate the red color component in all the pixels of the
picture. Include a Model.
24. Write an app that enables the user to take a picture, display it, and has
three radio buttons to eliminate either the red, green, or blue color
component in all the pixels of the picture. Include a Model.
25. Write an app that enables the user to take a picture, display it, and has
a button to enable the user to change the picture to a mirror image of
itself (the left and right sides of the picture are switched). If the user
clicks on the button two times, the app shows the original picture again.
Include a Model.
26. Write an app that enables the user to take a picture, display it, and has
a button to enable the user to display a frame (of your choice) inside the
picture. You can define a frame with a color and a number of pixels for
its thickness. Include a Model.
27. Same as 26, but add a button to send an email with the framed picture.
Include a Model.
28. Write an app that enables the user to take a picture, display it, and has
a button to enable the user to display a frame around the picture of
several possible colors (you can have at least three radio buttons for
these colors). Include a Model.
29. Write an app that enables the user to take two pictures, and display
them in the same ImageView container like an animated GIF: picture 1
shows for a second, then picture 2 shows for a second, then picture 1
shows for a second, and so on.
30. Same as 29, but add a button to send the two pictures via email.
31. Write an app that enables the user to take a picture, display it, and add
some text of your choice on the picture. You can use the Canvas and
Paint classes, in particular the Canvas constructor that takes a Bitmap
as a parameter.
32. Same as 31, but add a bubble (you can hard code the drawing of the
bubble). The text goes inside the bubble.
33. Write an app that enables the user to take a picture, display it, and add
a vignette effect. A vignette effect is a reduction of the image’s
saturation at the periphery of the image. Include a Model.
CHAPTER THIRTEEN: XML and
Content Apps
Chapter Summary
Introduction
Smartphones and tablets include a browser so users can surf the Internet.
However, a regular web page displayed on a small screen can be
overwhelming. Content apps are apps that display selected content from a
website so that the amount of information is manageable and the user
experience is optimal. Such content apps exist for many sites, particularly
social media websites and news sites. Often, websites offer frequently
updated, consistently formatted information available to the outside world in
general and app developers in particular. One such formatted information is
called a Really Simple Syndication (RSS) feed. A typical RSS feed, not
only is formatted in XML, but also often uses generic elements such as item ,
title , link , description , or pubDate . In order to build a content app for a
▸ DOM parsers
▸ SAX parsers
A Document Object Model (DOM) parser converts an XML file into a tree.
Once the XML has been converted into a tree, we can navigate the tree to
modify elements, add elements, delete elements, or search for elements or
values. This is similar to what a browser does with an HTML document: it
builds a tree, and once that tree data structure is in memory, it can be
accessed, searched, or modified using JavaScript.
For example, let’s assume that we want to parse the XML document shown
in EXAMPLE 13.1.
A DOM parser would convert that XML document to a tree similar to the tree
shown in FIGURE 13.1. There are actually a few more branches in the tree
than Figure 13.1 shows because empty strings between elements are also
branches of the tree.
FIGURE 13.2 A list of items for the XML document in Example 13.1
We will create a raw directory within the res directory and place a file named
test.xml containing the XML document in Example 13.1 in it. Later in the
chapter, we read an XML document dynamically from a URL. At this point,
we do not create an Item class yet. We do that in Version 1.
The DefaultHandler class is the base class for SAX level 2 event handlers.
The Default-Handler class implements four interfaces: EntityResolver ,
DTDHandler , ContentHandler , and ErrorHandler . It implements all the methods
that it inherits from these four interfaces as do-nothing methods. TABLE 13.1
shows these four interfaces and their methods.
When parsing an XML file using a SAX parser, there are many events that
can happen: for example, whenever the parser encounters a start tag, an
end tag, or text, it is an event. The DefaultHandler class includes a number
of methods that are automatically called when parsing a document and an
event occurs. TABLE 13.2 shows some of these methods. For example,
when the parser encounters a start tag, the startElement method is called.
When the parser encounters an end tag, the endElement method is called.
When the parser encounters text, the characters method is called. These
methods are implemented as do-nothing methods. In order to handle these
events, we need to subclass the DefaultHandler class and override the
methods that we are interested in.
Method Description
void startElement( String Called when a start tag is encountered by the parser; uri is the
uri, String localName, namespace uri and localName its local name; startElement is the name
String startElement, of the start tag; attributes contains a list of (name, value) pairs that are
Attributes attributes ) found inside the startElement tag.
void endElement( String Called when an end tag is encountered by the parser.
uri, String localName,
String endElement )
void characters( char [ ] Called when character data is encountered by the parser; ch stores the
ch, int start, int length ) characters, start is the starting index, and length is the number of
characters to read from ch.
Method Description
void parse( InputStream is, Parses the content of the XML input stream is and uses dh to
DefaultHandler dh ) handle events.
void parse( File f, DefaultHandler dh Parses the content of the XML file f and uses dh to handle
) events.
void parse( String uri, Parses the content of the XML file located at uri and uses dh
DefaultHandler dh ) to handle events.
Method Description
reference to it, and the newSAXParser method creates a SAXParser object and
returns a reference to it.
Method Description
Some of these exceptions are checked and some others are unchecked. To
keep things simple, we use one try block at lines 14–20 and catch a
generic Exception at line 20. Inside the try block, we create a SAXParser at
lines 15–16 and declare and instantiate a SAXHandler named handler at line
17. We open an input stream to read the data in the test.xml file located in
the raw directory of the res directory at line 18, and start parsing that input
stream with handler at line 19. The parsing triggers a number of calls to the
various methods inherited from DefaultHandler by SAXHandler , in particular
the three methods that we overrode in Example 13.2.
FIGURE 13.3 shows the output inside Logcat when we run the app. We
observe the following:
▸ Between the start and end tags of the same element, we execute
inside the characters method and the array ch stores the characters
between the start and end tags (also called the element contents).
▸ Between the end tag of an element and the start tag of another
element, we execute inside the characters method and the text only
contains white space characters.
In Version 1, we parse the test.xml file and convert it to a list. We code the
Item class as part of our Model. The Item class encapsulates an item in the
XML document: it has two String instance variables, title and link . In the
SAXHandler class, we build an ArrayList of Item objects that reflect what we
tag is item , we need to instantiate a new Item object. If the tag is title or
link , the text that we will read inside the characters method is the value for
the title or link instance variable of the current Item object. When the
endElement method is called, if the tag is item , we are done processing the
▸ element , a String , stores the current element ( xml , rss , item , title ,
or link in this example).
▸ currentItem , an Item , stores the current Item object.
If there is an end tag following the start tag, the characters method is called
next. If currentItem is not null and validText is true and the value of
element is either title or link (lines 39 and 41), we are in the middle of
building the current Item object. If element is equal to title or link , we set
the value of the title or link instance variable of the current Item object to
the characters read (lines 40 and 42).
After the startElement and characters methods are called, the endElement
method (lines 30–35) is called. We assign false to validText at line 32. In
this way, any text found after an end tag and before a start tag is not
processed by the characters method. If the value of element is item (line
33), we are done processing the current Item object, and we add it to items
at line 34.
FIGURE 13.4 shows the output inside Logcat when we run the app, Version
1. We can check that the ArrayList items contains the three Item objects
that are listed in the test.xml file.
When we start the app, our code executes in the main thread, so we need to
open that URL in a different thread. Furthermore, we need the XML parsing
in the secondary thread to finish before trying to fill the list with data in the
main thread because we use the data read in the secondary thread for the
list. The AsyncTask class, from the android.os package, allows us to perform
a background operation and then access the main thread to communicate its
results. It is designed to execute short tasks, lasting a few seconds or less,
and should not be used to start a thread that runs for a long time. AsyncTask
is abstract so we must subclass it and instantiate an object of the subclass
in order to use it. Appendix D explains the AsyncTask in detail, and it also
includes a very simple app using AsyncTask .
The AsyncTask class uses three generic types that we must specify when
extending it. The class header of a subclass is as follows:
where Params , Progress , and Result are placeholders for actual class names
and have the following meanings:
▸ Params is the data type of the array that is passed to the execute
method of AsyncTask when we call it.
▸ Progress is the data type used for progress units as the task executes
in the background. If we choose to report progress, that data type is
often Integer or Long .
▸ Result is the data type of the value returned upon execution of the
task.
Method Description
AsyncTask execute( Params. params represent an array of values of type Params, a generic
. . params ) type.
Result doInBackground( params is the argument passed to execute when we call it.
Params. . . params )
void onPostExecute( Result Automatically called after doInBackground finishes. Result is the
result ) value returned by doInBackground.
▸ With that object, call the execute method to start the task.
TABLE 13.6 shows these three methods. The execute and doInBackground
method headers show that they both accept a variable number of arguments.
When we call the execute method, the following methods of the AsyncTask
class execute in this order: onPreExecute , doInBackground , and
onPostExecute .
Before the task executes, if we want to perform some initialization, we can
place that code inside the onPreExecute method. We place the code for the
task we want to execute in the doInBackground method. That method is
abstract and must be overridden. The argument that is automatically passed
executing, if we want to report progress to the main thread, we can call the
publishProgress method. That in turn triggers a call to the onProgressUpdate
method, which we can override: for example, we can update a progress bar
in the user-interface thread. When the doInBackground method finishes
executing, the onPostExecute method is called. We can override it to update
the user interface with the results of the task that just completed. The
argument that is automatically passed to the onPostExecute method is the
value that is returned by the doInBackground method.
In our ParseTask class, the Params data type is String and the Result data
type is ArrayList . Thus, the doInBackground method has the following
method header:
EXAMPLE 13.7 shows the ParseTask class. In the class header (line 9), we
specify String as the Params data type, Void as the Progress data type
since we do not report progress in this app, and ArrayList as the Result
data type.
parameter and assigns it to activity . When we call that constructor from the
MainActivity class, we pass this as the argument.
with the activity instance variable, passing returnedItems at line 30. Inside
the MainActivity class, we need to code the displayList method.
Since our app accesses the Internet, we need to add the appropriate
permission code inside the manifest element of the AndroidManifest.xml file
as follows:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"/>
At lines 19–24, we code the displayList method. At this point, it outputs the
contents of its parameter items to Logcat. Items could be null so we test
for it at line 20 before the for loop.
Note that the sequencing of our code is important when we use an
AsyncTask . After the call to execute at line 16, execution would continue
inside the onCreate method if there were statements after line 16. In that
case, there would be interleaved execution between those statements and
the statements executing inside the ParseTask class. If we attempted to
retrieve the ArrayList of Item objects right after line 16 inside the onCreate
method, it would very likely be null . The correct way to process the result
of a task is to do it inside the onPostExecute method.
FIGURE 13.5 shows a partial output inside Logcat when we run the app,
Version 2. Note that this is a live blog, therefore contents may change daily
and may not match the output in the figure.
In order to keep this example simple, we only allow our app to run in vertical
position. Thus, we add the following to the AndroidManifest.xml file inside the
activity element:
android:screenOrientation="portrait"
To display the titles as a list on the screen, we use a ListView element in the
activity_main.xml file. A ListView contains an arbitrary number of Strings .
We do not have to specify how many elements when we define the ListView .
Thus, we can build an ArrayList of Item objects dynamically and place the
corresponding list of titles inside the ListView programmatically.
EXAMPLE 13.9 shows the activity_main.xml file, Version 3. We replace the
TextView that displays Hello World! in the skeleton file with a ListView
Inside the displayList method (lines 23–36), we populate listView with all
the titles from the ArrayList items . We generate titles , an ArrayList of
Strings containing the titles in items at lines 25–28. At lines 30–31, we
Constructor Description
list of data.
Method Description
void setAdapter( Sets the ListAdapter of this ListView to adapter; adapter stores the list of data
ListAdapter backing this ListView and produces a View for each item in the list of data.
adapter )
At this point, we do not set up any list event handling; we do that in Version
4. If items is null , we show a Toast at lines 34–35.
FIGURE 13.6 shows the app, Version 3, running inside the emulator.
FIGURE 13.6 The Web Content app, Version 3, running inside the
emulator
In Version 4, we open the browser at the URL chosen by the user from the
list. In order to do that, we do the following:
▸ Implement event handling when the user selects an item from the list.
▸ Retrieve the link associated with that item.
In Version 3, our list shows the titles of the ArrayList of Item objects that
we generated parsing the XML document. In Version 4, we actually need to
access the link values. In order to retrieve the link for a given Item object,
we add an instance variable to the MainActivity class that represents the list
of Item objects. When the user selects an item from the list, we retrieve its
index, retrieve the Item object from the ArrayList at that index, and retrieve
the value of the link instance variable of that Item .
Method Description
void onItemClick( This method is called when an item is selected in a list: parent is the
AdapterView<?> AdapterView; view is the view within the AdapterView that was selected;
parent, View view, int position is the position of view within the AdapterView; id is the row index of
position, int id ) the item selected.
Method Description
static Uri parse( String uriString ) Creates a Uri that parses uriString and returns a reference to it.
The parameter position of the onItemClick method stores the index of the
item in the list that the user selected. We use it to retrieve the Item object in
listItems at that index at line 51. At line 52, we use the static parse
method of the Uri class, shown in TABLE 13.10, to create a Uri object with
the String stored in the link instance variable of that Item object. At line
53, we create an Intent to open the web browser at that Uri . We pass the
ACTION_VIEW constant of the Intent class and the Uri object to the Intent
constructor. At line 54, we start a new activity with that Intent . Since the
activity is to open the browser, there is no layout file and class associated
with that activity.
FIGURE 13.7 shows the app, Version 4, running inside the emulator, after
the user selects an item from the list. If we click on the back button of the
emulator, we go back to the list of links, which then becomes the activity at
the top of the activity stack.
FIGURE 13.7 The Web Content app, Version 4, running inside the
emulator, after the user selects an item from the list
Chapter Summary
There are two types of XML parsers: DOM and SAX.
A DOM parser converts an XML document into a tree. A SAX parser
reads the XML document sequentially and reports what it finds. We can
use it to convert the XML document to a list of objects.
We can use the SAXParserFactory class to get a SAXParser reference.
The SAXParser class contains several methods to parse an XML
document.
We can extend the DefaultHandler class to parse an XML document
using a SAX parser.
The DefaultHandler class includes callback methods that are
automatically called when parsing an XML document. For example, the
startElement method is called when the parser encounters a start tag.
10. This code starts parsing a file named myFile.xml located in the raw
directory of the res directory, using the DefaultHandler myHandler
(assume that SAXHandler extends DefaultHandler).
SAXParserFactory factory = SAXParserFactory.newInstance( );
String myString;
// Your code goes here
17. Write an app similar to the app in the chapter: use a different URL and
display the published date along with the title.
18. Write an app similar to the app in the chapter: use a different URL and
display only the titles that have been published within the last 30 days.
19. Write an app similar to the app in the chapter: use a different URL and
add a text field to let users specify how recent they want the data to be.
For example, if a user enters 45, then only display results with a
published date that is within the last 45 days.
20. Write an app similar to the app in the chapter: use a different URL and
add a front end activity with a text field to let the user specify a search
term so that the titles displayed include that search term.
21. Write an app that presents the user with a list of websites of your choice
in a ListView. When the user clicks on one, the browser opens that
website.
CHAPTER FOURTEEN: Making an
Android Widget
Chapter Summary
Introduction
App widgets, also known as widgets, are small apps that are embedded in
other apps, called app widget hosts. The Home screen and the Lock screen
are examples of app widget hosts. We can also implement our own app
widget host, but that is beyond the scope of this chapter. Widgets can
receive periodic updates and update their view accordingly. The minimum
frequency of automatic periodic updates is 30 minutes. Many Android
devices come with native widgets already installed. A well-known and
common widget is a weather app widget from www.accuweather.com. In this
chapter, we create a widget that displays the current temperature at a
location specified by the user during the installation of the widget.
Class Description
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/name_of_widget_info_file" />
</meta-data>
</receiver>
directory of the res directory that contains the widget information such as
size and update frequency.
attribute inside the receiver element at line 12 is mandatory. Its value is the
name of the class that extends AppWidgetProvider . The action element (lines
14–15) inside the intent-filter element is mandatory and must have an
android:name attribute whose value is the equivalent of the
AppWidgetProviderInfo class.
The res directory does not contain an xml directory by default, so we need to
create it. Since we specify widget_info as the name of the xml file defining
the AppWidgetProviderInfo object in the AndroidManifest.xml file in Example
14.1, we add an xml file in the xml directory named widget_info.xml. That file
must contain a single appwidget-provider element. It is defined at lines 2–8
of Example 14.2. At line 4, we specify widget_layout.xml, located in the
res/layout directory, as the layout resource of the widget. At lines 5–6, we
set its minimum height and width to 50 and 200 pixels. These will be the
width and height of the widget when it is created and added to the host. An
Android home screen is organized as a grid of cells in which widgets (and
icons) can be placed. The grid can vary by device.
int minWidth android:minWidth The width of the widget when added to the host,
in dp.
int minHeight android:minHeight The height of the widget when added to the host,
in dp.
int android:minResizeWidth The minimum width the widget can be resized to,
minResizeWidth in dp.
n 70 * n – 30
1 40
2 110
3 180
4 250
At line 7, we set the update frequency to be 1800000 milliseconds, which is
equal to 30 minutes. This is the minimum frequency at which a widget can be
automatically updated. However, we can trigger a widget update by code by
setting up some event handling on the widget, for example, if the user
touches the widget. We do that in Version 2.
Example 14.2 specifies at line 4 that the layout of the widget is defined in the
widget_layout.xml file. Although a widget is typically small with a simple GUI,
a widget can have a complex GUI, but with some restrictions. TABLE 14.4
lists the Views and layout managers that a widget can use.
TABLE 14-4 Views and layout managers that we can use in a widget
version shows some hard coded text using the string resource city_and_temp
(line 11); city_and_temp is defined in strings.xml as follows:
It uses the Unicode character \u00B0 to encode the degree sign (°). Inside
strings.xml, we change the value of the app_name String from
TemperatureWidgetV0 to TemperatureV0 .
EXAMPLE 14.3 The widget_layout.xml file
▸ An array of widget ids: the ids of the widgets of that provider for which
an update is needed.
void onReceive( Context Automatically called before the onUpdate Overrides this
context, Intent intent ) method before a widget is configured and after method to
the widget’s configuration activity has finished. dispatch calls to
the other
methods of this
class.
It is possible that several widgets of that class are installed. This is why we
refer to an array of widget ids rather than a single widget id. For example, if
this widget is customizable based on some location, we could have a widget
of that class displaying the temperature in New York, NY, and another one
displaying the temperature in Palo Alto, CA.
Inside the onUpdate method, we loop through all the widget ids of type
TemperatureProvider and update them using the AppWidgetManager parameter.
Note that we could choose to only update the widgets that need an update
by using the appWidgetIds parameter of the onUpdate method.
Method Description
int [ ] getAppWidgetIds( Returns the list of widget ids for the widgets of the type of
ComponentName provider ) this AppWidgetProvider provider
void updateAppWidget( int Sets views as the RemoteViews for the widget whose id
appWidgetId, RemoteViews views ) is appWidgetId.
TABLE 14.7 Selected Methods of the RemoteViews class
Constructor Description
RemoteViews( String Creates and returns a RemoteViews from the resource identified
packageName, int by layoutResourceId in the package named packageName.
layoutResourceId )
If there are several widgets of the same provider class installed on a device,
they have most likely been installed at different times. In Example 14.4, we
only update those that need to be updated. We can also update all of them
regardless of which ones need to be updated, so that they are synchronized.
We do that later in the chapter.
We can test a widget inside the emulator. If we click on the apps icon, then
click on the Widget Preview icon (see FIGURE 14.1), and then click on the
widget (see FIGURE 14.2), we can see a preview of the widget (see
FIGURE 14.3).
FIGURE 14.3 A preview of the TemperatureWidgetV0 widget inside the
emulator
FIGURE 14.4 The tablet after a long press showing a menu
After we run Example 14.4 on a device like a tablet, we need to install the
widget on the tablet. In order to do that, we do the following:
▸ Long press on the screen in order to bring the menu shown in FIGURE
14.4 (we can also click on the Apps icon),
▸ Choose Apps and widgets and click on the Widgets tab at the top of
the screen. Depending on how many widgets are already installed on the
device, we may have to swipe the screen in order to get to the screen
that shows the widget, which is shown in FIGURE 14.5.
FIGURE 14.6 shows the widget running on the Home screen. Note that we
can repeat that operation and install a second widget of the same provider
class on the Home screen.
FIGURE 14.5 The tablet showing the TemperatureWidget, Version 0,
widget
FIGURE 14.6 The TemperatureWidgetV0 widget running inside the
home screen of the tablet
In Version 1, we style the widget so that it looks more like the widgets we
are accustomed to, but we do not improve its functionality yet. We style it in
two ways:
▸ We give it a background.
specify a gradient to fill the rectangle with. Its starting color is ivory (line 12)
and its ending color is teal (line 13) with two different opacity levels. It is a
linear gradient going from left to right at a 45 degree angle (line 14).
margin to 5 pixels at line 6. At line 14, we center the text inside the TextView
element both horizontally and vertically. We set the padding to 5 pixels at line
15. Since we need to access the TextView by code in Versions 2 and after,
we give an id at line 10.
FIGURE 14.8 shows our widget, Version 1, running inside the tablet
alongside a widget from Version 0.
EXAMPLE 14.6 The widget_layout.xml file
FIGURE 14.8 The Version 0 and Version 1 widgets inside the Home
screen of the tablet
In Version 2, we make the widget dynamic: we retrieve the date and time
dynamically from the device, and display it inside the widget. In order to
implement this feature, we do the following:
▸ Retrieve the date and time by code and build a String that includes it.
The Date class of the java.util package enables us to retrieve the data and
time dynamically. The DateFormat class of the java.text package enables us
to format that date and time into a String .
For example, if the layout XML file for our widget is widget_layout.xml and it
contains a TextView whose id is display , we could set the text of that
TextView to Hello Widget , its color to green , and its size to 32 as follows:
R.layout.widget_layout );
TypedValue.COMPLEX_UNIT_SP, 32.0f );
TABLE 14.8 Selected methods of the RemoteViews class and their equivalent
methods (and their classes)
void addView( int viewId, RemoteViews void addView( View child ) ViewGroup
nestedView )
void setTextViewTextSize( int viewId, int units, void setTextSize( int units, float TextView
float size ) size )
void setTextColor( int viewId, int color ) void setTextColor( int color ) TextView
lines 20–21, we concatenate today and our default city, state, and
temperature into the String displayString . At line 25, we set the text inside
the TextView whose id is display to displayString .
FIGURE 14.9 shows our widget, Version 2, running inside the tablet
alongside widgets from Versions 0 and 1.
FIGURE 14.9 The Versions 0, 1, and 2 widgets running inside the Home
screen of the tablet
Method Description
void setOnClickPendingIntent( int viewId, When the user clicks on the View whose id is
PendingIntent pendingIntent ) viewId, pendingIntent is launched.
Method Description
FLAG_UPDATE_CURRENT Pending intent, if it exists, is reused but its extras are replaced with
the extras of the new intent.
in it (line 32–33). When the intent is launched, that triggers a call to the
onUpdate method of TemperatureProvider .
At line 37, we specify that clicking on the View whose id is display will
trigger the pending intent to be launched.
EXAMPLE 14.8 The TemperatureProvider class, app Version 3
FIGURE 14.10 shows our widget, Version 3, running inside the tablet
(second from the top on the left) alongside widgets from Versions 0, 1, and
2. If we touch the Version 3 widget, the data and time are updated, as
opposed to Versions 0, 1, and 2 for which the data is static.
FIGURE 14.10 The Versions 0, 1, 2, and 3 widgets running inside the
Home screen of the tablet
▸ Identify a remote source that supplies the data we are looking for.
▸ Identify what data to pass and how to pass it to the remote source.
▸ Display the desired data inside the widget (or the app).
Weather data is collected at weather stations. In the United States, there are
around 2,000 weather stations, and each weather station has an id and
geographical data associated with it, such as address, latitude, longitude,
etc. The National Weather Service (NWS) provides an abundance of free
weather data. In order to get weather data for a particular location, we first
retrieve a list of weather stations close to that location from NWS, calculate
the distances from the location to each weather station, choose the weather
station that is the closest, and read data from that weather station.
In addition to NWS, there are many available sources of weather data: some
are free, some are not, some require a key to get access, some do not.
They may accept one or several types of input, such as zip code, city, or
latitude and longitude coordinates, and they use different ways to format
data, such as XML or JavaScript Object Notation (JSON), which is a
lightweight way of formatting data. JSON is often used to format data
transmitted over the Internet between a server and client and vice versa,
because it is not as cumbersome as XML and is easy to parse.
▸ An array.
http://api.openweathermap.org/data/2.5/weather?
q=city,country&appid=your_key
8f4fc1b7ccx025gga22ac2db344a3hj8z
The URL also accepts a US state instead of a country. The String after the
question mark character ( ? ) (i.e., q=city,country&appid=key in this case) is
called the Query String.
q=London,UK&appid=YOUR_KEY
http://api.openweathermap.org/data/2.5/weather?q=New
York,NY&appid=YOUR_KEY
http://api.openweathermap.org/data/2.5/weather?
q=Baltimore,MD&appid=YOUR_KEY
If we open a browser and paste the first of these three examples (using an
actual key) into the URL field, the browser displays the JSON string shown in
FIGURE 14.11.
K C = K – 273.15 F = ( C * 9/5) + 32
273.15 0 32
298.15 25 77
There is more information returned than we actually want for this widget. We
keep the widget simple and only show the current temperature. Figure 14.11
indicates a temperature of 283.09 , the value for the field temp . The
temperature is given in degrees Kelvin. TABLE 14.12 shows the conversion
formulas between the various temperature scales.
As we build a Model to parse such a JSON string, we note that our Model
depends on the formatting of the data that we receive. Although it is not
expected that the data source will change its data format frequently, it is a
good idea to subscribe to notifications from the data source in order to be
able to edit our code as quickly as possible to adapt to any change in the
data and its formatting. The JSONObject class, part of the org.json package,
includes methods to parse a JSON string. TABLE 14.13 shows some of
them.
try {
}
TABLE 14-13 Selected methods of the JSONObject class
Returns the JSONArray mapped by name if Returns the int value mapped by name if there is
there is one and it is a JSONArray.int getInt( one and it can be cast to an int.double getDouble
String name ) ( String name )
Returns the double value mapped by name if Returns the String value mapped by name if there
there is one and it can be cast to a is one.
double.String getString( String name )
In order to retrieve the current temperature within the JSON string of Figure
14.11, we can use the following code sequence:
try {
EXAMPLE 14.9 shows the TemperatureParser class, part of our Model for
this app. Its main functionality is to extract the temperature data from the
JSON String. The constructor, at lines 13–18, instantiates jsonObject , the
only instance variable, calling the JSONObject constructor and passing its
String parameter. The getTemperatureK method, at lines 20–27, returns the
value associated with the key temp inside the json object associated with the
key main . Note that we catch a generic Exception at line 24 rather than a
JSONException , because jsonObject could be null . When a
TABLE 14-14 Selected classes and methods to read data from a remote
location
Class Constructor or Description
Method
URLConnection void setDoInput( We call this method with the argument true if we
boolean doInputFlag want to read, or false if we do not want to read (
) the default is true ).
URLConnection void setDoOutput( We call this method with the argument true if we
boolean want to write, or false if we do not want to write (
doOutputFlag ) the default is false ).
HttpURLConnection( void disconnect( ) Disconnects from the server located at the URL of
inherits from this HttpURLConnection.
URLConnection )
InputStream void close( ) Closes this InputStream and releases the memory
resources associated with it.
BufferedReader void close( ) Closes the input stream and releases the memory
resources associated with it.
TABLE 14.14 shows various classes and methods that we use to perform
those steps. HttpURLConnection is a subclass of URLConnection with added
support for the HTTP protocol. InputStreamReader is a subclass of Reader ,
thus, an InputStreamReader reference that can be used as the argument of
the BufferedReader constructor.
We have completed the Model for this version. Now we need to edit the
Controller for the widget, which is the TemperatureProvider class. For this
version of our widget, we go on the Internet and retrieve some data from a
remote server. Thus, we include two uses-permission elements in the
AndroidManifest.xml file inside the manifest element as follows:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"/>
Method Description
AsyncTask execute( Params. params represent an array of values of type Params, a generic
. . params ) type.
Result doInBackground( params is the argument passed to execute when we call it.
Params. . . params )
void onPostExecute( Result is automatically called after doInBackground finishes; result is the
result ) value returned by doInBackground.
where Params , Progress , and Result are placeholders for actual class names
and have the following meanings:
▸ Params is the data type of the array that is passed to the execute
method of AsyncTask when we call it.
▸ Progress is the data type used for progress units as the task executes
in the background. If we choose to report progress, that data type is
often Integer or Long .
▸ Result is the data type of the value returned upon execution of the
task.
TABLE 14.15 shows these three methods. The execute and doInBackground
method headers show that they both accept a variable number of arguments.
of the postExecute method. Thus, we need to update the widget from inside
the postExecute method. However, in order to update the widget, we need to
use the parameters of the onCreate method. In order to have access to
these parameters inside the onPostExecute , we pass them to the
TemperatureTask constructor and assign them to instance variables of the
lines 32–33, and reads the data at that URL location into a String named
json (line 34), and returns it.
The postExecute method, at lines 38–42, instantiates a TemperatureParser
object with its String parameter (the String returned by the doInBackground
method) at line 39. It retrieves the temperature value in Fahrenheit and calls
the updateWidget method of the TemperatureProvider class at lines 40–41,
passing the temperature and the three instance variables that are references
to the three parameters of the onCreate method.
TemperatureTask class.
View widget_layout.xml
TABLE 14.16 shows the various components of our widget, Version 4: the
Model is comprised of the RemoteDataReader and TemperatureParser classes,
and they are both reusable in other projects. The widget_layout.xml file is the
View. The TemperatureProvider and TemperatureTask classes make up the
Controller.
FIGURE 14.12 shows our widget, Version 4, running inside the tablet (third
from the top on the left) alongside widgets from Versions 0, 1, 2, and 3.
Notice that the temperature shows live data (77°F), not a hard coded value
as with the previous versions.
▸ We create a layout XML file for the activity that collects user input.
manifest.
Constant Description
EXTRA_APPWIDGET_ID Use this constant to retrieve the widget id from the extras of
an intent.
Constant Description
At lines 20–22, we retrieve user input. To pass simple data from an activity to
another, we typically use the putExtra and getExtra methods of the Intent
class. However, there is no intent associated with the AppWidgetProvider
class, so we cannot use that strategy here. Furthermore, the default
constructor of an AppWidgetProvider class is called automatically and re-
initializes the instance variables to their default values. Thus, it is not practical
to store widget data in an instance variable whose value can change over
time. In Version 4, we used the instance variable city of the
TemperatureProvider class, declared at line 19 of Example 14.12, to store the
city and state (or country), even though it is hard coded to the value New
York, NY for that version. In Version 5, we make that variable global by
At lines 27–34, we retrieve the widget id from the incoming intent. We set its
default value to 0 (the value of the INVALID_APPWIDGET_ID constant, shown in
Table 14.17) at line 30. At line 31, we test if there is some data stored in the
Bundle of the incoming intent. If there is, we attempt to retrieve the widget id
at lines 32–34. If the Bundle extras has a key entry equal to the value of the
EXTRA_APPWIDGET_ID constant of the AppWidgetManager class (see Table
14.17), then that key maps to the widget id value and that widget id value is
returned by calling getInt . If not, the call to getInt returns the second
argument, the value of INVALID_APPWIDGET_ID (i.e., 0 ).
We test if the widget id value is valid at line 36. If it is, we update the widget
at lines 37–43, create a return intent at lines 45–46, place the widget id in it
at lines 48–49, and set the result to the value of RESULT_OK at line 50. After
that, or if the widget id value is invalid, we terminate the activity at lines 53–
54.
TemperatureProvider:onUpdate Automatic
TemperatureWidgetConfigure:onCreate Automatic
TABLE 14.20 shows the state of the Model, View, and Controller for our
widget Version 5. As compared to Version 4, the Model is unchanged. We
added the widget_config.xml file to the View to define the layout of the
activity that configures the widget, and we added the
TemperatureWidgetConfigure class and modified the TemperatureProvider
FIGURE 14.13 shows the user configuring the city and state during the
configuration of the widget, Version 5. FIGURE 14.14 shows our widget,
Version 5, running inside the tablet alongside widgets from Versions 0, 1, 2,
3, and 4. The city and state of the widget, Version 5 (San Francisco, CA),
are different from the others, and it shows live data like Version 4.
FIGURE 14.13 The configure screen when installing the widget, Version
5
FIGURE 14.14 The Versions 0, 1, 2, 3, 4, and 5 widgets running inside
the Home screen of the tablet
Since Android Version 4.2, it is also possible to host a widget on the Lock
screen, also called the Keyguard. We enable hosting of a widget on the
Lock screen by assigning the value keyguard to the android:widgetCategory
attribute of the appwidget-provider element in the widget info file as follows:
Constant Description
android:widgetCategory="keyguard"
Bundle getAppWidgetOptions( int widgetId ) Returns the Bundle storing the extras for the widget
whose id is widgetId.
Method Description
int getInt( String key ) Returns the integer value associated with key, 0 if there is none.
android:initialKeyguardLayout="@layout/widget_layout_keyguard"
Inside the AppWidgetProvider class, we can test if the widget host is the
Home screen or the Lock screen. The host information is stored in the
Bundle associated with the widget. Inside the onUpdate method of the
14.22), passing the widget id as its only argument. With the Bundle
reference, we can call the getInt method, shown in TABLE 14.23, passing
the OPTION_APPWIDGET_HOST_CATEGORY constant of the AppWidgetManager class
(see Table 14.22), and retrieve the widget host category as an int . We can
then compare that integer value to the WIDGET_CATEGORY_HOME_SCREEN and
WIDGET_CATEGORY_KEYGUARD constants of the AppWidgetProviderInfo class (see
Table 14.21), whose values are 1 and 2 respectively, in order to test what
host the widget is on.
We can use the following sequence to test if the host is the Home screen or
the Lock screen.
int host =
Designing a widget that looks and behaves differently on the Lock screen is
left as an exercise.
Chapter Summary
App widgets, also known as widgets, are small apps that are embedded
in other apps, called app widget hosts.
The Home screen and the Lock screen are examples of app widget
hosts.
To create a widget, we can extend the AppWidgetProvider class and
override its onUpdate method.
The widget info file, located in the xml directory of the res directory,
defines an appwidget-provider element that specifies the characteristics
of a widget such as size and frequency of updates.
Not all View and layout manager classes can be used inside a widget.
A widget can use a drawable resource for its background.
A widget can update its display automatically at a certain frequency. The
minimum frequency is 30 minutes.
We can update the widget via user interaction with the widget, such as a
click.
It is possible to update the data displayed by the widget dynamically.
That data can be retrieved locally or from a remote source.
A widget can be customizable by the user at installation by adding an
activity that captures user input.
Not only can we install a widget on the Home screen, we can also install
a widget on the Lock screen.
11. Write the code so that the widget’s update frequency is 1 hour
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_layout"
android:minHeight="40dp"
android:minWidth="180dp"
</appwidget-provider>
13. Write the code to set the text inside a TextView whose id is my_view to
HELLO WIDGET.
),
R.layout.widget_layout
);
14. Inside the onUpdate method of the MyProvider class, write the code to
update the widget every time the user clicks on a View whose id is
my_view inside the widget.
),
R.layout.widget_layout
);
intent.setAction( AppWidgetManager.ACTION_APPWIDGET_UPDATE );
);
// Your code goes here
{"coord":{"lon":12.495800018311,"lat":41.903049468994},"sys":
{"country":
"Italy","sunrise":1374033016,"sunset":1374086531 } };
16. Write the code so that the widget is configured by an activity named
MyActivity and located in the com.xyz.q16 package.
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_layout"
android:minHeight="40dp"
android:minWidth="180dp"
<!-- your code goes here -->
</appwidget-provider>
18. Write the code so that the widget can be installed on the Home screen
or the Lock screen.
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_layout"
android:minHeight="40dp"
android:minWidth="180dp"
<!-- your code goes here -->
</appwidget-provider>
Write a Widget
Chapter Summary
Introduction
An important issue when building an app is how to monetize it. With so many
apps out there, most people will first try a free app whenever possible. The
Android SDK provides developers with the ability to include advertising inside
their apps and leverage Google’s advertising resources to upload ads at
runtime.
We want to build a stopwatch app that enables users to start the clock, stop
it, reset it, restart it, etc. In Version 0, we only build the GUI, reserving a
space for an ad banner at the bottom. FIGURE 15.1 shows the version 3 of
the app running inside the emulator. The screen is divided into three parts:
▸ At the top, we have the clock.
For the two round buttons, we use three drawables, shown in EXAMPLES
15.1, 15.2, and 15.3. We place them in the drawable directory. For the
start/stop button, we will toggle the background of the button between
start_button.xml and stop_button.xml. The start button has a green circle
outline and the stop button has a red circle outline (line 6 of Examples 15.1
and 15.2). The reset button has a gray circle outline (line 6 of Example
15.3).
EXAMPLE 15.1 The start_button.xml file, Stopwatch app, Version 0
We assign 4/9 (weight 4 – line 14) of the screen to the Chronometer element,
4/9 to the buttons (weight 4 – line 22), and 1/9 (weight 1 – line 63) to the
LinearLayout at the bottom. We give an id to the Chronometer at line 17
40 and 49–56). Each button has a diameter of size 150 pixels (lines 35–36
and 51–52) and a text size of 36. Although it is generally not good practice to
hard code dimension values, we do so here to keep the example simple.
Furthermore, these dimensions are reasonably small and are expected to
work on any device. At lines 39 and 55, we set the background of each
button to its corresponding drawable resource. Clicking on the Start/Stop
button will trigger a call to the startStop method (line 40) and clicking on the
Reset button will trigger a call to the reset method (line 56).
In this version, we color the bottom LinearLayout in light gray (line 66) so
that we can visualize where the ad banner will go.
In Version 1, we will code the startStop and reset methods to give the app
its functionality. In order to do this, we use the functionality of the
Chronometer class, which represents the Model for this app: the start , stop ,
Method Description
void setBase( long base ) Set the time of reference for the count.
EXAMPLE 15.7 shows the updated MainActivity class. Since the Start/Stop
button toggles between two states, we keep track of that state with a
boolean instance variable, started (line 12). Since we need access to the
Chronometer in both methods, we add an instance variable for it, chrono (line
▸ Turn started to false (line 25) to specify that the button is now in a
“stopped” state.
▸ Change the text of the button to START (line 26).
Otherwise (line 28), the Chronometer has not started yet or was stopped, the
button is in its “start” state and we do the following:
▸ Turn started to true (line 30) to specify that the button is now in a
“started” state.
The reset method (lines 36–39) resets chrono to 00:00 by calling the
setBase method, passing the current time in milliseconds. Thus, when we call
FIGURE 15.3 shows the Stopwatch, Version 1, running inside the emulator.
The clock is running and the Start/Stop button has a red circle outline and
says STOP.
There is one issue with Version 1: if we stop the clock and restart it later, it
does not restart where we stopped it. In fact, when we stop the
Chronometer , it keeps running in the background. In Version 2, we fix that
shown in EXAMPLE 15.8. The Model for this app is now comprised of the
Chronometer and ClockUtility classes. Furthermore, the functionality of the
equivalent number of milliseconds. The format of the String clock (line 10) is
expected to be hh:mm:ss or mm:ss where hh represents the number of hours
(between 00 and 23), mm the number of minutes (between 00 and 59), and
ss the number of seconds (between 00 and 59). In order to convert the
We convert clock to an array at line 12. If there are three elements in that
array (line 16), the number of milliseconds is equal to hh * 60 * 60 * 1000 +
mm * 60 * 1000 + ss * 1000 (lines 17–19). If there are two elements in that
returns 0 .
EXAMPLE 15.9 shows the updated MainActivity class. Inside the startStop
method, if we restart the Chronometer (lines 29–33), we first reset it to where
it had previously stopped at line 29 by calling the resetChrono method. Inside
resetChrono (lines 42–46), we convert the current value of chrono to a
number of milliseconds at lines 43–44. At line 45, we reset its base (i.e., its
starting value) to its value when it stopped. TABLE 15.2 shows possible
values returned by the elapsedRealtime and milliseconds methods when the
user clicks on START, then STOP when chrono shows 10:00 , then START
again.
Now, when we run the app, we can stop the Chronometer and it will restart
where it was stopped. For example, if the Chronometer says 00:15 when we
stop it and we wait 10 seconds before clicking on the Start/Stop button, the
Chronometer will restart at 00:15 , not 00:25 .
ad and manage the ad. TABLE 15.3 shows some of these classes.
Class Description
The AdSize class encapsulates the size of a banner ad. It provides constants
to match various industry standard sizes, as well as constants to cause the
width and height to be relative to the device’s width and height. TABLE 15.4
shows some of these constants. We can use the FULL_WIDTH and
AUTO_HEIGHT constants to create an AdSize object using an AdSize
The AdView class includes methods to create and manage a banner ad. Its
direct superclass is ViewGroup , itself a subclass of View . Thus, AdView
inherits from View . TABLE 15.5 shows some methods of the AdView class.
We must set the size and ad unit id of an AdView before we can load an ad
into the AdView . Otherwise, an (unchecked) IllegalStateException will be
thrown when we try to load the ad. In order to obtain an ad unit id from
Google, we must be a registered Android developer. Among other things, the
developer uses the ad unit id to generate ad revenues. The ad unit id is a
String that we can obtain from AdMob, the platform that Google uses for
AUTO_HEIGHT int Causes the height of the ad to scale based on the height of the
device.
FULL_WIDTH int Causes the width of the ad to match the width of the device.
Method Description
public void setAdSize( Sets the size of the banner ad. The argument can be one of the
AdSize adSize ) constants of the AdSize class.
https://support.google.com/admob/v2/answer/3052638
ca-app-pub-XXXXXXXXXXXXXXXX/NNNNNNNNNN.
For developers who are not registered and want to test an app containing an
AdView , Google provides a test ad unit id. We include it in the strings.xml file,
shown in EXAMPLE 15.12 (lines 3–4). We actually do not use that String in
Version 3, but we do use it in Versions 4 and 5.
The following shows a code sequence creating an AdView , setting its size
and setting its ad unit id, assuming we are inside an Activity class:
// Set ad size
adView.setAdSize( AdSize.SMART_BANNER );
// Set the ad unit id; use the default String from Google
Method Description
public AdRequest.Builder Adds a keyword for targeting purposes and can be called several
addKeyword( String times to add several keywords.
keyword )
public AdRequest.Builder Sets up a device to receive test ads rather than live ads. Use the
addTestDevice( String constant DEVICE_ID_EMULATOR from the AdRequest class to use
deviceId ) the emulator.
public AdRequest build( ) Constructs and returns an AdRequest with the attributes specified by
this AdRequest.Builder.
Once we have created an AdView , set its size and ad unit id, we need to
create an ad request and load it into the AdView . The AdRequest class
encapsulates the concept of an ad request. It includes a static inner class,
Builder , that we can use to set the characteristics of an AdRequest . TABLE
adView.loadAd( adRequest );
If we want to use the emulator to test our app, we add this line before calling
build to create the AdRequest :
adRequestBuilder.addTestDevice( AdRequest.DEVICE_ID_EMULATOR );
Thus, for the author’s device, we can include the following code to test the
app on the tablet (for security and privacy reasons, the device id is partially
hidden on Figure 15.4 and has been filled with ? characters below).
adRequestBuilder.addTestDevice( deviceId );
EXAMPLE 15.13 The MainActivity class, Stopwatch app, Version 3
retrieve the LinearLayout whose id is ad_view and place the AdView in it. At
lines 47–48, we load an ad conforming to adRequest inside the AdView .
Figure 15.1, at the beginning of this chapter, shows the Stopwatch app,
Version 3, running, including the banner ad at the bottom of the screen. If we
are a registered developer and are using our own app unit id, we should not
click on the live ad for testing purposes: it is against Google policy to do so.
If we want to test the functionality of the banner ad, then we should use test
ads, which we can get as follows:
EXAMPLE 15.15 shows the XML layout file for our fragment. We place the
AdView element (lines 7–15) inside a RelativeLayout element (line 2). We
give the AdView an id at line 8 so we can retrieve it inside the fragment class
using the findViewById method. We specify the size and the ad unit id of the
AdView at lines 13–14. At lines 11–12, we center the AdView horizontally and
Since all banner ad–related code is in the AdFragment class, the MainActivity
class is the same as the one in Version 2.
COMMON ERROR: Be careful to use test ads and not live ads when
testing your app. Clicking on a live ad while testing an app on a device
is against Google’s policy. When we are ready to publish our app, we
should comment out the code that specifies the emulator or a specific
device id to test the app.
15.6 Managing the Life Cycle of the AdView,
Stopwatch App, Version 5
The AdView class includes life-cycle methods, shown in TABLE 15.7, so that
we can avoid unnecessary processing when the app goes to the background
or is exited. Inside the fragment class, the onPause , onResume , and onDestroy
life-cycle methods are automatically called as the parent’s activity goes into
the background, the foreground, or is exited. Thus, we can call the AdView
life-cycle methods from the fragment’s life-cycle methods.
EXAMPLE 15.18 shows the updated AdFragment class of our app, Version 5.
All the other classes and files remain identical. Because we need to access
the AdView from onPause (lines 38–42), onResume (lines 44–48), and
onDestroy (lines 50–54), we make the AdView an instance variable (line 12)
so that we can have a direct reference to it. Inside the three methods, we
pause, resume, or destroy the AdView and call the super method. Note that
inside onPause and onDestroy , we first pause or destroy the AdView before
calling the super method: we want to pause the processing of the AdView or
destroy the AdView inside the fragment before pausing or destroying the
fragment itself. Inside onResume , we call the super method before calling
resume with the instance variable adView : we want to resume the processing
of the fragment before resuming the processing of the AdView , which is inside
the fragment.
Method Description
public void pause( ) Pauses any extra processing associated with this AdView.
public void resume( ) Resumes processing associated with this AdView following a call to pause.
milliseconds, the amount of time since the last boot, including sleep time.
The com.google.android.gms.ads package provides a set of classes to
display an ad and manage the ad.
The com.google.android.gms.ads package is part of Google Play
services. In order to use it, we need to edit the build.gradle file
accordingly.
The AdView class, which inherits from View , encapsulates a View that
can display an ad banner.
The AdSize class encapsulates the size of a banner ad. It includes
constants for various industry standard banner sizes.
The AdRequest.Builder class provides methods to define data, such as
gender, location, and keywords, that enable an ad to target a certain
type of demographic.
The build method of the AdRequest.Builder class returns an AdRequest
reference.
We can use the following ad unit id for testing purposes: ca-app-pub-
3940256099942544/6300978111
We must set the size and ad unit id of an AdView before the AdView calls
the loadAd method to load an ad.
We can use a fragment for the banner ad, per Google’s
recommendations.
Including a Google ad requires three additions to the
AndroidManifest.xml file:
The INTERNET permission is required since the Internet is used in
any app that displays a Google ad.
A meta-data element showing use of Google play services is also
required.
An additional activity element for AdActivity is required.
Exercises, Problems, and Projects
Multiple-Choice Exercises
9. Inside an Activity class, create an AdView and set its size so that it
meets the IAB leaderboard ad size (you need to look at the AdSize
class for that).
10. Inside an Activity class, create an AdView and set its ad unit id to the
default String provided by Google for testing purposes.
11. Create a simple AdRequest so that we use test ads and not live ads.
12. Create an AdRequest for men, using the two keywords game and video .
13. Create an AdRequest for women, using a birthday of 1/1/2000.
14. Create an AdRequest, considering that we will test the app in the
emulator, and we only want test ads, not live ads.
15. Assuming the AdView myAdView has been created, its size and ad unit
id have been set, and the AdRequest myRequest has been built, load
the ad into myAdView.
16. Inside the AndroidManifest.xml, code the permission-related element for
an app that includes banner ads.
17. Inside the AndroidManifest.xml, code the extra activity element for an
app that includes banner ads.
18. Inside the AndroidManifest.xml, code the metadata-related element for
an app that includes banner ads.
Write an App
19. Make a simple flashlight app with a banner ad at the bottom. The
flashlight is a yellow View taking the whole screen except the banner ad.
20. Make a flashlight app with a banner ad at the top. The flashlight is a
yellow View taking the whole screen except the banner ad and a
SeekBar. The yellow View is dimmable, which you should implement with
the SeekBar. Include a Model.
21. Make an app of your choice (it should have some functionality) with a
banner ad whose display is triggered by an event, for example the user
clicking on a button.
22. Make an app of your choice (it should have some functionality) with a
banner ad that shows 50% of the time the user runs the app.
23. Make an app of your choice (it should have some functionality) with a
banner ad. The app should include some form of user input (either an
EditText, or some list to pick from). You need to use the user input as a
keyword for the ad request.
24. Modify the Stopwatch app, Version 5, making the AdFragment class fully
reusable (i.e., it should not set specific gender and keywords). Limit the
customization of the ad to gender and keywords.
CHAPTER SIXTEEN: Security and
Encryption
Chapter Summary
Introduction
Security is a very important concern for every user of a computer in general,
and a mobile device in particular. Valuable data, such as credit card
numbers, can be stolen if not properly protected. One way to protect data is
to encrypt it. As technology evolves and computers get more and more
powerful, encryption algorithms that were once thought to be robust and safe
may become weaker. When choosing an encryption algorithm, it is wise to
check that it is still safe at the time we use it. In this chapter, we learn about
various encryption algorithms and how we can use them to encrypt data.
An encryption system can be one way or two ways. One way means that
once we have encrypted something, it is not possible to decrypt it. A one-
way encryption system can be used to encrypt passwords. Usernames and
encrypted passwords are typically stored in a database located on a server.
Users connect to that server from a remote location and log in. The plaintext
passwords are only known by the individual users and never stored on the
server. Because the encryption process takes place on the server side and
not on the client side, we do not cover one-way encryption in this chapter.
Asymmetric encryption means that the key used to encrypt the plain
message is different from the key used to decrypt the encrypted message:
there are two keys. One such system is RSA, which stands for Rivest,
Shamir, and Adleman, its three inventors. In the RSA system, every person
has a set of two keys, a public one (published) and a private one (secret).
For example, if Alice and Bob are two users of the system, Alice can send a
message to Bob, encrypting the message using the public key of Bob as
follows:
encryptedMessage1 = rsa( publicKeyBob, message1 )
Alice knows Bob’s public key because it is public and published. Because
that message has been encrypted using Bob’s public key, it can only be
decrypted using Bob’s private key. Since Bob’s private key is private and
secret, only Bob knows it. Thus, only Bob can decrypt that message. Bob
decrypts the message as follows:
Bob can reply to Alice, encrypting a message using Alice’s public key as
follows:
Bob knows Alice’s public key because it is public and published. Because
that message has been encrypted using Alice’s public key, it can only be
decrypted using Alice’s private key. Since Alice’s private key is private and
secret, only Alice knows it. Thus, only Alice can decrypt that message. Alice
decrypts the message as follows:
RSA relies on the fact that finding the factors of a very large composite
number is very hard. A composite number is the product of two prime
numbers. For example, 143, which is equal to 13 times 11, is a composite
number. The underlying mathematical foundations of RSA include group
theory and modular algebra.
16.2 Symmetric Encryption: The Model (AES),
Encryption App, Version 0
decryption algorithms. All of its methods are final (i.e., we cannot override
them). TABLE 16.1 shows some selected methods of the Cipher class.
Cipher does not have a public constructor. We use its getInstance static
▸ An element of randomness.
TABLE 16.1 Selected methods of the Cipher class
Method Description
public static Cipher Returns a Cipher for transformation, the name of an encryption algorithm.
getInstance( String Throws a NoSuchAlgorithmException and a NoSuchPaddingException.
transformation )
public byte [ ] doFinal( Encrypts input and other previously buffered bytes, and returns the
byte [ ] input ) resulting encrypted bytes. Throws an IllegalBlockSizeException, a
BadPaddingException, and an IllegalStateException.
public void init( int Initializes this Cipher with the operation opMode, key and with random as
opMode, Key key, a randomness source. Throws an InvalidKeyException and an
SecureRandom IllegalParameterException.
random )
Method Description
public Constructs a SecureRandom object using the default algorithm. We can use it
SecureRandom( to generate pseudo-random numbers.
)
Method Description
public static Returns a KeyGenerator that can generate a key for an encryption
KeyGenerator algorithm named algorithm. Throws a NoSuchAlgorithmException and a
getInstance( String NullPointerException.
algorithm )
public void init( int Initializes this KeyGenerator for a key whose size is keySize bits.
keySize )
We first write a class, part of our Model, to encapsulate the ability to encrypt
and decrypt a String using the AES algorithm. EXAMPLE 16.1 shows the
AESEncryption class. We can use it to generate a secret key and also to
perform encryption and decryption using any key. It has a default constructor,
two accessors for the secretKey instance variable, and the crypt method,
which we can use to encrypt or decrypt a String . We need a SecretKey , a
Cipher , and a SecureRandom reference in order to encrypt or decrypt a
rand ) of these types (lines 11–13) so we can access them in the crypt
We provide two accessors for the key of the algorithm, secretKey . The first
one, getSecretKey , (lines 38–40), returns secretKey . The second one,
getKeyBytes (lines 42–44), returns a representation of secretKey as an array
We can use the crypt method (lines 26–36) to either encrypt or decrypt a
String using a given key. Its first parameter specifies whether we encrypt or
Method Description
byte [ ] getEncoded( ) Returns the encoded form of this Key as an array of bytes.
into the original String . We also test that we can convert a key to an array
of bytes, reconstruct a key from that array of bytes, and that the
reconstructed key matches the original key. That is the type of operation that
happens when we distribute a key electronically. At line 20, we instantiate
aes , an AESEncryption instance variable. We encrypt the String original at
lines 25–26 and output the encrypted String to Logcat the result at line 27.
We decrypt the encrypted String at lines 28–29 and output the result to
Logcat at line 30, which, as FIGURE 16.1 shows, is identical to the String
we started with.
EXAMPLE 16.2 The MainActivity class of the Encryption app, Version 0
At lines 32–45, we simulate distributing a key and check that the key
received matches the key sent. We start with the key of aes and convert it to
an array of bytes at lines 32–33. At lines 34–36, we output the array of bytes
as an object and a String equivalent of that array of bytes. We then assume
that that array of bytes is sent to a user and reconstruct a key with it at lines
38–39. We retrieve the equivalent array of bytes for that reconstructed key
at lines 40–41. Finally, at lines 42–45, we output the array of bytes as an
object and a String equivalent of that array of bytes.
Figure 16.1 shows the output in Logcat. We can see two things: the memory
addresses for both arrays of bytes are different, and their values are
identical. Thus, this example shows that we can transfer a key from one user
to another by transferring bytes.
and the two TextViews on the right (lines 37–42 and 53–58). We give ids to
these three elements (lines 21, 38, 54) so we can retrieve them using the
findViewById method in the MainActivity class. When the user enters
something in the EditText and clicks on the button (lines 60–66), the
encryptAndDecryptAES method executes (line 66). We update the two
TextViews on the right accordingly, showing the encrypted String and the
update the two TextViews on the right side of the screen. We get references
to the EditText and the two TextViews at lines 22, 26–27, and 29–30. At line
23, we retrieve the user input. At lines 24–25, we encrypt it. We place the
result in the TextView in the middle right of the screen at line 28. We decrypt
the encrypted String at lines 31–32 and place the result in the other
TextView at line 33.
FIGURE 16.2 shows the app running inside the emulator after the user typed
in Android is fun and clicked on the button.
FIGURE 16.2 The Encryption app, Version 1, running inside the
emulator
EXAMPLE 16.8, to generate a set of private and public keys, and also to
perform encryption and decryption using any key. This design is similar to the
AESEncryption class. We declare three instance variables at lines 11–13:
cipher , a Cipher reference, and the two keys— privateKey and publicKey .
(called at line 20) returns a KeyPair : we can retrieve the two keys using the
getPrivate and getPublic methods of the KeyPair class (lines 21 and 22).
The crypt method (lines 43–53) is identical to the crypt method of the
AESEncryption class, except that it uses a Cipher reference for RSA instead
of AES .
In Version 3, we present the user with three buttons: the first one triggers
AES encryption and decryption, as before, and the other two trigger RSA
▸ One encrypting with the private key and decrypting with the public key.
▸ The other one encrypting with the public key and decrypting with the
private key.
We modify the activity_main.xml file, the View, and add two buttons. The two
buttons are coded at lines 67–74 and 76–83 of EXAMPLE 16.10. We give
the AES button an id (line 60) so we can position the two new buttons
relative to it. Also, since we now have three buttons, the AES button is no
longer centered. Because RSA encrypting results in a String much larger
than AES encrypting, we specify a bigger margin than before between the
first row and second row of components (line 30).
content View (line 23) before we retrieve them. Otherwise, they will be null
and the app will eventually crash at runtime. The encryptAndDecryptRSA1 and
encryptAndDecryptRSA2 methods are very similar to the encryptAndDecryptAES
FIGURE 16.4 The Encryption app, Version 3, after the user clicks on
RSA 1
FIGURE 16.5 The Encryption app, Version 3, after the user clicks on
RSA 2
FIGURES 16.4 and 16.5 show the app after the user enters Android is fun
and clicks on the RSA 1 button and RSA 2 button, respectively.
Chapter Summary
Encryption algorithms can be one way (something encrypted cannot be
decrypted), symmetric (the same key is used for encryption and
decryption), or asymmetric (a different key is used for encryption and
decryption).
The javax.crypto package provides interfaces and classes that
encapsulate various cryptology concepts and functionalities.
We can use the KeyGenerator class to generate a key for a symmetric
encryption algorithm.
We can use the KeyPairGenerator class to generate a pair of keys for an
asymmetric encryption algorithm.
Classes that encapsulate a key provide methods to convert a key object
to an array of bytes and to reconstruct a key from an array of bytes. In
this way, we can transfer a key electronically. Key distribution is an
important issue.
We can use the SecureRandom class to generate a cryptographically
secure pseudo-random number.
The Cipher class provides access to implementations of encryption and
decryption algorithms.
The doFinal method of the Cipher class encrypts or decrypts an array
of bytes into another array of bytes.
We can use the ISO-8859-1 encoding standard to convert an array of
bytes to a String and vice versa. It provides a one-to-one mapping
between a String and an array of bytes.
Before calling the doFinal method to encrypt or decrypt, we call init to
specify the mode (encryption or decryption), the key, and set an element
of randomness.
Exercises, Problems, and Projects
Multiple-Choice Exercises
8. Write the code to declare and instantiate a Cipher object for the AES
algorithm.
9. Write the code to declare and instantiate a Cipher object for the RSA
algorithm.
10. Write the code to declare and instantiate a KeyGenerator object for the
AES algorithm.
11. Write the code to declare and instantiate a KeyPairGenerator object for
the RSA algorithm.
12. The variable keyPair is a KeyPairGenerator reference and has already
been instantiated for the RSA algorithm. Write the code to retrieve the
array of bytes for its private key.
13. The variable keyPair is a KeyPairGenerator reference and has already
been instantiated for the RSA algorithm. Write the code to retrieve the
array of bytes for its public key.
14. A String named s has been initialized. Write the code to convert it to an
array of bytes using the ISO-8859-1 encoding standard.
15. An array of bytes has been initialized with some values. Write the code
to convert it into a String using the ISO-8859-1 encoding standard.
16. A Cipher reference named myCipher has been declared and
instantiated. The key myKey has also been declared and instantiated.
Write the code to initialize myCipher so that it is ready to encrypt
something with myKey.
17. A Cipher reference named myCipher has been declared, instantiated,
and initialized for encryption with some key. Write the code to encrypt
the array of bytes myBytes. Assign the result to a variable of your
choice.
Write an App
FIGURE A.1 shows the various parts of a device’s screen. The device’s
status bar is shown in yellow: it typically includes some icons for system and
application notifications, including the clock. In red is the app’s action bar,
which typically includes the app name on the top left and a menu on the right,
although it can be different depending on the app. The app content View is
shown in blue. This is where the contents of our app go. The visible display
frame is made up of the app’s action bar (in red) and the app content View
(in blue).
At the time of this writing, and according to Google’s design guidelines, the
height of the status bar is 24 dp and the height of the action bar is 56 dp.
These numbers are given in density independent pixels, so in order to
compute the actual number of pixels, we need to multiply them by the logical
pixel density of the device. There is no guarantee that the height of the status
and action bars will not change in the future. Thus, it is better to retrieve
them programmatically.
The following code sequence shows how to retrieve the logical pixel density
of a device.
information about the display, including its size, its density, and font scaling.
The density field of the DisplayMetrics class stores the logical pixel density
of the device. TABLE A.1 shows the getResources method from the
ContextWrapper class, the getDisplayMetrics from the Resources class, and
DisplayMetrics density The scaling factor for the density independent pixel
unit.
Once we have the density of the device, we can assign a default value to the
action bar height and the status bar height as follows:
Now that we have a default value for the action bar height, we can attempt to
retrieve its value dynamically using the following sequence. If we are not
successful, we use the action bar default value.
// set default value for action bar height
The height of the action bar is part of the theme of the app. The getTheme
method, shown in TABLE A.2, inherited from ContextThemeWrapper by
Activity , returns the Theme associated with the current Context . Theme is an
inner class of the Resources class: it stores the attribute values for a
particular theme. The resolveAttribute method of the Theme class, shown in
TABLE A.3, checks if an attribute is present in a theme. If it is, it returns
true and assigns the attribute value to its TypedValue parameter, the second
the TypedValue class, shown in TABLE A.4, to convert the data inside tv ,
tv.data , to an integer representing its number of pixels. If we output the type
Resources.Theme getTheme( ) Returns the Theme associated with the current Context.
TABLE A.3 The resolveAttribute method of the Resources.Theme class
boolean resolveAttribute( int Returns true if the attribute resourceId is present in this Theme. If
resourceId, TypedValue it is, it assigns its value to outValue; resolveRefs is used to
outValue, boolean resolveRefs determine the type of resource of the attribute.
)
To retrieve the value of the status bar height dynamically, we use the
following sequence. If we are not successful, we use the status bar default
value.
int resourceId =
res.getIdentifier( ”status_bar_height”, ”dimen”, ”android” );
We can get the id of a resource given its name and the type of resource it is
by calling the getIdentifier method of the Resources class shown in TABLE
A.5. If the value returned is not 0 , we have successfully retrieved it. We can
then obtain its dimension by calling the getDimensionPixelSize method of
Resources , passing the id value. Table A.5 also shows that method.
int getIdentifier( String Returns the resource id for the resource named name; type specifies the
name, String type, type of the resource (color, dimen, etc.), and package is the package that
String package ) the resource is in. Both type and package are optional if name includes
them (i.e., is a String like “package:type/resourceName”). Returns 0 if not
found.
EXAMPLE A.1 shows the MainActivity class for a simple app that retrieves
the status bar and action bar heights.
EXAMPLE A.1 The MainActivity class, showing how to retrieve the
status bar and action bar heights of the current device
FIGURE A.2 shows the output of Example A.1 when running in the Nexus 5
emulator. The retrieved action bar height, 168, is equal to 3 (the pixel
density) times 56 (the height given by Google for the action bar in dp units).
The retrieved status bar height, 72, is equal to 3 (the pixel density) times 24
(the height given by Google for the status bar in dp units).
FIGURE A.2 The output of Example A.1 for the Nexus 5 emulator
FIGURE A.3 The output of Example A.1 for the Nexus 4 emulator
FIGURE A.3 shows the output of Example A.1 when running in the Nexus 4
emulator. The retrieved action bar height, 112, is equal to 2 (the pixel
density) times 56 (the height given by Google for the action bar in dp units).
The retrieved status bar height, 48, is equal to 2 (the pixel density) times 24
(the height given by Google for the status bar in dp units). We can verify that
the resource id is the same as for the Nexus 5 emulator.
APPENDIX B: Setting the Font Size of
a TextView Dynamically
Sizing the font inside a TextView is one issue that arises often when building
an app. Since the app will run on many devices with various screen sizes, it
could look awkward on some devices if we use the same font size for all the
devices. There are many ways to set the size of the font inside a TextView
so that the text fits well. We explore how to set it so that the text fits in one
line and the font size is maximal. We assume that there is no padding inside
the TextView .
▸ It sets the size of the font in the TextView so that the text fits on one
line and that the font size is maximal.
We can use the getLineCount method, shown in TABLE B.1, of the TextView
class to access the number of lines that the text occupies within the
TextView . One issue that arises when resizing a View or a property of a View
order to access the width, height, and number of lines of text inside a
TextView . If we call measure first, then the call to getLineCount returns the
actual number of lines that the text takes inside the TextView . To obtain width
and height information after calling measure , we should call the
getMeasuredWidth and getMeasuredHeight methods. The measure method
takes two int parameters, specifying the type of size constraint that the
parent of the View has imposed or not imposed on it. TABLE B.2 shows
three constants of the MeasureSpec class that we can use to specify values
for these two parameters.
TABLE B.1 Selected methods of the View and TextView classes
View int getWidth( ) Returns the width of the View in pixels, 0 if it has not been
displayed yet.
View int getHeight( ) Returns the height of the View in pixels, 0 if it has not been
displayed yet.
View void measure( int Called if we want to find out the size of the View;
widthMeasureSpec, widthMeasureSpec and heightMeasureSpec represent the
int horizontal and vertical requirement imposed by the parent View.
heightMeasureSpec )
View int Returns the width of the View in pixels as measured by the call
getMeasuredWidth( ) to measure.
View int Returns the height of the View in pixels as measured by the call
getMeasuredHeight( ) to measure.
TextView int getLineCount( ) Returns the number of lines of text inside the TextView, 0 if the
TextView has not been displayed yet.
TextView void setTextSize( int Sets the size of the text inside the TextView to size units.
unit, float size )
for the font before we decrease it by 1 to find the correct value so that the
text fits on one line. We also declare another constant, MIN_FONT_SIZE , whose
value is set to 1 (line 9), and we use this value as the minimum font size for
the TextView .
The setFontSizeToFitInView method, at lines 11–32, accepts a TextView
parameter, tv , and modifies its font size. It returns its modified font size. We
assign the value of MAX_FONT_SIZE to the variable fontSize at line 18 and set
the font size of tv to fontSize at line 19. We use the integer constant
COMPLEX_UNIT_SP from the TypedValue class as the first argument of the
setTextSize method of the TextView class to specify the units. It means that
the unit is a scaled pixel. TABLE B.3 shows several constants of the
TypedValue class.
Constant Description
UNSPECIFIED Parent has not imposed any constraint on the child View.
EXACTLY Parent has determined the exact size of the child View.
AT_MOST Parent has determined the maximum size of the child View.
EXAMPLE B.1 The DynamicSizing utility class
Constant Description
Before we get the number of lines in the text by calling getLineCount at line
21, we call measure at line 20, so that getLineCount does not return 0 . If it
does not, we loop at lines 23–28 until the number of lines, stored in the
variable lines , is equal to 1 or the font size goes down to 2 . If for some
reason, for example, if the text is very long, the font size reaches size 2 , we
exit the loop. This is unlikely to happen, but it is good defensive programming
practice to guard against the unexpected. If we get inside the loop and the
font size is greater than 2 , it means that the number of lines is greater than
1 , and therefore the font size needs to be reduced. We decrease fontSize
by 1 at line 24, reset the font size of tv to reflect that at line 25, call measure
at line 26 to reset the measured values, and update lines at line 27. Since
the font size decreases inside the loop, either the value of lines will reach 1
or the value of fontSize will reach 2 .
After exiting the loop, we reset the font size of tv to fontSize – 1 at line 29,
to be sure that the text fits on one line within tv . We return fontSize at line
31. Note that we do not account for any padding inside the TextView .
We now build a simple app and edit the activity_main.xml file and the
MainActivity class to test the setFontSizeToFitInView method of the
FIGURE B. 1 shows the app running inside the emulator. Try changing the
text (to PRO or PROGRAM, for example) and/or the width (to 300, for
example) of the TextView and test the app again. The font size adjusts
accordingly.
APPENDIX C: How to Download and
Install Google Play Services, Use
Maps
The Android Standard Development Kit (Android SDK) does not include all
the packages and classes we need for development. For example, maps and
advertising-related classes may not be included. However, we can download
and install them with the Android SDK Manager. TABLE C.1 shows the
possible extra steps needed.
Step Description
1 Download and install Google Play Services (if not already done).
3 Update the build.gradle (Module: app) file (if not already done—that depends on the Android
template we use).
4 For an app using Google Maps, obtain a key from Google (if not already done).
5 Add the appropriate elements to the AndroidManifest.xml file (if not already done—that
depends on the Android template we use).
FIGURE C.1 The Android SDK manager before Google Play services are
installed
FIGURE C.2 The Android SDK manager: Google APIs are installed and
there is an update available
Step 3 (to do for each project):
Make Google Play services libraries
available to an app
In order to do this, we need to modify the build.gradle file (there are two
such files. We need to edit the one for the module). EXAMPLE C.1 shows
the edited file. The only addition is at line 27: we need to include the
appropriate version of the Google Play services libraries. This example
shows edition 9.4.0. Any time we modify a gradle file, we should sync the
project. We can do that by clicking on the sync icon. FIGURE C.3 shows the
sync icon. After this step, the project should compile properly.
Note that if we use an existing Android template such as the Google Maps
Activity template when creating a new project, the build.gradle file already
contains line 27.
If the keytool command is not recognized by the system, we can either add
the directory where it is located to the path, or run the keytool command
from inside its directory. The executable file for keytool, keytool.exe, should
be located inside the bin directory of the Java jdk directory. On the author’s
computer, keytool.exe is located in the C:\Program
Files\Java\jdk1.8.0_60\bin directory.
Fingerprint certificates are unique and provide a way to identify an app. That
enables Google to track the app in Google Play as well as track the use of
map resources by our app.
If we have already registered a project, the project shows on the web page.
Otherwise, click on Create project. Enter a name for our project and click on
Create. Then, select Use Google APIs.
Among the list of available services, locate Google Maps Android API, as
shown in FIGURE C.5, and click on it. Next, click on Enable API, as shown in
FIGURE C.6. At that time, unless we have done it before, we will be asked
to enter our credentials.
FIGURE C.5 Finding Google Maps Android API in Google Play services
Next, we need to obtain a key. On the top left, click on the three small
horizontal lines next to Google Developers Console. From the menu, choose
API Manager (see FIGURE C.7). Then select Credentials. From the New
Credentials menu, select API key (see FIGURE C.8), then choose Android
key. At that point, we should enter the package name of our app and the
SHA-1 fingerprint from Figure C.4, as shown in FIGURE C.9. Note that we
have hidden the author’s fingerprint in the figure. We then click on Create and
the key shows.
FIGURE C.8 Selecting API key from the New credentials menu
FIGURE C.9 Entering package name and SHA-1 fingerprint
▸ If the app is using Google Maps, specify the name and value of the
key to access map data using a meta-data element.
▸ If the app is using Google Maps, list the Google Maps library using a
uses-library element.
If we use the Google Maps Activity template, some of these may have been
included in the AndroidManifest.xml automatically.
APPENDIX D: The AsyncTask Class
When we start the app, our code executes in the main thread. Sometimes,
we need to start another thread. For example, we may need to retrieve data
from a remote location. Furthermore, we may need that other thread to finish
before we use and place its data inside a GUI component. The AsyncTask
class, from the android.os package, allows us to perform a background
operation and then access the main thread to communicate its results. It is
designed to execute short tasks, lasting a few seconds or less, and should
not be used to start a thread that runs for a long time. AsyncTask is abstract
so we must subclass it and instantiate an object of the subclass in order to
use it.
The AsyncTask class uses three generic types that we must specify when
extending it. The syntax for the header of a subclass is as follows:
where Params , Progress , and Result are placeholders for actual class names
and have the following meanings:
▸ Params is the data type of the array that is passed to the execute
method of AsyncTask when we call it.
▸ Progress is the data type used for progress units as the task executes
in the background. If we choose to report progress, that data type is
often Integer or Long .
▸ Result is the data type of the value returned upon execution of the
task.
▸ With that object, call the execute method to start the task.
The execute method is public and final. AsyncTask includes some
protected methods that can be overridden by a subclass. TABLE D.1 lists
The three dots syntax after the data type in the parameter list indicates that
a method accepts a variable number of arguments, also known as varargs.
Varargs must be at the final parameter position in the parameter list. The
syntax for a method header using a variable number of arguments is as
follows:
variableNames )
Method Description
AsyncTask execute( Public and final; params represent an array of values of type Params, a
Params. . . params ) generic type; returns this instance of AsyncTask.
void onPreExecute( ) Protected. Automatically called after we call execute and before
doInBackground starts.
Result doInBackground( Protected. Abstract method that we need to override to perform the task;
Params. . . params ) params is the argument passed to execute when we call it.
void publishProgress( Protected and final. Calling that method triggers a call to
Progress. . . values ) onProgressUpdate.
Note that there is no space between the data type and the three dots. The
execute , doInBackground , publishProgress , and onProgressUpdate methods
accept a variable number of arguments.
For example, and assuming we have defined a class named Item , we could
override the doInBackground method using the following method header:
task.execute( 7 );
// #3: pass several arguments
task.execute( 6, 8, 15 );
task.execute( values );
When we call the execute method, the following methods of the AsyncTask
class execute in this order: onPreExecute , doInBackground , and
onPostExecute .
method, which we can override: for example, we can update a progress bar
in the user interface thread. When the doInBackground method finishes
executing, the onPostExecute method is automatically called. We can override
it to update the user interface with the results of the task that just completed.
The argument that is automatically passed to the onPostExecute method is
the value that is returned by the doInBackground method.
Inside the onPostExecute method, it is very likely that we want to update the
activity of the user interface thread. There are essentially two ways to do
this:
We now build a very simple app in order to illustrate how to use the
AsyncTask class. We use the Empty Activity template. EXAMPLE D.1 shows
the TestTask class. In the class header (line 6), we specify Integer as the
Params data type, Void as the Progress data type (we are not interested in
variable, passing message at line 21. The updateView method, which we need
to add to the MainActivity class, is expected to update the View based on
the value of its argument.
Inside all the methods, we add an output statement (lines 10, 15, 20) so that
we can trace the order in which all the methods are called.
EXAMPLE D.2 shows the MainActivity class. At line 16, we declare and
instantiate a TestTask object and call the execute method at line 18, passing
1 . In this simple example, the argument we pass is never used and therefore
irrelevant. However, its data type must match the data type of the
parameters of the doInBackground method of the TestTask class. Since the
execute method accepts a variable number of arguments, we can pass 0, 1,
inside the onCreate method if there were statements after line 18. In that
case, there would be interleaved execution between those statements and
the statements executing inside the TestTask class. If we attempted to
retrieve the value or values generated by a task right after calling execute
(even though the value[s] generated by the task is inside the TestTask class,
it would be easy to write them as public static data of another class and
read that public static data from the MainActivity class), in this example
inside the onCreate method, that value or those values would very likely be
null . The correct way to process the value generated by a task is to do it
that the task has not finished executing and the results are not ready.
Instead, process the results inside the onPostExecute method.
FIGURE D.1 shows the Logcat output when we run, in particular the order of
execution. It shows that the Logcat output statement at line 19 of the
MainActivity class executes before the doInBackground method of the
TestTask class executes. This very simple simulation confirms that the
activities
life cycle of, 145–150
output and state of, stack as user interacts with app, 149t
sharing data between, 150–154
transitions between, 155–161
Activity class, 141, 223, 224, 227, 251, 252, 279, 280, 303, 306, 326,
addConnectionCallbacks , 436
afterTextChanged method, 76
android:inputType , 73
of LinearLayout , 343
android:theme , 69
animation, 155
XML elements and corresponding classes, 156t
AppBarLayout , 174
Apple, 2
AppTheme , 69
beforeTextChanged method, 76
BIOS menu, 18
Bitmap class, 303, 318, 319, 472–473, 473t, 496
BlackBerry, 2
blue_thumb.xml file, 479
build method
C
calculate method, 70, 76
class header
of Hangman class, 371b
of MainActivity class, 370b
constant values
of Configuration class, related to device’s screen size, 223t
for orientation field of Configuration class, 223t
content , 253
data
deleting, 197–202
handling persistent, 161–166
updating, 202–207
Debug
app with Logcat, 24–27
Debugger
breakpoint, 27–28
using, 27–28
defensive programming, 73
DeleteActivity class, 197, 198, 199–200b
duckRect , 303
duckShot , 313
EditText , 44, 46, 48, 50, 53, 64, 66, 70, 71, 73, 75, 78, 181, 371
element content, 10
email (Sending), 499
Empty Activity template, 251, 302, 341, 434
emulator, running app inside, 18–23
Encryption app
Version 1, 629f
Version 3, 638f
GPS app, Version 2, 453f
landscape orientation, 22, 23f
portrait orientation, 22, 22f
Show A Map app
Version 0, 384f
Version 1, 400f
Version 2, 403f
test app, 654f
TicTacToe app, Version 0, 96f
useful information about, 23t
vertical and horizontal position, 232f, 236f
Web Content app
Version 3, 530f
Version 4, 533f
widgets running in emulator, 547–549f
Encryption app
asymmetric
adding RSA to model, Version 2, 630–634
modifying the view, Version 3, 634–638
symmetric
adding a view, Version 1, 625–629
AES model, Version 0, 619–625
events, 40, 69
extending the View class, 317
extends , 314
FloatingActionButton , 174
fragment_game_control.xml file
Hangman app
Version 0, 343b
Version 1, 349b
Hangman app
Version 3, 361b
Version 4, 364b
GameStateFragment class, 357, 369, 370
Hangman app
Version 2, 358b
Version 6, 367–368b
Version 7, 372–373b
getNumberOfParts , 259
Google, 2
Google Maps
activity template, 391–400, 391–392f
android API, enabling, 660f
API key from New Credentials menu, 662f
API manager, selecting, 661f
appropriate permissions, adding, 661–663
in Google Play services finding, 660f
key from Google, 657–661, 659f
package name and SHA-1 fingerprint, 662f
graphics, 298–301
green_thumb.xml file, 479
handler, 75, 76
handling the event, 40, 69
handling touch events, 320–324
Hangman app
adding GUI components, styles, strings, and colors, Version 1,
346–352
communication between fragments and their activity, Version 4,
362–364
fragment, defining and adding
Version 0, 341–346
Version 2, 352–359
Version 3, 359–362
GUI improvement of, Version 7, 371–376
invisible fragment, using, Version 5, 365–367
reusable fragment, making, Version 6, 367–371
HelloAndroid, 3
emulator running, 22f
GUI preview, 8
MainActivity class, 16–18, 17b
iOS, 2
isLayoutSizeAtLeast method, 223
keyguard , 578–579
LabelStyle , 64, 66
life-cycle methods
of activity class, 347f
of fragment class, 344, 347f
placeholder, 592
ListFragment , 341
Logcat output
Encryption app
Version 0, 624–625, 624f
Version 2, 634f
GPS app
Version 0, 440
Version 1, 444, 445f
Version 3, 456, 458f
Stopwatch app, 603f
Web Content app
Version 0, 516, 517f
Version 1, 520, 521f
Version 2, 526f
MainActivity class, 6, 16–18, 17b, 25, 40, 70, 73, 100, 111, 142, 145,
148, 159, 163, 174, 229, 234, 252–254, 261, 306, 318, 320, 327, 346,
355, 357, 359, 367, 369, 370, 434, 440, 447, 456
Candy Store app
Version 0, 178–179b
Version 1, 184–185b
Version 3, 197b
Version 4, 203b
Version 5, 210–212b
Duck Hunting app
Version 0, 307b
Version 1, 319b
Version 2, 321–322b
Version 3, 328–329b
edits in, replacing activity_main with activity_data, 140b
Encryption app
Version 0, 623–624b
Version 1, 628–629b
Version 2, 632–633b
Version 3, 636–637b
GPS app
Version 0, 438–439b
Version 1, 442–444b
Version 2, 450–452b
Version 3, 457b
Hangman app
Version 1, 351b
Version 2, 356b
Version 3, 360b
Version 4, 363b
Version 5, 366b
Version 6, 370b
Version 7, 376b
HelloAndroid, Version 1, 26b
with life cycle methods, 146–147b
modifyData method in, 159b
Photo app
Version 0, 469–470b
Version 1, 475–476b
Version 2, 483–484b
Version 3, 488–489b
Version 4, 497–498b
Version 5, 499, 500b
playHitSound method of, 330
Puzzle app
Version 0, 264
Version 1, 266–267b
Version 3, 286b
Show A Map app
Version 3, 406, 407–408b
Version 4, 416–417b
status and action bar heights, retrieving, 646–647b
Stopwatch app
Version 0, 592b
Version 1, 594–595b
Version 2, 597–598b
Version 3, 604–605b
text view, setting font size of, 653b
TicTacToe app
Version 0, 93b
Version 1, 97–98b
Version 2, 101–102b
Version 3, 107–109b
Version 4, 112–114b
Version 5, 119–121b
Tip Calculator app
Version 3, 71–72b
Version 4, 77–78b
Touches app
Version 0, 254b
Version 1, 257–258b
Version 2, 281–282b
voice recognition app
Version 0, 387–388b
Web Content app
Version 0, 515–516b
Version 1, 520–521b
Version 2, 525b
Version 3, 528–529b
Version 4, 531–532b
MapsActivity class
Version 1, 398–399b
Version 2, 402b
Version 3, 409–411b
Version 5, 421–423b
match_parent value, 45
MouseEvent , 320
NUMBER_PARTS , 259
242
of Activity class, 227, 227t
onCreate method, 17, 227, 229, 232, 234, 241, 253, 263, 266, 320,
onDraw method, 299, 300, 303, 305, 315, 316, 318, 319, 320, 322, 331
onEditorAction method
of TextView.
OnEditorActionListener interface, 371, 371t
onTextChanged method, 76
OpenGL, 314
org.xml.sax packages, 513
OutputStyle , 66
params , 258
parent style, 64
parseFloat , 71
parseInt methods, 71
PreferenceFragment , 341
pseudo-code, 265
public fields and methods, 222, 222t, 345
R.drawable.nameOfFile , 300
Rect class
intersects method of, 313, 313t
res directory, 7, 8
Resources.Theme class
screen
of device, 643, 643f
view components of, 226, 226f
SCREENLAYOUT_SIZE_LARGE , 223
AbsSeekBar , 478t
setDoubleTapListener , 285
setNegativeButton , 111
setNeutralButton , 111
setOn-TouchListener method
setPadding , 53
setPositiveButton , 111
setShowAsAction , 177t
SoundPool.Builder , 325
default constructor and build method of, 326, 326t
Spec , 105
status bar
height of, retrieving, 643–648
String MainActivity , 25
String word , 340
strings.xml file, 13b, 14b, 51–52, 52b, 62b, 135b, 177b, 184b, 229,
231b, 234b, 350b, 415b, 449b, 601b, 627b
styles
syntax for defining, 63–64
and themes, 62–69
styles.xml file, 15b, 65b, 135b, 187b, 231b, 350–351b, 445b, 449b,
480b, 486b, 590b, 627–628b
super constructor, 302
Symbian, 2
symmetric encryption, 618
SystemClock class
TableLayout
for front screen GUI, 131–135
inheritance hierarchy for, 132
TemperatureParser class
TextAppearance , 64
TextStyle , 64, 66
TextView , 12, 44–45, 46, 48, 50, 61, 66, 70, 115, 181, 229, 250, 251,
android:textSize, 50t
dragging of, 270
font size of, setting, 649–654
moving, 256–259
selected XML attributes of, 55t
useful methods of, 71t
TextView.OnEditorActionListener interface
thread
main, 522
user-interface, 522
TicTacToe app
version 0, 91–96
version 1, 96–100
version 2, 100–102
version 3, 105–110
version 4, 110–115
version 5, 115–121
tipCalc , 70
tv.RelativeLayout.LayoutParams , 258
Update method, 97
updateCannon , 320
verticalDimensionsSet , 241
WebViewFragment , 341
XML, 510
XML parsing, 510