Android Pocketprimer
Android Pocketprimer
Pocket Primer
LICENSE, DISCLAIMER OF LIABILITY, AND LIMITED WARRANTY
By purchasing or using this book and disc (the “Work”), you agree that this
license grants permission to use the contents contained herein, including the
disc, but does not give you the right of ownership to any of the textual content
in the book / disc or ownership to any of the information or products contained
in it. This license does not permit uploading of the Work onto the Internet
or on a network (of any kind) without the written consent of the Publisher.
Duplication or dissemination of any text, code, simulations, images, etc. con-
tained herein is limited to and subject to licensing terms for the respective
products, and permission must be obtained from the Publisher or the owner
of the content, etc., in order to reproduce or network any portion of the textual
material (in any media) that is contained in the Work.
MERCURY LEARNING AND INFORMATION (“MLI” or “the Publisher”) and anyone
involved in the creation, writing, or production of the companion disc, accom-
panying algorithms, code, or computer programs (“the software”), and any
accompanying Web site or software of the Work, cannot and do not warrant
the performance or results that might be obtained by using the contents of the
Work. The author, developers, and the Publisher have used their best efforts
to insure the accuracy and functionality of the textual material and/or pro-
grams contained in this package; we, however, make no warranty of any kind,
express or implied, regarding the performance of these contents or programs.
The Work is sold “as is” without warranty (except for defective materials used
in manufacturing the book or due to faulty workmanship).
The author, developers, and the publisher of any accompanying content, and
anyone involved in the composition, production, and manufacturing of this
work will not be liable for damages of any kind arising out of the use of (or the
inability to use) the algorithms, source code, computer programs, or textual
material contained in this publication. This includes, but is not limited to, loss
of revenue or profit, or other incidental, physical, or consequential damages
arising out of the use of this Work.
The sole remedy in the event of a claim of any kind is expressly limited to
replacement of the book and/or disc, and only at the discretion of the Publisher.
The use of “implied warranty” and certain “exclusions” vary from state to state,
and might not apply to the purchaser of this product.
Companion files for this title may be requested at [email protected].
ANDROID
Pocket Primer
Oswald Campesato
This publication, portions of it, or any accompanying software may not be reproduced
in any way, stored in a retrieval system of any type, or transmitted by any means,
media, electronic display or mechanical display, including, but not limited to,
photocopy, recording, Internet postings, or scanning, without prior permission in
writing from the publisher.
The publisher recognizes and respects all marks used by companies, manufacturers,
and developers as a means to distinguish their products. All brand names and product
names mentioned in this book are trademarks or service marks of their respective
companies. Any omission or misuse (of any kind) of service marks or trademarks, etc.
is not an attempt to infringe on the property of others.
Our titles are available for adoption, license, or bulk purchase by institutions,
corporations, etc. For additional information, please contact the Customer Service
Dept. at (800) 232-0223 (toll free). Digital versions of our titles are available at:
www.authorcloudware.com and other e-vendors. Companion files for this title may be
requested at [email protected].
Preface xv
About the Technical Reviewer xxi
Index 283
So, while online code samples are obviously useful, you also need to spend
time reading them, just as you would for this book. Moreover, you also
need to determine which online code samples actually work correctly,
which this book has already done for you.
Android. On the other hand, if you are in management, you will under-
stand enough about Android to interact on a technical level with Android
developers.
(SAX and DOM) in this book. Another topic that is not covered is Java
Native Interface (JNI), which allows Java code to invoke C/C++ functions.
If you are interested in JNI, you need to download and install the Android
Native Development Kit (NDK), which contains a set of tools for creating
libraries that contain functions that can be called from Java code.
The material in the chapters will familiarize you with the Android APIs,
after which you can do further reading to deepen your knowledge. For
example, Chapter 4 covers graphics and animation effects, but the code
samples in that chapter will not make you a game expert.
1
A QUICK INTRODUCTION
TO ANDROID
T
his introductory chapter discusses how to develop Android appli-
cations, as well as their structure, various XML-based configura-
tion files, and some basic features of Android. You will learn how to
create mobile applications in Android Studio, which is the recommended
IDE for Android development. Two important features of Android are
Activitys and Intents, which you see throughout this book.
The first part of this chapter discusses the structure of Android projects
in Android Studio and some of the important files in Android projects.
Moreover, this section discusses the “Android way” of providing values for
properties that determine the position and size of Android components in
Android applications. Note that additional details about positional attrib-
utes, different screen densities, and different screen sizes are available in
Chapter 2.
The second part of this chapter switches focus to describe Android-
specific concepts and features. Specifically, this section quickly intro-
duces you to Android Activitys and Intents, which are key features of
Android. Almost every Android application in this book uses an Android
Activity, and several examples use Android Intents.
As you will see, this chapter contains a combination of a “hands on”
approach and a conceptual overview of Android. Additional “hands on”
material is presented in Chapter 2 so that you will learn techniques
and concepts for creating Android applications on an as-needed basis.
However, feel free to read this chapter (as well as other chapters) in the
order that best fits your learning style.
2 • ANDROID POCKET PRIMER
In some cases you might need to read subsections more than once in
order to “synchronize” your understanding of Android applications, which
is typical when you learn a new technology. In fact, Android Intents
will require longer study (compared to Android Activitys) because they
involve subtle points that you will understand better through practice.
Fortunately, subsequent chapters contain code samples that illustrate
how to use Intents and how to incorporate various UI components in
Android applications.
One final comment: at some point (now would be a good time) please
read the Preface for information regarding the goal of this book and
assumptions about your technical background.
The features of Android are cumulative (though there are new classes
that replace deprecated classes), so each version of Android supports the
preceding versions (see Figure 1.1). Keep in mind that sometimes there
are changes in the signature of APIs between consecutive Android ver-
sions. This progression of features will help you plan your Android appli-
cations in terms of which versions you can target with specific features.
Figure 1.1 displays the percentage of devices that operate different ver-
sions of Android.
Navigate to the following link to see more recent percentages:
https://developer.android.com/about/dashboards/index.html.
Android Studio
Navigate to this website in order to install Android Studio on your machine:
https://developer.android.com/sdk/index.html.
If you use the Simulator instead of an Android device, you must create
an AVD (Android Virtual Device), which is described in great detail here:
https://developer.android.com/tools/devices/index.html.
You can launch Android applications in the Simulator, but it’s often signif-
icantly faster to deploy Android applications to a device. You can deploy
Android applications from Android Studio as well as from a command
4 • ANDROID POCKET PRIMER
line via the adb utility (as you will see later). Android devices include (but
are not limited to) smart phones in the Nexus series and the Pixel series,
tablets, Google Auto, Google TV, Google Things (IoT), and Google VR.
In addition, there are several Android application types, including fore-
ground, background, intermittent, widgets, and wallpaper applications.
Now launch Android Studio, navigate to the HelloWorld project, and right-
click on the project name in order to display the expanded directory structure.
Earlier versions of Android projects have the XML file main.xml instead
NOTE of activity_main.xml as the default UI configuration file.
In brief, HelloWorld.java is the Java class where you put your custom
code (typically in the onCreate() method). The auto-generated Java
class R.java, located in the build-related portion of the Android pro-
ject, is essentially a Java-based “binding” class with static constants that
reference the UI components that are defined (by you) in the XML file
activity_main.xml. As you will soon see, a one-line snippet of Java
code enables you to “point” to this XML configuration file, as well as any
other configuration files in your Android application.
Here is a brief description of important XML files in Android applications:
1) the AndroidManifest.xml file is like a “master control” file that
defines Java-based resources (Activitys, Intent Filters,
Services, etc.) and permissions for your Android application
2) the activity_main.xml file (which is just the default name) is a
configuration file containing the UI components (if any) that are dis-
played in the main screen
3) the strings.xml file contains the text strings that are referenced in
the XML file activity_main.xml
4) the dimens.xml file contains dimension values (e.g., 16dp)
XML files such as strings.xml and dimens.xml provide a level of indi-
rection that supports a separation between the definition of UI compo-
nents and the text strings that are used in those UI components.
Notice that the app/src/main/res/values subdirectory can also con-
tain other files, such as styles.xml, color.xml, as well as custom files
8 • ANDROID POCKET PRIMER
that you need for your Android application. The contents of these files
will be discussed in later chapters.
import android.app.Activity;
import android.os.Bundle;
setContentView(R.layout.activity_main);
setContentView(R.layout.new_view);
Listing 1.3 displays the contents of the resources file R.java that is
automatically generated whenever you create an Android application in
Android Studio.
*
* This class was automatically generated by the
* aapt tool from the resource data it found. It
* should not be modified by hand.
*/
package com.iquarkt.hello;
The integer values in Listing 1.3 are essentially references that corre-
spond to assets of an Android application. For example, the variable icon
is a reference to the icon.png file that is located in one of the drawable
subdirectories of the res directory. The variable activity_main in the
Java layout class is a reference to the XML file activity_main.xml
(shown later in this section) that is in the res/layout subdirectory. The
variables app_name and hello are references to the XML <app_name>
element and XML <hello> element that are in the XML file strings.xml
(shown earlier in this section) that is in the res/values subdirectory.
Now that you have seen the contents of some Java-based project files, let’s
turn our attention to some of the other XML-based files, starting with the
primary “control file” in our Android project.
</application>
<uses-sdk android:minSdkVersion="9" />
</manifest>
The final part of Listing 1.4 specifies the minimum Android version
number that is required for this application, as shown here:
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
</RelativeLayout>
Keep in mind that the “available space” for match_parent involves two
situations:
1) If a View component is the only View component in the parent ele-
ment, that View component will occupy the entire width/height of
the parent (just like match_parent)
14 • ANDROID POCKET PRIMER
2) If there are other View components which have fixed size or sim-
ply wrap their content (that is, they do not occupy all the available
space), then the current View will occupy the rest of the available
width/height. Note that there can be only one View set to fill up the
remaining space.
Thus, the match_parent attribute forces a View to expand to take up
as much space as is available within the layout element in which it’s been
placed, whereas the attribute wrap_content means “expand enough to
display the view (and no more).”
To paraphrase the preceding paragraph: a value of wrap_content
performs a “minimalistic” match, whereas a value of fill_parent or
match_parent performs a “maximal” or “greedy” match with respect to
available space on a screen in an Android device.
Looking back at Listing 1.2, the TextView component contains the
attributes layout_width and layout_height, whose values are fill_
parent and wrap_content, respectively. The wrap_content attribute
specifies that the size of the view will be just big enough to enclose its
content (plus padding). The attribute text refers to the XML <hello>
element that is specified in the strings.xml file (located in the res/
values subdirectory), whose definition is shown here:
Meta-Characters in activity_main.xml
As you already saw, Listing 1.4 contains some common prefix-plus-
attribute combinations in AndroidManifest.xml, and there are many
such combinations that you can specify in activity_main.xml (or
whatever you decide to name this XML document). For now you need to
learn about the @ symbol and the + symbol.
The @ meta-character indicates that the string that follows is the name of
an XML file (which implies a .xml suffix for the filename). For example,
the following attribute appears in Listing 1.4:
android:text="@string/hello"
A QUICK INTRODUCTION TO ANDROID • 15
android:id="@+id/my_button"
In the preceding code snippet, the @ symbol instructs the XML parser
to parse and expand the rest of the id string and identify it as an id
16 • ANDROID POCKET PRIMER
resource. The string “+id” indicates that the string that follows the slash
(“/”) will appear as a constant in the auto-generated Java class R.java.
<manifest xmlns:android=http://schemas.android.com/apk/
res/android.
advisable for you first to learn about some of the key concepts in Android
that will help you not only for the code samples in Chapter 2, but also the
code samples in most of this book.
// item #2
TextView myTextView=(TextView) findViewById(R.id.textView);
myTextView.setText("Clicked Button");
}
import android.widget.Button;
import android.widget.TextView;
A QUICK INTRODUCTION TO ANDROID • 21
Return to the designer and you will see buttonOnClick() in the drop-
down list for the Button component.
The file content_main.xml is for the things that you want to display to
users.
Open the file activity_main.xml in Android Studio and you will see an
“include” for content_main.xml, as shown here:
include layout="@layout/content_main"
android:versionCode
android:versionName
android:icon
android:label
android:name
android:resource
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
Notice that Listing 1.7 does not contain this code snippet:
setContentView(R.layout.activity_main);
layout_alignParentTop
layout_alignParentLeft
layout_alignParentRight
layout_alignParentBottom
layout_centerInParent
layout_centerVertical
layout_centerHorizontal
The component which receives the Intent can extract the extra data (if
any) by using the getIntent().getExtras() method call, as shown in
the following code block:
if(value1 != null) {
// Do something with the extra data
}
available browsers (and also ask how often you want to use the browser
you have selected). This scenario is an example of an Android Intent in
action. Similarly, if you want to send email and you have multiple email
clients installed on your mobile device, Android will prompt you with a
list of the installed email clients.
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
The XML <action> element in the preceding code snippet specifies the
default value android.intent.action.MAIN and the XML <category>
element specifies android.intent.category.LAUNCHER (also a default
value), which means that the parent Activity will be displayed in the
application launcher.
An Intent Filter must contain an XML <action> element, and
optionally contain an XML <category> element or an XML <data> ele-
ment. As you can see, the <intent-filter> element in Listing 1.4 con-
tains the mandatory XML <action> element that specifies the default
action, and an optional XML <category> element, but not the optional
XML <data> element.
28 • ANDROID POCKET PRIMER
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
A QUICK INTRODUCTION TO ANDROID • 29
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string" />
<intent-filter . . . >
<data android:scheme="something"
android:host="project.example.com" />
. . .
</intent-filter>
The preceding code snippet is equivalent to the following:
<intent-filter . . . >
<data android:scheme="something" />
<data android:host="project.example.com" />
. . .
</intent-filter>
List<ResolveInfo> list =
mgr.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
You will see code samples with Intents in subsequent chapters, and
in Chapter 8 you will learn how to use an Intent to start a Service or a
BroadcastReceiver.
Summary
This chapter contains a great deal of information that you will probably
read more than once as you progress through the chapters in this book.
You first learned about the major versions of Android. Next you learned
how to create a “Hello World” Android application and how to deploy the
application to an Android device from Android Studio. You also learned
30 • ANDROID POCKET PRIMER
2
DESIGN AND UI CONTROLS
T
his chapter contains Android mobile applications with some com-
mon UI controls that are available in Android. For simplicity, the
code samples typically contain a single UI component, and after
reading this chapter you can easily combine multiple UI components in
Android applications.
The first half of this chapter contains a summary of some UI controls,
attributes, and layouts, whereas the actual code samples are in the second
half of the chapter. However, it’s not necessary to read everything in the
first half before delving into the code samples. So, feel free to alternate
between sections in both halves of this chapter.
The first part of this chapter provides an overview of resolution and den-
sity independence, UI controls and design, as well as a brief description
of layouts, events, and adapters.
The second part discusses different types of constraint-based layouts,
padding-related attributes and positional attributes, orientation, weight,
and gravity. This section shows you how to maintain values of variables
during device rotation (and explains why it’s necessary to do so).
The third part of this chapter contains an example of an event listener that
can respond to specific events, such as button clicks. This section also explains
why computed values are not retained when users rotate their Android
devices, along with an example of the type of code that solves this problem.
The final part of this chapter contains information about memory leaks that
can occur with event listeners, annotations, and various types of permissions.
32 • ANDROID POCKET PRIMER
There are at least two important topics that you need to be aware of as
you become more proficient in Android development: UI Guidelines,
and Resolution and Density Independence. At some point you need to
acquire a basic understanding of these topics (before you submit Android
applications to the Android Play Store). Online classes are available as
links on this website:
http://developer.android.com/training/best-ux.html.
One final point pertains to terminology: “UI component” and “UI control”
have the same meaning in this book; in addition, “Android Application” is
used interchangeably with “Android Mobile Application.”
With the preceding points in mind, the next section gives you an overview
of how to manage views, adapters, and events in Android applications.
The purpose of an adapter is twofold: to retrieve data from the data set and
to generate View objects based on that data. After the data is retrieved,
the adapter view (that is bound to the adapter) is populated with those
generated View objects. Consider using the Android adapter classes, such
as ArrayAdapter and SimpleCursorAdapter, and also look at open-
source alternatives prior to writing custom adapter classes.
A <button> Component
Android supports a Button component (discussed later in this chap-
ter) for displaying text and an ImageButton component for displaying
a binary image via its src property. After you have read the material and
code in this section, you can replace the <Button> component with an
ImageButton component (keeping in mind the src property) to create
a similar code sample.
You can include multiple XML-based layout files with UI components
for an Android application by creating those XML documents in Android
Studio and then referencing those XML documents in your Java code.
You can also create the XML documents outside of Android Studio and
then explicitly import them into the Android applications that you create
in Android Studio.
For example, if you create an XML configuration file called fun_stuff.
xml in the same directory as activity_main.xml, you can “point” to
this configuration file with the following code snippet in the onCreate()
method:
setContentView(R.layout.fun_stuff);
All the Android code samples in this book use activity_main.xml for
the definition of the UI elements. However, Android applications can
contain multiple XML configuration files, and you can find many exam-
ples that do so in the Android SDK.
Keep in mind the following points regarding the various units of measure.
First, one pt is 1/72 of an inch. The dp unit of measure refers to densi-
ty-independent pixels, which is sometimes abbreviated as dip. This unit
of measurement is based on a “baseline” of 160dpi. For example, 1dp on
a 320dpi device is actually two pixels.
The sp unit of measure is an abbreviation for scale-independent pixels.
Use the sp unit of measure for font sizes. The px unit of measure is the
actual screen pixels.
A good point about resolution and density independence is here:
There are many devices where resolution is not an aspect ratio
preserving scaling of a G1 screen. The Motorola Droid is 480x854
and many other HDPI screens are 480x800. This is at the core
of why AbsoluteLayout is deprecated—density and resolution
are not necessarily linked. Do not use absolute positioning. Use
smarter layouts that can handle these cases instead.
The preceding quote is from this link:
http://stackoverflow.com/questions/5058310/android-density-
independent-pixels-and-the-samsung-galaxy-tab.
Padding-Related Attributes
Android provides many padding-related attributes. For example, the fol-
lowing attributes set the padding (in pixels) of the left, top, right, and
bottom of a View component:
android:paddingLeft
android:paddingTop
DESIGN AND UI CONTROLS • 37
android:paddingRight
android:paddingBottom
android:paddingLeft="20dp"
You can also specify the padding value as an XML element that is defined in
the XML document dimens.xml that is located in the res/values sub-
directory. For example, suppose that dimens.xml contains this snippet:
<resources>
<dimen name="my_left_padding">20dp</dimen>
</resources>
Then the following snippet will also set the left padding to 20dp:
android:paddingLeft="@dimen/my_left_padding"
If you want to specify the same padding for all four attributes, you can do
so with the following type of code snippet:
android:padding="20px"
LISTING 2.1 activity_main.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="false"
android:layout_alignParentLeft="false" >
<Button
android:id="@+id/Button01"
android:layout_width="match_parent"
android:layout_height="wrap_content"
38 • ANDROID POCKET PRIMER
android:layout_weight="1"
android:text="Button1" />
<Button
android:id="@+id/Button02"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Button2" />
<Button
android:id="@+id/Button03"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Button3" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Text1" />
</LinearLayout>
</RelativeLayout>
As you can clearly see, Listing 2.1 contains a root-level RelativeLayout
component with a nested LinearLayout component, which in turn con-
tains three Button components and one TextView component. The UI
components in Listing 2.1 use some of the attributes that are discussed
earlier in this chapter. You can experiment with these UI components by
specifying other attributes that you have seen in this chapter.
As a convenience, Android also provides two padding-related “setters”
methods setPadding(int, int, int, int) and setPaddingRel-
ative(int, int, int, int).
Moreover, Android providers various
“getters” for querying padding-related
values, such as getPaddingLeft(),
getPaddingTop(), getPaddin-
gRight(), getPaddingBottom(),
getPaddingStart(), and getPad-
dingEnd(). These methods are useful
when you need to programmatically
modify the values of attributes.
Figure 2.1 is a screenshot from after
launching the Android Layout-
Attributes project on a Samsung FIGURE 2.1 Three Buttons and One
Galaxy S5 with Android 6.0.1. TextView in an Android Application.
DESIGN AND UI CONTROLS • 39
android:layout_above
android:layout_below
android:layout_toLeftOf
android:layout_toRightOf
<TextView android:id="@+id/text1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hellot"
android:layout_above="@id/clickme"
/>
If you want to position a View component below, to the left of, or to the
right of another View component, you can use the corresponding attrib-
ute that is displayed in the beginning of this section.
While it’s helpful to read about how to render UI components, a good way
to learn how to change the layout of UI components is to experiment with
the various attributes that are available. Here are some examples that you
can try in Android applications. Note that some samples use layout man-
agers (such as FrameLayout) that have not been discussed yet, so you
need to defer those samples until later if you do not already know how to
use those layout managers.
Sample #1: Create a new layout file with a top-level RelativeLayout
component and position Button #1 so that it is displayed below Button
#2 (and both are on the top-right portion of the screen), as shown here:
<Button
android:id="@+id/button1"
40 • ANDROID POCKET PRIMER
android:layout_below="@id/button2"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1" />
<Button
android:id="@+id/button2"
android:layout_above="@id/button1"
android:layout_toLeftOf="@id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button2" />
Sample #2: You can shift the preceding two buttons to the bottom of the
screen by adding this property to both Button elements:
android:layout_alignParentBottom="true"
Sample #3: Create a new layout file with a top-level FrameLayout com-
ponent, center the second TextView component and place it on top of
the first TextView component (which is also centered on the screen):
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:text="First"
android:background="#00f" />
<TextView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:text="Second"
android:background="#f00" />
android:layout_gravity="right"
android:gravity="center_horizontal|bottom"
Sample #5: Create a new layout file with a top-level LinearLayout com-
ponent, and display three TextView elements vertically:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
DESIGN AND UI CONTROLS • 41
tools:context=".LinearLayout" >
<!-- specify three TextView elements here -->
</LinearLayout>
Sample #6: Change the line in bold in the preceding code block to this line:
android:orientation="horizontal"
Sample #7: create a new layout file with a top-level LinearLayout com-
ponent, and display a pair of TextView elements horizontally, and then
display another pair of TextView elements vertically:
<LinearLayout xmlns:android="http://schemas.android.com/
apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".LinearLayout" >
The sample layouts in this section give you enough information to create
a variety of layouts. You can also experiment with the effects of moving UI
components around the Android Studio Designer, and then inspect the
values of the attributes in the XML layout file.
<manifest ...>
<supports-screens
android:largeScreens="true"
android:requiresSmallestWidthDp="600" />
...
</manifest>
Also keep in mind that Android 3.2 introduced the following attributes:
android:requiresSmallestWidthDp
android:compatibleWidthLimitDp
android:largestWidthLimitDp
If you’re developing your application for Android 3.2 and higher, you
should use these attributes to declare your screen size support, instead of
the attributes based on generalized screen sizes.
The general format (note that android:resizeable is deprecated) is
shown here:
<supports-screens
android:resizeable=["true"| "false"]
android:smallScreens=["true" | "false"]
android:normalScreens=["true" | "false"]
android:largeScreens=["true" | "false"]
android:xlargeScreens=["true" | "false"]
android:anyDensity=["true" | "false"]
android:requiresSmallestWidthDp="integer"
android:compatibleWidthLimitDp="integer"
android:largestWidthLimitDp="integer"
/>
import android.util.Log;
Deploy the Android application to a mobile device and observe that the
previous string is displayed in LogCat each time that you rotate your
mobile device. Moreover, computed values (such as the click count for
a button) that are displayed on the screen are reset to their initial values
when users rotate their device. Later in this chapter you will learn how to
retain computed values and to display their correct values.
The next section shows you how to create an Android application that
consists of an Android Button component, followed by a code sample
that shows you how to handle a Button click event.
LISTING 2.2 SimpleButtonActivity.java
package com.example.oswaldcampesato2.simplebutton;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
{
private Button myButton = null;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_main);
this.myButton = (Button)this.findViewById(R.id.button1);
}
}
LISTING 2.3 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android=http://schemas.android.com/apk/res/android
android:id="@+id/layout1"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:id="@+id/button1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hellob"
android:layout_alignParentBottom="true" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hellot"
/>
</RelativeLayout>
Listing 2.3 is straightforward: the root element is a RelativeLayout
that contains a Button control and a TextView control as its child ele-
ments. The text that is displayed in the Button element is specified as
@string/button1 and the text for the TextView element is specified
as @string/hello. Whenever you see this type of expression, check the
XML document strings.xml for the actual strings that are substituted
for this expression.
As a side comment, if you display a grid-like pattern with two or more
columns of <button> elements that have text strings of different lengths,
sometimes the columns will have different widths. However, you can
insert the string in the middle of the longest text string so that
46 • ANDROID POCKET PRIMER
LISTING 2.4 strings.xml
<resources>
<string name="app_name">SimpleButton</string>
<string name="hellot">Hello Textfield</string>
<string name="hellob">Hello Button</string>
</resources>
The key point to observe in Listing 2.4 is the first XML <string> ele-
ment contains the actual text that is displayed in the TextView element.
In addition, the second XML <string> element contains the text that is
displayed as the Button label.
Figure 2.2 displays the rendering of the SimpleButton project on a
Samsung Galaxy S5 with Android 6.0.1.
onClick()
From View.OnClickListener. This is called when the user either
touches the item (when in touch mode), or focuses upon the item
with the navigation-keys or trackball and presses the suitable “enter”
key or presses down on the trackball.
onLongClick()
From View.OnLongClickListener. This is called when the user either
touches and holds the item (when in touch mode), or focuses upon
the item with the navigation-keys or trackball and presses and holds
the suitable “enter” key or presses and holds down on the trackball
(for one second).
LISTING 2.5 MainActivity.java
package com.example.oswaldcampesato2.clickbutton;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.RelativeLayout;
{
private RelativeLayout relLayout = null;
private Button myButton = null;
private int clickCount = 0;
private String TAG = "ClickMe";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_main);
this.myButton = (Button)this.findViewById(R.id.button1);
this.relLayout = (RelativeLayout)this.findViewById(R.id.layout1);
this.myButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
++clickCount;
myButton.setText("Click Me: "+clickCount);
The code in boldface in Listing 2.5 is the new Java code that has been
added to MainActivity.java in Listing 2.2 in order to add an event
listener that responds to button click events.
The first section of new code contains view-related import statements,
and the second section of new code contains event-handling statements,
as shown here:
this.myButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
++clickCount;
myButton.setText("Click Me: "+clickCount);
The solution involves two new blocks of code. The first code block involves
storing the value of clickCount in the savedInstanceState variable,
as shown here:
@Override
protected void onSaveInstanceState(Bundle outState)
{
outState.putInt("clickCount", clickCount);
super.onSaveInstanceState(outState);
}
if(savedInstanceState != null)
{
clickCount = savedInstanceState.getInt("clickCount");
}
Uncomment the two new code blocks in Listing 2.2 and launch the appli-
cation. When you click the button and then rotate your Android device,
you will see that the correct value for clickCount is displayed.
DESIGN AND UI CONTROLS • 51
this.myButton = (Button)this.findViewById(R.id.button1);
Method #2: Include the following code block in the <Button> definition:
<Button android:id="@+id/button1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="@string/clickme"
android:onClick="handleButtonClick" />
Although one technique involves “pure” Java code and another technique
involves specifying an event listener via an XML attribute, the result is
the same.
Now that you know how to define Button and TextView components
and event listeners in Android, you might also be interested in reading
the code in the Java class SimpleEditText.java on the companion disc
that illustrates how to enable users to enter text in an editable input field.
Android Annotations
Every Android project with an Activity class in this book uses the
@Override annotation that precedes the onCreate() method in the
main Activity class. This annotation indicates that your code is overrid-
ing a method with the same name in the parent class. Hence, if you mis-
spell the name of the method that you intend to override, a compilation
error will alert you about the misspelled method name.
Other annotations are available that can reduce the amount of code in
your Android applications, as described here:
http://tools.android.com/tech-docs/support-annotations.
In addition, there are various open source projects that provide additional
annotations for Android applications. For instance, the excilys project
provides Android annotations, and its home page is here:
https://github.com/excilys/androidannotations/tree/master.
As a simple example, Listing 2.5 contains this code block for handling a
click event on an Android Button control:
DESIGN AND UI CONTROLS • 55
this.myButton = (Button)this.findViewById(R.id.button1);
this.myButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
myButton.setText("Click Me: "+clickCount);
}
});
The excilys project enables you to replace the preceding code block
with the following code that contains an @Click annotation:
@Click(R.id.button1)
void button1Clicked() {
myButton.setText("Click Me: "+clickCount);
}
As you can see, the preceding code block with the annotation is much
simpler to understand, shorter in length, and less error-prone than the
original code block.
In addition to reducing the lines of code, Android annotations can make
your code more readable and also simplify maintenance in larger Android
applications.
Another open source project that provides Android annotations is here:
http://jakewharton.github.io/butterknife.
Perform an Internet search to find various other open source projects for
Android annotations and evaluate them to determine which one best suits
your needs.
Two articles that contain code samples that illustrate memory leaks and
how to address them are here (make sure you read the comments in these
posts as well):
https://medium.com/freenet-engineering/memory-leaks-in-android-
identify-treat-and-avoid-d0b1233acc8#.y2duxl6dw
http://blog.nimbledroid.com/2016/05/23/memory-leaks.html
You won’t encounter some of these topics (such as the sensor manager)
until the second half of this book, or in an appendix (such as Threads), so
you don’t need to worry about dealing with memory leaks right now.
Fortunately, Android provides various tools to help you detect memory
leaks in Android applications, some of which are described in one of the
appendices for this book. In addition, Square provides a memory leak
detection library for Android as well as Java, and it’s downloadable here:
https://github.com/square/leakcanary.
Summary
This chapter showed you how to develop native Android applications with
simple UI controls, including buttons and text fields. You also learned how
to create event listeners so that your Android application can respond to
specific events, such as button clicks.
In addition, you learned about different types of constraint-based layouts,
padding-related attributes and positional attributes, orientation, weight,
and gravity. You saw how to maintain values of variables during device
rotation. Finally, you learned about the lifecycle methods for an Android
Activity and different types of memory leaks.
CHAPTER
3
ADDITIONAL UI CONTROLS
T
his chapter contains code samples with an assortment of Android
UI components. Please read Chapter 2 (if you have not already
done so) because it contains useful information regarding UI com-
ponents, event handlers, and so forth. Unless otherwise noted, the UI
components in this chapter are supported from Android version 2.0 and
higher.
The first part of this chapter briefly discusses UI components that provide
container-like functionality, followed by Android Alerts and TimePickers.
The second part of this chapter discusses Android Fragments (which are
essentially submodules of an Android Activity) that were introduced in
Android 3.x.
The third part of this chapter discusses some Android layout managers,
including LinearLayout and RelativeLayout (you saw the latter in
Chapter 2). This section also discusses briefly the Android RecyclerView,
which is a powerful and more efficient “successor” to the LinearLayout
manager and GridLayout manager. Note that Android provides other
layout managers (such as AbsoluteLayout, FrameLayout, and
TableLayout) that are not discussed in this chapter.
Alerts
Android Alerts enable your Android application to display a message
when a particular event has occurred.
Copy the directory MyAlert from the companion disc to a convenient
location. Listing 3.1 displays the contents of MainActivity.java, which
illustrates how to display an Alert in an Android application.
LISTING 3.1 MainActivity.java
package com.example.oswaldcampesato2.myalert;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
builder
.setMessage("Are you sure you want to exit?")
.setCancelable(false)
.setPositiveButton("Yes",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,int id){
MainActivity.this.finish();
}
})
.setNegativeButton("No",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,int id){
ADDITIONAL UI CONTROLS • 61
dialog.cancel();
}
});
Listing 3.1 starts with the usual boilerplate Java code, and the onCre-
ate() method instantiates an AlertDialog.Builder object called
builder that uses method chaining in order to set three properties.
These properties are the cancel, yes, and no options that users can
select in an Alert dialog.
If you are unfamiliar with the Builder
pattern, read item #2 of this link:
https://github.com/mgp/book-
notes/blob/master/effective-
java-2nd-edition.markdown.
After creating (“building”) an instance
of AlertDialog.Builder, you
need to obtain an instance of the
AlertDialog class and then invoke
the show() method to launch the
alert, as shown here:
AlertDialog alert = builder.
create();
Time Pickers
The Android package android.widget contains the class TimePicker
that provides date-related functionality. In addition, Android supports a
date picker widget that enables users to select a date based on the year,
62 • ANDROID POCKET PRIMER
month, and day. The example in this section shows you how to use the
TimePicker component.
LISTING 3.2 MainActivity.java
package com.example.oswaldcampesato2.mytimepicker;
import android.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.TimePicker;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
timePicker.setOnTimeChangedListener(
new TimePicker.OnTimeChangedListener(){
public void onTimeChanged(
TimePicker view,int
hourOfDay,int minute){
updateTimeDisplay(hourOfDay, minute);
}
});
}
ADDITIONAL UI CONTROLS • 63
mTimeDisplay.setText(padHour+":"+padMin);
}
TimePicker timePicker =
(TimePicker) findViewById(R.id.timePicker);
timePicker.setHour(12);
timePicker.setMinute(15);
The next portion of Listing 3.2 displays a string with the hour and minute
values in a TextView component after prepending a 0 (if necessary) to
this string. The last block of code in onCreate() adds an event listener to
the TimePicker component: whenever the time is changed, the method
updateTimeValue() is invoked with the new hour and minute values.
mTimeDisplay.setText(padHour+":"+padMin);
The code sample in this section is a “bare bones” example of using the
TimePicker component. Regarding the DatePicker component: addi-
tional options are available, such as setting the mode to “spinner,” as well
as a CalenderView option. Consult the Android documentation for
details about the DatePicker component.
64 • ANDROID POCKET PRIMER
Fragment-Related Classes
The Android package android.app contains the Fragment class. A
Fragment has its own lifecycle (discussed later), with its own input events
that you can add or remove while the “parent” Activity is active.
The Android android.app package contains several Fragment subclasses,
including DialogFragment, ListFragment, PreferenceFragment,
and WebViewFragment.
The Android package android.sup-
port.v4.app provides a Fragment
NOTE
class for Android mobile applications
prior to Android 3.0 (API Level 11).
The package android.sup-
port.v4.app contains not only
the Fragment base class, but also
the Fragment-related classes
ListFragment, FragmentActivity,
and FragmentTransaction.
onAttach(Activity)
onCreate(Bundle)
onCreateView(LayoutInflater,
ViewGroup, Bundle)
onActivityCreated(Bundle)
onViewStateRestored(Bundle)
onStart()
onResume()
you can add or remove while the “main” activity is running. More infor-
mation about Fragments is here:
http://developer.android.com/guide/components/fragments.html.
Figure 3.2 displays the Fragment lifecycle, which is also displayed in the
preceding link.
Google recommends the use of Fragments, which can be used as “con-
tainers” for Activitys. Fragments make it easier to reuse components
in different layouts.
Since Android 4 (ICS) supports both smart phones and tablets, you can
deploy the same Android application in both devices. However, the visual
display will probably be different on these two devices, and Fragments
provide a nice solution. Although Fragments are not mandatory, they do
make it easier to support multiple screen sizes.
If you intend to work extensively with Fragments, then you also need
NOTE to learn about the Fragment lifecycle, which is available in the online
Android documentation.
./java/com/example/oswaldcampesato2/myfragment/
Fragment1.java
./java/com/example/oswaldcampesato2/myfragment/
Fragment2.java
./java/com/example/oswaldcampesato2/myfragment/
MyFragment.java
./res/layout/activity_my_fragment.xml
./res/layout/fragment1.xml
./res/layout/fragment2.xml
LISTING 3.3 activity_my_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_my_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.oswaldcampesato2.
myfragment.MyFragment">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/
res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<fragment
android:id="@+id/fragment2"
android:name="com.example.oswaldcampesato2.
myfragment.Fragment2"
android:layout_width="0px"
ADDITIONAL UI CONTROLS • 69
android:layout_height="match_parent"
android:layout_weight="1"
/>
<fragment
android:id="@+id/fragment1"
android:name="com.example.oswaldcampesato2.
myfragment.Fragment1"
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1"
/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
LISTING 3.4 MyFragment.java
package com.example.oswaldcampesato2.myfragment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
}
else
{
// Portrait mode of the device
Fragment fragment2 = new Fragment2();
fragmentTransaction.replace(android.R.id.
content, fragment2);
}
fragmentTransaction.commit();
}
}
LISTING 3.5 Fragment1.java
package com.example.oswaldcampesato2.myfragment;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
LISTING 3.6 fragment1.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.
com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is Fragment One View1"
android:textAppearance="?android:attr/textAppearanceLarge" />
<CalendarView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/calendarView" />
</LinearLayout>
LISTING 3.7 fragment2.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.
com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is Fragment Two View1"
android:textAppearance="?android:attr/
textAppearanceLarge" />
<TimePicker
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/timePicker" />
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is Fragment Two View2"
android:textAppearance="?android:attr/
textAppearanceLarge" />
</LinearLayout>
LISTING 3.8 MainActivity.java
package com.example.oswaldcampesato2.mylist1;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
74 • ANDROID POCKET PRIMER
import android.widget.ListView;
import android.widget.Toast;
@Override
protected void onListItemClick(ListView l, View v,
int position, long id) {
super.onListItemClick(l, v, position, id);
Listing 3.8 declares the variable names that references an array of strings,
which is used to instantiate an Android Adapter class, as shown here:
this.setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_checked, names));
LISTING 3.9 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.oswaldcampesato2.
simplelist1.MainActivity">
<TextView
android:text="TextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="33dp"
android:id="@+id/textView" />
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true"
android:layout_marginTop="147dp"
android:id="@android:id/list" />
</RelativeLayout>
LISTING 3.10 simple_list_item_
checked.xml
<?xml version="1.0"
encoding="utf-8"?>
<TextView
xmlns:android="http://
schemas.android.com/apk/res/
android"
android:id="@+id/
rowTextView"
android:layout_width="fill_
parent"
android:layout_height="wrap_
content"
android:padding="10dp"
android:textSize="16sp" >
</TextView>
Some properties, such as Text, can be set directly on a View object. There
are other properties that you must set on an appropriate LayoutParam
object, after which the latter object is set at the View object’s LayoutParam
property.
One point to keep in mind: each Layout type has an associated class whose
name is the concatenation of the Layout type and the LayoutParam
class. As an example, LinearLayout has an associated class called
LinearLayout.LayoutParam (and similarly for the other Layout man-
agers). The LinearLayout.LayoutParam class is sort of like a “defini-
tion class” that defines the layout properties that are available to any View
object that is added to a LinearLayout object.
Instantiate the LinearLayout.LayoutParam class as follows:
ADDITIONAL UI CONTROLS • 77
LinearLayout.LayoutParam linLayoutParams =
new LinearLayout.LayoutParam(200,200);
Set the preceding class in View objects that you add to the LinearLayout
as follows:
btn.setLayoutParams(linLayoutParams);
linLayout.addView(b3);
After executing the preceding code block, the Button btn will be
200x200 pixels. However, keep in mind that the pixels are not device-in-
dependent pixels. In addition, more complex Layout objects have more
complex LayoutParams that must be initialized.
The LayoutParams class can also provide a constructor to set some prop-
erties (such as the weight), whereas other properties are set by invoking
the setProperty() methods after the constructor has been invoked,
where you need to replace Property with the property in question. The
details depend on the specific Layout manager.
For example, you can replace Property with Margins, an example of
which is here:
linLayout.setMargins(10,10,10,10);
The RecyclerView
Android 5.x introduced the RecyclerView, which is an improve-
ment over the ListView component. The RecyclerView “recycles”
View elements more efficiently in order to improve performance. The
RecyclerView simplifies the display and handling of large data sets by
providing layout managers (such as the CardView LayoutManager) for
positioning items.
A list of some supported built-in layout managers is here:
LinearLayoutManager (vertical or horizontal scrolling list)
GridLayoutManager (items in a grid)
StaggeredGridLayoutManager (items in a staggered grid)
78 • ANDROID POCKET PRIMER
Setting Up a RecyclerView
In order to use a RecyclerView, you must do two things: specify an
adapter and a layout manager. You create an adapter by extending the
RecyclerView.Adapter class: the actual details of the implementation
depend on the specifics of your dataset and the type of views in your
application.
The reason for specifying a layout manager is straightforward: the layout
manager positions item views inside a RecyclerView and determines
when to reuse item views that are no longer visible to the user. Moreover,
when a view is reused (or recycled), the layout manager may instruct the
adapter to replace the contents of such a view with a different element
from the dataset. This recycling technique improves performance by
avoiding the creation of unnecessary views or performing expensive find-
ViewById() lookups.
LISTING 3.11 MainActivity.java
package com.android_examples.recyclerview_android_examplescom;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Window;
import android.widget.RelativeLayout;
Context context;
RecyclerView recyclerView;
RelativeLayout relativeLayout;
RecyclerView.Adapter recyclerViewAdapter;
RecyclerView.LayoutManager recylerViewLayoutManager;
String[] wineList = {
"Merlot", "Barolo", "Cremona", "Tokai",
"Retsina", "Pinot Noir", "Cabernet",
"Merlot", "Barolo", "Cremona", "Tokai",
"Retsina", "Pinot Noir", "Cabernet",
"Merlot", "Barolo", "Cremona", "Tokai",
"Retsina", "Pinot Noir", "Cabernet",
"Merlot", "Barolo", "Cremona", "Tokai",
"Retsina", "Pinot Noir", "Cabernet",
"Merlot", "Barolo", "Cremona", "Tokai",
"Retsina", "Pinot Noir", "Cabernet"
};
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_ACTION_BAR);
setContentView(R.layout.activity_main);
context = getApplicationContext();
// find the RelativeLayout component
relativeLayout =
(RelativeLayout) findViewById(R.id.relativeLayout);
recylerViewLayoutManager =
new LinearLayoutManager(context);
recyclerView.setAdapter(recyclerViewAdapter);
}
}
LISTING 3.12 RecyclerViewAdapter.java
package com.android_examples.recyclerview_android_examplescom;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@Override
public RecyclerViewAdapter.ViewHolder
onCreateViewHolder(ViewGroup parent, int viewType)
ADDITIONAL UI CONTROLS • 81
{
view = LayoutInflater.from(context)
.inflate(R.layout.
recyclerview_items,parent,false);
return viewHolder;
}
@Override
public void onBindViewHolder(ViewHolder holder,
int position){
holder.textView.setText(wineList[position]);
}
@Override
public int getItemCount(){
return wineList.length;
}
}
LISTING 3.13 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
82 • ANDROID POCKET PRIMER
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.android_examples.recyclerview_
android_examplescom.MainActivity"
android:id="@+id/relativeLayout">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:scrollbars="vertical"
android:layout_width="fill_parent"
android:layout_
height="fill_parent"
/>
</RelativeLayout>
Constraint-Based Layouts
This section discusses the constraint-based layout for Android applica-
tions that was introduced in Android Studio 2.2.
The ConstraintLayout is the Android layout type (introduced in
Android Studio 2.2) that provides several new features, along with
a sophisticated built-in Layout Editor. The ConstraintLayout
decreases the depth and complexity of the view hierarchy in Android
applications. ConstraintLayout helps you optimize the UI render-
ing phase of Android applications. In particular, ConstraintLayout
is well-suited for use with the RecyclerView. ConstraintLayout
ADDITIONAL UI CONTROLS • 83
What Is ConstraintLayout?
ConstraintLayout is a more recent type of layout for Android
Applications that is backward compatible down to API level 9 and is also
part of the support library. The purpose of the ConstraintLayout is to
reduce the depth of layout hierarchies, to improve performance of layouts,
and to reduce the complexity of trying to work with RelativeLayouts.
Fortunately, this new layout is compatible with other layouts, which
means that you are not forced to choose one particular layout.
compile 'com.android.support.
constraint:constraint-layout:1.0.0-beta4'
Summary
This chapter showed you how to create Android applications that create
various UI controls, such as Android Alerts and TimePickers. Then you
learned about Android Fragments (which are essentially submodules of
an Android Activity) that were introduced in Android 3.x.
You also got an overview of some Android layout managers, including
LinearLayout and RelativeLayout. In addition, you saw an example
of the Android RecyclerView, which is a powerful and more efficient
“successor” to the LinearLayout manager and GridLayout manager.
Next you learned about ConstraintLayout, which is more power-
ful and more efficient than other list-based layouts, and it’s intended to
replace the RelativeLayout manager. You got a brief introduction to
some Android 6 features, followed by a list of some features of Android 7.
CHAPTER
4
GRAPHICS AND ANIMATION
T
his chapter contains Android code samples for creating various
graphics and animation effects. You will learn how to render
2D shapes (code samples are on the companion disc), create
gradient effects and filter effects, how to render binary images, and how
to create 2D animation effects in Android applications. Additional open
source toolkits, such as Glide and Picasso, are discussed in Appendix A.
The first part of this chapter shows you how to render text strings with
gradient colors and how to render nested gradient rectangles. The com-
panion disc contains Android code samples that render other 2D shapes,
such as line segments, rectangles, circles, ellipses, and Bezier curves.
The second part of this chapter shows you how to apply filter effects to
PNG files, such as a Gaussian blur in a code sample. The final part of
this chapter discusses animation effects in Android, and also shows you
several techniques for creating animation effects.
This chapter contains code samples with graphics or animation effects
that are invoked via button clicks. However, you can enhance the code
samples in this chapter so that they incorporate various user gestures that
are discussed in Chapter 5. For example, after you learn how to render a
PNG image, you could add DnD (drag and drop) support so that users can
drag PNG images across the screen of an Android mobile device.
the value for each component is an integer between 0 and 255. These
numbers can be expressed as decimal or as hexadecimal numbers.
In Android the opacity is between 0 and 255, but in other systems the
NOTE opacity can be a decimal number between 0 and 1.
Another point to keep in mind is that the hexadecimal values for colors
in many programming languages are of the following form: 0xRRGGBBAA.
However, the hexadecimal values for a color and opacity in Android are of
the following form: 0xAARRGGBB.
You can also set colors in an XML document. For example, the following
code snippet sets the background to Red:
android:background = "#FFFF000000"
LISTING 4.1 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.
com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:textColor="#000"
android:textSize="32dp" />
</LinearLayout>
android:layout_height="80dp"
android:layout_margin="20dp"
android:background="@drawable/gradient2"
android:orientation="vertical"
android:padding="8dp" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:textColor="#FFF"
android:textSize="20dp" />
</LinearLayout>
</LinearLayout>
Listing 4.2 displays the contents of gradient1.xml and Listing 4.3 dis-
plays the contents of gradient2.xml.
LISTING 4.2 gradient1.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/
apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<stroke
android:width="8dp"
android:color="#FFFFFF" />
<gradient
android:angle="270"
android:centerColor="#FFFFFF"
android:endColor="#FF0000"
android:startColor="#0000FF" />
</shape>
92 • ANDROID POCKET PRIMER
Listing 4.2 contains a top-level XML <shape> element with several XML
child elements that are used to define the properties of a radial gradient.
The XML <gradient> child element contains attributes whose values
specify the start color, center color, and the end color for the radial gradient.
LISTING 4.3 gradient2.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/
res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<stroke
android:width="2dp"
android:color="#FFFFFF" />
<gradient
android:angle="90"
android:centerColor="#FF0000"
android:endColor="#0000ff"
android:startColor="#00FF00" />
</shape>
Listing 4.3 also defines a radial gradient, and since it is very similar to
Listing 4.2 and does not introduce any new concepts, we can skip the
details.
LISTING 4.4 MyCustomSubviewActivity.java
public class MyCustomSubviewActivity
{
@Override
GRAPHICS AND ANIMATION • 93
doSomethingInteresting();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// render your graphics code here
}
}
}
Listing 4.4 contains a code snippet shown in bold that replaces the default
setContentView() method invocation, as shown here:
//setContentView(R.layout.activity_main);
setContentView(new SimpleView(this));
The preceding code snippet instantiates the private Java class called
SimpleView (whose contents are written by you) that is defined in the
same file. The details of the private Java class are omitted from Listing 4.4,
but you will see an example shortly.
As you can see from the preceding code block, an instance of the
Canvas class is automatically supplied as an argument of the onDraw()
method.
Incidentally, you can create a new Canvas object by first defining a
bitmap for drawing shapes using the Bitmap class, and then instantiating
a Canvas instance with the bitmap instance, as shown here:
Bitmap b = Bitmap.createBitmap(0, 0, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
Graphics effects in Android Canvas typically use the Paint class (which
also belongs to the android.graphics package) to create color-related
effects. For example, the following code block renders a white Canvas:
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
The following code block (which you would place inside the onDraw()
method) renders a circle and a rectangle:
paint.setColor(Color.BLUE);
canvas.drawCircle(100, 50, 20, paint);
paint.setColor(Color.GREEN);
canvas.drawRect(50, 50, 200, 100, paint);
The next section contains a complete code sample that creates a gradient
effect by rendering a set of nested rectangles.
void setScale(float sx, float sy, float px, float py): sets the matrix to
scale by sx and sy, with a pivot point at (px, py)
void setSkew(float kx, float ky, float px, float py): sets the matrix to
skew by sx and sy, with a pivot point at (px, py)
void setTranslate(float dx, float dy): sets the matrix to translate by
(dx, dy)
There are many other methods available in the Matrix class, and you can
get additional information about this class here: http://developer.android.
com/reference/android/graphics/Matrix.html.
The next section shows you how to render a drawable PNG, which is to
say that you can display a PNG and then draw other graphics shapes on
top of the PNG.
LISTING 4.5 MyDrawablePNG.java
package com.example.oswaldcampesato2.mydrawablepng;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
GRAPHICS AND ANIMATION • 97
import android.view.View;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// a white canvas
paint.setColor(Color.WHITE);
canvas.drawPaint(paint);
Bitmap b = BitmapFactory.decodeResource(
mContext.getResources(),
R.mipmap.sample1);
canvas.drawColor(Color.TRANSPARENT);
canvas.drawBitmap(b, 0, 0, null);
canvas.save();
canvas.scale(0.5f, 0.5f);
canvas.drawBitmap(b, 600, 100, null);
canvas.restore();
canvas.save();
canvas.skew(0.2f, 0.3f);
canvas.drawBitmap(b, 500, 300, null);
canvas.restore();
canvas.save();
canvas.rotate(45);
98 • ANDROID POCKET PRIMER
initialized = false;
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if(initialized) { initialize(canvas); }
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
return true;
}
}
Listing 4.5 contains boilerplate code and a code snippet that instantiates
an instance of the Bitmap class, which references the PNG sample1.
png, as shown here:
Bitmap b = BitmapFactory.decodeResource(
mContext.getResources(),
R.mipmap.sample1);
Keep in mind that filenames of binary files must not contain uppercase
letters.
The actual directory names depend on the version of Android, and for
Android 4.2 they are as follows:
drawable-hdpi
drawable-ldpi
drawable-mdpi
drawable-xhdpi
drawable-xxhdpi
GRAPHICS AND ANIMATION • 99
LISTING 4.6 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
100 • ANDROID POCKET PRIMER
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.oswaldcampesato2.
simplerotateimage.MainActivity">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@android:drawable/alert_dark_frame"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="204dp"
android:id="@+id/imageView" />
<Button
android:text="ClickMe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/imageView"
android:layout_centerHorizontal="true"
android:layout_marginTop="79dp"
android:id="@+id/button" />
</RelativeLayout>
As you can see, Listing 4.6 is a simple layout file containing an ImageView
component and a Button component.
Listing 4.7 displays the contents of MainActivity.java, which illustrates
how to use the Bitmap class and the Matrix class to rotate a PNG file.
LISTING 4.7 MainActivity.java
package com.example.oswaldcampesato2.simplerotateimage;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Button;
import android.widget.ImageView;
import android.graphics.Matrix;
import android.view.Menu;
import android.view.View;
{
private Bitmap image;
private ImageView imageView;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.button.setOnClickListener(new View.
OnClickListener() {
private int current = 0;
@Override
public void onClick(View view) {
Bitmap toRemove = image;
Matrix matrix = new Matrix();
matrix.setRotate(30, 0.5f, 0.5f);
image = Bitmap.createBitmap(image, 0, 0,
image.getWidth(),
image.getHeight(),
matrix, true);
imageView.setImageBitmap(image);
if(toRemove != null) {
toRemove.recycle();
}
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
}
Listing 4.7 contains the usual boilerplate code, followed by the onCre-
ate() method that references an image file and a Button component in
the layout file activity_main.xml. When users click on this button, the
code rotates the PNG by invoking the createBitmap() method of the
Bitmap class. Notice how the matrix variable, which is an instance of the
102 • ANDROID POCKET PRIMER
image = Bitmap.createBitmap(image, 0, 0,
image.getWidth(),
image.getHeight(),
matrix, true);
source.getHeight());
Matrix matrix = new Matrix();
matrix.mapRect(rectF);
Transforms in Android
Transforms are available in many programming languages, and the seman-
tics are similar in Android graphics. A transform in graphics can involve a
change in the shape or location of a graphics image. Common transforms
include translate (move horizontally and/or vertically), scale (con-
tract or expand), rotate (spin), or skew (a twisting-like effect, and also
sometimes called shear).
Each of these transforms can be expressed in a matrix, for 2D as well
as 3D effects (skew is not defined for 3D). The Android Canvas class
provides the following intuitively named methods (and many other graph-
ics-related methods) for performing transforms:
void rotate(float degrees);
final void scale(float sx, float sy, float px,
float py);
void skew(float sx, float sy)
void translate(float dx, float dy)
The next two sections provide additional (albeit brief) details about
tweening animation and frame-by-frame animation.
Now that you know about the relevant animation-related classes, let’s look
at an example of creating a “fade in” effect for a PNG image, which is the
topic of the next section.
LISTING 4.8 MainActivity.java
package com.example.fadeanimation;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
{
ImageView image;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
image = (ImageView)findViewById(R.id.sample1);
Animation animationFadeIn =
AnimationUtils.loadAnimation(this, R.anim.fadein);
image.startAnimation(animationFadeIn);
}
@Override
protected void onPause() {
super.onPause();
image.clearAnimation();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items
// to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Animation animationFadeIn =
AnimationUtils.loadAnimation(this, R.anim.fadein);
image.startAnimation(animationFadeIn);
Listing 4.9 displays the contents of fadein.xml with the details of the
animation effect.
LISTING 4.9 fadein.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
108 • ANDROID POCKET PRIMER
android:shareInterpolator="false">
<alpha android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="5000">
</alpha>
</set>
Listing 4.9 contains an XML <alpha> element that specifies the val-
ues 0.0, 1.0, and 5000 for the attributes fromAlpha, toAlpha, and
duration, respectively. These values cause the opacity to vary line-
arly from 0 to 1 (hence the “fade in” effect) during a period of 5000
milliseconds.
Listing 4.10 displays the contents of activity_main.xml with an
ImageView component and a TextView component.
LISTING 4.10 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/
activity_horizontal_margin"
android:paddingRight="@dimen/
activity_horizontal_margin"
android:paddingTop="@dimen/
activity_vertical_margin"
tools:context="com.example.
oswaldcampesato2.
fadeanimation.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/textView" />
<ImageView
android:layout_width="match_parent"
FIGURE 4.3 A fade-in effect on
android:layout_height="wrap_content" a Pixel phone with Android 7.1.
GRAPHICS AND ANIMATION • 109
app:srcCompat="@android:drawable/alert_light_frame"
android:layout_below="@+id/textView"
android:layout_toEndOf="@+id/textView"
android:layout_marginStart="69dp"
android:layout_marginTop="133dp"
android:id="@+id/imageView" />
</RelativeLayout>
<scale android:interpolator="@android:anim/
accelerate_decelerate_interpolator"
android:fromXScale="1.0"
android:toXScale="1.4"
android:fromYScale="1.0"
android:toYScale="0.6"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="700" />
This concludes the graphics and animation techniques for this chapter.
Although you could also learn about RenderScript for graphics and ani-
mation, the graphics and animation techniques in this chapter are suita-
ble for the majority of Android applications that do not involve intensive
game-related functionality.
Nine-Patch
The Android android.graphics package contains the NinePatch
class that enables you to render a bitmap in nine sections (and hence
its name). Nine-patch (aka 9-patch) graphics are PNG files whose
names have an extended suffix of .9.png. These PNG files can
be edited in standard graphics tools, and because of their nam-
ing convention, Android applies nine-patch rules to their use:
http://developer.android.com/reference/android/graphics/Nine
Patch.html.
You can experiment with the draw9patch program located in the tools
subdirectory of your Android SDK installation.
Summary
This chapter showed you various techniques for creating graphics effects.
First you learned how to use XML-based configuration files to render gra-
dient effects. Next you learned how to use Java code to programmatically
create gradient effects. You also learned about NinePatch for creating
graphics effects. In addition, you saw how to apply filter effects to PNG
files.
In the animation-related portion of this chapter, you learned how to use
XML-based configuration files to create “fade in” effects as well as multi-
ple animation effects on PNG files.
CHAPTER
5
USER GESTURES
T
his chapter shows you how to detect and respond to touch-related
events and various user gestures in Android applications. The
code samples show you how to handle simple gestures, and links
are provided for handling some of the more complex types of gestures in
Android. As you will see, the second half of this chapter contains code
samples that combine user gestures with graphics, partly because they
provide some pleasing visual effects, and also to provide you with “base
code” to which you can add your own enhancements.
The first part of this chapter contains an Android code sample that shows
you how to detect and process touch events. This section also shows
you how to add touch-related event handlers to the Android application
BarChart2D that is available on the companion disc. The second part of
this chapter contains Android code samples that show you how to detect
other gestures, such as pinch and swipe events. The final part of this chap-
ter contains code samples that create simple animation effects.
When you have completed this chapter, you will know how to create
Android applications that combine graphics and animation effects (dis-
cussed in Chapter 4) with user gestures. The basic examples in this
chapter will serve as a starting point for you to create richer and more
interesting Android applications.
The next section shows you how to write custom code for the Android
method onTouchEvent(), which enables you to detect and process a
very common event in Android applications.
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("ACTION_DOWN (x,y) = " , xyLoc);
break;
case MotionEvent.ACTION_MOVE:
Log.i("ACTION_MOVE (x,y) = " , xyLoc);
break;
case MotionEvent.ACTION_UP:
Log.i("ACTION_MOVE (x,y) = " , xyLoc);
break;
}
return true;
}
The first part of Listing 5.1 shows you that the Event object provides the
x coordinate and y coordinate of the location where users have performed
a touch event. This code sample merely displays a message containing the
coordinates of each touch event in the console via the Log.i() method.
The main section of code in Listing 5.1 is the switch statement that com-
pares the value of event.getAction() with three constants that corre-
spond to touch down, touch move, and touch up events. In this example,
the coordinates of the location of the touch event are displayed in the
console. Later in this chapter you will see examples where touch-related
events trigger other actions, such as redrawing the graphics on the screen.
@Override
protected void onDraw(Canvas canvas) {
renderLineSegments(canvas);
renderHorizontalAxis(canvas);
renderVerticalAxis(canvas);
labelHorizontalAxis(canvas);
labelVerticalAxis(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
randomizeMultiLines();
invalidate();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
}
The key point to notice in Listing 5.2 is the method onTouchEvent() that
contains a switch() statement with several case statements. The first
USER GESTURES • 117
LISTING 5.3 MainActivity.java
package com.example.oswaldcampesato2.simpletouch;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
touchX = event.getX();
120 • ANDROID POCKET PRIMER
touchY = event.getY();
xyLoc = "("+touchX+","+touchY+")";
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i("ACTION_DOWN (x,y) = " , xyLoc);
break;
case MotionEvent.ACTION_MOVE:
Log.i("ACTION_MOVE (x,y) = " , xyLoc);
break;
case MotionEvent.ACTION_UP:
Log.i("ACTION_UP (x,y) = " , xyLoc);
break;
}
return true;
}
xyLoc = "("+touchX+","+touchY+")";
Log.i("DOUBLE_TAP (x,y) = " , xyLoc);
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent event)
{
touchX = event.getX();
touchY = event.getY();
xyLoc = "("+touchX+","+touchY+")";
Log.i("DOUBLE_TAP E (x,y) = " , xyLoc);
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent event)
{
touchX = event.getX();
touchY = event.getY();
xyLoc = "("+touchX+","+touchY+")";
Log.i("SINGLE_TAP C (x,y) = " , xyLoc);
USER GESTURES • 121
return false;
}
xyLoc = "("+touchX+","+touchY+")";
Log.i("ON_TOUCH (x,y) = " , xyLoc);
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY)
{
touchX = e1.getX();
touchY = e2.getY();
xyLoc = "("+touchX+","+touchY+")";
Log.i("ON_FLING (x,y) = " , xyLoc);
return false;
}
@Override
122 • ANDROID POCKET PRIMER
xyLoc = "("+touchX+","+touchY+")";
Log.i("LONG_PRESS (x,y) = " , xyLoc);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent
e2, float distanceX, float distanceY)
{
return false;
}
@Override
public void onShowPress(MotionEvent event)
{
touchX = event.getX();
touchY = event.getY();
xyLoc = "("+touchX+","+touchY+")";
Log.i("SHOW_PRESS (x,y) = " , xyLoc);
}
@Override
public boolean onSingleTapUp(MotionEvent event)
{
touchX = event.getX();
touchY = event.getY();
xyLoc = "("+touchX+","+touchY+")";
Log.i("SINGLE_TAP_UP (x,y) = " , xyLoc);
return false;
}
}
As you can see in the preceding list, the onClick() method takes a
View argument and the onKey() method takes a KeyEvent argu-
ment. All the other methods take a MotionEvent argument (and
sometimes other arguments as well). Moreover, all the touch-
related methods obtain the coordinates of the touch point with the follow-
ing two lines of code:
touchX = event.getX();
touchY = event.getY();
Notice that the name of each method in Listing 5.3 indicates the type of ges-
ture. For example, when the method onTouchEvent() is being executed,
you know that the gesture is a touch event, whereas when you are inside the
onDoubleTap() method, you know that the gesture is a double tap, and so
forth for the other intuitively named methods in the preceding list.
Listing 5.3 also contains the method onFling() that is executed when-
ever a fling event is detected (and it displays information about that event).
A Sketching Program
The Android SDK contains a sketching program called FingerPaint.
java that enables users to create freestyle sketching or “finger painting”
on the screen. This Java class is three pages in length and you can find it in
the samples subdirectory of the Android SDK that you installed on your
machine. Listing 5.4 displays the contents of the onCreate() method
that illustrates how to initialize some variables (such as mPaint) and also
specify values of various attributes.
mPaint.setColor(0xFFFF0000);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(12);
The onDraw() method is very short, and it renders users’ finger move-
ments that are tracked in a path element, as shown here:
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFFAAAAAA);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.drawPath(mPath, mPaint);
}
LISTING 5.5 MainActivity.java
package com.example.oswaldcampesato2.drawableanimation;
import android.graphics.drawable.AnimationDrawable;
126 • ANDROID POCKET PRIMER
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MotionEvent;
import android.widget.ImageView;
setContentView(R.layout.activity_main);
@Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
@Override
public void onStart() {
super.onStart();
}
@Override
public void onStop() {
super.onStop();
}
}
Listing 5.6). Listing 5.5 also defines an onTouchEvent() that starts the
animation effect whenever users touch the screen.
LISTING 5.6 spin_animation.xml
<!-- Animation frames: sample1.png -> sample3.png in
res/drawable -->
<animation-list
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/selected" android:oneshot="false">
<item android:drawable="@drawable/sample1"
android:duration="150" />
<item android:drawable="@drawable/sample2"
android:duration="150" />
<item android:drawable="@drawable/sample3"
android:duration="150" />
<item android:drawable="@drawable/sample2"
android:duration="150" />
<item android:drawable="@drawable/sample3"
android:duration="150" />
<item android:drawable="@drawable/sample1"
android:duration="150" />
</animation-list>
<ImageView
android:id="@+id/sample1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="centerCrop"
android:src="@drawable/sample1"
/>
You need to include the preceding XML <ImageView> element for each
of the three PNG files.
Summary
In this chapter you learned how to detect various touch-related events,
including single tap and multi-tap events. Next you learned how to add
touch-related functionality to a multi-line graph whose graphics code is
based on material from Chapter 4.
Then you learned about the package with Java classes for handling various
types of user gestures. In addition you learned about Sprites and why
they can be useful in Android applications.
CHAPTER
6
SENSORS AND MULTIMEDIA
T
his chapter discusses various sensors that are accessible on Android
mobile devices and also how to work with multimedia. Since sup-
port for sensors on Android devices does vary, you will learn how
to obtain a list of sensors that are available on a given Android device. You
will also see an example that combines graphics effects and gestures with
the output from a sensor. In addition, you will learn some details about
playing audio and video files on an Android device. As usual, the code
samples in this section were deployed to Android phones (but simulators
were not tested).
The first (and longest) part of this chapter contains code samples that
illustrate how to list the sensors that are supported on an Android device,
how to check for the availability of specific sensors on an Android device,
and then how to report information about values related to those sen-
sors. This section contains a “touch and shake” Android code sample that
combines graphics and sensors. This code sample enables users to render
circles by touching the screen, and then they can erase their “sketch” by
shaking their mobile device.
The second part of this chapter contains Android code samples with
media-related functionality, such as playing audio files and video files.
One interesting point is that the code sample for playing an MP4 video
file from YouTube works on a phone with Android 6.0.1 but not on a
Pixel phone with Android 7.1. After reading these code samples, you will
be in a better position to understand how to create Android applications
that record audio files and video files. The latter functionality is beyond
130 • ANDROID POCKET PRIMER
the scope of this chapter, but you can perform an Internet search to find
articles that discuss how to record audio and video files.
The third part of this chapter (which is very short) discusses some ideas
for Android applications that combine sensors and multimedia, along with
some links that provide potentially useful code blocks.
The final part of this chapter delves into Android permissions. Starting
from API level 23, Android permissions have become more user friendly
(e.g., runtime permissions are available), and correspondingly more
involved for developers.
As you will see, Android applications with multimedia—and examples
from earlier chapters—require various permissions. In some cases,
Android applications require multiple permissions.
If you prefer to use a sensor library, which is easier than reading raw
data, the following link is helpful: https://github.com/emotionsense/
SensorManager.
accelerometers
ambient temperature (added in Android 4.0)
gravity sensors
humidity sensor (added in Android 4.0)
gyroscopes
rotational vector
barometers
photometers
thermometers
orientation sensors
magnetometers
Each sensor typically has one or more constants associated with that
sensor. The associated constants for the new sensors that were added in
Android 4.0 are:
TYPE_AMBIENT_TEMPERATURE: A temperature sensor that provides
the ambient (room) temperature in degrees Celsius.
TYPE_RELATIVE_HUMIDITY: A humidity sensor that provides the
relative ambient (room) humidity as a percentage.
The improved sensors rely on the gyroscope sensor to improve their out-
put, so the sensors appear only on devices that have a gyroscope.
There are two ways to obtain sensor data in an Android application: either
deploy the application to an Android device, or use a simulator to test
most of the supported sensors on Android devices.
mSensorManager = (SensorManager)
getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.
TYPE_GYROSCOPE);
You can easily modify the preceding code block to check for differ-
ent sensors. For example, if you are interested in getting gravity-
related information, simply modify the final line of code in the preceding
code block as follows (shown in bold):
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
LISTING 6.1 MainActivity.java
package com.example.oswaldcampesato2.devicesensors;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import java.util.List;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_device_sensors);
mSensorMgr =
(SensorManager) getSystemService(Context.
SENSOR_SERVICE);
List<Sensor> sensors = mSensorMgr.
getSensorList(Sensor.TYPE_ALL);
for(Sensor s : sensors) {
Log.i(TAG, s.getName());
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy)
{
}
Listing 6.1 contains boilerplate code, and the onCreate() method starts
by instantiating a SensorManager object, after which you can obtain a
list of sensors. Next, iterate over that list of available sensors on a device,
as shown in this code block:
for(Sensor s : sensors) {
Log.i("sensor: ", s.getName());
}
Listing 6.3 displays the list of twenty-three sensors that are available on a
Pixel phone with Android 7.1.
SENSORS AND MULTIMEDIA • 135
The next section shows you how to check for the presence of specific sen-
sors on an Android device.
TYPE_TEMPERATURE
TYPE_RELATIVE_HUMIDITY
TYPE_PRESSURE
136 • ANDROID POCKET PRIMER
After you have chosen the constant that is associated with a particular sen-
sor, use the method getDefaultSensor()that accepts a sensor constant
to determine whether or not a sensor is available on a device.
Note that if a device has more than one sensor of a given type, one of
them is usually the default sensor. However, if no default sensor is avail-
able, then the method getDefaultSensor()returns null, which indi-
cates that the sensor is not present.
As a simple example, you can check for a gyroscope sensor using the
method getDefaultSensor(), as shown in the following code snippet:
if(mSensorMgr.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null)
{
// determine the orientation of the gyroscope
}
else
{
// the gyroscope is unavailable on this device
}
The following code first checks if the gravity sensor is present. If so, it
then checks that the vendor is Google and that the sensor is version 3
(which you can replace with a different version number).
if(mSensorMgr.getDefaultSensor(Sensor.TYPE_GRAVITY) != null)
{
List gravity = mSensorMgr.getSensorList(Sensor.
TYPE_GRAVITY);
for (int i = 0; i < gravity.size(); i++)
{
if (gravity.get(i).getVendor().contains("Google Inc.")
&&
gravity.get(i).getVersion() == 3)
{
// I've got the version 3 Google gravity sensor I need.
mGravity = gravity.get(i);
}
}
}
else
{
// No Google gravity sensor version 3
}
for devices that must have an accelerometer, add the following to the
AndroidManifest.xml file:
If users navigate to Google Play on a device that does not have a light
sensor and search for applications, Google Play will not show users any
applications that require a light sensor (which makes sense).
Another useful method is getMinDelay(), which determines the mini-
mum time interval (measured in microseconds) that a sensor requires in
order to obtain sensor data. If getMinDelay() returns a non-zero value,
then the sensor is a streaming sensor.
Streaming sensors were introduced in Android 2.3, and they can sense
data at regular intervals. On the other hand, non-streaming sensors only
report data when the sensor’s parameters change and will return zero
when getMinDelay() is invoked.
Device Orientation
This section shows you how to detect orientation changes (portrait mode
versus landscape mode) in a mobile device.
Copy the directory DeviceOrientation from the companion disc to a
convenient location. Listing 6.2 displays the contents of MainActivity.
java, which illustrates how to detect and report orientation changes of an
Android mobile device.
LISTING 6.2 MainActivity.java
package com.example.oswaldcampesato2.deviceorientation;
import android.support.v7.app.AppCompatActivity;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onPause");
sensorManager.unregisterListener(this, sensor);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy)
{
Log.d(TAG, String.format(
"onAccuracyChanged sensor: %d
accuracy: %d", sensor, accuracy));
}
Log.i(TAG, apr);
textView.setText(apr);
}
}
SENSORS AND MULTIMEDIA • 139
Log.d(TAG, apr);
outView.setText(apr);
The preceding code block displays the new values for the sensor in the
LogCat view as well as the TextField component outView that was
initialized in the onCreate() method.
LISTING 6.3 MainActivity.java
package com.example.oswaldcampesato2.simplecompass;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
140 • ANDROID POCKET PRIMER
import android.view.Menu;
import android.view.View;
paint.setColor(0xff00ff00);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2);
paint.setAntiAlias(true);
};
paint.setColor(0xff0000ff);
paint.setColor(0xff00ff00);
}
}
CustomDrawableView mCustomDrawableView;
SENSORS AND MULTIMEDIA • 141
accelerometer = mSensorManager.getDefaultSensor(
Sensor.TYPE_ACCELEROMETER);
magnetometer = mSensorManager.getDefaultSensor(
Sensor.TYPE_MAGNETIC_FIELD);
}
mSensorManager.registerListener(this, magnetometer,
SensorManager.SENSOR_DELAY_UI);
}
float[] mGravity;
float[] mGeomagnetic;
if(event.sensor.getType() == Sensor.
TYPE_MAGNETIC_FIELD)
142 • ANDROID POCKET PRIMER
mGeomagnetic = event.values.clone();
if(success) {
float orientation[] = new float[3];
SensorManager.getOrientation(R, orientation);
mCustomDrawableView.invalidate();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
}
if(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
mGravity = event.values.clone();
if(event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
mGeomagnetic = event.values.clone();
The next portion of this method uses conditional logic before initializing
a pair of arrays R and I, that are passed in to the getRotationMatrix()
method of the SensorManager class, as shown here:
If the value of the Boolean variable success is true, the final portion of
code (which is nested inside the previous code block) passes the R array
(initialized earlier in the code) and a second array to the getOrienta-
tion() method of the SensorManager class, as shown here:
if(success) {
float orientation[] = new float[3];
SensorManager.getOrientation(R, orientation);
The most up-to-date value for the azimuth is assigned in the last line of
code in the preceding code block.
Since the contents of the Canvas of this custom class has been modified,
we need to explicitly trigger the invalidate() method of the Canvas
class so that Android will refresh the contents of the Canvas with the
latest updated value of the azimuth.
Vibration
This section provides an outline of the code that is required to detect
vibrations in an Android mobile device. Copy the directory Vibrate from
the companion disc to a convenient location.
Include this permission in the file AndroidManifest.xml:
<uses-permission android:name="android.permission.VIBRATE"/>
LISTING 6.4 MainActivity.java
package com.example.oswaldcampesato2.vibrate;
import android.app.Notification;
import android.app.NotificationManager;
144 • ANDROID POCKET PRIMER
import android.app.Service;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.v7.app.AppCompatActivity;
//import android.support.v7.app.NotificationCompat;
import android.support.v4.app.NotificationCompat;
import android.view.View;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
vibrateOnce();
vibrateMulti(THREE_CYCLES);
}
vibrator.vibrate(1000);
}
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher);
builder.setVibrate(
SENSORS AND MULTIMEDIA • 145
notificationManager.notify(0, builder.build());
}
}
This concludes the portion of the chapter regarding sensors. The remain-
der of this chapter provides an overview of multimedia, which includes
playing audio files and video files.
Playing a Ringtone
The Android package android.media contains the MediaPlayer class
that enables you to play ringtones in an Android application.
Copy the directory RingTone from the companion disc to a convenient
location. Listing 6.5 displays the contents of MainActivity.java, which
illustrates how to generate a ringtone.
SENSORS AND MULTIMEDIA • 147
LISTING 6.5 RingToneActivity.java
package com.example.oswaldcampesato2.ringtone;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ring_tone);
ringtoneButton.setOnClickListener(new
OnClickListener() {
@Override
public void onClick(View v) {
Uri ringtoneUri = RingtoneManager
.getDefaultUri(RingtoneManager.
TYPE_RINGTONE);
playRingtone(RingtoneManager.getRingtone(
getApplicationContext(),
ringtoneUri));
}
});
}
mCurrentRingtone = newRingtone;
if (null != newRingtone) {
mCurrentRingtone.play();
148 • ANDROID POCKET PRIMER
}
}
@Override
protected void onPause() {
playRingtone(null);
super.onPause();
}
}
LISTING 6.6 activity_main.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:text="@string/ringtone_string" >
</Button>
</LinearLayout>
LISTING 6.7 SimpleAudio1.java
package com.example.oswaldcampesato2.simpleaudio1;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_audio1);
myButton.setOnClickListener(new Button.OnClickListener()
{
public void onClick(View v) {
Log.i(TAG, "Playing audio file");
mp = MediaPlayer.create(SimpleAudio1.this,
R.raw.japanese1);
mp.start();
mp.setOnCompletionListener(new
OnCompletionListener() {
public void onCompletion(MediaPlayer arg0) {
Log.i(TAG, "Inside onCompletion");
}
}
);
}
}
);
}
if (mp != null) {
mp.stop();
mp.release();
mp = null;
}
}
}
The preceding code block initializes the variable mp (an instance of the
MediaPlayer class) by passing a reference to the current Java class
SimpleAudio1 as well as the audio file japanese1.m4a located in the
res/raw subdirectory. After the initialization step, the code invokes the
start() method order to play the audio clip.
Another option for playing audio files is to use SoundPool class that is
documented here: https://developer.android.com/reference/android/
media/SoundPool.html.
LISTING 6.8 activity_you_tube.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_you_tube"
android:layout_width="match_parent"
android:layout_height="match_parent"
152 • ANDROID POCKET PRIMER
tools:context="com.example.oswaldcampesato2.
youtubevideo.YouTubeActivity">
<VideoView
android:id="@+id/myvideoview"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</android.support.constraint.ConstraintLayout>
LISTING 6.9 YouTubeActivity.java
package com.example.youtubevideo;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
VideoView myVideoView =
(VideoView)findViewById(R.id.myvideoview);
myVideoView.setMediaController(mediaController);
SENSORS AND MULTIMEDIA • 153
myVideoView.setVideoPath(youTubeLink);
myVideoView.setVideoURI(uri);
myVideoView.requestFocus();
myVideoView.start();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
m.youtube.com/watch?v=ESXgJ9-H-2U&app=m
154 • ANDROID POCKET PRIMER
Step 2: right click on the desired video, select Copy Link Location, and
paste the result in another browser session, which will display something
like this:
rtsp://r3---sn-a5mekned.googlevideo.com/Cj0LENy73wIaNAll-4ffJ-
AlERMYDSANFC0zUl5YMOCoAUIASARgt__7jfLH3vdWigELZTZEV0NOTmNRc
k0M/AFB58984FDF12A453C9C17F5D5DF848838D214A4.
6228BEB2775CD11CEB648058FA09D1A0AB12B72C/yt6/1/video.3gp
place MP4 files in that location. Next, use the following type of code snip-
pet to access an MP4 file:
Recording audio and video files via Android applications is more complex
than playing audio and video files, and they are beyond the scope of this
book. However, you can find code samples in the Android SDK, in online
tutorials, or from the following links:
https://github.com/steelkiwi/AndroidRecording
https://developer.android.com/guide/topics/media/audio-capture.
html
fragment.startActivityForResult(pIntent,
TAKE_PHOTO_RC);
} else {
String toastText = (String) mContext.
getResources()
.getText(R.string.toast_need_camera_app);
156 • ANDROID POCKET PRIMER
Toast.makeText(mContext,toastText,Toast.LENGTH_
SHORT).show();
}
}
ExoPlayer
As you have seen in this chapter, Android provides MediaPlayer for play-
ing media with minimal code, and the MediaCodec and MediaExtractor
classes are provided for building custom media players.
As an alternative, ExoPlayer is an open source project that supports fea-
tures that are not currently provided by MediaPlayer, such as Dynamic
adaptive streaming over HTTP (DASH), SmoothStreaming, and Common
Encryption.
The ExoPlayer home page is here: http://google.github.io/ExoPlayer.
ExoPlayer can be customized and extended, allowing many compo-
nents to be replaced with custom implementations. You can include the
ExoPlayer library in Android applications. Keep in mind that ExoPlayer
is not part of the Android framework and is distributed separately from
the Android SDK. The ExoPlayer Github repository is here: https://
github.com/google/ExoPlayer.
The next section regarding Android permissions is quite lengthy, so you
might need to return to parts of this section as you encounter Android
applications that involve various types of Android permissions.
Android Permissions
Although the code samples in this chapter do not contain Android per-
missions, you will discover that they are used in practically every non-triv-
ial Android application. Android supports many types of permissions,
and forgetting to include them in AndroidManifest.xml is a common
error.
SENSORS AND MULTIMEDIA • 157
<uses-sdk android:minSdkVersion="14"/>
<uses-permission android:name="android.permission.CAMERA"/>
<application
// details omitted
</application>
</manifest>
the applications are installed on a device. Users can deny any permissions
upon request, or deny any permissions later (even for legacy applications).
Although Android M users can revoke those privileges after they have
been granted, it’s unclear whether they can grant and revoke individual
access permissions per application. The following link contains screen-
shots that illustrate how to manage permissions on Android 6:
http://www.howtogeek.com/230683/
how-to-manage-app-permissions-on-android-6.0.
Dealing with permission denial in Android M:
https://plus.google.com/+AndroidDevelopers/posts/8aaudh5n1zM.
An example of runtime permissions for Android Marshmallow:
https://github.com/googlesamples/android-RuntimePermissions.
Android Marshmallow has removed almost thirty permissions that are
listed here:
https://commonsware.com/blog/2015/08/17/random-musings-
android-6p0-sdk.html.
The preceding link discusses combinations of permissions that are pos-
sible (or required) when working with Android Marshmallow and older
versions of Android.
Summary
This chapter showed you how to create Android mobile applications to
detect the presence of various sensors on an Android device (which can
vary between devices). You saw how to use the accelerometer, the gyro-
scope, and also how to detect the battery level of an Android device.
Then you learned about multimedia for playing ringtones, audio files,
and video files. In particular, you learned how to play a YouTube video
on an Android device. Next, you learned how to determine whether or
not an Activity is available to handle an Implicit Intent, thereby
avoiding a runtime error. In addition, you learned about the open source
project ExoPlayer that supports features that are not currently provided
by MediaPlayer, such as SmoothStreaming and Common Encryption.
Finally, you learned about various types of permissions for Android appli-
cations, how to request them, and how they differ based on the Android
API level.
CHAPTER
7
DATA STORAGE AND
FILE I/O
T
his chapter discusses context-related Android classes that
enable you to store data in a global context, followed by
a discussion of the storage feature of a mobile device to
maintain user-related information. You will learn about various storage-
related options, managing text files, working with a database, and invok-
ing system commands. As usual, the code samples in this section were
deployed to Android phones (but simulators were not tested).
This chapter also addresses Content Providers vis-à-vis Android API
level 23 and permissions. As you might have already discovered, code
samples that work correctly with lower API levels sometimes no longer
work when they are recompiled with Android Marshmallow or higher.
Some code modifications are required, as illustrated in the contacts-re-
lated code section, which contains an example of conditional logic that
compares the SDK version of an Android application against Android
Marshmallow and then executes the appropriate code.
The first part of this chapter shows you how to work with content provid-
ers so that you can update and access data on a mobile device. This section
also discusses various types of loaders for Android applications.
The second part of this chapter shows you how to create an Android appli-
cation that uses a SQLite database to persist data on a mobile device.
Next, you will learn about Realm, which is a good alternative to SQLite.
Note that Firebase is a cloud-based database, whereas you would install
Realm on your Android device.
162 • ANDROID POCKET PRIMER
The third part of this chapter involves managing text files and also
how to store binary files on an Android device. The final section of
this chapter contains an Android application that invokes the ps com-
mand (which displays a list of processes) in order to display the list
of processes that are running on an Android device. Note that this
code sample assumes that you are familiar with Java classes such as
BufferedReader and InputStreamReader.
Before delving into Content Providers, let’s take a look at how to use
some Context-related classes in Android applications.
You can also use this object when writing code in your Activity class
(because the Activity class is derived from the Context class). After
DATA STORAGE AND FILE I/O • 163
getting this valid application context, you can access features and services
at a system level.
Listing 7.1 displays the contents of MyCustomApp.java, which illustrates
how to keep track of variables in a global context in an Android application.
LISTING 7.1 MyCustomApp.java
public class MyCustomApp extends Application
{
private static Context context;
private String state = "someState";
private String url = null;
Listing 7.1 is essentially a “value object” (VO) class, with “getters” and
“setters” for the private variables state and url. Listing 7.1 also contains
the onCreate() method that invokes the onCreate() method of its par-
ent class and then initializes the variable content, as shown here:
in mind is that you can access variables in the global context class with this
code snippet:
LISTING 7.2 MainActivity.java
package com.example.oswaldcampesato2.mycontactlist;
import android.Manifest;
import android.app.ListActivity;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(layout.activity_main);
//---------------------------------------------
// ListView id MUST be '@android:id/list' else:
// Caused by: java.lang.RuntimeException:
// Your content must have a ListView whose id
// attribute is 'android.R.id.list'
//---------------------------------------------
this.listNames =
(ListView) findViewById(android.R.id.list);
showContacts();
}
ArrayAdapter<String> adapter =
new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, contacts);
listNames.setAdapter(adapter);
}
}
if (cursor.moveToFirst()) {
// Iterate through the cursor
do {
// Get the contacts name
String name = cursor.getString(
DATA STORAGE AND FILE I/O • 167
cursor.getColumnIndex(
ContactsContract.Contacts.DISPLAY_NAME));
contacts.add(name);
++userCount;
} while (cursor.moveToNext());
}
cursor.close();
Listing 7.2 contains boilerplate code and then defines the MainActivity
class that extends the Android ListActivity class instead of the
AppCompatActivity class that you have seen in almost all the other
code samples.
The onCreate() method initializes the variable listNames as a refer-
ence to the ListView component in activity_main.xml. The last line
of code in onCreate() invokes the showContacts() method that con-
tains conditional logic to determine the SDK version and the build version.
If the API level of the Android application is at least Android Marshmallow
and permission has been already granted, then the readPermissions()
method is invoked with the READ_CONTACTS permission that has been
added to AndroidManifest.xml.
Otherwise, the API level is lower than Marshmallow (or permission has
been granted), in which case the contacts variable (which is a list of
strings) is initialized as the return value of the getContactNames()
method (discussed later). Then the adapter variable is initialized as an
instance of the ArrayAdapter class, and then initialized as the adapter
for the listNames variable.
The getContactNames() method initializes the cursor variable (an
instance of the Cursor class) as a “pointer” to the contacts that are avail-
able on your Android device. If cursor is non-null, the do-while loop
advances the cursor and simultaneously adds each user to the contacts
variable (which is a list of strings). When the do-while loop completes,
the cursor is closed and the contacts variable is returned.
The preceding description of the getContactNames() method is admit-
tedly light, but you can read the online documentation for more information
regarding cursors and the other classes that are referenced in this method.
168 • ANDROID POCKET PRIMER
LISTING 7.3 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.oswaldcampesato2.
mycontactlist.MainActivity">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
The following link contains good information about how to prompt users
for permissions (and other relevant aspects of permissions): https://mate-
rial.io/guidelines/patterns/permissions.html#.
Note that this code sample was deployed to a Pixel phone with Android 7.1,
but has not been tested on other devices or on a simulator.
The first point to understand is that custom content providers are sub-
classes of the class android.content.ContentProvider that is pro-
vided by Android. Custom content providers implement the following
methods:
DATA STORAGE AND FILE I/O • 169
onCreate()
delete()
getType()
insert()
update()
The getType() method returns the MIME type of the data that is stored
by the content provider, and the other methods in the preceding list pro-
vide functionality that you would expect (based on their names).
The second point is that the Android system provides a Content URI
in order to identify different content providers that are available on
an Android device. The Android system stores references to content
providers according to an authority string that is part of the provider’s
content URI. The Android system looks up the authority in its list of
known providers and their authorities. The Authority section of the
content URI identifies the content provider, and it’s usually expressed
as the package name of the content provider, an example of which is
here:
com.iquarkt.myapp.myprovider
com.iquarkt.myapp.myprovider/employee
You can access a specific row in the table employee by appending its
rowID to the table name, as shown here:
com.iquarkt.myapp.myprovider/employee/50
Data Storage
You can store data on a mobile device in several ways (for different pur-
poses), some of which are listed here:
Shared Preferences
Internal Storage
External Storage
SQLite Databases
Network Connection
The method that you select for persisting application data depends on
factors such as:
the volume of data
private versus public data
accessibility to other Android apps
You can use an Android ContentProvider to share data among multiple
Android applications. The following sections discuss these storage tech-
niques in more detail, along with some code samples that you can use in
Android applications.
User Preferences
The Android package android.app contains the PreferenceActivity
(a subclass of android.app.ListActivity) that is the base class for pref-
erence-related Android classes. According to the Android documentation:
Prior to HONEYCOMB this class only allowed the display
of a single set of preference; this functionality should now be
found in the new PreferenceFragment class. If you are using
PreferenceActivity in its old mode, the documentation there
applies to the deprecated APIs here.
This activity shows one or more headers of preferences, each of
which is associated with a PreferenceFragment to display the
preferences of that header.
The Android SharedPreferences class provides APIs for saving a rela-
tively small collection of key-value pairs. A SharedPreferences object
points to a file containing key-value pairs and provides simple methods to
read and write them. Each SharedPreferences file is managed by the
framework and can be private or shared. The SharedPreferences APIs
enable you to store and retrieve simple values.
Keep in mind that the SharedPreferences APIs are only for reading
and writing key-value pairs. By contrast, the Preference APIs are use-
ful for creating a user interface for application settings.
Create a shared preference file or access an existing one by invoking one
of two methods:
1) getSharedPreferences() is for multiple shared preference files
2) getPreferences() is invoked from an Activity when a single shared
preference file is needed in the activity
172 • ANDROID POCKET PRIMER
Loaders
This section provides a brief overview of Android Loaders, which include
the main classes that Loaders use in order to provide data access to
Android applications.
Starting with Android 3.0, loaders became the preferred way to access
data of databases or content providers.
Loaders load data asynchronously and notify listeners when the results
are ready. Loaders involve the following Android classes:
LoaderManager
LoaderManager.LoaderCallbacks (interface)
Loader
AsyncTaskLoader
CursorLoader
The LoaderManager class manages loaders and it’s also responsible for
dealing with the Activity or Fragment lifecycle. The LoaderManager.
LoaderCallbacks interface contains methods that you must implement.
The Loader class is the base class for all Loaders. The AsyncTaskLoader
class is an implementation that uses an AsyncTask to do its work. Finally,
the CursorLoader class is a subclass of AsyncTaskLoader for accessing
ContentProvider data.
A SQLite Example
The Android package android.database.sql contains classes for creating
and opening a SQLite database on an Android mobile device. In addition,
the Android package android.view contains the Cursor class that enables
you to iterate through the result set that returned from a database query.
Copy the directory SaveDataToSQLite from the companion disc to a
convenient location. Listing 7.4 displays the contents of MainActivity.
java, which illustrates how to store and retrieve a set of names in a
SQLite database in an Android application.
174 • ANDROID POCKET PRIMER
LISTING 7.4 MainActivity.java
package com.iquarkt.savedatatosqlite;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
saveData();
TextView tv = (TextView)findViewById(R.id.text1);
tv.setText("Data saved on SQLite database!");
loadData();
}
TextView tv = (TextView)findViewById(R.id.text1);
//tv.setText(user.toString());
tv.setText("Name: "+user.name+" Rating: "+user.
rating);
}
@Override
protected void onPause() {
super.onPause();
saveData();
}
@Override
protected void onResume() {
DATA STORAGE AND FILE I/O • 175
super.onResume();
loadData();
}
}
The onCreate() method in Listing 7.4 contains six steps (as you can
see from the comments in the code) that start with obtaining a reference
to the DAODelegate class (displayed in Listing 7.5). Next, the code
deletes the current list of friends, populates a new list of friends, and
displays that new list in a TextView component.
LISTING 7.5 MySQLiteHelper.java
package com.iquarkt.savedatatosqlite;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
@Override
public void onCreate(SQLiteDatabase db) {
String CREATE_USERS_TABLE = "CREATE TABLE "
+ TABLE_USERS + " ( " +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT, " +
"rating INTEGER )";
db.execSQL(CREATE_USERS_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db,
int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS);
this.onCreate(db);
}
176 • ANDROID POCKET PRIMER
cursor.moveToFirst();
return user;
}
}
Listing 7.5 starts with some boilerplate code and some database-
related initialization, such as defining the INSERT variable, which is a
SQL statement for inserting new rows into the database.
The constructor in Listing 7.5 is passed a reference to the MainActivity
class that is used for obtaining a reference to the current database.
Listing 7.5 also contains an assortment of “helper” methods with straight-
forward code for inserting and deleting rows from the friends table
in the database called database1. As you can see in Listing 7.5, these
helper methods are invoked from an instance of the SaveDataToSQLite
class.
Listing 7.6 displays the contents of User.java, which is a convenience
class for keeping track of user-related information.
DATA STORAGE AND FILE I/O • 177
LISTING 7.6 User.java
package com.iquarkt.savedatatosqlite;
public User() {}
debugCompile 'com.amitshekhar.android:debug-db:0.3.0'
Use debugCompile so that it will only compile in your debug build and
not in your release apk.
Now start the application, and logcat will contain the following type of
entry:
You can also always get the debug address URL from your code by invok-
ing the following method:
DebugDB.getAddressLog();
debug {
resValue("string", "PORT_NUMBER", "8081")
}
DATA STORAGE AND FILE I/O • 179
Each Android application starts with its own user and group ID, so
some file folders are accessible through a given Android application
only if they are specifically given the following access permission in
AndroidManifest.xml:
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
if (!sddir.mkdirs()) {
if (sddir.exists()) {
}
else {
Toast.makeText(TutorialOnImages.this,
"Folder error", Toast.LENGTH_SHORT)
.show();
return;
}
}
The following code block saves the resulting image onto the SD card
under our folder:
try {
FileOutputStream fos = new FileOutputStream(
dirname + "sample1.png");
mBitmap.compress(CompressFormat.JPEG, 75, fos);
fos.flush();
fos.close();
} catch (Exception e) {
Log.e("MyLog", e.toString());
}
The following code snippet returns an array of File objects (where each
object is associated with one filename) in the current directory:
In Android you can easily retrieve the list of files in a directory on the SD
card, as shown here:
Another technique for retrieving the list of files in a directory is to use the
FileFilter class, as shown here:
The next several sections contain code samples that illustrate how to write
data to files and how to write data to the /sdcard directory of Android
devices.
Writing to Files
Listing 7.7 displays the contents of MainActivity.java, which illus-
trates how to write to a file in an Android application.
LISTING 7.7 MainActivity.java
package com.example.oswaldcampesato2.writetofile;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStreamWriter;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
DATA STORAGE AND FILE I/O • 183
OutputStreamWriter oStreamWriter =
new OutputStreamWriter(
openFileOutput(FILENAME,
Context.MODE_PRIVATE));
oStreamWriter.write(text1);
oStreamWriter.close();
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Earlier in this chapter you learned how to work with files and directories
on an Android mobile device. You can also process the contents of a direc-
tory on the SD Card, as shown here:
You can determine the total and available space on the SD Card by using
the StatFs and Environment classes from the android.os package, as
shown here:
You can display the value with two decimal places via a DecimalFormat
object in java.text, as shown here:
If you need to create, read, or write files on the SD Card, consult the
Android documentation for sample code.
LISTING 7.8 SaveImages.java
public void saveImageToSDCard(byte[] data, String targetName)
{
FileOutputStream outStream = null;
String location = "/sdcard/" +targetName;
String TAG = "saveImageToSDCard";
try {
// write to sdcard
outStream = new FileOutputStream(location);
outStream.write(data);
outStream.close();
catch (IOException e) {
e.printStackTrace();
}
finally {}
String dirname =
Environment.getExternalStorageDirectory() + "/
newdirectory/";
if (!sddir.mkdirs())
{
if (sddir.exists())
{
// save the file here
}
else
{ Toast.makeText(MainActivity.this,
"Folder creation error", Toast.LENGTH_
SHORT).show();
}
}
186 • ANDROID POCKET PRIMER
ReadTextFilel
SaveDataToFile
SaveTextFile
LISTING 7.9 MainActivity.java
package com.iquarkt.execcommand;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
runCommand();
}
process = Runtime.getRuntime().exec("ps");
process = Runtime.getRuntime().exec("ps");
The next step obtains a buffered input stream with all the details of the
running processes on the Android device:
The next portion of code contains a loop that reads one “line” at a time
from the input stream, where each “line” contains the information about
a single process. Each line of data is then added to listAdapter, as
shown here:
LISTING 7.10 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/mainListView">
</ListView>
</LinearLayout>
The XML <ListView> element in Listing 7.10 specifies that the data
returned from the ps command will be displayed as a list of items.
However, the format for each row in the list of items needs to be specified
as well. This format is defined in Listing 7.11: it displays the contents of
onerow.xml, which contains a single TextView component.
LISTING 7.11 onerow.xml
<TextView xmlns:android="http://schemas.android.com/
apk/res/android"
android:id="@+id/rowTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:textSize="16sp" >
</TextView>
Listing 7.11 defines a TextView component that specifies the format for
rendering a single row of data, where each row corresponds to a “line” of
output from the ps command.
Figure 7.2 displays the result of launching the ExecCommand project on a
Pixel phone with Android 7.1.
Figure 7.3 displays the result of launching the ExecCommand project on a
Samsung Galaxy S5 phone with Android 6.0.1.
190 • ANDROID POCKET PRIMER
As another example, you can display the output from the LogCat com-
mand with the following onCreate() method:
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_log_cat);
try {
Process process = Runtime.getRuntime().exec("logcat -d");
new InputStreamReader(process.getInputStream()));
TextView tv = (TextView)findViewById(R.id.textView1);
tv.setText(log.toString());
}
catch (IOException e) {}
}
The preceding code block contains one code snippet that differs from
Listing 7.9:
Keep in mind that you need to provide additional code in order to improve
the alignment of the data that is displayed on an Android device.
Summary
The first part of this chapter described how to work with content pro-
viders and loaders for Android applications. Then you saw how to create
an Android application that uses a SQLite database to persist data on a
mobile device, and also how to work with the Realm database.
Next you learned how to manage text files in Android, and also how to
store binary files on an Android device. Finally, you saw an Android
application that invokes the ps command in order to display the list of
processes that are running on an Android device.
CHAPTER
8
ServiceS and BroadcaSt
receiverS
T
his chapter discusses Android services and Android broadcast
receivers, both of which provide useful functionality in Android
applications. One use case for an Android Service involves down-
loading music in the background. A use case for an Android Broadcast
Receiver involves detecting a change in the battery level of a mobile
device; another use case involves receiving a notification when your
Android device has rebooted.
The first part of this chapter delves into Android Services, which are suit-
able for various tasks. A Service runs in the main UI thread, and you can
launch a separate Thread in a Service in order to perform long-running
tasks. This section discusses several types of services and also contains
code samples. If you are unfamiliar with threads, read the appropriate
Appendix that discusses threads and networking, which also contains an
example of a custom Service that uses a custom thread.
The second part of this chapter discusses Broadcast Receivers, which
can respond to system-wide broadcasts as well as broadcast messages
from other Android applications on the same device. The system delivers
broadcasts for system events, and you can use a Broadcast Receiver
to receive a notification about those events. This section also contains an
example of an Android Alarm that performs a simple background task.
Android Services
Android supports Services via a <service> element. Every Service in
an Android application must have its associated XML <service> ele-
ment included in AndroidManifest.xml. These XML elements are
placed as siblings of the Activity element(s).
An Android Service provides a mechanism to communicate with the
Android system regarding a background task or operation. The associated
method in this case is Context.startService(), which starts the exe-
cution of the task.
In addition, an Android Service provides a mechanism for exposing
functionality to other Android applications. The associated method in this
case is Context.bindService(), which makes available a long-lasting
connection with the Service.
SERVICES AND BROADCAST RECEIVERS • 195
<service android:name=".subpackagename.SimpleService"/>
Android Services can run in the foreground and as well as the back-
ground. Note that Services can run in the background indefinitely, even if
the component that started the service is destroyed. In general, a service
performs a single operation and stops after completing its task. If a service
involves any long running blocking operation, it’s better to place that code
in a separate Thread, thereby avoiding an Application Not Responding
(ANR) event.
196 • ANDROID POCKET PRIMER
Now that you have an overview of Android Services, let’s take a look at a
simple example, after which we’ll discuss the Service lifecycle methods.
LISTING 8.1 MainActivity.java
package com.iquarkt.mysimpleservice;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
//getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
SERVICES AND BROADCAST RECEIVERS • 197
{
// Handle action bar item clicks here. The action
bar will
// automatically handle clicks on the Home/Up button,
so long
// as you specify a parent activity in
AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
Listing 8.1 contains familiar code. The new section of code is in the
onCreate() method, which defines an Android Intent, places some
extra information in the intent, and then involves the startService()
method.
Now let’s look at Listing 8.2, which displays the contents of the class
MyBasicService.java that illustrates how to define a custom Service
class.
LISTING 8.2 MyBasicService.java
package com.iquarkt.mysimpleservice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
@Override
public int onStartCommand(Intent intent, int flags,
int startId)
{
String extra = intent.getStringExtra("APP1");
Log.i(TAG, "onStartCommand APP1 = "+extra);
return Service.START_NOT_STICKY;
}
@Override
198 • ANDROID POCKET PRIMER
Listing 8.2 contains two new methods: the onStartCommand() and the
onBind() method, both of which are part of the Service lifecycle that
is discussed in the next section. The only thing that this code does is to
retrieve the “extra” information (specified on the onCreate() method in
MainActivity.java) and then use the Log.i() method to display the
value of the extra information.
Next, update AndroidManifest.xml by inserting the following code
immediately after the closing tag for the <activity> element in order to
register the custom service:
<service android:name="MyBasicService"
android:icon="@drawable/samplel"
android:label="@string/service_name">
</service>
Note that if you want to display a custom icon for this application, change
the icon attribute in the <application> element, which in this case
equals sample1, and therefore refers to the sample1.png file.
Launch this application from Android Studio and deploy to an Android
device, after which you will see the familiar “Hello, World” text string on
the screen. If you list the applications on our device and search for the
MySimpleService application, you will see your custom icon if you have
made the preceding change; otherwise you will see the default Android icon.
As noted above, Listing 8.2 contains two methods that belong to the
Service class lifecycle, which are briefly discussed in the next section.
onStartCommand()
onBind()
onCreate()
onDestroy()
The onStartCommand() method is invoked (via the startService()
method) when the service is started by another component. This method
does not need to be implemented for bound services.
The onCreate() method, which handles initialization tasks, is invoked
before the method onStartCommand() or the initial invocation of the
onBind() method (discussed later). As you probably expect, the onDe-
stroy() method is invoked when the service is being destroyed.
LISTING 8.3 MyService.java
public class MyService extends Service
{
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
protected void onCreate() {
super.onCreate();
startservice(); // defined elsewhere
}
@Override
protected void startService() {
// insert your code here
}
@Override
protected void onStart() {
// insert your code here
}
}
200 • ANDROID POCKET PRIMER
When users click on the <button> element, the custom code in the
startService() method of the HelloService class is executed.
On the other hand, a Service can be stopped in two ways. The first way
is to invoke the stopSelf() method after the Service finishes its task.
The second way is to invoke the method stopService(). In this case,
the method stopService() invokes the onDestroy() method that is
defined in the custom Service.
<service android:name=".HelloService"
android:exported="false"/>
</application>
In fact, not even an explicit Intent can start the preceding service.
Set the android:exported attribute to false in order to restrict access
NOTE to a custom Service.
Types of Services
Although Android Services have a simple foundation, there are various
types of Services available, and they can become quite complex. Just to
summarize, Android supports the following types of Services:
bound and unbound services
sticky and non-sticky services
IntentService services
The preceding services differ in terms of their lifespan, whether or not
their methods can be accessed directly from other components, and
whether or not they are restarted. The following subsections provide
more details about these Services.
JobScheduler
The Android JobScheduler class provides APIs for scheduling various
types of jobs that will be executed in the same process as your application.
For example, you can extend the JobScheduler class in order to batch
network requests in order to improve the performance of your Android
application. The JobScheduler class is an abstract class that is available
from API level 21.
SERVICES AND BROADCAST RECEIVERS • 203
LISTING 8.4 AndroidManifest.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the
"License");
you may not use this file except in compliance with the
License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in
writing, software
distributed under the License is distributed on an "AS
IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied.
See the License for the specific language governing
permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/
apk/res/android"
package="com.example.android.jobscheduler" >
<uses-permission android:name="android.permission.
INTERNET" />
204 • ANDROID POCKET PRIMER
<uses-permission
android:name="android.permission.RECEIVE_BOOT_
COMPLETED" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:windowSoftInputMode="stateHidden" >
<intent-filter>
<action android:name="android.intent.
action.MAIN" />
<category
android:name="android.intent.
category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".service.MyJobService"
android:permission="android.permission.
BIND_JOB_SERVICE"
android:exported="true"/>
</application>
</manifest>
Listing 8.4 contains boilerplate code and a <service> element that refer-
ences the class MyJobService. As you can see, the <service> element
specifies the permission android.permission.BIND_JOB_SERVICE,
and the Android documentation states that: “If a job service is declared in
the manifest but not protected with this permission, that service will be
ignored by the OS.”
Additional information about the JobScheduler class and the
JobService class is here:
https://developer.android.com/reference/android/app/job/
JobScheduler.html
https://developer.android.com/reference/android/app/job/JobService.
html
A good blog post that provides more detailed information about various
options for a JobScheduler is here: http://toastdroid.com/2015/02/21/
how-to-use-androids-job-scheduler/.
SERVICES AND BROADCAST RECEIVERS • 205
After creating these two Java classes, you will see how to register the
custom Broadcast Receiver in AndroidManifest.xml and update
activity_main.xml with an <EditText> element for user input and a
<Button> element to submit the text string.
LISTING 8.5 MainActivity.java
package com.iquarkt.mysimplebroadcastreceiver;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
EditText editText =
(EditText)findViewById(R.id.intent_data);
intent.setAction(
"com.iquarkt.mysimplebroadcastreceiver.
A_CUSTOM_INTENT");
Listing 8.5 contains familiar boilerplate code, and the relevant code is
specified in the broadcastCustomIntent method. When users click
the Button component, this method reads the text string supplied by
users and populates the message property with that text string. This
method also sets the “action” to a custom string via the setAction()
method, and this custom string is required in Listing 8.5 in order to cor-
rectly match the launched Intent. The final portion of this method spec-
ifies the action property and then invokes the sendBroadcast() method
with the populated Intent.
One more detail: Listing 8.5 does not contain click-related code because
the XML file activity_main.xml (displayed in Listing 8.6) specifies that
the method broadcastCustomIntent will be invoked when users click
on the Button control, as shown here:
<Button android:id="@+id/startBroadcastButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/intentdatat"
android:onClick="broadcastCustomIntent"
android:text="@string/myBroadcastIntent" />
LISTING 8.6 MyBroadcastReceiver.java
package com.iquarkt.mybroadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
intent.getCharSequenceExtra("message");
Toast.makeText(context,
"Message from Intent: "+intentData,
Toast.LENGTH_LONG).show();
}
}
Listing 8.6 contains the onReceive() method that has a Context argu-
ment and an Intent argument. The Intent argument contains the text
string that users entered in the EditText control, and the getChar-
SequenceExtra() method is used to extract that text string (as a
CharSequence instead of a String). Next, the onReceive() method
launches a Toast via the makeText() method in order to display the
users’ text string in the form of a Toast.
Listing 8.7 displays the contents of AndroidManifest.xml, which reg-
isters the custom Broadcast Receiver.
LISTING 8.7 AndroidManifest.xml
package com.iquarkt.mybroadcastreceiver;
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/
android"
package="com.iquarkt.mysimplebroadcastreceiver" >
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.
category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="MyBroadcastReceiver">
<intent-filter>
<action
android:name="com.iquarkt.
mysimplebroadcastreceiver.A_CUSTOM_INTENT">
</action>
</intent-filter>
SERVICES AND BROADCAST RECEIVERS • 209
</receiver>
</application>
</manifest>
LISTING 8.8 activity_main.xml
<receiver android:name="MySimpleBroadcastReceiver">
<intent-filter>
<action android:name="com.iquarkt.
mysimplebroadcastreceiver.A_CUSTOM_INTENT">
</action>
</intent-filter>
</receiver>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<EditText android:id="@+id/intent_data"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/send_message" />
<Button
android:id="@+id/startBroadcastButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/intentdata"
android:onClick="broadcastCustomIntent"
android:text="@string/myBroadcastIntent" />
</RelativeLayout>
210 • ANDROID POCKET PRIMER
Listing 8.8 contains basic layout details, as well as the onClick attribute
for the Button control that specifies the method broadcastCustom-
Intent() in Listing 8.5 as the method to be executed whenever users
click on the Button control.
Alarms
The code sample in this section shows you how to create an
Android Alarm that uses an Android Toast as well as an Android
Notification.
LISTING 8.9 SimpleAlarm1.java
package com.iquarkt.gui;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
SERVICES AND BROADCAST RECEIVERS • 211
import android.widget.Toast;
import java.util.Calendar;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String tickerText =
showIconOnly ? null : this.
getString(statusBarTextID);
this.nm.notify(this.APP_NOTIFICATION_ID, notif);
}
AlarmManager am =
(AlarmManager) getSystemService(Context.
ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(),
appIntent);
The next portion of this method creates a Calendar instance called cal-
endar and an AlarmManager instance called am in order to set the time
at which the alarm will be invoked, as shown here:
am.set(AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(),
appIntent);
SimpleAlarm1.this.mToast = Toast.makeText(SimpleAlarm1.this,
R.string.alarm_message,
Toast.LENGTH_LONG);
SimpleAlarm1.this.mToast.show();
LISTING 8.10 ReceiveAlarm1.java
package com.iquarkt.gui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
Toast.makeText(context,
"hello from
received alarm",
Toast.
LENGTH_SHORT).show();
}
}
LISTING 8.11 SimpleBattery1.java
package com.example.simplebattery1;
import android.os.Bundle;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.view.Menu;
import android.widget.ProgressBar;
import android.widget.TextView;
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
LISTING 8.12 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textfield"
android:layout_marginTop="40dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<ProgressBar
android:id="@+id/progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dip"
android:layout_gravity="center"
android:minWidth="200dip"
android:minHeight="100dip"
android:max="100"
style="?android:attr/progressBarStyleHorizontal"/>
</LinearLayout>
LISTING 8.13 MyReceiver.java
*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the
"License");
* you may not use this file except in compliance with
the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in
writing, software
* distributed under the License is distributed on an
"AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied.
* See the License for the specific language governing
permissions and
* limitations under the License.
*/
package com.example.android.appshortcuts;
import android.content.BroadcastReceiver;
import android.content.Context;
218 • ANDROID POCKET PRIMER
import android.content.Intent;
import android.util.Log;
@Override
public void onReceive(Context context, Intent intent)
{
Log.i(TAG, "onReceive: " + intent);
if (Intent.ACTION_LOCALE_CHANGED.equals(intent.
getAction()))
{
// Refresh all shortcut to update the labels
// (Right now shortcut labels don't contain
// localized strings though)
new ShortcutHelper(context).
refreshShortcuts(/*force=*/ true);
}
}
}
Summary
This chapter discussed Android Services, their lifecycle, and also an
example of creating a custom Service in an Android application. Next
you learned about Android Broadcast Receivers, and a simple example of
implementing a Broadcast Receiver. You also saw how to use a Broadcast
Receiver to receive a notification whenever the battery levels change on
an Android device.
CHAPTER
9
ANDROID VR, TV,
AUTO, AND THINGS
T
his chapter contains an introduction to an assortment of Android
topics, including Android VR (Virtual Reality), Android TV,
Android Auto, and Android Things (formerly Android IoT).
There are a few things (such as limitations) that you need to know before
delving into the material. First of all, this chapter provides a cursory intro-
duction to VR. If you plan to acquire a deeper knowledge of VR, the last
part of the VR section makes a recommendation based on a high-level
comparison between Google VR and Unity.
In addition, some code samples require Android 7 (Nougat), and the
screenshots for those code samples are from a Google Pixel phone. Since
the Pixel phone is designed to work with the DayDream headset, we’ve
used a Pixel to generate screenshots in this chapter. In addition, you will
only be able to see the full VR effects and the 360-degree panorama of
Macchu Picchu, Peru on a Pixel phone (or one with comparable power).
Moreover, the Google VR code samples require a Google “certified”
phone, and the setup with the Pixel phone is straightforward. However,
if you don’t have a Pixel phone, it’s possible to use a Nexus 6P (see the
online documentation for the setup steps) with Android VR, as well as
Android-based simulators to view the code samples.
The first part of this chapter provides a modest overview of some features
of Android VR (Virtual Reality), which you can view on Google Cardboard
(about USD 35) and also with the Google DayDream headset (about USD
80). This section also discusses portions of an Android VR application that
220 • ANDROID POCKET PRIMER
Android VR SDK
The Android VR SDK supports both Daydream and Cardboard, with an
API to create applications. A more complex API is available that supports
Daydream-ready phones and the Daydream controller. The Android VR
SDK can handle various VR development tasks such as:
Lens distortion correction
Spatial audio
Head tracking
3D calibration
Side-by-side rendering
Stereo geometry configuration
User input event handling
The Google VR NDK for Android provides a C/C++ API for develop-
ers writing native code (not covered in this chapter). After creating such
applications, deploy them to a Pixel phone and then insert the phone into
the DayDream headset or the Cardboard viewer.
in the previous section. The screenshots were taken via Vysor, which
is a Chrome extension that allows you to “mirror” the contents of an
Android device on a MacBook (and vice versa). As you navigate around
your Android device, you will see the contents updated on your MacBook
(and vice versa).
The code samples in previous chapters
contain single Android Studio projects.
By contrast, the VR code samples in this
section are in the top-level directory
gvr-android-sdk-master, and you
can launch different projects at runtime
in Android Studio. For your conveni-
ence, Figure 9.1 displays a screenshot of
a section of Android Studio that displays
the list of the VR projects after you navi-
gate to Run > Run.
Figure 9.2 displays a screenshot with the
simplepanowidget project rendered
on a Pixel phone.
Figure 9.3 displays a screenshot with the
treasurehunt project rendered on a
Pixel phone.
Figure 9.4 displays a screenshot with the
FIGURE 9.2 Pixel Phone and the simplevideowidget VR project ren-
simplepanowidget VR project. dered on a Pixel phone.
224 • ANDROID POCKET PRIMER
The next section discusses some of the code samples associated with the
screenshots in this section.
FIGURE 9.4 Pixel Phone and the FIGURE 9.5 Pixel Phone and the
simplevideowidget VR project. controllerclient VR project.
ANDROID VR, TV, AUTO, AND THINGS • 225
LISTING 9.1 SimpleVrVideoActivity.java
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
setIsMuted(!isMuted);
}
});
loadVideoStatus = LOAD_VIDEO_STATUS_UNKNOWN;
Listing 9.1 displays the onCreate() method that simply references the
UI components in the main XML layout file, and also defines relevant lis-
teners. For example, the TextView component has a motion-associated
listener, whereas the VrVideoView component has an associated event
listener.
Listing 9.2 displays a portion of the contents of main_layout.xml, which
is associated with this VR project.
LISTING 9.2 main_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/
apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main_layout"
android:padding="10dip"
android:orientation="vertical" >
<TextView
android:id="@+id/title"
style="@style/ContentText"
android:textSize="@dimen/title_text_size"
android:textStyle="bold"
android:textColor="@color/textDark"
android:text="@string/title" />
<com.google.vr.sdk.widgets.video.VrVideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:scrollbars="@null"
android:layout_height="250dip"/>
ANDROID VR, TV, AUTO, AND THINGS • 227
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<!-- Seeking UI & progress indicator.-->
<SeekBar
android:id="@+id/seek_bar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_height="32dp"
android:layout_weight="8"
android:layout_width="0dp"/>
<ImageButton
android:background="@android:color/transparent"
android:id="@+id/volume_toggle"
android:paddingTop="4dp"
android:paddingStart="0dp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:src="@drawable/volume_on"/>
</LinearLayout>
// omitted elements are on the companion disc
</LinearLayout>
</ScrollView>
Useful Links
The following link shows you how to create a VR video: http:
//mashable.com/2016/04/20/lg-360-cam-camera-review/#GCsZJ0DfAuqp.
Play the VR in an Android VR video viewer in an Activity, as described
here: https://developers.google.com/vr/concepts/vrview.
ANDROID VR, TV, AUTO, AND THINGS • 229
Android TV
Android Studio enables you to create Android TV applications, which
rely on Android Fragments and Lists. Fragments are very useful in
these applications, especially for reducing the amount of time to load
different videos in a section of an Activity (i.e., for performance
reasons).
Google released the TV Input Framework (TIF), which is an easier
alternative for creating Android applications for Android TV. The TIF
provides an API for creating TV Input Modules and live TV search and
recommendations, where the latter facilitates meeting regional digital TV
broadcast standards.
The TIF provides implementations of various TV input service features,
along with various components, such as HDMI-CEC, TV Input, and TV
Input HAL. For more information about building Android TV appli-
cations, navigate to the website developer.android.com and search for
“Building Apps for TV.”
Useful Links
A video for Android TV: https://developer.android.com/training/tv/index.
html.
Android TV documentation: https://developer.android.com/
training/tv/tif/tvinput.html.
A sample Android TV application on Github: https://github.com/
googlesamples/androidtv-sample-inputs.
Android TV Applications
Before we look at a code block in an Android TV application, this section
provides a condensed description of what you will encounter when you
230 • ANDROID POCKET PRIMER
LISTING 9.3 MainFragment.java
@Override
public void onActivityCreated(Bundle savedInstanceState)
{
Log.i(TAG, "onCreate");
super.onActivityCreated(savedInstanceState);
prepareBackgroundManager();
setupUIElements();
loadRows();
setupEventListeners();
}
int i;
for (i = 0; i < NUM_ROWS; i++) {
if (i != 0) {
Collections.shuffle(list);
}
ArrayObjectAdapter listRowAdapter =
new ArrayObjectAdapter(cardPresenter);
listRowAdapter.add(list.get(j % 5));
}
HeaderItem header =
new HeaderItem(i, MovieList.MOVIE_CATEGORY[i]);
ArrayObjectAdapter gridRowAdapter =
new ArrayObjectAdapter(mGridPresenter);
gridRowAdapter.add(
getResources().getString(R.string.grid_view));
gridRowAdapter.add(getString(R.string.error_fragment));
gridRowAdapter.add(
getResources().getString(R.string.personal_settings));
setAdapter(mRowsAdapter);
}
Useful Links
https://codelabs.developers.google.com/codelabs/androidauto-
messaging/index.html
A free Android Auto course is offered by Udacity: https://www.udacity.
com/course/android-auto-development--ud875C.
An Android Auto book is available here: http://www.apress.com/us/
book/9781484217832.
The Github repository for the Android Auto book is here: https://github.
com/apress/android-tv-apps-dev.
234 • ANDROID POCKET PRIMER
LISTING 9.4 MyMusicService.java
public class MyMusicService extends MediaBrowserService
{
private MediaSession mSession;
@Override
ANDROID VR, TV, AUTO, AND THINGS • 235
setSessionToken(mSession.getSessionToken());
mSession.setCallback(new MediaSessionCallback());
mSession.setFlags(
MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
}
@Override
public void onDestroy() {
mSession.release();
}
// code omitted
}
LISTING 9.5 MessageReceiver.java
package com.example.oswaldcampesato2.myandroid2auto;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationManagerCompat;
import android.util.Log;
MessageReadReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent)
{
if(MyMessagingService.READ_ACTION.
equals(intent.getAction()))
{
int conversationId =
intent.getIntExtra(
MyMessagingService.CONVERSATION_ID, -1);
if (conversationId != -1) {
Log.d(TAG,
"Conversation " +conversationId+" was read");
NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(context);
notificationManager.cancel(conversationId);
}
}
}
}
Listing 9.5 contains boilerplate code, followed by the contents of the custom
class MessageReadReceiver that extends the BroadcastReceiver
class. As you know from Chapter 8, a Broadcast Receiver must imple-
ment the onReceive() method that is invoked when the associated
event occurs.
The code compares the action specified in the intent that is the second
argument of the onReceive() method with the value of MyMessaging.
READ_ACTION (not shown here). If they match, then the extra data of the
same intent is retrieved: if this value differs from -1, then the current
notification is cancelled.
of the original Android SDK, and can also be integrated with Firebase.
Android Things enables you to develop Android applications for various
boards that are listed here: https://developer.android.com/things/hard-
ware/developer-kits.html.
Supported devices include Intel Edison Arduino, Intel Edison Sparkfun,
NXPixo, and Raspberry PI. You must use API Level 24 or higher and a
device with Android 7.x. When you create an application (discussed later)
you must update build.gradle as follows:
dependencies {
...
provided 'com.google.android.
things:androidthings:0.1-devpreview'
}
You must also add the things shared library entry in AndroidManifest.
xml:
<application ...>
<uses-library android:name="com.google.android.things"/>
...
</application>
The supported boards and the GPIO pins are assumed on each board.
Content Providers
Android Things does not include the standard suite of system applications
and content providers. In particular, avoid using common intents as well
as the following content provider APIs in applications:
CalendarContract
ContactsContract
DocumentsContract
DownloadManager
MediaStore
Settings
Telephony
UserDictionary
VoicemailContract
For ease of development, this same activity should include the intent fil-
ter CATEGORY_LAUNCHER so Android Studio can launch it as the default
Activity when deploying or debugging.
ANDROID VR, TV, AUTO, AND THINGS • 239
LISTING 9.6 AndroidManifest.xml
<application
android:label="@string/app_name">
<activity android:name=".HomeActivity">
<!-- Launch activity as default from Android Studio -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.
category.LAUNCHER"/>
</intent-filter>
LISTING 9.7 ButtonActivity.java
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.i(TAG, "Starting ButtonActivity");
PeripheralManagerService pioService =
new PeripheralManagerService();
240 • ANDROID POCKET PRIMER
try {
Log.i(TAG, "Configuring GPIO pins");
mLedGpio = pioService.openGpio(BoardDefaults.
getGPIOForLED());
mLedGpio.setDirection(Gpio.
DIRECTION_OUT_INITIALLY_LOW);
mButtonInputDriver.register();
} catch (IOException e) {
Log.e(TAG, "Error configuring GPIO pins", e);
}
}
LISTING 9.8 MainActivity.java
/*
* Copyright 2016, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
ANDROID VR, TV, AUTO, AND THINGS • 241
package com.example.androidthings.myproject;
import android.app.Activity;
import android.os.Bundle;import android.util.Log;
/**
* Skeleton of the main Android Things activity.
Implement your
* device's logic in this class.
*
* Android Things peripheral APIs are accessible
through the class
* PeripheralManagerService. For example, the snippet
below will
* open a GPIO pin and set it to HIGH:
*
* <pre>{@code
* PeripheralManagerService service = new
PeripheralManagerService();
* mLedGpio = service.openGpio("BCM6");
* mLedGpio.setDirection(Gpio.DIRECTION_OUT_
INITIALLY_LOW);
* mLedGpio.setValue(true);
* }</pre>
*
* For more complex peripherals, look for an existing
user-space driver, or implement one if none
* is available.
*
*/
public class MainActivity extends Activity
{
private static final String TAG =
MainActivity.class.getSimpleName();
242 • ANDROID POCKET PRIMER
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
}
Listing 9.8 contains the standard onCreate() method and the onDe-
stroy() method, both of which simply invoke their corresponding
method in the parent class.
Notice that the initial portion of Listing 9.6 contains a comment section
with the following code block:
PeripheralManagerService service =
new PeripheralManagerService();
mLedGpio = service.openGpio("BCM6");
mLedGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
mLedGpio.setValue(true);
The preceding code block opens a GPIO (General Purpose Input Output)
pin of an attached device and sets it to HIGH.
Useful Links
The IoT Starter is a sample Android application for interacting with the
IBM Watson IoT Platform, and you can download the code from this Github
repository: https://github.com/ibm-messaging/iot-starter-for-android.
More information regarding the Watson IoT Platform is here: https://
docs.internetofthings.ibmcloud.com/index.html.
If you see the preceding text string on the screen, then TensorFlow is
installed correctly on your machine.
The following Python commands access TensorFlow in order to add two
numbers:
>>> a = tf.constant(10)
>>> b = tf.constant(32)
>>> sess.run(a+b)
42
244 • ANDROID POCKET PRIMER
The following code block shows you how to define two matrices and com-
pute their product:
If you have more than one GPU available on your machine, you can assign
specific GPU beyond the first, and you must assign a value to it explicitly.
Devices are specified with strings, and the currently supported devices
are:
tensorflow.python.framework.errors.InvalidArgumentError: Cannot
assign a device to node 'MatMul_2': Could not satisfy explicit
device specification '/device:GPU:1' because no devices
matching that specification are registered in this process;
available devices: /job:localhost/replica:0/task:0/cpu:0
This machine becomes the master for the session. The master distrib-
utes the graph across other machines in the cluster (workers), much as
the local implementation distributes the graph across available computer
resources within a machine.
You can use with tf.device(): statements to directly specify workers
for particular parts of the graph:
with tf.device("/job:ps/task:0"):
weights = tf.Variable(...)
biases = tf.Variable(...)
Summary
This chapter started with a brief introduction to Android VR, with sample
videos of VR on a Pixel phone, and a discussion of how to use Cardboard,
the DayDream headset, and a Pixel phone for viewing Android VR appli-
cations. You also saw some Java classes that are part of Android VR
applications.
Then you learned about Android TV applications, which make
heavy use of Fragments and Lists. Next you saw how to create two
types of Android Auto applications: music-based and messaging-
based applications. Then you learned about Android Things (IoT)
applications.
Finally, you got an introduction to TensorFlow, along with information
for installing the TensorFlow apk to an Android device.
CHAPTER
10
FUNCTIONAL REACTIVE
PROGRAMMING
T
his chapter discusses Functional Reactive Programming (FRP) as
a segue for RxAndroid for Android applications. If you are new to
FRP, you might be surprised to discover that it originated almost
twenty years ago. In fact, Microsoft has been involved in FRP since 2009
(more or less), and early contributors to ReactiveX are current or former
Microsoft employees.
Moreover, FRP is available not only as RxAndroid for Android, but also
for fifteen or more programming languages, including JavaScript (RxJS),
Java (RxJava2), Scala (RxScala), and even Swift (RxSwift). FRP has
undergone a sort of “resurgence” recently, perhaps popularized through
the use of FRP in Angular 2 and (to a lesser extent) ReactJS. FRP argua-
bly has the potential to become a significant technology in the near future,
which is the rationale for this chapter.
If you plan to use FRP in Android mobile applications, the good news
is that RxAndroid is an Android-specific extension of RxJava2, so the
knowledge that you gain from the RxJava2 material in this chapter will
serve you well. If you are already familiar with the material in any of the
preliminary sections, feel free to skim those sections.
The first part of this chapter provides a very brief (and equally fast) “dive”
into operators and then an analogy to help you understand Observables.
The purpose of this section is for readers who have never worked with an
Observable, and there’s a decent chance that the other portions of this
chapter will become easier to understand.
248 • ANDROID POCKET PRIMER
If this analogy has triggered a “lightbulb moment” for you regarding inter-
mediate operators and terminal operators, the good news is that many of the
code samples in this chapter will be much simpler to understand. If you still
aren’t sure, think of the first three “requests” (by the mother) as analogous
to intermediate operators. The mother’s final statement acts like a terminal
operator, which then results in the execution of the first three “requests.”
With these observations in mind, let’s rewind to the beginning with
Lambda Expressions in Java8, which is the topic of the next section.
showName();
}
}).start();
The preceding code block is very compact: the new operator creates
an instance of the Thread class, and the run() method invokes the
showName() method (defined elsewhere), after which the start()
method is invoked in order to execute the Thread.
In Java8 you can replace the preceding code block with this code snippet:
The preceding code snippet uses the new Java8 syntax for Lambda
Expressions and, as you can see, the code is much shorter and sim-
pler than the first code block that contains an anonymous inner class. In
addition, the Java compiler performs the necessary code generation and
compilation “behind the scenes.”
Notice that both code blocks reference a method showName() that is
defined elsewhere. In fact, Listing 10.1 displays the contents of the class
Lambda1.java that contains both of the preceding code blocks and also
an example of the showName() method.
LISTING 10.1 Lambda1.java
public Lambda1() {}
Listing 10.1 contains two code blocks, both of which were discussed ear-
lier in this section (please read them if you have not already done so).
Functional Interfaces
A functional interface is a Java interface that contains a single abstract
method. For example, the Runnable interface consists only of the run()
method. In case you are new to Java programming, Java8 introduced
default methods (which are not abstract) in Java8 interface definitions,
and also introduced interfaces with static methods.
In addition, Java8 and higher support the @FunctionalInterface
annotation (in order to support Lambda Expressions), an example of
which is here:
@FunctionalInterface
public interface MyInterface {
int doSomething();
}
Type Inferencing
Java8 and higher also support type inferencing, which means that the
compiler can often infer the parameter types in an expression. Java8 sup-
ports type inferencing for Lambda Expressions, an example of which
is here:
Method References
Method references enable the reuse of a method as a Lambda
Expression. Method references use a double colon (“::”) instead of an
arrow (“->”) syntax that you see in definitions of Lambda Expressions
(an example is given later in the chapter).
There are three types of method references in Java8:
a static method
an instance method of an existing type
an instance method of an arbitrary type
As an illustration of a defining a static method reference, suppose that
you want to convert a given string to an integer. The latter can be accom-
plished via the parseInt() method of the Integer class. The Lambda
Expression for the conversion has a very simple definition, as shown
here:
Stream Operators
A Stream supports two types of operators: intermediate (“eager”) opera-
tors and terminal (“lazy”) operators. Terminal operators minimize mem-
ory consumption, so they are well-suited for large data streams. On the
other hand, intermediate operators are well-suited for performance, but
they also consume large amounts of memory. An intermediate operator
manipulates the data in the stream, after which subsequent operators (if
any) can perform additional data manipulation.
For example, suppose that a data stream consists of positive integers.
The filter() operator enables you to “extract” only the numbers
that meet some criteria that is expressed via conditional logic. Simple
examples of conditional logic include determining whether or not a
number is an even number, or a multiple of 7, or a multiple of 3 that
is also a multiple of 10, and so forth. The conditional logic can be
placed in a single filter() operator, or split into multiple filter()
invocations.
Intermediate operators are executed after a terminal operator is
NOTE invoked.
The following stream illustrates how to define a filter() operator to
retrieve only even numbers:
In the preceding code snippet, the stream() operator and the filter()
operator are intermediate operators, and the forEach() operator is the
terminal operator. When we discuss Observables, you will see that the
filter() operator is also available, and the subscribe() method is the
terminal operator instead of the forEach() operator.
FUNCTIONAL REACTIVE PROGRAMMING • 255
Marble Diagrams
“Marble diagrams” are popular because of their visually oriented rep-
resentation of the execution sequence of intermediate operators in FRP.
An example of a marble diagram involving the filter() operator is here:
http://rxmarbles.com/#filter.
If you prefer marble diagrams, then by all means study them for the other
operators that are co-located in the preceding link.
Infinite Streams
Infinite Streams are streams that do not “impose” a terminal value. An
example of such a stream is here:
System.out.println(
Stream.iterate(1, e -> e+1)
.filter(e -> e > 40)
.findFirst()
.orElse(3));
The preceding stream contains the iterate() operator that starts from
the integer 1 and then “maps” each integer to its successor (i.e., the inte-
ger incremented by 1). The filter() operator returns a given integer
only if its value is greater than 40.
Next the findFirst() operator returns the first value if one exists, which
in this case is the integer 41. If a first value does not exist, the orElse()
operator returns the integer 3.
The interesting point to notice is that the iterate() method can start
with initial value and return all the numbers that are greater than that
initial value (which is an infinite stream).
import java.util.Arrays;
import java.util.List;
import java.util.stream.*;
As a quick preview, this Java class contains the following stream definition:
As you will see in the Java class Streams2.java (discussed later in this
chapter), Streams can be obtained from a range of numbers, as shown
here:
IntStream.range(1,4).forEach(System.out::println);
Although the preceding code snippets might not be 100% clear, you can
probably get a sense of what they do, and you can see how to chain oper-
ators together.
LISTING 10.2 Streams1.java
import java.util.Arrays;
import java.util.List;
.sorted()
.forEach(System.out::println);
// output: a1
Arrays.asList("a1", "a2", "a3")
.stream()
.findFirst()
.ifPresent(System.out::println);
}
}
Listing 10.2 contains a main() method that starts by initializing the var-
iable myList as a list of strings. The next code block creates a Stream
from the myList array by invoking the stream() method. Next, the
Stream chains the intermediate operators filter(), map(), and sort()
that retrieve the strings which start with lowercase “b,” then converts those
strings to uppercase, and then sorts the matching strings. The terminal
operator forEach() causes the result set to be printed. The third code
block in Listing 10.2 uses the findFirst() operator to extract the first data
item from a stream of strings and then prints that data item (if it exists).
Compile and launch the code in Listing 10.2 (make sure CLASSPATH is set
correctly) and you will see the following output:
B1
B2
B3
a1
LISTING 10.3 Streams2.java
import java.util.Arrays;
import java.util.List;
import java.util.stream.*;
// #1 output: a1
Stream.of("a1", "a2", "a3")
.findFirst()
.ifPresent(System.out::println);
// #3 output: 7.5
Arrays.stream(new int[] {1, 2, 3, 4})
.map(n -> 3*n)
.average()
.ifPresent(System.out::println);
// #4 output: 3
Stream.of("a1", "a2", "a3")
.map(s -> s.substring(1))
.mapToInt(Integer::parseInt)
.max()
.ifPresent(System.out::println);
Listing 10.3 contains a main() method that defines six Streams, and each
stream is preceded with a comment line that displays the output of that
stream. For instance, stream #4 is a stream consisting of the strings a1,
a2, and a3, followed by the map() operator that “extracts” the charac-
ter in the second position of each string via the substring() function.
When the first element of the stream reaches the map() operator, the
string 1 is passed to the mapToInt() operator that returns the integer 1,
which is assigned the current maximum via the max() operator.
In a similar manner, the string a2 is processed, after which the number 2
(from the string a2) becomes the new maximum value, and then the num-
ber 3 becomes the new (and final) maximum value after the third stream
element a3 is processed.
Compile and launch the code in Listing 10.3 and you will see the output
that is listed in the comments in Listing 10.3.
FUNCTIONAL REACTIVE PROGRAMMING • 261
LISTING 10.4 StreamsFilter1.java
import java.util.Arrays;
import java.util.List;
import java.util.stream.*;
Listing 10.4 contains three Streams that are preceded with a comment
line that indicates the output of each Stream. Compile and launch the
code in Listing 10.4 and you will see that the output is the concatenation
of the output listed in the comment lines.
So far you have seen examples of chaining intermediate operators
together. Due to their simplicity, performance was not an issue. However,
performance can become an important consideration in more complex
Streams, and the following article (along with its accompanying code)
provides some insight regarding filters and streams:
https://dzone.com/articles/
single-filter-perform-better-than-multiple-one-in
https://github.com/rokon12/stream-filter-benchmark
As you learned earlier, Java8 Streams are processed synchronously.
However, the JDK9 Flow API processes streams asynchronously,
which makes it well-suited for reactive programming, and also pro-
vides methods such as Flow.Publisher and Flow.Subscription.
In case you are interested in a comparison of JDK9 Flow API ver-
sus RxJava Observables in order to see the differences, the fol-
lowing link is useful: http://stackoverflow.com/questions/30216979/
difference-between-java-8-streams-and-rxjava-observables.
FUNCTIONAL REACTIVE PROGRAMMING • 263
What Is RxJava2?
RxJava2 is a library of APIs for asynchronous and event-based programs
using observable sequences for the Java VM. RxJava was created as a
port from Netflix and is available with an Apache 2.0 License, and its
home page is here: https://github.com/ReactiveX/RxJava/tree/2.x.
Note that the preceding link is the Github repository for RxJava2, which
supersedes RxJava 1.x. If you are using an older version of RxJava, it’s
probably a good idea to plan on updating your code. Fortunately, sup-
port for version 1.x will be available for several years, according to the
RxJava2 documentation.
compile 'io.reactivex.rxjava2:rxjava:x.y.z'
Operators
Observables support various operators that can be chained together in
order to transform data that is emitted by an Observable, and the des-
tination of the transformed data is a subscriber. Operators are methods
in Observables that enable you to compose new observables and also to
create custom operators based on RxJS operators. Examples of interme-
diate operators include filter(), map(), reduce(), merge(), and
flatMap(), and you will see code samples involving these operators later
in this chapter.
LISTING 10.5 RxObservable1.java
import java.util.Arrays;
import java.util.List;
import rx.Observable;
import rx.Observer;
listObservable.subscribe(new
Observer<List<String>>() {
@Override
public void onCompleted() {}
@Override
public void onError(Throwable e) {}
@Override
public void onNext(List<String> list) {
System.out.println(list);
}
});
}
}
Listing 10.5 initializes the list variable as a List of the 3 strings one,
two, and three, followed by the listObservable variable that is an
Observable created from the list variable.
LISTING 10.6 MyObservableMapJ.java
import rx.Observable;
import rx.functions.Func1;
import java.util.List;
import rx.functions.Action1;
import rx.Subscriber;
import java.util.concurrent.TimeUnit;
{
public static void main(String[] args)
{
Observable
.interval(200, TimeUnit.MILLISECONDS)
.take(10)
.map(new Func1<Long, String>() {
@Override
public String call(Long x) {
return "x = "+x+" "+x+"*10 = "+(x*10);
}
})
.toBlocking()
.forEach(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
}
});
}
}
LISTING 10.7 MyObservableMap2J.java
import rx.Observable;
import rx.functions.Func1;
import java.util.List;
import rx.functions.Action1;
import rx.Subscriber;
import java.util.concurrent.TimeUnit;
0: Multiple of 3
1: NEITHER
2: NEITHER
3: Multiple of 3
4: NEITHER
5: Multiple of 5
6: Multiple of 3
7: NEITHER
8: NEITHER
9: Multiple of 3
Exercise: Change Listing 10.7 to take 30 numbers and to display the numbers
that are multiples of 3 and 5 before the numbers that are multiple of 3 or 5.
LISTING 10.8 RxJavaOperators1.java
import java.util.Arrays;
import java.util.List;
import rx.Observable;
Observable.from(list)
.filter(s -> s.contains("e"))
.map(s -> s.toUpperCase())
.reduce(new StringBuilder(),
StringBuilder::append)
.subscribe(System.out::print,
e -> {},
() -> System.out.println("!")
);
}
}
270 • ANDROID POCKET PRIMER
Listing 10.8 creates an Observable from a list of three strings, and then
invokes several intermediate operators. The from()operator converts
other objects and data types into Observables, which in this example cre-
ates an Observable from the variable list. Next, the filter() operator
returns a string if it contains the letter e, followed by the map()operator
that returns the result of converting the string to uppercase letters.
Next the reduce() operator uses an instance of the Java StringBuilder
class and invokes its append() method in order to concatenate the
strings that it “receives.” Finally, the subscribe() operator invokes the
print() method of the Java System.out class in order to display the
result, which is shown here:
HELLOWORLDRXJAVA!
specify the capacity of the buffer, and an Observable will terminate with
an error in the event of a buffer overflow.
More information is here: http://reactivex.io/RxJava/javadoc/rx/
Observable.html#onBackpressureBuffer(long,%20rx.functions.Action0).
An RxMarbles diagram is here: http://rxmarbles.com/#pausable
Buffered.
The key point to understand is that an Observer controls the rate (and
stop/start points) at which cold Observables can produce items.
By contrast, hot Observables generate items immediately and at their own
pace, which can result in back pressure. For instance, a simple example of
a hot Observable involves mouse-related events, which are generated by
users (and can be extremely rapid), and hence are not under the control
of any Observers. As an additional point, note that the hot Observable
for a live online presentation (discussed in the previous section) does not
create back pressure.
This concludes the portion of the chapter regarding Observables. There
are many other intermediate operators available (such as flatMap, merge,
and concat) that you can read about in the online documentation.
What Is RxBinding?
RxBinding is a set of libraries for handling UI events in a fashion that
is similar to RxJava. By way of comparison, consider the following code
block, which is a typical way to handle a button-click event:
The following code block illustrates how to rewrite the preceding code
block using RxBinding:
@Override
public void call(Void aVoid) {
// do something
}
});
// make sure to unsubscribe the subscription
Keep in mind the following points. First, avoid the use of weak references
(read the documentation for a detailed explanation). Second, the packages
and classes in each RxBinding library have a corresponding counterpart
in Android. For example, the package android.widget.* contains
views and widgets, and their RxBinding counterparts are located in the
package com.jakewharton.rxbinding.widget.*.
Finally, RxBinding also provides libraries for the support libraries, an
example of which is shown here:
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
compile 'com.jakewharton.
rxbinding:rxbinding-design:0.4.0'
LISTING 10.9 MyRxBinding.java
package com.example.oswaldcampesato2.myrxbinding;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Button;
import com.jakewharton.rxbinding.view.RxView;
import rx.Subscription;
import rx.functions.Action1;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_rx_binding);
Subscription buttonSub =
RxView.clicks(btn).subscribe(new
Action1<Void>() {
@Override
public void call(Void aVoid) {
Log.i(TAG, "Inside call method");
}
});
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
What Is RxAndroid?
RxAndroid is an extension of RxJava that provides additional function-
ality, and its home page is here: https://github.com/ReactiveX/RxAndroid.
274 • ANDROID POCKET PRIMER
As this book goes to print the latest version for the RxAndroid JAR files is
0.25, which might be different when you download this zip file.
After uncompressing jar_files.zip, add the JAR files to the CLASSPATH
NOTE environment variables in order to compile the code samples that are pre-
sented later in this chapter.
Use FRP in Android for the scenario in which users rotate their Android
device from portrait to landscape mode while a currently running Android
application is executing a long-running task.
compile 'io.reactivex:rxandroid:1.1.0'
compile 'io.reactivex:rxjava:1.1.0'
import rx.Observable;
import rx.functions.Action1;
The following subsections show you how to use the RxAndroid opera-
tors just(), filter(), and map() in an Android application. For your
FUNCTIONAL REACTIVE PROGRAMMING • 275
LISTING 10.10 MainActivity.java
package com.example.rxandroid1;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import rx.Observable;
import rx.functions.Action1;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Observable<String> myObservable =
Observable.just("Hello");
myObservable.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.i(TAG, "s = "+s);
}
});
}
}
myObservable2.subscribe(new Action1<Integer>() {
@Override
public void call(Integer i) {
Log.i(TAG, "i = "+String.valueOf(i));
}
});
Compile the modified code and launch the application, and you will see
the following output in logcat:
import rx.functions.Func1;
myObservable3
.skip(2) // Skip the first two items
.filter(new Func1<Integer, Boolean>() {
@Override
public Boolean call(Integer num ) {
return num % 2 == 0;
}
})
.subscribe(new Action1<Integer>() {
@Override
public void call(Integer i) {
Log.i(TAG, "i = "+String.valueOf(i));
}
});
Compile the modified code and launch the application, and you will see
the following output in logcat:
myObservable4
278 • ANDROID POCKET PRIMER
Compile the modified code and launch the application, and you will see
the following output in logcat:
LISTING 10.11 RxObservable1.java
import java.util.ArrayList;
import java.util.List;
import rx.Observable;
import rx.Observer;
Observable<List<String>> listObservable =
Observable.just(names);
listObservable.subscribe(new
Observer<List<String>>() {
@Override
public void onCompleted() {
System.out.println("Inside onCompleted");
}
@Override
public void onError(Throwable e) {
System.out.println("Inside onError");
}
@Override
public void onNext(List<String> names) {
System.out.println("Inside onNext names =
"+names);
}
});
}
}
Listing 10.11 contains boilerplate code and then initializes the variable
names (which is an instance of the Java ArrayList class) and populates
its contents with five user names. The next portion of Listing 10.11 ini-
tializes the the listObservable variable that is an Observable created
from the list variable.
280 • ANDROID POCKET PRIMER
RxTextView.textChanges(textInput)
.filter(text -> text.length() >= 3)
.debounce(150, TimeUnit.MILLISECONDS)
.subscribe(this::updateSearchResults);
On the other hand, the corresponding code block for RxAndroid is here:
RxTextView.textChanges(textInput)
.filter(text -> text.length() >= 3)
.debounce(150, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateSearchResults);
Studio (which requires the Kotlin plugin) and also how to convert an exist-
ing Android application to a Kotlin application.
In case Kotlin is not already installed in Android Studio on your machine,
launch Android Studio, navigate to Configure > Plugins > Browse
repositories, and search for Kotlin. Install the Kotlin plugin (which also
installs its dependencies) and then perform the following steps:
create a new Android project
open the MainActivity.java file
click on the “Help” menu
enter “Convert Java file to Kotlin file”
More information about Kotlin is here:
http://blog.gouline.net/2014/08/31/kotlin-the-swift-of-android/
http://kotlinlang.org/docs/tutorials/kotlin-android.html
https://github.com/ahmedrizwan/RxRealmRetroKotlin/tree/master
An RxKotlin Code Sample is here:
https://github.com/ReactiveX/RxKotlin.
Miscellaneous Topics
This section contains an assortment of links that might be helpful for
learning about Observables.
Examples of Observables and Click Events are here:
http://stackoverflow.com/questions/25457737/
how-to-create-an-observable-from-onclick-event-android
http://fernandocejas.com/2015/07/18/architecting-android-
the-evolution/
Working with SQLite, Android, and RxJava:
http://java.dzone.com/articles/easy-sqlite-android-rxjava
http://blog.danlew.net/2015/09/01/how-to-upgrade-to-
rxandroid-10/
Reductor is essentially Redux for Android, an example of which is here:
http://yarikx.github.io/Reductor-prologue
https://github.com/Polidea/RxAndroidBle
282 • ANDROID POCKET PRIMER
Summary
This chapter provided an introduction to FRP (Functional Reactive
Programming). Then you learned about RxJava, which is a port from
Netflix, along with some Java code samples that use FRP.
Next you learned about RxAndroid, which is an extension of RxJava.
You saw some Java code samples that rely on an RxAndroid JAR file to
use the Observable class. These code samples are convenient because
they can be launched from the command line, and you can easily “blend”
the RxAndroid-related code into your Android applications.
In addition, you learned about various operators that are supported by
Observables, such as map(), filter(), reduce(), and also how to use
Observables in RxAndroid.
INDEX
A AlertDialog, 61
AlertDialog.Builder, 61
<action> element, 28
alerts, 60–61
Action1 interface, 275–276
alignParentLeft, 24
active terminal operators, 249–250
alignParentRight, 24
Activity, 18–19
Android 3.0, 32
<intent-filter> attribute of, 27–28
Activity.
Android 4.0, 32
getApplicationContext(), 162 android.animation, 104
activity_main.xml, 21, 168, 189, android.app.Fragment, 66
209–210, 216 android.app.FragmentManager, 67
fade animation effects via XML, android.app.FragmentTransaction, 67
108–109 Android application, structure of, 5–7
gradient effects, 90–91 Android Auto
lists and array adapters, 75 applications, 233
padding-related attributes, 37–38 messaging applications, 235–236
playing a ringtone, 148 music applications, 234–235
RecyclerView, 81–82 setup and testing, 234
rotating PNG files, 99–100 android.content.ContentProvider,
working with Buttons, 45–46 168
activity_main.xml file Android designer, working in
HelloWorld, 12 layout properties, 24
meta-characters in, 14–17 RelativeLayout, 23
activity_my_fragment.xml, 68–69 Android 6.0, features, 85
activity_you_tube.xml, 151–152 Android 7 (Nougat), features in, 85–86
adapters, 35 android.gesture package, 117–118
Alarm android.graphics, 88, 94, 95
ReceiveAlarm1.java, 213–214 android.graphics.Bitmap, 88
SimpleAlarm1.java, 210–214 android.graphics.drawable, 88
284 • INDEX
G I
GestureDetector class, 117–118 id
GestureDetector. attribute, 15–16
OnDoubleTapListener, 118 of UI component, 52–53
GestureDetector. ImageButton component, 35
SimpleOnGestureListener, 118 ImageView, 33
gestures implicit Intent, 26
handling other types of, 125 runtime check for, 155–156
overview, 117–118 import statements, 20–21
getCharSequenceExtra() method, 208 Infinite Streams, 256
getContactNames() method, 167 Intent, 24–25, 208, 213
getDefaultSensor(), 136 with action, category, data, and extra,
getMinDelay(), 137 28–29
getType() method, 169 creating, 25–26
Google TensorFlow, 243–244 types of, 26–27
deploying to Android device, <intent-filter> attribute of
245–246 Activity, 27–28
launching graph in distributed Intent listeners, 194
session, 245 IntentService, 201
simple examples of, 243–245 intermediate operators, 249, 254
gradient effects, with TextView Internal Storage, 170
components, 90–92 Interpolator class, 105
gradient1.xml, gradient effects, iterate() operator, 256
91–92
graphics
J
effects in Android Canvas, 93–95 JAR file, 151
other techniques, 110–111 Java8
overview, 87–88 Lambda Expressions in, 240–252
using custom subviews for, 92–93 functional interfaces, 252
GridView, 33 method references, 253
gvr-android-sdk-master, 223, 225 type inferencing, 253
and Parallel Streams, 258
H Stream, 253–254
height attributes, 13 classes, 257–258
HelloService.java, 200 collections versus, 255–256
HelloWorld, 4–5, 21 Infinite, 256
activity_main.xml file, 7, 12 Marble diagrams, 255
INDEX • 287