Download Android Development 1

Document related concepts
no text concepts found
Transcript
Android Development 1
Lesso n 1: Ge t t ing St art e d wit h Andro id De ve lo pm e nt
Abo ut Eclipse
Perspectives and the Red Leaf Ico n
Wo rking Sets
Hello , Andro id!
Create the Pro ject
Run the Applicatio n
Editing Pro grams
Andro id Package Structure
Bo nus Ro und
Wrapping Up
Lesso n 2: Act ivit ie s and Vie ws
Andro idManifest.xml
Activity Class
Basic View Co mpo nents: Layo uts and Butto ns
Layo uts
View Co mpo nents
Wrapping Up
Lesso n 3: Navigat io n wit h Dat a
Wo rking with Intent
An Emulato r Email Alternative
Sharing Data Between Activities
Sending Data to a New Activity
Returning Data to the Previo us Activity
Applicatio n Class
Wrapping Up
Lesso n 4: Andro id Re so urce s
String Reso urces
Lo ading Strings in XML
Lo ading Strings in Co de
The Reso urce Values Fo lder
Wrapping Up
Lesso n 5: Drawable s - Im age Basics
Drawable Fo lders and Qualifiers
Using Drawables
Dimensio ns
Image Padding
The ImageButto n Widget
Wrapping Up
Lesso n 6 : List s
Implementing an Andro id List
ListView
ListActivity
Empty Lists
ListAdapter
So rting the Adapter
Overriding ArrayAdapter
List Interactio n
Wrapping Up
Lesso n 7: Dialo gs, Ne w and Old
Old Style
AlertDialo g
Custo m Dialo g
New Style
Suppo rt Library
Fragments
Dialo gFragment
Wrapping Up
Lesso n 8 : Me nus
Menus, Menus, Menus
Optio ns Menu
Mo difying an Optio ns Menu
Co ntext Menu
Wrapping Up
Lesso n 9 : Saving Dat a wit h Share d Pre f e re nce s
Shared Preferences
Getting Started with SharedPreferences
PreferenceActivity
Wrapping Up
Lesso n 10 : Saving Dat a wit h a Dat abase
SQLite
Creating a Helper
Using the Helper
Curso r and Curso rAdapater
Wrapping Up
Lesso n 11: T hre ading wit h AsyncT asks
Threading in Andro id
AsyncTask
Tracking Pro gress
Wrapping Up
Lesso n 12: St yle s and T he m e s
Intro ductio n to Styling
Defining Styles
Defining Themes
Style Inheritance
Direct Theme References
Learning to Learn
Wrapping Up
Lesso n 13: Andro id Final Pro je ct
Final Pro ject
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Getting Started with Android Development
Welco me to the O'Reilly Scho o l o f Techno lo gy Andro id 1 co urse! We're glad yo u've decided to take this jo urney with us into
Andro id applicatio n develo pment. By the time yo u finish the co urse, we're co nfident that yo u'll have a firm grasp o n develo ping
applicatio ns fo r the Andro id platfo rm.
Course Objectives
When yo u co mplete this co urse, yo u will be able to :
use basic view co mpo nents and applicatio n classes.
pro gram strings, drawables, and lists.
display dialo gs, menus, styles, and themes.
save and manipulate data using Shared Preferences and SQLite databases.
use thread pro cesses.
create an applicatio n that implements multiple activities and can interact with a SQLite database.
In this co urse, yo u will learn the fundamentals o f writing Andro id applicatio ns. To pics co vered include activities, views,
navigatio n with data, drawables, lists, menus, saving data with an SQLite database, and threading. By the end o f the co urse, yo u
will be able to create an applicatio n that implements multiple activities and can interact with an SQLite database.
To be successful in this co urse, yo u must have a basic understanding o f o bject-o riented pro gramming and the Java
pro gramming language. If either o f tho se are unfamiliar to yo u, talk to yo ur instructo r abo ut taking the O'Reilly Scho o l o f
Techno lo gy Object Oriented Java co urse.
Learning with O'Reilly School of T echnology Courses
As with every O'Reilly Scho o l o f Techno lo gy co urse, we'll take a user-active appro ach to learning. This means that yo u
(the user) will be active! Yo u'll learn by do ing, building live pro grams, testing them and experimenting with them—
hands-o n!
To learn a new skill o r techno lo gy, yo u have to experiment. The mo re yo u experiment, the mo re yo u learn. Our system
is designed to maximize experimentatio n and help yo u learn to learn a new skill.
We'll pro gram as much as po ssible to be sure that the principles sink in and stay with yo u.
Each time we discuss a new co ncept, yo u'll put it into co de and see what YOU can do with it. On o ccasio n we'll even
give yo u co de that do esn't wo rk, so yo u can see co mmo n mistakes and ho w to reco ver fro m them. Making mistakes
is actually ano ther go o d way to learn.
Abo ve all, we want to help yo u to learn to learn. We give yo u the to o ls to take co ntro l o f yo ur o wn learning experience.
When yo u co mplete an OST co urse, yo u kno w the subject matter, and yo u kno w ho w to expand yo ur kno wledge, so
yo u can handle changes like so ftware and o perating system updates.
Here are so me tips fo r using O'Reilly Scho o l o f Techno lo gy co urses effectively:
T ype t he co de . Resist the temptatio n to cut and paste the example co de we give yo u. Typing the co de
actually gives yo u a feel fo r the pro gramming task. Then play aro und with the examples to find o ut what else
yo u can make them do , and to check yo ur understanding. It's highly unlikely yo u'll break anything by
experimentatio n. If yo u do break so mething, that's an indicatio n to us that we need to impro ve o ur system!
T ake yo ur t im e . Learning takes time. Rushing can have negative effects o n yo ur pro gress. Slo w do wn and
let yo ur brain abso rb the new info rmatio n tho ro ughly. Taking yo ur time helps to maintain a relaxed, po sitive
appro ach. It also gives yo u the chance to try new things and learn mo re than yo u o therwise wo uld if yo u
blew thro ugh all o f the co ursewo rk to o quickly.
Expe rim e nt . Wander fro m the path o ften and explo re the po ssibilities. We can't anticipate all o f yo ur
questio ns and ideas, so it's up to yo u to experiment and create o n yo ur o wn. Yo ur instructo r will help if yo u
go co mpletely o ff the rails.
Acce pt guidance , but do n't de pe nd o n it . Try to so lve pro blems o n yo ur o wn. Go ing fro m
misunderstanding to understanding is the best way to acquire a new skill. Part o f what yo u're learning is
pro blem so lving. Of co urse, yo u can always co ntact yo ur instructo r fo r hints when yo u need them.
Use all available re so urce s! In real-life pro blem-so lving, yo u aren't bo und by false limitatio ns; in OST
co urses, yo u are free to use any reso urces at yo ur dispo sal to so lve pro blems yo u enco unter: the Internet,
reference bo o ks, and o nline help are all fair game.
Have f un! Relax, keep practicing, and do n't be afraid to make mistakes! Yo ur instructo r will keep yo u at it
until yo u've mastered the skill. We want yo u to get that satisfied, "I'm so co o l! I did it!" feeling. And yo u'll have
so me pro jects to sho w o ff when yo u're do ne.
Lesson Format
We'll try o ut lo ts o f examples in each lesso n. We'll have yo u write co de, lo o k at co de, and edit existing co de. The co de
will be presented in bo xes that will indicate what needs to be do ne to the co de inside.
Whenever yo u see white bo xes like the o ne belo w, yo u'll type the co ntents into the edito r windo w to try the example
yo urself. The CODE TO TYPE bar o n to p o f the white bo x co ntains directio ns fo r yo u to fo llo w:
CODE TO TYPE:
White boxes like this contain code for you to try out (type into a file to run).
If you have already written some of the code, new code for you to add looks like this.
If we want you to remove existing code, the code to remove will look like this.
We may also include instructive comments that you don't need to type.
We may run pro grams and do so me o ther activities in a terminal sessio n in the o perating system o r o ther co mmandline enviro nment. These will be sho wn like this:
INTERACTIVE SESSION:
The plain black text that we present in these INTERACTIVE boxes is
provided by the system (not for you to type). The commands we want you to type look lik
e this.
Co de and info rmatio n presented in a gray OBSERVE bo x is fo r yo u to inspect and absorb. This info rmatio n is o ften
co lo r-co ded, and fo llo wed by text explaining the co de in detail:
OBSERVE:
Gray "Observe" boxes like this contain information (usually code specifics) for you to
observe.
The paragraph(s) that fo llo w may pro vide additio n details o n inf o rm at io n that was highlighted in the Observe bo x.
We'll also set especially pertinent info rmatio n apart in "No te" bo xes:
Note
T ip
No tes pro vide info rmatio n that is useful, but no t abso lutely necessary fo r perfo rming the tasks at hand.
Tips pro vide info rmatio n that might help make the to o ls easier fo r yo u to use, such as sho rtcut keys.
WARNING
Warnings pro vide info rmatio n that can help prevent pro gram crashes and data lo ss.
About Eclipse
We're using an Integrated Develo pment Enviro nment (IDE) called Eclipse. It's the pro gram filling up yo ur screen right
no w. IDEs assist pro grammers by perfo rming many o f the tasks that need to be do ne repetitively. IDEs can also help
to edit and debug co de, and o rganize pro jects.
Note
Yo u'll make so me changes to yo ur wo rking enviro nment during this lesso n, so when yo u co mplete the
lesso n, yo u'll need to exit Eclipse to save tho se changes.
The Eclipse windo w displays lesso n co ntent, and pro vides space fo r yo u to create, manage, and run pro grams:
Perspectives and the Red Leaf Icon
The Ellipse Plug-in fo r Eclipse, develo ped by the O'Reilly Scho o l o f Techno lo gy, adds an ico n to the to o l bar
in Eclipse. This ico n is yo ur "panic butto n." Since Eclipse is so versatile, yo u are allo wed to mo ve things
aro und, like views, to o lbars, and such. If yo u beco me co nfused and want to return to the default perspective
(windo w layo ut), clicking o n the Red Leaf ico n allo ws yo u to do that right away.
The
ico n has these functio ns:
It allo ws yo u to reset the current perspective, by clicking the ico n.
It allo ws yo u to change perspectives by clicking the dro p-do wn arro w beside the Red Leaf ico n and
selecting a series name (ANDROID, JAVA, PYTHON, C++, etc.). Mo st o f the perspectives lo o k
similar, but subtle changes may be present "under the ho o d," so it's best to use the co rrect
perspective fo r the co urse. Fo r this co urse, select Andro id.
Working Sets
All pro jects created in Eclipse exist in the wo rkspace directo ry o f yo ur acco unt o n o ur server. As yo u create
multiple pro jects fo r each lesso n in each co urse, it's po ssible that yo ur wo rkspace directo ry co uld beco me
pretty cluttered. To help alleviate the po tential clutter, in this co urse, we'll use working sets. A wo rking set is a
lo gical view o f the wo rkspace; it behaves like a fo lder, but it's really just an asso ciatio n o f files. Wo rking sets
allo w yo u to limit the detail that yo u see at any given time. The difference between a wo rking set and a fo lder is
that a wo rking set do esn't actually exist in the file system. A wo rking set is a co nvenient way to gro up related
items to gether. Yo u can assign a pro ject to o ne o r mo re wo rking sets. In so me cases, like with the Andro id
ADT plugin to Eclipse, new pro jects are created witho ut regard fo r wo rking sets and will be placed in the
wo rkspace, but no t assigned to a wo rking set (appearing in the "Other Pro jects" wo rking set). To assign o ne
o f these pro jects to a wo rking set, right-click o n the pro ject name and select the Assign Wo rking Se t s menu
item.
We've created so me wo rking sets in the Eclipse IDE fo r yo u already. To turn the wo rking set display o n and
o ff in Eclipse, see these instructio ns.
Setting Up Your Android Emulator
The Andro id team has made an excellent Eclipse plugin fo r Andro id called ADT (Andro id Develo per To o lkit). ADT helps
with Andro id develo pment in Eclipse in many different ways, so it's impo rtant that we get the Eclipse enviro nment and
ADT set up co rrectly fro m the start, so we can build and test o ur Andro id applicatio ns.
Note
The Andro id Develo per To o lkit plugin fo r Eclipse changes extremely frequently. The develo pers behind
the to o lkit are do ing amazing wo rk and co nstantly updating and impro ving the plugin. Ho wever, this
means the mo st recent versio n may differ fro m what yo u see here and what the instructio ns detail. Do n't
wo rry if what yo u see slightly differs fro m the instructio ns. While the lo o k, feel, and features may have
changed (likely fo r the better), the co re decisio ns and o ptio ns such as applicatio n and package names
will generaly still be reco gnizable. We perio dically update the to o lkit o n o ur systems.
Point ADT to the Android SDK
The ADT plugin is installed o n the instance o f Eclipse that yo u are using right no w. To o pen ADT, yo u can
either click the Andro id Virtual Device Manager ico n in the butto n bar at the to p, o r select Windo w | AVD
Manage r:
Go ahead and try that no w. Yo u'll pro bably get an erro r message info rming yo u that the Andro id SDK co uld
no t be fo und:
To fix this erro r, o pen the Eclipse preferences fro m the to o lbar menu by clicking Windo w | Pre f e re nce s. The
Eclipse preferences windo w will appear. Then click the Andro id sectio n o n the left. (Yo u may be asked if yo u
want to send usage data to Go o gle. Click "No .") Then, in the SDK Lo catio n field, type C:\Pro gram File s
(x86 )\Andro id\andro id-sdk and click OK.
Note
So metimes when reo pening a remo te Eclipse sessio n, ADT will fo rget that it already has the
lo catio n o f the SDK, and will po p-up the erro r again. If that happens, just o pen the Eclipse
Preferences windo w again (Windo w | Pre f e re nce s) and it sho uld sho w that the path is in there
already. Click OK and everything sho uld wo rk fine again.
Yo ur Preferences fo r Andro id will lo o k like this:
No w ADT is ready to go ! To test to make sure it's wo rking, o pen the ADT windo w by clicking the
butto n
o r selecting Windo w | AVD Manage r. The ADT dialo g windo w will o pen. Feel free to lo o k aro und in the
windo w to get an idea o f what go es o n there befo re yo u co ntinue o n to the next sectio n, where we'll create an
emulato r using the AVD Manager.
Note
Yo ur AVD Manager pro bably wo n't be empty like the screensho t abo ve. Due to the nature o f the
remo te develo pment enviro nment we're using and the way the AVD Manager handles
emulato rs, yo u'll pro bably see many o ther users' emulato rs. Co nversely, any changes yo u
make in the AVD Manager will be visible to o ther users as well. Please be respectful o f the o ther
users and do not mo dify o r delete any emulato rs o ther than tho se yo u've created fo r yo urself.
Create an Emulator
If yo u clo sed it, o pen yo ur ADT windo w again. This is the windo w that allo ws yo u to create and co nfigure as
many Andro id emulato rs as yo u like so yo u can test yo ur applicatio n o n vario us different hardware and
so ftware co nfiguratio ns. Fo r no w, we'll create a single emulato r.
On the right side o f the ADT windo w, click Ne w.... The "Create new Andro id Virtual Device (AVD)" wizard
appears.
Fo r the Name, enter your-ost-username-andro id2.2.3 (fo r example, if yo ur username is
jjam iso n, yo ur emulato r name wo uld be jjam iso n-andro id2.2.3).
In the Device dro pdo wn, select the Ne xus S.
in the Target dro pdo wn, select Andro id 2.2.3 - API Le ve l 10 .
Fo r the SD card, select the Size radio butto n and enter 20 MiB.
When yo u're ready, click Cre at e AVD at the bo tto m. Then, select yo ur new emulato r in the Virtual Devices list,
and click St art ... o n the right:
A Launch Optio ns windo w appears. The emulato r is actually a little to o big fo r o ur remo te Eclipse sessio n, so
we'll scale it do wn a little. Check the Scale display t o re al size bo x, enter 8.0 in the Screen Size (in.) field,
and then click Launch:
The emulato r will take a while to lo ad. No w might be a go o d time to po ur yo urself ano ther cup o f co ffee o r let
the do g o ut. When the emulato r is finally lo aded, yo u'll see it in ano ther windo w o n to p o f Eclipse.
At this po int, yo u can clo se the Virtual Device Manager windo w, but try no t to clo se the emulato r when
develo ping yo ur applicatio n. Yo u'll save a lo t o f time if yo u do n't have to sit thro ugh the bo o t-up pro cess o f
the emulato r. Alternatively, yo u might use the Snapshot feature in the Launch Optio ns windo w (abo ve). In
Snapsho t mo de, whenever the emulato r is clo sed, AVD saves a snapsho t o f the current state o f the emulato r,
which allo ws it to bo o t up faster. Ho wever, if yo ur emulato r ends up in a weird o r bro ken state, yo u'll need to
check the Wipe use r dat a bo x in the Launch Optio ns windo w when yo u restart it, in o rder to reset the
snapsho t state o f the emulato r.
To switch between this lesso n co ntent and the emulato r, use the tabs at the bo tto m o f the screen:
Note
Yo u can set up o ther emulato rs to match different devices, if yo u like. Always begin the emulato r
name with yo ur OST user name, so yo u can differentiate them fro m emulato rs created by o ther
users.
In the next sectio n, we'll finally dig into so me co de and run o ur first Andro id applicatio n!
Hello, Android!
Create the Project
We need an Andro id pro ject! Let's create o ne no w. Select File | Ne w | Ot he r, and then select Andro id
Applicat io n Pro je ct fro m the New Pro ject Windo w as sho wn belo w:
Note
When yo u finish the pro cess belo w to create yo ur new pro ject, ADT will likely auto matically
switch yo ur Eclipse Perspective to Java (which will hide this instructio n windo w). Do n't be
alarmed, just remember to select the Android perspective in the dro p-do wn again to return to the
lesso n.
No w yo u see the first windo w o f the "New Andro id Applicatio n" Wizard. This pro cess takes yo u thro ugh three
different windo ws to help set up yo ur new pro ject. In the first windo w, type the Pro ject name as He llo Wo rld,
enter the Package Name co m .o st .andro id1.he llo wo rld, and select the o ther o ptio ns as sho wn:
Click Ne xt . In the next windo w, uncheck the Cre at e cust o m launche r ico n bo x, and make sure the Add
pro je ct t o wo rking se t s bo x is checked and the Andro id1_Le sso ns wo rking set is entered in the
Wo rking Sets field:
Click Ne xt . In the next windo w, check the Cre at e Act ivit y bo x and select Blank Act ivit y:
Click Ne xt . In the next windo w, accept the default Activity Name MainAct ivit y, Layo ut Name act ivit y_m ain,
and Navigatio n Type No ne :
Click Finish.
Remember these steps—yo u'll need to perfo rm them fo r any new pro ject yo u create in this co urse.
If Andro id1_Le sso ns do esn't appear in yo ur Package Explo rer windo w, fix it no w. In the to p-right co rner o f
the Package Explo rer windo w, click the do wnward-po inting arro w and select Co nf igure Wo rking Se t s....
Check the bo xes fo r the Andro id1 wo rking sets and click OK:
No w yo u'll see tho se wo rking sets (and the Other Pro jects) in the Package Explo rer windo w:
Run the Application
To run the applicatio n, right-click the ro o t pro ject fo lder He llo Wo rld in the Package Explo rer, and select Run
As | Andro id Applicat io n. If yo ur emulato r was clo sed, it will o pen auto matically no w; if it was still o pen,
yo u'll have to bring the emulato r windo w back to the fro nt. If yo ur emulato r is in lo ck mo de, unlo ck it by
dragging the green unlo ck butto n to the right side o f the screen. Once ADT has finished installing the
applicatio n o nto the emulato r, it will launch auto matically.
It's no t much to lo o k at yet, but it's a great start. No w we have a so lid fo undatio n to start getting into so me real
Andro id applicatio n develo pment.
Editing Programs
When yo u create the pro ject, the act ivit y_m ain.xm l file, in the /re s/layo ut fo lder, is created:
By default, Andro id XML files lo ad in a Graphical Layout view. We'll talk abo ut that in detail later; fo r no w, we'll
fo cus o n the actual XML. Click the act ivit y_m ain.xm l tab in the lo wer po rtio n o f the edito r screen:
Edit the co de as sho wn:
CODE TO TYPE:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_worldHello World!" />
</RelativeLayout>
Save and run the applicatio n again. Yo u see yo ur new text:
Android Package Structure
Let's take a mo ment to get familiar with the Andro id package structure. Take no te o f the default files that were
created in the ro o t directo ry o f the pro ject:
All Andro id pro jects have an Andro idManif e st .xm l file, alo ng with a /re s fo lder, and a so urce fo lder, usually
titled /src. If yo u o pen the /src fo lder, and then the co m .o st .andro id1.he llo wo rld package, yo u'll see the
MainAct ivit y class that we defined when creating the pro ject. Go ahead and do uble-click that file to o pen it
no w:
Let's take a lo o k at the co de:
OBSERVE: MainActivity.java
package com.ost.android1.helloworld;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
This MainActivity is the first entry po int into o ur Java co de fo r this applicatio n. The o nCre at e () metho d is first
called when the Activity is created. We will co ver the Activity class in depth in the next lesso n, but fo r no w, just
be aware that each view in an applicatio n is co ntro lled by an Activity.
Also , no tice that the seco nd line o f o nCre at e () calls se t Co nt e nt Vie w(R.layo ut .act ivit y_m ain). This
metho d lo ads the view that MainActivity will co ntro l. R.layo ut .act ivit y_m ain is a reference to the
act ivit y_m ain.xm l file in the /re s/layo ut fo lder. Let's lo o k at that file again:
OBSERVE: activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World!" />
</RelativeLayout>
It may seem like there's a lo t go ing o n in this metho d, but we'll just fo cus o n the tag names fo r no w. This view
defines a Re lat ive Layo ut with o ne child, a T e xt Vie w.
Bonus Round
Haven't had eno ugh yet? That's great! There is so much mo re we can do no w that we have a running applicatio n. Let's
get back into the co de and start making so me changes o f o ur o wn!
Our earlier change was pretty straightfo rward. Let's try changing it up a bit mo re. Edit act ivit y_m ain.xm l again as
sho wn:
/res/layo ut/activity_main.xml
<RelativeLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World!" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="This is going to be the best app ever!" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Whoa, look, a button!" />
</RelativeLinearLayout>
Save and run it again to see yo ur view has gro wn:
After yo u run an applicatio n fo r the first time using right-click and Run As o r the Run menu, there's a
Note
faster way to run it. Yo u can click the Run ico n butto n
in the butto n bar at the to p. With Eclipse,
there's o ften mo re than o ne way to acco mplish a particular task. These sho rtcuts will help cut do wn o n
yo ur develo pment time, so yo u'll definitely want to use them!
Wrapping Up
We've co vered lo ts o f to pics here that are essential to every go o d Andro id develo per. Fro m setting up yo ur
enviro nment to creating an emulato r to creating and running an Andro id pro ject, these skills fo rm the fo undatio n fo r
building and testing any Andro id applicatio n. Yo u're do ing great—see yo u in the next lesso n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Activities and Views
Welco me back! In the last lesso n we co vered the fo undatio ns o f Andro id develo pment—setting up the Eclipse enviro nment with
ADT, creating an emulato r, creating a new Andro id pro ject, and installing and running it o n the emulato r. In this lesso n we'll learn
mo re abo ut views, and also explo re the fundamental classes o f every Andro id applicatio n.
AndroidManifest.xml
Every Andro id pro ject must have an Andro idManifest.xml file lo cated in the ro o t o f the pro ject directo ry. Think o f it as
the backbo ne o f yo ur Andro id applicatio n, defining the package name (unique fo r each applicatio n in the market), every
Activity and Service, each permissio n that the Applicatio n requires, and mo re. We'll refer back to the Andro idMainfest
o ften during the co urse.
Let's go back into o ur existing pro ject and use it to demo nstrate the impo rtance o f the Andro idManifest. We'll start by
writing so me co de to launch a new activity. Open yo ur pro ject, then o pen the MainAct ivit y.java file and then edit the
co de as sho wn:
MainActivity.java
package com.ost.android1.helloworld;
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.content.Intent;
android.view.Menu;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startHermes();
}
public void startHermes() {
Intent intent = new Intent(MainActivity.this, HermesActivity.class);
startActivity(intent);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Note
T he Im po rt ance o f im po rt : If yo u're familiar with Java, then yo u kno w the impo rtance o f impo rt
statements at the to p o f classes. Thro ugho ut the co urse we'll reference o ther classes that will require
additio nal impo rts (such as the Intent class abo ve, which requires the impo rt declaratio n import
android.content.Intent;). I will rarely refer to the exact impo rt updates that are necessary fo r each co de
change we make, tho ugh, because Eclipse can add tho se impo rts to o ur files auto matically. There are
vario us ways to get Eclipse to do this. Fo r example, yo u might use So urce | Organize Im po rt s o n the
menu o r the keybo ard sho rtcut Ct rl+Shif t +O. These co mmands will save yo u a lo t o f develo pment time
in Java.
The co de we've just written will launch a new Activity called HermesActivity during the o nCreate metho d. (The Intent
class is an impo rtant o ne in Andro id and it has many purpo ses beyo nd launching Activities; we'll discuss tho se o ther
purpo ses in detail a bit later.) Eclipse displays a red squiggly line under HermesActivity.class, because it do esn't exist
yet. Let's create it no w. Select File | Ne w | Class. In the "New Class" windo w, name the class HermesActivity and set
the superclass to be andro id.app.Act ivit y:
Click Finish to create the class. Yo u can clo se the HermesActivity.java class file no w, because we wo n't be mo difying
it fo r this demo nstratio n. No w let's run the applicatio n (click the Run
Note
ico n).
The emulato r sho uld start up auto matically if it wasn't already started befo re running the pro ject. It may
take a while fo r it to start tho ugh. When it finishes, the pro ject sho uld install o n to the emulato r
auto matically and execute. Altho ugh o n o ccasio n Andro id may think the emulato r has timed-o ut while
waiting fo r it to start; if that happens, just re-run the pro ject after the emulato r is up and running.
So , ho w do es it lo o k? Did yo u get an erro r message that lo o ked so mething like this?
Do n't wo rry. This was o ne o f tho se planned erro rs we sneak in fro m time to time to get yo u used to enco untering them
—and fixing them. This was the mo st co mmo n erro r I ran acro ss when I first do ve into Andro id pro gramming. Let's
take a lo o k at the lo gs and see what's go ing o n. Click the Lo gCat view tab o n the Package Explo rer pane:
This view displays all the lo g info rmatio n fro m a co nnected emulato r o r device. Scro ll do wn to the bo tto m o f the
Lo gCat view and find the red text (the co lo r used fo r Erro r lo gs). Click and drag the right edge o f the panel to widen it,
and ho ver with the mo use o ver the seco nd erro r. Yo u sho uld see so mething like this:
Yo u can always have multiple devices co nnected and multiple emulato rs running, but LogCat can o nly
display o ne device o r emulato r's lo gs at a time. If Lo gCat isn't sho wing yo u the lo gs yo u expected, use
Note
the DDMS perspective
in detail in a later lesso n.
to select the co rrect device/emulato r. We'll co ver the DDMS perspective
This erro r is a little vague, but in sho rt, it's saying it co uldn't find o ur Activity, HermesActivity. That's because we haven't
defined it in Andro idManifest.xml yet. As we discussed earlier, the Andro idManifest defines each Activity available to an
applicatio n. The declaratio n info rms the Andro id system which Activities are present and ho w they can be launched. So
let's add the declaratio n no w. Go back to the Package Explo rer tab (drag its right bo rder back to make it narro wer) and
o pen Andro idManif e st .xm l in the He llo Wo rld pro ject. There are a lo t o f different sub-screens available fo r
Andro idManifest to help mo dify the file using a GUI, but we'll just edit the XML directly. Click the
Andro idManif e st .xm l sub-tab in the bo tto m o f the view :
No w that we're in the co rrect view, let's add the HermesActivity to the manifest:
CODE TO TYPE:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ost.android1.helloworld"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10"
android:targetSdkVersion="10" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.ost.android1.helloworld.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".HermesActivity"
android:label="Hermes Activity"/>
</application>
</manifest>
Save the changes and run the applicatio n again. This time yo u do n't see the erro r—o r much o f anything else:
This pro bably do esn't co me as a surprise, tho ugh, because we didn't even add a view to o ur HermesActivity.
Let's analyze the changes we did make to the Andro idManifest:
OBSERVE:
<activity android:name=".HermesActivity"
android:label="Hermes Activity"/>
Each Activity in an Andro id applicatio n requires an <act ivit y /> no de, nested within the <applicatio n /> no de.
andro id:nam e refers to the name o f the activity prefaced by the package in which the Activity is lo cated, relative to the
package o f the applicatio n, which is defined by the attribute andro id:package in the ro o t <manifest> no de. The
package o f o ur applicatio n is "co m.o st.andro idhello wo rld," which we defined in the New Project wizard previo usly.
Since Hermes activity is lo cated in the ro o t o f o ur pro ject (and no t in a subfo lder), " .He rm e sAct ivit y" is sufficient fo r
the value o f the andro id:name attribute. This is the o nly required attribute fo r activity no des, but there are many o ther
o ptio nal parameters; fo r example, andro id:labe l specifies the text that appears in o ur o utput. We'll co ver a few mo re
o f these attributes in lesso ns to co me, but feel free to explo re the o ther po ssible attributes o n yo ur o wn.
Activity Class
In the MVC (Mo del-View-Co ntro ller) design pattern, the Activity class is co nsidered the Co ntro ller. The MVC pattern is
o utside o f the sco pe o f this co urse, but if yo u are unfamiliar with it, I highly reco mmend that yo u take a few minutes to
read abo ut it in Wikipedia. The Activity class is used to co mmunicate with the View by po pulating it with data (fro m the
Mo del) and handling o r respo nding to user interactio ns with the View.
We'll lo o k at the Activity class mo re later, but first we have to make o ne small change to o ur XML view. Open
act ivit y_m ain.xm l. If yo u haven't made any changes to it since we wo rked o n it befo re, the parent no de will still be a
LinearLayo ut with three child no des: two TextViews and a Butto n. Mo dify the Butto n no de in
act ivit y_act ivit y_m ain.xm l as sho wn:
/res/layo ut/activity_activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World!" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="This is going to be the best app ever!" />
<Button
android:id="@+id/my_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Whoa, look, a button!"/>
</LinearLayout>
Save the file, switch to the MainAct ivit y.java file, then edit yo ur co de as sho wn:
CODE TO TYPE:
package com.ost.android1.helloworld;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startHermes();
Button myButton = (Button) findViewById(R.id.my_button);
myButton.setOnClickListener(myButtonClickListener);
}
private OnClickListener myButtonClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
startHermes();
}
};
public void startHermes() {
Intent intent = new Intent(MainActivity.this, HermesActivity.class);
startActivity(intent);
}
}
After yo u've made tho se changes, when yo u fix the impo rts, yo u might be presented with multiple classes fo r
OnClickListener. Be sure to cho o se to impo rt the Vie w.OnClickList e ne r class.
Save yo ur changes and run the pro ject to see ho w these changes have affected the applicatio n. The ho me screen
pro bably lo o ks familiar to yo u, and no w when yo u click the butto n it will actually do so mething! If everything is ho o ked
up co rrectly in the co de, the butto n will cause o ur HermesActivity to lo ad, and the empty Hermes view sho uld be
visible.
So what did we do exactly? Let's review o ur changes, o ne by o ne:
OBSERVE:
Button myButton = (Button) findViewById(R.id.my_button);
First, we lo cated and sto red a reference to the Butto n into a variable . The f indVie wById() metho d co mes fro m the
parent Act ivit y class, takes o ne Integer parameter, and returns a generic View o bject, so we must cast it to its specific
class. Our parameter R.id.m y_but t o n is a reso urce reference to the id attribute we added to o ur XML view earlier. R
is a class that is generated by the Andro id co mpiler and auto matically po pulated with references to reso urces in the
/re s fo lder, because we referenced it earlier in the o nCreate() metho d to lo ad the view R.layo ut.activity_main.
OBSERVE:
myButton.setOnClickListener(myButtonClickListener);
Next, we attached a click listener to the butto n using the se t OnClickList e ne r() metho d, which takes a parameter o f
the Vie w.OnClickList e ne r type.
OBSERVE:
private OnClickListener myButtonClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
startHermes();
}
};
Finally, we created the m yBut t o nClickList e ne r o bject that we passed to setOnClickListener. View.OnClickListener
is an Interface that has o ne metho d, o nClick(Vie w vie w), to handle each click event. The View parameter sent to the
o nClick() metho d is a reference back to the View that dispatched the click event. Then, we call the metho d we defined
earlier to launch the Hermes Activity.
This is the mo st basic way o f respo nding to clicks o n Butto ns in Andro id. Originally, this was the o nly way to handle
clicks; but as o f Andro id versio n 1.6 , there is ano ther, mo re efficient way. Since o ur pro ject is already targeting versio n
2.2 o f Andro id, let's update o ur co de to use this alternate metho d o f handling clicks. Open act ivit y_m ain.xm l class
again and make the fo llo wing change:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World!" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="This is going to be the best app ever!" />
<Button
android:id="@+id/my_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Whoa, look, a button!"
android:onClick="handleMyButtonClick" />
</LinearLayout>
No w, mo dify MainAct ivit y.java belo w as sho wn:
MainActivity.java
package com.ost.android1.helloworld;
import
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.view.View.OnClickListener;
android.widget.Button;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button myButton = (Button) findViewById(R.id.my_button);
myButton.setOnClickListener(myButtonClickListener);
}
private OnClickListener myButtonClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
startHermes();
}
};
public void startHermes() {
Intent intent = new Intent(MainActivity.this, HermesActivity.class);
startActivity(intent);
}
public void handleMyButtonClick(View view) {
startHermes();
}
}
Run the pro ject again to test the co de. The app will functio n the same way it did befo re. This change simplifies the co de
fo r handling clicks siginificantly. Instead o f finding the butto n in the View and attaching a listener o bject to it, we use the
XML andro id:o nClick attribute to reference a metho d in o ur activity. There is no co mpiler-time checking fo r this metho d
name; the metho d is presumed to be present in the Activity that implements the View. If the metho d is no t present (o r if
it is defined inco rrectly) then the applicatio n will thro w an erro r when a user clicks the butto n. Metho ds referenced fro m
the andro id:o nClick attribute must have a return type o f vo id and receive o ne parameter o f type View.
Using this abbreviated metho d eliminates the need to sto re a reference to the butto n o n the View. It's still so metimes
necessary to get a reference to co mpo nents o n a View tho ugh, so yo u'll want to kno w ho w to use the findViewById()
metho d co rrectly.
Basic View Components: Layouts and Buttons
Layouts
No w that we kno w a bit mo re abo ut co ntro lling o ur Views, let's explo re so me mo re features o f Andro id XML
Views, starting with layo uts. When yo u first create a View using the ADT wizard, it's po pulated with a
LinearLayout tag fo r its ro o t no de auto matically. The LinearLayo ut tag is used fo r arranging elements
auto matically, in a single directio n, either ho rizo ntally o r vertically. Ho rizo ntal layo ut is the default directio n. To
change the directio n, use the android:orientation attribute.
There are two o ther co mmo n layo uts to co nsider using when setting up yo ur views, RelativeLayo ut and
FrameLayo ut. RelativeLayo ut allo ws yo u to define po sitio n co nstraints fo r a view's co mpo nents, relative to
the parent and o ther co mpo nents. FrameLayo ut puts each child o n a separate layer (o r frame), stacking them
o n to p o f each o ther.
View Components
There are many co mpo nents available to use fo r Andro id views. We've already used two in o ur main view—
TextView and Butto n. The standard view elements yo u wo uld expect to see such as tabs, checkbo xes, radio
butto ns, to ggle butto ns, and editable TextViews (called EditText), are already available. We're no t go ing to
co ver each available co mpo nent in this co urse, but yo u'll pro bably want to lo o k into the available
co mpo nents o n yo ur o wn in the Andro id SDK o n the do cumentatio n site o r using the Graphical Layo ut XML
edito r pro vided by ADT.
Wrapping Up
We've co vered a lo t in this lesso n! Yo u sho uld feel co mfo rtable using the Activity class to find View co mpo nents and
handle click events, and yo u sho uld kno w abo ut many o f the different types o f View co mpo nents available in Andro id.
Feel free to experiment so me mo re o n yo ur o wn until yo u feel co nfident in using tho se to o ls. See yo u in the next
lesso n, where we'll dig even further into Navigatio n and Data!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Navigation with Data
Welco me back! Earlier we mentio ned starting a new Activity with an Intent. In this lesso n, we'll go o ver Activities in mo re depth.
We'll also talk mo re abo ut the Intent class, and vario us metho ds fo r sharing data between activities.
Working with Intent
In the previo us lesso n we started a new Activity by creating an Intent and sending it to o ur current activity's startActivity()
metho d. Here's the co de we used in o ur MainActivity class to start the He rm e sAct ivit y:
OBSERVE:
Intent intent = new Intent(MainActivity.this, HermesActivity.class);
startActivity(intent);
The Intent class in Andro id is used fo r much mo re than just starting Activities. Think o f the Intent class as yo ur way o f
letting the Andro id OS kno w o f yo ur "intent" to perfo rm an actio n.
Fo r example, to start a Service, yo u call Act ivit y.st art Se rvice and send an int e nt as the parameter. Yo u can also
use Intents to request that an actio n be perfo rmed in ano ther applicatio n, such as o pening a web page in the bro wser,
sending a text message, o r sending an email. Let's try do ing that last o ne no w.
First, we'll add a new butto n to o ur view to start this actio n. Edit act ivit y_m ain.xm l as sho wn here:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="This is going to be the best app ever!" />
<Button
android:id="@+id/my_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Whoa, look, a button!"
android:onClick="handleMyButtonClick" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send Email"
android:onClick="handleSendEmailClick" />
</LinearLayout>
No w, in MainAct ivit y.java, add the lo gic to be perfo rmed when this butto n is clicked:
MainActivity.java
package com.ost.android1.helloworld;
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startHermes() {
Intent intent = new Intent(MainActivity.this, HermesActivity.class);
startActivity(intent);
}
public void handleMyButtonClick(View view) {
startHermes();
}
public void handleSendEmailClick(View view) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("plain/text");
startActivity(Intent.createChooser(emailIntent, "Email"));
}
}
Save and run it. If we try to test this no w using the Andro id emulato r, we wo n't see much o f anything aside fro m a
warning message.
This is because the default Andro id emulato r do esn't co me pre-bundled with any applicatio ns that suppo rt sending
email. Specifically, the emulato r do esn't have any applicatio ns that handle the Intent.ACTION_SEND actio n.
The best way to test this co de wo uld be to install the applicatio n o n an actual device. But even if yo u o wn an Andro id
device, yo u wo n't be able to install the applicatio n to it because the remo te deskto p co nnectio n enviro nment can't
reco gnize a device attached to yo ur lo cal co mputer. Yo u wo uld have to set up the andro id SDK and develo per
enviro nment o n yo ur o wn co mputer in o rder to install to yo ur o wn device. That's pretty extreme fo r o ur purpo ses, but
do n't wo rry—we can wo rk aro und that.
An Emulator Email Alternative
I've created a basic mo ck applicatio n to handle the email intent, which yo u can do wnlo ad directly fro m the
emulato r. Open the bro wser o n the emulato r and type the url
ht t p://co urse s.o re illyscho o l.co m /andro id1/so f t ware /Mo ck.apk:
This do wnlo ads the applicatio n. Once it finishes do wnlo ading, drag do wn the windo w no tificatio n shade, and
click o n the do wnlo ad co mplete no tificatio n to install it:
When the ico n appears, click and drag anywhere in the to p bar to pull do wn the "windo w shade."
This applicatio n wo n't actually send email, but no w, when yo u test the co de we wro te earlier, yo u'll see mo re
than just the "No applicatio ns can perfo rm this actio n" message. Instead, yo u'll see this:
Using this emulato r, we can also define the fields o f the email—such as the T o , the Subje ct , and the Bo dy—using
the Int e nt .put Ext ra metho d:
OBSERVE:
public void handleSendEmailClick(View view) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("plain/text");
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[]{"predefined@e
mail"});
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "predefined Subject");
startActivity(Intent.chooseIntent(emailIntent));
}
Fo r a list o f available Intent actio ns, see the andro id do cumentatio n site fo r the Intent class.
Sharing Data Between Activities
So metimes yo u'll need to pass data fro m o ne activity to ano ther. We can do that using the Intent class as well. In fact,
yo u've already do ne that o nce befo re in o ur example when yo u started the email Intent by using the Int e nt .put Ext ra
metho d. Let's update o ur applicatio n to send so me data back and fo rth between the MainActivity and the
HermesActivity.
Sending Data to a New Activity
First, let's add an EditText to o ur act ivit y_m ain.xm l so we can get so me user-defined text.
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="This is going to be the best app ever!" />
<Button
android:id="@+id/my_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Whoa, look, a button!"
android:onClick="handleMyButtonClick" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send Email"
android:onClick="handleSendEmailClick" />
<EditText
android:id="@+id/my_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Next, edit MainAct ivit y.java as sho wn:
MainActivity.java
package com.ost.android1.helloworld;
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.EditText;
public class MainActivity extends Activity {
private EditText myEditText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myEditText = (EditText) findViewById(R.id.my_edit_text);
}
public void startHermes() {
Intent intent = new Intent(MainActivity.this, HermesActivity.class);
intent.putExtra(HermesActivity.MY_EXTRA, myEditText.getText().toString()
);
startActivity(intent);
}
public void handleMyButtonClick(View view) {
startHermes();
}
public void handleSendEmailClick(View view) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("plain/text");
startActivity(Intent.createChooser(emailIntent, "Email"));
}
}
int e nt .put Ext ra uses a key/value pair system to sto re and retrieve the data being shared. The first
parameter is the key, and is always a String value. Because this value must be exactly the same fo r sto ring
and retrieving the value fro m the Intent, it's a go o d idea to use a static co nstant value here that bo th Activities
can access. We're using the value o n HermesActivity, which we just defined.
The seco nd parameter to Intent.putExtra is the value. This parameter must be a primitive data type (such as
Integer, Lo ng, Flo at, o r String) o r it must be an o bject that implements the Parcelable interface. Fo r no w, we're
o nly go ing to be sharing primitives between o ur activities; we'll talk abo ut using the Parcelable interface in a
later lesso n.
When yo u save this file, yo u'll see a co mpiler erro r o n the seco nd line o f the startHermes metho d. This is
because we haven't defined the MY_EXTRA variable in HermesActivity yet. Let's do that befo re we pro ceed
any further. Update He rm e sAct ivit y.java as sho wn:
HermesActivity.java
package com.ost.android1.helloworld;
import android.app.Activity;
public class HermesActivity extends Activity {
public static final String MY_EXTRA = "myExtra";
}
Save yo ur changes and run the pro ject; yo u'll see the new EditText field o n the screen.
Returning Data to the Previous Activity
When sending data to a previo us Activity, we use the Intent class, but the pro cess is a bit different fro m the
pro cess used when sharing in the o ther directio n. First o f all, if an Activity expects to receive data fro m an
Activity it starts, then it needs to use a different metho d to start that Activity.
We also need to add ano ther metho d to handle receiving the data. Make the changes to MainAct ivit y.java
as sho wn:
MainActivity.java
package com.ost.android1.helloworld;
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.EditText;
public class MainActivity extends Activity {
private EditText myEditText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myEditText = (EditText) findViewById(R.id.my_edit_text);
}
public void startHermes() {
Intent intent = new Intent(MainActivity.this, HermesActivity.class);
intent.putExtra(HermesActivity.MY_EXTRA, myEditText.getText().toString()
);
startActivity(intent);
startActivityForResult(intent, HermesActivity.EXTRA_REQUEST);
}
public void handleMyButtonClick(View view) {
startHermes();
}
public void handleSendEmailClick(View view) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("plain/text");
startActivity(Intent.createChooser(emailIntent, "Email"));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data
) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode) {
case HermesActivity.EXTRA_REQUEST:
if (resultCode == RESULT_OK) {
String stringExtra = data.getStringExtra(HermesActivity.MY_EXTRA
);
myEditText.setText(stringExtra);
}
break;
}
}
}
Next, we'll need to update bo th HermesActivity and its view. Well, actually, we haven't created a view fo r
HermesActivity yet, so let's start there. To create the new layo ut XML file, we'll use the ADT wizard. Select File
| Ne w | Ot he r and cho o se Andro id XML Layo ut File in the Andro id fo lder. Name the file
he rm e s_vie w.xm l and click Finish. Leave the rest o f the settings at their default values.
Click the he rm e s_vie w.xm l tab at the bo tto m and make these changes to the file:
CODE TO TYPE:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/hermes_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Finish"
android:onClick="onFinishClick"
/>
</LinearLayout>
Finally, we'll update He rm e sAct ivit y to handle the data passed to it fro m the previo us Activity, apply that
data to the EditText, and respo nd to the Finish butto n being clicked by clo sing the Activity and sending the
data back to the fo rmer Activity. Mo dify yo ur co de as sho wn:
CODE TO TYPE:
package com.ost.android1.helloworld;
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.EditText;
public class HermesActivity extends Activity {
public static final String MY_EXTRA = "myExtra";
public static final int EXTRA_REQUEST = 0;
private EditText hermesEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.hermes_view);
hermesEditText = (EditText) findViewById(R.id.hermes_edit_text);
Intent i = getIntent();
if (i.hasExtra(MY_EXTRA))
hermesEditText.setText(i.getStringExtra(MY_EXTRA));
}
public void onFinishClick(View view) {
String text = hermesEditText.getText().toString();
Intent i = new Intent();
i.putExtra(MY_EXTRA, text);
setResult(RESULT_OK, i);
finish();
}
}
Run the applicatio n and test the co de. The pro cesses fo r sending data to an Activity and receiving data back
fro m a started Activity are really similar. Bo th invo lve sto ring and retrieving data by use o f an Intent o bject.
Yo u'll be able to make changes to the EditText in either activity, and then see the result when navigating to the
o ther Activity using the Who a, lo o k, a But t o n! and Finish Butto ns.
Application Class
Yo u can also share data between multiple Activities thro ugho ut an Applicatio n using a custo m Applicatio n class. As
we mentio ned earlier, every App o n Andro id has a single Application class. This class is essentially a singleto n (a
design pattern that restricts the instantiatio n o f a class to o ne o bject), and we can o verride the class with o ur o wn
custo m extensio n o f the Applicatio n class to sto re state data.
Note
Do no t abuse the singleto n mo del in the Applicatio n class. The Andro id develo per do cumentatio n o n
develo per.andro id.co m reco mmends using the Applicatio n class o nly fo r sto ring sessio n state. Yo u
co uld also just sto re yo ur state data o n a helper class using public static variables. This wo uld allo w yo u
to keep yo ur co de mo re mo dular and remo ve any dependencies o n the Applicatio n framewo rk.
Let's make a custo m Applicatio n class no w to sto re so me applicatio n data. First, create a new class called
MyApplicat io n and make it extend the andro id.app.Applicat io n class.
The Applicatio n class has lifecycle metho ds similar to the Activity class. Any default data initializatio n sho uld o ccur
during the Applicatio n.o nCreate() metho d. Edit yo ur new class as sho wn:
MyApplicatio n.java
package com.ost.android1.helloworld;
import android.app.Application;
public class MyApplication extends Application {
public String defaultString;
@Override
public void onCreate() {
super.onCreate();
defaultString = "some default text";
}
}
To get the Applicatio n to use o ur new class, we'll need to update the Andro idManif e st .xm l file. Update the
<applicat io n> tag with a reference to the new class:
Andro idManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ost.android1.helloworld"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10"
android:targetSdkVersion="10"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="MyApplication" >
<activity
android:label="@string/app_name"
android:name=".MainActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".HermesActivity"
android:label="Hermes Activity"/>
</application>
</manifest>
No w that we've ho o ked up o ur new class pro perly, we just need to get a reference to it fro m o ur activities. Add the
fo llo wing to MainActivity.java:
CODE TO TYPE:
package com.ost.android1.helloworld;
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.EditText;
public class MainActivity extends Activity {
private EditText myEditText;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myEditText = (EditText) findViewById(R.id.my_edit_text);
MyApplication app = (MyApplication) getApplication();
myEditText.setHint(app.defaultString);
}
public void startHermes() {
Intent intent = new Intent(MainActivity.this, HermesActivity.class);
intent.putExtra(HermesActivity.MY_EXTRA, myEditText.getText().toString());
startActivityForResult(intent, HermesActivity.EXTRA_REQUEST);
}
public void handleMyButtonClick(View view) {
startHermes();
}
public void handleSendEmailClick(View view) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("plain/text");
startActivity(Intent.createChooser(emailIntent, "Email"));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode) {
case HermesActivity.EXTRA_REQUEST:
if (resultCode == RESULT_OK) {
String stringExtra = data.getStringExtra(HermesActivity.MY_EXTRA);
myEditText.setText(stringExtra);
}
break;
}
}
}
Test the applicatio n again. The default text fo r the EditText no w co ntains the text we defined in o ur Applicatio n class
("so me default text"). There are certainly better (and easier) ways o f defining default text fo r a view co mpo nent, but this
will wo rk just fine fo r o ur purpo ses right no w.
Wrapping Up
Ho pefully by no w yo u're feeling co mfo rtable with the Intent class and sharing data thro ugho ut yo ur applicatio n. In the
next lesso n, we'll get to kno w the co ntents o f the Andro id reso urces fo lder even better! See yo u there!
Copyright © 1998-2014 O'Reilly Media, Inc.
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Android Resources
Welco me back! No w that we've co vered the basics o f Navigatio n and sharing data, it's time to go further into the Andro id
reso urces fo lder. In this lesso n we'll co ver string reso urces and ho w to use them in yo ur co de and views. Let's get started, shall
we?
String Resources
If yo u've explo red the /res fo lder, yo u may have no ticed the /re s/value s/st rings.xm l file. This file defines and co llects
immutable string values fo r use in an applicatio n. Keeping all o f yo ur permanent strings defined in this file can be
useful fo r so lo develo pers o r develo pment teams—all Strings can be fo und, mo dified, and reused in a single lo catio n
witho ut having to hunt thro ugh every class just to find so mething like a typo , fo r example.
Yo u've seen the strings that are already defined in strings.xml by default. Open strings.xml no w (select the
st rings.xm l tab at the bo tto m to edit it in xml mo de) and add a few mo re strings:
/res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">HelloWorld</string>
<string name="action_settings">Settings</string>
<string name="hello_world">Hello world!</string>
<string name="header_text">Headers are cool.</string>
<string name="subheader_text">Make sure you \"escape\" special characters like quot
es &amp; ampersands.</string>
<string name="next">Go to Next Activity</string>
<string name="send_email">Send Email</string>
<string name="hint_text">This is hint text</string>
</resources>
Save the file.
Loading Strings in XML
That was pretty straightfo rward, but there's no thing to lo o k at until we implement it in o ur view. Let's use o ur
strings to po pulate the labels fo r o ur Butto ns in o ur main view. Open act ivit y_m ain.xm l and update the
views as sho wn:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:text="@string/header_text" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="This is going to be the best app ever!"
android:text="@string/subheader_text" />
<Button
android:id="@+id/my_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Whoa, look, a button!"
android:onClick="handleMyButtonClick" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send Email"
android:text="@string/send_email"
android:onClick="handleSendEmailClick" />
<EditText
android:id="@+id/my_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_text" />
</LinearLayout>
Yo u can see in yo ur co de that when referencing string reso urces in XML views, yo u use the fo rmat
@string/<string name>. The "co de co mplete" feature (Ct rl+space ) also wo rks in these views to help yo u find
available reso urces and prevent typo s. If yo u have tro uble getting "co de co mplete" to wo rk in the edito r, make
sure the XML file has been o pened in the appro priate edito r. The file ico n in the edito r tab sho uld lo o k like this
. If yo u're seeing a different ico n, clo se the file, and then reo pen it by right-clicking the file
name in the Package Explo rer and selecting Ope n Wit h | Andro id Layo ut Edit o r. That way yo u'll be sure
that the "co de co mplete" feature fo r reso urce values is wo rking in yo ur xml.
Save and run the applicatio n to test the results. Yo ur first screen o f the applicatio n will lo o k like this:
At this po int we do n't actually have to run this in the emulato r to make sure o ur view is co rrect. We can use the
Design view o f the Andro id Layo ut Edito r, which is much faster. It can take a mo ment to initialize the first time
the Design view is lo aded fo r an Eclipse sessio n, but ultimately it will save yo u valuable time.
The Design view wo n't always be able to render a pixel-perfect representatio n o f the way a view will lo o k in
actual devices, but it sho uld be sufficient fo r basic layo uts and value testing like this.
Note
There's ano ther nice little feature in so me versio ns o f ADT that can help yo u to create string
reso urces. When yo u're wo rking in yo ur layo ut, yo u can select a string value that needs to be
co nverted into a string reso urce, then use the Re f act o r | Andro id | Ext ract Andro id
St ring... menu o ptio n to add the value to the strings file and update the co mpo nent to use the
new reso urce auto matically. Inco rpo rating this feature means yo u do n't have to keep switching
back and fo rth between yo ur XML view layo uts and the string reso urces.
Loading Strings in Code
No w that we've go t o ur string reso urces lo ading in o ur views, let's use them in o ur co de! I left o ut the next
string reso urce intentio nally so we co uld test that o ne in co de; it co uld have been lo aded in the XML like the
o thers just as easily tho ugh. Open the MainAct ivit y.java class and enter the co de belo w into the o nCreate
metho d, as sho wn:
MainActivity.java
package com.ost.android1.helloworld;
import
import
import
import
import
import
android.app.Activity;
android.content.Intent;
android.os.Bundle;
android.view.View;
android.widget.Button;
android.widget.EditText;
public class MainActivity extends Activity {
private Button myButton;
private EditText myEditText;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myButton = (Button) findViewById(R.id.my_button);
String next = getString(R.string.next);
myButton.setText(next);
myEditText = (EditText) findViewById(R.id.my_edit_text);
MyApplication app = (MyApplication) getApplication();
myEditText.setHint(app.defaultString);
}
private OnClickListener myButtonClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
startHermes();
}
};
public void startHermes() {
Intent intent = new Intent(MainActivity.this, HermesActivity.class);
intent.putExtra(HermesActivity.MY_EXTRA, myEditText.getText().toString()
);
startActivityForResult(intent, HermesActivity.EXTRA_REQUEST);
}
public void handleSendEmailClick(View view) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("plain/text");
startActivity(Intent.createChooser(emailIntent, "Email"));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data
) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode) {
case HermesActivity.EXTRA_REQUEST:
if (resultCode == RESULT_OK) {
String stringExtra = data.getStringExtra(HermesActivity.MY_EXTRA
);
myEditText.setText(stringExtra);
}
break;
}
}
}
Just like layo ut files (which are also lo cated in the /res fo lder), string reso urces are lo aded by using the
generated R.java file. The actual string value is lo aded by using the helper metho d ge t St ring(), which is
defined o n the encapsulated Co ntext class. I haven't mentio ned the Co ntext class yet, but as we go further
into the Andro id SDK, yo u'll see that Co ntext is used frequently. Yo u'll need a Co ntext o bject to acco mplish
certain tasks (like lo ading reso urces o r creating a database). Bo th Activity and Applicatio n classes
encapsulate the Co ntext class, so we typically pass o ne o r the o ther as the Co ntext.
Note
There is also a helper metho d available o n the TextView co mpo nent that takes the reso urce
string id directly, which means the co de co uld be simplified even mo re so that it's just a single
line, fo r example: m yBut t o n.se t T e xt (R.st ring.ne xt ).
T he Resource Values Folder
In lesso ns to co me, we'll create and use mo re files in the /res/values fo lder. The names o f the files in the /res/values
fo lder are cho sen acco rding to co nventio n; the XML ro o t no de uses the <reso urces> tag. We used strings.xml file here
to gather all the string definitio ns into a single file, but we co uld actually name the file whatever we want, just so lo ng as
the XML ro o t no de is the <reso urces> tag.
The files in the values fo lder are the o nly reso urce files where the name is no t impo rtant. Fo r every o ther file fro m o ther
/res subfo lders, the name is extremely impo rtant. This is because in tho se fo lders the name is essentially the 'id' value
used to lo ad the reso urce. Fo r example, to access layo uts (in co de), we use R.layo ut.<filename>. The same pattern is
used fo r every o ther subfo lder in /res except values; the values subfo lder adheres to this pattern: R.<subfo ldername>.<filename>. To lo ad values defined in files in the values subfo lder, we use the pattern R.<value-type>.<nameattribute-value>.
Note
Andro id restricts the names o f files in the reso urces fo lder. Filenames can o nly co ntain lo wer-case
characters a thru z, numbers 0 thru 9 , and the undersco re symbo l. No capital letters, spaces, o r special
characters are allo wed. The exceptio n to this rule is fo r files in the res/values fo lder. Fo r files in the
/res/values fo lder, the rules apply to the values fo r the name attribute instead.
Wrapping Up
Using string reso urces in Andro id will help yo u to create better, mo re efficient co de, and also make yo ur co de easier
fo r o ther develo pers to read. Learn them, and lo ve them! See yo u next lesso n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Drawables - Image Basics
In this lesso n we'll wo rk with reso urces again, but this time we'll fo cus o n the Drawable class, which is used to manage images
in Andro id. Let's get started!
Drawable Folders and Qualifiers
So far we've do ne all o f o ur wo rk o n a single pro ject, and it's starting to beco me a little cluttered. Let's clo se that pro ject
and start a new o ne fo r this lesso n to clean up the wo rkspace a bit and fo cus o n just the current to pics.
1. Right-click yo ur He llo Wo rld ro o t pro ject fo lder and select Clo se Pro je ct . (Also , clo se any o pen files
fro m the Hello Wo rld pro ject in the Edito r windo w.)
2. Create a new Andro id pro ject named Drawable s.
3. Set the package name o f the pro ject to co m .o st .andro id1.drawable s.
4. Uncheck the Cre at e cust o m launche r ico n bo x.
5. Add the pro ject to the Andro id1_Le sso ns wo rking set.
By default, images fo r Andro id applicatio ns sho uld be sto red in the re s/drawable fo lder. There are already fo ur
different "drawable" fo lders in o ur pro ject: drawable-hdpi, drawable-ldpi, drawable-mdpi, and drawable-xhdpi. The
extended names fo r these fo lders are called "qualifiers." A qualifier is a string appended to o ne o f the default fo lder
names to indicate fo r which unique co nfiguratio n that fo lder sho uld be used.
The generated drawable fo lders in o ur applicatio n have qualifiers fo r the vario us Andro id pho ne screen reso lutio n
ranges. The "-hdpi" qualifier is fo r high reso lutio n devices. This means a device that suppo rts the high-density
reso lutio n range (~240 dpi) fo r Andro id will attempt to lo ad drawables o ut o f the drawable-hdpi fo lder by default. "mdpi" is fo r medium-density reso lutio ns (~16 0 dpi) and "ldpi" is fo r lo w-density reso lutio n devices (~120 dpi).
Note
Qualifiers are used in Andro id fo r mo re than just screen reso lutio n. They can be used to o verride any
reso urce value fo r almo st any hardware co nfiguratio n, such as screen size, device layo ut, lo cale, and
hardware suppo rt (such as a camera o r trackball). We'll use qualifiers mo re in the co ming lesso ns, but if
yo u're curio us to find o ut mo re abo ut qualifiers no w, check o ut this article o n Suppo rting Multiple Screens
o n the Andro id develo per do cumentatio n site.
Using Drawables
Our drawable fo lders are already po pulated with a single default image that is being used fo r the applicatio n ico n. No w
let's add ano ther o ne to integrate into o ur applicatio n. To do wnlo ad the image, right-click o n the image belo w and save
the file to yo ur /re s/drawable -hdpi fo lder:
Note
The pro ject fo lders are lo cated o n the V drive in the /workspace/ fo lder; the full path where yo u sho uld
save the image is V:\wo rkspace \Drawable s\re s\drawable -hdpi.
No w that we have the new image in place, let's get it lo aded into o ur applicatio n. Open act ivit y_m ain.xm l fro m the
/re s/layo ut / fo lder and make these changes:
/res/layo ut/activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_android_robot"
/>
</RelativeLayout>
Like all values in the reso urces fo lder, drawables are referenced using the @ symbo l syntax. Let's run the applicatio n
no w to make sure that the ImageView lo ads the image co rrectly:
When yo u want to lo ad a no n-interactive image fo r display in yo ur applicatio n, yo u'll typically use the Im age Vie w
co mpo nent, like we just did. Other co mmo n use cases fo r lo ading images are fo r butto n ico ns and butto n skins.
Note
"Butto n skinning" is a little beyo nd the sco pe o f this lesso n, but we'll discuss ho w to implement a butto n
skin later, when we co ver Applicatio n skinning. Trust me, it will all make perfect sense to yo u later!
Let's add a butto n with the previo us image as the butto n ico n. Mo dify act ivit y_m ain.xm l as sho wn:
/res/layo ut/activity_main.xml
<RelativeLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_android_robot"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_android_robot"
android:text="Icon Button"
/>
</RelativeLinearLayout>
Run the applicatio n again; yo ur view will lo o k like this:
The drawable Le f t is a co nvenience pro perty (alias) defined o n the TextView class (o f which Butto n is a subclass). As
yo u may have guessed, there are additio nal pro perties called, drawable T o p, drawable Right , and
drawable Bo t t o m ; they behave exactly as yo u'd expect.
Dimensions
So far we've defined the layo ut _widt h and layo ut _he ight attributes o f o ur Images (and all o f o ur
co mpo nents) as either m at ch_pare nt o r wrap_co nt e nt . These are handy relative dimensio n pro perties,
but when neither pro perty is sufficient fo r yo ur needs, yo u'll want to use a mo re specific dimensio n.
If yo u've ever develo ped a user interface fo r ano ther applicatio n, yo u're pro bably used to defining yo ur width
and height dimensio ns in pixels. In Andro id, using precise pixels fo r dimensio ns is no t reco mmended
tho ugh, because Andro id devices co me in so many different shapes and sizes, with so many different
reso lutio ns. This means that the number o f available pixels o n the screen can vary greatly by device. To
address this issue, the Andro id SDK has its o wn unit fo r dimensio ns called "density independent
pixels"—"dip" o r "dp" fo r sho rt. When yo u use "dip" units fo r yo ur dimensio ns, the Andro id SDK will
auto matically scale the actual pixel dimensio n to an appro priate relative pixel size to keep the relative sizes
and spacing the same fo r each device.
Let's update o ur view no w to use the "dip" unit fo r so me o f o ur dimensio ns, so we can better co ntro l the size
o f o ur co mpo nents. Make these changes to activity_main.xml:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<ImageView
android:layout_width="wrap_content200dp"
android:layout_height="wrap_content40dp"
android:src="@drawable/ic_android_robot"
android:scaleType="fitXY" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_android_robot"
android:text="Icon Button" />
</LinearLayout>
Save the file and give it a test run; the first screen o f the applicatio n will lo o k like this:
Note
There isn't any direct way to have a butto n resize its drawableX (drawableLeft, drawableRight,
and so o n) ico ns. Yo u can change the dimensio ns o f the butto n, but the image will remain its
o riginal size (and clip the edge o f the butto n if the butto n is smaller than the image). Ano ther way
to appro ach this issue (witho ut creating a new image) wo uld be to use an XML drawable. We'll
co ver XML drawables in a future lesso n.
In o rder to see a co mplete co mpariso n o f the differences between using density independent pixels and
o rdinary pixel units, yo u'd need to create a seco nd emulato r with slightly different dimensio ns and test the
co de o n each device o nce using "dip" fo r yo ur dimensio ns and again using "px." Do ing that while using a
remo te deskto p co nnectio n wo uld be pretty time co nsuming tho ugh.
Note
When defining a fo nt size fo r text co mpo nents using exact pixels is no t reco mmended either.
Andro id pro vides an alternative unit called "scale independent pixels"—"sp" fo r sho rt. These
units behave exactly like "dip" units, but they will also be scaled by the user's default fo nt scale if
specified.
Fo r an in-depth explanatio n o f ho w dip units wo rk, check o ut the Andro id develo per do cumentatio n site
regarding dimensio ns.
Image Padding
If yo u want to adjust the placement and spacing o f the ico n image inside yo ur butto n, there are a few tricks yo u
can use. First, there is a pro perty called drawable Padding, which defines the minimum amo unt o f padding
the widget sho uld use between the ico n and the text co ntent. This pro perty adds padding o nly between the text
and the ico n, and o nly when there is a drawable ico n defined as well. This will no t create padding between the
ico n and the edge o f the butto n. If yo u want to add space between the ico n and the edge o f the butto n, use the
padding pro perty just as yo u wo uld fo r any layo ut co mpo nent that has children.
Note
By default, the drawableX pro perty will draw the ico n as clo se as it can to the edge o f the butto n.
This is mo st no ticeable when using a drawableLeft o r drawableRight ico n and the butto n has a
layo ut_width value o f "match_parent" so that it fills the entire width o f the device. In that situatio n
the ico n will be drawn near the edge o f the butto n and no t near the text inside the butto n. So , in
this instance the drawable Padding pro perty will have no effect o n the butto n.
Let's add so me drawablePadding to the Butto n co mpo nent in act ivit y_m ain.xm l:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<ImageView
android:layout_width="200dp"
android:layout_height="40dp"
android:src="@drawable/ic_android_robot"
android:scaleType="fitXY" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_android_robot"
android:drawablePadding="20dp"
android:text="Icon Button" />
</LinearLayout>
Save and run it; yo u'll see so mething like this:
The o nly space that is affected is that between the ico n and the text o f the butto n. The space between the ico n
and the to p, left, and bo tto m edges o f the butto n remains unchanged.
T he ImageButton Widget
If yo u have a butto n that needs o nly an ico n (and no text o n the butto n) then yo u sho uld pro bably use an
ImageButto n. The ImageButto n class is actually a subclass o f ImageView (and no t Butto n). There are actually
very few differences between an ImageButto n and an ImageView. Bo th have a backgro und pro perty that
takes a drawable, as well as an src pro perty that also takes a drawable, and bo th are clickable view
co mpo nents. Ho wever, by default, the ImageButto n co mpo nent will use the default skin fo r Butto n as its
backgro und drawable, while the ImageView do es no t have a default backgro und.
Let's add an ImageButto n co mpo nent to o ur co de:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<ImageView
android:layout_width="200dp"
android:layout_height="40dp"
android:src="@drawable/ic_android_robot"
android:scaleType="fitXY" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_android_robot"
android:drawablePadding="20dp"
android:text="Icon Button" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_android_robot" />
</LinearLayout>
No w run the app to verify the results:
As yo u can see, the ImageButto n still has the default skin o f the Butto n, with o ur ico n drawn in the middle o f
the butto n. Since the ImageButto n co mpo nent is a subclass o f ImageView and no t Butto n, there is no
drawable Padding attribute available (besides, it wo uldn't be o f any use fo r this co mpo nent). It do esn't have
a t e xt attribute either, but it do es have the scale T ype pro perty available to help yo u to define ho w the image
is scaled inside o f the view co mpo nent.
Wrapping Up
We've co vered a lo t o f impo rtant stuff in this lesso n. I'm co nfident that yo u kno w ho w to use Images in yo ur views, as
well as ho w Andro id uses "density independent pixels" as a dimensio n unit.
We've spent a lo t o f time in the reso urces fo lder so far, and yo u have a pretty stro ng grasp o f ho w to implement a
majo rity o f Andro id's view co mpo nents. If yo u feel like yo u want to experiment a bit mo re with these co ncepts o n yo ur
o wn, do it! In upco ming lesso ns, we'll get back to wo rk in the Java classes. See yo u there!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Lists
Go o d to see yo u again! This lesso n will co ver Andro id lists. Let's get started!
Implementing an Android List
Go ahead and start a new applicatio n. Select File | Ne w | Pro je ct | Andro id Applicat io n Pro je ct , and create the
applicatio n using these settings:
1. Name the Pro ject List s.
2. Type co m .o re illyscho o l.andro id1.list s fo r the Package name.
3. Clear the Cre at e cust o m launche r ico n check bo x.
4. Add the pro ject to the Andro id1_Le sso ns wo rking set.
ListView
Befo re we can start implementing o ur list, we need to make o ne small change to o ur view; we need to add a
list to it, o f co urse! Open act ivit y_m ain.xm l in the /re s/layo ut / fo lder and make these changes:
/res/layo ut/activity_main.xml
<RelativeLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLinearLayout>
Note
As o f Andro id API 8 , 2.2, the layo ut attribute f ill_pare nt was replaced with the mo re accurate
m at ch_pare nt label.
The ListView co mpo nent is the default List handler fo r Andro id. No tice that we used a slightly different "id"
value fro m what we used befo re. (We'll discuss that in greater detail a bit later.)
There's no t much to see in o ur pro gram yet, but if yo u click the Graphical Layo ut tab in the XML edito r, yo u
will see a stubbed default List:
This view can sho w o nly fake stub data and will never reflect the actual co ntent o f yo ur list. Yo u'll need to run
the applicatio n to test and make sure that yo ur list items are wo rking co rrectly. Befo re yo u can do that, tho ugh,
yo u'll need to implement the list in co de.
ListActivity
To use a List in yo ur view, yo u'll want to use a different kind o f Activity class called the ListActivity. ListActivity
is a subclass o f Activity, and while it is no t required fo r implementing lists, it can make the setup and
maintenance o f a list much easier.
Open MainAct ivit y.java and make these changes:
/src/MainActivity.java
package com.oreillyschool.android1.lists;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.Menu;
public class MainActivity extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Remember earlier ho w we used a unique value fo r o ur "andro id:id" attribute in the XML view? This id value is
actually predefined by the Andro id SDK. The co de reference equivalent is andro id.R.id.list . Ho wever, unlike
befo re, we do n't need to find and sto re a reference to this view (using findViewById), because ListActivity has
already taken care o f that. ListActivity expects that the layo ut lo aded by se t Co nt e nt Vie w co ntains a list that
co ntains that id value, lo ads that value, and manages it internally. Any interactio n with the ListView co mpo nent
is then handled by helper metho ds available o n the ListActivity class. If yo u wanted to subclass a regular
Activity class, yo u wo uld need to manage the ListView co mpo nent manually.
Empty Lists
ListActivity also has the added benefit o f sho wing an alternate view when its list is empty. Taking advantage o f
this feature requires ano ther special Andro id id, andro id.R.id.e m pt y. Let's o pen act ivit y_m ain.xm l again
and add an "empty" view:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/empty_text"
android:gravity="center"
/>
</LinearLayout>
Yo u'll also need to add a string named e m pt y_t e xt to the strings.xml file. Yo u can do that manually o r use
the Re f act o r | Andro id | Ext ract Andro id St ring... sho rtcut we discussed earlier. Give the string
whatever value yo u like. When yo u're finished, go ahead and run the applicatio n; yo u'll see that the empty text
is sho wn, because o ur list do esn't have any data yet:
ListAdapter
A list isn't at all useful witho ut data, so we'll need to wo rk o n that. To manage list data in Andro id we use an
Adapter. A list view expects an o bject o f the type android.widget.ListAdapter, which is an interface.
Implementing the entire interface isn't necessary, tho ugh, because there are many default implementatio ns
available in the SDK that yo u can use that are less labo r intensive. I perso nally prefer the
andro id.widge t .ArrayAdapt e r<T > class.
Let's get o ur ListView ho o ked up to an adapter with an instance o f ArrayAdapter. Make these changes to
MainAct ivit y.java:
MainActivity.java
package com.oreillyschool.android1.lists;
import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
public class MainActivity extends ListActivity {
private String[] data = new String [] {
"Odin",
"Thor",
"Loki",
"Baldr",
"Freyr",
"Heimdallr",
"Ullr",
"Meili",
"Hodr",
"Forseti"
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.
layout.simple_list_item_1, android.R.id.text1, data);
setListAdapter(adapter);
}
}
No w run the applicatio n and test yo ur results. Yo ur emulato r sho uld lo o k like this:
Let's lo o k at o ur co de in so me mo re detail and see exactly what's go ing o n:
OBSERVE:
...
public class MainActivity extends ListActivity {
private String[] data = new String [] {
"Odin",
"Thor",
"Loki",
...
"Forseti"
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.
layout.simple_list_item_1, android.R.id.text1, data);
setListAdapter(adapter);
}
}
First, we created so me basic st ub dat a f o r t he list , o therwise the list wo uld still be empty. Then we used
se t List Adapt e r(), a helper metho d o n ListActivity that sets the adapter o n the ListView co mpo nent
managed by the ListActivity.
Our co nstructo r fo r ArrayAdapter has a signature that requires fo ur parameters:
ArrayAdapter Co nstructo r
ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects)
The first parameter is a Context. All Activity classes are subclasses o f Co ntext, so we just pass t his as the
value.
The next parameter, int re so urce , is a reso urce reference to the XML layo ut to be used fo r each item in the
list. We used a basic layo ut that was already pro vided in the Andro id SDK:
andro id.R.layo ut .sim ple _list _it e m _1. This layo ut co ntains o nly o ne co mpo nent: a TextView.
The third parameter, int t e xt Vie wRe so urce Id, is mo re o r less self-explanato ry. It's an id reference to the
TextView co mpo nent that is co ntained in the previo usly defined reso urce's layo ut. The id reference to the
TextView co ntained in the simple_list_item_1 layo ut is andro id.R.id.t e xt 1.
The final parameter is to an Array o f the data that will be used to po pulate the TextView co mpo nent fo r each
item in the list. No te that this Array is type-restricted to the bo unded type parameter that was used fo r the
co nstructo r metho d, which in o ur case is St ring.
Sorting the Adapter
To so rt the ArrayAdapter, just call so rt () o n the adapter and send it a Comparator o bject. Let's implement so rt
in o ur co de no w. Change MainAct ivit y.java as sho wn:
CODE TO TYPE:
package com.oreillyschool.android1.lists;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
public class MainActivity extends ListActivity {
private String[] data = new String [] {
"Odin",
"Thor",
"Loki",
...
"Forseti"
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.
layout.simple_list_item_1, android.R.id.text1, data);
adapter.sort(new Comparator<String>() {
@Override
public int compare(String arg0, String arg1) {
return arg0.compareTo(arg1);
}
});
setListAdapter(adapter);
}
}
Save and run it again. Yo u'll see this:
Overriding ArrayAdapter
If all yo u need to display in yo ur list is a simple view, the basic ArrayAdapter implementatio n is all yo u need.
Ho wever, many Lists require a mo re invo lved layo ut to display a mo re co mplicated data mo del. There are
so me o ther default layo uts that o ffer a bit mo re detail. Fo r example, using
andro id.R.layo ut.simple_list_item_single_cho ice includes a CheckBo x in the list item layo ut. These default
layo uts can o nly get yo u so far tho ugh; in o rder to create a truly custo mized layo ut, yo u'll need to o verride the
default implementatio n o f yo ur adapter. Let's do that no w.
First, we'll need to create a new layo ut that will be used fo r each item in the List. Create a new Andro id XML
layo ut named m y_list _it e m .xm l. Mo dify the XML as sho wn:
CODE TO TYPE:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
android:orientation="horizontal"
android:gravity="center_vertical">
<View
android:id="@+id/color"
android:layout_width="10dp"
android:layout_height="50dp"
/>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
Next, let's change o ur data mo del a little bit so that it co ntains mo re than just a String. We're go ing to create a
class lo cally inside o f MainActivity, but yo u co uld as easily create it in a new file:
CODE TO TYPE:
package com.oreillyschool.android1.lists;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
public class MainActivity extends ListActivity {
private String[] data = new String [] {
"Odin",
"Thor",
"Loki",
"Baldr",
"Freyr",
"Heimdallr",
"Ullr",
"Meili",
"Hodr",
"Forseti"
};
public class MyData {
public String name;
public boolean clicked;
public MyData(String name) {
this.name = name;
this.clicked = false;
}
}
private
new
new
new
new
new
new
new
new
new
new
};
MyData[] data = new MyData[] {
MyData("Odin"),
MyData("Thor"),
MyData("Loki"),
MyData("Baldr"),
MyData("Freyr"),
MyData("Heimdallr"),
MyData("Ullr"),
MyData("Meili"),
MyData("Hodr"),
MyData("Forseti")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.
layout.simple_list_item_1, android.R.id.text1, data);
adapter.sort(new Comparator<String>() {
@Override
public int compare(String arg0, String arg1) {
return arg0.compareTo(arg1);
}
});
setListAdapter(adapter);
}
}
Yo u get an erro r; do yo u kno w why? Mull that o ver; we'll fix it in a minute. Fo r no w, let's mo ve back to creating
o ur custo mized Adapter. Create a new class in the co m .o re illyscho o l.andro id1.list s package named
MyList Adapt e r, and set the Superclass to andro id.widge t .ArrayAdapt e r<MyDat a>:
With the new class created and o pened, make these changes to yo ur co de:
CODE TO TYPE:
package com.oreillyschool.android1.lists;
import
import
import
import
import
import
import
import
android.widget.ArrayAdapter;
com.oreillyschool.android1.lists.MainActivity.MyData;
android.content.Context;
android.graphics.Color;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;
android.widget.TextView;
public class MyListAdapter extends ArrayAdapter<MyData> {
private LayoutInflater inflater;
public MyListAdapter(Context context, MyData[] data) {
super(context, R.layout.my_list_item, data);
inflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup root) {
View view = convertView;
if (view == null) {
view = inflater.inflate(R.layout.my_list_item, null);
}
MyData data = getItem(position);
TextView textView = (TextView) view.findViewById(R.id.text);
textView.setText(data.name);
View imageView = view.findViewById(R.id.color);
int color = data.clicked ? Color.RED : Color.BLUE;
imageView.setBackgroundColor(color);
return view;
}
}
No w, we need o ne last change to ho o k up the new class to the ListView. Go back to MainActivity.java and
make these changes:
CODE TO TYPE:
package com.oreillyschool.android1.lists;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
public class MainActivity extends ListActivity {
public class MyData {
public String name;
public boolean clicked;
public MyData(String name) {
this.name = name;
this.clicked = false;
}
}
private
new
new
new
new
new
new
new
new
new
new
};
MyData[] data = new MyData[] {
MyData("Odin"),
MyData("Thor"),
MyData("Loki"),
MyData("Baldr"),
MyData("Freyr"),
MyData("Heimdallr"),
MyData("Ullr"),
MyData("Meili"),
MyData("Hodr"),
MyData("Forseti")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.
layout.simple_list_item_1, android.R.id.text1, data);
adapter.sort(new Comparator<String>() {
@Override
public int compare(String arg0, String arg1) {
return arg0.compareTo(arg1);
}
});
MyListAdapter adapter = new MyListAdapter(this, data);
adapter.sort(new Comparator<MyData>() {
@Override
public int compare(MyData arg0, MyData arg1) {
return arg0.name.compareTo(arg1.name);
}
});
setListAdapter(adapter);
}
}
There's a lo t go ing o n here, but befo re we analyze o ur changes in detail, let's run it and make sure the co de is
wo rking. Yo ur list will lo o k like this:
No w that we've verified that everything is wo rking alright, let's take a clo ser lo o k at o ur custo m adapter,
starting with the co nstructo r:
OBSERVE:
public MyListAdapter(Context context, MyData[] data) {
super(context, R.layout.my_list_item, data);
inflater = LayoutInflater.from(context);
}
In o ur co nstructo r, we start o ff with the mandato ry call to the supe r co nst ruct o r; but we're calling a different
super than we did befo re. And this time we're using a simpler co nstructo r because we'll be handling the
creatio n o f each list item manually. The superclass will still handle the Array data, tho ugh:
OBSERVE:
@Override
public View getView(int position, View convertView, ViewGroup root) {
View view = convertView;
if (view == null) {
view = inflater.inflate(R.layout.my_list_item, null);
}
MyData data = getItem(position);
TextView textView = (TextView) view.findViewById(R.id.text);
textView.setText(data.name);
View imageView = view.findViewById(R.id.color);
int color = data.clicked ? Color.RED : Color.BLUE;
imageView.setBackgroundColor(color);
return view;
}
The ge t Vie w() metho d gets called each time the ListView needs to create a new ListItem o r update an
existing o ne. There are three parameters that get sent to the metho d, but o ur main co ncern is with the first two .
The first, int po sit io n, is the po sitio n o f the list item that needs a layo ut. The seco nd, Vie w co nve rt Vie w,
can be either null o r co ntain a recycled View co mpo nent that was created previo usly with this metho d fo r a
different item in the list. If the co nvertView is null, we'll need to inflate a new View co mpo nent; o therwise we
just need to update the data o n the view co mpo nent.
Inflating a layo ut can co nsume a fair amo unt o f reso urces and time fo r the CPU. Inflating just o ne o r two views
isn't usually no ticeable by a user, but a ListView can be scro lled very quickly, which means many different
layo uts co uld be required in very quick successio n. Fo r this reaso n, the Andro id ListView will recycle its items
so that unnecessary layo ut inflatio n can be avo ided.
Note
A co mmo n erro r pro grammers make when creating a custo m list adapter is no t updating all
necessary values in a recycled view. Never rely o n any default values o f a newly inflated View,
because if the view has been recycled, tho se values have very likely changed. If yo u find that
yo ur view is sho wing inco rrect data o n a list item, data that was perhaps o n a previo usly
rendered list item, then yo ur first step to fixing that bug sho uld be to verify yo u have invalidated
all yo ur custo m data o n yo ur View in getView.
To get a reference to o ur data, we call the superclass metho d ge t It e m (). This metho d returns an o bject o f
the type defined by the bo und type parameter, which in o ur simple use-case is a String. This value type can be
whatever data mo del yo u wish, just as lo ng as yo ur Array (o r ArrayList) supplied to the super co nstructo r
co ntains o bjects o nly o f that type.
The rest o f the co de pro bably lo o ks familiar. It's the same type o f co de used to find views o n an Activity and
update the data fo r tho se views.
List Interaction
No w that we have the list data lo ading, let's update o ur co de to react to a user clicking o n a list item. Add the
MainAct ivit y.java metho d as sho wn:
MainActivity.java
package com.oreillyschool.android1.lists;
import
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.widget.ArrayAdapter;
android.view.View;
android.widget.ListView;
public class MainActivity extends ListActivity {
public class MyData {
public String name;
public boolean clicked;
public MyData(String name) {
this.name = name;
this.clicked = false;
}
}
private
new
new
new
...
new
};
MyData[] data = new MyData[] {
MyData("Odin"),
MyData("Thor"),
MyData("Loki"),
MyData("Forseti")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyListAdapter adapter = new MyListAdapter(this, data);
adapter.sort(new Comparator<MyData>() {
@Override
public int compare(MyData arg0, MyData arg1) {
return arg0.name.compareTo(arg1.name);
}
});
setListAdapter(adapter);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
MyListAdapter adapter = (MyListAdapter) getListAdapter();
MyData item = adapter.getItem(position);
item.clicked = !item.clicked;
adapter.notifyDataSetInvalidated();
}
}
This metho d o verride is available o nly to ListActivity. If yo u were handling yo ur ListView co mpo nent manually, yo u
wo uld need to set up a listener and send it to the o nIt e m ClickList e ne r() metho d available o n the ListView
co mpo nent.
No w, run the applicatio n o ne last time to test the results. Click so me o f the items to make sure the click handler is
wo rking:
Wrapping Up
We've co vered a lo t o f gro und here! It's go o d to kno w that yo u have a handle o n ListView co mpo nent and Adapters
no w, because virtually every Andro id applicatio n o n the market uses at least o ne list. Yo u're go ing to want to kno w
ho w to use them well.
Alright then. So far, so go o d! See yo u next lesso n...
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Dialogs, New and Old
Back fo r mo re, huh? Excellent. In this lesso n, we'll talk abo ut Dialo gs. There are a few different types o f dialo gs, and also a few
different metho ds to create and sho w Dialo gs in Andro id.
There are essentially two ways to create dialo gs in Andro id no w, the new style and the o ld. With the release o f Ho neyco mb,
Andro id 3.x, which is specifically fo r tablets, Andro id added the new way o f creating dialo gs. This pro cess has been carried o ver
into Ice Cream Sandwich, Andro id 4.0 , which wo rks o n bo th pho nes and tablets. Also , thanks to the Andro id suppo rt library,
many features fro m Andro id 3+ are available to Andro id applicatio ns that target o lder builds o f Andro id.
Old Style
Befo re we get go ing, let's create a new pro ject fo r this lesso n named Dialo gs, assigned to the Andro id1_Le sso ns
wo rking set. Name the package co m .o re illyscho o l.andro id1.dialo gs.
The API metho ds used to create and sho w dialo gs fro m an Activity are no w marked "@Deprecated" in the mo st recent
API updates to Andro id. Ho wever, it's still wo rth learning ho w to use these o ld metho ds in case yo u run acro ss co de
that has already implemented dialo gs that use it.
AlertDialog
There are a few different types o f specialized Dialo gs available in Andro id, such as AlertDialo g and
Pro gressDialo g. Yo u can also create a custo m Dialo g class and po pulate it with whatever co ntent yo u like.
Let's begin by building an AlertDialo g by adding a butto n to the primary view. Open act ivit y_m ain.xm l and
make these changes:
/res/layo ut/activity_main.xml
<RelativeLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Old AlertDialog"
android:onClick="onOldAlertDialogClick" />
</RelativeLinearLayout>
Next, let's update the MainActivity to handle this butto n's click event; we'll tell the Activity we want to sho w a
Dialo g, and o verride the o nCre at e Dialo g metho d to create o ur Dialo g. Make these changes:
MainActivity.java
package com.oreillyschool.android1.dialogs;
import
import
import
import
import
import
android.app.Activity;
android.app.AlertDialog;
android.app.Dialog;
android.os.Bundle;
android.view.Menu;
android.view.View;
public class MainActivity extends Activity {
private static final int SIMPLE_DIALOG = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onOldAlertDialogClick(View view) {
showDialog(SIMPLE_DIALOG);
}
@Override
protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
switch(id) {
case SIMPLE_DIALOG:
dialog = new AlertDialog.Builder(this).setTitle("My Alert Dialog")
.setMessage("Pancakes or Waffles?")
.create();
break;
}
return dialog;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
No w save the changes and run the co de to see what it do es. Click the OldAle rt Dialo g butto n and yo u'll see
a dialo g like this:
To clo se this dialo g, yo u'll need to click the back butto n o n the emulato r. Typical dialo gs co ntain at least o ne
butto n to clo se, o r multiple butto ns to allo w a user to make a decisio n. AlertDialo g makes it easy to add o ne,
two , o r three butto ns. Make these changes to MainAct ivit y.java:
MainActivity.java
package com.oreillyschool.android1.dialogs;
import
import
import
import
import
import
import
android.app.Activity;
android.app.AlertDialog;
android.app.Dialog;
android.content.DialogInterface;
android.os.Bundle;
android.view.View;
android.widget.Toast;
public class MainActivity extends Activity {
private static final int SIMPLE_DIALOG = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onOldAlertDialogClick(View view) {
showDialog(SIMPLE_DIALOG);
}
@Override
protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
switch(id) {
case SIMPLE_DIALOG:
dialog = new AlertDialog.Builder(this).setTitle("My Alert Dialog")
.setMessage("Pancakes or Waffles?")
.setNegativeButton("Boring Pancakes", dialogListener)
.setPositiveButton("Awesome Waffles!!", dialogListener)
.create();
break;
}
return dialog;
}
private DialogInterface.OnClickListener dialogListener = new DialogInterface
.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case Dialog.BUTTON_NEGATIVE:
Toast.makeText(MainActivity.this, "Pancakes? Really?", Toast.LEN
GTH_LONG).show();
break;
case Dialog.BUTTON_POSITIVE:
Toast.makeText(MainActivity.this, "Waffles are where it's at!",
Toast.LENGTH_LONG).show();
break;
}
removeDialog(SIMPLE_DIALOG);
}
};
}
We defined the listener functio n we just used as a parameter.
Save and run this co de to test the results. No w after clicking either butto n, yo u'll see two butto ns in the dialo g,
as well as the appro priate To ast respo nse. To clo se the dialo g, we call the re m o ve Dialo g metho d and send
as well as the appro priate To ast respo nse. To clo se the dialo g, we call the re m o ve Dialo g metho d and send
the same id that was passed to sho wDialo g. The dialo g will clo se after either butto n click.
Note
In the Andro id SDK, there are two interface metho ds with the signature OnClickList e ne r(). One
is in the andro id.vie w.Vie w package. It's the interface that we used earlier fo r handling clicks to
butto ns, and it's used fo r all standard view co mpo nent click handling. The o ther is in the
andro id.co nt e nt .Dialo gInt e rf ace package, which is used fo r dialo gs, and is the o ne we
used in o ur co de here. I like to define the type as Dialo gInt e rf ace .OnClickList e ne r fo r dialo g
listeners to help differentiate between the mo re co mmo n Vie w.OnClickList e ne r metho ds; yo u
can just as easily remo ve the Dialo gInt e rf ace po rtio n fro m the type as lo ng as yo u are sure to
impo rt the pro per package.
The o nCre at e Dialo g() metho d that we implemented is called by an Activity o nly o nce per id parameter
passed. If we want to make changes to the dialo g created in o nCre at e Dialo g befo re the dialo g is sho wn, we
need to define a co unt variable and an o nPre pare Dialo g metho d:
MainActivity.java
package com.oreillyschool.android1.dialogs;
import
import
import
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.view.View;
android.app.AlertDialog;
android.app.Dialog;
android.content.DialogInterface;
android.widget.Toast;
public class MainActivity extends Activity {
private static final int SIMPLE_DIALOG = 0;
private int count = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onOldAlertDialogClick(View view) {
showDialog(SIMPLE_DIALOG);
}
@Override
protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
switch(id) {
case SIMPLE_DIALOG:
dialog = new AlertDialog.Builder(this).setTitle("My Alert Dialog")
.setMessage("Pancakes or Waffles?")
.setNegativeButton("Boring Pancakes", dialogListener)
.setPositiveButton("Awesome Waffles!!", dialogListener)
.create();
break;
}
return dialog;
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch (id)
{
case SIMPLE_DIALOG:
count++;
dialog.setTitle("Dialog "+count);
break;
}
}
private DialogInterface.OnClickListener dialogListener = new DialogInterface
.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case Dialog.BUTTON_NEGATIVE:
Toast.makeText(MainActivity.this, "Pancakes? Really?", Toast.LEN
GTH_LONG).show();
break;
case Dialog.BUTTON_POSITIVE:
Toast.makeText(MainActivity.this, "Waffles are where it's at!",
Toast.LENGTH_LONG).show();
break;
}
removeDialog(SIMPLE_DIALOG);
}
};
}
Save and run the pro gram. The title o f the dialo g is replaced by the new title we defined in o nPre pare Dialo g
and the co unt is updated each time a new dialo g is sho wn:
Custom Dialog
Ale rt Dialo g and its Builde r class are handy to o ls fo r creating a quick and functio nal dialo g, but if yo u need
a custo mized view o r mo re co ntro l o ver functio nality, yo u'll need to create a custo m dialo g.
Let's create a new custo m dialo g that co ntains an image, a radio butto n, and a regular butto n. We'll start by
creating a new butto n in the act ivit y_m ain.xm l view:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Old AlertDialog"
android:onClick="onOldAlertDialogClick"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Old CustomDialog"
android:onClick="onOldCustomDialogClick"
/>
</LinearLayout>
Next, update MainAct ivit y.java with the fo llo wing changes:
MainActivity.java
package com.oreillyschool.android1.dialogs;
import
import
import
import
import
import
import
android.app.Activity;
android.app.AlertDialog;
android.app.Dialog;
android.content.DialogInterface;
android.os.Bundle;
android.view.View;
android.widget.Toast;
public class MainActivity extends Activity {
private static final int SIMPLE_DIALOG = 0;
private static final int CUSTOM_DIALOG = 1;
private int count = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onOldAlertDialogClick(View view) {
showDialog(SIMPLE_DIALOG);
}
public void onOldCustomDialogClick(View view) {
showDialog(CUSTOM_DIALOG);
}
@Override
protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
switch(id) {
case SIMPLE_DIALOG:
dialog = new AlertDialog.Builder(this).setTitle("My Alert Dialog")
.setMessage("Pancakes or Waffles?")
.setNegativeButton("Boring Pancakes", dialogListener)
.setPositiveButton("Awesome Waffles!!", dialogListener)
.create();
break;
case CUSTOM_DIALOG:
dialog = new Dialog(this);
dialog.setContentView(R.layout.custom_dialog);
dialog.setTitle("My Custom Dialog");
break;
}
return dialog;
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch (id)
{
case SIMPLE_DIALOG:
count++;
dialog.setTitle("Dialog "+count);
break;
}
}
private DialogInterface.OnClickListener dialogListener = new DialogInterface
.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case Dialog.BUTTON_NEGATIVE:
Toast.makeText(MainActivity.this, "Pancakes? Really?", Toast.LEN
GTH_LONG).show();
break;
case Dialog.BUTTON_POSITIVE:
Toast.makeText(MainActivity.this, "Waffles are where it's at!",
Toast.LENGTH_LONG).show();
break;
}
removeDialog(SIMPLE_DIALOG);
}
};
}
Finally, we'll need to create the layo ut fo r the dialo g. Create a new XML Layo ut file in the /re s/layo ut fo lder,
name it cust o m _dialo g.xm l, and then make these changes to it:
/res/layo ut/custo m_dialo g.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/ic_menu_help" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Is this the real life?" />
</LinearLayout>
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:id="@+id/rb_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Real life"/>
<RadioButton
android:id="@+id/rb_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Just fantasy"/>
</RadioGroup>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/cancel_btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cancel" />
<Button
android:id="@+id/okay_btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Okay" />
</LinearLayout>
</LinearLayout>
Save all the changes and run the applicatio n to test the co de. If all is wo rking co rrectly, yo u'll see a new dialo g
like this:
Just like befo re, we haven't ho o ked up any event listeners to o ur dialo g, so the o nly way to clo se it is by
clicking the back butto n. Let's ho o k these butto ns up to clo se the dialo g. Make these changes to
MainAct ivit y.java:
MainActivity.java
package com.oreillyschool.android1.dialogs;
import
import
import
import
import
import
import
import
android.app.Activity;
android.app.AlertDialog;
android.app.Dialog;
android.content.DialogInterface;
android.os.Bundle;
android.view.View;
android.widget.Button;
android.widget.Toast;
public class MainActivity extends Activity {
private static final int SIMPLE_DIALOG = 0;
private static final int CUSTOM_DIALOG = 1;
private int count = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onOldAlertDialogClick(View view) {
showDialog(SIMPLE_DIALOG);
}
public void onOldCustomDialogClick(View view) {
showDialog(CUSTOM_DIALOG);
}
@Override
protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
switch(id) {
case SIMPLE_DIALOG:
dialog = new AlertDialog.Builder(this).setTitle("My Alert Dialog")
.setMessage("Pancakes or Waffles?")
.setNegativeButton("Boring Pancakes", dialogListener)
.setPositiveButton("Awesome Waffles!!", dialogListener)
.create();
break;
case CUSTOM_DIALOG:
dialog = new Dialog(this);
dialog.setContentView(R.layout.custom_dialog);
dialog.setTitle("My Custom Dialog");
((Button)dialog.findViewById(R.id.cancel_btn)).setOnClickListener(cu
stomDialogClickListener);
((Button)dialog.findViewById(R.id.okay_btn)).setOnClickListener(cust
omDialogClickListener);
break;
}
return dialog;
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch (id)
{
case SIMPLE_DIALOG:
count++;
dialog.setTitle("Dialog "+count);
break;
}
}
private View.OnClickListener customDialogClickListener = new View.OnClickLis
tener() {
@Override
public void onClick(View v) {
removeDialog(CUSTOM_DIALOG);
}
};
private DialogInterface.OnClickListener dialogListener = new DialogInterface
.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case Dialog.BUTTON_NEGATIVE:
Toast.makeText(MainActivity.this, "Pancakes? Really?", Toast.LEN
GTH_LONG).show();
break;
case Dialog.BUTTON_POSITIVE:
Toast.makeText(MainActivity.this, "Waffles are where it's at!",
Toast.LENGTH_LONG).show();
break;
}
removeDialog(SIMPLE_DIALOG);
}
};
}
Test the co de to make sure it wo rks. Bo th butto ns sho uld clo se the dialo g. Yo u might've no ticed that this time
we used the Vie w.OnClickList e ne r instead o f the Dialo gInt e rf ace .OnClickList e ne r interface. That's
because we're setting the listener directly o n the butto n in the view o f o ur Dialo g, and no t o n the Dialo g itself.
In fact, the base Dialo g class do esn't even use the Dialo gInt e rf ace .OnClickList e ne r interface, it uses
o nly the subclasses like AlertDialo g and Pro gressDialo g.
Note
Yo u might be tempted to try and use the andro id:o nClick attribute sho rtcut in the XML to
handle the click event fro m the butto ns in the dialo g. Unfo rtunately, this wo n't wo rk inside a
dialo g and will actually thro w an erro r when yo u click the butto n. That's because the view is a
part o f the Dialo g and no t the Activity, and the Dialo g class do es no t implement a dynamic
andro id:o nClick listener.
New Style
As o f Andro id 3.0 and beyo nd, there is a new standard way to create and manage Andro id dialo gs. But there is a way
to implement Dialo gs using the new pro cesses and still target an o lder versio n o f the Andro id SDK. Befo re we can get
to that tho ugh, we're go ing to have to take a sho rt deto ur and talk abo ut the Andro id co mpatibility library and fragments.
Support Library
Unfo rtunately, the majo rity o f Andro id pho nes in use run o lder versio ns o f Andro id and, as such, they do n't
have access to the latest Andro id SDK features. In o rder to help develo pers take advantage o f the new
features o f the latest SDK, Go o gle created a "co mpatibility library" (also called "suppo rt library" o r "suppo rt
package") that allo ws applicatio ns that target o lder versio ns o f the Andro id SDK to take advantage o f many o f
the new features o f Andro id.
To use the Suppo rt library we will need to do wnlo ad and add it to o ur pro ject. ADT makes this pro cess very
easy no w. Right-click o n the ro o t pro ject fo lder Dialo gs, cho o se Andro id T o o ls | Add Suppo rt Library,
select the latest suppo rt library versio n when pro mpted, click Acce pt , and yo u're do ne. Eclipse will
auto matically do wnlo ad the latest versio n o f the suppo rt library and include it in yo ur pro ject. When it's
finished yo u can verify the pro cess wo rked by expanding the libs fo lder to find the android-support-v4.jar file.
Fragments
Perhaps the mo st impo rtant feature in the Suppo rt Library is its suppo rt fo r fragments. A Fragment is like a
mini-activity. They behave much like Activities, but they must be added to an activity. They can be created and
destro yed within an Activity's lifecycle, but if the Activity is sto pped, no fragment within it can be started; if the
Activity is destro yed, then all o f its fragments are destro yed as well.
Let's get a little practice in with basic fragments. We'll co nvert o ur current dialo g applicatio n to use a fragment.
Start by creating a new class named MainFragm e nt and have it extend Fragm e nt :
No w let's create a new XML Layo ut File named m ain_f ragm e nt .xm l, co py all the XML fro m
act ivit y_m ain.xm l, and place it in m ain_f ragm e nt .xm l. It will lo o k like this when yo u are finished:
/res/layo ut/main_fragment.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Old AlertDialog"
android:onClick="onOldAlertDialogClick" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Old CustomDialog"
android:onClick="onOldCustomDialogClick" />
</LinearLayout>
No w clo se m ain_f ragm e nt .xm l, go back to act ivit y_m ain.xm l, and update it to use the fragment
MainFragm e nt . We'll also change it to a basic FrameLayo ut since we'll o nly be using a single fragment:
/res/layo ut/activity_main.xml
<LinearFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Old AlertDialog"
android:onClick="onOldAlertDialogClick" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Old CustomDialog"
android:onClick="onOldCustomDialogClick" />
<fragment
class="com.ost.android1.dialogs.MainFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearFrameLayout>
Save that file. No w, back in MainFragm e nt .java, update it to lo ad a view:
MainFragemnt.java
package com.oreillyschool.android1.dialogs;
import
import
import
import
import
android.os.Bundle;
android.support.v4.app.Fragment;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;
public class MainFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.activity_main_fragment, container, fals
e);
}
}
This is a little different fro m ho w we define a view in an Activity, but it's actually similar to ho w we create views
in a list adapter. In a fragment that has a view, yo u need to o verride and implement the o nCre at e Vie w
metho d. An Inf lat e r o bject is passed to the metho d as a co nvenience to inflate a new View. The
Inf lat e r.inf lat e metho d is actually o verlo aded, but the versio n we're using here takes three parameters: a
reso urce id to the view layo ut, a Vie wGro up o bject that will be used to parent the View, and a Bo o le an to
determine whether to attach the View to the ViewGro up. One o f the o verlo aded metho ds do esn't have the 3rd
Bo o le an parameter, effectively making that an o ptio nal parameter. We do n't want to attach o ur View to the
co ntainer during inflatio n, so we send "false," that way the inflated view inherits o nly the layo ut parameters
fro m the co ntainer.
Befo re we can test this co de we'll need to make o ne last change to MainActivity so it can lo ad fragments (it's
o nly a small change so we'll just sho w the affected lines this time):
MainActivity.java
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.app.AlertDialog;
android.app.Dialog;
android.content.DialogInterface;
android.os.Bundle;
android.support.v4.app.FragmentActivity;
android.view.View;
android.widget.Button;
android.widget.Toast;
public class MainActivity extends FragmentActivity {
...
Save and run the Applicatio n. Everything functio ns exactly as it did befo re. We'll explo re the benefits and best
usage fo r fragments in future lesso ns, but right no w we'll mo ve o n, o r rather get back, to creating dialo gs in
the "new way" with a type o f fragment called Dialo gFragm e nt .
DialogFragment
A Dialo gFragm e nt is a type o f fragment that's lo aded into an Activity like any o ther fragment, except that its
view is a dialo g. This allo ws the dialo g's lifecycle to be tied to a fragment instance instead o f directly to the
Activity itself. This also lets us create dialo gs that suppo rt the standard fo r Andro id devices running SDK 3.0
and up.
Let's add and implement a Dialo gFragment to o ur applicatio n. Create a new class called
MyCust o m Dialo gFragm e nt and have it extend the Dialo gFragm e nt class:
Next, let's update this co de to use the same view as o ur previo us custo m dialo g:
MyCusto mDialo gFragment.java
package com.oreillyschool.android1.dialogs;
import
import
import
import
import
import
import
android.app.Dialog;
android.os.Bundle;
android.support.v4.app.DialogFragment;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;
android.widget.Button;
public class MyCustomDialogFragment extends DialogFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Dialog);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.custom_dialog, container, false);
((Button)v.findViewById(R.id.cancel_btn)).setOnClickListener(listener);
((Button)v.findViewById(R.id.okay_btn)).setOnClickListener(listener);
return v;
}
private View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
};
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog d = super.onCreateDialog(savedInstanceState);
d.setTitle("My New Custom Dialog");
return d;
}
}
No w, o pen m ain_f ragm e nt .xm l and add ano ther butto n:
main_fragment.xml
...
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Old CustomDialog"
android:onClick="onOldCustomDialogClick"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New CustomDialog"
android:onClick="onNewCustomDialogClick"
/>
</LinearLayout>
And finally, go back to MainAct ivit y.java to implement the click event fo r this butto n:
MainActivity.java
import
import
import
import
import
import
import
import
import
import
import
android.app.AlertDialog;
android.app.Dialog;
android.content.DialogInterface;
android.os.Bundle;
android.support.v4.app.DialogFragment;
android.support.v4.app.FragmentActivity;
android.support.v4.app.FragmentManager;
android.support.v4.app.FragmentTransaction;
android.view.View;
android.widget.Button;
android.widget.Toast;
public class MainActivity extends FragmentActivity {
private static final int SIMPLE_DIALOG = 0;
private static final int CUSTOM_DIALOG = 1;
private static final String CUSTOM_DIALOG_FRAGMENT = "customDialogFragment";
private int count = 0;
...
public void onOldCustomDialogClick(View view) {
showDialog(CUSTOM_DIALOG);
}
public void onNewCustomDialogClick(View view) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
DialogFragment df = new MyCustomDialogFragment();
df.show(ft, CUSTOM_DIALOG_FRAGMENT);
}
...
Save all this co de and run it. The new butto n launches a custo m dialo g that lo o ks exactly the same as the
previo us custo m dialo g (since they're using the same layo ut view), o nly with a slightly different title.
No w instead o f using the Activity to manage the display and co ncealment o f the Dialo g, we use the
Dialo gFragment thro ugh the Dialo gFragm e nt .sho w() and Dialo gFragm e nt .dism iss() metho ds.
Dialo gFragm e nt .sho w() requires either a Fragm e nt Manage r o bject o r a Fragm e nt T ranscat io n. An
instance o f Fragm e nt Manage r can always be retrieved fro m a Fragm e nt Act ivit y by calling the
ge t Fragm e nt Manage r() metho d in Andro id SDK versio n 3.0 and up, o r by calling
ge t Suppo rt Fragm e nt Manage r() which is available to the Fragm e nt Act ivit y in the suppo rt library fo r
use in applicatio ns using o lder versio ns o f Andro id SDK.
We co uld've created an AlertDialo g using a Dialo gFragment as well. The o nly difference between this and the
o ld metho d is that we wo uldn't even need to implement o nCreateView() fo r the AlertDialo g. All we wo uld have
to do is co py the Ale rt Dialo g.Builde r co de fro m befo re into the o nCre at e Dialo g implementio n o f the
dialo g fragment.
Wrapping Up
Wo w! We co vered a lo t in this lesso n to o . We learned abo ut Dialo gs and dipped o ur to es into the suppo rt library and
fragments. Do n't be disco uraged if yo u still find fragments perplexing. The mo re we use them, the mo re yo u'll
understand them.
See yo u in the next lesso n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Menus
Hi there and welco me back! In this lesso n we'll co ver the vario us types o f menus in Andro id. Menus are co nvenient to o ls that
pro vide co ntextual o ptio ns to a user during an activity. There's quite a bit to co ver so let's get started!
Menus, Menus, Menus
The term "menu" can refer to a few different types o f co mpo nents in Andro id. The mo st co mmo n menu in Andro id is
the Options Menu. The Optio ns Menu appears when a user to uches the hardware menu butto n o n their device. In
versio ns befo re Andro id 3.0 , the Optio ns Menu appears as a small windo w ancho red to the bo tto m o f the device
screen, ho lding up to six menu items (auto matically arranged into two ro ws o f three butto ns). On devices using
Andro id 3.0 and up, the menu is integrated into the Applicatio n Bar.
Ano ther type o f menu is the Context Menu. Unlike the Optio ns Menu, the Co ntext Menu is directly asso ciated with a
View co mpo nent instead o f the Activity. A Co ntext Menu will appear when the user lo ng-presses o n the View with
which it was registered. The Co ntext Menu is similar in appearance to a traditio nal Dialo g. These are typically
implemented o n items in a list to allo w the user to perfo rm an alternate actio n o n the list item.
The last type o f menu is a Submenu. A Submenu is a menu item co ntained within ano ther menu. A Submenu can be
added to an Optio ns Menu o r a Co ntext Menu. Regardless o f the type o f menu to which a Submenu is attached, it will
resemble a Co ntext Menu.
Options Menu
To get started making o ur menus, create a new pro ject named Me nus, with the with the Package name
co m .o re illyscho o l.andro id1.m e nus, in the Andro id1_Le sso ns wo rking set.
Menus in Andro id are typically defined using XML reso urce files that are sto red in the /re s/m e nu fo lder o f the
pro ject. Let's use the ADT XML values file wizard to create o ur menu:
1. Select the Menus pro ject and then select File | Ne w | Ot he r (o r use the keybo ard sho rtcut Ct rlN).
2. In the "Select a Wizard" dialo g, cho o se the Andro id XML File o ptio n in the Andro id fo lder, and
click Ne xt .
3. In the "New Andro id XML File" wizard, change the "Reso urce Type" to Me nu, cho o se the Me nus
pro ject, enter the file name m ain_m e nu. Click Finish to create the XML reso urce.
If it did no t already exist, the wizard creates the /re s/m e nu fo lder auto matically and saves the new XML
reso urce into that fo lder. No w let's get into that new XML and create so me menu items! Mo dify
m ain_m e nu.xm l as sho wn:
/res/menu/main_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/mi_dalek"
android:title="Dalek"/>
<item
android:id="@+id/mi_cybermen"
android:title="Cybermen"/>
</menu>
In o rder to define menus in the XML reso urce, the ro o t xml no de must be m e nu, and its children must be
either it e m o r gro up no des. An item no de typically takes no children, but has many po ssible attributes, fo r
instance, id, t it le , ico n, o r visible . Each item no de represents an item in the menu. A gro up no de can take
o nly o ther item no des as children; a gro up no de is used to define certain attributes fo r its child item no des,
such as visible , e nable d, and che ckable .
Our menu here has o nly two items defined with titles (and no ico ns).
Note
There's a pletho ra o f native ico ns available to develo pers in the Andro id SDK. There are many
co mmo n menu actio ns amo ng apps o n Andro id (like "info " and "help"). If yo u implement these
kinds o f menu items in yo ur applicatio ns, I reco mmend that yo u use system ico ns. System
ico ns pro vide a co nsistent experience fo r the end user that will help them to understand ho w
yo ur applicatio n wo rks. If yo u find yo urself in need o f custo m ico ns, check o ut the Andro id
reco mmended guidelines fo r ico n design.
We've hard-co ded o ur strings fo r the title attribute, but we co uld have used a res/string.xml string reference id
instead (using the @string/<string-id> fo rmat). In fact, just abo ut any Andro id functio n that takes a string can
also take a reference to string reso urce id. I generally reco mmend using string reso urces rather than hardco ded strings. So far in the co urse, we've been hard-co ding mo st o f o ur strings to keep the co de co ncise and
fo cused. But in yo ur o wn future pro jects, yo u'll want to put all strings that will be visible to users in the strings
XML reso urce file.
Okay, no w let's get started with MainAct ivit y.java to implement the menu reso urce. Add the co de belo w to
MainAct ivit y.java as sho wn:
MainActivity.java
package com.oreillyschool.android1.menus;
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.view.Menu;
android.view.MenuInflater;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
}
And that's it! No w we can save and run the pro ject. Once the applicatio n is installed and running o n the
emulato r, click the Me nu key o n the emulato r screen; the menu sho uld po p up fro m the bo tto m:
OBSERVE:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
We use the o nCre at e Opt io nsMe nu() metho d to create o ur menu. The metho d is called by Andro id
auto matically when a user clicks o n the Menu butto n. o nCre at e Opt io nsMe nu() receives o ne o bject in its
parameters, a Me nu o bject. Then we inflate the menu o bject with o ur XML by using a Me nuInf lat e r o btained
fro m ge t Me nuInf lat e r(). The menu inflater takes two parameters: an R.java reference to the XML file and
the m e nu o bje ct into which the XML is inflated.
Just like the dialo g metho ds fo r creating a dialo g, o nCre at e Opt io nsMe nu() is called o nly o nce by the
Activity during its active lifecycle. To make updates to the menu befo re it is presented to the user, we wo uld
o verride and implement the o nPre pare Opt io nsMe nu() metho d. We'll practice do ing that later in the lesso n,
but fo r no w let's add so me co de to MainAct ivit y.java in o rder to respo nd to clicks o n the menu items:
MainActivity.java
package com.oreillyschool.android1.menus;
import
import
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.view.Menu;
android.view.MenuInflater;
android.view.MenuItem;
android.widget.Toast;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_LONG).show();
break;
case R.id.mi_cybermen:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT
).show();
break;
}
return true;
}
}
Save and run it. Yo u'll see the appro priate to ast message po p up fo r each menu item.
OBSERVE:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_LONG).show();
break;
case R.id.mi_cybermen:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT
).show();
break;
}
return true;
}
When a menu item is selected, the Andro id system triggers a call to the o nOpt io nsIt e m Se le ct e d()
metho d, passing the selected Me nuIt e m o bject as the o nly parameter. As lo ng as yo u have assigned an id
attribute to each o f the items in yo ur XML, yo u can use a switch/cases blo ck o n the it e m .ge t It e m Id() integer
to find o ut which item was clicked and respo nd acco rdingly. The t hird param e t e r fo r the makeText) metho d
co ntro ls ho w lo ng the message will display.
To demo nstrate the "Mo re" butto n, we'll add a few mo re items to m ain_m e nu.xm l:
/res/menu/main_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/mi_dalek"
android:title="Dalek"/>
<item
android:id="@+id/mi_cybermen"
android:title="Cybermen"/>
<item
android:id="@+id/mi_angels"
android:title="Weeping Angels"/>
<item
android:id="@+id/mi_silence"
android:title="The Silence"/>
<item
android:id="@+id/mi_silurians"
android:title="Silurians"/>
<item
android:id="@+id/mi_sontarans"
android:title="Sontarans"/>
<item
android:id="@+id/mi_master"
android:title="The Master"/>
</menu>
No w when yo u run the applicatio n yo u'll see the regular Optio ns Menu sho wing o nly the first five items; the
sixth item is a "Mo re" butto n. When yo u click the Mo re butto n, yo u'll see ano ther vertically arranged menu that
co ntains the remaining items:
We can try experimenting with a submenu no w as well. Mo dify m ain_m e nu.xm l as sho wn (in Eclipse, yo u
can indent a blo ck o f co de by highlighting it and pressing the T ab key):
/res/menu/main_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/mi_dalek"
android:title="Dalek"/>
<item
android:id="@+id/mi_cybermen"
android:title="Cybermen"/>
<item
android:title="Others">
<menu>
<item
android:id="@+id/mi_angels"
android:title="Weeping Angels"/>
<item
android:id="@+id/mi_silence"
android:title="The Silence"/>
<item
android:id="@+id/mi_silurians"
android:title="Silurians"/>
<item
android:id="@+id/mi_sontarans"
android:title="Sontarans"/>
<item
android:id="@+id/mi_master"
android:title="The Master"/>
</menu>
</item>
</menu>
Here we wrapped so me o f the previo us items in a m e nu tag, and then wrapped that within a new it e m tag
titled "Others." No w when we test the applicatio n menu butto n, the initial Optio ns Menu o nly sho ws "Dalek,"
"Cybermen," and "Others."
When yo u click Ot he rs, yo u see a submenu with the remaining items:
Modifying an Options Menu
As yo u do with a Dialo g, yo u'll want to update a menu befo re it beco mes visible to the user. Since the "create"
metho d fo r a menu gets called just o nce during the lifecycle o f an activity, yo u need to use ano ther metho d to
handle the updates. Make these changes to MainAct ivit y.java:
MainActivity.java
package com.oreillyschool.android1.menus;
import
import
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.view.Menu;
android.view.MenuInflater;
android.view.MenuItem;
android.widget.Toast;
public class MainActivity extends Activity {
private int optionLastClickedId = -1;
private int optionClickedId = -1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(optionLastClickedId);
if (item != null) {
item.setEnabled(true);
}
item = menu.findItem(optionClickedId);
if (item != null) {
item.setEnabled(false);
}
optionLastClickedId = optionClickedId;
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
optionClickedId = item.getItemId();
switch (optionClickedId) {
switch (item.getItemId()) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_LONG).show();
break;
case R.id.mi_cybermen:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT).sh
ow();
break;
}
return true;
}
}
Save and run it. Whichever menu item yo u click will beco me disabled the next time the Optio ns Menu is
presented (and the previo usly disabled item will beco me enabled):
OBSERVE:
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(optionLastClickedId);
if (item != null) {
item.setEnabled(true);
}
item = menu.findItem(optionClickedId);
if (item != null) {
item.setEnabled(false);
}
optionLastClickedId = optionClickedId;
return super.onPrepareOptionsMenu(menu);
}
The o nPre pare Opt io nsMe nu() metho d receives o ne parameter, the Me nu o bject that was created earlier
in the o nCreateOptio nsMenu() metho d. We can use this o bject to mo dify the menu any way we like, such as
finding spe cif ic Me nuIt e m o bje ct s and m o dif ying t he ir pro pe rt ie s, o r even adding o r remo ving a
MenuItem fro m the Menu.
Note
Menus can be created pro grammatically as well, using the Me nu and Me nuIt e m co nstructo rs,
then adding them with any o f the vario us "add" metho ds available o n Me nu. In this lesso n,
tho ugh, we create all o f o ur Menus with the mo st co mmo nly used Me nuInf lat e r metho d.
Context Menu
Like Optio ns Menus, Co ntext Menus can also be defined using an XML reso urce file. We can reuse the menu
XML reso urce that we used earlier fo r the Optio ns Menu to implement a Co ntext Menu. In o rder to do that, we
register the menu with a view co mpo nent in o ur Activity. First, we'll need to define a view co mpo nent in
act ivit y_m ain.xm l:
/res/layo ut/activity_main.xml
<RelativeLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello" />
</RelativeLinearLayout>
We use the default TextView that was generated with o ur pro ject, and we add an id attribute so we can lo cate
the co mpo nent. Next let's update MainAct ivit y.java to register the view with a menu:
MainActivity.java
package com.oreillyschool.android1.menus;
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.view.Menu;
android.view.MenuInflater;
android.view.MenuItem;
android.widget.Toast;
android.view.ContextMenu;
android.view.ContextMenu.ContextMenuInfo;
android.view.View;
public class MainActivity extends Activity {
private int optionLastClickedId = -1;
private int optionClickedId = -1;
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View textView = findViewById(R.id.text);
registerForContextMenu(textView);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo me
nuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(optionLastClickedId);
if (item != null) {
item.setEnabled(true);
}
item = menu.findItem(optionClickedId);
if (item != null) {
item.setEnabled(false);
}
optionLastClickedId = optionClickedId;
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
optionClickedId = item.getItemId();
switch (optionClickedId) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_LONG).show();
break;
case R.id.mi_cybermen:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT).sh
ow();
break;
}
return true;
}
}
In o ur Activity, we must o verride and implement the o nCre at e Co nt e xt Me nu() metho d in o rder to handle
creating the co ntext menu when o ur registered View has been lo ng-pressed (that is, when part o f yo ur screen
has been tapped and held do wn). We inflated the menu here exactly the same way we did fo r the Optio ns
Menu. This wo rks because we've registered o nly a single view fo r a Co ntext Menu, but o nce we register
multiple views (o r a list), we'll pro bably need to add so me mo re co de to determine which View is requesting a
Co ntext Menu, o therwise each View wo uld present the exact same Co ntext Menu. We can save and run o ur
co de no w to test the menu. To present the menu, yo u'll need to click and ho ld o n the TextView (the emulato r
versio n o f a lo ng-press):
Respo nding to Co ntext Menu clicks is similar to the Optio ns Menu as well. We just need to o verride a different
metho d. We can reuse the co de fro m o nOpt io nsIt e m Se le ct e d() since we're already inflating the same
menu:
MainActivity.java
package com.oreillyschool.android1.menus;
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.os.Bundle;
android.view.Menu;
android.view.MenuInflater;
android.view.MenuItem;
android.widget.Toast;
android.view.ContextMenu;
android.view.ContextMenu.ContextMenuInfo;
android.view.View;
public class MainActivity extends Activity {
private int optionLastClickedId = -1;
private int optionClickedId = -1;
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View textView = findViewById(R.id.text);
registerForContextMenu(textView);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo me
nuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_LONG).show();
break;
case R.id.mi_cybermen:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT).sh
ow();
break;
}
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(optionLastClickedId);
if (item != null) {
item.setEnabled(true);
}
item = menu.findItem(optionClickedId);
if (item != null) {
item.setEnabled(false);
}
optionLastClickedId = optionClickedId;
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
optionClickedId = item.getItemId();
switch (optionClickedId) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_LONG).show();
break;
case R.id.mi_cybermen:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT).sh
ow();
break;
}
return true;
}
}
Save and run the app o nce mo re to test that the click handler is wo rking co rrectly. The metho d fo r handling
Co ntext Menu clicks wo rks exactly the same way as the Optio ns Menu click handler metho d. Ho wever, while
the Optio ns Menu always co mes fro m the same menu so urce, the Co ntext Menu co uld po tentially be
generated fro m any View in the activity that registered to display a Co ntext Menu, so yo u might need to write
so me defensive co de to determine which View initiated the menu. This is especially true when using a
Co ntext Menu with a ListView.
Let's add a ListView to this applicatio n to see ho w to use Co ntext Menu with a list. We can reuse the list co de
fro m o ur earlier lesso n that co vered the ListView co mpo nent. If yo u still have the List s pro ject available, go
ahead and co py the /src/MyList Adapt e r.java and re s/layo ut /m y_list _it e m .xm l files into the
co rrespo nding fo lders in this pro ject, as well as the data and setup that was defined in MainActivity.java and
the ListView co mpo nent fro m activity_main.xml. Do n't just co py tho se last two files o ver, tho ugh; we want to
merge, no t replace, the list data with o ur existing co de.
If yo u do n't have the previo us co de o r yo u just want to re-type it, yo u can fo llo w the change instructio ns
belo w. Create a new class file named MyList Adapt e r and make it extend the
andro id.widge t .ArrayAdapt e r class. Then mo dify MyListAdapter.java as sho wn:
MyListAdapter.java
package com.oreillyschool.android1.menus;
import
import
import
import
import
import
import
import
com.oreillyschool.android1.menus.MainActivity.MyData;
android.content.Context;
android.graphics.Color;
android.view.LayoutInflater;
android.view.View;
android.view.ViewGroup;
android.widget.ArrayAdapter;
android.widget.TextView;
public class MyListAdapter extends ArrayAdapter<MyData> {
private LayoutInflater inflater;
public MyListAdapter(Context context, MyData[] data) {
super(context, R.layout.my_list_item, data);
inflater = LayoutInflater.from(context);
}
@Override
public View getView(int position, View convertView, ViewGroup root) {
View view = convertView;
if (view == null) {
view = inflater.inflate(R.layout.my_list_item, null);
}
MyData data = getItem(position);
TextView textView = (TextView) view.findViewById(R.id.text);
textView.setText(data.name);
View imageView = view.findViewById(R.id.color);
int color = data.clicked ? Color.RED : Color.BLUE;
imageView.setBackgroundColor(color);
return view;
}
}
Next, create a new XML layo ut file named m y_list _it e m .xm l as sho wn:
/res/layo ut/my_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center_vertical">
<View
android:id="@+id/color"
android:layout_width="10dp"
android:layout_height="50dp" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Next, mo dify act ivit y_m ain.xm l and MainAct ivit y.java as sho wn:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
MainActivity.java
package com.oreillyschool.android1.menus;
import
import
import
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.app.ListActivity;
android.os.Bundle;
android.view.ContextMenu;
android.view.ContextMenu.ContextMenuInfo;
android.view.Menu;
android.view.MenuInflater;
android.view.MenuItem;
android.view.View;
android.widget.Toast;
android.widget.ListView;
java.util.Comparator;
public class MainActivity extends Activity {
public class MainActivity extends ListActivity {
private int optionLastClickedId = -1;
private int optionClickedId = -1;
public class MyData {
public String name;
public boolean clicked;
public MyData(String name) {
this.name = name;
this.clicked = false;
}
}
private
new
new
new
new
new
new
new
new
new
new
};
MyData[] data = new MyData[] {
MyData("Odin"),
MyData("Thor"),
MyData("Loki"),
MyData("Baldr"),
MyData("Freyr"),
MyData("Heimdallr"),
MyData("Ullr"),
MyData("Meili"),
MyData("Hodr"),
MyData("Forseti")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View textView = findViewById(R.id.text);
registerForContextMenu(textView);
MyListAdapter adapter = new MyListAdapter(this, data);
adapter.sort(new Comparator<MyData>() {
@Override
public int compare(MyData arg0, MyData arg1) {
return arg0.name.compareTo(arg1.name);
}
});
setListAdapter(adapter);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
MyListAdapter adapter = (MyListAdapter) getListAdapter();
MyData item = adapter.getItem(position);
item.clicked = !item.clicked;
adapter.notifyDataSetInvalidated();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo me
nuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_LONG).show();
break;
case R.id.mi_cybermen:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT).sh
ow();
break;
}
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(optionLastClickedId);
if (item != null) {
item.setEnabled(true);
}
item = menu.findItem(optionClickedId);
if (item != null) {
item.setEnabled(false);
}
optionLastClickedId = optionClickedId;
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
optionClickedId = item.getItemId();
switch (optionClickedId) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_LONG).show();
break;
case R.id.mi_cybermen:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT).sh
ow();
break;
}
return true;
}
}
Befo re we make any further changes, save everything here and run the pro ject to make sure o ur previo us
menus and the list fro m the previo us lesso n are wo rking. Yo u'll see the list and still get the Co ntext menu
when yo u click and ho ld o n the TextView:
No w let's make so me mo re changes to get the ListView to sho w a co ntext menu fo r each item. We'll start by
creating a new menu XML reso urce. Name the new reso urce list _m e nu.xm l and make these changes:
/res/menu/list_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/mi_alpha"
android:title="Alpha" />
<item
android:id="@+id/mi_echo"
android:title="Echo" />
<item
android:id="@+id/mi_sierra"
android:title="Sierra" />
</menu>
No w we'll register o ur list to sho w a co ntext menu fo r each item in the list. Make these changes to
MainAct ivit y.java:
MainActivity.java
package com.oreillyschool.android1.menus;
import
import
import
import
import
import
import
import
import
import
import
import
android.app.ListActivity;
android.os.Bundle;
android.view.ContextMenu;
android.view.ContextMenu.ContextMenuInfo;
android.view.Menu;
android.view.MenuInflater;
android.view.MenuItem;
android.view.View;
android.widget.AdapterView.AdapterContextMenuInfo;
android.widget.ListView;
android.widget.Toast;
java.util.Comparator;
public class MainActivity extends ListActivity {
private int optionLastClickedId = -1;
private int optionClickedId = -1;
public class MyData {
public String name;
public boolean clicked;
public MyData(String name) {
this.name = name;
this.clicked = false;
}
}
private
new
new
new
new
new
new
new
new
new
new
};
MyData[] data = new MyData[] {
MyData("Odin"),
MyData("Thor"),
MyData("Loki"),
MyData("Baldr"),
MyData("Freyr"),
MyData("Heimdallr"),
MyData("Ullr"),
MyData("Meili"),
MyData("Hodr"),
MyData("Forseti")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View textView = findViewById(R.id.text);
registerForContextMenu(textView);
registerForContextMenu(getListView());
MyListAdapter adapter = new MyListAdapter(this, data);
adapter.sort(new Comparator<MyData>() {
@Override
public int compare(MyData arg0, MyData arg1) {
return arg0.name.compareTo(arg1.name);
}
});
setListAdapter(adapter);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
MyListAdapter adapter = (MyListAdapter) getListAdapter();
MyData item = adapter.getItem(position);
item.clicked = !item.clicked;
adapter.notifyDataSetInvalidated();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo me
nuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
final MenuInflater inflater = getMenuInflater();
if (v == getListView()) {
inflater.inflate(R.menu.list_menu, menu);
} else {
inflater.inflate(R.menu.main_menu, menu);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
MyData data = null;
if (item.getMenuInfo() != null && item.getMenuInfo() instanceof AdapterC
ontextMenuInfo) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuI
nfo();
data = (MyData) getListAdapter().getItem(info.position);
}
switch (item.getItemId()) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_LONG).show();
break;
case R.id.mi_cybermen:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT
).show();
break;
case R.id.mi_alpha:
case R.id.mi_echo:
case R.id.mi_sierra:
if (data != null)
Toast.makeText(this, "You clicked " + item.getTitle(), Toast.LENGT
H_LONG).show();
break;
}
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(optionLastClickedId);
if (item != null) {
item.setEnabled(true);
}
item = menu.findItem(optionClickedId);
if (item != null) {
item.setEnabled(false);
}
optionLastClickedId = optionClickedId;
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
optionClickedId = item.getItemId();
switch (optionClickedId) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_LONG).show();
break;
case R.id.mi_cybermen:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT
).show();
break;
}
return true;
}
}
Save yo ur changes and test the app. Yo u'll see the new Co ntext Menu when yo u lo ng-press o n a list item. If
yo u tap a Co ntext menu item, yo u'll see the co rrespo nding To ast message:
No w we'll make a change to the o nCo nt e xt It e m Se le ct e d metho d in MainActivity so it lo o ks up which
ListItem was clicked and displays bo th the list item name and the menu o ptio n name that was clicked in the
To ast message:
MainActivity.java
...
@Override
public boolean onContextItemSelected(MenuItem item) {
MyData data = null;
if (item.getMenuInfo() != null && item.getMenuInfo() instanceof AdapterC
ontextMenuInfo) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuI
nfo();
data = (MyData) getListAdapter().getItem(info.position);
}
switch (item.getItemId()) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_LONG).show();
break;
case R.id.mi_cybermen:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT
).show();
break;
case R.id.mi_alpha:
case R.id.mi_echo:
case R.id.mi_sierra:
if (data != null)
Toast.makeText(this, data.name + " - " + item.getTitle(), To
ast.LENGTH_LONG).show();
break;
}
return true;
}
...
Save and run the co de. When yo u lo ng-press an item and then select alpha, echo , o r sierra, yo u'll see a
message with bo th selectio ns.
OBSERVE:
MyData data = null;
if (item.getMenuInfo() != null && item.getMenuInfo() instanceof AdapterContextMe
nuInfo) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
data = (MyData) getListAdapter().getItem(info.position);
}
In the beginning o f the metho d we call ge t Me nuInf o () o n the Me nuIt e m o bject that was passed to the
metho d. If the so urce o f this Co ntext Menu is a list view item, the ge t Me nuInf o () returns an instance o f
Adapt e rCo nt e xt Me nuInf o . If the so urce o f the Co ntext Menu is a TextView, ge t Me nuInf o () will o nly
return a null, so we have to write so me defensive co de here to make sure we do n't get a null po inter erro r. If
we get an Adapt e rCo nt e xt Me nuInf o o bject, we can use it to find the po sitio n o f the list item, and with that
we can retrieve the mo del that was used to create the list item and then use the mo del name pro perty in the
To ast message.
Note
Unlike the Optio ns Menu, the Co ntext Menu's create metho d gets called after each lo ng-press
o n the registered View, so there's no "prepare" metho d to o verride in o rder to make changes to
the menu befo re it is sho wn; instead, yo u just put the lo gic in the o nCre at e Co nt e xt Me nu()
metho d. Keep in mind that Co ntext Menus do no t suppo rt ico ns.
Wrapping Up
Well, it seem like menus can po p up anywhere, huh? That's go o d, because they allo w yo u to pro vide additio nal
functio nality to the screen witho ut cluttering up the view. No w that yo u have experience creating, mo difying, and
handling each o f the vario us types o f Menus, feel free to play aro und with these to o ls o n yo ur o wn. If yo u feel like yo u'd
like a bit mo re guidance, hit up the Andro id do cumentatio n site to explo re Menus even further.
When yo u're ready, mo ve o n to the next lesso n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Saving Data with Shared Preferences
Go o d to have yo u back. I appreciate yo ur persistence! This lesso n co vers Shared Preferences. Shared Preferences help yo ur
applicatio n to remember decisio ns and the state o f the data between each applicatio n sessio n. There are o ther, mo re invo lved
metho ds o f data persistence available o n Andro id (such as a sqlite database), but we'll co ver tho se in future lesso ns.
Shared Preferences
Fo r this lesso n, we'll recycle co de fro m befo re. There was a lo t go ing o n in the co de fro m that last pro ject, but that
density will make it especially useful fo r us to use to test the Shared Preferences feature.
Think o f a Shared Preference as a basic data mo del to which yo ur applicatio n can read and write efficiently. The
SharedPreferences class is the interface used to co mmunicate with the data. Yo u can write any primitive data o bject to
the SharedPrefences data mo del (such as int, flo at, lo ng, and string). The data is saved as a key-value pair.
SharedPreferences do esn't suppo rt co mplex data mo dels, but it wo rks well fo r preserving applicatio n state and
perso nalized user settings between sessio ns. There's actually a specialized Activity fo r managing user settings that
handles much o f the wo rk auto matically. We'll get into that later, but right no w let's go o ver with the basics o f using the
SharedPreferences class.
Note
To save space in this and future lesso ns, we'll so metimes just sho w relevant po rtio ns o f the pro grams in
o ur CODE TO TYPE bo xes. We'll use ellipses (...) to indicate that so me co de has been o mitted.
Getting Started with SharedPreferences
We'll jump right in and integrate SharedPreferences into o ur existing co de. Previo usly, we created an Optio ns
Menu that wo uld disable the previo usly selected o ptio n. Let's mo dify o ur co de to persist the previo us
disabled o ptio n between sessio ns. In the Me nus pro ject, o pen the MainAct ivit y.java file, and make these
changes:
MainActivity.java
...
import
import
import
import
import
import
import
import
import
import
import
import
import
...
public
android.app.ListActivity;
android.content.SharedPreferences;
android.os.Bundle;
android.view.ContextMenu;
android.view.ContextMenu.ContextMenuInfo;
android.view.Menu;
android.view.MenuInflater;
android.view.MenuItem;
android.view.View;
android.widget.AdapterView.AdapterContextMenuInfo;
android.widget.ListView;
android.widget.Toast;
java.util.Comparator;
class MainActivity extends ListActivity {
private static final String DISABLED_OPTION_KEY = "disabledOption";
private int optionLastClickedId = -1;
private int optionClickedId = -1;
...
We start by defining a permanent key to use in the key-value pair. In larger pro jects, it's so metimes mo re
practical to define co nstant values in a helper/utility class, but we didn't do that here because we're fo cused o n
using SharedPreferences. We also delete the o ptio nClickedId variable, because we'll be handling that entirely
in SharedPreferences no w. Go ahead and make the next set o f changes to MainAct ivit y.java:
MainActivity.java
...
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem item = menu.findItem(optionLastClickedId);
if (item != null) {
item.setEnabled(true);
}
int optionClickedId = getPreferences(MODE_PRIVATE).getInt(DISABLED_OPTIO
N_KEY, -1);
item = menu.findItem(optionClickedId);
if (item != null) {
item.setEnabled(false);
}
optionLastClickedId = optionClickedId;
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
optionClickedId = item.getItemId();
int optionClickedId = item.getItemId();
final SharedPreferences prefs = getPreferences(MODE_PRIVATE);
final SharedPreferences.Editor editor = prefs.edit();
editor.putInt(DISABLED_OPTION_KEY, optionClickedId);
editor.commit();
switch (optionClickedId) {
case R.id.mi_dalek:
Toast.makeText(this, "Exterminate!", Toast.LENGTH_SHORT).show();
break;
case R.id.mi_cyberman:
Toast.makeText(this, "You will be upgraded.", Toast.LENGTH_SHORT).sh
ow();
break;
}
return true;
}
}
Save the pro gram. To make sure yo ur co de wo rks, run the applicatio n, click the Menu butto n, cho o se a menu
o ptio n and make sure that it's disabled when yo u o pen the menu again, clo se the applicatio n (hit the back
butto n until yo u see the deskto p), and then re-o pen the applicatio n to make sure the menu item that was
disabled befo re, is still disabled. Yo u sho uld be able to restart the emulato r and the applicatio n sho uld still
persist the disabled menu o ptio n:
Since we are no lo nger using the o pt io nClicke dId class member variable, we have to update each
reference that uses it to use the SharedPreferences class instead. Take a lo o k at o nPrepareOptio nsMenu():
OBSERVE:
int optionClickedId = getPreferences(MODE_PRIVATE).getInt(DISABLED_OPTION_KEY, 1);
The first fix was to lo ad the value o ut o f the SharedPreferences. We call the metho d ge t Pre f e re nce s()
(available fro m Activity) to get an instance o f the SharedPreference class unique to the current Activity. The
metho d takes o ne param e t e r, which defines the permissio ns o f the preferences file that is created fo r the
Activity. The Activity class defines a series o f co nstants to help yo u co nfigure the privacy co rrectly:
MODE_PRIVATE, MODE_APPEND, MODE_WORLD_READABLE, and MODE_WORLD_WRITABLE. We use
MODE_PRIVAT E to keep the preferences private and inaccessible to o ther languages. If a preference file
already exists with a privacy o ther than MODE_PRIVATE, that preference file will be deleted and a new o ne
created. Using MODE_APPEND as o ur parameter wo uld o nly create a new preference file if o ne didn't exist,
regardless o f privacy setting. MODE_WORLD_READABLE allo ws o ther apps to read the preference file, and
MODE_WORLD_WRITABLE allo ws o ther apps to read and mo dify the preference file.
We use the ge t Int () metho d, sending it the DISABLED_OPT ION_KEY key, to retrieve the saved value fo r
o ur key-value pair. This metho d (as well as every o ther get metho d o n SharedPreferences) takes a seco nd
parameter as a "default value" to be returned if the key-value pair do es no t exist yet in the SharedPreference
file. It is safe to use -1 as a default value here because reso urce id parameters never use negative values:
OBSERVE: o nOptio nsItemSelected()
int optionClickedId = item.getItemId();
final SharedPreferences prefs = getPreferences(MODE_PRIVATE);
final SharedPreferences.Editor editor = prefs.edit();
editor.putInt(DISABLED_OPTION_KEY, optionClickedId);
editor.commit();
Next, we update o nOpt io nsIt e m Se le ct e d() to save the co rrect value into Share dPre f e re nce s. Again we
use ge t Pre f e re nce s with the sam e privacy param e t e r to get access to o ur Share dPre f e re nce s o bject.
To write a value to the preference file, yo u have to get an instance o f the Share dPre f e re nce s.Edit o r class
by calling the e dit () metho d o n the SharedPreferences class. Then, with the Edito r, we use the helper metho d
to update the key-value pair to the preference. Finally, we call co m m it () o n the Edito r class in o rder to write
the value to the SharedPreferences file; if we didn't call co m m it (), SharedPreferences wo uld ro ll back to their
previo us values.
There are many o ther helper metho ds available o n the Edito r class fo r the different types o f value that are
suppo rted by SharedPreferences, such as put St ring() and put Lo ng(). Instead o f using the
ge t Pre f e re nce s() metho d to get the SharedPreferences o bject, we co uld have used the
ge t Share dPre f e re nce s() metho d. Bo th metho ds wo rk almo st identically, but ge t Share dPre f e re nce s()
takes ano ther parameter (a String) that is used as a unique name fo r the SharedPreferences file that is created
fo r the Activity. Using ge t Share dPre f e re nce s(), yo u can create as many different SharedPreference files as
yo u want. If yo u need o nly a single preference cache fo r a single Activity, use ge t Pre f e re nce s(). If yo u want
multiple activities to have access to the same preference class, then yo u have a few o ptio ns available. Yo u
co uld use ge t Share dPre f e re nce s() and use the same name in each Activity that lo ads the preference, o r
yo u co uld use the Applicatio n class to create a default SharedPreference; fo r example,
ge t Applicat io n().ge t Pre f e re nce s(MODE_PRIVAT E).
We haven't co vered the Applicatio n class in great detail, but there's no t typically much need to do that. The
Applicatio n class functio ns almo st exactly like an Activity with similar metho ds available to it (bo th Activity and
Applicatio n extend the Co ntext class). Ho wever, each Andro id app has o nly o ne Applicatio n, and it can be
accessed fro m any Activity class using the ge t Applicat io n() metho d.
Pro bably the best o ptio n fo r making sure all yo ur activities use the same preferences file is the
PreferenceManager class. It has a static metho d called ge t De f ault Share dPre f e re nce s() that takes o ne
parameter (a Co ntext such as an Activity o r Applicatio n) and returns a default SharedPreferences o bject will
use the same file, regardless o f the Co ntext that is passed to it. This file is best used with the
Pre f e re nce Act ivit y class because it's the same Share dPre f e re nce file used by that class.
Note
Pre f e re nce Manage r.ge t De f ault Share dPre f e re nce s() is actually using the o ptio n I
described earlier; it's using the same name fo r the ge t Share dPre f e re nce s() call each time.
The name it uses is actually a co mbinatio n o f the applicatio n package name and the string
"_preferences" (co nt e xt .ge t Package Nam e () + " _pre f e re nce s" ).
PreferenceActivity
Many applicatio ns have what's co mmo nly referred to as a "Settings screen." Andro id has a native custo m
Activity available, the Pre f e re nce Act ivit y, that is meant to assist yo u in creating a Settings screen designed
specifically fo r yo ur applicatio n. The PreferenceActivity has its o wn unique XML fo rmat fo r declaring its view.
The PreferenceActivity uses the PreferenceManager class to manage its SharedPreferences o bject as well, so
that the preferences managed o n the activity are available to the rest o f the activities in the applicatio n.
Note
If yo u bro wse the Andro id develo per do cumentatio n fo r help implementing the
PreferenceActivity, yo u might find it a bit frustrating. As o f Andro id 3.0 , all o f the previo us APIs fo r
setting up a PreferenceActivity were deprecated in favo r o f the new standard using the
PreferenceFragment class. Unlike o ther fragments, ho wever, the PreferenceFragment is no t
available in the suppo rt library, so we still have to use the deprecated APIs if we want to suppo rt
devices running an Andro id versio n befo re 3.0 .
Let's implement a PreferenceActivity screen in o ur applicatio n no w. Start by creating a new class called
MyPre f e re nce Act ivit y; make sure it extends the Pre f e re nce Act ivit y class:
Click Finish to create the activity. We have relatively little co de to mo dify fo r this activity; just make this
change:
MyPreferenceActivity.java
package com.oreillyschool.android1.menus;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class MyPreferenceActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
This sho uld be thro wing an erro r right no w since we reference a reso urces file that we haven't created yet—so
let's create it no w:
1. With the Me nus pro ject selected, select File | Ne w | Ot he r (o r use the keybo ard sho rtcut Ct rlN).
2. In the "Select a Wizard" dialo g, cho o se the Andro id XML File o ptio n in the Andro id fo lder, and
click Ne xt .
3. In the "New Andro id XML File Wizard," change the "Reso urce Type" to Pre f e re nce ; name the
file pre f e re nce s; under "Ro o t Element," select the Pre f e re nce Scre e n; and click Finish to
create the XML reso urce.
This creates the /re s/xm l fo lder in yo ur pro ject (if it do esn't already exist), then creates the new
pre f e re nce s.xm l file and saves it in there. No w let's add a preference to this XML. Mo dify yo ur co de as
sho wn:
/res/xml/preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<PreferenceCategory android:title="Important Stuff">
<CheckBoxPreference
android:key="listViewVisible"
android:title="ListView visible"
android:defaultValue="true"
/>
<EditTextPreference
android:key="username"
android:title="Username"
android:defaultValue="User"
/>
</PreferenceCategory>
<PreferenceCategory android:title="Unimportant Stuff">
<CheckBoxPreference
android:key="doesNothing"
android:title="Unimportant Text"
/>
</PreferenceCategory>
</PreferenceScreen>
The PreferenceActivity XML suppo rts many different types o f standard preference screen co mpo nents
including checkbo xes, editable, text areas, lists, as well as preference gro ups to help o rganize yo ur
preferences. Here we've used two types o f co mpo nents: the Che ckBo xPre f e re nce and the
Edit T e xt Pre f e re nce , which will co rrespo nd to a CheckBo x and EditText view co mpo nent, respectively.
Befo re we can even test o ur co de, we need to update MainAct ivit y.java with a ho o k to lo ad this activity.
Let's go the quick-and-dirty ro ute o f setting up a quick click-listener o n the to p TextView. Mo dify yo ur co de as
sho wn:
MainActivity.java
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View textView = findViewById(R.id.text);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, MyPreferenceActivity
.class));
}
});
registerForContextMenu(textView);
registerForContextMenu(getListView());
...
}
Finally, we need to add the Activity to the Andro idManif e st .xm l file. Mo dify yo ur co de so it lo o ks like this:
Andro idManifest.xml
...
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".MainActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MyPreferenceActivity"></activity>
</application>
...
No w we're ready to test the co de. The preferences aren't ho o ked up to anything yet, but we sho uld at least be
able to test to make sure that the PreferenceActivity is creating its view co rrectly. Start up the applicatio n, and
click the He llo wo rld text at the to p o f the screen; yo ur PreferenceActivity screen will lo o k like this:
Note
The Pre f e re nce Scre e n xml tag can even be nested inside o f itself. When clicked, this will
create an item o n the screen that will lo ad a brand new preference screen, po pulated with the
preferences that are children o f the nested tag. This is co mmo nly used fo r things like "Advanced
Settings" o ptio ns in a typical settings screen.
No w let's ho o k up so me o f these preferences to verify that they're wo rking. Make these changes to
MainAct ivit y.java:
MainActivity.java
...
@Override
protected void onResume() {
super.onResume();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
this);
TextView textView = (TextView)findViewById(R.id.text);
textView.setText(String.format("Hello %s, welcome back!", prefs.getStrin
g("username", "user")));
if (prefs.getBoolean("listViewVisible", true)) {
getListView().setVisibility(View.VISIBLE);
} else {
getListView().setVisibility(View.INVISIBLE);
}
}
Here we cho o se to o verride the o nRe sum e metho d instead o f using o nCre at e . In the lifecycle o f an
Andro id Activity class, the o nCreate metho d will get called o nly o nce when the Activity is initially prepared and
created. The o nRe sum e metho d, ho wever, always gets called just befo re the activity beco mes visible. Since
the MainAct ivit y class isn't destro yed when we lo ad the MyPre f e re nce Act ivit y (just added to the backstack), we have to manage the update o f o ur preference changes in the o nRe sum e metho d. To learn mo re
abo ut the lifecycle o f an Activity, I highly reco mmend checking o ut the Andro id develo per site (co mplete with a
handy info -graphic).
After lo ading the Share dPre f e re nce s o bject fro m the Pre f e re nce Manage r, the rest o f the co de wo rks
exactly the same way it did befo re, using the vario us "get" metho ds to retrieve the cached data. Be sure to test
yo ur applicatio n and verify that the settings page no w co ntro ls the "name" that's used in the TextView, as well
as the CheckBo xPreference co ntro lling whether the ListView is visible.
Note
There are a co uple o f do wn-sides to be aware o f with the PreferenceActivity. First, yo u can't use
a "static final" variable as yo ur key in the XML preference reso urce, so make certain that yo ur key
strings are always identical (o r co nsider using a strings.xml reso urce). Also , frequently yo u'll
need to define a default value in multiple areas. It's usually best to keep them co nsistent. In any
case, the benefits o f using the Pre f e re nce Act ivit y far o utweigh these mino r inco nveniences.
Wrapping Up
We've o nly just scratched the surface o f managing data o n Andro id with these essential classes. Make sure yo u're
co nfident using a simple SharedPreferences o bject and setting up a PreferencesActivity. These co nvenient classes will
help yo u to implement simple data persistence quickly, in any applicatio n.
Go o d wo rk so far. Let's press o n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Saving Data with a Database
Welco me back! In this lesso n, we'll wo rk o n sto ring and retrieving data fro m a database in Andro id. The Andro id SDK has builtin classes to help create and manage an SQLite database.
SQLite
Note
While it is no t necessary to be an expert in SQLite fo r this lesso n, it's go o d to have a basic understanding
o f ho w databases wo rk, and ho w to perfo rm SQL queries. If yo u think yo u co uld use so me help when it
co mes to wo rking with databases, co nsider taking the O'Reilly Scho o l o f Techno lo gy co urse PHP/SQL 1:
Intro ductio n to Database Pro gramming.
Creating a Helper
Let's get started! Create a pro ject named Dat abase , name the package
co m .o re illyscho o l.andro id1.dat abase , and assign it to the Andro id1_Le sso ns wo rking set.
The primary class fo r interacting with a SQLite database in Andro id is the SQLit e Ope nHe lpe r class. The
SQLit e Ope nHe lpe r class is an abstract class to be used fo r creatio n and versio n management o f the
SQLite database. Let's set up a basic SQLit e Ope nHe lpe r implementatio n first. In the Dat abase pro ject,
create a new class file named DBHe lpe r that extends the SQLit e Ope nHe lpe r class:
SQLit e Ope nHe lpe r has two abstract metho ds that we must implement: o nCre at e () and o nUpgrade ().
Each metho d is intended to be used to mo dify the structure o f a database after the respective event. During
o nCre at e , we'll set up the basic tables fo r all the data, and in o nUpgrade , we'll enter any migratio n lo gic
needed to co nvert a database fro m an o lder versio n to match the new database. Make sure that bo th
metho ds result in the database having the same schema, regardless o f whether they just installed the
applicatio n o r are updating fro m a previo us applicatio n versio n.
Yo u might have no ticed that the class generated fro m the "New Class Wizard" thro ws a co mpiler erro r
initially. This is because we haven't implemented a co nstructo r fo r the class to co mplete o ur implementatio n.
Let's tackle that no w. Make these changes to yo ur co de:
DBHelper.java
package com.oreillyschool.android1.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "myDatabase.db";
private static final int DB_VERSION = 1;
public DBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
}
}
In the co nstructo r, we call the super co nstructo r with the appro priate values:
OBSERVE:
private static final String DB_NAME = "myDatabase.db";
private static final int DB_VERSION = 1;
public DBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
We can predefine the last three values fro m within this class, but we'll need a Co nt e xt fro m whatever class is
attempting to access the database (mo st likely an Activity). The seco nd parameter is the unique nam e to use
fo r o ur database. This sho uld never change, especially when yo u update the applicatio n. If yo u need multiple
databases, make sure the name value is unique fo r each database. We can safely igno re the t hird
param e t e r fo r no w. The last parameter is the ve rsio n o f t he Dat abase that we are currently using. Any
time yo u update the applicatio n and yo u have to make changes to the schema o f yo ur database, yo u sho uld
increment this versio n id (just change the "static final" variable). If the SQLit e Ope nHe lpe r detects an
existing database with a versio n lo wer than this id, the o nUpgrade () metho d will be called instead o f
o nCre at e ().
Let's define and initialize a simple database fo r use in o ur applicatio n in the o nCre at e () metho d:
DBHelper.java
...
public class DBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "myDatabase.db";
private static final int DB_VERSION = 1;
public static final String TABLE_PEOPLE = "people";
public static final String C_ID = "_id";
public static final String C_NAME = "name";
public DBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
final String sqlCreateTablePeople = "CREATE TABLE "
+ TABLE_PEOPLE + "( " + C_ID
+ " integer primary key autoincrement, " + C_NAME
+ " text not null);";
db.execSQL(sqlCreateTablePeople);
}
...
We added a few mo re "static final" String values to the class that defines the table and co lumn names. We left
these public to allo w o ther classes to use them as well. In the o nCre at e () metho d, we created the SQL
statement necessary to add o ur table to the database using a String, and executed the string by passing it to
the SQLit e Dat abase metho d e xe cSQL(). The e xe cSQL() statement is used o nly fo r quick SQL
co mmands to execute o n the database when yo u do n't require any feedback fro m the database. This makes it
ideal fo r schema updates such as creating/deleting a table o r mo difying table co lumns.
Next, let's write a quick and basic implementatio n o f the o nUpgrade () metho d:
DBHelper.java
...
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
final String sqlDropTablePeople = "DROP TABLE IF EXISTS " + TABLE_PEOPLE
+ ";";
db.execSQL(sqlDropTablePeople);
onCreate(db);
}
Fo r o nUpgrade (), we dro p the "peo ple" table (if it already exists) and then recreate it by calling the
o nCre at e () metho d. This is the easiest way to safely implement a database upgrade, but with o ne glaring
po tential co ncern: depending o n yo ur applicatio n, yo u might want to preserve any data already in the
database during an upgrade. In that situatio n yo u wo uld need to write the appro priate migratio n lo gic fo r yo ur
tables to co rrectly update the database schema.
Using the Helper
No w that we've created a simple helper and defined o ur database, we'll need to write so me co de to use this
data in a view. Let's start with updating the act ivit y_m ain.xm l layo ut file. Mo dify yo ur co de as sho wn:
/res/layo ut/activity_main.xml
<RelativeLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/add_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Add Person"
android:onClick="onAddClicked" />
<Button
android:id="@+id/delete_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete Person"
android:onClick="onDeleteClicked" />
</LinearLayout>
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLinearLayout>
Here we have two butto ns at the to p: o ne to add ro ws to the database and o ne to remo ve ro ws. We'll also
use a ListView to display the data in the database. Next, let's create a view fo r a dialo g that can be present
when we click the Ne w Pe rso n butto n. Create a new Andro id Layo ut XML file named add_pe rso n_dialo g
and make these changes:
/res/layo ut/add_perso 0 n_dialo g.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Name" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/okay_btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Okay" />
<Button
android:id="@+id/cancel_btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Cancel" />
</LinearLayout>
</LinearLayout>
This basic view with an EditText and two Butto ns sho uld wo rk. Next, mo dify MainAct ivit y.java to ho o k the
dialo g up to the "Add Perso n" butto n and implement an insert o n o ur database:
MainActivity.java
package com.oreillyschool.android1.database;
import
import
import
import
import
import
import
import
android.app.Activity;
android.app.Dialog;
android.content.ContentValues;
android.database.sqlite.SQLiteDatabase;
android.os.Bundle;
android.view.Menu;
android.view.View;
android.widget.TextView;
public class MainActivity extends Activity {
private static final int ADD_DIALOG = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onAddClicked(View view) {
showDialog(ADD_DIALOG);
}
public void onDeleteClicked(View view) {
}
@Override
protected Dialog onCreateDialog(int id) {
Dialog d;
switch (id) {
case ADD_DIALOG:
d = new Dialog(this);
d.setContentView(R.layout.add_person_dialog);
d.setTitle("Add a Person");
final TextView nameText = (TextView) d.findViewById(R.id.name);
d.findViewById(R.id.okay_btn).setOnClickListener(new View.OnClickLis
tener() {
@Override
public void onClick(View v) {
addPerson(nameText.getText().toString());
dismissDialog(MainActivity.ADD_DIALOG);
}
});
d.findViewById(R.id.cancel_btn).setOnClickListener(new View.OnClickL
istener() {
@Override
public void onClick(View v) {
dismissDialog(MainActivity.ADD_DIALOG);
}
});
break;
default:
d = super.onCreateDialog(id);
break;
}
return d;
}
public void addPerson(String name) {
// add the new data to the db
DBHelper helper = new DBHelper(this);
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put(DBHelper.C_NAME, name);
db.insert(DBHelper.TABLE_PEOPLE, null, cv);
db.close();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
The impo rtant new co de to take no te o f here is in the new addPe rso n() metho d:
OBSERVE:
public void addPerson(String name) {
// add the new data to the db
DBHelper helper = new DBHelper(this);
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put(DBHelper.C_NAME, name);
db.insert(DBHelper.TABLE_PEOPLE, null, cv);
db.close();
}
Here we use o ur DBHe lpe r class to get a SQLit e Dat abase o bject o n which we can perfo rm inserts. We
also need to create a Co nt e nt Value s o bject fo r the new data. The Co nt e nt Value s class ho lds all the data
in a key-value map where the key is the database co lumn to use fo r the value. Then we use the
SQLit e Dat abase inse rt () metho d to co mmit the data. Its parameters are t he t able nam e , a "null co lumn
hack" string (which we can safely igno re), and the co nt e nt value s to be inserted. SQLite o nly suppo rts a
single ro w insert at a time, so fo r each insert yo u want to perfo rm, yo u must call inse rt () again. Finally, since
we are do ne using the database, we call clo se () o n the SQLit e Dat abase .
At this po int we are able to save and test to make sure that o ur Dialo g is being created and dismissed
co rrectly, but we'll still have no idea whether o ur Database inserts are actually wo rking. Fo r that, we'll have to
write so me lo gic to query the database and update the ListView with the results.
Cursor and CursorAdapater
Andro id pro vides a wrapper class fo r retrieving the results fro m a query to a database called a Cursor.
Andro id also pro vides a co nvenient implementatio n o f the Adapt e r interface fo r supplying data to a list
thro ugh a Curso r, called a Sim ple Curso rAdapt e r. These classes are perfect fo r testing the results o f o ur
earlier co de. Make these changes to MainAct ivit y.java:
MainActivity.java
package com.oreillyschool.android1.database;
import
import
import
import
import
import
import
import
import
import
android.app.Activity;
android.app.Dialog;
android.app.ListActivity;
android.content.ContentValues;
android.database.Cursor;
android.database.sqlite.SQLiteDatabase;
android.os.Bundle;
android.view.View;
android.widget.SimpleCursorAdapter;
android.widget.TextView;
public class MainActivity extends ActivityListActivity {
private static final int ADD_DIALOG = 0;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// initialize the adapter
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_single_choice,
null,
new String[]{DBHelper.C_NAME},
new int[]{android.R.id.text1});
setListAdapter(adapter);
updateAdapterData();
}
public void updateAdapterData() {
// re-query the data
SQLiteDatabase db = new DBHelper(this).getReadableDatabase();
Cursor c = db.query(DBHelper.TABLE_PEOPLE,
null, null, null, null, null, null);
((SimpleCursorAdapter)getListAdapter()).changeCursor(c);
db.close();
}
...
public void addPerson(String name) {
// add the new data to the db
DBHelper helper = new DBHelper(this);
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put(DBHelper.C_NAME, name);
db.insert(DBHelper.TABLE_PEOPLE, null, cv);
db.close();
// update the view
updateAdapterData();
}
No w we sho uld be able to save and run the co de to make sure that o ur add metho d is co rrectly adding to the
database, as well as see o ur data being lo aded co rrectly into the List Vie w:
There's a lo t go ing o n in here. Let's lo o k at it bit by bit:
OBSERVE: o nCreate()
// initialize the adapter
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1,
null,
new String[]{DBHelper.C_NAME},
new int[]{android.R.id.text1});
setListAdapter(adapter);
We start o ff by creating an inst ance o f t he Sim ple Curso rAdapat e r fo r o ur list, which takes five
parameters fo r its co nstructo r: a Co nt e xt , an int layo ut id re f e re nce , a Curso r, a St ring array o f
co lum n nam e s, and an int array o f vie w ids. Fo r the layo ut , we use a default layo ut available to us in
Andro id which pro vides a basic TextView. Fo r the Curso r, we send null fo r no w, because we're handling that
later in the updat e Adapt e rDat a() metho d. The last two array parameters are intended to match o ne ano ther
in length, so that the values fro m each co lumn defined in the St ring array will be assigned to the respective
view co mpo nent with the id defined in the int array.
After creating this adapt e r, we assign it to the list thro ugh the List Act ivit y metho d se t List Adapt e r(). And
finally we call the updat e Adapt e rDat a metho d that we just created belo w to lo ad the data into the list. We
must refresh the Curso r after each additio n to keep that lo gic in a helper metho d so we're no t writing the same
co de in multiple areas:
OBSERVE: updateAdapterData()
public void updateAdapterData() {
// re-query the data
SQLiteDatabase db = new DBHelper(this).getReadableDatabase();
Cursor c = db.query(DBHelper.TABLE_PEOPLE,
null, null, null, null, null, DBHelper.C_NAME);
startManagingCursor(c);
((SimpleCursorAdapter)getListAdapter()).changeCursor(c);
db.close();
}
In updat e Adapt e rDat a(), we use the DBHe lpe r class again. This time we call the que ry() metho d to
retrieve the data. The que ry() metho d is a helper metho d fo r perfo rming a "SELECT" query o n the database.
It takes many parameters in o rder to suppo rt many vario us types o f "SELECT" queries. We are perfo rming
o nly basic queries, so we end up passing null to many o f the parameters. The first parameter is the t able
nam e —it is no t o ptio nal. The o nly o ther parameter we're sending is the last o ne, which defines by which
co lum n t he re sult s sho uld be so rt e d. If we didn't care abo ut so rting, we co uld pass null fo r that
parameter as well. This que ry() call is the equivalent to the SQL "SELECT * FROM peo ple ORDER BY
name;". Pro per usage o f the que ry() metho d sanitizes the query auto matically, to prevent SQL injectio n
hacks.
We wo n't go into all the parameters available to que ry() here, but mo st o f them are mo re o r less selfexplanato ry when yo u read the co de hints in Eclipse. If yo u want to read mo re abo ut the parameters, check
the Andro id develo per do cumentatio n o n the SQLiteDatabase query metho d.
Finally, we'll implement the "delete" butto n that we created earlier. Make these changes to MainAct ivit y.java:
MainActivity.java
...
import android.widget.ListView;
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ListView list = getListView();
list.setItemsCanFocus(false);
list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// initialize the adapter
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_single_choice,
null,
new String[]{DBHelper.C_NAME},
new int[]{android.R.id.text1});
setListAdapter(adapter);
updateAdapterData();
}
...
public void onDeleteClicked(View view) {
int position = getListView().getCheckedItemPosition();
if (position >= 0) {
long itemId = getListAdapter().getItemId(position);
SQLiteDatabase db = new DBHelper(this).getWritableDatabase();
int rowsAffected = db.delete(DBHelper.TABLE_PEOPLE, DBHelper.C_ID +
" = " + itemId, null);
db.close();
if (rowsAffected > 0)
updateAdapterData();
}
}
No w yo u'll be able to run the applicatio n again and delete whatever ro w is checked:
We made a few changes to the list here to allo w us to select an individual ro w. The default layo ut
sim ple _list _it e m _single _cho ice lo o ks just like the previo us layo ut, but with a Radio Butto n added to the
side o f the ro w as well. By calling se t Cho ice Mo de () o n the list earlier and giving it the parameter
List Vie w.CHOICE_MODE_SINGLE, we instruct the list to manage the checked ro w, and to allo w o nly o ne
ro w to be checked at a time.
In the o nDe le t e Clicke d() metho d, we implemented the delete actio n. We use the List Vie w metho d
ge t Che cke dIt e m Po sit io n() to find o ut which item is checked. This co uld po tentially be -1 if no item is
checked, so we co de defensively aro und that. Then we have to use the ListAdapter to retireve the actual ro w id
o f the item. This co rrespo nds to the "_id" co lumn o f the data in the database. Finally, we get a writable versio n
o f the database fro m o ur helper again, o nly this time we call de le t e , giving it the table name, and an SQL
"WHERE" clause to delete just the ro w that matches the "_id" o f the list item that is checked. Passing null to
the seco nd argument wo uld delete all ro ws in the table. The final argument is used to help sanitize the query
again by replacing any questio n marks in the "WHERE" clause with a sanitized value fro m the third argument.
We aren't co ncerned with that here, so we just pass in null. If any ro ws were affected by o ur delete query, then
we update the adapter (after clo sing the database, o f co urse).
Wrapping Up
Pro perly managing a database in Andro id can seem like quite a daunting task. Thankfully Andro id has pro vided many
co nvenient helper classes to make that easier. Ho pefully by no w yo u're feeling co mfo rtable with perfo rming the CRUD
actio ns (create, read, update, delete) o n a database in Andro id. There's a lo t o f advanced SQLite co ntent that we didn't
co ver in this lesso n, but this is an excellent fo undatio n and usually eno ugh fo r mo st applicatio ns. If yo u find yo urself in
need o f so me really advanced SQLite wo rk in yo ur applicatio n, as always, be sure to check o ut the Andro id develo per
do cumentatio n site.
See yo u in the next lesso n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Threading with AsyncTasks
Welco me back! This lesso n co vers threading in Andro id. Implementing pro per threading is crucial fo r Andro id applicatio n
develo pment. It helps yo u to maintain fast and seamless views that enable a go o d user experience, and even mo re impo rtantly,
it can help prevent yo ur applicatio n fro m falling into an Applicatio n No t Respo nding (ANR) state.
An ANR Dialo g will be presented o ver yo ur applicatio n in the event that the user interface do esn't respo nd to input events (such
as a screen to uch o r key press event) within 5 seco nds. When the ANR dialo gue is presented, the Andro id system (and the
user) assume that yo ur applicatio n has crashed and will no t reco ver. In mo st cases, this will lead to negative reviews o n the
Andro id app market as well.
T hreading in Android
In Andro id, every Applicatio n is assigned a default "main" thread, co mmo nly referred to as the UI Thread. All wo rk fo r
an applicatio n is do ne o n this thread by default, including user interface drawing, event dispatching and handling, and
all the co de that yo u write. If yo u write co de that takes a lo ng time to finish, during that time, yo ur interface may no t be
able to draw updates to the screen. This is o ften the cause o f an ANR state. To prevent an o ccurrence o f an ANR state,
we'll create a separate thread to handle the wo rk that, when executed, will run asynchro no usly. The Andro id SDK
pro vides a helper class fo r creating and managing wo rk in a separate Thread, called an AsyncT ask.
AsyncT ask
AsyncTask is a helper class that makes it easier to spin o ff a Thread to do wo rk, track pro gress, and respo nd
to the results. It can be a little co nfusing to set up at first because it takes three generic parameters. We'll set
up a new pro ject named T hre ads, with package name co m .o re illyscho o l.andro id1.t hre ads, assigned to
the Andro id1_Le sso ns wo rking set.
Let's create a sho rt metho d that fakes so me heavy wo rk. Make these changes to MainAct ivit y.java:
MainActivity.java
package com.oreillyschool.android1.threads;
import
import
import
import
import
android.app.Activity;
android.graphics.Bitmap;
android.graphics.BitmapFactory;
android.os.Bundle;
android.view.Menu;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private Bitmap downloadImage() {
final long start = System.currentTimeMillis();
// wait 5 seconds (5000 milliseconds) until proceeding
while (System.currentTimeMillis() - start < 5000) {
}
return BitmapFactory.decodeResource(getResources(), R.drawable.ic_launch
er);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
This sho rt metho d will wait five seco nds and then return the app ico n image as a Bitmap. Next, add so me
co mpo nents to help demo nstrate o ur co ncepts. Make these changes to act ivit y_m ain.xm l:
/res/layo ut/activity_main.xml
<RelativeLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onLoadImageClicked"
android:text="Load Image" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center" >
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="center"
android:visibility="gone" />
</RelativeLayout>
</RelativeLinearLayout>
We added a butto n to trigger o ur lo ading pro cess, as well as a Pro gre ssBar and an Im age Vie w nested and
centered in a Re lat ive Layo ut . The Pro gre ssBar is set to inde t e rm inat e so that it will spin co ntinuo usly.
The Im age Vie w is no t initially visible. Once we lo ad the image, we'll hide the Pro gre ssBar and sho w the
Im age Vie w. No w let's head back to MainAct ivit y.java and co nnect everything:
MainActivity.java
package com.oreillyschool.android1.threads;
import
import
import
import
import
import
import
android.app.Activity;
android.graphics.Bitmap;
android.graphics.BitmapFactory;
android.os.Bundle;
android.view.View;
android.widget.ImageView;
android.widget.ProgressBar;
public class MainActivity extends Activity {
private ImageView image;
private ProgressBar progress;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progress = (ProgressBar) findViewById(R.id.progress);
image = (ImageView) findViewById(R.id.image);
}
public void onLoadImageClicked(View view) {
// show the progress and hide the image
image.setVisibility(View.GONE);
progress.setVisibility(View.VISIBLE);
image.setImageBitmap(downloadImage());
// show the image and hide the progress
image.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
}
private Bitmap downloadImage() {
final long start = System.currentTimeMillis();
// wait 5 seconds (5000 milliseconds) until proceeding
while (System.currentTimeMillis() - start < 5000) {
}
return BitmapFactory.decodeResource(getResources(), R.drawable.ic_launch
er);
}
}
No w that we've ho o ked everything up, we can give the co de a test run. We aren't using an AsyncTask yet,
because I want to demo nstrate so me pro blems yo u can run into if yo u do n't use a separate thread to run
heavy co de. After the applicatio n is installed and running, yo u'll see the infinite pro gress bar spinning in the
middle. If yo u click Lo ad Im age , the pro gress bar spinner will freeze during the five seco nds that the image
takes to lo ad. Odds are that the butto n itself will be fro zen in the pressed state as well. This wo uld be pretty
frustrating to a user to say the least!
Alright, so no w that we have co de that's freezing the applicatio n, let's go back and fix it with a pro per
implementatio n o f threading. Make these changes to MainAct ivit y.java:
MainActivity.java
package com.oreillyschool.android1.threads;
import
import
import
import
import
import
import
import
android.app.Activity;
android.graphics.Bitmap;
android.graphics.BitmapFactory;
android.os.AsyncTask;
android.os.Bundle;
android.view.View;
android.widget.ImageView;
android.widget.ProgressBar;
public class MainActivity extends Activity {
private ImageView image;
private ProgressBar progress;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progress = (ProgressBar) findViewById(R.id.progress);
image = (ImageView) findViewById(R.id.image);
}
public void onLoadImageClicked(View view) {
// show the progress and hide the image
image.setVisibility(View.GONE);
progress.setVisibility(View.VISIBLE);
image.setImageBitmap(downloadImage());
// show the image and hide the progress
image.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
return downloadImage();
}
@Override
protected void onPostExecute(Bitmap bitmap) {
image.setImageBitmap(bitmap);
// show the image and hide the progress
image.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
}
}.execute();
}
private Bitmap downloadImage() {
final long start = System.currentTimeMillis();
// wait 5 seconds (5000 milliseconds) until proceeding
while (System.currentTimeMillis() - start < 5000) {
}
return BitmapFactory.decodeResource(getResources(), R.drawable.ic_launch
er);
}
}
AsyncTask is an abstract class, meaning we have to define its implementatio n. Typically, yo u wo uld create a
new class that extends AsyncTask, but that's no t always necessary. Yo u can define an in-line class
implementatio n (also kno wn as an ano nymo us class). This is especially useful when yo ur implementatio n is
go ing to be sho rt and relatively unco mplicated:
OBSERVE:
new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
return downloadImage();
}
@Override
protected void onPostExecute(Bitmap bitmap) {
image.setImageBitmap(bitmap);
// show the image and hide the progress
image.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
}
}.execute();
AsyncTask has three generic class parameters that must be defined in any implementatio n. The first
parameter (Param s) is used to define any parameter input. In o ur example, we didn't need any parameters so
we defined this parameter as Vo id (Null). This generic class parameter co rrespo nds to the class type o f
o bjects passed to the e xe cut e () metho d and received in the do InBackgro und() metho d. The ellipses (...)
at the end o f the type in the do InBackgro und() metho d is called "varargs" (variable-length argument). This
syntax allo ws any number o f arguments o f the class type to be sent to the metho d, which will then be
co mbined into an array in the metho d auto matically. Had o ur image-lo ading metho d been implemented to
do wnlo ad an image fro m the internet co rrectly, then we pro bably wo uld've defined this generic as the St ring
o r the URI class. In that case, o ur implementatio n might have lo o ked so mething lo o ked like this:
OBSERVE:
new AsyncTask<String, Void, Bitmap[]>() {
@Override
protected Bitmap doInBackground(String... params) {
Bitmap[] bitmaps = new Bitmap[params.length];
for (int i=0; i<params.length; i++) {
bitmaps[i] = downloadImage();
}
return bitmaps;
}
@Override
protected void onPostExecute(Bitmap[] bitmaps) {
loadBitmapsIntoImageViews(bitmaps);
}
}.execute(url1, url2, url3);
The seco nd generic parameter fo r AsyncTask is used to track pro gress. We usually use a numeric primitive
fo r this generic, like an Int e ge r o r a Flo at , but yo u can use whatever class yo u like, including a String o r
even yo ur o wn custo m class. (We'll discuss pro gress tracking further a bit later in the lesso n.)
The third parameter is used to define the class type o f the result o f the wo rk do ne in the do InBackgro und()
metho d. This parameter is used as the return type o f do InBackgro und(), as well as the parameter type fo r the
o nPo stExecute() metho d. All o f the generic parameters are technically o ptio nal. If yo u'd rather no t use any in
yo ur applicatio n, yo u can just define each o f them as Null and igno re them in yo ur co de.
do InBackgro und() is the o nly metho d in AsyncTask that we must define in o ur implementatio n (that is, the
o nly abstract metho d). So , yo u might be wo ndering why we return the final result value in do InBackgro und()
and then handle assigning the image to the ImageView in o nPo stExecute(). We do that because in Andro id
yo u canno t interact with View co mpo nents that are attached to the view hierarchy fro m any thread o ther than
the main "UI" thread. The do InBackgro und() metho d runs o n a new thread that was created specifically fo r the
AsyncTask class and so it canno t assign the bitmap to the ImageView co mpo nent. Ho wever, the
o nPo stExecute() metho d is guaranteed to run o n the UI thread, so we can assign the data to the ImageView in
that metho d safely.
T racking Progress
When an applicatio n is perfo rming extensive wo rk, it will o ften repo rt pro gress to the user in the fo rm o f a
pro gress bar. The seco nd generic parameter in AsyncTask helps with the implementatio n o f pro gress
tracking in a thread-safe manner. Again, the do InBackgro und() metho d do es no t run o n the UI thread, so it
canno t be used to update any views. Fo rtunately, there is ano ther helper metho d that runs o n the UI thread
that we can o verride to handle pro gress updates. Make these changes to MainAct ivit y.java:
MainActivity.java
package com.oreillyschool.android1.threads;
import
import
import
import
import
import
import
import
android.app.Activity;
android.graphics.Bitmap;
android.graphics.BitmapFactory;
android.os.AsyncTask;
android.os.Bundle;
android.view.View;
android.widget.ImageView;
android.widget.ProgressBar;
public class MainActivity extends Activity {
private ImageView image;
private ProgressBar progress;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progress = (ProgressBar) findViewById(R.id.progress);
image = (ImageView) findViewById(R.id.image);
}
public void onLoadImageClicked(View view) {
// show the progress and hide the image
image.setVisibility(View.GONE);
progress.setVisibility(View.VISIBLE);
new AsyncTask<Void, Void, Bitmap>() {
new AsyncTask<Void, Integer, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
return downloadImage();
}
private Bitmap downloadImage() {
final long start = System.currentTimeMillis();
// wait 5 seconds (5000 milliseconds) until proceeding
int progress = 0;
int current = 0;
publishProgress(progress);
while ((current = (int)(System.currentTimeMillis() - start)) < 5
000) {
current = (int) ((float)current * 100 / 5000);
if (current > progress) {
progress = current;
publishProgress(current);
}
}
return BitmapFactory.decodeResource(getResources(), R.drawable.i
c_launcher);
}
@Override
protected void onProgressUpdate(Integer... values) {
progress.setProgress(values[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
image.setImageBitmap(bitmap);
// show the image and hide the progress
image.setVisibility(View.VISIBLE);
progress.setVisibility(View.GONE);
}
}.execute();
}
private Bitmap downloadImage() {
final long start = System.currentTimeMillis();
// wait 5 seconds (5000 milliseconds) until proceeding
while (System.currentTimeMillis() - start < 5000) {
}
return BitmapFactory.decodeResource(getResources(), R.drawable.ic_launch
er);
}
}
Here, we mo ved the do wnlo adImage() metho d we defined earlier into the ano nymo us class implementatio n
o f AsyncTask and made a co uple o f changes to it. No w we'll make o ne mino r change to the Pro gressBar in
the XML layo ut in o rder to display the pro gress. Mo dify the Pro gressBar in act ivit y_m ain.xm l as sho wn:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onLoadImageClicked"
android:text="Load Image" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center" >
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:minWidth="200dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="false" />
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="center"
android:visibility="gone" />
</RelativeLayout>
</LinearLayout>
Note
Yo u might be wo ndering abo ut the curio us syntax we just used fo r the style attribute o f the
Pro gressBar. This is a unique way o f referencing a dynamic style value. Do n't wo rry to o much
abo ut it fo r no w, we'll hit this subject again later. Just remember that if yo u need to sho w a
ho rizo ntal pro gress bar, use the style value ?andro id:at t r/pro gre ssBarSt yle Ho rizo nt al.
Make sure all yo ur changes are saved, and run the pro ject in the emulato r. No w when yo u lo ad the image yo u
sho uld see the pro gress bar crawl its way acro ss the screen during the five seco nds the do wnlo ad metho d
takes to finish:
We mo ved the do wnlo ad lo gic inside o f the ano nymo us class so that we co uld make calls to the
publishPro gre ss() metho d. The Integer value we passed to publishPro gre ss() gets sent to the
o nPro gre ssUpdat e () metho d that we have implemented as well no w. o nPro gre ssUpdat e () runs o n the
UI thread, so we can update the pro gress o f the Pro gressBar view co mpo nent here safely. The Int e ge r
generic value is also defined with varargs, so it is put into an array auto matically. We o nly send o ne value at a
time to publishPro gre ss(), so we can grab the first item o ut o f the array (value s[0 ]) safely each time.
We've also added so me co de to o ur do wnlo ad metho d to make sure we do n't call publishPro gre ss() o n
every single iteratio n o f the while lo o p. Calling publishPro gre ss() each time is unnecessary (since the
pro gress wo n't always increment eno ugh to even be no ticeable o n the pro gress bar), and co uld cause the
applicatio n to slo w do wn if we o verwhelm the UI thread with pro gress updates. That wo uld defeat o ur
purpo se and make the applicatio n lo o k bro ken to users, so we've added a small check to make sure the
pro gress has increased by at least a facto r o f 1%.
Ano ther metho d that can be useful when yo u're implementing an AsyncTask class is the o nPreExecute()
metho d. It's guaranteed to run o n the UI thread just like publishPro gress() and o nPo stExecute(). This metho d
is a great place to implement any setup fo r a pro gress bar o r sho w a no tificatio n to the user that a backgro und
actio n is abo ut to begin.
Wrapping Up
This lesso n was relatively brief, but it may be the mo st impo rtant lesso n yet. If yo u kno w ho w to write an Andro id
applicatio n that implements threading pro perly, it can make the difference between getting featured o n the Andro id
Market and getting a slew o f lo wly o ne-star reviews. I'm co nfident that yo u can wo rk with the AsyncTask and avo id the
dreaded ANR dialo g no w. Go o d wo rk!
See yo u in the next lesso n!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Styles and Themes
Welco me back! In this lesso n we'll go o ver the vario us ways to style Andro id views and co mpo nents. Styling is a significant
po rtio n o f applicatio n develo pment and is way to o dense to be co vered in just o ne lesso n. I'm go ing to fo cus o n the basics o f
styling that will apply to mo st co mpo nents. By the end o f the lesso n, yo u'll feel co mfo rtable mo difying the default Andro id style
fo r applicatio ns.
Introduction to Styling
Like mo st elements in Andro id, there are a lo t o f different ways to go abo ut implementing styles fo r yo ur Andro id
co mpo nents. We'll co ver a few o f the mo re co mmo n o nes. Create a new pro ject named St yling, with the package
name co m .o re illyscho o l.andro id1.st yling, and assign the pro ject to the Andro id1_Le sso ns wo rking set.
Defining Styles
Perhaps the easiest way to define styles o n a co mpo nent is directly o n the view co mpo nent XML definitio n.
We've already do ne a little bit o f this in previo us lesso ns, and yo u might have no ticed the o ptio ns yo urself if
yo u used co de assist in Eclipse to write yo ur XML. Changing a style directly o n a co mpo nent is an efficient
way to update a single co mpo nent, but when yo u want to style an entire applicatio n, that can get tedio us. To
manage the style o f an entire applicatio n, yo u'll want to use the styles and themes reso urces.
Note
We use a particular co nventio n to name all o f o ur XML files in the /re s/value s fo lder. Yo u can
use whatever file names yo u like; just make sure the ro o t XML tag is <re so urce s>.
Let's add a styles XML reso urce file to o ur pro ject and define so me initial values. Select File | Ne w | Ot he r |
Andro id XML Value s File to create a file named st yle s as sho wn:
Answer Ye s when pro mpted to o verwrite the existing file. When yo u finish, o pen the st yle s.xm l file in the
/re s/value s fo lder, switch to the manual edit (styles.xml) sub-tab and make these changes:
/res/values/styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyButtonStyle">
<item name="android:background">#aa0000</item>
<item name="android:textColor">#000000</item>
<item name="android:drawableLeft">@drawable/ic_launcher</item>
</style>
</resources>
This is a basic style definitio n. Every style definitio n needs just o ne attribute, a nam e , which is used to
reference the style using the @ st yle syntax. All children o f the st yle tag must be it e m tags, with nam e
attributes o f their o wn. The it e m tag's nam e attribute must reference a style pro perty o f the View co mpo nent
that this style will mo dify. There is no co mpo nent type-checking handled here, so be careful that the styles yo u
define are actually applicable to the co mpo nent yo u are styling, o therwise yo u might be unpleasantly
surprised when yo u change styles later and no thing changes in the view!
The andro id:backgro und style is co mmo n to all view co mpo nents in Andro id. It can accept bo th drawable
reso urces and co lo rs. In fact, just abo ut any style that accepts a drawable can accept a co lo r definitio n
instead. Ho wever, the inverse is no t true. Fo r example, the andro id:t e xt Co lo r attribute must be a co lo r
definitio n and no t a drawable reference.
Next, we'll use this style in a layo ut view. Open act ivit y_m ain.xm l and make these changes:
/res/layo ut/activity_main.xml
<RelativeLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="My Styled Button"
style="@style/MyButtonStyle" />
</RelativeLinearLayout>
Note
Yo u sho uld be able to test the styles o f mo st co de yo u change in this lesso n just by lo o king at
the "Graphical Layo ut" sub-tab o f activity_main.xml in the XML edito r. So metimes the "Graphical
Layo ut" is unable to generate the view pro perly and yo u'll need to run the applicatio n in the
emulato r, but fo r mo st mino r changes yo u make, yo u wo n't need to wait fo r the emulato r to test
them.
The style attribute is a unique attribute fo r view co mpo nents in XML layo uts because it do esn't use the
"andro id" namespace (no tice it's called "style" instead o f "andro id:style"). While mo st layo ut XML attributes
co rrespo nd to a pro perty o n the respective view class, style is no t a pro perty o f any view. This is impo rtant; it
means that in o rder to change the styles o f a view co mpo nent at runtime yo u will have to change each
individual style. There's no way to update the XML-defined style pro perty at runtime. Also , no te that we use the
@ st yle /<st yle nam e > syntax to reference the style we defined in styles.xml.
Using the "Graphical Layo ut" mo de o f the edito r, o r running the applicatio n in the emulato r, we can no w test
to make sure that o ur styles defined in st yle s.xm l do indeed get assigned to the butto n in the view.
Defining T hemes
Using the style attribute o n a XML view makes it co nvenient fo r reusing styles that yo u wo uld po tentially apply
to multiple views. But what if yo u just want to apply the style to every co mpo nent in yo ur applicatio n
generically? This is where t he m e s co me into play.
generically? This is where t he m e s co me into play.
Themes are defined exactly like styles—they even use the same XML no de name o f <st yle >. The difference
is in ho w yo u use them. We'll get to that, but first let's create a new file to co ntain o ur themes fo r the
applicatio n. This is a standard co nventio n used to o rganize the definitio ns and make it easier to find each
reso urce later; if yo u really wanted to , yo u co uld define all yo ur styles and themes in the same file.
Okay let's get to wo rk. Remo ve the style tag fro m Butto n in act ivit y_m ain.xm l:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="My Styled Button"
style="@style/MyButtonStyle" />
</LinearLayout>
In the "Graphical Layo ut," verify that the style has been remo ved and o ur butto n is back to lo o king like a
regular butto n. Next, create ano ther new Andro id Values XML file named t he m e s.xm l, and then make these
changes:
/res/values/theme.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyTheme" parent="@android:style/Theme">
<item name="android:buttonStyle">@style/MyButtonStyle</item>
</style>
</resources>
No w let's use the theme. As I mentio ned befo re, the difference between themes and styles isn't in the way yo u
define them, but ho w yo u use them. To use a theme, yo u define it fo r either an activity o r the entire applicatio n.
This is all do ne in the Andro idManif e st .xm l file.
Andro idManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.oreillyschool.android1.styling"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10"
android:targetSdkVersion="10" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/MyTheme" >
<activity
android:name="com.oreillyschool.android1.styling.MainActivity"
android:label="@string/app_name" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Here, we defined o ur theme o n the <applicat io n> tag. This will ensure that every activity we create in this
applicatio n will have its co re styles defined by the theme reso urce MyT he m e . (If yo u wanted to have a
specific alternate theme fo r just o ne Activity, yo u co uld also add an andro id:t he m e attribute to the
<act ivit y> tag. Then any styles defined in the applicatio n theme wo uld be o verridden by the styles fro m the
activity's theme.)
The "Graphical Layo ut" viewer fo r XML layo uts o ccasio nally has a hard time lo ading themes co rrectly. Yo u'll
need to use the theme dro pdo wn lo cated in the to p right to select yo ur new theme MyT he m e :
If MyT he m e do esn't sho w up initially, try changing so mething else (like the Andro id SDK versio n abo ve the
theme dro pdo wn) to get it to refresh, o r clo se and reo pen the edito r. If all else fails, yo u can just run the
applicatio n to test it o n the emulato r:
Let's lo o k at the theme we defined in t he m e s.xm l in mo re detail:
OBSERVE:
<resources>
<style name="MyTheme" parent="@android:style/Theme">
<item name="android:buttonStyle">@style/MyButtonStyle</item>
</style>
</resources>
The o nly item we've added to this theme/style is andro id:but t o nSt yle , which references the style we
defined in styles.xml named MyBut t o nSt yle . This item wo rks o nly in a style that is used as a theme.
andro id:but t o nSt yle defines the style that is used o n butto ns. There's a list o f all items yo u can define in a
theme (and descriptio ns o f what they will do ) available o n the Andro id develo per do cumentatio n site.
Here are so me o f the mo re co mmo n items (with the acceptable value types in parentheses) that yo u might
co nsider o verriding when designing themes fo r yo ur o wn applicatio ns:
andro id:windo wBackgro und (drawable o r co lo r)
andro id:windo wNo Title (bo o lean)
andro id:butto nStyle (style)
andro id:tabWidgetStyle (style)
andro id:checkBo xStyle (style)
andro id:listViewStyle (style)
andro id:listDivider (style)
andro id:listPreferredItemHeight (dimensio n)
andro id:dialo gTheme (style/theme)
andro id:textAppearance (style)
andro id:textAppearanceButto n (style)
andro id:textCo lo rPrimary (drawable o r co lo r)
andro id:textCo lo rPrimaryInverse (drawable o r co lo r)
andro id:textCo lo rSeco ndary (drawable o r co lo r)
andro id:textCo lo rSeco ndaryInverse (drawable o r co lo r)
Ano ther difference between styles and themes is that a theme will cascade, but a style will no t. If yo u're
familiar with CSS files in web develo pment, yo u're familiar with the co ncept o f cascading styles. In Andro id, if
yo u define a style fo r a Line arLayo ut , such as a backgro und image o r co lo r, that style will no t cascade to its
children. That means yo u do n't have to wo rry that all the sub-views will get assigned the same backgro und o r
co lo r. Ho wever, if yo u define a style item such as andro id:backgro und in a theme, and then use that theme
o n an Activity, every view in that activity's view hierarchy will inherit that same backgro und (if they do n't
manually o verride with a different backgro und). Be careful abo ut what yo u define in themes. As a general rule,
yo u sho uld never define andro id:backgro und in a theme. Go ahead and define andro id:backgro und in
o ur current applicatio n giving it the @ drawable /ic_launche r as the value and run the applicatio n. Beho ld the
disastro us results!:
When yo u want to define a glo bal backgro und that sho ws up as the backgro und o f every Activity o f yo ur
applicatio n, but no t every view co mpo nent, use the andro id:windo wBackgro und pro perty instead. That way
yo u can be certain that yo ur selected backgro und sho ws up o nly as the backgro und to yo ur activity windo w,
and wo n't cascade to any view co mpo nents in the view hierarchy.
Style Inheritance
The last co ncept I want to discuss regarding themes.xml is inheritance:
/res/values/themes.xml
<resources>
<style name="MyTheme" parent="@android:style/Theme">
<item name="android:buttonStyle">@style/MyButtonStyle</item>
</style>
</resources>
Styles and themes can inherit items fro m o ther styles in two different ways. The first way is demo nstrated in
o ur theme with the pare nt attribute. Using the pare nt attribute o n a style, we can inherit all o f the elements o f
a style defined in the Andro id SDK package. This is reco mmended, especially fo r themes, so that yo u receive
all the default styles that yo u are used to seeing, and then yo u can select which individual items to o verride.
The fo llo wing are so me o f the mo re po pular Andro id SDK themes yo u can use to parent yo ur o wn themes:
@andro id:style/Theme
@andro id:style/Theme.Black
@andro id:style/Theme.Black.No TitleBar
@andro id:style/Theme.Black.No TitleBar.Fullscreen
@andro id:style/Theme.Light
@andro id:style/Theme.Light.No TitleBar
@andro id:style/Theme.Light.No TitleBar.Fullscreen
The seco nd metho d o f inheriting fro m ano ther style is demo nstrated in the list abo ve. Yo u can prefix the
name o f yo ur style with ano ther style name and a perio d. Yo u can use this metho d to create alternates o f a
style. Fo r example, to create a sub-style o f o ur earlier MyBut t o nSt yle yo u co uld name it
MyBut t o nSt yle .Large , and then have ano ther style inheriting fro m that o ne named
MyBut t o nSt yle .Large .Re d, and so o n.
Note
Prefix inheritance o nly wo rks fo r o ther styles that yo u have defined in yo ur applicatio n. In o rder
to inherit fro m Andro id SDK styles, yo u must use the pare nt attribute.
Direct T heme References
While themes will style the default co mpo nents with the styleable items available to the Theme class
auto matically, o n o ccasio n yo u might want to pull a value directly fro m a theme to be assigned to a different
View. We used this syntax in ano ther lesso n when we defined the style o f the Pro gre ssBar co mpo nent. The
syntax fo r referencing a style item fro m a theme is written either, " ?at t r/t he m e At t ribut e " , o r with the
Andro id SDK namespace, " ?andro id:at t r/t he m e At t ribut e " .
Let's practice using o ur butto n co de. First, remo ve the theme reference that o verrides the default butto n style
(and the o ne we added to create the ugly backgro und) in t he m e s.xm l:
/res/values/themes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyTheme" parent="@android:style/Theme">
<item name="android:buttonStyle">@style/MyButtonStyle</item>
<item name="android:background">@drawable/ic_launcher</item>
</style>
</resources>
Next, make the fo llo wing changes to act ivit y_m ain.xm l:
/res/layo ut/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="My StyledPrimary Button" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:text="My Secondary Button" />
</LinearLayout>
Save and run it in the emulato r o r use the "Graphical Layo ut" view (tho ugh o ccasio nally the "Graphical
Layo ut" view will struggle when using these reso urces). Yo ur view will lo o k so mething like this:
Here we made a seco nd butto n and to ld it to lo ad the t e xt Co lo rPrim ary theme value fo r its text co lo r. The
textCo lo r o n the Butto n co mpo nent uses a dark black o r near black co lo r by default; we can see that in the first
butto n. When we define the style as ?andro id:at t r/t e xt Co lo rPrim ary, the co lo r gets lo aded as whatever is
assigned to the t e xt Co lo rPrim ary item in the current theme. We can even o verride t e xt Co lo rPrim ary in
o ur theme. Change t he m e s.xm l as sho wn:
/res/values/themes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyTheme" parent="@android:style/Theme">
<item name="android:textColorPrimary">#00ff00</item>
</style>
</resources>
Here we gave the t e xt Co lo rPrim ary item a hexidecimal co lo r value o f "#0 0 ff0 0 ", which is a hideo us bright
green co lo r. No w when we test the act ivit y_m ain.xm l view in the "Graphical Layo ut" o r in the emulato r, the
seco nd butto n's text is that co lo r:
While o verriding pro perties in this manner can be co nvenient fo r changing a style pro perty in yo ur applicatio n
glo bally, do no t rely o n this technique. Changing a system pro perty can have dramatic effects o n yo ur design
and unintended co nsequences in weird places!
Learning to Learn
The mo st impo rtant to o l yo u can have when it co mes to styling may be the ability to lo o k up what can and canno t be
styled. The ADT plugin fo r Eclipse has co me a lo ng way and includes co de hints fo r mo st XML pro perties. This may
help yo u disco ver new attributes, but it can o nly get yo u so far. The best reso urce fo r learning abo ut pro perties that can
be used in styles fo r a View is o n the class reference page fo r the view o n the Andro id develo per do cumentatio n site.
Fo r example, here is the XML attributes sectio n fo r the TextView co mpo nent.
Also , check the parent co mpo nents o f a view to learn abo ut o ther attributes available to co mpo nents. Take a lo o k at
the (reference page fo r the Butto n co mpo nent), fo r example. Even tho ugh there are many specific XML attributes
available to Butto n, the reference page do esn't sho w any. That's because it do esn't have any specific unique styles
available. All o f Butto n's styles are inherited fro m its parent co mpo nents (TextView and its parent, View).
Ano ther mo re co ncise list can be fo und o n the (R.st ylable reso urce page). This page actually lists the pro perties
available to every standard co mpo nent in the Andro id SDK, and is definitely wo rth putting in yo ur bo o kmarks.
Wrapping Up
Having a go o d design fo r yo ur Andro id applicatio n is crucial fo r its success o n the Andro id market. As the platfo rm has
matured, users have co me to expect a high-quality lo o k and feel in their applicatio ns. The Andro id styles and design
standards seem to change with each new release o f the Andro id SDK, but the co re metho ds to styling remain the
same. The skills yo u have no w will help yo u keep current with the latest styling thechniques thro ugh each update to the
Andro id SDK.
Yo u're almo st do ne! Great wo rk so far. In the next lesso n, yo u'll be co mpleting yo ur final pro ject fo r the co urse. Yo u'll
have a chance to sho w yo ur stuff there—I'm lo o king fo rward to seeing what yo u can do !
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.
Android Final Project
Final Project
Co ngratulatio ns o n co mpleting the lesso ns! Fo r yo ur final pro ject yo u will create yo ur very o wn Andro id applicatio n
(surprised?). The type o f applicatio n yo u create is entirely up to yo u, but just in case, here are so me ideas yo u can use
fo r yo ur pro ject:
A basic no te-taking applicatio n—suppo rts creating multiple no tes, editing no tes, and deleting no tes.
A "To Do " list applicatio n—slightly mo re invo lved than the no te taking applicatio n, the "To Do " applicatio n
suppo rts creating, editing, and deleting multiple "To Do " lists. Each list can be mo dified to add, edit, o r
remo ve "To Do " items. The items can also be "checked" o ff when co mpleted and the time the items were
co mpleted is reco rded (and displayed in the view).
Hangman!—the Hangman applicatio n will implement the game Hangman using the co mmo n view
co mpo nents (no need fo r intense graphics here). The wo rd used in the game is rando mly cho sen fro m a
st ring-array XML reso urce. Users guess letters to spell o ut the wo rd, and lo se po ints fo r each inco rrect
guess. Use a simple po ints system, co unting do wn fro m the appro priate number (usually 6 , fo r a head,
to rso , two arms, and two legs), to keep track o f remaining guesses and/o r get creative with the view
co mpo nents o r yo ur o wn graphics which utimatelt lead to the drawing o f a hanged stick figure.
Any type o f internet data presentatio n applicatio n using a freely available public API (such as imgur, reddit, o r
yaho o weather). There are many o ther publicly available APIs (such as Flickr, all Go o gle APIs, and Twitter),
but they usually require yo u to sign up fo r an API key (feel free to do that if yo u like). This type o f Applicatio n
will need to implement a data interpreter such as an XML o r JSON parser. If yo u are unfamiliar with using
libraries fo r these interpreters, then yo u might no t want to tackle this type o f applicatio n right o ff the bat. If yo u
do cho o se this type o f applicatio n, make sure yo u adhere to the po licies fo r the API and give pro per
attributio n to the so urce o f the data.
Whichever yo u cho o se, yo u applicatio n must meet these requirements:
Functio ns o n Andro id devices.
Implements at least three Activities, each with a unique view layo ut.
At least two Activities share data between each o ther using the pro per Intent passing metho ds.
Implements at least o ne ListView, with its o wn custo m adapter and custo m view layo ut fo r the list items.
Implements at least o ne Dialo g using the ne w pro cess with the suppo rt library.
Implements a SharedPreferences o bject (implementing a PreferenceActivity is o ptio nal, but can co unt
to wards o ne o f yo ur three Activities).
Implements a SQLite database fo r caching data between applicatio n sessio ns.
All SQLiteDatabase usage (such as query, insert, and delete) sho uld be used inside o f an AsyncTask.
All internet usage (if implemented) is used inside o f an AsyncTask.
All hard-co ded strings are lo aded fro m string XML reso urces.
Use themes and styles via XML reso urces fo r all appro priate styling pro perly.
Make an applicatio n that yo u are pro ud to have created! Keep yo ur co de clean, o rganized, and bug-free! Yo u might
even co nsider publishing yo ur wo rk o n the Andro id Market when yo u are finished. Thanks fo r taking the co urse and
go o d luck!
Copyright © 1998-2014 O'Reilly Media, Inc.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.