Get Started Developing For Android With Eclipse, Reloaded
2011-05-16 22:48
477 查看
Inthefirstpart
ofthistutorialseries,webuiltasimplebrewtimerapplicationusing
AndroidandEclipse.Inthissecondpart,we’llcontinuedevelopingthe
applicationbyaddingextrafunctionality.Indoingthis,you’llbe
introducedtosomeimportantandpowerfulfeaturesoftheAndroidSDK,
includingPersistentdatastorage,ActivitiesandIntentaswellas
Shareduserpreferences.
Tofollowthistutorial,you’llneedthe
codefromthepreviousarticle.Ifyouwanttogetstartedrightaway,
grabthecodefromGitHub
andcheckoutthetutorial_part_1
tagusingthis:
Onceyou’vecheckedoutthecodeonGitHub,you’llneedtoimporttheprojectintoEclipse:
LaunchEclipseandchooseFile→Import…
IntheImportwindow,select“ExistingProjectsintoWorkspace”andclick“Next.”
Onthenextscreen,click“Browse,”andselecttheprojectfolderthatyouclonedfromGitHub.
Click“Finish”toimportyourprojectintoEclipse.
AfterimportingtheprojectintoEclipse,youmightreceiveawarningmessage:
If
thisisthecase,right-clickonthenewlyimported“BrewClock”project
inthe“ProjectExplorer,”choose“FixProjectProperties,”andthen
restartEclipse.
BrewClockletsuserssetaspecifictimeforbrewingtheirfavorite
cupsoftea.Thisisgreat,butwhatiftheyregularlydrinkavariety
ofteas,eachwiththeirowndifferentbrewingtimes?Atthemoment,
usershavetorememberbrewingtimesforalltheirfavoriteteas!This
doesn’tmakeforagreatuserexperience.So,inthistutorialwe’ll
developfunctionalitytoletusersstorebrewingtimesfortheir
favoriteteasandthenchoosefromthatlistofteaswhentheymakea
brew.
Todothis,we’lltakeadvantageofAndroid’srich
data-storageAPI.Androidoffersseveralwaystostoredata,twoof
whichwe’llcoverinthisarticle.Thefirst,morepowerfuloption,uses
theSQLitedatabaseenginetostoredataforourapplication.
SQLite
isapopularandlightweightSQLdatabaseenginethatsavesdataina
singlefile.Itisoftenusedindesktopandembeddedapplications,
whererunningaclient-serverSQLengine(suchasMySQLorPostgreSQL)
isn’tfeasible.
EveryapplicationinstalledonanAndroiddevice
cansaveanduseanynumberofSQLitedatabasefiles(subjecttostorage
capacity),whichthesystemwillmanageautomatically.Anapplication’s
databasesareprivateandsocannotbeaccessedbyanyother
applications.(Datacanbesharedthroughthe
class,butwewon’tcovercontentprovidersinthistutorial.)Database
filespersistwhentheapplicationisupgradedandaredeletedwhenthe
applicationisuninstalled.
We’lluseasimpleSQLitedatabasein
BrewClocktomaintainalistofteasandtheirappropriatebrewing
times.Here’sanoverviewofhowourdatabaseschemawilllook:
Ifyou’veworkedwithSQLbefore,thisshouldlookfairlyfamiliar.Thedatabasetablehasthreecolumns:auniqueidentifier(
),
nameandbrewingtime.We’llusetheAPIsprovidedbyAndroidtocreate
thedatabasetableinourcode.Thesystemwilltakecareofcreating
thedatabasefileintherightlocationforourapplication.
ensurethedatabasecodeiseasytomaintain,we’llabstractallthe
codeforhandlingdatabasecreation,insertsandqueriesintoaseparate
class,
.Thisshouldbefairlyfamiliarifyou’re
usedtothemodel-view-controllerapproach.Allthedatabasecodeis
keptinaseparateclassfromour
.TheActivitycanthenjustinstantiateanew
instance(whichwillconnecttothedatabase)anddowhatitneedsto
do.Workinginthiswayenablesustoeasilychangethedatabaseinone
placewithouthavingtochangeanythinginanyotherpartsofour
applicationthatdealwiththedatabase.
Createanewclasscalled
intheBrewClockprojectbygoingtoFile→New→Class.Ensurethat
extendsthe
classandthatyouchecktheboxfor“Constructorsfromsuperclass.”
The
classwillautomaticallyhandlethecreationandversioningofaSQLite
databaseforyourapplication.We’llalsoaddmethodstogiveother
partsofourcodeaninterfacetothedatabase.
Addtwoconstantsto
tostorethenameandversionofthedatabase,thetable’snameandthe
namesofcolumnsinthattable.We’llusetheAndroid-providedconstant
forthetable’suniqueidcolumn:
Addaconstructorto
thatcallsitsparentmethod,supplyingourdatabasenameandversion.
Androidwillautomaticallyhandleopeningthedatabase(andcreatingit
ifitdoesnotexist).
We’llneedtooverridethe
methodtoexecuteastringofSQLcommandsthatcreatethedatabase
tableforourtea.Androidwillhandlethismethodforus,calling
whenthedatabasefileisfirstcreated.
Onsubsequentlaunches,Androidcheckstheversionofthedatabaseagainstthe
numberwesuppliedtotheconstructor.Iftheversionhaschanged,Androidwillcallthe
method,whichiswhereyouwouldwriteanycodetomodifythedatabase
structure.Inthistutorial,we’lljustaskAndroidtodropandrecreate
thedatabase.
So,addthefollowingcodeto
:
Next,we’lladdanewmethodto
thatletsuseasilyaddnewtearecordstothedatabase.We’llsupply
themethodwithanameandbrewingtimefortheteatobeadded.Rather
thanforcingustowriteouttherawSQLtodothis,Androidsuppliesa
setofclassesforinsertingrecordsintothedatabase.First,wecreate
asetof
,pushingtherelevantvaluesintothatset.
Withaninstanceof
,
wesimplysupplythecolumnnameandthevaluetoinsert.Androidtakes
careofcreatingandrunningtheappropriateSQL.UsingAndroid’s
databaseclassesensuresthatthewritesaresafe,andifthedata
storagemechanismchangesinafutureAndroidrelease,ourcodewill
stillwork.
Addanewmethod,
,tothe
class:
interfacefordoingjustthis.A
representstheresultsofrunningaSQLqueryagainstthedatabase,and
itmaintainsapointertoonerowwithinthatresultset.Thispointer
canbemovedforwardsandbackwardsthroughtheresults,returningthe
valuesfromeachcolumn.Itcanhelptovisualizethis:
In
thisexample,theCursorispointingatthesecondrowintheresult
set(Greentea).WecouldmovetheCursorbackarowtorepresentthe
firstrow(EarlGrey)bycalling
,ormoveforwardtotheAssamrowwith
.TofetchthenameoftheteathattheCursorispointingout,wewouldcall
,where
isthecolumnindexofthecolumnwewishtoretrieve(notethatthe
indexiszero-based,socolumn0isthefirstcolumn,1thesecond
columnandsoon).
NowthatyouknowaboutCursors,addamethodthatcreatesa
objectthatreturnsalltheteasinourdatabase.Addan
methodto
:
Let’s
gooverthismethodindetail,becauseitlooksalittlestrangeat
first.Again,ratherthanwritingrawSQLtoquerythedatabase,wemake
useofAndroid’sdatabaseinterfacemethods.
First,weneedto
tellAndroidwhichcolumnsfromourdatabasewe’reinterestedin.Todo
this,wecreateanarrayofstrings—eachoneofthecolumnidentifiers
thatwedefinedatthetopof
.We’llalsosetthecolumnthatwewanttoordertheresultsbyandstoreitinthe
string.
Next,wecreatearead-onlyconnectiontothedatabaseusing
,andwiththatconnection,wetellAndroidtorunaqueryusingthe
method.The
methodtakesasetofparametersthatAndroidinternallyconvertsintoa
SQLquery.Again,Android’sabstractionlayerensuresthatour
applicationcodewilllikelycontinuetowork,eveniftheunderlying
datastoragechangesinafutureversionofAndroid.
Becausewejustwanttoreturneveryteainthedatabase,wedon’tapplyanyjoins,filtersorgroups(i.e.
,
,and
clausesinSQL)tothemethod.The
and
variablestellthequerywhatcolumnstoreturnonthedatabaseandtheorderinwhichtheyareretrieved.Weusethe
methodasaninterfacetothedatabase.
Last,weaskthesuppliedActivity(inthiscase,our
)
tomanagetheCursor.Usually,aCursormustbemanuallyrefreshedto
reloadanynewdata,soifweaddedanewteatoourdatabase,wewould
havetoremembertorefreshourCursor.Instead,Androidcantakecare
ofthisforus,recreatingtheresultswhenevertheActivityis
suspendedandresumed,bycalling
.
Finally,
we’lladdanotherutilitymethodtoreturnthenumberofrecordsinthe
table.Onceagain,Androidprovidesahandyutilitytodothisforus
inthe
class:
Addthefollowingmethod,
,toyour
class:
Savethe
class,andfixanymissingimportsusingEclipse(Source→Organize
Imports).Withourdataclassfinished,it’stimetochangeBrewClock’s
interfacetomakeuseofthedatabase!
purposeofstoringpresetteasandbrewtimesistolettheuser
quicklyselecttheirfavoriteteafromthepresets.Tofacilitatethis,
we’lladda
(analogoustoapop-upmenuindesktopinterfaces)tothemainBrewClockinterface,populatedwiththelistofteasfrom
.
As
intheprevioustutorial,useEclipse’slayouteditortoaddthe
SpinnertoBrewClock’smaininterfacelayoutXMLfile.Addthefollowing
codejustbelowthe
forthebrewcountlabel
(aroundline24).Remember,youcanswitchtothe“CodeView”tabalong
thebottomofthewindowifEclipseopensthevisuallayouteditor.
Inthe
class,addamembervariabletoreferencetheSpinner,andconnectittotheinterfaceusing
:
Try
runningyourapplicationtomakesurethenewinterfaceworks
correctly.Youshouldseeablankpop-upmenu(orSpinner)justbelow
thebrewcount.Ifyoutapthespinner,Androidhandlesdisplayinga
pop-upmenusothatyoucanchooseanoptionforthespinner.Atthe
moment,themenuisempty,sowe’llremedythatbybindingtheSpinner
toourteadatabase.
object.TheCursorrepresentsasetofresultsfromthedatabaseand
canbemovedthroughtheresultstoretrievevalues.Wecaneasilybind
theseresultstoaview(inthiscase,theSpinner)usingasetof
classesprovidedbyAndroidcalled“Adapters.”Adaptersdoallthehard
workoffetchingdatabaseresultsfroma
anddisplayingthemintheinterface.
Rememberthatour
methodalreadyreturnsaCursorpopulatedwiththecontentsofourteas
table.UsingthatCursor,allweneedtodoiscreatea
tobinditsdatatoour
,andAndroidwilltakecareofpopulatingthespinner’soptions.
ConnecttheCursorreturnedby
totheSpinnerbycreatinga
:
Noticethatwe’vemadeuseofAndroid’sbuilt-in
object.Thisprovidessomegenericdefaultresourcesforyour
application,suchassimpleviewsandlayouts.Inthiscase,we’veused
,whichisasimpletextlabellayout.
If
youruntheapplicationagain,you’llseethatthespinnerisstill
empty!Eventhoughwe’veconnectedthespinnertoourdatabase,there
arenorecordsinthedatabasetodisplay.
Let’sgivetheusera
choiceofteasbyaddingsomedefaultrecordstothedatabasein
BrewClock’sconstructor.Toavoidduplicateentries,we’lladdonlythe
defaultteasifthedatabaseisempty.WecanmakeuseofTeaData’s
methodtocheckifthisisthecase.
Addcodetocreateadefaultsetofteasifthedatabaseisempty.Addthislinejustabovethecodetofetchtheteasfrom
:
Now
runtheapplicationagain.You’llnowseethatyourteaSpinnerhasthe
firstteaselected.TappingontheSpinnerletsyouselectoneofthe
teasfromyourdatabase!
Congratulations!
You’vesuccessfullyconnectedyourinterfacetoadatasource.Thisis
oneofthemostimportantaspectsofanysoftwareapplication.Asyou’ve
seen,Androidmakesthistaskfairlyeasy,butitisextremely
powerful.Usingcursorsandadapters,youcantakevirtuallyanydata
source(fromasimplearrayofstringstoacomplexrelationaldatabase
query)andbindittoanytypeofview:aspinner,alistvieworeven
aniTunes-likecover-flowgallery!
Althoughnowwouldbeagood
timeforabrew,ourworkisn’toveryet.Whileyoucanchoosedifferent
teasfromtheSpinner,makingaselectiondoesn’tdoanything.Weneed
tofindoutwhichteatheuserhasselectedandupdatethebrewtime
accordingly.
needstolistenforanevent.Similartothe
eventthatistriggeredbybuttonpresses,we’llimplementthe
.Eventsinthislisteneraretriggeredwhentheusermakesaselectionfromaview,suchasourSpinner.
Enablethe
in
byaddingittotheclassdeclaration.Remembertoimplementtheinterfacemethods
and
:
Herewecheckwhetherthespinnerthattriggeredthe
eventwasBrewClock’s
.Ifso,weretrieveaCursorobjectthatrepresentstheselectedrecord.Thisisallhandledforusbythe
thatconnects
totheSpinner.AndroidknowswhichquerypopulatestheSpinnerand
whichitemtheuserhasselected.Itusesthesetoreturnthesinglerow
fromthedatabase,representingtheuser’sselectedtea.
Cursor’s
methodtakestheindexofthecolumnwewanttoretrieve.RememberthatwhenwebuiltourCursorin
,thecolumnswereadwere
,
and
.AssumingwechoseJasmineteain
,theCursorreturnedbyourselectionwouldbepointingatthatrecordinthedatabase.
WethenasktheCursortoretrievethevaluefromcolumn2(using
),whichinthisqueryisour
column.Thisvalueissuppliedtoourexisting
method,whichupdatestheinterfacetoshowtheselectedtea’sbrewingtime.
Finally,weneedtotellthe
that
islisteningfor
events.Addthefollowinglineto
’s
method:
That
shoulddoit!Runyourapplicationagain,andtryselectingdifferent
teasfromtheSpinner.Eachtimeyouselectatea,itsbrewtimewillbe
shownonthecountdownclock.Therestofourcodealreadyhandles
countingdownfromthecurrentbrewtime,sowenowhaveafullyworking
brewtimer,withalistofpresetteas.
Youcan,ofcourse,go
backintothecodeandaddmorepresetteastothedatabasetosuityour
tastes.ButwhatifwereleasedBrewClocktothemarket?Everytime
someonewantedtoaddanewteatothedatabase,we’dneedtomanually
updatethedatabase,andrepublishtheapplication;everyonewouldneed
toupdate,andeverybodywouldhavethesamelistofteas.Thatsounds
prettyinflexible,andalotofworkforus!
Itwouldbemuchbetteriftheuserhadsomewaytoaddtheirownteasandpreferencestothedatabase.We’lltacklethatnext…
.
Everytimeyougofromonescreentoanother,Androidcreatesanew
Activity.Inreality,althoughanapplicationmaycompriseanynumberof
screens/activities,Androidtreatsthemasseparateentities.
ActivitiesworktogethertoformacohesiveexperiencebecauseAndroid
letsyoueasilypassdatabetweenthem.
Inthisfinalsection,you’lladdanewActivity(
)toyourapplicationandregisteritwiththeAndroidsystem.You’llthenpassdatafromtheoriginal
tothisnewActivity.
First,though,weneedtogivetheuserawaytoswitchtothenewActivity.We’lldothisusinganoptionsmenu.
menusarethepop-upmenusthatappearwhentheuserhitsthe“Menu”
keyontheirdevice.Androidhandlesthecreationanddisplayofoptions
menusautomatically;youjustneedtotellitwhatoptionstodisplay
andwhattodowhenanoptionischosenbytheuser.
However,
ratherthanhard-codingourlabelsintothemenuitself,we’llmakeuse
ofAndroidstringresources.Stringresourcesletyoumaintainallthe
human-readablestringsandlabelsforyourapplicationinonefile,
callingthemwithinyourcode.Thismeansthere’sonlyoneplaceinyour
codewhereyouneedtochangestringsinthefuture.
Intheprojectexplorer,navigateto“res/values”andyouwillseethatastrings.xml
filealreadyexists.ThiswascreatedbyEclipsewhenwefirstcreated
theproject,anditisusedtostoreanystringsoftextthatwewantto
usethroughouttheapplication.
Openstrings.xml
bydoubleclickingonit,andswitchtotheXMLviewbyclickingthestrings.xml
tabalongthebottomofthewindow.
Addthefollowinglinewithinthe
element:
Hereyou’vedefinedastring,
,anditsassociatedtext.Wecanuse
toreferencethestringthroughouttheapplication’scode.Ifthelabel
needstochangeforsomereasoninthefuture,you’llneedtochangeit
onlyonceinthisfile.
Next,let’screateanewfiletodefine
ouroptionsmenu.Justlikestringsandlayouts,menusaredefinedinan
XMLfile,sowe’llstartbycreatinganewXMLfileinEclipse:
CreateanewAndroidXMLfileinEclipsebychoosingFile→New→Other,andthenselect“AndroidXMLFile.”
Selectaresourcetypeof“Menu,”andsavethefileasmain.xml
.Eclipsewillautomaticallycreateafolder,res/menu
,whereyourmenuXMLfileswillbestored.
Opentheres/menus/main.xml
file,andswitchtoXMLviewbyclickingthe“main.xml”tabalongthebottomofthewindow.
Addanewmenuitem,
.
Noticethe
attributeissetto
.ThistellsAndroidtolookup
inourstrings.xml
fileandreturntheassociatedlabel.Inthiscase,ourmenuitemwillhavealabel“AddTea.”
Next,we’lltellourActivitytodisplaytheoptionsmenuwhentheuserhitsthe“Menu”keyontheirdevice.
BackinBrewClockActivity.java
,overridethe
methodtotellAndroidtoloadourmenuwhentheuserpressesthe“Menu”button:
Whentheuserpressesthe“Menu”buttonontheirdevice,Androidwillnowcall
.Inthismethod,wecreatea
,
whichloadsamenuresourcefromyourapplication’spackage.Justlike
thebuttonsandtextfieldsthatmakeupyourapplication’slayout,themain.xml
resourceisavailableviatheglobal
object,soweusethattosupplythe
withourmenuresource.
To
testthemenu,saveandruntheapplicationintheAndroidemulator.
Whileit’srunning,pressthe“Menu”button,andyou’llseetheoptions
menupopupwithan“AddTea”option.
If
youtapthe“AddTea”option,Androidautomaticallydetectsthetapand
closesthemenu.Inthebackground,Androidwillnotifytheapplication
thattheoptionwastapped.
usertapsthe“AddTea”menuoption,wewanttodisplayanewActivity
sothattheycanenterthedetailsoftheteatobeadded.Startby
creatingthatnewActivitybyselectingFile→New→Class.
Namethenewclass
,andmakesureitinheritsfromthe
class.Itshouldalsobeinthe
package:
Thissimple,blankActivitywon’tdoanythingyet,butitgivesusenoughtofinishouroptionsmenu.
Addthe
overridemethodto
.ThisisthemethodthatAndroidcallswhenyoutapona
(noticeitreceivesthetapped
intheitemparameter):
Withthiscode,we’vetoldAndroidthatwhenthe“AddTea”menuitemistapped,wewanttostartanewActivity;inthiscase,
.However,ratherthandirectlycreatinganinstanceof
,noticethatwe’veusedan
.
IntentsareapowerfulfeatureoftheAndroidframework:theybind
Activitiestogethertomakeupanapplicationandallowdatatobe
passedbetweenthem.
Intentsevenletyourapplicationtake
advantageofanyActivitieswithinotherapplicationsthattheuserhas
installed.Forexample,whentheuseraskstodisplayapicturefroma
gallery,Androidautomaticallydisplaysadialoguetotheuserallowing
themtopicktheapplicationthatdisplaystheimage.Anyapplications
thatareregisteredtohandleimagedisplaywillbeshowninthe
dialogue.
Intentsareapowerfulandcomplextopic,soit’sworthreadingaboutthemindetailintheofficialAndroidSDKdocumentation
.
Let’stryrunningourapplicationtotestoutthenew“AddTea”screen.
Runyourproject,tapthe“Menu”buttonandthentap“AddTea.”
Instead
ofseeingyour“AddTea”Activityasexpected,you’llbepresentedwith
adialoguethatisalltoocommonforAndroiddevelopers:
Althoughwecreatedthe
andtoldittostartour
Activity,theapplicationcrashedbecausewehaven’tyetregisteredit
withinAndroid.Thesystemdoesn’tknowwheretofindtheActivitywe’re
tryingtorun(rememberthatIntentscanstartActivitiesfromany
applicationinstalledonthedevice).Let’sremedythisbyregistering
ourActivitywithintheapplicationmanifestfile.
Openyourapplication’smanifestfile,AndroidManifest.xml
inEclipse,andswitchtothecodeviewbyselectingthe“AndroidManifest.xml”tabalongthebottomofthewindow.
The
application’smanifestfileiswhereyoudefineglobalsettingsand
informationaboutyourapplication.You’llseethatitalreadydeclares
astheActivitytorunwhentheapplicationislaunched.
Within
,addanew
nodetodescribethe“AddTea”Activity.Usethesame
stringthatwedeclaredearlierinstrings.xml
fortheActivity’stitle:
Save
themanifestfilebeforerunningBrewClockagain.Thistime,whenyou
openthemenuandtap“AddTea,”Androidwillstartthe
.Hitthe“Back”buttontogobacktothemainscreen.
WiththeActivitieshookedtogether,it’stimetobuildaninterfaceforaddingtea!
theinterfacetoaddateaisverysimilartohowwebuiltthemain
BrewClockinterfaceintheprevioustutorial.Startbycreatinganew
layoutfile,andthenaddtheappropriateXML,asbelow.
Alternatively,
youcoulduseAndroid’srecentlyimprovedlayouteditorinEclipseto
buildasuitableinterface.CreateanewXMLfileinwhichtodefinethe
layout.GotoFile→New,thenselect“AndroidXMLFile,”andselecta
“Layout”type.Namethefileadd_tea.xml
.
Replacethecontentsofadd_tea.xml
withthefollowinglayout:
We’llalsoneedtoaddsomenewstringstostrings.xml
forthelabelsusedinthisinterface:
In
thislayout,we’veaddedanewtypeofinterfacewidget,theSeekBar.
Thisletstheusereasilyspecifyabrewtimebydraggingathumbfrom
lefttoright.TherangeofvaluesthattheSeekBarproducesalwaysruns
fromzero(0)tothevalueof
.
Inthis
interface,we’veusedascaleof0to9,whichwewillmaptobrewtimes
of1to10minutes(brewingfor0minuteswouldbeawasteofgood
tea!).First,though,weneedtomakesurethat
loadsournewinterface:
AddthefollowinglineofcodetotheActivity’s
methodthatloadsanddisplaysthe
layoutfile:
Nowtestyourapplicationbyrunningit,pressingthe“Menu”buttonandtapping“AddTea”fromthemenu.
You’ll
seeyournewinterfaceonthe“AddTea”screen.Youcanentertextand
slidetheSeekBarleftandright.Butasyou’dexpect,nothingworksyet
becausewehaven’thookedupanycode.
Declaresomepropertiesin
toreferenceourinterfaceelements:
Next,connectthosepropertiestoyourinterface:
The
interfaceisfairlysimple,andtheonlyeventsweneedtolistenfor
arechangestotheSeekBar.WhentheusermovestheSeekBarthumbleft
orright,ourapplicationwillneedtoreadthenewvalueandupdatethe
labelbelowwiththeselectedbrewtime.We’llusea
todetectwhentheSeekBarischanged:
Addan
interfacetothe
classdeclaration,andaddtherequiredmethods:
Theonlyeventwe’reinterestedinis
,
soweneedtoaddthecodebelowtothatmethodtoupdatethebrewtime
labelwiththeselectedvalue.RememberthatourSeekBarvaluesrange
from0to9,sowe’lladd1tothesuppliedvaluesothatitmakesmore
sensetotheuser:
Addthefollowingcodeto
inAddTeaActivity.java
:
SettheSeekBar’slistenertobeour
in
:
NowwhenruntheapplicationandslidetheSeekBarlefttoright,thebrewtimelabelwillbeupdatedwiththecorrectvalue:
aworkinginterfaceforaddingteas,allthat’sleftistogivethe
usertheoptiontosavetheirnewteatothedatabase.We’llalsoadda
littlevalidationtotheinterfacesothattheusercannotsaveanempty
teatothedatabase!
Startbyopeningstrings.xml
intheeditorandaddingsomenewlabelsforourapplication:
Justlikebefore,we’llneedtocreateanewoptionsmenufor
sothattheusercansavetheirfavoritetea:
CreateanewXMLfile,add_tea.xml
,intheres/menus
folderbychoosingFile→NewandthenOther→AndroidXMLFile.Remembertoselect“Menu”astheresourcetype.
Addanitemtothenewmenuforsavingthetea:
Backin
,addtheoverridemethodsfor
and
,justlikeyoudidin
.However,thistime,you’llsupplytheadd_tea.xml
resourcefiletothe
:
Next,we’lladdanewmethod,
,tohandlesavingthetea.The
methodfirstreadsthenameandbrewtimevalueschosenbytheuser,
validatesthemand(ifalliswell)savesthemtothedatabase:
Thisisquiteaheftychunkofcode,solet’sgooverthelogic.
First,wereadthevaluesoftheEditText
andtheSeekBar
(rememberingtoadd1tothevaluetoensureabrewtimeofbetween1
and10minutes).Next,wevalidatethatanamehasbeenenteredthatis
twoormorecharacters(thisisreallysimplevalidation;youmightwant
toexperimentdoingsomethingmoreelaborate,suchasusingregular
expressions).
Iftheteanameisnotvalid,weneedtolettheuserknow.WemakeuseofoneofAndroid’shelperclasses,
,
whichgivesusahandyshortcutforcreatinganddisplayingamodal
dialogwindow.Aftersettingthetitleanderrormessage(usingour
stringresources),thedialogueisdisplayedbycallingits
method.Thisdialogueismodal,sotheuserwillhavetodismissitby
pressingthe“Back”key.Atthispoint,wedon’twanttosaveanydata,
sojustreturn
outofthemethod.
Iftheteaisvalid,wecreateanewtemporaryconnectiontoourteadatabaseusingthe
class.Thisdemonstratestheadvantageofabstractingyourdatabase
accesstoaseparateclass:youcanaccessitfromanywhereinthe
application!
Aftercalling
toaddourteatothedatabase,wenolongerneedthisdatabaseconnection,sowecloseitbeforereturning
toindicatethatthesavewassuccessful.
Try
thisoutbyrunningtheapplicationintheemulator,pressing“Menu”
andtapping“AddTea.”Onceonthe“AddTea”screen,trysavinganempty
teabypressing“Menu”againandtapping“SaveTea.”Withyour
validationinplace,you’llbepresentedwithanerrormessage:
Next,
tryenteringanameforyourtea,choosingasuitablebrewtime,and
choosing“SaveTea”fromthemenuagain.Thistime,youwon’tseean
errormessage.Infact,you’llseenothingatall.
functional,thisisn’tagreatuserexperience.Theuserdoesn’tknow
thattheirteahasbeensuccessfullysaved.Infact,theonlywayto
checkistogobackfromthe“AddTea”Activityandcheckthelistof
teas.Notgreat.Lettingtheuserknowthattheirteawassuccessfully
savedwouldbemuchbetter.Let’sshowamessageonthescreenwhena
teahasbeenaddedsuccessfully.
Wewantthemessagetobepassive,ornon-modal,sousingan
likebeforewon’thelp.Instead,we’llmakeuseofanotherpopularAndroidfeature,the
.
Toasts
displayashortmessagenearthebottomofthescreenbutdonot
interrupttheuser.They’reoftenusedfornon-criticalnotifications
andstatusupdates.
Startbyaddinganewstringtothestrings.xml
resourcefile.Noticethe
inthestring?We’llusethisinthenextsteptointerpolatethenameofthesavedteaintothemessage!
Modifythecodein
tocreateandshowa
pop-upiftheresultof
is
.Thesecondparameterusesof
interpolatethenameofourteaintothe
message.Finally,weclearthe“TeaName”textsothattheusercanquicklyaddmoreteas!
Nowre-runyourapplicationandtryaddingandsavinganewtea.You’llseeanice
popuptoletyouknowtheteahasbeensaved.The
methodinterpolatesthenameoftheteathatwassavedintotheXMLstring,whereweplacedthe
.
Click
the“Back”buttontoreturntotheapplication’smainscreen,andtap
theteaspinner.Thenewteasyouaddedinthedatabasenowshowupas
optionsinthespinner!
fullyfunctional.Userscanaddtheirfavoriteteasandtherespective
brewingtimestothedatabase,andtheycanquicklyselectthemtostart
anewbrew.AnyteasaddedtoBrewClockaresavedinthedatabase,so
evenifwequittheapplicationandcomebacktoitlater,ourlistof
teasisstillavailable.
Onethingyoumightnoticewhen
restartingBrewClock,though,isthatthebrewcounterisresetto0.
Thismakeskeepingtrackofourdailyteaintake(avitalstatistic!)
difficult.Asafinalexercise,let’ssavethetotalbrewcounttothe
device.
Ratherthanaddinganothertabletoourteasdatabase,
we’llmakeuseofAndroid’s“SharedPreferences,”asimpledatabasethat
Androidprovidestoyourapplicationforstoringsimpledata(strings,
numbers,etc.),suchashighscoresingamesanduserpreferences.
StartbyaddingacoupleofconstantstothetopofBrewClockActivity.java
.
Thesewillstorethenameofyoursharedpreferencesfileandthename
ofthekeywe’llusetoaccessthebrewcount.Androidtakescareof
savingandpersistingoursharedpreferencesfile.
Next,
we’llneedtomakesomechangestothecodesothatwecanreadand
writethebrewcounttotheuserpreferences,ratherthanrelyingonan
initialvalueinourcode.In
’s
method,changethelinesaround
tothefollowing:
Herewe’reretrievinganinstanceoftheapplication’ssharedpreferencesusing
,andaskingforthevalueofthe
key(identifiedbythe
constantthatwasdeclaredearlier).Ifavalueisfound,itwillbe
returned;ifnot,we’llusethedefaultvalueinthesecondparameterof
(inthiscase,0).
Nowthatwecanretrievethestoredvalueofbrewcount,weneedtoensureitsvalueissavedto
wheneverthecountisupdated.
Addthefollowingcodeto
in
:
Sharedpreferencesareneversaveddirectly.Instead,wemakeuseofAndroid’s
class.Calling
on
returnsan
instance,whichcanthenbeusedtosetvaluesinourpreferences.When
thevaluesarereadytobesavedtothesharedpreferencesfile,we
justcall
.
Withourapplication’scodeallwrappedup,it’stimetotesteverything!
Run
theapplicationontheemulator,andtimeafewbrews(thisisthe
perfectexcusetogoandmakeawell-deservedteaortwo!),andthen
quittheapplication.Tryrunninganotherapplicationthatisinstalled
ontheemulatortoensureBrewClockisterminated.RememberthatAndroid
doesn’tterminateanActivityuntilitstartstorunoutofmemory.
Whenyounextruntheapplication,you’llseethatyourpreviousbrewcountismaintained,andallyourexistingteasaresaved!
You’vebuiltafullyworkingAndroidapplicationthatmakesuseofa
numberofcorecomponentsoftheAndroidSDK.Inthistutorial,youhave
seenhowto:
CreateasimpleSQLitedatabasetostoreyourapplication’sdata;
MakeuseofAndroid’sdatabaseclassesandwriteacustomclasstoabstractthedataaccess;
Addoptionmenustoyourapplication;
CreateandregisternewActivitieswithinyourapplicationandbindthemtogetherintoacoherentinterfaceusingIntents;
Storeandretrievesimpleuserdataandsettingsusingthebuilt-in“SharedPreferences”database.
Data
storageandpersistenceisanimportanttopic,nomatterwhattypeof
applicationyou’rebuilding.Fromutilitiesandbusinesstoolsto3-D
games,nearlyeveryapplicationwillneedtomakeuseofthedatatools
providedbyAndroid.
isnowonitswaytobeingafullyfunctionalapplication.However,we
couldstillimplementafewmorefeaturestoimprovetheuser
experience.Forexample,youmightliketodevelopyourskillsbytrying
anyofthefollowing:
Checkingforduplicateteanameentriesbeforesavingatea,
Addingamenuoptiontoresetthebrewcounterto0,
Storingthelast-chosenbrewtimeinasharedpreferencesothattheapplicationdefaultstothatvaluewhenrestarted,
Addinganoptionfortheusertodeleteteasfromthedatabase.
SolutionsfortheActivitieswillbeincludedinafuturebranchontheGitHubrepository
,
whereyou’llfindthefullsource-codelistings.Youcandownloadthe
workingtutorialcodebyswitchingyourcopyofthecodetothe
branch:
I
hopeyou’veenjoyedworkingthroughthistutorialandthatithelpsyou
indesigningandbuildingyourgreatAndroidapplications.Pleaselet
meknowhowyougetoninthecommentsbelow,orfeelfreetodropmean
email.
ofthistutorialseries,webuiltasimplebrewtimerapplicationusing
AndroidandEclipse.Inthissecondpart,we’llcontinuedevelopingthe
applicationbyaddingextrafunctionality.Indoingthis,you’llbe
introducedtosomeimportantandpowerfulfeaturesoftheAndroidSDK,
includingPersistentdatastorage,ActivitiesandIntentaswellas
Shareduserpreferences.
Tofollowthistutorial,you’llneedthe
codefromthepreviousarticle.Ifyouwanttogetstartedrightaway,
grabthecodefrom
andcheckoutthetutorial_part_1
tagusingthis:
1 | $gitclonegit: / / github.com / cblunt / BrewClock.git |
2 | $cdBrewClock |
3 | $gitcheckouttutorial_part_1 |
LaunchEclipseandchooseFile→Import…
IntheImportwindow,select“ExistingProjectsintoWorkspace”andclick“Next.”
Onthenextscreen,click“Browse,”andselecttheprojectfolderthatyouclonedfromGitHub.
Click“Finish”toimportyourprojectintoEclipse.
AfterimportingtheprojectintoEclipse,youmightreceiveawarningmessage:
1 | Androidrequired. class compatibility set to 5.0 . |
2 | Pleasefixprojectproperties. |
thisisthecase,right-clickonthenewlyimported“BrewClock”project
inthe“ProjectExplorer,”choose“FixProjectProperties,”andthen
restartEclipse.
GettingStartedWithDataStorage
Currently,BrewClockletsuserssetaspecifictimeforbrewingtheirfavorite
cupsoftea.Thisisgreat,butwhatiftheyregularlydrinkavariety
ofteas,eachwiththeirowndifferentbrewingtimes?Atthemoment,
usershavetorememberbrewingtimesforalltheirfavoriteteas!This
doesn’tmakeforagreatuserexperience.So,inthistutorialwe’ll
developfunctionalitytoletusersstorebrewingtimesfortheir
favoriteteasandthenchoosefromthatlistofteaswhentheymakea
brew.
Todothis,we’lltakeadvantageofAndroid’srich
data-storageAPI.Androidoffersseveralwaystostoredata,twoof
whichwe’llcoverinthisarticle.Thefirst,morepowerfuloption,uses
theSQLitedatabaseenginetostoredataforourapplication.
SQLite
isapopularandlightweightSQLdatabaseenginethatsavesdataina
singlefile.Itisoftenusedindesktopandembeddedapplications,
whererunningaclient-serverSQLengine(suchasMySQLorPostgreSQL)
isn’tfeasible.
EveryapplicationinstalledonanAndroiddevice
cansaveanduseanynumberofSQLitedatabasefiles(subjecttostorage
capacity),whichthesystemwillmanageautomatically.Anapplication’s
databasesareprivateandsocannotbeaccessedbyanyother
applications.(Datacanbesharedthroughthe
ContentProvider
class,butwewon’tcovercontentprovidersinthistutorial.)Database
filespersistwhentheapplicationisupgradedandaredeletedwhenthe
applicationisuninstalled.
We’lluseasimpleSQLitedatabasein
BrewClocktomaintainalistofteasandtheirappropriatebrewing
times.Here’sanoverviewofhowourdatabaseschemawilllook:
1 | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |
2 | |Table:teas| |
3 | + - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - + |
4 | |Column|Description| |
5 | + - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - + |
6 | |_ID|integer,autoincrement| |
7 | |name|text, not null| |
8 | |brew_time|integer, not null| |
9 | + - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - + |
_ID
),
nameandbrewingtime.We’llusetheAPIsprovidedbyAndroidtocreate
thedatabasetableinourcode.Thesystemwilltakecareofcreating
thedatabasefileintherightlocationforourapplication.
AbstractingtheDatabase
Toensurethedatabasecodeiseasytomaintain,we’llabstractallthe
codeforhandlingdatabasecreation,insertsandqueriesintoaseparate
class,
TeaData
.Thisshouldbefairlyfamiliarifyou’re
usedtothemodel-view-controllerapproach.Allthedatabasecodeis
keptinaseparateclassfromour
BrewClockActivity
.TheActivitycanthenjustinstantiateanew
TeaData
instance(whichwillconnecttothedatabase)anddowhatitneedsto
do.Workinginthiswayenablesustoeasilychangethedatabaseinone
placewithouthavingtochangeanythinginanyotherpartsofour
applicationthatdealwiththedatabase.
Createanewclasscalled
TeaData
intheBrewClockprojectbygoingtoFile→New→Class.Ensurethat
TeaData
extendsthe
android.database.sqlite.SQLiteOpenHelper
classandthatyouchecktheboxfor“Constructorsfromsuperclass.”
The
TeaData
classwillautomaticallyhandlethecreationandversioningofaSQLite
databaseforyourapplication.We’llalsoaddmethodstogiveother
partsofourcodeaninterfacetothedatabase.
Addtwoconstantsto
TeaData
tostorethenameandversionofthedatabase,thetable’snameandthe
namesofcolumnsinthattable.We’llusetheAndroid-providedconstant
BaseColumns._ID
forthetable’suniqueidcolumn:
01 | //src/com/example/brewclock/TeaData.java |
02 | importandroid.app.Activity; |
03 | importandroid.content.ContentValues; |
04 | importandroid.content.Context; |
05 | importandroid.database.Cursor; |
06 | importandroid.database.DatabaseUtils; |
07 | importandroid.provider.BaseColumns; |
08 |
09 | publicclassTeaDataextendsSQLiteOpenHelper{ |
10 | privatestaticfinalStringDATABASE_NAME= "teas.db" ; |
11 | privatestaticfinalintDATABASE_VERSION=1; |
12 |
13 | publicstaticfinalStringTABLE_NAME= "teas" ; |
14 |
15 | publicstaticfinalString_ID=BaseColumns._ID; |
16 | publicstaticfinalStringNAME= "name" ; |
17 | publicstaticfinalStringBREW_TIME= "brew_time" ; |
18 |
19 | //… |
20 | } |
TeaData
thatcallsitsparentmethod,supplyingourdatabasenameandversion.
Androidwillautomaticallyhandleopeningthedatabase(andcreatingit
ifitdoesnotexist).
1 | //src/com/example/brewclock/TeaData.java |
2 | publicTeaData(Contextcontext){ |
3 | super (context,DATABASE_NAME, null ,DATABASE_VERSION); |
4 | } |
onCreate
methodtoexecuteastringofSQLcommandsthatcreatethedatabase
tableforourtea.Androidwillhandlethismethodforus,calling
onCreate
whenthedatabasefileisfirstcreated.
Onsubsequentlaunches,Androidcheckstheversionofthedatabaseagainstthe
DATABASE_VERSION
numberwesuppliedtotheconstructor.Iftheversionhaschanged,Androidwillcallthe
onUpgrade
method,whichiswhereyouwouldwriteanycodetomodifythedatabase
structure.Inthistutorial,we’lljustaskAndroidtodropandrecreate
thedatabase.
So,addthefollowingcodeto
onCreate
:
01 | //src/com/example/brewclock/TeaData.java |
02 | @Override |
03 | publicvoidonCreate(SQLiteDatabasedb){ |
04 | //CREATETABLEteas(idINTEGERPRIMARYKEYAUTOINCREMENT,nameTEXTNOTNULL,brew_timeINTEGER); |
05 | Stringsql= |
06 | "CREATETABLE" +TABLE_NAME+ "(" |
07 | +_ID+ "INTEGERPRIMARYKEYAUTOINCREMENT," |
08 | +NAME+ "TEXTNOTNULL," |
09 | +BREW_TIME+ "INTEGER" |
10 | + ");" ; |
11 |
12 | db.execSQL(sql); |
13 | } |
14 |
15 | @Override |
16 | publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion){ |
17 | db.execSQL( "DROPTABLEIFEXISTS" +TABLE_NAME); |
18 | onCreate(db); |
19 | } |
TeaData
thatletsuseasilyaddnewtearecordstothedatabase.We’llsupply
themethodwithanameandbrewingtimefortheteatobeadded.Rather
thanforcingustowriteouttherawSQLtodothis,Androidsuppliesa
setofclassesforinsertingrecordsintothedatabase.First,wecreate
asetof
ContentValues
,pushingtherelevantvaluesintothatset.
Withaninstanceof
ContentValues
,
wesimplysupplythecolumnnameandthevaluetoinsert.Androidtakes
careofcreatingandrunningtheappropriateSQL.UsingAndroid’s
databaseclassesensuresthatthewritesaresafe,andifthedata
storagemechanismchangesinafutureAndroidrelease,ourcodewill
stillwork.
Addanewmethod,
insert()
,tothe
TeaData
class:
01 | //src/com/example/brewclock/TeaData.java |
02 | publicvoidinsert(Stringname,intbrewTime){ |
03 | SQLiteDatabasedb=getWritableDatabase(); |
04 |
05 | ContentValuesvalues= new ContentValues(); |
06 | values.put(NAME,name); |
07 | values.put(BREW_TIME,brewTime); |
08 |
09 | db.insertOrThrow(TABLE_NAME, null ,values); |
10 | } |
RetrievingData
Withtheabilitytosavedataintothedatabase,we’llalsoneedawaytogetitbackout.AndroidprovidestheCursor
interfacefordoingjustthis.A
Cursor
representstheresultsofrunningaSQLqueryagainstthedatabase,and
itmaintainsapointertoonerowwithinthatresultset.Thispointer
canbemovedforwardsandbackwardsthroughtheresults,returningthe
valuesfromeachcolumn.Itcanhelptovisualizethis:
1 | SQLQuery:SELECT * from teasLIMIT 3 ; |
2 |
3 | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |
4 | |_ID|name|brew_time| |
5 | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |
6 | | 1 |EarlGrey| 3 | |
7 | | 2 |Green| 1 |< = Cursor |
8 | | 3 |Assam| 5 | |
9 | + - - - - - - - + - - - - - - - - - - - - - + - - - - - - - - - - - - - + |
thisexample,theCursorispointingatthesecondrowintheresult
set(Greentea).WecouldmovetheCursorbackarowtorepresentthe
firstrow(EarlGrey)bycalling
cursor.moveToPrevious()
,ormoveforwardtotheAssamrowwith
moveToNext()
.TofetchthenameoftheteathattheCursorispointingout,wewouldcall
cursor.getString(1)
,where
1
isthecolumnindexofthecolumnwewishtoretrieve(notethatthe
indexiszero-based,socolumn0isthefirstcolumn,1thesecond
columnandsoon).
NowthatyouknowaboutCursors,addamethodthatcreatesa
Cursor
objectthatreturnsalltheteasinourdatabase.Addan
all
methodto
TeaData
:
01 | //src/com/example/brewclock/TeaData.java |
02 | publicCursorall(Activityactivity){ |
03 | String[]from={_ID,NAME,BREW_TIME}; |
04 | Stringorder=NAME; |
05 |
06 | SQLiteDatabasedb=getReadableDatabase(); |
07 | Cursorcursor=db.query(TABLE_NAME,from, null , null , null , null ,order); |
08 | activity.startManagingCursor(cursor); |
09 |
10 | return cursor; |
11 | } |
gooverthismethodindetail,becauseitlooksalittlestrangeat
first.Again,ratherthanwritingrawSQLtoquerythedatabase,wemake
useofAndroid’sdatabaseinterfacemethods.
First,weneedto
tellAndroidwhichcolumnsfromourdatabasewe’reinterestedin.Todo
this,wecreateanarrayofstrings—eachoneofthecolumnidentifiers
thatwedefinedatthetopof
TeaData
.We’llalsosetthecolumnthatwewanttoordertheresultsbyandstoreitinthe
order
string.
Next,wecreatearead-onlyconnectiontothedatabaseusing
getReadableDatabase()
,andwiththatconnection,wetellAndroidtorunaqueryusingthe
query()
method.The
query()
methodtakesasetofparametersthatAndroidinternallyconvertsintoa
SQLquery.Again,Android’sabstractionlayerensuresthatour
applicationcodewilllikelycontinuetowork,eveniftheunderlying
datastoragechangesinafutureversionofAndroid.
Becausewejustwanttoreturneveryteainthedatabase,wedon’tapplyanyjoins,filtersorgroups(i.e.
WHERE
,
JOIN
,and
GROUPBY
clausesinSQL)tothemethod.The
from
and
order
variablestellthequerywhatcolumnstoreturnonthedatabaseandtheorderinwhichtheyareretrieved.Weusethe
SQLiteDatabase.query()
methodasaninterfacetothedatabase.
Last,weaskthesuppliedActivity(inthiscase,our
BrewClockActivity
)
tomanagetheCursor.Usually,aCursormustbemanuallyrefreshedto
reloadanynewdata,soifweaddedanewteatoourdatabase,wewould
havetoremembertorefreshourCursor.Instead,Androidcantakecare
ofthisforus,recreatingtheresultswhenevertheActivityis
suspendedandresumed,bycalling
startManagingCursor()
.
Finally,
we’lladdanotherutilitymethodtoreturnthenumberofrecordsinthe
table.Onceagain,Androidprovidesahandyutilitytodothisforus
inthe
DatabaseUtils
class:
Addthefollowingmethod,
count
,toyour
TeaData
class:
1 | //src/com/example/brewclock/TeaData.java |
2 | publiclongcount(){ |
3 | SQLiteDatabasedb=getReadableDatabase(); |
4 | return DatabaseUtils.queryNumEntries(db,TABLE_NAME); |
5 | } |
TeaData
class,andfixanymissingimportsusingEclipse(Source→Organize
Imports).Withourdataclassfinished,it’stimetochangeBrewClock’s
interfacetomakeuseofthedatabase!
ModifyBrewClock’sInterfacetoAllowTeaSelection
Thepurposeofstoringpresetteasandbrewtimesistolettheuser
quicklyselecttheirfavoriteteafromthepresets.Tofacilitatethis,
we’lladda
Spinner
(analogoustoapop-upmenuindesktopinterfaces)tothemainBrewClockinterface,populatedwiththelistofteasfrom
TeaData
.
As
intheprevioustutorial,useEclipse’slayouteditortoaddthe
SpinnertoBrewClock’smaininterfacelayoutXMLfile.Addthefollowing
codejustbelowthe
LinearLayout
forthebrewcountlabel
(aroundline24).Remember,youcanswitchtothe“CodeView”tabalong
thebottomofthewindowifEclipseopensthevisuallayouteditor.
01 | <!--/res/layout/main.xml--> |
02 |
03 | <!--TeaSelection--> |
04 | < LinearLayout |
05 | android:orientation = "vertical" |
06 | android:layout_width = "fill_parent" |
07 | android:layout_height = "wrap_content" > |
08 |
09 | < Spinner |
10 | android:id = "@+id/tea_spinner" |
11 | android:layout_width = "fill_parent" |
12 | android:layout_height = "wrap_content" /> |
13 |
14 | </ LinearLayout > |
BrewClockActivity
class,addamembervariabletoreferencetheSpinner,andconnectittotheinterfaceusing
findViewById
:
01 | //src/com/example/brewclock/BrewClockActivity.java |
02 | protectedSpinnerteaSpinner; |
03 | protectedTeaDatateaData; |
04 |
05 | //… |
06 |
07 | publicvoidonCreate(BundlesavedInstanceState){ |
08 | //… |
09 | teaData= new TeaData( this ); |
10 | teaSpinner=(Spinner)findViewById(R.id.tea_spinner); |
11 | } |
runningyourapplicationtomakesurethenewinterfaceworks
correctly.Youshouldseeablankpop-upmenu(orSpinner)justbelow
thebrewcount.Ifyoutapthespinner,Androidhandlesdisplayinga
pop-upmenusothatyoucanchooseanoptionforthespinner.Atthe
moment,themenuisempty,sowe’llremedythatbybindingtheSpinner
toourteadatabase.
DataBinding
WhenAndroidretrievesdatafromadatabase,itreturnsaCursor
object.TheCursorrepresentsasetofresultsfromthedatabaseand
canbemovedthroughtheresultstoretrievevalues.Wecaneasilybind
theseresultstoaview(inthiscase,theSpinner)usingasetof
classesprovidedbyAndroidcalled“Adapters.”Adaptersdoallthehard
workoffetchingdatabaseresultsfroma
Cursor
anddisplayingthemintheinterface.
Rememberthatour
TeaData.all()
methodalreadyreturnsaCursorpopulatedwiththecontentsofourteas
table.UsingthatCursor,allweneedtodoiscreatea
SimpleCursorAdapter
tobinditsdatatoour
teaSpinner
,andAndroidwilltakecareofpopulatingthespinner’soptions.
ConnecttheCursorreturnedby
teaData.all()
totheSpinnerbycreatinga
SimpleCursorAdapter
:
01 | //com/example/brewclock/BrewClockActivity.java |
02 |
03 | publicvoidonCreate(BundlesavedInstanceState){ |
04 | //… |
05 | Cursorcursor=teaData.all( this ); |
06 |
07 | SimpleCursorAdapterteaCursorAdapter= new SimpleCursorAdapter( |
08 | this , |
09 | android.R.layout.simple_spinner_item, |
10 | cursor, |
11 | new String[]{TeaData.NAME}, |
12 | new int[]{android.R.id.text1} |
13 | ); |
14 |
15 | teaSpinner.setAdapter(teaCursorAdapter); |
16 | teaCursorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
17 | } |
android.R
object.Thisprovidessomegenericdefaultresourcesforyour
application,suchassimpleviewsandlayouts.Inthiscase,we’veused
android.R.layout.simple_spinner_item
,whichisasimpletextlabellayout.
If
youruntheapplicationagain,you’llseethatthespinnerisstill
empty!Eventhoughwe’veconnectedthespinnertoourdatabase,there
arenorecordsinthedatabasetodisplay.
Let’sgivetheusera
choiceofteasbyaddingsomedefaultrecordstothedatabasein
BrewClock’sconstructor.Toavoidduplicateentries,we’lladdonlythe
defaultteasifthedatabaseisempty.WecanmakeuseofTeaData’s
count()
methodtocheckifthisisthecase.
Addcodetocreateadefaultsetofteasifthedatabaseisempty.Addthislinejustabovethecodetofetchtheteasfrom
teaData
:
01 | //com/example/brewclock/BrewClockActivity.java |
02 | publicvoidonCreate(BundlesavedInstanceState){ |
03 | //… |
04 |
05 | //Addsomedefaultteadata!(Adjusttoyourpreference:) |
06 | if (teaData.count()==0){ |
07 | teaData.insert( "EarlGrey" ,3); |
08 | teaData.insert( "Assam" ,3); |
09 | teaData.insert( "JasmineGreen" ,1); |
10 | teaData.insert( "Darjeeling" ,2); |
11 | } |
12 |
13 | //Codefromthepreviousstep: |
14 | Cursorcursor=teaData.all( this ); |
15 |
16 | //… |
17 | } |
runtheapplicationagain.You’llnowseethatyourteaSpinnerhasthe
firstteaselected.TappingontheSpinnerletsyouselectoneofthe
teasfromyourdatabase!
Congratulations!
You’vesuccessfullyconnectedyourinterfacetoadatasource.Thisis
oneofthemostimportantaspectsofanysoftwareapplication.Asyou’ve
seen,Androidmakesthistaskfairlyeasy,butitisextremely
powerful.Usingcursorsandadapters,youcantakevirtuallyanydata
source(fromasimplearrayofstringstoacomplexrelationaldatabase
query)andbindittoanytypeofview:aspinner,alistvieworeven
aniTunes-likecover-flowgallery!
Althoughnowwouldbeagood
timeforabrew,ourworkisn’toveryet.Whileyoucanchoosedifferent
teasfromtheSpinner,makingaselectiondoesn’tdoanything.Weneed
tofindoutwhichteatheuserhasselectedandupdatethebrewtime
accordingly.
ReadSelectedTea,andUpdateBrewTime
Todeterminewhichteatheuserhasselectedfromourdatabase,BrewClockActivity
needstolistenforanevent.Similartothe
OnClickListener
eventthatistriggeredbybuttonpresses,we’llimplementthe
OnItemSelectedListener
.Eventsinthislisteneraretriggeredwhentheusermakesaselectionfromaview,suchasourSpinner.
Enablethe
onItemSelectedListener
in
BrewClockActivity
byaddingittotheclassdeclaration.Remembertoimplementtheinterfacemethods
onItemSelected()
and
onNothingSelected()
:
01 | //src/com/example/brewclock/BrewClockActivity.java |
02 | publicclassBrewClockActivityextendsActivityimplementsOnClickListener,OnItemSelectedListener{ |
03 | //… |
04 | publicvoidonItemSelected(AdapterView<?>spinner,Viewview,intposition,longid){ |
05 | if (spinner==teaSpinner){ |
06 | //Updatethebrewtimewiththeselectedtea’sbrewtime |
07 | Cursorcursor=(Cursor)spinner.getSelectedItem(); |
08 | setBrewTime(cursor.getInt(2)); |
09 | } |
10 | } |
11 |
12 | publicvoidonNothingSelected(AdapterView<?>adapterView){ |
13 | //Donothing |
14 | } |
15 | } |
onItemSelected
eventwasBrewClock’s
teaSpinner
.Ifso,weretrieveaCursorobjectthatrepresentstheselectedrecord.Thisisallhandledforusbythe
SimpleCursorAdapter
thatconnects
teaData
totheSpinner.AndroidknowswhichquerypopulatestheSpinnerand
whichitemtheuserhasselected.Itusesthesetoreturnthesinglerow
fromthedatabase,representingtheuser’sselectedtea.
Cursor’s
getInt()
methodtakestheindexofthecolumnwewanttoretrieve.RememberthatwhenwebuiltourCursorin
teaData.all()
,thecolumnswereadwere
_ID
,
NAME
and
BREW_TIME
.AssumingwechoseJasmineteain
teaSpinner
,theCursorreturnedbyourselectionwouldbepointingatthatrecordinthedatabase.
WethenasktheCursortoretrievethevaluefromcolumn2(using
getInt(2)
),whichinthisqueryisour
BREW_TIME
column.Thisvalueissuppliedtoourexisting
setBrewTime()
method,whichupdatestheinterfacetoshowtheselectedtea’sbrewingtime.
Finally,weneedtotellthe
teaSpinner
that
BrewClockActivity
islisteningfor
OnItemSelected
events.Addthefollowinglineto
BrewClockActivity
’s
onCreate
method:
1 | //src/com/example/brewclock/BrewClockActivity.java |
2 | publicvoidonCreate(){ |
3 | //… |
4 | teaSpinner.setOnItemSelectedListener( this ); |
5 | } |
shoulddoit!Runyourapplicationagain,andtryselectingdifferent
teasfromtheSpinner.Eachtimeyouselectatea,itsbrewtimewillbe
shownonthecountdownclock.Therestofourcodealreadyhandles
countingdownfromthecurrentbrewtime,sowenowhaveafullyworking
brewtimer,withalistofpresetteas.
Youcan,ofcourse,go
backintothecodeandaddmorepresetteastothedatabasetosuityour
tastes.ButwhatifwereleasedBrewClocktothemarket?Everytime
someonewantedtoaddanewteatothedatabase,we’dneedtomanually
updatethedatabase,andrepublishtheapplication;everyonewouldneed
toupdate,andeverybodywouldhavethesamelistofteas.Thatsounds
prettyinflexible,andalotofworkforus!
Itwouldbemuchbetteriftheuserhadsomewaytoaddtheirownteasandpreferencestothedatabase.We’lltacklethatnext…
IntroducingActivities
EachscreeninyourapplicationanditsassociatedcodeisanActivity
.
Everytimeyougofromonescreentoanother,Androidcreatesanew
Activity.Inreality,althoughanapplicationmaycompriseanynumberof
screens/activities,Androidtreatsthemasseparateentities.
ActivitiesworktogethertoformacohesiveexperiencebecauseAndroid
letsyoueasilypassdatabetweenthem.
Inthisfinalsection,you’lladdanewActivity(
AddTeaActivity
)toyourapplicationandregisteritwiththeAndroidsystem.You’llthenpassdatafromtheoriginal
BrewClockActivity
tothisnewActivity.
First,though,weneedtogivetheuserawaytoswitchtothenewActivity.We’lldothisusinganoptionsmenu.
OptionsMenus
Optionsmenusarethepop-upmenusthatappearwhentheuserhitsthe“Menu”
keyontheirdevice.Androidhandlesthecreationanddisplayofoptions
menusautomatically;youjustneedtotellitwhatoptionstodisplay
andwhattodowhenanoptionischosenbytheuser.
However,
ratherthanhard-codingourlabelsintothemenuitself,we’llmakeuse
ofAndroidstringresources.Stringresourcesletyoumaintainallthe
human-readablestringsandlabelsforyourapplicationinonefile,
callingthemwithinyourcode.Thismeansthere’sonlyoneplaceinyour
codewhereyouneedtochangestringsinthefuture.
Intheprojectexplorer,navigateto“res/values”andyouwillseethatastrings.xml
filealreadyexists.ThiswascreatedbyEclipsewhenwefirstcreated
theproject,anditisusedtostoreanystringsoftextthatwewantto
usethroughouttheapplication.
Openstrings.xml
bydoubleclickingonit,andswitchtotheXMLviewbyclickingthestrings.xml
tabalongthebottomofthewindow.
Addthefollowinglinewithinthe
<resources>…</resources>
element:
1 | <!--res/values/strings.xml--> |
2 | < resources > |
3 | <!--…--> |
4 | < string name = "add_tea_label" >AddTea</ string > |
5 | </ resources > |
add_tea_label
,anditsassociatedtext.Wecanuse
add_tea_label
toreferencethestringthroughouttheapplication’scode.Ifthelabel
needstochangeforsomereasoninthefuture,you’llneedtochangeit
onlyonceinthisfile.
Next,let’screateanewfiletodefine
ouroptionsmenu.Justlikestringsandlayouts,menusaredefinedinan
XMLfile,sowe’llstartbycreatinganewXMLfileinEclipse:
CreateanewAndroidXMLfileinEclipsebychoosingFile→New→Other,andthenselect“AndroidXMLFile.”
Selectaresourcetypeof“Menu,”andsavethefileasmain.xml
.Eclipsewillautomaticallycreateafolder,res/menu
,whereyourmenuXMLfileswillbestored.
Opentheres/menus/main.xml
file,andswitchtoXMLviewbyclickingthe“main.xml”tabalongthebottomofthewindow.
Addanewmenuitem,
add_tea
.
1 | <? xml version = "1.0" encoding = "utf-8" ?> |
2 | < menu xmlns:android = " > |
3 | < item android:id = "@+id/add_tea" android:title = "@string/add_tea_label" /> |
4 | </ menu > |
android:title
attributeissetto
@string/add_tea_label
.ThistellsAndroidtolookup
add_tea_label
inourstrings.xml
fileandreturntheassociatedlabel.Inthiscase,ourmenuitemwillhavealabel“AddTea.”
Next,we’lltellourActivitytodisplaytheoptionsmenuwhentheuserhitsthe“Menu”keyontheirdevice.
BackinBrewClockActivity.java
,overridethe
onCreateOptionsMenu
methodtotellAndroidtoloadourmenuwhentheuserpressesthe“Menu”button:
1 | //src/com/example/brewclock/BrewClockActivity.java |
2 | @Override |
3 | publicbooleanonCreateOptionsMenu(Menumenu){ |
4 | MenuInflaterinflater=getMenuInflater(); |
5 | inflater.inflate(R.menu.main,menu); |
6 |
7 | return true ; |
8 | } |
onCreateOptionsMenu
.Inthismethod,wecreatea
MenuInflater
,
whichloadsamenuresourcefromyourapplication’spackage.Justlike
thebuttonsandtextfieldsthatmakeupyourapplication’slayout,themain.xml
resourceisavailableviatheglobal
R
object,soweusethattosupplythe
MenuInflater
withourmenuresource.
To
testthemenu,saveandruntheapplicationintheAndroidemulator.
Whileit’srunning,pressthe“Menu”button,andyou’llseetheoptions
menupopupwithan“AddTea”option.
If
youtapthe“AddTea”option,Androidautomaticallydetectsthetapand
closesthemenu.Inthebackground,Androidwillnotifytheapplication
thattheoptionwastapped.
HandlingMenuTaps
Whentheusertapsthe“AddTea”menuoption,wewanttodisplayanewActivity
sothattheycanenterthedetailsoftheteatobeadded.Startby
creatingthatnewActivitybyselectingFile→New→Class.
Namethenewclass
AddTeaActivity
,andmakesureitinheritsfromthe
android.app.Activity
class.Itshouldalsobeinthe
com.example.brewclock
package:
01 | //src/com/example/brewclock/AddTeaActivity.java |
02 | packagecom.example.brewclock; |
03 |
04 | importandroid.app.Activity; |
05 | importandroid.os.Bundle; |
06 |
07 | publicclassAddTeaActivityextendsActivity{ |
08 | @Override |
09 | publicvoidonCreate(BundlesavedInstanceState){ |
10 | super .onCreate(savedInstanceState); |
11 | } |
12 | } |
Addthe
onOptionsItemSelected
overridemethodto
BrewClockActivity
.ThisisthemethodthatAndroidcallswhenyoutapona
MenuItem
(noticeitreceivesthetapped
MenuItem
intheitemparameter):
01 | //src/com/example/brewclock/BrewClockActivity.java |
02 | @Override |
03 | publicbooleanonOptionsItemSelected(MenuItemitem){ |
04 | switch (item.getItemId()){ |
05 | case R.id.add_tea: |
06 | Intentintent= new Intent( this ,AddTeaActivity.class); |
07 | startActivity(intent); |
08 | return true ; |
09 |
10 | default : |
11 | return super .onOptionsItemSelected(item); |
12 | } |
13 | } |
AddTeaActivity
.However,ratherthandirectlycreatinganinstanceof
AddTeaActivity
,noticethatwe’veusedan
Intent
.
IntentsareapowerfulfeatureoftheAndroidframework:theybind
Activitiestogethertomakeupanapplicationandallowdatatobe
passedbetweenthem.
Intentsevenletyourapplicationtake
advantageofanyActivitieswithinotherapplicationsthattheuserhas
installed.Forexample,whentheuseraskstodisplayapicturefroma
gallery,Androidautomaticallydisplaysadialoguetotheuserallowing
themtopicktheapplicationthatdisplaystheimage.Anyapplications
thatareregisteredtohandleimagedisplaywillbeshowninthe
dialogue.
Intentsareapowerfulandcomplextopic,soit’sworthreadingaboutthemindetailinthe
.
Let’stryrunningourapplicationtotestoutthenew“AddTea”screen.
Runyourproject,tapthe“Menu”buttonandthentap“AddTea.”
Instead
ofseeingyour“AddTea”Activityasexpected,you’llbepresentedwith
adialoguethatisalltoocommonforAndroiddevelopers:
Althoughwecreatedthe
Intent
andtoldittostartour
AddTeaActivity
Activity,theapplicationcrashedbecausewehaven’tyetregisteredit
withinAndroid.Thesystemdoesn’tknowwheretofindtheActivitywe’re
tryingtorun(rememberthatIntentscanstartActivitiesfromany
applicationinstalledonthedevice).Let’sremedythisbyregistering
ourActivitywithintheapplicationmanifestfile.
Openyourapplication’smanifestfile,AndroidManifest.xml
inEclipse,andswitchtothecodeviewbyselectingthe“AndroidManifest.xml”tabalongthebottomofthewindow.
The
application’smanifestfileiswhereyoudefineglobalsettingsand
informationaboutyourapplication.You’llseethatitalreadydeclares
.BrewClockActivity
astheActivitytorunwhentheapplicationislaunched.
Within
<application>
,addanew
<activity>
nodetodescribethe“AddTea”Activity.Usethesame
add_tea_label
stringthatwedeclaredearlierinstrings.xml
fortheActivity’stitle:
1 | <!--AndroidManifest.xml--> |
2 | < application …> |
3 | … |
4 | < activity android:name = ".AddTeaActivity" android:label = "@string/add_tea_label" /> |
5 | </ application > |
themanifestfilebeforerunningBrewClockagain.Thistime,whenyou
openthemenuandtap“AddTea,”Androidwillstartthe
AddTeaActivity
.Hitthe“Back”buttontogobacktothemainscreen.
WiththeActivitieshookedtogether,it’stimetobuildaninterfaceforaddingtea!
BuildingTheTeaEditorInterface
Buildingtheinterfacetoaddateaisverysimilartohowwebuiltthemain
BrewClockinterfaceintheprevioustutorial.Startbycreatinganew
layoutfile,andthenaddtheappropriateXML,asbelow.
Alternatively,
youcoulduseAndroid’srecentlyimprovedlayouteditorinEclipseto
buildasuitableinterface.CreateanewXMLfileinwhichtodefinethe
layout.GotoFile→New,thenselect“AndroidXMLFile,”andselecta
“Layout”type.Namethefileadd_tea.xml
.
Replacethecontentsofadd_tea.xml
withthefollowinglayout:
01 | <!--res/layouts/add_tea.xml--> |
02 | <? xml version = "1.0" encoding = "utf-8" ?> |
03 | < LinearLayout |
04 | xmlns:android = " |
05 | android:layout_width = "fill_parent" |
06 | android:layout_height = "fill_parent" |
07 | android:orientation = "vertical" |
08 | android:padding = "10dip" > |
09 |
10 | < TextView |
11 | android:text = "@string/tea_name_label" |
12 | android:layout_width = "fill_parent" |
13 | android:layout_height = "wrap_content" /> |
14 |
15 | < EditText |
16 | android:id = "@+id/tea_name" |
17 | android:layout_width = "fill_parent" |
18 | android:layout_height = "wrap_content" /> |
19 |
20 | < TextView |
21 | android:text = "@string/brew_time_label" |
22 | android:layout_width = "wrap_content" |
23 | android:layout_height = "wrap_content" /> |
24 |
25 | < SeekBar |
26 | android:id = "@+id/brew_time_seekbar" |
27 | android:layout_width = "fill_parent" |
28 | android:layout_height = "wrap_content" |
29 | android:progress = "2" |
30 | android:max = "9" /> |
31 |
32 | < TextView |
33 | android:id = "@+id/brew_time_value" |
34 | android:text = "3m" |
35 | android:textSize = "20dip" |
36 | android:layout_width = "fill_parent" |
37 | android:layout_height = "wrap_content" |
38 | android:gravity = "center_horizontal" /> |
39 | </ LinearLayout > |
forthelabelsusedinthisinterface:
1 | <!--res/values/strings.xml--> |
2 | < resources > |
3 | <!--…--> |
4 | < string name = "tea_name_label" >TeaName</ string > |
5 |
6 | < string name = "brew_time_label" >BrewTime</ string > |
7 | </ resources > |
thislayout,we’veaddedanewtypeofinterfacewidget,theSeekBar.
Thisletstheusereasilyspecifyabrewtimebydraggingathumbfrom
lefttoright.TherangeofvaluesthattheSeekBarproducesalwaysruns
fromzero(0)tothevalueof
android:max
.
Inthis
interface,we’veusedascaleof0to9,whichwewillmaptobrewtimes
of1to10minutes(brewingfor0minuteswouldbeawasteofgood
tea!).First,though,weneedtomakesurethat
AddTeaActivity
loadsournewinterface:
AddthefollowinglineofcodetotheActivity’s
onCreate()
methodthatloadsanddisplaysthe
add_tea
layoutfile:
1 | //src/com/example/brewclock/AddTeaActivity.java |
2 | publicvoidonCreate(BundlesavedInstanceState){ |
3 | super .onCreate(savedInstanceState); |
4 | setContentView(R.layout.add_tea); |
5 | } |
You’ll
seeyournewinterfaceonthe“AddTea”screen.Youcanentertextand
slidetheSeekBarleftandright.Butasyou’dexpect,nothingworksyet
becausewehaven’thookedupanycode.
Declaresomepropertiesin
AddTeaActivity
toreferenceourinterfaceelements:
01 | //src/com/example/brewclock/AddTeaActivity.java |
02 | publicclassAddTeaActivity{ |
03 | //… |
04 |
05 | /**Properties**/ |
06 | protectedEditTextteaName; |
07 | protectedSeekBarbrewTimeSeekBar; |
08 | protectedTextViewbrewTimeLabel; |
09 |
10 | //… |
1 | publicvoidonCreate(BundlesavedInstanceState){ |
2 | //… |
3 | //Connectinterfaceelementstoproperties |
4 | teaName=(EditText)findViewById(R.id.tea_name); |
5 | brewTimeSeekBar=(SeekBar)findViewById(R.id.brew_time_seekbar); |
6 | brewTimeLabel=(TextView)findViewById(R.id.brew_time_value); |
7 | } |
interfaceisfairlysimple,andtheonlyeventsweneedtolistenfor
arechangestotheSeekBar.WhentheusermovestheSeekBarthumbleft
orright,ourapplicationwillneedtoreadthenewvalueandupdatethe
labelbelowwiththeselectedbrewtime.We’llusea
Listener
todetectwhentheSeekBarischanged:
Addan
onSeekBarChangedListener
interfacetothe
AddTeaActivity
classdeclaration,andaddtherequiredmethods:
01 | //src/com/example/brewclock/AddTeaActivity.java |
02 | publicclassAddTeaActivity |
03 | extendsActivity |
04 | implementsOnSeekBarChangeListener{ |
05 | //… |
06 |
07 | publicvoidonProgressChanged(SeekBarseekBar,intprogress,booleanfromUser){ |
08 | //TODODetectchangeinprogress |
09 | } |
10 |
11 | publicvoidonStartTrackingTouch(SeekBarseekBar){} |
12 |
13 | publicvoidonStopTrackingTouch(SeekBarseekBar){} |
14 | } |
onProgressChanged
,
soweneedtoaddthecodebelowtothatmethodtoupdatethebrewtime
labelwiththeselectedvalue.RememberthatourSeekBarvaluesrange
from0to9,sowe’lladd1tothesuppliedvaluesothatitmakesmore
sensetotheuser:
Addthefollowingcodeto
onProgressChanged()
inAddTeaActivity.java
:
1 | //src/com/example/brewclock/AddTeaActivity.java |
2 | publicvoidonProgressChanged(SeekBarseekBar,intprogress,booleanfromUser){ |
3 | if (seekBar==brewTimeSeekBar){ |
4 | //Updatethebrewtimelabelwiththechosenvalue. |
5 | brewTimeLabel.setText((progress+1)+ "m" ); |
6 | } |
7 | } |
AddTeaActivity
in
onCreate
:
1 | //src/com/example/brewclock/AddTeaActivity.java |
2 | publicvoidonCreate(BundlesavedInstanceState){ |
3 | //… |
4 |
5 | //SetupListeners |
6 | brewTimeSeekBar.setOnSeekBarChangeListener( this ); |
7 | } |
SavingTea
Withaworkinginterfaceforaddingteas,allthat’sleftistogivethe
usertheoptiontosavetheirnewteatothedatabase.We’llalsoadda
littlevalidationtotheinterfacesothattheusercannotsaveanempty
teatothedatabase!
Startbyopeningstrings.xml
intheeditorandaddingsomenewlabelsforourapplication:
1 | <!--res/values/strings.xml--> |
2 | < string name = "save_tea_label" >SaveTea</ string > |
3 | < string name = "invalid_tea_title" >Teacouldnotbesaved.</ string > |
4 |
5 | < string name = "invalid_tea_no_name" >Enteranameforyourtea.</ string > |
AddTeaActivity
sothattheusercansavetheirfavoritetea:
CreateanewXMLfile,add_tea.xml
,intheres/menus
folderbychoosingFile→NewandthenOther→AndroidXMLFile.Remembertoselect“Menu”astheresourcetype.
Addanitemtothenewmenuforsavingthetea:
1 | <!--res/menus/add_tea.xml--> |
2 | <? xml version = "1.0" encoding = "utf-8" ?> |
3 | < menu xmlns:android = " > |
4 | < item android:title = "@string/save_tea_label" android:id = "@+id/save_tea" /> |
5 | </ menu > |
AddTeaActivity
,addtheoverridemethodsfor
onCreateOptionsMenu
and
onOptionsItemSelected
,justlikeyoudidin
BrewClockActivity
.However,thistime,you’llsupplytheadd_tea.xml
resourcefiletothe
MenuInflater
:
01 | //src/com/example/brewclock/AddTeaActivity.java |
02 | @Override |
03 | publicbooleanonCreateOptionsMenu(Menumenu){ |
04 | MenuInflaterinflater=getMenuInflater(); |
05 | inflater.inflate(R.menu.add_tea,menu); |
06 |
07 | return true ; |
08 | } |
09 |
10 | @Override |
11 | publicbooleanonOptionsItemSelected(MenuItemitem){ |
12 | switch (item.getItemId()){ |
13 | case R.id.save_tea: |
14 | saveTea(); |
15 |
16 | default : |
17 | return super .onOptionsItemSelected(item); |
18 | } |
19 | } |
saveTea()
,tohandlesavingthetea.The
saveTea
methodfirstreadsthenameandbrewtimevalueschosenbytheuser,
validatesthemand(ifalliswell)savesthemtothedatabase:
01 | //src/com/example/brewclock/AddTeaActivity.java |
02 | publicbooleansaveTea(){ |
03 | //Readvaluesfromtheinterface |
04 | StringteaNameText=teaName.getText().toString(); |
05 | intbrewTimeValue=brewTimeSeekBar.getProgress()+1; |
06 |
07 | //Validateanamehasbeenenteredforthetea |
08 | if (teaNameText.length()<2){ |
09 | AlertDialog.Builderdialog= new AlertDialog.Builder( this ); |
10 | dialog.setTitle(R.string.invalid_tea_title); |
11 | dialog.setMessage(R.string.invalid_tea_no_name); |
12 | dialog.show(); |
13 |
14 | return false ; |
15 | } |
16 |
17 | //Theteaisvalid,soconnecttotheteadatabaseandinsertthetea |
18 | TeaDatateaData= new TeaData( this ); |
19 | teaData.insert(teaNameText,brewTimeValue); |
20 | teaData.close(); |
21 |
22 | return true ; |
23 | } |
First,wereadthevaluesoftheEditText
teaName
andtheSeekBar
brewTimeSeekBar
(rememberingtoadd1tothevaluetoensureabrewtimeofbetween1
and10minutes).Next,wevalidatethatanamehasbeenenteredthatis
twoormorecharacters(thisisreallysimplevalidation;youmightwant
toexperimentdoingsomethingmoreelaborate,suchasusingregular
expressions).
Iftheteanameisnotvalid,weneedtolettheuserknow.WemakeuseofoneofAndroid’shelperclasses,
AlertDialog.Builder
,
whichgivesusahandyshortcutforcreatinganddisplayingamodal
dialogwindow.Aftersettingthetitleanderrormessage(usingour
stringresources),thedialogueisdisplayedbycallingits
show()
method.Thisdialogueismodal,sotheuserwillhavetodismissitby
pressingthe“Back”key.Atthispoint,wedon’twanttosaveanydata,
sojustreturn
false
outofthemethod.
Iftheteaisvalid,wecreateanewtemporaryconnectiontoourteadatabaseusingthe
TeaData
class.Thisdemonstratestheadvantageofabstractingyourdatabase
accesstoaseparateclass:youcanaccessitfromanywhereinthe
application!
Aftercalling
teaData.insert()
toaddourteatothedatabase,wenolongerneedthisdatabaseconnection,sowecloseitbeforereturning
true
toindicatethatthesavewassuccessful.
Try
thisoutbyrunningtheapplicationintheemulator,pressing“Menu”
andtapping“AddTea.”Onceonthe“AddTea”screen,trysavinganempty
teabypressing“Menu”againandtapping“SaveTea.”Withyour
validationinplace,you’llbepresentedwithanerrormessage:
Next,
tryenteringanameforyourtea,choosingasuitablebrewtime,and
choosing“SaveTea”fromthemenuagain.Thistime,youwon’tseean
errormessage.Infact,you’llseenothingatall.
ImprovingtheUserExperience
Whilefunctional,thisisn’tagreatuserexperience.Theuserdoesn’tknow
thattheirteahasbeensuccessfullysaved.Infact,theonlywayto
checkistogobackfromthe“AddTea”Activityandcheckthelistof
teas.Notgreat.Lettingtheuserknowthattheirteawassuccessfully
savedwouldbemuchbetter.Let’sshowamessageonthescreenwhena
teahasbeenaddedsuccessfully.
Wewantthemessagetobepassive,ornon-modal,sousingan
AlertDialog
likebeforewon’thelp.Instead,we’llmakeuseofanotherpopularAndroidfeature,the
Toast
.
Toasts
displayashortmessagenearthebottomofthescreenbutdonot
interrupttheuser.They’reoftenusedfornon-criticalnotifications
andstatusupdates.
Startbyaddinganewstringtothestrings.xml
resourcefile.Noticethe
%s
inthestring?We’llusethisinthenextsteptointerpolatethenameofthesavedteaintothemessage!
1 | <!--res/values/strings.xml--> |
2 | < string name = "save_tea_success" >%steahasbeensaved.</ string > |
onOptionsItemSelected
tocreateandshowa
Toast
pop-upiftheresultof
saveTea()
is
true
.Thesecondparameterusesof
getString()
interpolatethenameofourteaintothe
Toast
message.Finally,weclearthe“TeaName”textsothattheusercanquicklyaddmoreteas!
1 | //src/com/example/brewclock/AddTeaActivity.java |
2 | //… |
3 | switch (item.getItemId()){ |
4 | case R.id.save_tea: |
5 | if (saveTea()){ |
6 | Toast.makeText( this ,getString(R.string.save_tea_success,teaName.getText().toString()),Toast.LENGTH_SHORT).show(); |
7 | teaName.setText( "" ); |
8 | } |
9 | //… |
Toast
popuptoletyouknowtheteahasbeensaved.The
getString()
methodinterpolatesthenameoftheteathatwassavedintotheXMLstring,whereweplacedthe
%s
.
Click
the“Back”buttontoreturntotheapplication’smainscreen,andtap
theteaspinner.Thenewteasyouaddedinthedatabasenowshowupas
optionsinthespinner!
UserPreferences
BrewClockisnowfullyfunctional.Userscanaddtheirfavoriteteasandtherespective
brewingtimestothedatabase,andtheycanquicklyselectthemtostart
anewbrew.AnyteasaddedtoBrewClockaresavedinthedatabase,so
evenifwequittheapplicationandcomebacktoitlater,ourlistof
teasisstillavailable.
Onethingyoumightnoticewhen
restartingBrewClock,though,isthatthebrewcounterisresetto0.
Thismakeskeepingtrackofourdailyteaintake(avitalstatistic!)
difficult.Asafinalexercise,let’ssavethetotalbrewcounttothe
device.
Ratherthanaddinganothertabletoourteasdatabase,
we’llmakeuseofAndroid’s“SharedPreferences,”asimpledatabasethat
Androidprovidestoyourapplicationforstoringsimpledata(strings,
numbers,etc.),suchashighscoresingamesanduserpreferences.
StartbyaddingacoupleofconstantstothetopofBrewClockActivity.java
.
Thesewillstorethenameofyoursharedpreferencesfileandthename
ofthekeywe’llusetoaccessthebrewcount.Androidtakescareof
savingandpersistingoursharedpreferencesfile.
1 | //src/com/example/brewclock/BrewClockActivity.java |
2 | protectedstaticfinalStringSHARED_PREFS_NAME= "brew_count_preferences" ; |
3 | protectedstaticfinalStringBREW_COUNT_SHARED_PREF= "brew_count" ; |
we’llneedtomakesomechangestothecodesothatwecanreadand
writethebrewcounttotheuserpreferences,ratherthanrelyingonan
initialvalueinourcode.In
BrewClockActivity
’s
onCreate
method,changethelinesaround
setBrewCount(0)
tothefollowing:
01 | //src/com/example/brewclock/BrewClockActivity.java |
02 | publicvoidonCreate(){ |
03 | //… |
04 |
05 | //Settheinitialbrewvalues |
06 | SharedPreferencessharedPreferences=getSharedPreferences(SHARED_PREFS_NAME,MODE_PRIVATE); |
07 | brewCount=sharedPreferences.getInt(BREW_COUNT_SHARED_PREF,0); |
08 | setBrewCount(brewCount); |
09 |
10 | //… |
11 | } |
SharedPreferences
,andaskingforthevalueofthe
brew_count
key(identifiedbythe
BREW_COUNT_SHARED_PREF
constantthatwasdeclaredearlier).Ifavalueisfound,itwillbe
returned;ifnot,we’llusethedefaultvalueinthesecondparameterof
getInt
(inthiscase,0).
Nowthatwecanretrievethestoredvalueofbrewcount,weneedtoensureitsvalueissavedto
SharedPreferences
wheneverthecountisupdated.
Addthefollowingcodeto
setBrewCount
in
BrewClockActivity
:
01 | //src/com/example/brewclock/BrewClockActivity.java |
02 | publicvoidsetBrewCount(intcount){ |
03 | brewCount=count; |
04 | brewCountLabel.setText(String.valueOf(brewCount)); |
05 |
06 | //UpdatethebrewCountandwritethevaluetothesharedpreferences. |
07 | SharedPreferences.Editoreditor=getSharedPreferences(SHARED_PREFS_NAME,MODE_PRIVATE).edit(); |
08 | editor.putInt(BREW_COUNT_SHARED_PREF,brewCount); |
09 | editor.commit(); |
10 | } |
SharedPreferences.Editor
class.Calling
edit()
on
SharedPreferences
returnsan
editor
instance,whichcanthenbeusedtosetvaluesinourpreferences.When
thevaluesarereadytobesavedtothesharedpreferencesfile,we
justcall
commit()
.
Withourapplication’scodeallwrappedup,it’stimetotesteverything!
Run
theapplicationontheemulator,andtimeafewbrews(thisisthe
perfectexcusetogoandmakeawell-deservedteaortwo!),andthen
quittheapplication.Tryrunninganotherapplicationthatisinstalled
ontheemulatortoensureBrewClockisterminated.RememberthatAndroid
doesn’tterminateanActivityuntilitstartstorunoutofmemory.
Whenyounextruntheapplication,you’llseethatyourpreviousbrewcountismaintained,andallyourexistingteasaresaved!
Summary
Congratulations!You’vebuiltafullyworkingAndroidapplicationthatmakesuseofa
numberofcorecomponentsoftheAndroidSDK.Inthistutorial,youhave
seenhowto:
CreateasimpleSQLitedatabasetostoreyourapplication’sdata;
MakeuseofAndroid’sdatabaseclassesandwriteacustomclasstoabstractthedataaccess;
Addoptionmenustoyourapplication;
CreateandregisternewActivitieswithinyourapplicationandbindthemtogetherintoacoherentinterfaceusingIntents;
Storeandretrievesimpleuserdataandsettingsusingthebuilt-in“SharedPreferences”database.
Data
storageandpersistenceisanimportanttopic,nomatterwhattypeof
applicationyou’rebuilding.Fromutilitiesandbusinesstoolsto3-D
games,nearlyeveryapplicationwillneedtomakeuseofthedatatools
providedbyAndroid.
Activities
BrewClockisnowonitswaytobeingafullyfunctionalapplication.However,we
couldstillimplementafewmorefeaturestoimprovetheuser
experience.Forexample,youmightliketodevelopyourskillsbytrying
anyofthefollowing:
Checkingforduplicateteanameentriesbeforesavingatea,
Addingamenuoptiontoresetthebrewcounterto0,
Storingthelast-chosenbrewtimeinasharedpreferencesothattheapplicationdefaultstothatvaluewhenrestarted,
Addinganoptionfortheusertodeleteteasfromthedatabase.
SolutionsfortheActivitieswillbeincludedinafuturebranchonthe
,
whereyou’llfindthefullsource-codelistings.Youcandownloadthe
workingtutorialcodebyswitchingyourcopyofthecodetothe
tutorial_2
branch:
1 | #Ifyou’venotalreadyclonedtherepository, |
2 | #you’llneedtodothatfirst: |
3 | #$gitclone |
4 | #$cdBrewClock |
5 | $gitcheckouttutorial_2 |
hopeyou’veenjoyedworkingthroughthistutorialandthatithelpsyou
indesigningandbuildingyourgreatAndroidapplications.Pleaselet
meknowhowyougetoninthecommentsbelow,orfeelfreetodropmean
email.
相关文章推荐
- 【Android 进阶:翻译】Get Started With Firebase for Android
- Get started with Android fragments
- Windows Phone Dev Center - Getting started with developing for Windows Phone
- Android Wear Preview - Get Started With Developer Preview
- eclipse导入github上的android项目,出现unable to get system library for the project
- Get started with Docker for Windows
- Get Started With Continuous Integration For Your .NET (C#) Projects - Test Your Project
- Getting started with JRebel for Android
- Solutions for common Android development problems with the Eclipse IDE- Tutorial
- Get started with OpenCV on Android
- Get Started With Continuous Integration For Your .NET (C#) Projects - Build Your Project
- Get Started With Blastwave.org for Solaris 10 Users
- android eclipse 导入工程报错unable to get system library for the project
- Get Started With Continuous Integration For Your .NET (C#) Projects - Integrate Sonar
- Android:Eclipse-unable to get system library for the project
- Enhancing Security with Device Management Policies 加强安全与设备管理策略 Developing for Enterprise
- Getting Started with Python Programming for Mac Users
- Google Developing for Android 学习总结
- Eclipse调试Android App若选择“Use same device for future launches”
- Android开发环境部署—— Eclipse+NDK for Android JNI 的开发环境(linux 环境)