501 Android Hacking Schoole
501 Android Hacking Schoole
MANNING
www.finebook.ir
50 Android Hacks
www.finebook.ir
www.finebook.ir
50 Android Hacks
CARLOS SESSA
MANNING
SHELTER ISLAND
www.finebook.ir
For online information and ordering of this and other Manning books, please visit
www.manning.com. The publisher offers discounts on this book when ordered in quantity.
For more information, please contact
Special Sales Department
Manning Publications Co.
20 Baldwin Road
PO Box 261
Shelter Island, NY 11964.
Many of the designations used by manufacturers and sellers to distinguish their products are
claimed as trademarks. Where those designations appear in the book, and Manning
Publications was aware of a trademark claim, the designations have been printed in initial caps
or all caps.
Recognizing the importance of preserving what has been written, it is Manning’s policy to have
the books we publish printed on acid-free paper, and we exert our best efforts to that end.
Recognizing also our responsibility to conserve the resources of our planet, Manning books
are printed on paper that is at least 15 percent recycled and processed without the use of
elemental chlorine.
ISBN 9781617290565
Printed in the United States of America
1 2 3 4 5 6 7 8 9 10 – MAL – 18 17 16 15 14 13
www.finebook.ir
Al milagro que hizo esto posible
www.finebook.ir
www.finebook.ir
brief contents
1 ■ Working your way around layouts 1
2 ■ Creating cool animations 19
3 ■ View tips and tricks 29
4 ■ Tools 47
5 ■ Patterns 53
6 ■ Working with lists and adapters 77
7 ■ Useful libraries 97
8 ■ Interacting with other languages 107
9 ■ Ready-to-use snippets 117
10 ■ Beyond database basics 133
11 ■ Avoiding fragmentation 157
12 ■ Building tools 171
vii
www.finebook.ir
www.finebook.ir
contents
foreword xvii
preface xix
acknowledgments xxi
about this book xxiii
about the cover illustration xxvii
ix
www.finebook.ir
x CONTENTS
www.finebook.ir
CONTENTS xi
4 Tools 47
HACK 18 REMOVING LOG STATEMENTS BEFORE RELEASING 47
4.1 The bottom line 48
4.2 External links 48
HACK 19 USING THE HIERARCHY VIEWER TOOL TO REMOVE
UNNECESSARY VIEWS 49
4.3 The bottom line 52
4.4 External links 52
5 Patterns 53
HACK 20 THE MODEL-VIEW-PRESENTER PATTERN 53
5.1 The bottom line 55
5.2 External links 56
www.finebook.ir
xii CONTENTS
www.finebook.ir
CONTENTS xiii
7 Useful libraries 97
HACK 31 ASPECT-ORIENTED PROGRAMMING IN ANDROID 97
7.1 The bottom line 100
7.2 External links 100
HACK 32 EMPOWERING YOUR APPLICATION USING
COCOS2D-X 101
7.3 What is Cocos2d-x? 101
7.4 Using Cocos2d-x 101
7.5 The bottom line 104
7.6 External links 105
www.finebook.ir
xiv CONTENTS
www.finebook.ir
CONTENTS xv
www.finebook.ir
xvi CONTENTS
index 183
www.finebook.ir
foreword
Android as an ecosystem is expanding rapidly in all directions. Every day manufactur-
ers introduce new devices and form factors, consumers purchase and activate over
one million devices, and users download and try new apps. It’s the job of developers
(yourself included, hopefully) to fill this ecosystem with beautiful, engaging, and
deeply fulfilling applications through which users can better interpret and interact
with their world.
As a platform, Android was birthed in late 2003 by former employees of Danger
(the company behind the popular Sidekick phones). In 2005 the company driving
Android was acquired by Google, and three years later the HTC Dream (G1) was
released as the first consumer device running Android. Over the next three years the
hardware and platform were heavily iterated, but Android remained solely a phone
operating system.
In 2011 Google introduced two new form factors for the Android: tablets and TV.
This represented the first official deviation from phones as the device of choice and
sparked manufacturer interest in other devices. Android now runs on laptops, wrist-
watches, video game consoles, and car stereos. It can only be expected that in the
future the number of devices supporting Android will continue to grow.
As application developers, it’s extremely important that you understand the diver-
sity of the platform and the direction in which it’s heading. Creating content on
Android is no longer as simple as designing for a phone-sized screen held in portrait
orientation. While this does mean more work for the developer creating apps, the end
result is a vastly more pleasant experience for the user, regardless of which device your
content is consumed on.
xvii
www.finebook.ir
xviii FOREWORD
In developing applications there are three major things that you’ll need aside from
your own creativity and desire to develop: the platform documentation, the open
source community, and glue to hold everything together. The platform documenta-
tion is easy, since the latest version is always hosted at http://developer.android.com.
The open source community is spread across GitHub, Google Code, Stack Overflow,
and the like, providing libraries, code snippets, and design patterns for simplifying
development. You still need something to tie these disjointed pieces together as one
cohesive app. If it were as simple as arranging a few building blocks, everyone would
be developing applications. This book is that glue.
Contained in the book are examples of how to solve common problems that arise
in Android development. Some are relatively trivial and some quite complex. What
they share, however, is being loosely or sparsely documented facets of app develop-
ment which often cause developers pain. 50 Android Hacks is not meant as a sole
resource for learning or mastering Android development, but rather exists to fill in
the cracks.
It’s a great task to craft an app that’s dynamic enough to support Android’s grow-
ing device diversity. With the knowledge provided by this book, accompanied by that
of similar print and online sources, it’s my hope that you’re more empowered to
develop and publish apps. Beyond this, while I am a developer just like you, I am also
an avid Android user and patiently await that next great application. Perhaps you will
be the one to write it.
JAKE WHARTON
ANDROID ENGINEER
www.finebook.ir
preface
I started learning about Android back in 2009. Android version 1.5 had just been
released, and it showed a lot of potential.
In July 2009, thanks to a friend living in Australia, I got my first Android-powered
device, an HTC Magic with Android version 1.5. To be honest, it processed more
slowly than I expected, but I started testing the APIs and creating apps that I wanted to
have on my cell phone. I sensed that Android would get a lot of attention and I knew
that if I managed to create an application, it would be available to a lot of people.
I was proved right—not long afterward, there was a kick-off for Android develop-
ment, which soon grew bigger and bigger. Suddenly a lot of tools and third-party
libraries supporting the Android platform emerged—everything from game frame-
works, like cocos2d-x, to build systems, like Apache Maven.
In November 2010 I was asked to review a book from Manning Publications called
Android in Practice (www.manning.com/collins/). Delving deep into Manning’s work,
it occurred to me that I could write a book about Android development using a differ-
ent approach. I wanted to imitate Joshua Bloch’s Effective Java (www.amazon.com/
Effective-Java-2nd-Joshua-Bloch/dp/0321356683), providing tips and patterns I had
learned over all my years of developing for the Android platform.
Essentially, I wanted to gather together in one book every Android tip I have
learned and provide some degree of documentation for it. That’s what 50 Android
Hacks is all about: a collection of tips gathered in the process of developing different
Android applications.
Something I enjoyed about Effective Java was that the book doesn’t have any partic-
ular order and I could read various sections, learning something different from each
xix
www.finebook.ir
xx PREFACE
of them. After some time, I would go back to the book and find a different application
for the project I was working on. I kept that in mind while writing this book. I imagine
the reader investigating a hack while going to work or before going to sleep, getting
new ideas for the project they’re working on.
I’m already using this book on my new projects, copying the sample code for cer-
tain tasks and using its examples to explain to my coworkers certain patterns. It’s
proven to be useful for myself, and I hope it will be useful for you as well.
While writing the book and samples, I set the minimum SDK to 1.6. Most of the
hacks in the book work in Android version 1.6 onward unless mentioned. You’ll
notice that there are hacks specific to the newest Android versions, but most of them
are recommendations or ideas that would work for every version. Every hack has an
icon identifying the minimum SDK it will work with.
So pick a hack of interest to you from the table of contents and start reading. I
hope you learn as much reading this book as I learned writing it.
www.finebook.ir
acknowledgments
When reading acknowledgments in other books, I’m always surprised by the number
of people the author thanks. I now understand how big the list can be, and as I write
these words I’m nervous that I may be forgetting someone.
First of all, I want to thank Cynthia Kane, my development editor. She helped me
manage the book. She pointed out every single thing that needed a change, dealt with
my inadequacies in English, and helped me understand the key parts of creating a
book. Almost every single line I wrote needed a fix, and while it was sometimes frustrat-
ing for Cynthia, the result of these repeated iterations is a book of which I am proud.
Another key player was Nicholas Chase. Nick is in charge of support for the Man-
ning XML schema and the authoring tool. Fortunately, Nick was online on Skype every
time I had an question for him.
The rest of the Manning team also played a big part. Some of the people who
worked with me are Ozren Harlovic, Kevin Sullivan, Tara McGoldrick Walsh, Benja-
min Berg, Katie Tennant, Candace Gillhoolley, Martin Murtonen, Michael Stephens,
and Maureen Spencer.
Thanks to the collaborators: William Sanville (Hack 40: Last-in-first-out image
loading; and Hack 41: Building databases with ORMLite); Chris King (Hack 26: Add-
ing section headers to a ListView); and Christopher Orr (Hack 50: Using Jenkins to
deal with device diversity). They lent their expertise to complete these areas.
Thanks to Cyril Mottier, who took an in-depth look at the book and didn’t hesitate
to tell me which parts he hated and wanted to change. He kept the bar very high and
I enjoyed working with him. Merci beaucoup!
xxi
www.finebook.ir
xxii ACKNOWLEDGMENTS
www.finebook.ir
about this book
Android is a project with a lot of momentum. The first Android release happened on
September 23, 2008, and by the end of 2010 it had become the leading smartphone
platform.
Every time there’s a new release, a new set of APIs and possibilities show up. While
Android version 1.5 (Donut) only worked in the HTC Dream, right now Android runs
in many devices from cellphone to TVs, and on different sizes of tablets and laptops.
This causes two big problems when developing for Android. First, you have to deal
with different types of supported devices. While there are lots of ways of dealing with
different screen sizes and screen density, you need to create an app that works, and
looks great, in every device. Also, targeting every possible Android-powered device
might result in different user experiences. The user won’t interact in the same way
with a cellphone as with a TV.
The second problem is how long the Android versions stay alive. The story is always
the same: with a new Android version, we get new APIs. A new API would be an excel-
lent addition to your app, but as a developer you still need to support older versions,
because not everyone will get the update and also because it may take a lot of time to
reach your main target audience.
You’ll need to choose if you want to add the new API functionality and release an
app just for people using the newest Android version, or go with a hybrid approach
where some functionalities are only available in newer versions.
I’ve created this book to help you out, because when you’re developing for
Android, all the decisions are in your hands. 50 Android Hacks offers a problem/solu-
tion approach to tasks you might encounter while developing, but also ways to
enhance what’s already there.
xxiii
www.finebook.ir
xxiv ABOUT THIS BOOK
What is Android?
Android is an open source operating system based on Linux. In the beginning, it was
just for cell phones, but now it works on tablets, TVs, computers, and even car stereos.
It has been gaining a lot of momentum in the mobile scene and is now used in more
than 50% of mobile devices.
The apps that run on an Android-powered device are usually coded in Java and it
has a powerful SDK that allows the developer to create different types of applications.
Android allows developers to customize almost everything. For example, you can cre-
ate custom wallpapers, custom keyboards, and custom home screens, things you
wouldn’t imagine doing in other platforms.
Roadmap
While the book is flexible enough to let you go forward and backward between hacks
without an issue, you can also read it sequentially.
■ Chapter 1, “Working your way around layouts,” has four hacks that offer you dif-
ferent layout tips.
■ The four hacks in chapter 2, “Creating cool animations,” describe different tips
for dealing with animations.
■ Chapter 3, “View tips and tricks,” has nine hacks covering every tip related to
views.
■ The two hacks in chapter 4, “Tools,” give you an overview of available tools apart
from the IDE.
www.finebook.ir
ABOUT THIS BOOK xxv
■ Chapter 5, “Patterns,” offers pattern examples in its four hacks that are applica-
ble for Android.
■ Chapter 6, “Working with lists and adapters,” groups tips about the ListView
and Adapter classes in its seven hacks.
■ Two hacks in chapter 7, “Useful libraries,” explain how to use third-party librar-
ies in your apps.
■ Chapter 8, “Interacting with other languages,” shows some examples of coding
for Android in programming languages other than Java in one hack focused on
Objective-C and one hack discussing Scala.
■ Chapter 9, “Ready-to-use snippets,” offers six hacks that provide copy-and-paste
code snippets.
■ The three hacks in chapter 10, “Beyond database basics,” state some advanced
tips about database usage.
■ Chapter 11, “Avoiding fragmentation,” includes four hacks that show how to
make your app work in different Android versions.
■ The final three hacks presented in chapter 12, “Building tools,” include tips on
how to build your app.
Author Online
The purchase of 50 Android Hacks includes free access to a private web forum run by
Manning Publications, where you can make comments about the book, ask technical
questions, and receive help from the author and from other users. To access the forum
and subscribe to it, point your web browser to www.manning.com/50AnroidHacks.
www.finebook.ir
xxvi ABOUT THIS BOOK
This page provides information on how to get on the forum once you are registered,
what kind of help is available, and the rules of conduct on the forum.
Manning’s commitment to our readers is to provide a venue where a meaningful
dialogue between individual readers and between readers and the author can take
place. It is not a commitment to any specific amount of participation on the part of
the author, whose contribution to the forum remains voluntary (and unpaid). We sug-
gest you try asking the author some challenging questions lest his interest stray!
The Author Online forum and the archives of previous discussions will be accessi-
ble from the publisher’s website as long as the book is in print.
www.finebook.ir
about the cover illustration
The figure on the cover of 50 Android Hacks is captioned “A Woodsman.” The illustra-
tion is taken from a nineteenth-century edition of Sylvain Maréchal’s four-volume
compendium of regional dress customs published in France. Each illustration is finely
drawn and colored by hand. The rich variety of Maréchal’s collection reminds us viv-
idly of how culturally apart the world’s towns and regions were just 200 years ago. Iso-
lated from each other, people spoke different dialects and languages. On the streets
or in the countryside, it was easy to identify where they lived and what their trade or
station in life was just by their dress.
Dress codes have changed since then and the diversity by region, so rich at the
time, has faded away. It is now hard to tell apart the inhabitants of different conti-
nents, let alone different towns or regions. Perhaps we have traded cultural diversity
for a more varied personal life—certainly for a more varied and fast-paced technolog-
ical life.
At a time when it is hard to tell one computer book from another, Manning cele-
brates the inventiveness and initiative of the computer business with book covers
based on the rich diversity of regional life of two centuries ago, brought back to life by
Maréchal’s pictures.
xxvii
www.finebook.ir
www.finebook.ir
Working your way
around layouts
In this chapter, we’ll cover tips and recommendations for Android layouts. You’ll
learn how to create certain types of layouts from scratch as well as how to improve
upon existing ones.
www.finebook.ir
2 CHAPTER 1 Working your way around layouts
Figure 1.1 Button with 50% of Figure 1.2 Button with 50% of its parent width (landscape)
its parent width (portrait)
www.finebook.ir
Using lazy loading and avoiding replication 3
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFFFF"
android:gravity="center"
android:orientation="horizontal" B Reads the
android:weightSum attribute
android:weightSum="1">
The LinearLayout reads the android:weightSum attribute B and learns that the sum
of the weights of its children needs to be 1. Its first and only child is the Button and
because the button has its android:layout_width set to 0dp C, the LinearLayout
knows that it must decide the button’s width by the available space given by the
android:weightSum. Because the Button has the android:layout_weight set to 0.5
D, it will use exactly 50% of the available space.
A possible example would be a 200dp wide LinearLayout with its
android:weightSum set to 1. The width of the Button would be calculated as follows:
Button's width + Button's weight * 200 / sum(weight)
Because the Button’s width is 0dp, the Button’s weight is 0.5. With the sum(weight)
set to 1, the result would be the following:
0 + 0.5 * 200 / 1 = 100
When you’re creating complex layouts, you may find yourself adding a lot of View-
Groups and Views. But making your view hierarchy tree taller will also make it slower.
www.finebook.ir
4 CHAPTER 1 Working your way around layouts
</RelativeLayout/>
In this first example, we’ve used the <include /> tag with the only required layout.
You might be thinking, “OK, this works because we’re using a RelativeLayout for our
main XML. What’ll happen if one of the XML files is a LinearLayout? android
:layout_alignParentBottom="true" wouldn’t work because it’s a RelativeLayout
attribute.” That’s true. Let’s look at the second way to use includes, where we’ll place
android:layout_* attributes in the <include /> itself.
www.finebook.ir
Using lazy loading and avoiding replication 5
The following modified main.xml uses the <include /> tag with android:layout_*
attributes:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center_horizontal"
android:text="@string/hello"/>
<include
layout="@layout/footer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="30dp"/>
</RelativeLayout/>
In this second example, we’ve let the container of the included footer decide where to
place it. Android’s issue tracker has reported an issue, which says that the <include />
tag is broken (overriding layout params never works). This is partially true. The prob-
lem is that the <include /> tag must specify both android:layout_width and
android:layout_height if we want to override any android:layout_* attributes.
Note a small detail about what we’ve done in this hack. As you can see in the sec-
ond example, we moved every android:layout_* attribute to the <include /> tag.
Take a look at the width and height we placed in the footer.xml file: they’re both 0dp.
We did this to make users specify a width and height when used together with the
<include /> tag. If users don’t add them, they won’t see the footer because the width
and height are zero.
www.finebook.ir
6 CHAPTER 1 Working your way around layouts
If the venue doesn’t have GPS information, you can’t place a marker on the map, and
if the user doesn’t need the map, why load it? Let’s place the MapView inside a View-
Stub and let the user decide whether to load the map.
To achieve this, you’ll use the following layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/show_map"
android:onClick="onShowMap"/>
<ViewStub
android:id="@+id/map_stub"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout="@layout/map"
android:inflatedId="@+id/map_view"/>
</RelativeLayout>
It might be obvious, but we’ll use the map_stub ID to get the ViewStub from the
Activity, and the layout attribute tells the ViewStub which layout should inflate. For
this example, we’ll use the following layout for the map:
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.maps.MapView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
android:apiKey="my_api_key"/>
The last attribute we need to discuss is inflatedId. The inflatedId is the ID that the
inflated view will have after we call inflate() or setVisibility() in the ViewStub
class. In this example, we’ll use setVisibility(View.VISIBLE) because we won’t do
www.finebook.ir
Using lazy loading and avoiding replication 7
anything else with the MapView. If we want to get a reference to the view inflated, the
inflate() method returns the view to avoid a second call to findViewById().
The code for the Activity is simple:
public class MainActivity extends MapActivity {
private View mViewStub;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mViewStub = findViewById(R.id.map_stub);
}
...
As you can see, we only need to change the ViewStub visibility when we want to show
the map.
www.finebook.ir
8 CHAPTER 1 Working your way around layouts
<View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#FF0000" />
<View
android:layout_width="100dp"
android:layout_height="150dp"
android:layout_marginLeft="30dp"
android:layout_marginTop="20dp"
android:background="#00FF00" />
<View
android:layout_width="100dp"
android:layout_height="150dp"
android:layout_marginLeft="60dp"
android:layout_marginTop="40dp"
android:background="#0000FF" />
</RelativeLayout>
</FrameLayout>
www.finebook.ir
Creating a custom ViewGroup 9
You can use custom attributes to customize the position of the ViewGroup chil-
dren.
The XML will be easier to understand because it’ll be more concise.
If you need to change the margins, you won’t need to recalculate by hand every
child’s margin.
Let’s take a look at how Android draws views.
www.finebook.ir
10 CHAPTER 1 Working your way around layouts
android:layout_height="150dp"
android:background="#FF0000" />
<View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#00FF00" />
<View
android:layout_width="100dp"
android:layout_height="150dp"
android:background="#0000FF" />
</com.manning.androidhacks.hack003.view.CascadeLayout>
</FrameLayout>
Now that you know what you need to build, let’s get started. The first thing we’ll do is
define those custom attributes. To do this, we need to create a file called attrs.xml
inside the res/values folder, with the following code:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CascadeLayout">
<attr name="horizontal_spacing" format="dimension" />
<attr name="vertical_spacing" format="dimension" />
</declare-styleable>
</resources>
We’ll also use default values for the horizontal and vertical spacing for those times
when the user doesn’t specify them. We’ll place the default values inside a dimens.xml
file inside the res/values folder. The contents of the dimens.xml file are as follows:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="cascade_horizontal_spacing">10dp</dimen>
<dimen name="cascade_vertical_spacing">10dp</dimen>
</resources>
After understanding how Android draws views, you might imagine that you need to
write a class called CascadeLayout that extends ViewGroup and overrides the
onMeasure() and onLayout() methods. Because the code’s a bit long, let’s analyze it
in three separate parts: the constructor, the onMeasure() method, and the
onLayout() method. The following code is for the constructor:
public class CascadeLayout extends ViewGroup {
www.finebook.ir
Creating a custom ViewGroup 11
R.styleable.CascadeLayout_horizontal_spacing,
getResources().getDimensionPixelSize(
R.dimen.cascade_horizontal_spacing));
mVerticalSpacing = a.getDimensionPixelSize(
R.styleable.CascadeLayout_vertical_spacing,
getResources()
.getDimensionPixelSize(
R.dimen.cascade_vertical_spacing));
} finally {
a.recycle();
}
...
Before coding the onMeasure() method, we’ll create a custom LayoutParams. This
class will hold the x,y position values of each child. We’ll have the LayoutParams class
as a CascadeLayout inner class. The class definition is as follows:
public static class LayoutParams extends ViewGroup.LayoutParams {
int x;
int y;
www.finebook.ir
12 CHAPTER 1 Working your way around layouts
lp.x = width;
Inside the LayoutParams,
lp.y = height; hold x and y positions for
width += child.getMeasuredWidth(); each child.
height += mVerticalSpacing;
Uses calculated }
width and width += getPaddingRight();
height to set height += getChildAt(getChildCount() - 1).getMeasuredHeight()
measured
+ getPaddingBottom();
dimensions of
whole layout. setMeasuredDimension(resolveSize(width, widthMeasureSpec),
resolveSize(height, heightMeasureSpec));
The last step is to create the onLayout() method. Let’s look at the code:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
As you can see, the code is dead simple. It calls each child layout() method using the
values calculated inside the onMeasure() method.
Because the attribute name starts with layout_ instead of containing a View attribute,
it’s added to the LayoutParams attributes. We’ll read this new attribute inside the
LayoutParams constructor as we did with the ones from CascadeLayout. The code is
the following:
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CascadeLayout_LayoutParams);
try {
verticalSpacing = a.getDimensionPixelSize(
www.finebook.ir
Creating a custom ViewGroup 13
R.styleable.CascadeLayout_LayoutParams_layout_vertical_spacing,
-1);
} finally {
a.recycle();
}
}
...
if (lp.verticalSpacing >= 0) {
verticalSpacing = lp.verticalSpacing;
}
...
width += child.getMeasuredWidth();
height += verticalSpacing;
www.finebook.ir
14 CHAPTER 1 Working your way around layouts
android:key="pref_first_preferencescreen_key"
It’s good android:title="Preferences">
practice to give
preferences an <PreferenceCategory We can use a PreferenceCategory to
android:key. android:title="User"> separate preferences by certain
With that key group names.
we’re able to <EditTextPreference
retrieve the android:key="pref_username"
preferences android:summary="Username"
To pick a username, we’ll
object. android:title="Username"/> use an EditTextPreference.
</PreferenceCategory> A summary is set, but
we’ll replace it with the
<PreferenceCategory username the user picked.
android:title="Application">
We’ll use a Preference
<Preference
for options that will
launch an Intent. android:key="pref_rate"
android:summary="Rate the app in the store!"
android:title="Rate the app"/>
www.finebook.ir
Preferences hacks 15
<Preference
android:key="pref_share"
android:summary="Share the app with your friends"
android:title="Share it"/>
<com.manning.androidhacks.hack004.preference.EmailDialog
android:dialogIcon="@drawable/ic_launcher"
android:dialogTitle="Send Feedback"
android:dialogMessage="Do you want to send an email?"
android:key="pref_sendemail_key"
android:negativeButtonText="Cancel"
android:positiveButtonText="OK"
android:summary="Send your feedback by e-mail"
android:title="Send Feedback"/>
<com.manning.androidhacks.hack004.preference.AboutDialog
android:dialogIcon="@drawable/ic_launcher"
android:dialogTitle="About"
android:key="pref_about_key" Inside preferences, we
android:negativeButtonText="@null" can also create custom
android:title="About"/> preferences to extend one
</PreferenceCategory>
of the existing widgets.
</PreferenceScreen>
The XML we’ve created will take care of the UI. Now it’s time to add all of the logic. To
do this, we’ll create an Activity, but instead of extending android.app.Activity,
we’ll extend android.preference.PreferenceActivity. The code follows:
public class MainActivity extends PreferenceActivity implements
OnSharedPreferenceChangeListener {
Instead of calling
@Override setContentView(), we need
public void onCreate(Bundle savedInstanceState) { to call addPreferences-
super.onCreate(savedInstanceState); FromResource with XML
addPreferencesFromResource(R.xml.prefs); we created previously.
...
getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
Register to be
} notified of
preferences
@Override changes.
protected void onPause() {
super.onPause();
www.finebook.ir
16 CHAPTER 1 Working your way around layouts
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
Unregister to
} preferences
@Override changes.
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
if (key.equals("pref_username")) {
When there’s a change in
updateUserText();
username preference, we
} need to update preference
} summary.
private void updateUserText() {
EditTextPreference pref;
pref = (EditTextPreference) findPreference("pref_username");
String user = pref.getText();
To update summary, we need to get
if (user == null) { preference and update summary using
user = "?"; EditTextPreference’s getText() method.
}
The code we want to create shows how to create custom preferences. It works as if we
were creating a custom view. To understand it, let’s look at the following, where we
create the code for the EmailDialog class:
public class EmailDialog extends DialogPreference {
Custom class should
Context mContext;
extend some of existing
public EmailDialog(Context context) { preferences widgets. In
this(context, null); this case, we’ll use
} DialogPreference.
www.finebook.ir
Preferences hacks 17
www.finebook.ir
www.finebook.ir
Creating cool animations
In this chapter, you’ll learn about animations. You’ll find different examples that
use a variety of APIs to add animations to your application widgets.
19
www.finebook.ir
20 CHAPTER 2 Creating cool animations
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = (TextView) findViewById(R.id.your_textview);
...
mTextView.setText(“something”);
}
As you might’ve noticed, if we change the content of a TextView, it’ll change instantly;
TextSwitcher is what we need if we want to add an animation to avoid the hard swap.
A TextSwitcher is useful to animate a label onscreen. Whenever it’s called,
TextSwitcher animates the current text out and animates the new text in. We can get
a more pleasant transition by following these easy steps:
1 Get the view using findViewById(), or construct it in your code like any normal
Android view.
2 Set a factory using switcher.setFactory().
3 Set an in-animation using switcher.setInAnimation().
4 Set an out-animation using switcher.setOutAnimation().
Here’s how TextSwitcher works: it uses the factory to create new views, and whenever
we use setText(), it first removes the old view using an animation set with the set-
OutAnimation() method, and then places the new one using the animation set by the
setInAnimation() method. So let’s see how to use it:
private TextSwitcher mTextSwitcher;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Animation in = AnimationUtils.loadAnimation(this,
android.R.anim.fade_in);
Animation out = AnimationUtils.loadAnimation(this,
android.R.anim.fade_out);
@Override
public View makeView() {
TextView t = new TextView(YourActivity.this);
t.setGravity(Gravity.CENTER);
www.finebook.ir
Adding eye candy to your ViewGroup’s children 21
return t;
}
});
mTextSwitcher.setInAnimation(in);
mTextSwitcher.setOutAnimation(out);
}
That’s it. The user gets the new text, and we get some cool animations for free. The
new transition fades out the original text while the new text fades in to replace it.
Because we used android.R.anim.fade_in in our example, the effect was a fade-in.
This technique works equally well with other effects. Providing your own animation or
using one from android.R.anim. ImageSwitcher works in the same way, except with
images instead of text.
By default, when you add views to a ViewGroup, they’re instantly added and displayed,
but there’s an easier way to animate that action. In this hack, I’ll show you how to
apply an animation to children views being added to their parent ViewGroup. I’ll show
you how to add eye candy to your application in a few lines.
Android provides a class called LayoutAnimationController. This class is useful to
animate a layout’s or a ViewGroup’s children. It’s important to mention that you won’t
be able to provide different animations for each child, but the LayoutAnimation-
Controller can help you decide when the animation should apply to each child.
The best way to understand how to use LayoutAnimationController is through an
example. We’ll animate ListView’s children with a mix of two animations, alpha and
translate. You can use the LayoutAnimationController in two ways: from the code
www.finebook.ir
22 CHAPTER 2 Creating cool animations
and from the XML. I’ll show how to do it from code and you can try converting it to
XML as an exercise. Let’s look at the code used to apply the animation:
mListView = (ListView) findViewById(R.id.my_listview_id);
Get
Create set AnimationSet set = new AnimationSet(true); B ListView ref.
and use Animation animation = new AlphaAnimation(0.0f, 1.0f);
default. C Create alpha
animation.setDuration(50);
set.addAnimation(animation);
D animation.
The idea is simple: we need to provide initial and final x,y coordinates. Android pro-
vides a way to specify where it should calculate the position from, with three options:
Animation.ABSOLUTE
Animation.RELATIVE_TO_SELF
Animation.RELATIVE_TO_PARENT
If we go back to our example, we can explain every child position with words like this:
Initial X: Position provided by its parent
Initial Y: -1 from the position provided by its parent
Final X: Position provided by its parent
Final Y: Position provided by its parent
The end result will be every child “falling” through the y axis to its position. Because
we have a delay between children, it’ll look like a cascade.
www.finebook.ir
Doing animations over the Canvas 23
If you’re animating your own widgets, you might find the animation APIs a bit limited.
Is there an Android API to draw things directly to the screen? The answer is yes.
Android offers a class called Canvas.
In this hack, I’ll show you how to use the Canvas
class to draw elements and animate them by creating
a box that will bounce around the screen. You can
see the finished application in figure 7.1.
Before we create this application, let’s make sure
you understand what the Canvas class is—the follow-
ing is from the documentation (see section 7.2):
A Canvas works for you as a pretense, or
interface, to the actual surface upon which your
graphics will be drawn—it holds all of your
“draw” calls. Via the Canvas, your drawing is
performed upon an underlying Bitmap, which is
placed into the window.
Based on that definition, the Canvas class holds all
of the draw calls. We can create a View, override the
onDraw() method, and start drawing primitives
there.
To make everything more clear, we’ll create a Figure 7.1 Box bouncing around
DrawView class that will take care of drawing the box the screen
www.finebook.ir
24 CHAPTER 2 Creating cool animations
and updating its position. Because we don’t have anything else onscreen, we’ll make it
the Activity’s content view. The following is the code for the Activity:
public class MainActivity extends Activity {
We’ll use the WindowManager to get the screen width and height B. These values will
be used inside the DrawView to limit where to draw. Afterward, we’ll set the DrawView
as the Activity’s contentView C. This means that the DrawView will take all of the
available space.
Let’s take a look at what’s happening inside the DrawView class:
public class DrawView extends View {
private Rectangle mRectangle;
public int width;
public int height;
public DrawView(Context context) {
super(context);
B Plays the role
mRectangle = new Rectangle(context, this); of the box.
mRectangle.setARGB(255, 255, 0, 0);
mRectangle.setSpeedX(3);
mRectangle.setSpeedY(3);
}
We’ll first create a Rectangle instance that will play the role of the box B. The
Rectangle class also knows how to draw itself to a canvas and contains all of the bor-
ing logic regarding how to update its position to be drawn in the correct place. When
the onDraw() method gets called, we’ll change the rectangle’s position C and draw it
to the canvas D. The invalidate() call E is the hack itself. The invalidate() call is
a View’s method to force a view to draw. Placing it inside the onDraw() method means
www.finebook.ir
Slideshow using the Ken Burns effect 25
that onDraw() will be called as soon as the view finishes drawing itself. To put it differ-
ently, we’re looping over the Rectangle’s move() and onDraw() calls to create a nice
animation.
www.finebook.ir
26 CHAPTER 2 Creating cool animations
In this hack, I’ll show you how to mimic the Ken Burns effect in an image slideshow.
To do this, we’ll use a library created by Jake Wharton called Nine Old Androids. The
Nine Old Androids library lets you use the new Android 3.0 animation API in older
versions.
To create the Ken Burns effect, we’ll have a number of preset animations. These
animations will be applied randomly to an ImageView and, when the animation is fin-
ished, we’ll start another animation with the next photo. The main layout will be a
FrameLayout, and we’ll place ImageViews inside it. The layout is created with the fol-
lowing code:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mView = createNewView();
mContainer.addView(mView); Create and add ImageView.
setContentView(mContainer);
}
So far, so good. We’ll use the createNewView() to create new ImageViews and keep
track of the image we’re showing next. The next step is to create a method called
nextAnimation(). This method will take care of setting the animation and start it.
The code follows:
private void nextAnimation() {
AnimatorSet anim = new AnimatorSet();
final int index = mRandom.nextInt(ANIM_COUNT); Pick animation randomly.
switch (index) {
case 0:
anim.playTogether( Scaling
ObjectAnimator.ofFloat(mView, "scaleX", 1.5f, 1f), animation.
ObjectAnimator.ofFloat(mView, "scaleY", 1.5f, 1f));
break;
...
www.finebook.ir
Slideshow using the Ken Burns effect 27
case 3:
default:
AnimatorProxy.wrap(mView).setScaleX(1.5f); Translation
AnimatorProxy.wrap(mView).setScaleY(1.5f); B animation.
anim.playTogether(ObjectAnimator.ofFloat(mView,
"translationX", 0f, 40f));
break;
} C Set the duration, set
Activity as listener,
anim.setDuration(3000); and start it.
anim.addListener(this);
anim.start();
}
The AnimatorProxy B is a class available in the Nine Old Androids library to modify
View’s properties. The new animation framework is based on the possibility of modify-
ing View’s properties over time. The AnimatorProxy is used because on Android ver-
sions lower than 3.0 some properties had no getters/setters.
The remaining code is calling the nextAnimation() method when the animation
is finished. Remember, we set the Activity as the animation listener C? Let’s look at
the overridden method:
@Override
public void onAnimationEnd(Animator animator) { Remove old view from
mContainer.removeView(mView); container and add new one.
mView = createNewView();
mContainer.addView(mView);
That’s it. We have our Ken Burns effect running on every photo. You can try improv-
ing the sample by doing two things: adding an alpha animation when switching views
and adding an AnimationSet that pans and zooms at the same time. You can get addi-
tional ideas from the Nine Old Androids sample code.
www.finebook.ir
28 CHAPTER 2 Creating cool animations
www.finebook.ir
View tips and tricks
In this chapter, you’ll read about different hacks that use views. Most of them show
how to customize and/or tweak widgets to perform certain functionalities.
We all know that validating data in forms is boring as well as error-prone. I worked
on an Android application that used a lot of forms and had a couple of date inputs.
I didn’t want to validate the date fields, so I found an elegant way to avoid it. The
idea is to make users think they have an EditText when it’s in fact a button that will
show a DatePicker when clicked.
To make this happen, we’ll change the default background of an Android
Button to the EditText’s background. We can do this easily from the XML:
<Button android:id="@+id/details_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="@android:drawable/edit_text" />
29
www.finebook.ir
30 CHAPTER 3 View tips and tricks
@Override
public void onClick(View v) {
showDialog(DATE_DIALOG_ID);
}
});
The rest of the code sets up the DatePicker and sets the text into the Button after the
user has picked a date.
Imagine a Twitter application showing a tweet (see figure 10.1). Note the different
text styles within it. You might think that Twitter created a new custom view, but the
widget used is a TextView.
www.finebook.ir
Formatting a TextView’s text 31
In this hack, I’ll show how the TextView helps us add styled text and links.
The first thing we’ll add is the hyperlink. We can set a TextView’s text using
Html.fromHtml(). The idea is simple: we’ll use HTML for the TextView’s text. Here’s
the code:
mTextView1 = (TextView) findViewById(R.id.my_text_view_html);
String text =
"Visit <a href=\"http://manning.com/\">Manning home page</a>";
mTextView1.setText(Html.fromHtml(text));
mTextView1.setMovementMethod(LinkMovementMethod.getInstance());
Using HTML to set styles in a TextView is fine, but what does the Html.fromHtml()
method do? What does it return? It converts HTML into a Spanned object to use with a
TextView’s setText() method.
Now we’ll try something different. Instead of using HTML to format the text, we’ll
create a Spanned object using the SpannableString class. Here’s the source code:
mTextView2 = (TextView) findViewById(R.id.my_text_view_spannable);
Spannable sText = new SpannableString(mTextView2.getText());
sText.setSpan(new BackgroundColorSpan(Color.RED), 1, 4, 0);
sText.setSpan(new ForegroundColorSpan(Color.BLUE), 5, 9, 0);
mTextView2.setText(sText);
We can see the visual output of both examples in figure 10.2. The idea is simple: we
add different spans using different indexes inside the text. Using a SpannableString,
we can place different styles in different parts of the text.
www.finebook.ir
32 CHAPTER 3 View tips and tricks
When the object is created, we get the font from the assets folder and set it as the type-
face B. Now that we have a widget capable of showing text with a custom font, we’ll
take care of how the numbers will be drawn. If you check figure 11.1 you’ll notice it
can be done with two TextViews. The first one is a shadow in the back that draws
88:88:88, and the second one draws the current time.
To add the glowing effect, the TextView provides a method with the following sig-
nature:
public void setShadowLayer (float radius, float dx, float dy, int color)
This can also be accessed from the XML with the following properties: android
:shadowColor, android:shadowDx, android:shadowDy, and android:shadowRadius.
Let’s take a look on how we can apply it:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.manning.androidhacks.hack011.view.LedTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="88:88:88"
android:textSize="80sp"
www.finebook.ir
Rounded borders for backgrounds 33
android:textColor="#3300FF00"/>
Sets color to be
<com.manning.androidhacks.hack011.view.LedTextView Btransparent
android:id="@+id/main_clock_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="08:43:02"
android:textSize="80sp"
android:textColor="#00FF00" C
Text color, shadow
android:shadowColor="#00FF00" color are same
android:shadowDx="0"
android:shadowDy="0"
android:shadowRadius="10"/>
Modifies shadow radius
</RelativeLayout> D to look brighter
The first LedTextView draws the 88:88:88 in the back. The purpose of this view is
mocking the ghosting effect in old digital clocks. We’ve achieved that look by setting
the text color to be a bit transparent B. The second LedTextView shows the current
time. Note that the text color and the shadow color are the same C. We could’ve
played with the alpha as well.
Modifying the android:shadowDx and android:shadowDy values differentiates
the shadow position from the text position. The shadow radius will give the sensation
of the text being brighter. To create the glowing effect, we didn’t use the
android:shadowDx or android:shadowDy properties, but we modified the shadow
radius to make it look brighter D.
When you pick a background for your application’s widgets, you typically use images.
In general, you want to avoid the default styles, adding your own colors and shapes.
www.finebook.ir
34 CHAPTER 3 View tips and tricks
As you can see, we didn’t add any strange properties. A drawable is assigned as a back-
ground, but it’s not an image, it’s an XML file. In the drawable’s XML resides a Shape-
Drawable object. A ShapeDrawable is a drawable object that creates primitive shapes
such as rectangles. Here’s the XML for the ShapeDrawable:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#AAAAAA"/>
<corners android:radius="15dp"/>
</shape>
Apart from the radius, we defined a shape and solid color. These aren’t the only avail-
able properties; you can read the documentation (section 12.2) and see what else is
available for ShapeDrawables.
When you want to do something that depends on a widget’s width and height, you
might want to use View’s getHeight() and getWidth() methods. A common pitfall
www.finebook.ir
Getting the view’s width and height in the onCreate() method 35
for new Android developers is trying to get a widget’s width and height inside the
Activity’s onCreate() method. Unfortunately, those methods will return 0 if you call
them from there, but I’ll show you an easy way around this.
Let’s first see why we get a 0 when we ask for the view’s sizes inside the Activity’s
onCreate() method. When the onCreate() method is called, the content view is set
inflating the layout XML with a LayoutInflater. The process of inflation involves cre-
ating the views but not setting their sizes. So when does the view get assigned its size?
Let’s review what the Android documentation (see section 13.2) says:
The conclusion is the following: Views get their height and width when the layout hap-
pens. Layout happens after the onCreate() method is called, so we get a 0 when we
call getHeight() or getWidth() from it.
Imagine the XML layout as a cake recipe: the LayoutInflater would be the person
in charge of buying all of the items; the bakers would do the measuring and layout of
passes; and the view would be the cake itself. During the onCreate() method, the
ingredients will be purchases, but knowing what ingredients make up the cake isn’t
enough information to know how big the cake will end up being.
To solve this issue, we can use the View’s post() method. This method receives a
Runnable and adds it to the message queue. An interesting thing is that the Runnable
will be executed on the user interface thread. The code to use the post() call should
look like the following:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
View view = findViewById(R.id.main_my_view);
Get size of view
view.post(new Runnable() { after layout
@Override
public void run() {
Correct width
Log.d(TAG, "view has width: "+view.getWidth() + and height
" and height: "+view.getHeight());
}
});
}
www.finebook.ir
36 CHAPTER 3 View tips and tricks
www.finebook.ir
VideoViews and orientation changes 37
In figure 14.2 you can see how the view tree is created. The videoView hangs from the
root view at the same level as the portrait content. Placing the videoView there will
allows us to change its size and position without needing to use two different layouts
or changing the videoView’s parent when rotation occurs. On the other hand, the
white background view, called the portrait position, is placed deeper in the tree.
0
@43774320
TextView
id/main_portrait_position
0
0
@43773a70
@43776e50
ScrollView
@437775c8
TextView
View
2
id/main_portrait_content
LinearLayout
@43773580
@43777110
ScrollView
@43776fc0
View
1
id/main_portrait_content
0
id/main_videoview
LinearLayout
VideoView
@43778428
@43773260
View tree
0
RelativeLayout
Measure: n/a
@43772b88
Layout: n/a
Draw: n/a
10 Views
Figure 14.2
www.finebook.ir
38 CHAPTER 3 View tips and tricks
Now that we have the layout, we can take care of the Activity’s code. The first thing
to do is to enable handling the orientation changes. To do this, we need to add
android:configChanges="orientation" to the proper <Activity> element inside
AndroidManifest.xml. Adding that attribute will cause the onConfiguration-
Changed() method to be called instead of restarting the Activity when the device is
rotated.
When the orientation is changed, we need to change the video’s size and position.
For this we’ll call a private method called setVideoViewPosition(). Here’s is the con-
tent of this method:
private void setVideoViewPosition() {
if (getResources().getConfiguration().orientation ==
B Portrait
landscape
and
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { configurations
mPortraitContent.setVisibility(View.VISIBLE);
Makes
content int[] locationArray = new int[2]; D videoView
visible C mPortraitPosition.getLocationOnScreen(locationArray); position
RelativeLayout.LayoutParams params =
new RelativeLayout.LayoutParams(mPortraitPosition.getWidth(),
mPortraitPosition.getHeight());
params.leftMargin = locationArray[0];
params.topMargin = locationArray[1];
E Sets videoView’s
layout parameters
mVideoView.setLayoutParams(params);
} else {
F Hides portrait
mPortraitContent.setVisibility(View.GONE); content
RelativeLayout.LayoutParams params =
new RelativeLayout.LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT);
G Shows layout
params.addRule(RelativeLayout.CENTER_IN_PARENT); parameters we
mVideoView.setLayoutParams(params); created in videoView
}
}
The setVideoViewPosition() method is separated into two parts: the portrait and
the landscape configurations B. First, we’ll make the portrait content visible C.
Because the videoView will have the same position and size as the white view, we want
its position D to be set as the videoView’s layout parameters E.
Something similar is done in the second part, for the landscape orientation. In this
case, we first hide the portrait content F, and afterward we create the layout parame-
ters to make the videoView use the whole screen. Finally, we set the layout parameters
we’ve created to the videoView G.
www.finebook.ir
Removing the background to improve your Activity startup time 39
aspect ratio when resizing, and if you wish to make it fill the space available, you’ll
need to override the onMeasure() method in your own custom view.
www.finebook.ir
40 CHAPTER 3 View tips and tricks
Figure 15.3 Hierarchy Viewer showing the view tree without title
First, let’s remove some of the nodes by removing the title. The title is the gray bar on
top with the text that reads BackgroundTest, which is formed by a FrameLayout and a
TextView. We can delete these nodes by creating a theme.xml file under the res/
values directory with the following content:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.NoBackground" parent="android:Theme">
<item name="android:windowNoTitle">true</item>
</style>
</resources>
We can apply this theme in our Android manifest by modifying the <application>
tag and adding android:theme="@style/Theme.NoBackground" as an attribute. If we
run the application again, the title will disappear and the view tree will look like
figure 15.3.
You already know what LinearLayout and TextView are, but what about Phone-
Window$DecorView and FrameLayout?
FrameLayout is created when we execute the setContentView() method, and the
DecorView is the root of the tree. By default, the framework fills our window with a
default background color and the DecorView is the view that holds the window’s back-
ground drawable. So if we have an opaque UI or a custom background, our device is
wasting time drawing the default background color.
If we’re sure that we’ll use opaque user interfaces in our activity, we can remove
the default background to boost our startup time. To do this, we need to add a line to
the theme mentioned previously, as shown next:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.NoBackground" parent="android:Theme">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@null</item>
</style>
</resources>
www.finebook.ir
Toast’s position hack 41
should always set windowBackground to null. Remember that the theme can be set in
an <application> or an <activity> tag.
The Toast class isn’t flexible at all. For example, for the dura-
tion parameter we can only pick between Toast.LENGTH
_SHORT and Toast.LENGTH_LONG. Although there aren’t many
things we can change about Toast, what we can change is
where the pop-up is placed.
Depending on our application layout, we might want to
position the Toast somewhere else, for instance, on top of
certain views. Let’s see how to create a Toast so that it’s
shown in a different position than the default one. A working
example can be seen in figure 16.2. In the sample applica-
tion, we have four bottoms, one on each corner. When a but-
ton is clicked, a Toast is created and positioned over the
Figure 16.2 Toast with different
position
corner where the button is located.
www.finebook.ir
42 CHAPTER 3 View tips and tricks
To move the Toast around the screen, we need to create it a bit differently. It has a
public method inside the class with the following signature:
public void setGravity(int gravity, int xOffset, int yOffset);
To reproduce the Toast shown in figure 16.2 we’d need to use the following:
Toast toast = Toast.makeText(this, "Bottom Right!", Create Toast
Toast.LENGTH_SHORT);
You may find circumstances will arise when you need your users to fill out a long form.
Maybe you need to create a registration form, or your application needs some form to
upload content. In other platforms, you can create something called a wizard form,
which is a form separated in different views. But in Android, this type of widget
doesn’t exist. In this hack, we’ll use the Gallery widget to create a registration form
with many fields. The result we’re after is shown in figure 17.1.
www.finebook.ir
Creating a wizard form using a Gallery 43
For the sake of this example, we’ll create a registration form where the user will need
to fill in the following information:
Full name
Email
Password
Gender
City
Country
Postal code
We’ll have two fields per page, so in total we’ll have four pages. To create the wizard
form, we need to create an Activity called CreateAccountActivity. This Activity
will use a Theme.Dialog style to give the form the look and feel of a pop-up. Inside it
we’ll place a Gallery, which will be populated with an Adapter. The Adapter will need
to communicate with the Activity, and for that we’ll use a Delegate interface.
Let’s first create the generic view for each page. The XML follows:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="270dp"
android:layout_height="350dp">
<LinearLayout android:id="@+id/create_account_form" Inside
android:layout_width="fill_parent" LinearLayout
android:layout_height="wrap_content" you place
android:layout_alignParentTop="true" all fields.
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:paddingRight="10dp"
android:background="#AAAAAA">
<TextView At first item of
android:layout_width="wrap_content" LinearLayout you
android:layout_height="wrap_content" place form title.
android:text="Account creation"
android:textColor="#000000"
android:textStyle="bold"
android:textSize="20sp"/>
</LinearLayout>
<Button
android:id="@+id/create_account_next" Next button will
android:layout_width="wrap_content" be used to move
android:layout_height="wrap_content" forward through
android:layout_alignParentTop="true" wizard pages.
android:layout_alignParentRight="true"
android:textSize="12sp"
android:gravity="center"
android:layout_marginTop="10dp"
www.finebook.ir
44 CHAPTER 3 View tips and tricks
android:layout_marginRight="10dp"
android:text="Next"/>
</RelativeLayout>
As you can see, we placed a LinearLayout as a placeholder to every field. You’ll see
later how to populate it from the Gallery’s Adapter code.
Now that we have the XML for the generic view, we should create the Adapter’s
code. We’ll call our AdapterCreateAccountAdapter and extend from BaseAdapter.
Because the Adapter’s code is quite long, we’ll discuss only the important methods.
The first thing to write is the interface we’ll use to communicate with the Activity.
Use the following:
public static interface CreateAccountDelegate {
int FORWARD = 1;
int BACKWARD = -1;
We’ll use the scroll() method when the user presses the next button and the proc-
cessForm() method when the user submits the form. We’ll need to call the delegate
when these buttons are pressed, so we’ll want to set the click listeners in the get-
View() method, which is shown here:
public View getView(int position, View convertView, ViewGroup parent) {
convertView = mInflator.inflate(
R.layout.create_account_generic_row, parent, false);
Inflate
LinearLayout formLayout = (LinearLayout) convertView custom
.findViewById(R.id.create_account_form); view.
Get
View nextButton = convertView LinearLayout
.findViewById(R.id.create_account_next); where we’ll
if (position == FORMS_QTY - 1) { place all form
nextButton.setVisibility(View.GONE); widgets.
} else {
nextButton.setVisibility(View.VISIBLE);
Next button
}
should be visible
if (mDelegate != null) { in every page
nextButton.setOnClickListener(new OnClickListener() { but last one.
@Override
public void onClick(View v) {
www.finebook.ir
Creating a wizard form using a Gallery 45
mDelegate.scroll(CreateAccountDelegate.FORWARD);
}
});
}
Button createButton = (Button) convertView
.findViewById(R.id.create_account_create); Create button
if (position == FORMS_QTY - 1) { should be visible
createButton.setOnClickListener(new OnClickListener() { only in last page.
@Override
public void onClick(View v) {
processForm();
}
});
createButton.setVisibility(View.VISIBLE);
} else {
createButton.setVisibility(View.GONE);
}
switch (position) {
In last step, switch
case 0: over the position
populateFirstForm(formLayout); and populate
break; LinearLayout
... accordingly.
}
return convertView;
}
The code inside the populateFirstForm() is the creation of fields and titles, which
will end inside the LinearLayout. In the sample code, I decided to do everything by
code, but we could easily create the views by inflating XMLs.
The missing piece of the puzzle is the one in charge of implementing the Create-
AccountDelegate. In this case, it will be our CreateAccountActivity.
CreateAccountActivity will track the page that the user is in and it will be in
charge of the page turn logic. The code is the following:
public class CreateAccountActivity extends Activity implements
CreateAccountDelegate {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.create_account);
mGallery = (Gallery) findViewById(R.id.create_account_gallery);
www.finebook.ir
46 CHAPTER 3 View tips and tricks
@Override
protected void onResume() {
Set Activity as Adapter’s
super.onResume(); delegate in onResume()
mAdapter.setDelegate(this); method and set it to null
} when onPause() is called.
@Override
protected void onPause() {
super.onPause();
mAdapter.setDelegate(null);
}
@Override
public void onBackPressed() {
Override Activity’s
if (mGalleryPosition > 0) { onBackPressed() method
scroll(BACKWARD); so there’s a way to go back
} else { to a previous page.
super.onBackPressed();
}
}
Inside scroll() method,
@Override Activity moves Gallery to next
public void scroll(int type) { or previous page depending
switch (type) { on the parameter.
case FORWARD:
if (mGalleryPosition < mGallery.getCount() - 1) {
mGallery.onKeyDown(KeyEvent.KEYCODE_DPAD_RIGHT,
new KeyEvent(0, 0));
mGalleryPosition++;
}
break;
...
}
...
}
Unfortunately, we can’t animate the page turn in Android’s Gallery widget. The only
way I found is to send a KeyEvent.KEYCODE_DPAD_RIGHT event. It’s hacky but it works.
The remaining code of the CreateAccountActivity takes care of validations and
error handling. It contains nothing out of the ordinary, so I’ll leave it for you to read
from the sample code.
www.finebook.ir
Tools
In this chapter, we’ll look at two interesting tools you can use to create an Android
application.
If your application is making requests to a server, you might be using some type of
log to check whether or not your requests are successful. Unfortunately, those logs
don’t get removed when you build the final APK (Android application package
file). Removing logs is important to keep the logcat output as clean as possible.
Leaving log statements in could also expose you to unintentional disclosure of sen-
sitive information. In this hack, I’ll show you how easy it is to remove logs for your
market release.
Developers have their own technique preferences for removing logs from the
final release. Some prefer doing something like the following:
if (BuildConfig.DEBUG) LOG.d(TAG, "The log msg");
From my point of view, the best way to remove logs is to use the ProGuard tool. If
you’ve never used ProGuard, let me introduce it with the following quote from the
Android documentation (see section 18.2):
47
www.finebook.ir
48 CHAPTER 4 Tools
If you haven’t noticed yet, when we build an Android application we’ll find a pro-
guard.cfg file in our project root directory. Its presence there doesn’t mean it’s on by
default; we need to enable it. Fortunately, it’s simple: we need to add the following
line in the default.properties file located in our project root directory:
proguard.config=proguard.cfg
Now ProGuard is enabled, but it’ll only be used when exporting a signed APK. We
need to add the necessary lines to the proguard.cfg to get rid of those logs. Append
the following lines to proguard.cfg:
-assumenosideeffects class android.util.Log {
public static *** d(...);
}
What we’re telling ProGuard is this: remove every use of a d() method with any
amount of parameters that returns something and belongs to the android.util.Log
class. This will match with Log’s d() method and every debug log will be removed.
www.finebook.ir
Using the Hierarchy Viewer tool to remove unnecessary views 49
<RelativeLayout
android:id="@+id/slow_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
<com.test.SlowDrawView
android:id="@+id/slow_draw"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:layout_alignParentTop="true"
android:background="#FF0000"
android:text="Slow Draw"/>
www.finebook.ir
50 CHAPTER 4 Tools
<com.test.SlowLayoutView
android:id="@+id/slow_layout"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:layout_below="@id/slow_draw"
android:background="#00FF00"
android:text="Slow Layout"/>
<com.test.SlowMeasureView
android:id="@+id/slow_measure"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:layout_below="@id/slow_layout"
android:background="#0000FF"
android:text="Slow Measure"/>
</RelativeLayout>
</RelativeLayout>
This application is the default one, with some minor modifications. I’ve added three
custom views in the button and removed the title bar. Let’s load the Hierarchy Viewer
with this application. You can see the results in figure 19.2.
NOTE For now, forget the definitions for the PhoneWindow$DecorView and
the FrameLayout. Let’s say they’re nodes placed by the framework and
unmodifiable. We talked about them in hack 15.
The first things to look for are ViewGroups inside ViewGroups. In this case, we have a
TextView that has the android:layout_alignParentTop attribute and a second
RelativeLayout holding all of the custom views, with android:layout_align-
ParentBottom. You can also see that the second RelativeLayout has its three
www.finebook.ir
Using the Hierarchy Viewer tool to remove unnecessary views 51
performance indicators in red. This means that it’s the slowest view in the tree. Let’s
try removing it by changing the other view’s attributes. The modified XML looks like
the following:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="@string/hello"/>
<com.test.SlowMeasureView
android:id="@+id/slow_measure"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:background="#0000FF"
android:text="Slow Measure"/>
<com.test.SlowLayoutView
android:id="@+id/slow_layout"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:layout_above="@id/slow_measure"
android:background="#00FF00"
android:text="Slow Layout"/>
<com.test.SlowDrawView
android:id="@+id/slow_draw"
android:layout_width="fill_parent"
android:layout_height="30dp"
android:layout_above="@id/slow_layout"
android:background="#FF0000"
android:text="Slow Draw"/>
</RelativeLayout>
The last fix reduced the view tree height by one. When creating views, it’s always better
to avoid tall view trees. Android draws the layout in a two-pass process: a measure pass
and a layout pass. If you have a lot of nodes, it’ll take longer to do the tree traversal.
After you’ve modified the XML to generate the shallowest tree, start looking at the
performance indicators. Note that this indicator is relative to other view objects in the
tree, so don’t be fooled by this. Most of the nodes might be green, but that doesn’t
mean they’re OK. Check how long it takes for them to draw and make sure everything
is working well.
www.finebook.ir
52 CHAPTER 4 Tools
www.finebook.ir
Patterns
In this chapter, you’ll read about different development patterns you can use inside
Android.
You’ve most likely heard of the MVC (Model-View-Controller) pattern, and you’ve
probably used it in different frameworks. When I was trying to find a better way to
test my Android code, I learned about the MVP (Model-View-Presenter) pattern. The
basic difference between MVP and MVC is that in MVP, the presenter contains the UI
business logic for the view and communicates with it through an interface.
In this hack, I’ll show you how to use MVP inside Android and how it improves
the testability of the code. To see how it works, we’ll build a splash screen. A splash
screen is a common place to put initialization code and verifications, before the
application starts running. In this case, inside the splash screen we’ll provide a
progress bar while we’re checking whether or not we have internet access. If we do,
we continue to another activity, but if we don’t, we’ll show the user an error mes-
sage to prevent them from moving forward.
To create the splash screen, we’ll have a presenter that will take care of the com-
munication between the model and the view. In this particular case, the presenter
53
www.finebook.ir
54 CHAPTER 5 Patterns
will have two functions: one that knows when we’re online and another to take care of
controlling the view. You can see the project structure in figure 20.1.
The presenter will use a model class called ConnectionStatus that will implement
the IConnectionStatus interface. This interface will answer whether we have internet
access with a single method:
public interface IConnectionStatus {
boolean isOnline();
}
As you might be thinking, the code in charge of controlling the view will be an
Activity that implements the ISplashView interface. The interface will be used by
the presenter to control the flow of the application. Let’s look at the code for the
ISplashView interface:
public interface ISplashView {
void showProgress();
void hideProgress();
www.finebook.ir
The Model-View-Presenter pattern 55
void showNoInetErrorMsg();
void moveToMainView();
}
Because we’re coding in Android, the view will be the first to be created and afterward
we’ll give the control to the presenter. Let’s see how we do that:
public class SplashActivity extends Activity implements ISplashView {
private SplashPresenter mPresenter;
@Override
public void onCreate(Bundle savedInstanceState) {
B Activity
initialization
... code
mPresenter = new SplashPresenter();
Instantiate presenter
mPresenter.setView(this);
}
C for this Activity
@Override
protected void onResume() { D Start presenter code
when we reach
super.onResume();
mPresenter.didFinishLoading(); onResume() method
}
We’ll first need to initialize the Activity B. Afterward, we create the presenter C
that will take care of getting everything done and we set the Activity instance to the
presenter. We can override the onResume() method D to let the presenter know the
view is ready to give control to it.
The presenter code is simple. Following is the presenter’s didFinishLoading()
method:
public void didFinishLoading() {
ISplashView view = getView();
Getting view, in this
if (mConnectionStatus.isOnline()) { B case the Activity
view.moveToMainView();
} else {
Logic to decide if we
view.hideProgress();
view.showNoInetErrorMsg();
C can move on
}
}
www.finebook.ir
56 CHAPTER 5 Patterns
presenter, you don’t need to run in an Android-powered device and instead can run it
in the JVM. In this case, you’ve used Mockito to mock the interfaces.
Because you’ve been working with Android, you’ll notice that a lot of code ends up
in the Activity. Unfortunately, testing activities is painful. Using the MVP pattern will
help you create tests and apply TDD (test-driven development) in an easy way.
Android uses different kinds of messages to notify applications when something hap-
pens. For example, if you want to know whether or not a device has connected to the
internet, you have to listen to an Intent whose action is android.net.conn
.CONNECTIVITY_CHANGE. This Intent can be heard using a BroadcastReceiver.
Although using a BroadcastReceiver to listen to different notifications from the
OS works well, you can’t access an Activity from the receiver.
Imagine trying to update the UI depending on the connectivity status. How would
you do it? What would you do if you wanted to get the receiver’s information inside
one of your activities? In this hack, I’ll show you how to use a BroadcastReceiver as
an Activity’s inner class to get broadcast Intents.
Setting up a BroadcastReceiver as an Activity’s inner class lets us do two impor-
tant things:
Call the Activity’s methods from inside the receiver
Enable and disable the receiver depending on the Activity’s status
For this hack, we’ll create a Service that, when activated, waits for 5 seconds and then
broadcasts a message. For this toy application, the message we’ll send is a string with
a date. The implementation of the service isn’t that important, but you should
know that it’ll broadcast an Intent with an action—com.manning.androidhacks
.hack021.SERVICE_MSG—and the date travels as an extra.
Because we want to use the date information the service sends in order to update
the UI, we’ll want to listen to this message only when the Activity’s screen is shown.
Let’s see how to achieve that using the following code:
public class MainActivity extends Activity {
private ProgressDialog mProgressDialog;
private TextView mTextView;
www.finebook.ir
BroadcastReceiver following Activity’s lifecycle 57
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main); B Creates new instance
of BroadcastReceiver
mReceiver = new MyServiceReceiver();
mIntentFilter = new IntentFilter(MyService.ACTION);
Creates and
startService(new Intent(this, MyService.class)); defines which
} type of Intent
C the receiver gets
@Override
protected void onResume() {
super.onResume();
registerReceiver(mReceiver, mIntentFilter);
Registers receiver
}
in onResume()
@Override D method
public void onPause() {
super.onPause();
unregisterReceiver(mReceiver);
Unregisters
}
receiver inside
private void update(String msg) { E onPause() method
/* Do something with the msg */
}
class MyServiceReceiver extends BroadcastReceiver {
Invokes
@Override Activity’s
public void onReceive(Context context, Intent intent) { update()
update(intent.getExtras().getString(MyService.MSG_KEY)); F method
}
}
}
www.finebook.ir
58 CHAPTER 5 Patterns
Before Android library projects were released, sharing code between Android projects
was hard or even impossible. You could use a JAR to share Java code, but you couldn’t
share code that needed resources. Sharing an Activity or a custom view was impossi-
ble because you can’t add resources to JARs and use them later in an Android applica-
tion. Android library projects were created as a way to share Android code. In this
hack, we’ll look at a way to use them.
As an example, we’ll create a small application with a login screen. The application
is divided into three layers:
Back-end logic and model (JAR file)
Android library
Android application
www.finebook.ir
Architecture pattern using Android libraries 59
www.finebook.ir
60 CHAPTER 5 Patterns
Almost every Android application uses the internet to fetch information or to sync
data. If you’ve already created a couple of applications, you’ll be able to describe
many different ways to create a connection and show a progress animation while
fetching results.
www.finebook.ir
The SyncAdapter pattern 61
Some time ago, you started developing for Android. You learned that you
shouldn’t place background logic in the main thread. You searched the web for an
explanation of how to do it and you found a nice Android developer’s article entitled
“Painless Threading.” Near the end of the article (see section 23.4), it states this:
Always remember these two rules about the single thread model.
Do not block the UI thread, and make sure that you access the
Android UI toolkit only on the UI thread.
The issue with this approach is the system’s flexibility. For example, you have many
ways to communicate with an Activity. Should the Activity bind to the Service?
Should it use a Handler? Should it communicate via Intents? Should it communicate
through a database? Many possibilities exist and the answer to the question of which
you should use is always “it depends.”
The question I started asking myself was, how does the Gmail application work?
How does it sync and work offline without an issue? Google uses something called
SyncAdapter. Unfortunately, this is one of Android’s best but least documented
www.finebook.ir
62 CHAPTER 5 Patterns
features. If you ask Android developers if they know what it is, they’ll say yes, but
they’ve never used it.
In this hack, we’ll see how to use a SyncAdapter to organize an internet-dependent
application, making our development life easier.
Figure 23.1 Server’s front end Figure 23.2 Android application’s front end
www.finebook.ir
The SyncAdapter pattern 63
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion,
int newVersion) {
Upgrades
db.execSQL("DROP TABLE IF EXISTS " +
from an old
TodoContentProvider.TODO_TABLE_NAME);
onCreate(db);
E
schema
}
}
www.finebook.ir
64 CHAPTER 5 Patterns
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
www.finebook.ir
The SyncAdapter pattern 65
return c;
}
...
private TodoDAO() {}
B Implements
public static TodoDAO getInstance() { singleton
return instance;
}
C Places
public void addNewTodo(ContentResolver contentResolver, calls
Todo list, int flag) {
ContentValues contentValue = getTodoContentValues(list, flag);
contentResolver.insert(TodoContentProvider.CONTENT_URI,
contentValue);
} D to content
Converts
www.finebook.ir
66 CHAPTER 5 Patterns
As you can see, the TodoDAO is implemented with a singleton B. There, we placed calls
such as addNewTodo()C which, after a proper conversion to content values D, will
end in a database insert.
Both activities function in a similar way. When they need to modify some data, they’ll
do it through the TodoDAO. Let’s take a look at the code for the MainActivity:
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mListView = (ListView) findViewById(R.id.main_activity_listview);
mAdapter = new TodoAdapter(this);
Creates
mListView.setAdapter(mAdapter);
}
B ListView
public void addNew(View v) {
C Starts
AddNewActivity
startActivity(new Intent(this, AddNewActivity.class)); activity
}
Nothing out of the ordinary here. We created a ListView that will use a TodoAdapter
B, and every time the user clicks on the Add New button, we’ll start the AddNew-
Activity activity C.
The TodoAdapter holds more interesting code. Let’s see how it’s done:
public class TodoAdapter extends CursorAdapter {
...
private static final String[] PROJECTION_IDS_TITLE_AND_STATUS =
new String[] {
TodoContentProvider.COLUMN_ID,
TodoContentProvider.COLUMN_TITLE,
TodoContentProvider.COLUMN_STATUS_FLAG };
www.finebook.ir
The SyncAdapter pattern 67
PROJECTION_IDS_TITLE_AND_STATUS,
TodoContentProvider.COLUMN_STATUS_FLAG + " != "
Checks use of
+ StatusFlag.DELETE, null,
TodoContentProvider’s
}
TodoContentProvider.DEFAULT_SORT_ORDER); C URI and a projection
@Override
public void bindView(View view, Context context, Cursor c) {
final ViewHolder holder = (ViewHolder) view.getTag();
holder.id.setText(c.getString(mInternalIdIndex));
holder.title.setText(c.getString(mTitleIndex));
final int status = c.getInt(mInternalStatusIndex);
D Changes
background
if (StatusFlag.CLEAN != status) { of text
holder.title.setBackgroundColor(Color.RED);
} else {
holder.title.setBackgroundColor(Color.GREEN);
}
final Long id = Long.valueOf(holder.id.getText().toString());
holder.deleteButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
TodoDAO.getInstance().deleteTodo(
Removes
mActivity.getContentResolver(), id);
TODO from
}
});
E the list
}
...
}
www.finebook.ir
68 CHAPTER 5 Patterns
credentials once, and they’re saved inside an Account. All of the applications that
have the USE_CREDENTIALS permission can query the manager to obtain an account
where an authentication token or whatever is necessary to authenticate against a
server is saved.
Before coding this part, you need to understand that the login functionality will be
used in these situations:
When the application starts and no account has been created
When the user goes to Accounts & Sync and clicks on New Account
When the SyncAdapter tries to sync and the authentication fails
Let’s look at the first two situations in this section and the last one after we have the
SyncAdapter working. For the first one, we’ll create a BootstrapActivity:
public class BootstrapActivity extends Activity {
private static final int NEW_ACCOUNT = 0;
private static final int EXISTING_ACCOUNT = 1;
private AccountManager mAccountManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bootstrap);
mAccountManager = AccountManager.get(this);
B Gets list of
accounts of
Account[] accounts = mAccountManager our type
Creates C .getAccountsByType(AuthenticatorActivity.PARAM_ACCOUNT_TYPE);
a new
account if (accounts.length == 0) {
final Intent i = new Intent(this, AuthenticatorActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
startActivityForResult(i, NEW_ACCOUNT);
Asks user for } else {
password
D String password = mAccountManager.getPassword(accounts[0]);
if (password == null) {
final Intent i = new Intent(this, AuthenticatorActivity.class);
i.putExtra(AuthenticatorActivity.PARAM_USER, accounts[0].name);
startActivityForResult(i, EXISTING_ACCOUNT);
} else {
startActivity(new Intent(this, MainActivity.class));
Continues to
finish();
}
E
MainActivity
}
}
...
Inside the onCreate() method, we get a list of accounts of our type B. If we have no
account, we launch the AuthenticatorActivity to help create a new account C. If the
account exists but the AccountManager doesn’t have a password for it, we need to ask
the user for the password D. This can happen when the password gets invalidated. The
last case is when everything is in place, so we can continue to the MainActivity E.
www.finebook.ir
The SyncAdapter pattern 69
The second situation is more complicated but will leave everything in place for the
last situation. To create a new account through the Accounts & Sync settings, we’ll
need to extend AbstractAccountAuthenticator.
The AbstractAccountAuthenticator is a base class for creating account authenti-
cators. In order to provide an authenticator, we must extend this class, provide imple-
mentations for the abstract methods, and write a service that returns the result of
getIBinder() in the service’s onBind(android.content.Intent) method when
invoked with an Intent with action AccountManager.ACTION_AUTHENTICATOR_INTENT.
We’ll extend the AbstractAccountAuthenticator with a class called Authentica-
tor. It’s OK to return null values from the methods we’re not going to use. The impor-
tant ones are addAcount() and getAuthToken(). The code follows:
public class Authenticator extends AbstractAccountAuthenticator {
private final Context mContext;
public Authenticator(Context context) {
super(context);
mContext = context;
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType, String authTokenType,
String[] requiredFeatures, Bundle options)
throws NetworkErrorException {
return bundle;
}
..
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle options)
throws NetworkErrorException {
if (!authTokenType
.equals(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE)) {
Checks if
final Bundle result = new Bundle(); required
result.putString(AccountManager.KEY_ERROR_MESSAGE, token is
"invalid authTokenType"); B the same
return result;
}
final AccountManager am = AccountManager.get(mContext);
final String password = am.getPassword(account);
www.finebook.ir
70 CHAPTER 5 Patterns
if (password != null) {
Gets a
boolean verified = false;
String loginResponse = null;
C password
try {
loginResponse = LoginServiceImpl.sendCredentials(
account.name, password);
verified = LoginServiceImpl.hasLoggedIn(loginResponse);
} catch (AndroidHacksException e) {
verified = false;
}
D Returns
the result
if (verified) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE,
AuthenticatorActivity.PARAM_ACCOUNT_TYPE);
return result;
}
} E Lets caller know
which activity to call
final Intent intent = new Intent(mContext, for user to sign in
AuthenticatorActivity.class);
intent.putExtra(AuthenticatorActivity.PARAM_USER, account.name);
intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE,
authTokenType);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
www.finebook.ir
The SyncAdapter pattern 71
...
if (mRequestNewAccount) {
Sets a new
mAccountManager.addAccountExplicitly(account, mPassword, null);
password E } else {
mAccountManager.setPassword(account, mPassword);
}
final Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);
www.finebook.ir
72 CHAPTER 5 Patterns
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE,
PARAM_ACCOUNT_TYPE);
if (mAuthTokenType != null
&& mAuthTokenType.equals(PARAM_AUTHTOKEN_TYPE)) {
intent.putExtra(AccountManager.KEY_AUTHTOKEN, mAuthToken);
}
setAccountAuthenticatorResult(intent.getExtras());
Sets the
setResult(RESULT_OK, intent);
finish();
F result
}
...
When the user enters the login details and clicks OK, handleLogin() gets executed.
There we launch a thread that will hit the server B and return the result to the
AuthenticatorActivity in the onAuthenticationResult() method C. If the service
can authenticate correctly, we’ll call finishLogin() D, and if not we’ll show an error
and let the user try again. Inside finishLogin(), if the Request New Account flag is
set, we use the AccountManager to create an account. If the account exists, we’ll set a
new password E. Finally, we set the result that’s to be sent as the result of the request
that caused this activity to be launched F.
The last step is modifying the AndroidManifest.xml to register the Service. We do
that by adding the following:
<service android:name=".authenticator.AuthenticationService"
android:exported="true">
B Returns an
Authenticator
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service> C
Additional information
www.finebook.ir
The SyncAdapter pattern 73
This means that the TodoSyncService will use the TodoContentProvider’s authority
and will need a com.manning.androidhacks.hack023 account type.
The next step is to extend AbstractThreadedSyncAdapter. Following is the code:
public class TodoSyncAdapter extends AbstractThreadedSyncAdapter {
private final ContentResolver mContentResolver;
private AccountManager mAccountManager;
private final static TodoDAO mTodoDAO = TodoDAO.getInstance();
@Override
public void onPerformSync(Account account, Bundle extras,
String authority, ContentProviderClient provider,
SyncResult syncResult) {
Gets every
TODO from
B C Removes the
try { TODOs from the
the server List<Todo> data = fetchData(); local database
syncRemoteDeleted(data);
www.finebook.ir
74 CHAPTER 5 Patterns
syncFromServerToLocalStorage(data);
Calls
syncFromServer-
syncDirtyToServer( E Gets every TODO from
mTodoDAO.getDirtyList(mContentResolver)); database; either push a
ToLocalStorage D new TODO to server
and update or delete
} catch (Exception e) {
handleException(e, syncResult);
}
}
...
When the sync starts, we first get every TODO from the server B. Note that if we have
lots of TODOs, we might need to use some sort of pagination. The next step is remov-
ing from the local database TODOs that are no longer in the server C. We do this by
getting a list of TODOs from our local database with the CLEAN flag set, and checking
whether a TODO is in the server’s list. If it’s not there, we can delete it from our local
database. After that, syncFromServerToLocalStorage is called D. There we’ll iterate
over the server’s TODOs. We can use the server_id to check whether it exists locally.
If it exists, we update it with the information from the server. If not, we create a new
one. The last step is syncDirtyToServer() E. In this case, we get every TODO from
the local database that’s dirty (not clean). There, depending on the status flag, we
push a new TODO to the server and update or delete.
Note how the exceptions are handled F. Depending on the exception, we modify
the syncResult object. We do this to help the SyncManager decide when to call the
SyncAdapter again.
www.finebook.ir
The SyncAdapter pattern 75
The final step is to wrap the SyncAdapter inside the TodoSyncService, which we
can do using the following code:
public class TodoSyncService extends Service {
private static final Object sSyncAdapterLock = new Object();
private static TodoSyncAdapter sSyncAdapter = null;
@Override
public void onCreate() {
synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) {
sSyncAdapter = new TodoSyncAdapter(
getApplicationContext(), true);
}
}
}
@Override
public IBinder onBind(Intent intent) {
return sSyncAdapter.getSyncAdapterBinder();
}
www.finebook.ir
www.finebook.ir
Working with lists
and adapters
Lists and adapters are two of the main concepts to master in Android development.
In this chapter, you’ll learn several tips and tricks you can use with lists and adapters.
A common way to show data to the user in mobile platforms is to place it inside a list.
When you do this, you need to handle two cases: the ordinary list full of items and
an empty state. For the list, you’ll use a ListView, but how do you handle the empty
state? Fortunately, there’s an easy way to achieve this. Let’s look at how to do it.
ListView and other classes that extend AdapterView easily handle emptiness
through a method called setEmptyView(View). When the AdapterView needs to
draw, it’ll draw the empty view if its Adapter is null, or the adapter’s isEmpty()
method returns true.
Let’s try an example. Imagine we want to create an application to handle our
TODO list. Our main screen will be a ListView with all our TODO items, but when
we launch it for the first time, it’ll be empty. For our empty state, we’ll draw a nice
image. Following is the XML layout:
77
www.finebook.ir
78 CHAPTER 6 Working with lists and adapters
</FrameLayout>
The only thing missing is the onCreate() code, where we fetch the ListView and
place the ImageView as the empty view. The code to use is the following:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Because we’re not setting an adapter to the ListView when we run this code, it’ll show
the ImageView.
If you’ve already been programming in Android, you’ve probably used the Adapter
class. But for those of you who haven’t used the Adapter, it’s described in the Android
documentation (see section 25.2) as follows:
www.finebook.ir
Creating fast adapters with a ViewHolder 79
In this hack, I’ll provide a short introduction on how the Adapter works so you can
learn how to construct one quickly, making your application as responsive as possible.
The AdapterView is the abstract class for views that use an Adapter to fill them-
selves. A common subclass is the ListView. Both classes work together in a simple way.
When the AdapterView is shown, it calls the Adapter’s getView() method to create
and add the views as children. The Adapter will take care of creating the views in its
getView() method. As you can imagine, instead of returning new views per row,
Android offers a way to recycle them. Let’s first look at how this works and then how to
take advantage of the recycling.
In figure 25.1, we see a recycling example in action. In A we see the list loaded for
the first time. In B the user scrolls down and the view for Item 1 disappears—instead of
freeing the memory, it’s sent to the recycler. When the AdapterView asks the Adapter
for the next view, the getView() method is called and we get a recycled view in the
convertView parameter. This way if Item 5’s view is the same as Item 1’s view, we can
change the text and return it. The populated row will end in the empty space in C.
To explain this in a few words, when getView() is called, if convertView isn’t null,
then we use convertView instead of creating a new view. We need to fetch each wid-
get’s reference using convertView.findViewById() and populate it with the informa-
tion from the model corresponding to the position.
A. B. C.
Item1
Item7 Item8
www.finebook.ir
80 CHAPTER 6 Working with lists and adapters
Although this will work, we can tweak it further. To do so, we’ll use the ViewHolder
pattern. The ViewHolder is a static class where we can save the row’s widgets to avoid
the findViewById() calls every time getView() is called.
Let’s see an example of how it’s used. In the example, we’ll create an Adapter that
inflates a view that has an ImageView and two TextViews. The code follows:
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
if (convertView == null) {
If convertView
is null, convertView = mInflater.inflate(R.layout.row_layout, parent, false);
inflate view B viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) C Fetch references
to widgets
convertView.findViewById(R.id.image);
viewHolder.text1 =
(TextView) convertView.findViewById(R.id.text1);
viewHolder.text2 =
(TextView) convertView.findViewById(R.id.text2);
convertView.setTag(viewHolder);
ViewHolder
saved as tag D E Ifisn’t
convertView
null,
} else {
viewHolder = (ViewHolder) convertView.getTag(); recycle it
}
Model model = getItem(position);
Get
model viewHolder.imageView.setImageResource(model.getImage());
object F viewHolder.text1.setText(model.getText1());
Populate
viewHolder.text2.setText(model.getText2());
G view
return convertView;
}
static class ViewHolder {
ViewHolder
public ImageView imageView;
public TextView text1;
H class
public TextView text2;
}
If the convertView is null, then inflate the view B. When we create the view, we need
to fetch the references to the widgets and save them inside the ViewHolder C. The
ViewHolder gets saved as a tag D. If the convertView isn’t null, that means we can
recycle it. We can get the ViewHolder from the convertView’s tag E. Then we get the
model object, depending on the position F, and populate the view with information
from the model G. The ViewHolder class contains all of the widgets as public fields H.
www.finebook.ir
Adding section headers to a ListView 81
Imagine that you want to create a vacation-planning application that allows users to
browse a list of popular destinations organized by country. To present a long list of
data, you’ll want to include section information to help orient people within the list.
For example, contacts applications will often group users by the first letter of their last
name, and scheduling applications will group appointments by dates. You can accom-
plish this with a design similar to that used in the iPhone contacts screen, where a sec-
tion header scrolls with the list, with the current section’s header always visible at the
top of the screen. In figure 26.1, the highlighted letters are the section headers, and
the lists below them contain the countries whose name begins with those letters. What
you see in the figure is difficult to create in Android because ListView doesn’t have a
concept of a section or a section header, only of items
within the list.
Android developers often try to solve this problem
by creating two types of list items: a regular item for
data, and a separate item for section headers. We can
do this by overriding the getViewTypeCount()
method to return 2, and modifying our getView()
method to create and return the appropriate type of
item. In practice, however, this will lead to messy
code. If our underlying list of data contains 20 items,
our adapter will need to contain anywhere from 21 to
40 items, depending on how many sections it con-
tains. This can lead to complicated code: the List-
View might want to show the 15th visible item, which
might be the 9th item in the underlying list.
A much simpler approach is to embed the section
header within the list item, and then make it visible
or invisible as needed. This greatly simplifies the Figure 26.1 A sectioned list of
logic for building the list and looking up items when country names
www.finebook.ir
82 CHAPTER 6 Working with lists and adapters
the user makes a selection. We can create a special TextView that overlaps the top of
the list, and update it when the list scrolls a new section into view.
The text has a custom background color B to distinguish it from regular text in the
list. Now, write the following XML for the screen, including the stationary section
header:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
The list B uses the standard Android list ID so we can use it in our subclass of List-
Activity. Include the header in this frame, so it will overlap the list and show the cur-
rent section.
The last XML to create is the list item, which follows, and includes both the data
field and the section header:
<?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="wrap_content">
B Visible
headers
sections
<include layout="@layout/header"/>
<TextView
android:id="@+id/label"
style="@android:style/TextAppearance.Large"
android:layout_width="fill_parent" C Shows data
for the slot
android:layout_height="wrap_content"/>
</LinearLayout>
www.finebook.ir
Adding section headers to a ListView 83
}
R.layout.list_item, parent, false);
C Checks if item
starts with a
TextView header = (TextView) view.findViewById(R.id.header); different letter
String label = getItem(position); than preceding
if (position == 0 item
|| getItem(position - 1).charAt(0) != label.charAt(0)) {
header.setVisibility(View.VISIBLE);
Labels
header.setText(label.substring(0, 1));
section
header D } else { E
Hides section
header
header.setVisibility(View.GONE);
}
return super.getView(position, view, parent);
}
}
The ArrayAdapter parent class can do most of the work if we provide B the XML for
its custom views. After creating a list item, check to see whether it starts with a differ-
ent letter than the preceding item C. If it does, then it’s the first item in this section,
and so we label the section header and make it visible D. Otherwise, we hide it E.
Now that the section headers within the list are properly set, write a helper method
that will configure the floating section header at the top of the screen:
private TextView topHeader; B Accesses section header
...
www.finebook.ir
84 CHAPTER 6 Working with lists and adapters
}
topHeader.setText(text); C Updates text
The instance variable B lets us access the section header at the top of the screen.
When we initially create or scroll the list, we’ll call this helper method, which finds the
appropriate letter to use for this section and updates the text C.
26.3 Wrapping up
Finally, bring it all together in the Activity’s onCreate() method. Configure the list
and attach a new listener that updates the header when the list scrolls:
private int topVisiblePosition;
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); B
Attaches a
scroll listener
setContentView(R.layout.list);
topHeader = (TextView) findViewById(R.id.top);
setListAdapter(new SectionAdapter(this, Countries.COUNTRIES));
getListView().setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view,
int scrollState) {
// Empty.
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (firstVisibleItem != topVisiblePosition) {
topVisiblePosition = firstVisibleItem; C
Invokes the
helper method
setTopHeader(firstVisibleItem);
}
} Initializes first
D
}); header to the
setTopHeader(0); first item
}
After configuring the UI B, attach a scroll listener. When users scroll the list, check to
see whether they’ve changed position, and if so, invoke the helper method C to
update the floating header. Make sure to initialize the header to the first item D when
the list first appears.
www.finebook.ir
Communicating with an Adapter using an Activity and a delegate 85
A lot of Android widgets use an Adapter to populate themselves. Every Android wid-
get that uses an undefined list of views will have an Adapter to fetch them. This means
that after you learn how to use one, you’ll be able to operate a wide range of widgets
easily. One benefit of this approach is that you can place all of the code related to the
visual logic inside the Adapter. Why is this important? Because you can apply the con-
cept of separation of concerns (SoC). Imagine that you need to show a list of tele-
phone numbers with two different clickable widgets inside each row—the first one to
remove the telephone number from the list, and the second one to make the call.
Where would you place all of those click handlers?
In this hack, we’ll look at how to solve this problem using the Delegation pattern.
This pattern will help us to move all of the business logic away from the Adapter and
place it inside the Activity. We’ll create a simple application that adds numbers to a
list and each row will have a Remove button to remove the phone number.
The idea is simple: we’ll add the Remove button click handler in the Adapter, but
instead of removing the object there, we’ll call an Activity’s method through the del-
egate interface. The first thing we’ll create is the Adapter’s code:
public class NumbersAdapter extends ArrayAdapter<Integer> {
@Override
public View getView(int position, View cv, ViewGroup parent) {
if ( null == cv ) {
cv = mInflator.inflate(R.layout.number_row, parent, false);
}
@Override
public void onClick(View v) {
www.finebook.ir
86 CHAPTER 6 Working with lists and adapters
if ( null != mDelegate ) {
Removes
mDelegate.removeItem(value);
} C objects
}
});
return cv;
}
public void setDelegate(NumbersAdapterDelegate delegate) {
Sets as the
mDelegate = delegate;
Adapter
}
D delegate
}
We define the delegate interface B that will be used to handle removing the object
C. The Activity will need a way to set itself as the Adapter delegate, and for that we
have a setter D.
Now that we have the Adapter in place, let’s take a look at the Activity code:
public class MainActivity extends Activity implements
NumbersAdapterDelegate {
Implements
private ListView mListView; NumberAdapterDelegate
private ArrayList<Integer> mNumbers; B interface
private NumbersAdapter mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mListView = (ListView) findViewById(R.id.main_listview);
mNumbers = new ArrayList<Integer>();
mAdapter = new NumbersAdapter(this, mNumbers);
mListView.setAdapter(mAdapter);
}
@Override
protected void onResume() {
super.onResume();
mAdapter.setDelegate(this);
Registers on the
}
onResume()
@Override C method
protected void onPause() {
super.onPause();
mAdapter.setDelegate(null);
Unregisters in
}
the onPause()
@Override D method
public void removeItem(Integer value) {
Removes element from
mNumbers.remove(value);
list and notifies Adapter
mAdapter.notifyDataSetChanged();
} E of the change
}
www.finebook.ir
Taking advantage of ListView’s header 87
www.finebook.ir
88 CHAPTER 6 Working with lists and adapters
but this wouldn’t work because a ListView is already a ScrollView. You can try it out,
but you’ll run into issues because the ListView already handles scrolling.
Fortunately, the ListView provides methods to add custom headers and footers to
it. Let’s look at the following code to see how to use those methods to place the
Gallery as a ListView’s header:
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
B References
the ListView
mListView = (ListView) findViewById(R.id.main_listview);
ArrayAdapter<String> adapter =
Sets the
adapter to new ArrayAdapter<String>(this, R.layout.list_item, NUMBERS);
ListView F mListView.setAdapter(adapter);
mListView.setOnItemClickListener( G Adds an
onItemClick listener
new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
mGallery.setSelection(position-1);
}
});
}
}
The code provides a reference to the ListView B. This ListView will take the whole
screen. For the header, we created a different XML file that needs to get inflated C.
You can see that we make a second call to findViewById() inside the header view
because we created a LinearLayout with the Gallery inside. It’s not needed, but we
might add something else in the future. We replace the original LayoutParams from
the header with the ListView version D and then add the whole header view to the
ListView E. After setting the header, we set the adapter to the ListView F and,
www.finebook.ir
Handling orientation changes inside a ViewPager 89
finally, we add an onItemClick listener G that will take care of scrolling the images
inside the gallery every time we hover over a number.
With the release of Compatibility Package revision 3, the ViewPager class was made
available. If you’ve never used the ViewPager class, you should know it’s an implemen-
tation of a horizontal view swiper. What’s possible with the ViewPager class? You can
create any kind of application that requires paginated views. The best part is that it
works like an AdapterView, meaning that you use it as you’d use a ListView —simple.
Imagine you want to create a magazine-like application. Although the ViewPager
class is an excellent ally to help you achieve this, it’s hard to handle different orienta-
tion changes depending on the page. In this hack I’ll show you how to use the View-
Pager class and configure everything to make it work correctly.
For this hack, we’ll create a color viewer application. We’ll be able to swipe
through colors and every page where (index % 2) == 0 will have a landscape version
available. To create this we’ll need the following:
An Activity that will hold the ViewPager and control the orientation changes
A ColorFragment class that will show a color and some text in the middle of the
screen
A ColorAdapter class that will be in charge of creating the fragments and
telling the Activity which fragment will be able to change the orientation
configuration
A ViewPager that will use the ColorAdapter to display our fragments
www.finebook.ir
90 CHAPTER 6 Working with lists and adapters
@Override
public void onPageSelected(int position) {
Adds a
if (mAdapter.usesLandscape(position)) {
allowOrientationChanges();
D listener
} else {
enforcePortrait();
}
}
...
});
}
methods
E Makes the
public void allowOrientationChanges() {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
The first thing to do is set the default orientation to portrait B. This means that if the
view doesn’t specify whether it allows orientation changes, it’ll stay portrait. The code
provides a reference to the ViewPager C, and we’ll set the ColorAdapter to it. Add a
listener D when the page is changed, and inside it ask the Adapter whether to allow
orientation changes. Finally, make the methods E that take care of enabling or dis-
abling the orientation changes using the setRequestedOrientation() method that
comes from the Activity.
www.finebook.ir
ListView’s choiceMode 91
On the other hand, in this hack you saw how to limit orientation changes in your
views. Remember that it’s always better to support both orientations for every view.
Your users will appreciate it if you allow them to position the device in different ways
when using your application.
Android’s ListView is one of the most important classes in the SDK. Apart from show-
ing items in a scrollable list, it can also be used to pick stuff from that list. Imagine you
need to create an Activity to let your user pick a country from a list. How would you
do it? Would you handle the selection yourself? You could create a ListView and han-
dle the selection yourself using click handlers, but in this hack I’ll provide a better way
to do it.
In this hack, you’ll learn how to use a ListView to
create a country picker. An example of this picker is
shown in figure 30.1. When a country row is selected
and you click on the Pick Country button, a Toast
will be shown with the country name.
The ListView has something called choiceMode.
In the documentation (see section 30.2), you’ll see
the following explanation:
www.finebook.ir
92 CHAPTER 6 Working with lists and adapters
Another interesting feature of the ListView widget is that whether we use single-
Choice or multipleChoice, they automatically save the selected position(s). You
already know that the ListView will help us create the picker by setting the choice-
Mode to singleChoice. Let’s create the Activity’s layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
B Uses a
Button to
android:onClick="onPickCountryClick" execute the
android:text="@string/activity_main_add_selection" /> method
<ListView
android:id="@+id/activity_main_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" C Shows the
country list
android:choiceMode="singleChoice" />
</LinearLayout>
The layout is simple. We’ll use a Button B to execute the method that handles the
logic of retrieving the selected country, and a ListView with singleChoice C to show
the country list.
Now let’s create the custom row layout and the Activity source code. The row lay-
out will use the following code:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="@+id/country_view_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.9"
android:padding="10dp" />
<CheckBox
android:id="@+id/country_view_checkbox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.1"
android:gravity="center_vertical"
android:padding="10dp" />
</LinearLayout>
www.finebook.ir
ListView’s choiceMode 93
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Helper method
setContentView(R.layout.activity_main);
to populate list
createCountriesList(); of countries
mToastFmt = getString(R.string.activity_main_toast_fmt);
mAdapter = new CountryAdapter(this, -1, mCountries);
mListView = (ListView)
findViewById(R.id.activity_main_list);
Create an
mListView.setAdapter(mAdapter);
Adapter and set
} it to ListView
public void onPickCountryClick(View v) {
int pos = mListView.getCheckedItemPosition(); If something is selected,
show a Toast with
if (ListView.INVALID_POSITION != pos) { country name
String msg = String.format(mToastFmt, mCountries.get(pos)
.getName());
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
}
Sounds simple so far, right? It is, but there’s a trick to using it. We need to understand
how the ListView sets a position to be checked or not to use it correctly.
If you stop reading this and search the web looking for code samples about the
ListView’s choiceMode, you’ll notice that most of the results use a class called
CheckedTextView for the row view, instead of a custom view as we did. If you look for
the source code of that class, you’ll notice that it’s an extension of the TextView class,
which implements the Checkable interface.
So the ListView is somehow using the Checkable interface to handle the view
state. If you browse the ListView source code, you’ll find the following:
if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
if (child instanceof Checkable) {
((Checkable) child).setChecked(mCheckStates.get(position));
}
}
We need to make our custom row implement the Checkable interface if we want the
ListView to handle the selection. Unfortunately, this is only possible when creating a
custom view. Let’s create a class called CountryView. The code is the following:
public class CountryView extends LinearLayout
implements Checkable {
www.finebook.ir
94 CHAPTER 6 Working with lists and adapters
@Override
public void setChecked(boolean checked) {
mCheckBox.setChecked(checked);
}
@Override
public void toggle() {
mCheckBox.toggle();
}
}
Notice how the Checkable interface methods are implemented. Every method calls
the mCheckBox implementation. This means that when the ListView wants to select a
row it will call the CountryView’s setChecked() method.
Everything is set. We can now run the application. You’ll notice that when you click
on a row, the CheckBox won’t be ticked, but if you click over the CheckBox it is. You
might also be able to check a row and when you scroll, you might see rows getting
selected. What’s wrong?
The issue is that we’re adding a focusable widget, the CheckBox. The best way to solve
this is to disallow the CheckBox to gain focus. And, because the ListView is the one that
decides what should and shouldn’t be checked, we’ll also remove the CheckBox possi-
bility of getting clicks. We do this by adding the following attributes to the XML:
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
If we run the application now with this modification, everything will work as we’d
expect.
www.finebook.ir
ListView’s choiceMode 95
when you understand how it works, it’s a great feature to use when you need to pick
one or several items from a list.
www.finebook.ir
www.finebook.ir
Useful libraries
In this chapter, we’ll cover two third-party libraries. The first one lets you use aspect-
oriented programming inside an Android application. The second is a game frame-
work. We’ll walk through what’s possible when you add them to your application.
Have you ever tried to add analytics, ads, and logs to an Android Activity? If you
have, you know that your class can get polluted with a lot of code that has nothing
to do with your Activity’s logic. In this hack, you’ll see how to solve this issue
using aspect-oriented programming (AOP). As an example, we’ll add a log state-
ment to the Activity’s onCreate() method using AOP to make sure that the
Activity doesn’t get polluted.
Aspect-oriented programming is a programming paradigm that aims to increase
modularity by allowing the separation of cross-cutting concerns. Here’s a basic idea
of how all of this works: we specify our cross-cutting concerns in a separated mod-
ule (aspect), and we place the code that we want to be executed (either before or
after our cross-cutting concern) in the separate module or modules. Figure 31.1
illustrates this concept.
97
www.finebook.ir
98 CHAPTER 7 Useful libraries
Logic
Logic
Logging
Security
Logic
Logging
Logic
Activity Aspects
Logic
Activity
Inside Android, AOP can be implemented using a library called AspectJ. Since
Android doesn’t support bytecode generation, we can’t use all the AspectJ features.
One AspectJ feature that works in Android is called compile-time weaving. To under-
stand how this works, you first need to understand when it happens. AspectJ will mod-
ify our code after it’s compiled to bytecode and before it’s converted to dex. There it’ll
take care of adding the additional code to our cross-cutting concerns. See figure 31.2.
Activity Aspect
Compiler
www.finebook.ir
Aspect-oriented programming in Android 99
To make AOP work, we’ll need to modify the build procedure. In this case, we’ll use
Apache Maven because then we only need to add some dependencies to a pom.xml,
and a build plugin makes everything extremely simple.
The Apache Maven plugin we’ll use is called aspectj-maven-plugin.
Let’s take a look at the aspectj-maven-plugin configuration inside the pom.xml
build section:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<source>1.5</source>
<complianceLevel>1.5</complianceLevel>
<showWeaveInfo>true</showWeaveInfo> B
showWeaveInfo on
<verbose>true</verbose>
</configuration>
C
verbose on
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
D
goal is set to compile
</execution>
</executions>
</plugin>
While developing aspects, turn the showWeaveInfo B and verbose C flags on. This will
log information about the weaving process, helping us understand how everything gets
applied. Using compile D as goal tells the plugin to weave all the main classes. If we
need to weave our test classes as well, we’ll need to add <goal>test-compile</goal>.
Because we didn’t specify a path for the code, the AspectJ plugin will look for files
inside the src/main/ directory. There we’ll create a java directory for the Java source
code and an aspect folder for the aspects.
We’ve configured everything to start using AspectJ in our project. Because we want
to clean our Activity from logs, we’ll now create a log aspect. We have two possibili-
ties for creating an aspect: the AspectJ language syntax and the @AspectJ annotation
style. The big difference is that the language syntax should be easier to write aspects
in, since it was purposefully designed for that, whereas the annotation style follows
regular Java compilation. Because we’re not doing something huge and our aspect is
simple, we’ll use the annotation style.
Inside the aspect folder is a file, LogAspect.java, that describes the aspect:
@Aspect
public class LogAspect {
B AspectJ annotation
@Pointcut("within(com.manning.androidhacks.hack031.MainActivity)")
private void mainActivity() {
}
C
Pointcut for our Activity
@Pointcut("execution(* onCreate(..))")
Pointcut for the
D onCreate() method
www.finebook.ir
100 CHAPTER 7 Useful libraries
If you haven’t used AspectJ, here’s a small reference for understanding the code:
A join point is a well-defined point in the program flow.
A pointcut picks out certain join points and values at those points.
A piece of advice is code that’s executed when a join point is reached.
Because we chose to use the annotation style, we’ll need to annotate the class with
@Aspect B. The first two methods from the class are annotated with @Pointcut. In
this example, the first one creates a pointcut for our MainActivity C class and the
second one for any method that is called onCreate() D. The third method is an
advice. Because we’ve annotated it with @AfterReturning, the advice runs when the
matched method execution returns normally. Note how the mainActivity() and
onCreate pointcuts are mixed with an && E. When you reach that join point, the
advice code will get executed F.
There’s more than one way to describe a join point. In the example, we mix two
pointcuts, but you can easily find other ways of doing the same thing. Depending on
what you want to achieve, you’ll need to start playing with pointcuts and advices.
www.finebook.ir
Empowering your application using Cocos2d-x 101
Android provides different ways to present your application information to the user,
but sometimes these might be insufficient. Imagine you want to add a graph view or a
3D animation to your application. How would you do that? Some developers might try
using OpenGL for their views, but this means adding a layer of complexity, and not
everyone knows how to code OpenGL.
In this hack, I’ll show you to how use the game framework called Cocos2d-x to add
an edge to your applications.
www.finebook.ir
102 CHAPTER 7 Useful libraries
The last paragraph holds a lots of important information, so let me try to explain it in
an easier way. Every time we add a widget or a custom view to our application, it gets
added to the view hierarchy. Our complete tree of views (which forms our Activity)
gets drawn in what’s called the UI thread. On the other hand, the SurfaceView gets its
own thread to draw and it won’t use the UI thread. If the SurfaceView doesn’t use the
UI thread to draw itself, how does Android deal with the mixture of the view hierarchy
and surface views? To understand this, we must analyze the following paragraph (see
section 32.4):
The big conclusion we can get from this last paragraph is that we can mix both worlds
but with certain restrictions. The SurfaceView will be placed in front of or in back of
our view hierarchy. In our example, we’ll have our view hierarchy in the back and will
place the SurfaceView in front of it. So let’s get started creating our view hierarchy first.
We’ll first create the XML for our Activity. Here’s the code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView android:id="@+id/winter_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginTop="5dp"
android:gravity="center"
android:text="Hello Winter!"
android:textSize="30sp" />
<View android:id="@+id/separator"
android:layout_width="fill_parent"
android:layout_height="5dp"
android:layout_below="@id/winter_text"
www.finebook.ir
Empowering your application using Cocos2d-x 103
android:background="#FFFFFF" />
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginTop="5dp"
android:gravity="center"
android:text="It's snowing!"
android:textSize="30sp" />
<FrameLayout android:layout_width="fill_parent"
android:layout_height="fill_parent"
B FrameLayout
android:layout_below="@id/separator">
<org.cocos2dx.lib.Cocos2dxEditText
C Creates an
org.coco2dx.lib
android:id="@+id/game_edittext" .Cocos2dxEditText
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:background="@null"/> D Places
SurfaceView
<org.cocos2dx.lib.Cocos2dxGLSurfaceView inside the XML
android:id="@+id/game_gl_surfaceview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</FrameLayout>
</RelativeLayout>
The layout has nothing special in it. I’ve organized the different views using a Rela-
tiveLayout. The interesting stuff is inside the FrameLayout B. We can first see how
an org.cocos2dx.lib.Cocos2dxEditText is created C. The Cocos2dxEditText is
needed by Cocos2d-x to show the keyboard when the game demands text input from
the user. It’s not something that we’ll use, but it’s required. The other important ele-
ment is the SurfaceView D. Placing the SurfaceView inside the XML offers an
unique way of positioning and providing a width and height to our Cocos2d-x’s view.
We could’ve used the whole screen, but I wanted to show you how we can use Android
resources to place the SurfaceView on the screen without worrying about device sizes,
pixel density, and so on.
Let’s continue with the Activity’s code. It’s just copied and pasted from the
Cocos2d-x Hello World sample application. Here’s what it does:
public class MainActivity extends Cocos2dxActivity {
Extends
private Cocos2dxGLSurfaceView mGLView;
B Cocos2dxActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (detectOpenGLES20()) {
C Tells Cocos2d-x
our application
String packageName = getApplication().getPackageName(); package
super.setPackageName(packageName);
setContentView(R.layout.game_demo); D Informs
mGLView = (Cocos2dxGLSurfaceView) Cocos2d-x where
findViewById(R.id.game_gl_surfaceview); Cocos2dxEditText is
Cocos2dxEditText edittext = (Cocos2dxEditText)
findViewById(R.id.game_edittext);
mGLView.setEGLContextClientVersion(2);
www.finebook.ir
104 CHAPTER 7 Useful libraries
mGLView.setCocos2dxRenderer(new Cocos2dxRenderer());
mGLView.setTextField(edittext);
} else {
Log.d("activity", "do not support gles2.0");
}
finish(); B Closes the app
}
}
We have all the Java code in place; we just need to write the C++ code to take care of
the snow. Since this is just an example of what’s possible, I copied and pasted one of
Cocos2d-x’s particle system tests that involves snow falling down. The code is all inside
the HelloWorldScene.cpp file that comes with the sample code for this book.
If you’ve never used C++ in Android before, you should know that you need to use
the Android NDK.
www.finebook.ir
Empowering your application using Cocos2d-x 105
www.finebook.ir
www.finebook.ir
Interacting with
other languages
Android applications are coded mainly in Java. Officially, Android also supports C/
C++ using the Android NDK (Native Development Kit). But is it possible to develop
applications using other programming languages? In this chapter, we’ll analyze the
other possibilities.
During the summer of 2011, my company released an iOS game called Shaman
Doctor. The game was developed using cocos2d-iphone, an iOS library. The
cocos2d-iphone library is coded in Objective-C, but there are a lot of forks that
offer the same API in other programming languages. One of the most active forks is
cocos2d-x. Instead of using Objective-C, cocos2d-x uses C++, and the most interest-
ing thing about this project is that the API looks like Objective-C. To get an idea of
what the Cocos2d-x team has created, take a look at the following code:
[[SimpleAudioEngine sharedEngine] playEffect:@"sfx.file"];
cocos2d-
iphone SimpleAudioEngine::sharedEngine()->playEffect("sfx.file"); cocos2d-x
version version
107
www.finebook.ir
108 CHAPTER 8 Interacting with other languages
As you might have noticed, the API is exactly the same, but to port a game from
cocos2d-iphone to cocos2d-x you would need to port all your Objective-C code to C++,
which is a boring task.
When I started looking for alternatives, I found a library called Itoa created by
Dmitry Skiba. To understand what Itoa is capable of, let me quote its documentation
(see section 33.5):
Itoa’s main purpose is more than just running Objective-C in Android; it’s to magi-
cally convert an iOS application to an Android one. While its main feature is far from
complete, the fact that it allows running Objective-C in Android is real.
What we’ll do in this hack is port a simple Objective-C library called Text-
Formatter. This means that we’ll run the Objective-C code in Android without need-
ing to modify it.
FOUNDATION: THE NDK AND OBJECTIVE-C Itoa makes heavy use of the Android
NDK. You’ll need to understand how the NDK works to understand what
comes next. If you have never used the Android NDK, you can read about it in
Android in Action, Third Edition (W. Frank Ableson et al., Manning Publica-
tions, 2011). You’ll also need to have a basic understanding of Objective-C.
www.finebook.ir
Running Objective-C in Android 109
The ItoaApp.mk file contains more interesting information. The content is the
following:
APP_IS_LIBRARY := true
B Turn on library mode
APP_LIBRARY_BIN_PATH = ../libs/$(TARGET_ABI) C Set path for .so files
The default settings for the ItoaApp.mk file are enough for what we want to create.
Since we don’t want to create an Android APK from the Objective-C code, we need to
turn on the library mode B. The second setting is to set the path where the .so files
will be saved C.
...
#import "TextFormatter.h"
@implementation TextFormatter
TextFormatter.m file
www.finebook.ir
110 CHAPTER 8 Interacting with other languages
objc, text];
return string; TextFormatter.m file
}
@end
As you can see, the library doesn’t need any modification. It’s just a .h and .m like you
would use in an Objective-C application. Now let’s see how to configure the
ItoaModule.mk file to compile this. Itoa NDK build scripts were derived from Android
NDK, but they were refactored. For example, the ItoaModule.mk file renames all the
LOCAL_* variables to MODULE_*. The content of the make file is the following:
MODULE_PATH := $(call my-dir)
include $(CLEAR_VARS)
MODULE_C_INCLUDES += \
$(MODULE_PATH) \ Path to the include files
include $(BUILD_SHARED_LIBRARY)
extern "C"
{
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
return JNI_VERSION_1_6;
}
}
Because the virtual machine calls the JNI_OnLoad method when the native library is
loaded, it’s a great place to make the initialization needed by Itoa.
Now let’s complete the main.mm implementation, which is the following:
www.finebook.ir
Running Objective-C in Android 111
#include <jni.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <TextFormatter.h>
extern "C"
{
jstring
Java_com_manning_androidhacks_hack033_TextFormatter_formatString(
JNIEnv* env, jobject thiz, jstring text)
TextFormatter
{
jstring result = NULL;
JNI call B
NSAutoreleasePool *pool = [NSAutoreleasePool new];
const char *nativeText = env->GetStringUTFChars(text, 0);
Convert
NSString *objcText =
jstring to
[NSString stringWithUTF8String:nativeText];
env->ReleaseStringUTFChars(text, nativeText);
C NSString *
In the previous example, we have a mixture of C, C++, and Objective-C in the same
file. From the method signature, we can learn that the TextFormatter Java native call
will get a String as a parameter and will return a String B. Another interesting con-
cept to learn here is that we can’t send the jstring we get as a parameter to the
TextFormatter implementation directly. We need to convert the jstring to a char *
and then convert that char * to an NSString * C. After calling the TextFormatter
implementation, we’ll get an NSString * that will need to be converted to a jstring.
This is done by converting it to char * first, and using the env to be able to return a
jstring D.
The ItoaModule.mk file for main is the following:
MODULE_PATH := $(call my-dir)
include $(CLEAR_VARS)
www.finebook.ir
112 CHAPTER 8 Interacting with other languages
Let’s talk about what the APP_SHARED_LIBRARIES is for B. For that variable, we used
the macro $(TARGET_ITOA_LIBRARIES), which means that the .so files located at
$ITOA_NDK/itoa/platform/arch-arm/usr/lib will be included in the libs directory. If
you check what’s inside that directory, you’ll notice there are more .so files than we
actually need. Before building it, you’ll need to delete (or move) the following librar-
ies from $ITOA_NDK/itoa/platform/arch-arm/usr/lib:
libcg.so
libcore.so
libjnipp.so
libuikit.so
33.2.4 Compiling
Now that we have all the native code in place, we need to compile all the .so files. Run
this code
$ITOA_NDK/itoa-build
After the compilation procedure finishes, we’ll get every .so file needed to run our
Objective-C code in Android. In the next section, we’ll see how to call it from the Java
layer.
@Override
public void onCreate(Bundle savedInstanceState) { Set a text to
super.onCreate(savedInstanceState);
TextView using
TextFormatter’s
setContentView(R.layout.main); formatString
mTextView = (TextView) findViewById(R.id.text); method
String text = TextFormatter.formatString("Text from Java");
mTextView.setText(text);
}
}
www.finebook.ir
Using Scala inside Android 113
System.loadLibrary("objc");
System.loadLibrary("cf");
System.loadLibrary("foundation");
System.loadLibrary("textformatter");
System.loadLibrary("main");
}
}
The most important part of this piece of code is understanding what libraries will get
loaded inside the static block B. They include the following:
macemu: Contains emulation of some APIs used by objc4 and CoreFoundation
libraries
objc: objc4 runtime
cf: CoreFoundation classes
foundation: The Foundation library
textformatter: Our TextFormatter library
main: Our main library
When you run the application, you’ll see a TextView populated with a mixture of texts
from the Java and Objective-C worlds.
www.finebook.ir
114 CHAPTER 8 Interacting with other languages
Discussing the benefits of Scala over Java is beyond the scope of this book, but let’s
look at what’s possible with Scala. In this hack, we’ll create a two-Activity application.
One will be coded in Java and the other in Scala. This is a basic example we’ll use to
understand how to compile an Android application with Scala code.
As you might know, Android builds code by compiling your Java classes to byte-
code, and afterward that bytecode is converted to dex. To make Scala code work
inside Android, we need a tool that does all of this:
Converts Scala code to bytecode
Processes the Scala standard library to minimize the app size
Processes Java code
Creates an APK
Believe it or not, there are a lot of ways of getting this done. From my personal point
of view, the best tool is SBT with its Android plugin.
What is SBT? SBT stands for Simple Build Tool. It’s an open source build tool for
Scala. Among its benefits:
The project structure is similar to Maven.
It manages dependencies using existing Maven and/or Ivy package
repositories.
It allows you to mix Scala and Java
code.
What does the SBT Android plugin pro-
vide? The Android plugin is a script for
creating a new Android project that SBT
can compile. It also has several handy SBT
targets for doing things such as packaging
your app for the market and deploying to
your device.
If we create a new Android applica-
tion using the SBT Android plugin, we’ll
get a project directory structure similar
to figure 34.1.
Since SBT allows Java code as well,
we’ll add our Java code inside src/main/
java. Remember that, though Scala
doesn’t need to place files on a certain
folder depending of the defined pack-
age, Java does. In this hack, we’ll use Figure 34.1 SBT Android plugin project structure
www.finebook.ir
Using Scala inside Android 115
Do we need to do anything different to call the Activity done in Scala? No, there
isn’t anything special. We start the Scala Activity as any ordinary Activity B.
Now let’s take a look at the Scala Activity code to see what’s there:
www.finebook.ir
116 CHAPTER 8 Interacting with other languages
You can see that the Scala Activity’s code is 100% Scala. The Scala coded there
comes from the demo application created by the SBT Android plugin. Take a closer
look at how the content view is set B. That line creates an anonymous subclass of the
TextView, and with the help of an initializer block it calls the setText() method.
To run the application, we can launch SBT and execute the following:
android:package-debug
android:start-device
www.finebook.ir
Ready-to-use snippets
Do you sometimes use the same code in different applications? If so, this chapter is
for you. We’ll go through some code snippets that you can copy and paste into any
Android application.
One of the nicest features about Android is the intent system. If you want to share
something with another application, you can use an intent to do so. If you want to
open a link, you have an intent for that. In
Android, almost everything can be done with
an intent.
If you use the mobile messenger app,
WhatsApp, you might know that you can
share images with your contacts from an
image in the gallery or by taking a photo.
The dialog presented to the user to pick an
image from the gallery or to take a picture is
shown in figure 35.1. Obviously, this was cre-
ated with intents but, unfortunately, it can’t Figure 35.1 Dialog to choose how to
be done with only one. handle an action
117
www.finebook.ir
118 CHAPTER 9 Ready-to-use snippets
In this hack, we’ll analyze how this can be done. We’ll see which is the intent to
take a photo, which is the intent to pick a picture from the gallery, and how to com-
bine both.
startActivityForResult(chooserIntent, PICK_OR_TAKE_PICTURE);
Using the previous code will show all applications that handle both intents, taking a
photo and picking a picture. Remember that we need to override the onActivity-
Result() method inside our Activity to do something with the image picked/taken
by the user.
www.finebook.ir
Getting user information when receiving feedback 119
www.finebook.ir
120 CHAPTER 9 Ready-to-use snippets
...
We already have a class that takes care of getting the information, but how do we send
that through an email? We use the LaunchEmailUtil class:
public class LaunchEmailUtil {
public static void launchEmailToIntent(Context context) {
Method to
Intent msg = new Intent(Intent.ACTION_SEND);
be called
from the StringBuilder body = new StringBuilder("\n\n----------\n");
Activity B body.append(EnvironmentInfoUtil.getApplicationInfo(context));
msg.putExtra(Intent.EXTRA_EMAIL, C
Setting recipient
context.getString(R.string.mail_support_feedback_to)
.split(", "));
msg.putExtra(Intent.EXTRA_SUBJECT,
context.getString(R.string.mail_support_feedback_subject));
msg.putExtra(Intent.EXTRA_TEXT, body.toString());
Setting body text
msg.setType("message/rfc822"); using Environment-
Setting
title for
E D InfoUtil’s information
context.startActivity(Intent.createChooser(msg,
the picker context.getString(R.string.pref_sendemail_title)));
}
}
We can use this class from an Activity using the launchEmailToIntent() method B.
The logic is simple: we identify to whom we should send the email from
strings.xml C, and we provide a subject D. Just in case the user has more than one
application that takes care of sending emails, we’ll create a picker with a custom title E.
www.finebook.ir
Adding an MP3 to the media ContentProvider 121
If you’re an Android user, you should know that whenever you want to listen to new
music on your device, the only thing you need to do is copy those files onto the exter-
nal storage (usually an SD card). After the files are copied, you can open your music
player and the files will be there. How does this work?
Inside Android is something called a ContentProvider. A ContentProvider is the
correct way to offer data to external applications. For example, Android has a contacts
ContentProvider. This means that inside your device is an application (Contacts) that
offers a ContentProvider to handle contacts. As you can imagine, you’ll also find a
media ContentProvider.
When you copy your media files to the external storage, there’s a process that will
browse all the folders looking for media, and it will add it to the media Content-
Provider. After media’s added to the ContentProvider, everyone can use it.
Imagine you’re creating an application that downloads music. It’s important that
every media file you download gets added to the media ContentProvider. Otherwise,
the user will not be able to use that media from another application.
In this hack, we’ll look at two possible ways to add an MP3 file to the media Content-
Provider. The demo application will hold two MP3 files in the res/raw folder and we’ll
copy them to the external storage. After they’re copied, we can let the Content-
Provider know that we’ve added new media.
www.finebook.ir
122 CHAPTER 9 Ready-to-use snippets
values.put(Media.ARTIST, "Android");
values.put(Media.ALBUM, "60AH");
Complete all necessary
values.put(Media.TITLE, "hack037");
fields to insert media
values.put(Media.MIME_TYPE, "audio/mp3");
values.put(Media.DATA, LOOP1_PATH);
Insert values to Content-
getContentResolver().insert( Provider using its URI
Media.EXTERNAL_CONTENT_URI, values);
The ActionBar API was added to Android version 3.0 (Honeycomb). The idea behind
the ActionBar pattern is to have a place where you locate the user inside your applica-
tion and offer contextual actions.
www.finebook.ir
Adding a refresh action to the action bar 123
The second step is to create an activity, but instead of extending Activity, we need to
extend SherlockActivity. The code to show a progress icon in the action bar is the
following:
public class MainActivity extends SherlockActivity {
private static final int MENU_REFRESH = 10;
private MenuItem mRefreshMenu;
...
@Override Create
public boolean onCreateOptionsMenu(Menu menu) { refresh
mRefreshMenu = menu.add(MENU_REFRESH, MENU_REFRESH, menu
MENU_REFRESH, "Refresh");
mRefreshMenu.setIcon(R.drawable.menu_reload);
mRefreshMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
return true;
}
www.finebook.ir
124 CHAPTER 9 Ready-to-use snippets
The next step is to handle what to do when a user presses the Refresh button in the
action bar or the button in the middle of the screen. Both items should launch a back-
ground task. To simulate the background task, we’ll create an AsyncTask with the fol-
lowing code:
private class LoadingAsyncTask extends AsyncTask<Void, Void, Void> {
@Override
protected void onPreExecute() { Handle UI changes
super.onPreExecute(); when the task is
startLoading(); about to start
}
@Override
protected Void doInBackground(Void... params) { Sleep for
SystemClock.sleep(5000L); 5 seconds
return null;
}
@Override
protected void onPostExecute(Void result) { Handle UI changes
super.onPostExecute(result); when the task is
stopLoading(); about to finish
}
}
This method is called from the centered button from the Activity’s layout using the
android:onClick property and from the action bar in the onOptionsItemSelected()
method.
We have almost everything in place. The only missing part is how to handle UI
changes when the background process starts and finishes. For the centered button,
the logic is simple. We want to disable the button while the background task is work-
ing and enable it when finished. We can do this by using the setEnabled(boolean
enabled) method. The big question here is how to replace the progress menu item
with something spinning. To do that, we’ll use an ActionView.
The ActionView is explained in the documentation (see section 38.2):
www.finebook.ir
Adding a refresh action to the action bar 125
Now that we have the XML, the rest is quite simple. This is how the startLoading()
and stopLoading() methods handle the refresh menu item’s action view:
private void startLoading() {
mRefreshMenu.setActionView(R.layout.menu_item_refresh);
mButton.setEnabled(false);
}
private void stopLoading() {
mRefreshMenu.setActionView(null);
mButton.setEnabled(true);
}
www.finebook.ir
126 CHAPTER 9 Ready-to-use snippets
It’s common in Android to find applications that use other applications to help per-
form tasks. Thanks to Android’s Intent system, you can ask other applications to help
you finish a task. For example, instead of adding the logic to take a photo using the
camera, you can ask the photo application to do it for you and return the result.
Because you can create a program that offers its functionalities through an intent
call, the market has lots of applications your application can use.
In this hack, we’ll see how to check if an application is installed before trying to
launch an intent call. If it’s not installed, we’ll ask the user to get it from the market.
For this example, we’ll use Layar. Layar is an application that offers a mobile browser
that allows users to find various items based upon augmented reality technology.
Developers can create something called a layer, which shows points of interest inside
Layar’s browser. We’ll create an ordinary Android program that will have a link to a
Layar’s layer. To create our application we’ll need the following:
A way to know if Layar is installed
Code to open the market to download Layar
The intent call to open a specific layer
To check if Layar is installed, we’ll use the PackageManager class. The code to make
this check is the following:
public static boolean isLayarAvailable(Context ctx) {
PackageManager pm = ctx.getPackageManager();
PackageManager’s
try {
getApplicationInfo()
pm.getApplicationInfo("com.layar", 0); method
return true;
Indicates
} catch (PackageManager.NameNotFoundException e) {
application
return false; isn’t available
}
}
www.finebook.ir
Getting dependencies from the market 127
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
After creating
});
the AlertDialog,
return downloadDialog.show(); we can show it.
}
The final step is to add the login so we can decide if we should download Layar or
launch our layer through an intent. This is the logic executed when a button is
clicked:
public void onLayarClick(View v) {
if ( !ActivityHelper.isLayarAvailable(this) ) { Logic to show the
ActivityHelper.showDownloadDialog(this); download dialog.
} else {
Intent intent = new Intent();
If Layar is
intent.setAction(Intent.ACTION_VIEW); available, use its
Uri uri = Uri.parse("layar://teather/?action=refresh"); uri scheme to
intent.setData(uri); show the teather
startActivity(intent); layer inside the
}
Layar application.
www.finebook.ir
128 CHAPTER 9 Ready-to-use snippets
program to grab snapshots, instead of providing a new way to do it, you can ask it to
use the photo application, which is well known by every Android user.
One challenge that developers commonly face is displaying images from a network
location. This challenge often comes in different forms, such as displaying many
images in a list. An ideal solution for this type of challenge will include
Maintaining a responsive UI
Performing network and disk I/O outside the application’s UI thread
Support for view recycling, as in the case of a ListView
A caching mechanism for quickly displaying images
Many solutions to this problem use an in-memory cache for holding previously loaded
images and a thread pool for queuing up images to load. But an often-overlooked fea-
ture is the order in which images are requested.
Consider the case of a ListView where each row contains an image. If a user
“flings” the list in the downward direction, most image-loading solutions will request
each image in the order its parent View is displayed on the screen. As a result, when
the user stops scrolling, the rows currently on the screen, which are the most impor-
tant rows at the current point in time, will load last. What you want is for the last-
requested rows to “jump the queue” and be processed first.
www.finebook.ir
Last-in-first-out image loading 129
original version by removing the problematic use of one AsyncTask instance per get-
View() call by the application’s adapter. The sample implementation makes it possi-
ble to cause a runtime exception when scrolling up and down several times, resulting
in a RejectedExecutionException caused by too many AsyncTask instances, so that’s
fixed in the final example.
@Override
public int compareTo(LIFOTask other) {
return priority > other.getPriority() ? -1 : 1;
}
}
Our choice of base class here is important. We extend FutureTask, a class accepted by
the executor classes because it exposes a cancel method, much like the old implemen-
tation using AsyncTask.
Building off the LIFOTask class, we’ll use its compareTo() method and the Thread-
PoolExecutor class:
public class LIFOThreadPoolProcessor {
private BlockingQueue<Runnable> opsToRun =
new PriorityBlockingQueue<Runnable>(64, new Comparator<Runnable>() {
@Override
public int compare(Runnable r0, Runnable r1) {
www.finebook.ir
130 CHAPTER 9 Ready-to-use snippets
return 0;
}
});
The noteworthy part of the class is the parameters passed to the ThreadPoolExecutor
constructor. We let the client application choose the exact thread pool size, and
choose a PriorityBlockingQueue to hold the incoming tasks that the client applica-
tion submits. We then use the compareTo() method of the LIFOTask object to get our
desired ordering. Note that in this case, the keepAlive parameter is not applicable
given the core and max thread pool sizes used.
www.finebook.ir
Last-in-first-out image loading 131
As a result, every step of the Runnable used to process images checks if it should stop
performing work. A stop condition is detected if the host activity sets a flag with
ImageWorker’s setExitTasksEarly() method, which should be called from
onPause(). Additionally, a stop condition is detected if the cancel() method of
FutureTask is called.
40.4 Considerations
For use in a production application, the Android Training article suggests using a bet-
ter disk-caching solution. The implementation provided in the original article is lack-
ing in a few key areas. To provide a more complete example here, the disk cache
implementation was modified to support rebuilding the disk cache upon application
restarts, and no longer maintains two copies of downloaded files.
www.finebook.ir
www.finebook.ir
Beyond database basics
If you’ve been developing Android applications, you may have used a database to
persist information. In this chapter, we’ll cover some advanced tips for developers
who are familiar with using databases in Android.
133
www.finebook.ir
134 CHAPTER 10 Beyond database basics
All database operations in this application are performed using ORMLite, rather
than writing any SQL statements by hand. This approach can save time by reducing
the amount of code needed to create the database schema.
www.finebook.ir
Building databases with ORMLite 135
When designing an application that needs a relational database, it’s useful to first start
with a diagram of the data model like this one. This is known as an entity-relationship
diagram (ER diagram). ER diagrams are used during the design stage of development
to identify different entities and the relationships between them.
@DatabaseField
ORMLite requires
public Date publishedDate;
parameterless
public Article() { constructor
}
}
This class, when part of a full implementation, would result in the following CREATE
TABLE SQL statement:
CREATE TABLE 'article'
('title' VARCHAR, 'publishedDate' VARCHAR, 'text' VARCHAR,
'id' INTEGER PRIMARY KEY AUTOINCREMENT);
Note the annotation on the field id. We specify the parameter generatedId = true to
signify that this field is our primary key, and it should be automatically assigned by
SQLite. Also note that, by default, ORMLite uses our class name as the SQL table and
the names of the member variables as the columns of the table.
Last, observe that ORMLite requires a zero-parameter constructor on the classes it
operates on. When ORMLite creates an instance of this class, in the case of a query
which returns articles, it will use the parameterless constructor and set member vari-
ables using reflection (ORMLite can also use setters for member variables if preferred).
www.finebook.ir
136 CHAPTER 10 Beyond database basics
The additions here are many, and we’re not done yet. We now specify the name of our
table in the DatabaseTable B annotation and names of columns in the Database-
Field C annotations. We can use these public variables elsewhere in the host applica-
tion for querying purposes.
Additionally, we require that the name member must not be null (columns can be
null by default) D. Finally, consider the annotation on the parent member. Any
www.finebook.ir
Building databases with ORMLite 137
member variable which is defined as a table in our relation must be marked as for-
eign, using foreign = true E. This instructs ORMLite to only store the ID of the for-
eign object in the current table. Taking this class one step further, we can ensure that
a parent category must exist. The final member declaration of the parent looks like
the following:
@DatabaseField(foreign = true, foreignAutoRefresh = true,
columnName = PARENT_COLUMN, columnDefinition = "integer references " +
TABLE_NAME + "(" + ID_COLUMN + ") on delete cascade")
private Category parent;
We can fine-tune the exact SQL used to define this column using columnDefinition.
Here we have specified that the parent column has a foreign key to the categories
table (the same table on which it is defined). This states that values in the parent col-
umn must either be null or exist in the categories table in the _id column. We also
specify that records that refer to a parent category get deleted when the parent cate-
gory is deleted. This is known as a cascading delete. This last technique is not required
in a database, but for demonstration purposes we’ll include it. Our finished table for
the Category class looks like the following:
CREATE TABLE 'categories' ('parent' integer references categories(_id)
on delete cascade, 'name' VARCHAR NOT NULL ,
'_id' INTEGER PRIMARY KEY AUTOINCREMENT )
www.finebook.ir
138 CHAPTER 10 Beyond database basics
Notice the use of techniques described earlier, such as final variables for table and col-
umn names B, referential integrity using the columnDefinition element C, and the
requirement of setting foreign = true D when storing complex objects. The result-
ing table is as follows:
CREATE TABLE 'articlecategories'
('article_id' integer references articles(_id) on delete cascade,
'category_id' integer references categories(_id) on delete cascade,
UNIQUE ('article_id','category_id') );
www.finebook.ir
Building databases with ORMLite 139
Note that when using foreign keys, the ordering of these statements is critical. Since
ArticleCategory’s table references the corresponding tables of Article and Category,
it must be created after the tables it depends on.
At runtime, when ORMLite is first used to operate on the database, the onCreate()
method will be called. At that time, looking at the logcat output will show us the
exact statements used in the create process, for example:
INFO/TableUtils(2075): executed create table statement changed 1 rows:
CREATE TABLE 'categories'
('parent' integer references categories(_id) on delete cascade,
'name' VARCHAR NOT NULL , '_id' INTEGER PRIMARY KEY AUTOINCREMENT )
Implementing the onUpgrade() method will vary per application per upgrade. The
simplest implementation involves dropping each table with TableUtils.dropTable()
and then calling onCreate(). While perfectly suitable for development time, please
be careful to ensure users do not incur data loss in a production environment. A solid
implementation would likely transform data to the new schema, execute alter table
statements if needed, and only drop a table if it’s no longer required.
Finally, because we’re targeting API Level 8 and up with this application, we can
use foreign keys. However, foreign keys are not enabled by default. Doing so requires
executing one line of SQL, which we can do when the database is opened by overrid-
ing onOpen(), as follows:
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
db.execSQL("PRAGMA foreign_keys=ON;");
}
www.finebook.ir
140 CHAPTER 10 Beyond database basics
return instance;
} Specifies
filename and
private DatabaseHelper(Context context) { its version
super(context, DATABASE_NAME, null, DATABASE_VERSION); number
}
}
In the private constructor, we specify the filename of the database and its version num-
ber. The version number passed in the constructor works in conjunction with the
onUpgrade() method mentioned in the earlier section.
/* Remainder omitted */
After a DAO is obtained, it exposes a number of methods for creating, updating, delet-
ing, and querying for objects. To create a Category record in the database, for exam-
ple, we simply create a Category instance, fill out the information we want persisted,
and call the create() method on the DAO. ORMLite will then set the ID field of our
object that was assigned by the database. Suppose we wanted to create two categories,
one nested in the other. We can do so like this:
Category tutorials = new Category(); Get an
Create our object
tutorials.setName("Tutorials"); instance of
DatabaseHelper
DatabaseHelper helper = DatabaseHelper.getInstance(context); singleton
Dao<Category, Integer> categoryDao = helper.getCategoryDao();
www.finebook.ir
Building databases with ORMLite 141
Tutorials object
Category programmingTutorials; has its ID set, so
String title = "Programming Tutorials"; it’s used as parent
programmingTutorials = new Category(title, tutorials); in new category
categoryDao.create(programmingTutorials);
Reading a single object given its ID field is as simple as calling the queryForId()
method on the DAO. The DAO objects also expose updates and deletes to single
objects just as easily. By passing in an instance with its ID field already set, these opera-
tions are just as easy. Suppose we know the ID of the first item created in the previous
snippet. We can rename it as follows:
Category renamed = new Category(1, "Android Tutorials", null);
categoryDao.update(renamed);
When updating, it’s important that the source object has all appropriate member vari-
ables filled out. When deleting, all that’s required is the ID. In the above example, we
could, of course, have passed in the original instances tutorials and programming-
Tutorials to the update and delete methods respectively.
The methods on the QueryBuilder class can be used to form a query using the typical
SQL operators. You can use combinations of and(), or(), eq() for equals, not(), ge()
for greater than or equals, and so on to form your where clause. The QueryBuilder
and its update and delete counterparts use a fluent interface, meaning each method
returns a reference to the same object, so developers will typically “chain” calls
together for readability purposes.
www.finebook.ir
142 CHAPTER 10 Beyond database basics
Delete operations are very similar. Suppose we want to run a delete statement to
remove any articles that are older than 30 days. We can do that using the Delete-
Builder class, as in the following example:
Calendar cutoff = Calendar.getInstance();
cutoff.add(Calendar.DATE, -30); B
Calculates the date
PreparedDelete<Article> deleteStatement;
deleteStatement = (PreparedDelete<Article>)articleDao
Builds
where
C .deleteBuilder()
.where()
clause .lt(Article.PUBLISHED_DATE_COLUMN, cutoff.getTime())
D
Calls
prepare()
.prepare(); method
articleDao.delete(deleteStatement);
Let’s dissect the example. We first calculate the date that is 30 days prior B. We use
the lt() function to build our where clause C, specifying that we should delete val-
ues that are less than the given date. Finally, after calling the prepare() method D,
we must typecast this to a PreparedDelete. The reason for this is that the delete()
method on our DAO doesn’t accept a PreparedQuery, which is the type that pre-
pare() will return. We know ahead of time that this cast is correct. Note that in com-
parison operations, such as less-than, we must be careful to pass to the ORM the same
type as we defined in our class. Here we pass in a Date, which corresponds to the
member variable on the Article class:
private Date publishedDate;
Now, when an article is deleted, we must ensure that our data integrity is maintained.
In this case, that means the IDs we delete with this statement should no longer appear
in the Article to Category cross-reference table, and similarly, the IDs shouldn’t
appear in the Comment class’s table. Fortunately for us, our delete statement also has a
hidden feature. Because we took care when designing our database schema earlier, we
www.finebook.ir
Building databases with ORMLite 143
specified a cascading delete on the ArticleCategory class to take care of this for us.
We can also use the same strategy when implementing the Comment class. Thus, the
above delete query is all that’s needed to delete articles including any comments and
their mappings to categories.
These examples are just some of the types of statements we can form using the
builder objects. A full application will likely contain many more combinations of
selecting data and performing inserts, updates, and deletes. Furthermore, we have yet
to touch on the tricky subject of handling foreign object references and the options
available when querying for data stored in different tables.
This will result in a create table statement that uses the BIGINT storage class.
Now, let’s handle the case of a foreign object. We know that a Category can have a
parent, but how should the ORM behave when we retrieve a Category that has one?
Should the parent in its entirety be returned? What about the parent’s parent? ORM-
Lite introduces foreign auto refresh to specify this behavior and foreign refresh
level to configure it. In the default scenario, querying for a category will result in the
parent being set, with only the ID field populated. The default behavior here will be
the most efficient in terms of the SQL queries performed by the ORM. When enabling
the auto-refresh features, developers should be aware of a potentially large amount of
statements being executed, since the version at the time of writing (4.41) doesn’t per-
form joins, but instead, executes additional statements.
Here’s a concrete example for a one-to-one relation. Suppose we always want a
Category’s parent refreshed. We can set foreignAutoRefresh = true on the annota-
tion of the parent member variable, such as this:
@DatabaseField(foreign = true, foreignAutoRefresh = true,
canBeNull = true, columnName = PARENT_COLUMN,
columnDefinition = "integer references " + TABLE_NAME +
"(" + ID_COLUMN + ") on delete cascade")
private Category parent;
www.finebook.ir
144 CHAPTER 10 Beyond database basics
When enabling this feature, ORMLite will by default perform two levels of refresh.
With the above definition of the annotation, ORMLite will populate a Category, its
parent, and its grandparent (if available). The default of 2 can be changed using the
maxForeignAutoRefreshLevel element of the annotation. If anything, changing this
value to 1 would be the most likely change (again, increasing this value will result in
more SQL queries being executed).
Now, suppose we’re interested in a relation that is one-to-many, as in the case of
one Article with potentially many comments. We can introduce a member variable
on the Article class and annotate it as a ForeignCollectionField. We can use this
field to either selectively refresh all the comments, or have it automatically happen
when an article is loaded, as specified by the eager element. Here’s an example:
@DatabaseTable(tableName = Article.TABLE_NAME)
public class Article {
...
@ForeignCollectionField(eager = true)
private ForeignCollection<Comment> comments;
With this definition, ORMLite won’t add any extra columns to the generated table for
the Article class. Instead, it will spin up a DAO and query for all the comments associ-
ated with each article. As you can imagine, this may be costly when querying for many
articles if each article has many comments. Thus, we’ll see how to work with a non-
eager collection, which can be tricky. Let’s remove the eager = true element from our
annotation (false is the default):
@ForeignCollectionField
private ForeignCollection<Comment> comments;
Now, ORMLite won’t query for the associated comments by default. However, we must
be careful when dealing with the comments variable, since its type is ForeignCollec-
tion. When the collection is non-eager, invoking any method on the collection will
cause I/O, such as size() and iterator(). Also, our debugger may be calling
iterator() for us, resulting in unexpected I/O and a strangely populated collection
when we didn’t expect it. The ORMLite documentation recommends populating a col-
lection of this form by using the toArray() method on the collection. Here’s one
example of loading a single article, and then all of its comments:
DatabaseHelper helper = DatabaseHelper.getInstance(context);
Dao<Article, Integer> articleDao = helper.getArticleDao();
Article article;
article = articleDao.queryForId(1); Load single article
Comment[] comments;
comments = article.getComments().toArray(new Comment[0]); Load all comments
Last, please consult the documentation (http://mng.bz/84k8) on properly calling
close() on an iterator, such as one obtained from a ForeignCollection.
www.finebook.ir
Building databases with ORMLite 145
We’ll demo the RawRowMapper case, because it involves the most explanation, yet often
results in code that is easiest to reuse. Suppose we want a list of all the articles in the
database along with their category names (along with ID s). Using the ORM to perform
this operation would result in an amount of queries that is proportional to the num-
ber of entries in the database. We can do better by using one query that joins three
tables, namely, the tables for Article, Category, and the cross-reference class
ArticleCategory. Our query will be this:
select a.title, a._id, c.name, c._id from articles a, categories c,
articlecategories ac
where ac.article_id = a._id and ac.category_id = c._id;
@Override
public ArticleCategoryName mapRow(String[] columnNames,
String[] resultColumns) throws SQLException {
www.finebook.ir
146 CHAPTER 10 Beyond database basics
return result;
}
}
When parsing results in the mapRow() method, it’s important to check for data consis-
tency. Putting all the components together, we can get a list of all the article names
and their categories using this:
GenericRawResults<ArticleCategoryName> rawResults;
String query = "select a.title, a._id, c.name, c._id from articles a,
categories c, articlecategories ac
where ac.article_id = a._id and ac.category_id = c._id";
ArticleWithCategoryMapper mapper = new ArticleWithCategoryMapper();
rawResults = articleDao.queryRaw(query, mapper);
List<ArticleCategoryName> results = rawResults.getResults();
41.10 Transactions
Transactions are a key component in database operations, because they allow multiple
statements to be treated as a single atomic unit. A transaction guarantees that one of
two possibilities will happen:
All statements will be executed and committed if no errors are encountered.
If an error is encountered at any point in a transaction, the entire transaction is
rolled back.
As a convenience, ORMLite provides a class called TransactionManager that wraps the
details of beginning a transaction, marking one as successful, and ending a transac-
tion. A TransactionManager exposes just one interesting method, which is call-
InTransaction(). This method accepts a Callable, which is just like a Runnable,
except Callable has a return value.
To run a transaction, we choose to expose this feature as a method of our Orm-
LiteSqliteOpenHelper subclass, DatabaseHelper:
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
} catch (SQLException e) {
Log.e(TAG, "Exception occurred in transaction.", e);
throw new RuntimeException(e);
}
}
}
www.finebook.ir
Building databases with ORMLite 147
return article;
}
});
}
We chose to use a transaction in this case because we want both write operations to
succeed, or in the case of failure, to have no writes committed. This approach is rec-
ommended when performing multiple writes, for data consistency. Additionally, trans-
actions can in some cases increase the performance of a combination of statements,
especially a mix of reads and writes.
www.finebook.ir
148 CHAPTER 10 Beyond database basics
static {
System.loadLibrary("hack042-native"); B Load native library
}
www.finebook.ir
Creating custom functions in SQLite 149
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " +
"pois (" + C POIs table schema
"_id INTEGER PRIMARY KEY AUTOINCREMENT," +
"title TEXT," +
"longitude FLOAT," +
"latitude FLOAT);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion,
int newVersion) {
db.execSQL("DROP TABLE IF EXISTS pois;");
}
D getNear() Java
public List<Poi>getNear(float latitude, float longitude) { implementation
File file = mContext.getDatabasePath(DATABASE_NAME);
return getNear(file.getAbsolutePath(), latitude, longitude);
}
The fist important line is loading the native library B. System.loadLibrary() is usu-
ally called from a static block. This means that when the class is loaded, it will also
load the native library called hack042-native. In the onCreate() method C, we can
learn what the database schema looks like. Our DatabaseHelper class contains a get-
Near()D method that will be called when the user clicks on the Search button. This
method is just a wrapper for its native version E. The Java version is the public one
because the native implementation needs the database path, and only the Database-
Helper class knows where it is.
www.finebook.ir
150 CHAPTER 10 Beyond database basics
if (err != SQLITE_DONE) {
LOGI("Query execution failed: %s\n", sqlite3_errmsg(db));
}
sqlite3_finalize(stmt);
} else {
www.finebook.ir
Creating custom functions in SQLite 151
return objArr;
}
The first thing to notice is the difference between the Java and NDK signatures B.
Since we need to return a List<Poi>, we create a new ArrayList using JNI C. After
that, we can open the database using the path provided D and create a custom func-
tion passing a function pointer E. The distance() function is defined inside the
main.cpp file. After the custom function is created, we can write our query using the
distance() function F. The final step is iterating through the results G, create a Poi
object using the row data H, and add it to the list.
Now that we have all the native code in place, whenever we call the Database-
Helper’s getNear() method, it will use the custom function created in this section.
www.finebook.ir
152 CHAPTER 10 Beyond database basics
A good pattern inside Android applications is to save your data inside a database and
show it in a ListView using a CursorAdapter. If you use a ContentProvider to handle
the database operations, you can return a Cursor that will be updated whenever the
data changes. This means that if you do everything correctly, you can work on the
logic to modify the information inside a table from a background thread and the UI
will update automagically. The problem with this approach is that when you do a large
number of operations to the database, your Cursor will get updated frequently, mak-
ing your UI flicker.
In this hack, we’ll see how to use batch operations to avoid this flickering, creating
three possible implementations to understand the problem and find a solution:
Without batching
With batching
With batching and using the SQLiteContentProvider class
The demo application is simple. It shows a list of numbers from 1 to 100. When the
user clicks on the Refresh button, the old numbers are deleted and new ones are cre-
ated. To accomplish this, we’ll code three different
implementations of the following:
An Activity to display the numbers
An Adapter to create and populate the views
for the ListView
A ContentProvider to handle queries to the
database
A Service that will update the table through
the ContentProvider
You can see the finished application in figure 43.1.
Each row shows the database ID on the left and the
generated number on the right.
As you an imagine, most of the code for the three
solutions is similar. Every implementation will have
its own Activity, Adapter, Service, and Content-
Provider. Since you can go through the sample
code, here we’ll only discuss the differences, which
reside in the Service and in the ContentProvider. Figure 43.1 List with numbers
43.1 No batch
This is the simplest example. Inside the Service, we just hit the ContentProvider
whenever we want to do an operation to the table. Here’s the Service code:
www.finebook.ir
Batching database operations 153
@Override Before
protected void onHandleIntent(Intent intent) { inserting new
numbers,
ContentResolver contentResolver = getContentResolver(); delete all old
contentResolver.delete( ones.
NoBatchNumbersContentProvider.CONTENT_URI,
null, null);
Inside the for loop
for (int i = 1; i <= 100; i++) { create ContentValue
ContentValues cv = new ContentValues(); and insert number
cv.put( using ContentResolver.
NoBatchNumbersContentProvider.COLUMN_TEXT, "" + i);
contentResolver.insert(
NoBatchNumbersContentProvider.CONTENT_URI, cv);
}
}
}
Try running the application and test this implementation. The best way of noticing
the flickering is clicking on the Refresh button and trying to scroll over the list of
numbers. You’ll find out that it’s very difficult to scroll.
This happens because every time we do an insert or a delete using the NoBatch-
NumbersContentProvider, it does this:
getContext().getContentResolver().notifyChange(uri, null);
...
@Override
protected void onHandleIntent(Intent intent) {
Builder builder = null; Create list of
ContentResolver contentResolver = getContentResolver(); ContentProvider-
ArrayList<ContentProviderOperation> operations = Operations.
new ArrayList<ContentProviderOperation>();
www.finebook.ir
154 CHAPTER 10 Beyond database basics
builder = ContentProviderOperation
Create delete
.newDelete(BatchNumbersContentProvider.CONTENT_URI);
operation using
operations.add(builder.build());
ContentProvider-
Operation’s for (int i = 1; i <= 100; i++) {
Builder and add ContentValues cv = new ContentValues();
it to list of Create an
cv.put(NoBatchNumbersContentProvider.COLUMN_TEXT, "" + i);
operations insert
to apply. builder = ContentProviderOperation operation
.newInsert(BatchNumbersContentProvider.CONTENT_URI); per number.
builder.withValues(cv);
operations.add(builder.build());
} With list of operations
try { created, call
contentResolver.applyBatch( applyBatch() method.
BatchNumbersContentProvider.AUTHORITY, operations);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't apply batch: " + e.getMessage());
} catch (OperationApplicationException e) {
Log.e(TAG, "Couldn't apply batch: " + e.getMessage());
}
}
}
If you test this approach, you won’t notice any difference: the flickering is still there.
Why?
If you go to Android’s ContentProvider implementation, you’ll notice that
the applyBatch() method doesn’t do anything in particular. It just iterates through
the operations and calls the apply() method, which will end up calling our insert()
/ delete() methods inside the BatchNumbersContentProvider class.
All this might sound awkward, but it’s exactly what the applyBatch() method doc-
umentation says (see section 43.5):
www.finebook.ir
Batching database operations 155
There’s a class inside the Android Open Source Project ( AOSP) called SQLite-
ContentProvider that doesn’t belong to the SDK. It’s inside com.android.providers
.calendar. For this case, instead of extending ContentProvider, we’ll extend from
SQLiteContentProvider.
The Service code is exactly the same as the second approach, so let’s look inside
the SQLiteContentProvider’s applyBatch() method:
@Override
public ContentProviderResult[] applyBatch(
ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException { All operations are
mDb = mOpenHelper.getWritableDatabase(); applied inside database
mDb.beginTransactionWithListener(this); transaction.
try {
mApplyingBatch.set(true);
final int numOperations = operations.size();
final ContentProviderResult[] results =
new ContentProviderResult[numOperations];
So far, we know that every operation is applied inside a database transaction, but this
implementation still calls the apply() method for every operation. Why wouldn’t we
get a notification for every insert() / delete()?
To understand why this works correctly, we need to read the SQLiteContent-
Provider’s insert() method:
@Override
public Uri insert(Uri uri, ContentValues values) {
Uri result = null; Check if we’re
boolean applyingBatch = applyingBatch(); applying a batch.
if (!applyingBatch) {
mDb = mOpenHelper.getWritableDatabase();
mDb.beginTransactionWithListener(this);
try {
result = insertInTransaction(uri, values);
if (result != null) {
mNotifyChange = true;
}
mDb.setTransactionSuccessful();
www.finebook.ir
156 CHAPTER 10 Beyond database basics
} finally {
mDb.endTransaction();
}
If we’re inside batch
onEndTransaction(); operation, call
} else { insertInTransaction().
result = insertInTransaction(uri, values);
if (result != null) {
mNotifyChange = true;
If something was inserted,
}
turn mNotifyChange flag
} on so onEndTransaction()
method knows if it needs
return result; to omit notification.
}
The logic for insertInTransaction() is inside our implementation. It’s the same as
the others, but it lacks the notification logic.
If you run this implementation, you’ll see how the flicker disappeared because the
UI will only be refreshed when all the operations get applied.
www.finebook.ir
Avoiding fragmentation
Fragmentation is a serious issue for Android developers. In this chapter, we’ll look
at some tips on how to achieve certain tasks and still be backward compatible with
older versions.
Since the early beginnings of Android, the whole system has had a status bar at the
top of the screen. In Android Honeycomb, the status bar was moved to the bottom
of the screen.
Applications such as games or image viewers need the full attention of the user,
and most of them take the whole screen to display themselves. For instance, in the
default Gallery application, when you click on an image, it’s shown full-screen with-
out any other content.
Imagine you need to provide this feature in your application, and it needs to be
compatible with every Android version. In this hack, we’ll build a simple toy appli-
cation that will have a red background and, when we press it, the application will
enter lights-out mode. We’ll take care of Android 2.x and 3.x separately, but then
we’ll merge them into a single implementation.
157
www.finebook.ir
158 CHAPTER 11 Avoiding fragmentation
} else {
w.addFlags(
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
w.clearFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
mUseFullscreen = !mUseFullscreen;
}
});
}
The code is self-explanatory. We first remove the title bar B. This needs to be done
before the setContentView() call is made. Afterward, we make an ordinary set-
ContentView() call and ask for a reference to the root element of our view C. This
element will work as an on/off switch for the full-screen mode.
The last part of the code states how the full-screen mode should work. You can see
in D how a field variable is used to toggle the status.
www.finebook.ir
Handling lights-out mode 159
An important change in Android 3.x is that there are no physical buttons; they’re
all placed in the status bar. Because of that, the status bar can’t be dismissed, but it can
be dimmed.
Here’s the code:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main); B Reference to
root element
mContentView = findViewById(R.id.content);
mContentView.setOnSystemUiVisibilityChangeListener(
Hides
new OnSystemUiVisibilityChangeListener() {
or shows
public void onSystemUiVisibilityChange(int visibility) { C action bar
ActionBar actionBar = getActionBar();
if (actionBar != null) {
mContentView.setSystemUiVisibility(visibility);
D Visibility
parameter
if (visibility == View.STATUS_BAR_VISIBLE) {
actionBar.show();
} else {
actionBar.hide();
}
}
}
});
E Sets a click
listener
mContentView.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if (mContentView.getSystemUiVisibility() ==
View.STATUS_BAR_VISIBLE) {
mContentView.setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
} else {
mContentView.setSystemUiVisibility(View.STATUS_BAR_VISIBLE);
}
}
});
}
In a similar way to what we did before, we get a reference to the root element of our
view B. In Honeycomb, views have a new method called setOnSystemUiVisibility-
ChangeListener(). This was created to have a place to receive callbacks when the visi-
bility of the system bar changes. We’ll use this method to hide or show the action bar,
depending on the visibility parameter C, as you can see in D. In E, we set a click lis-
tener to the root view where we toggle the system UI visibility, which basically means
turning on and off the lights-out mode.
www.finebook.ir
160 CHAPTER 11 Avoiding fragmentation
We used the Build class to check the Android version. The Build class has a
VERSION_CODES B inner class that can be used to check which version the device is
running. Based on that, we start different Activitys C.
With every Android release, a new set of APIs is made available. Most of the time, this
means that developers will have new ways of showing their content or improving the
device’s functionality. Commonly, when users acquire a new Android version on their
device, they’ll want to take advantage of all the benefits that come with the new API,
but you may still want users with older versions to be able to continue using your appli-
cation. Is there a way to start using new APIs and still be backward compatible?
www.finebook.ir
Using new APIs in older devices 161
In this hack, we’ll see how to use new Android APIs and still be able to run on older
devices. We’ll create a demo application that shows the number of times it was
opened. That information will be persisted with the help of the SharedPreferences
class. In the sample, we’ll use two APIs that are available in newer Android versions.
The first one is a method that became available in Android v9. An apply() method
was added to the SharedPreferences.Editor class. The second one is an API that
became available in Android API Level 8. It allows us to declare inside the manifest
whether we’ll allow our application to be installed on the SD card. Users with API
Level 8 and up will be able to install the application on the SD card, while others will
need to install on the device’s internal storage.
In short, the apply() method should be used instead of commit() if we don’t need the
return value of the operation.
Since we want our demo application to be super-responsive, we want to use the
apply() method to avoid slow commits to the disk in the UI thread. To accomplish
that, we’ll borrow Brad Fitzpatrick’s code to use the apply() method when it’s avail-
able and fall back to commit() if it’s not. Brad Fitzpatrick is a developer working inside
the Android team.
Let’s first take a look at our Activity’s code:
public class MainActivity extends Activity {
private static final String PREFS_NAME = "main_activity_prefs";
private static final String TIMES_OPENED_KEY = "times_opened_key";
private static final String TIMES_OPENED_FMT = "Times opened: %d";
private TextView mTextView;
private int mTimesOpened;
@Override
public void onCreate(Bundle savedInstanceState) { B Sets content
view and gets
super.onCreate(savedInstanceState);
a reference to
setContentView(R.layout.main); TextView
mTextView = (TextView) findViewById(R.id.times_opened);
}
www.finebook.ir
162 CHAPTER 11 Avoiding fragmentation
@Override
protected void onResume() {
super.onResume();
We first set the content view and get a reference to the TextView that will hold the
information about how many times the app has been opened B. In the onResume()
method, we get the persisted information from the SharedPreferences and we popu-
late the TextView C. Finally, in the onPause() method, we get an Editor from the
SharedPreferences and we increment the times opened variable D. Note that instead
of calling apply() directly, we call it through the SharedPreferencesCompat class E.
Let’s take a look inside the SharedPreferencesCompat class to learn how it makes
everything work:
public class SharedPreferencesCompat {
private static final Method sApplyMethod = findApplyMethod();
}
editor.commit(); D
Falls back to commit()
}
www.finebook.ir
Using new APIs in older devices 163
It’s an optional feature you can declare for your application with the
android:installLocation manifest attribute. If you do not declare
this attribute, your application will be installed on the internal storage
only and it cannot be moved to the external storage.
To make it work, we’ll need to modify AndroidManifest.xml with the following lines:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.manning.androidhacks.hack045"
android:versionCode="1" Sets B
android:installLocation
android:versionName="1.0"
android:installLocation="preferExternal"> to preferExternal
What we’re saying with that line is something like this: “Compile with API Level 8 JARs
and use the new APIs, but let the application be installed on devices with API Level 4
onward.” Although this works, there are some caveats. Compiling against higher API
levels will make available backward-incompatible classes and methods. To give you an
example of this, if you call a method that’s not available in the running version, you’ll
get a java.lang.VerifyError exception.
www.finebook.ir
164 CHAPTER 11 Avoiding fragmentation
www.finebook.ir
Backward-compatible notifications 165
To see how it works, we’ll create a demo application that will mock a message applica-
tion. Because the application will be backward compatible, it will have two possible
flows—one using the notifications actions and one without them. To visualize this, you
can see the possible flows using a device with Android v2.3.7 (see figure 46.2) without
the new notification API, and one with Android v4.1.2 (see figure 46.3).
You’ll notice that without the new API, the user is obliged to enter the application.
With the new API, users can delete a message without entering the application and they
can reply directly without needing to go through the Activity holding the message.
www.finebook.ir
166 CHAPTER 11 Avoiding fragmentation
Let’s now discuss how to create the application. We’ll need three Activitys:
MainActivity—This will hold a button to launch the notification.
MsgActivity—The message itself with Reply and Delete buttons.
ReplyActivity—The Activity holding the reply EditText and the Discard
and Send buttons.
There’s nothing out of the ordinary in those Activitys. You can read their code in
this book’s sample code.
To handle all of the notification’s clicks, we need to use PendingIntents. The big
difference between the PendingIntent and the Intent classes is that the former is
used for later execution. From the documentation (see section 46.2):
The limitation to using PendingIntents is that we can’t do something like “Run this
piece of code.” We can only launch an Activity, a Service or a BroadcastReceiver.
We’ll need to cover two types of operations in the application—the ones that don’t
require a UI (delete, discard, send message) and those that do (read, reply to a mes-
sage). Operations that don’t require a UI would ideally require back-end logic, so we’ll
create a Service called MsgService.
We’ll also create a static class called NotificationHelper that will be in charge
of all the notification logic and the creation of the PendingIntents. It’s code is the
following:
public class NotificationHelper {
public static void showMsgNotification(Context ctx) {
Called by
final NotificationManager mgr;
MainActivity to
mgr = (NotificationManager) ctx
show notification
.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder =
new NotificationCompat.Builder(
ctx).setSmallIcon(android.R.drawable.sym_def_app_icon)
.setTicker("New msg!").setContentTitle("This is the msg title")
.setContentText("content...")
.setContentIntent(getPendingIntent(ctx));
builder.addAction(android.R.drawable.ic_menu_send,
ctx.getString(R.string.activity_msg_button_reply),
Reply
getReplyPendingIntent(ctx));
action is
builder.addAction(android.R.drawable.ic_menu_delete, added
ctx.getString(R.string.activity_msg_button_delete),
getDeletePendingIntent(ctx));
www.finebook.ir
Backward-compatible notifications 167
mgr.notify(R.id.activity_main_receive_msg, builder.build());
}
With the NotificationHelper class, we have everything we need to handle the notifi-
cations. We’ll now analyze part of the MsgService code. Because MsgService extends
IntentService, this is the onHandleIntent() method:
@Override
protected void onHandleIntent(Intent intent) {
if ( MSG_RECEIVE.equals(intent.getAction()) ) {
handleMsgReceive();
} else if ( MSG_DELETE.equals(intent.getAction()) ) {
handleMsgDelete();
} else if ( MSG_REPLY.equals(intent.getAction()) ) {
handleMsgReply(intent.getStringExtra(MSG_REPLY_KEY));
}
}
We’ll have one method per possible action. For the sake of brevity, let’s take a look at
handleMsgDelete():
private void handleMsgDelete() { B Removes a message
instead of creates a log
Log.d(TAG, "Removing msg...");
NotificationHelper.dismissMsgNotification(this);
Dismisses
}
C notification
In a complete implementation, we’d place some back-end logic to remove a message
instead of creating a log B. After the message is deleted, we can dismiss the notifica-
tion with the help of the NotificationHelper class C.
www.finebook.ir
168 CHAPTER 11 Avoiding fragmentation
As you can see, all of the logic is handled inside the Service.
www.finebook.ir
Creating tabs with fragments 169
<Button android:id="@+id/tab_red"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="Red" />
<Button android:id="@+id/tab_green"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="Green" />
<Button android:id="@+id/tab_blue"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="Blue" />
</LinearLayout>
The FrameLayout in B will be the fragment container. Every time the user presses on
a tab, the Activity will take care of placing the corresponding fragment there. In C
www.finebook.ir
170 CHAPTER 11 Avoiding fragmentation
we use the include tag to add the tab’s layout to the Activity’s view. Note that we
place the include in the bottom for it to be drawn on top of the fragment container.
We already have all the UI in place. Let’s see how we handle the logic from the
Activity:
public class MainActivity extends FragmentActivity {
Enable use of
@Override B fragments
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main); C Button sets click listener
that calls switchFragment()
findViewById(R.id.tab_red).setOnClickListener( with new instance of a
new OnClickListener() { fragment
@Override
public void onClick(View v) {
switchFragment(ColorFragment.newInstance(Color.RED, "Red"));
}
});
...
}
D Reads the
implementation
private void switchFragment(Fragment fragment) {
FragmentTransaction ft;
ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.main_fragment_container, fragment);
ft.commit();
}
}
www.finebook.ir
Building tools
The Android SDK comes with a lot of classes and code that help you create your
applications, but sometimes even this isn’t enough. For example, if you want to add
Google Analytics or you want to add a JSON parser, you’ll have to add some kind of
dependencies. The Android SDK doesn’t provide a way to handle dependencies,
other than placing JAR files in the /libs folder. Fortunately, it has other building
tools. Even if you don’t use third-party dependencies, you might want to separate
your application in different modules and add dependencies between them in
order to organize your code or create reusable components. What you can do to
get around this issue is to use Apache Maven. In this hack you’ll see how to use
Apache Maven to build your application and run tests.
171
www.finebook.ir
172 CHAPTER 12 Building tools
If you’ve used Maven for Java application dependencies, you’ll agree that it’s a
powerful tool, but it takes some time to get used to it. In this case, we’ll take a look at
Manfred Moser’s roboguice-calculator demo. In this project, Manfred used different
dependencies, making it an excellent example to demonstrate how Maven works.
To understand how Maven works, we’ll go through the different pom.xml sections.
The pom.xml is the only Maven-related file your project will have. In it you’ll tell
Maven your application name, the build dependencies, the test dependencies, and
how to create your APK. Maven first checks if you have the dependencies in the local
repository, which is located at ~/.m2/repository by default. If they’re not there, it will
take care of downloading them from a central repository.
The first part has the following code:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://Maven.apache.org/POM/4.0.0" groupId, artifactId,
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version, and
As with every packaging establish
XML file, xsi:schemaLocation="http://Maven.apache.org/POM/4.0.0
http://Maven.apache.org/Maven-v4_0_0.xsd"> unique identifier for
start with artifact in repository,
schemas and <modelVersion>4.0.0</modelVersion>
and in general (like
namespaces <groupId>org.roboguice</groupId> coordinates)
<artifactId>calculator</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>apk</packaging>
<name>calculator</name>
...
<dependency> C Android
dependency
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
www.finebook.ir
Handling dependencies with Apache Maven 173
...
<dependency>
<groupId>com.pivotallabs</groupId> D Robolectric
dependency
<artifactId>robolectric</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Every dependency has four important attributes, groupId, artifactId, version, and
scope. The first dependency is roboguice B. It has a groupId, artifactId, and
version, which corresponds to a released version in some Maven repository. Remem-
ber what we learned in the first section? That information is required if someone
needs to use your artifact as a dependency.
Although the roboguice dependency doesn’t contain the scope attribute, you
should know that compile is the default value. Compile dependencies are available in
all classpaths of a project because they get included in the APK.
The next dependency is Android itself C. When you use Maven to build Android
applications, you must always have Android as a dependency, but its scope is
provided. provided is much like compile, but it indicates that you expect the JDK or a
container to provide the dependency at runtime—in our case, the device running
Android.
The last dependency is robolectric D. Robolectric is a test framework, so we only
need that dependency when we’re compiling/running the tests. That’s what the test
scope is for. This scope indicates that the dependency is not required for normal use
of the application, and is only available for the test compilation and execution phases.
After the dependencies section in the pom.xml file, we have the build section,
which has the plugins section inside. This is where you’ll configure the Android
Maven plugin. Let’s take a look at the following code to see how it’s done:
<build>
<plugins>
<plugin>
<groupId>
com.jayway.Maven.plugins.android.generation2
</groupId>
<artifactId>
android-Maven-plugin
groupId, artifactId, and version
</artifactId>
<version> B
for android-Maven-plugin
3.0.0-SNAPSHOT
</version>
<configuration> android-Maven-
<androidManifestFile> C
plugin configuration
${project.basedir}/AndroidManifest.xml
</androidManifestFile>
<assetsDirectory>
${project.basedir}/assets
</assetsDirectory>
<resourceDirectory>
www.finebook.ir
174 CHAPTER 12 Building tools
${project.basedir}/res
</resourceDirectory>
<sdk>
<platform>10</platform>
</sdk>
<undeployBeforeDeploy>
true
</undeployBeforeDeploy>
</configuration>
<extensions>true</extensions>
</plugin>
...
</plugins>
</build>
Build plugins works in a way similar to dependencies. The previous code shows how
the android-Maven-plugin gets configured B. If we were configuring a dependency,
we’d need to provide a groupId, an artifactId, and a version.
You’ll notice that Apache Maven follows the convention-over-configuration para-
digm, which results in decreasing the number of decisions that developers need to
make, gaining simplicity, but not necessarily losing flexibility. A great example of this
approach can be seen where the android-Maven-plugin gets configured C. You might
want to place the AndroidManifest.xml somewhere else so you have an attribute to
modify the default location.
When the pom.xml is ready, you can treat your Android application as a Maven
artifact. If you run the mvn package, you’ll get a target directory with the APK inside. If
you want to get the application installed in all attached devices, you can run mvn
android:deploy.
www.finebook.ir
Installing dependencies in a rooted device 175
www.finebook.ir
176 CHAPTER 12 Building tools
49.1 Predexing
The first step is predexing the dependencies. This means converting the JARs to dex.
It can be done with the dx application inside the ANDROID_SDK/tools folder. For
example, if our dependency is called dep.jar, we’ll need to use the following line:
dx -JXmx1024M -JXms1024M -JXss4M
--no-optimize --debug --dex
--output=./dep_dex.jar dep.jar
www.finebook.ir
Installing dependencies in a rooted device 177
<permissions>
<library name="dep" B
Specifies library name
file="/data/data/com.dep.package/files/dep_dex.jar"/>
Writes path for
</permissions>
C predexed file
We first need to specify the library name B. This library name is the string that we
should place in the use-library tag. We also need to write down the path for the pre-
dexed file inside the device C. We can upload the predexed file using adb or using an
Android application. An example of an application doing the installation is placed in
the sample code. The application is a modification of Johannes Rudolph’s scala-
android-libs source code.
That’s it. We’re now using dependencies from the device instead of compiling them
every time we want to run the application. Remember to change the build tool to
avoid compiling the dependencies. For instance, in Apache Maven we can set the
scope to provided.
www.finebook.ir
178 CHAPTER 12 Building tools
www.finebook.ir
Using Jenkins to deal with device diversity 179
accessed by Jenkins (all of which is available in the sample code for this hack), the first
thing to do is to choose the set of emulated devices you want to test with. As a mini-
mum, you should test on each major Android OS version between your minSdk-
Version and the latest version available. Other factors to think about are screen
density, supported locales, and any hardware properties that are important to your
application (e.g., camera, accelerometer).
Under the Configuration Matrix heading, click Add Axis, choose User-defined Axis,
and in the Name field enter os. As the values, enter the following:
2.2 2.3.3 4.0.3 4.1
www.finebook.ir
180 CHAPTER 12 Building tools
As you might be thinking, each value represents an Android version to test on. You
could later add further axes for screen density, locales, and so on, but for now let’s
stick with just one. By entering four distinct values here, Jenkins will run four individ-
ual builds each time you start this job, with each build seeing a different value in the
os environment variable.
Next, click Run an Android Emulator During Build, and enter the following values
under Run Emulator with Properties:
Android OS version: ${os}
Screen density: 240
Screen resolution: WVGA
You can leave the other fields unchanged, but you should uncheck the Show Emulator
Window option. By setting the value ${os} as the Android version, this ensures a dif-
ferent Android emulator will be created in each of the four builds that will occur. The
complete configuration can be seen in figure 50.2.
www.finebook.ir
Using Jenkins to deal with device diversity 181
Figure 50.3 Project page showing the configurations and a build in progress
In the Build section, add the build steps Install Android Project Prerequisites and
Invoke Ant, assuming that you have used the android tool to generate Ant build
scripts for your application and test projects. As the targets, enter clean debug
install test. Click Advanced, and for the build file enter tests/build.xml (assum-
ing tests is the directory name you’ve used for your test suite). Add a property:
sdk.dir=$ANDROID_HOME.
If you have your Android test suite configured to output results in JUnit XML for-
mat (e.g., using the android-junit-report project), you can also check the Publish
JUnit Test Result Report option under the Post-build Actions section.
Press Save to finalize the job configuration. You now have a Jenkins job that will
run multiple times, each time checking out your source code, starting a different
Android emulator, and then building your application and running its test suite. The
job page should look like figure 50.3, with each ball representing one configuration
(that is, OS version). They’re gray to indicate that a build hasn’t yet occurred.
www.finebook.ir
182 CHAPTER 12 Building tools
By default, Jenkins runs two builds in parallel, so you’ll have to wait a few minutes
before everything completes. In any case, the first builds will take a little longer as the
emulators have to be generated and booted for the first time. Furthermore, if you
don’t have the Android SDK installed on the machine where Jenkins is running, it will
be automatically installed for you, which will add to the initial build time.
When the progress bars disappear from the Jenkins sidebar, the build is complete.
So within a few minutes you’ve automatically tested your software on four different
versions of Android—and Jenkins will continue to do this automatically each time it
finds a new commit in your code repository.
After you have the basics running, you can refine your Jenkins job configuration by
adding further axes. For example, add an axis for different screen resolutions, allow-
ing you to automatically create emulators to test layouts designed for different phone
or tablet devices.
The Android Emulator plugin also lets you run the Android monkey tool to stress-
test your UI. You could set up a Jenkins job that runs nightly, rather than for every
commit, and that builds your APK, installs it onto an emulator, and then runs monkey
against your application to check for instabilities.
www.finebook.ir
index
Symbols addPreferencesFrom- APP_SHARED_LIBRARIES
Resource() method 15 variable 112
@android, drawable 30 advice 100 architecture design using
&& (double ampersand) 100 @AfterReturning Android libraries 58–60
annotation 100 ArrayAdapter class 83
Numerics and() method 141 artifactId attribute 172–174
Android 1.6 90 @Aspect annotation 100
3D animation 101 Android 2.x 158 @AspectJ annotation 99
Android 3.0 122 AspectJ library 98–100
Android 3.x 158–159 aspectj-maven-plugin 99
A Android application package aspect-oriented program-
file. See APK ming. See AOP
Ableson, W. Frank 108 Android in Action, Third AsyncTask class 60–61, 124,
AbstractAccountAuthentica- 129–131
Edition 108
tor class 69 attributes, custom 12–13
Android libraries 59
AbstractThreadedSync- attrs.xml file 10
Android NDK 108, 148
Adapter 73 AuthenticationService 73
AccountAuthenticator Android, views in 9
Android.mk file 149 AuthenticatorActivity 68, 70, 72
class 72
AccountManager class 67–68, androidlib 59
70, 72 AndroidManifest.xml file 163, B
accountType attribute 72 177
ACTION_AUTHENTICATOR animations background color for text,
_INTENT action 69 applying to children customizing 82
ActionBar API 125 views 23 backgrounds
ActionBarSherlock 123–125 using Canvas class 25 removing to improve
actions in notifications 164 AnimationSet class 27 startup time 40–41
activities, improving startup AnimatorProxy class 27 rounded corners for 34
time of 40–41 AOP (aspect-oriented backward compatibility
Activity class 4, 15, 36, 43, 87, programming) 100 notifications 168
97, 112, 152 Apache Maven 99, 174, 177 storing app on SD card 163
<activity> element 38, 41 APK (Android application using apply() method
Adapter class 43–44, 77, 80, package file) 47, 172 instead of commit()
83, 87, 90, 152 <application> element 40 method 161–163
AdapterView class 77, 79, 89 ApplicationInfo class 126 BaseAdapter class 44
adb tool 177 apply() method 154, 161–163 batching operations for data-
addAcount() method, viewing applyBatch() method bases
when device is rotated 69 154–155 implementing 153–154
183
www.finebook.ir
184 INDEX
batching operations for data- using Objective-C in singleton pattern for data-
bases (continued) Android 112 base access 139–140
using SQLiteContent- ConnectionStatus class 54 SQLiteOpenHelper
Provider class 154–156 CONNECTIVITY_CHANGE class 138–139
vs. non-batched property 56 transactions in 146–147
operations 152–153 constructors 16 DatabaseTable class 135–136
BatchNumbersContent- ContentProvider class 63, 65, DatePicker class 30
Provider class 154 67, 73, 152–154 dates, avoiding validation of 30
BIGINT type 143 adding MP3 to 121–122 debug statements,
Bitmap class 23 ContentProviderResult removing 47–48
borders, rounded 34 class 154 DecorView class 40
BounceInterpolator class 23 ContentResolver class 65, 153 Delegate interface 43
BroadcastReceiver class 57, 166 ContentValue class 121, 153 delegates 87
Build class 160 convertView parameter 24, Delegation pattern 85, 87
build section (pom.xml 79–80 delete() method 142, 154
file) 173 countOf() method 142 DeleteBuilder class 141–142
Button class 3, 29, 34, 92 CountryView class 93 deleteBuilder() method 141
bytecode 98, 114 CRUD (create, read, update, dependencies 59–60
and delete) getting from market
C operations 140–141 127–128
Cursor 65, 67, 151–152 handling with Apache
calendar provider 155 CursorAdapter class 151–152 Maven 174
Callable class 146 custom functions for SQLite installing in rooted device
callInTransaction() Java code 148–149 creating permissions
method 146 native code 149–151 XML 176–177
cancel() method 131 modifying
Canvas class 23, 25, 102 D AndroidManifest.xml
CascadeLayout view 9–12 177
cascading delete 137 DAO (data access object) 140 predexing 176
centering objects in views 2–3 data models for databases device info, gathering when
Checkable interface 93–94 134–135 feedback is submitted 121
CheckBox class 94 data types using ORMLite Dialog style 43
CheckedTextView class 93 tool 143–144 didFinishLoading()
checkLayoutParams() DatabaseField class 135–136 method 55
method 11 DatabaseHelper class 63, 65, dimens.xml file 10
choiceMode for ListView 139, 148–149 distance() function 151
94–95 databases Dobjanschi, Virgil 62
CLEAN flag 74 batching operations for downloading Itoa library 108
close() method 144 implementing 153–154 draw methods 23
cocos2d-iphone library 107 using SQLiteContent- drawable 34
Cocos2d-x framework Provider class DrawView class 23–24
implementing 101–104 154–156 dropTable() method 139
overview 101 vs. non-batched duration parameter, Toast
ColorAdapter class 89–90 operations 152–153 class 41
ColorFragment class 89 ORMLite tool dx application 176
columnDefinition building queries 141–143
attribute 137–138 CRUD operations E
commit() method 161–163 140–141
compareTo() method 129–130 data models 134–135 EditText class 30
Compatibility Package data types in 143–144 EmailDialog class 16
revision 3 89 defining database empty lists, handling 78
compile-time weaving 98 schema 136–138 entity-relationship diagram.
compiling foreign types in 143–144 See ER diagram
in goal element 99, 173 raw SQL queries 145–146 env 111
Itoa library 108 requirements for 135 eq() method 141
www.finebook.ir
INDEX 185
www.finebook.ir
186 INDEX
launchEmailToIntent() M O
method 120
LaunchEmailUtil class 120 magazine-like application Objective-C in Android
Layar application 126–127 89–91 downloading and compiling
layers 126 main module 110–112 Itoa library 108
layout() method 12 MainActivity class 68, 100 Java portion 112–113
layout_* attributes 4–5, 12 mapRow() method 146 separating code in
layout_alignParentBottom modules 108–112
MapView 6
attribute 50 compiling 112
market, getting dependencies ItoaApp.mk file 109
layout_alignParentTop from 127–128 ItoaModule.mk file 109
attribute 50 Maven, Apache 174 main module 110–112
layout_height attribute 5 maxForeignAutoRefreshLevel textformatter
LayoutInflater class 35 144 module 109–110
LayoutParams class 11–13, 88 media scanner 122 Object-Relational Mapping.
layout_weight attribute 2–3 minSdkVersion 163, 179 See ORM
layout_width attribute 3, 5 mNotifyChange 156 onActivityResult() method 118
lazy loading of views 5–7 Model-View-Controller pat- onAuthenticationResult()
LEDs (light-emitting tern. See MVC method 72
diodes) 32 Model-View-Presenter pattern. onBind() method 69
LayoutAnimationController See MVP onClick property 124
class 21–22 onConfigurationChanged()
MODULE_* variables 110
LedTextView class 32–33 method 38
monkey tool 182 onCreate() method 15, 36, 68,
LENGTH_LONG property 41
Moser, Manfred 172 97, 100, 138–139, 149
LENGTH_SHORT property 41
move() method 25 onDraw() method 23–25
LIFOTask class 129–130
MP3 files, adding to onEndTransaction()
LIFOThreadPoolProcessor
ContentProvider 121–122 method 156
class 129
MsgActivity 168 onHandleIntent() method 167
light-emitting diodes. See LEDs
MsgService 166, 168 onItemClick listener 89
lights-out mode 158–159 onLayout() method 9–10, 12
multipleChoice setting 91–92
LinearLayout class 3, 40, 44, onMeasure() method 9–13, 39
88, 169 MVC (Model-View-Controller)
pattern 53 onOpen() method 139
lint issues 182 onOptionsItemSelected()
ListActivity class 82 MVP (Model-View-Presenter)
pattern 55–56 method 124
ListView class 21, 34, 79, 89, onPause() method 57, 87, 131
128, 130–131, 152 onPostExecute() method 130
achieving custom layout N onResume() method 55, 57,
using 89 87, 162
choiceMode for 94–95 name attribute 172 onSurfaceCreated()
handling empty lists 78 NameNotFoundException 126 method 104
header for 89 Nine Old Androids library 26 onUpgrade() method 138–140
section headers for NoBatchNumbersContent- OpenGL 101
adding 83–84 Provider 153 or() method 141
creating layout for 82–83 not() method 141 orientation
NotificationHelper class handling changes in
LOCAL_* variables 110
ViewPager 90–91
Log class 48 166–167
viewing video full-screen
log statements, removing 48 notifications when rotated 38–39
logcat output 47, 139 actions in 164 ORM (Object-Relational
long forms, creating using backward compatibility Mapping) 133
Gallery 46 for 168 ORMLite tool
Los Cocos 101 null 137 building queries 141–143
LruCache class 128, 131 NumbersAdapterDelegate
lt() method 142 interface 86
www.finebook.ir
INDEX 187
www.finebook.ir
188 INDEX
singleChoice setting 91–92 placing tabs in activity videos, viewing full-screen when
size() method 144 169–170 rotated 38–39
Skiba, Dmitry 108 TabWidget class 168 videoView class 36–38
slideshow using Ken Burns targetSdkVersion attribute 164 View class 3, 6, 9, 23, 34–36,
effect 27 test scope 173 102
.so files 109, 112 testability of code, ViewGroup class 3, 50
SoC (separation of concerns) improving 53–56 applying animation to chil-
concept 85 testing using Jenkins dren views 23
SpannableString class 31 creating job 179–181 creating custom
Spanned class 31 running job 181–182 adding custom attributes
splash screens 53 text, glowing effect for 33 to children views
SQL TextFormatter class 108, 12–13
injection attacks 145 111–112 CascadeLayout view 9–12
raw queries 145–146 textformatter module 109–110 ViewHolder class, creating fast
SQLite 133 TextSwitcher, transitions adapters with 80
custom functions for with 21
ViewPager class 46, 90–91
Java code 148–149 TextView class 4, 19–20, 32, 40,
views
native code 149–151 50, 82, 93
TextWatcher class 30 adding custom attributes to
other 133
ThreadPoolExecutor 129 children 12–13
SQLiteContentProvider
toArray() method 144 applying animation to
class 154–156
Toast class 42, 91 children 23
SQLiteOpenHelper class 63,
138–139, 148 TransactionManager class 146 centering objects in 2–3
startLoading() method 125 transactions using ORMLite getting height and width
startup time, improving 40–41 tool 146–147 of 36
static block 149 transitions, with TextSwitcher in Android 9
stopLoading() method 125 and ImageSwitcher 21 lazy loading of 5–7
String type 111 TranslateAnimation class 22 removing unnecessary 49–52
strings.xml file 120 Twitter 30 ViewStub class 5–7, 78
SurfaceView class 101 visual feedback 31
switchFragment() method 170 U Void type 140
SyncAdapter class
adding login UI thread 102, 130–131 W
functionality 67–73 unique element 137
adding SyncAdapter 73–75 UNIQUE statement 138 weightSum attribute 2–3
creating database for 63–66 uniqueCombo element 137 Wharton, Jake 26, 125
overview 62 update() method 57 WhatsApp 117
populating database 66–67 UpdateBuilder class 141 windowBackground
syncing data updateBuilder() method 141 attribute 41
using AsyncTask class 60–61 UriMatcher class 65 WindowManager class 24
using service 61–62 USE_CREDENTIALS wireframes, transferring to
System.loadLibrary() permission 68 layouts 87, 89
method 149 use-library tag 176–177
wizard forms 46
T V
X
TabActivity class 168 validating input, avoiding 30
TabHost class 168 VARCHAR type 143 XML files 4
TableUtils class 138 verbose flag 99
tabs, using fragments in version attribute 172–174 Z
creating tab UI 169 verticalSpacing attribute 13
Zombie Smash! 101
www.finebook.ir
ANDROID/MOBILE
H
acks. Clever programming techniques to solve thorny little
problems. Ten lines of code that save you two days of work.
The little gems you learn from the old guy in the next cube
or from the geniuses on Stack Overflow. That’s just what you’ll “ How to solve common
problems that arise in
find in this compact and infinitely useful book.
The name 50 Android Hacks says it all. Ranging from the mun-
dane to the spectacular, each self-contained, fully illustrated
Android development.
—From the Foreword by Jake
Wharton, Android Engineer
”
hack is just a couple pages long and includes annotated source
code. These practical techniques are organized into twelve
collections covering layout, animations, patterns, and more. “ One of the best how-
to books I’ve read!
—Christian Badenas”
What’s Inside Android and .NET Developer
● Hack 3 Creating a custom ViewGroup
● Hack 8 Slideshow using the Ken Burns effect
● Hack 20 The Model-View-Presenter pattern
“Packed with useful
Android development tidbits
not found in the official
● Hack 23 The SyncAdapter pattern
● Plus 43 more!
“ A great resource for creating
nontrivial user experiences for
www.finebook.ir