182

Android SQLite Essentials - DropPDF1.droppdf.com/files/HDIYy/android-sqlite-essentials...Credits Authors Sunny Kumar Aditya Vikash Kumar Karn Reviewers Amey Haldankar Gaurav Maru Commissioning

  • Upload
    lytu

  • View
    220

  • Download
    0

Embed Size (px)

Citation preview

AndroidSQLiteEssentials

TableofContents

AndroidSQLiteEssentials

Credits

AbouttheAuthors

AbouttheReviewers

www.PacktPub.com

Supportfiles,eBooks,discountoffersandmore

WhySubscribe?

FreeAccessforPacktaccountholders

Preface

Whatthisbookcovers

Whatyouneedforthisbook

Whothisbookisfor

Conventions

Readerfeedback

Customersupport

Downloadingtheexamplecode

Errata

Piracy

Questions

1.EnterSQLite

WhySQLite?

TheSQLitearchitecture

TheSQLiteinterface

TheSQLcompiler

Thevirtualmachine

TheSQLitebackend

Aquickreviewofdatabasefundamentals

WhatisanSQLitestatement?

TheSQLitesyntax

DatatypesinSQLite

Storageclasses

TheBooleandatatype

TheDateandTimedatatype

SQLiteinAndroid

SQLiteversion

Databasepackages

APIs

TheSQLiteOpenHelperclass

TheSQLiteDatabaseclass

ContentValues

Cursor

Summary

2.ConnectingtheDots

Buildingblocks

Adatabasehandlerandqueries

BuildingtheCreatequery

BuildingtheInsertquery

BuildingtheDeletequery

BuildingtheUpdatequery

ConnectingtheUIanddatabase

Summary

3.SharingisCaring

Whatisacontentprovider?

Usingexistingcontentproviders

Whatisacontentresolver?

Creatingacontentprovider

UnderstandingcontentURIs

Declaringourcontractclass

CreatingUriMatcherdefinitions

Implementingthecoremethods

InitializingtheproviderthroughtheonCreate()method

Queryingrecordsthroughthequery()method

Addingrecordsthroughtheinsert()method

Updatingrecordsthroughtheupdate()method

Deletingrecordsthroughthedelete()method

GettingthereturntypeofdatathroughthegetType()method

Addingaprovidertoamanifest

Usingacontentprovider

Summary

4.ThreadCarefully

LoadingdatawithCursorLoader

Loaders

LoaderAPI’ssummary

UsingCursorLoader

Datasecurity

ContentProviderandpermissions

Encryptingcriticaldata

Generaltipsandlibraries

Upgradingadatabase

DatabaseminusSQLstatements

Shippingwithaprepopulateddatabase

Summary

Index

AndroidSQLiteEssentials

AndroidSQLiteEssentialsCopyright©2014PacktPublishing

Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthors,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:August2014

Productionreference:1200814

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

BirminghamB32PB,UK.

ISBN978-1-78328-295-1

www.packtpub.com

CoverimagebyPratyushMohanta(<[email protected]>)

CreditsAuthors

SunnyKumarAditya

VikashKumarKarn

Reviewers

AmeyHaldankar

GauravMaru

CommissioningEditor

PramilaBalan

AcquisitionEditor

NikhilKarkal

ContentDevelopmentEditor

RuchitaBhansali

TechnicalEditors

DennisJohn

GauravThingalaya

CopyEditors

RoshniBanerjee

GladsonMonteiro

AdithiShetty

ProjectCoordinator

KrantiBerde

Proofreaders

SimranBhogal

JoannaMcMahon

Indexers

MariammalChettiyar

RekhaNair

Graphics

RonakDhruv

ProductionCoordinator

SaiprasadKadam

CoverWork

SaiprasadKadam

AbouttheAuthorsSunnyKumarAdityahasbeenworkingontheAndroidplatformforthepast4years.HistrystwithAndroidbeganwithhiscollegeproject,andhecontinuedwithhisworkinR&DatHCLInfosystemsLtd.SunnylovestostayuptodatewiththelatesttrendsandpracticesinAndroiddevelopment.ApartfrombuildingAndroidapplications,hewritesatwww.deadmango.com.HeiscurrentlytheheadofAndroiddevelopmentatYamunix.

IwouldliketothankPacktPublishingforthisopportunityandmyfamilyaswellasfriendsfortheirsupport.

VikashKumarKarnisanIIITAllahabadalumnusandanECEstudentwhoseloveforcodedrovehimtowardsthesoftwaredevelopmentfield.HehasworkedwithleadingmultinationalsandiscurrentlyworkingatSamsungResearchInstitute,Bangalore,exploringAndroid.

VikashlikestolearntheintricaciesoftheAndroidframeworkandhelpnewcomersinthisfield.Someofhisapplications,suchasMovtanFishingandComparePictures,canbefoundonthePlayStore.

Iwouldliketothankmyfriendsandfamilyfortheirsupportduringthecourseofwritingthisbook.

AbouttheReviewersAmeyHaldankarisanAndroidenthusiasthookedontheplatformsinceitsearlydays.EquippedwithadegreeinComputerScienceEngineeringfromGIT,Belgaum,heisworkingforHCLInfosystemsLtd.asaSeniorSoftwareEngineer.

Ameyhasbeenworkingontheplatformforthepast3yearsdevelopingseveralapplicationsformajorclientssuchasDomino’s,Galatsaray,HCL,andNokia.

AnoteofthankstothepublishinghouseforconsideringmefortheroleofareviewerforAndroidSQLiteEssentials.

GauravMaruhasaBachelor’sdegreeinComputersfromShah&AnchorKutchhiEngineeringCollege.Since2011,hehasbeenworkingasanAndroidapplicationdeveloperatvariousorganizations,includingIndia’slargestretailsectorcompany.Gauravhasdevelopedvariousapps,includingtheonedevelopedfortheUSA’slargestbookseller(aFortune500company).Hedrinks,eats,andsleepsAndroid.Youcancontacthimat<[email protected]>.

Iwouldliketothankmyfamily,friends,colleagues,andPacktPublishing,whohelpedmepullthisoneoffsuccessfully.Cheers!

www.PacktPub.com

Supportfiles,eBooks,discountoffersandmoreYoumightwanttovisitwww.PacktPub.comforsupportfilesanddownloadsrelatedtoyourbook.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<[email protected]>formoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

http://PacktLib.PacktPub.com

DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcanaccess,readandsearchacrossPackt’sentirelibraryofbooks.

WhySubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,printandbookmarkcontentOndemandandaccessibleviawebbrowser

FreeAccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandviewnineentirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.

PrefaceAndroidisprobablythebuzzwordofthisdecade.Inashortspan,ithastakenoverthemajorityofthehandsetmarket.Androidisstagedtotakeoverwearables,ourTVrooms,aswellasourcarsthisautumnwiththeAndroidLrelease.WiththefranticpaceatwhichAndroidisgrowing,adeveloperneedstouphisorherskillsetsaswell.Database-orientedapplicationdevelopmentisoneofthekeyskillseverydevelopershouldhave.SQLitedatabaseinapplicationsistheheartofadata-centricproductandkeytobuildinggreatproducts.UnderstandingSQLiteandimplementingtheAndroiddatabasecanbeasteeplearningcurveforsomepeople.Conceptssuchascontentprovidersandloadersaremorecomplextounderstandandimplement.AndroidSQLiteEssentialsequipsdeveloperswithtoolstobuilddatabase-basedAndroidapplicationsinasimplisticmanner.Itiswrittenkeepinginmindthecurrentneedsandbestpracticesbeingfollowedintheindustry.Letusstartourjourney.

WhatthisbookcoversChapter1,EnterSQLite,providesaninsightintoSQLitearchitecture,SQLitebasics,anditsAndroidconnection.

Chapter2,ConnectingtheDots,covershowtoconnectyourdatabasetoAndroidviews.Italsocoverssomeofthebestpracticesoneshouldfollowinordertobuildadatabase-centric/database-enabledapplication.

Chapter3,SharingisCaring,willreflectonhowtoaccessandsharedatainAndroidviacontentprovidersandhowtoconstructacontentprovider.

Chapter4,ThreadCarefully,willguideyouonhowtouseloadersandensuresecurityofdatabaseanddata.ItwillalsoprovideyouwithtipstoexplorealternateapproachestobuildingandusingdatabasesinAndroidapplications.

WhatyouneedforthisbookToefficientlyusethisbook,youwillrequireaworkingsystemwithWindows,Ubuntu,orMacOSpreinstalled.DownloadandsetuptheJavaenvironment;werequirethisfortheIDEofourchoice,Eclipse,torun.DownloadAndroidSDKfromtheAndroiddeveloper’ssiteandAndroidADTpluginforEclipse.Alternatively,youcandownloadtheEclipseADTbundlethatcontainsEclipseSDKandtheADTplugin.YoucanalsotryAndroidStudio;thisIDE,whichjustmovedtobeta,isalsoavailableonthedevelopersite.Makesureyouroperatingsystem,JDK,andIDEareallofeither32bitor64bit.

WhothisbookisforAndroidSQLiteEssentialsisaguidebookforAndroidprogrammerswhowanttoexploreSQLitedatabase-basedAndroidapplications.Thereaderisexpectedtohavealittlebitofhands-onexperienceofAndroidfundamentalbuildingblocksandtheknow-howofIDEandAndroidtools.

ConventionsInthisbook,youwillfindanumberofstylesoftextthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.

Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“ToclosetheCursorobject,theclose()methodcallwillbeused.”

Ablockofcodeissetasfollows:

ContentValuescv=newContentValues();

cv.put(COL_NAME,"johndoe");

cv.put(COL_NUMBER,"12345000");

dataBase.insert(TABLE_CONTACTS,null,cv);

Anycommand-lineinputoroutputiswrittenasfollows:

adbshellSQLite3--version

SQLite3.7.11:API16-19

SQLite3.7.4:API11-15

SQLite3.6.22:API8-10

SQLite3.5.9:API3-7

Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,inmenusordialogboxesforexample,appearinthetextlikethis:“GotoAndroidVirtualDeviceManagerfromtheWindowsmenutostarttheemulator.”

NoteWarningsorimportantnotesappearinaboxlikethis.

TipTipsandtricksappearlikethis.

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedormayhavedisliked.Readerfeedbackisimportantforustodeveloptitlesthatyoureallygetthemostoutof.

Tosendusgeneralfeedback,simplysendane-mailto<[email protected]>,andmentionthebooktitleviathesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideonwww.packtpub.com/authors.

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforallPacktbooksyouhavepurchasedfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyouwouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheerratasubmissionformlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedonourwebsite,oraddedtoanylistofexistingerrata,undertheErratasectionofthattitle.Anyexistingerratacanbeviewedbyselectingyourtitlefromhttp://www.packtpub.com/support.

PiracyPiracyofcopyrightmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.Ifyoucomeacrossanyillegalcopiesofourworks,inanyform,ontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthors,andourabilitytobringyouvaluablecontent.

QuestionsYoucancontactusat<[email protected]>ifyouarehavingaproblemwithanyaspectofthebook,andwewilldoourbesttoaddressit.

Chapter1.EnterSQLiteDr.RichardHipp,thearchitectandprimaryauthorofSQLite,explainshowitallbeganinhisinterviewwithTheGuardianpublishedinJune2007:

“IstartedonMay292000.It’sjustoversevenyearsold,”hesays.Hewasworkingonaprojectwhichusedadatabaseserver,butfromtimetotimethedatabasewentoffline.“Thenmyprogramwouldgiveanerrormessagesayingthatthedatabaseisn’tworking,andIgottheblameforthis.SoIsaid,thisisnotademandingapplicationforthedatabase,whydon’tIjusttalkdirectlytothedisk,andbuildanSQLdatabaseenginethatway?Thatwashowitstarted.”

BeforewebeginourjourneyexploringSQLiteinthecontextofAndroid,wewouldliketoinformyouofsomeprerequisites.Thefollowingareverybasicrequirementsandwillrequirelittleeffortfromyou:

YouneedtoensurethattheenvironmentforbuildingAndroidapplicationsisinplace.Whenwesay“environment,”werefertothecombinationofJDKandEclipse,ourIDEchoice,ADTplugins,andAndroidSDKtools.Incasethesearenotinplace,theADTbundle,whichconsistsofIDE,ADTplugins,AndroidSDKtools,andplatformtools,canbedownloadedfromhttp://developer.android.com/sdk/index.html.Thestepsmentionedinthelinkareprettyself-explanatory.ForJDK,youcanvisitOracle’swebsitetodownloadthelatestversionandsetitupathttp://www.oracle.com/technetwork/java/javase/downloads/index.html.YouneedtohaveabasicknowledgeofAndroidcomponentsandhaverunmorethan“HelloWorld”programsonanAndroidemulator.Ifnot,averyaptguideispresentontheAndroiddevelopersitetosetupanemulator.WewouldsuggestyoubecomefamiliarwithbasicAndroidcomponents:Intent,Service,ContentProviders,andBroadcastReceiver.TheAndroiddevelopersitehasgoodrepositoriesofsamplesalongwithdocumentation.Someoftheseareasfollows:

Emulator:http://developer.android.com/tools/devices/index.htmlAndroidbasics:http://developer.android.com/training/basics/firstapp/index.html

Withthesethingsinplace,wecannowstartourforayintoSQLite.

Inthischapter,wewillcoverthefollowing:

WhySQLite?TheSQLitearchitectureAquickreviewofdatabasefundamentalsSQLiteinAndroid

WhySQLite?SQLiteisanembeddedSQLdatabaseengine.ItisusedbyprominentnamessuchasAdobeinAdobeIntegratedRuntime(AIR);Airbus,intheirflightsoftware;PythonshipswithSQLite;PHP;andmanymore.Inthemobiledomain,SQLiteisaverypopularchoiceacrossvariousplatformsbecauseofitslightweightnature.AppleusesitintheiPhoneandGoogleintheAndroidoperatingsystem.

Itisusedasanapplicationfileformat,adatabaseforelectronicgadgets,adatabaseforwebsites,andasanenterpriseRDBMS.WhatmakesSQLitesuchaninterestingchoicefortheseandmanyothercompanies?Let’stakeacloserlookatthefeaturesofSQLitethatmakeitsopopular:

Zero-configuration:SQLiteisdesignedinsuchamannerthatitrequiresnoconfigurationfile.Itrequiresnoinstallationstepsorinitialsetup;ithasnoserverprocessrunningandnorecoverystepstotakeevenifitcrashes.Thereisnoserveranditisdirectlyembeddedinourapplication.Furthermore,noadministratorisrequiredtocreateormaintainaDBinstance,orsetpermissionsforusers.Inshort,thisisatrueDBA-lessdatabase.No-copyright:SQLite,insteadofalicense,comeswithablessing.ThesourcecodeofSQLiteisinthepublicdomain;youarefreetomodify,distribute,andevensellthecode.Eventhecontributorsareaskedtosignanaffidavittoprotectfromanycopyrightswarfarethatmayoccurinfuture.Cross-platform:Databasefilesfromonesystemcanbemovedtoasystemrunningadifferentarchitecturewithoutanyhassle.Thisispossiblebecausethedatabasefileformatisbinaryandallthemachinesusethesameformat.Inthefollowingchapters,wewillbepullingoutadatabasefromanAndroidemulatortoWindows.Compact:AnSQLitedatabaseisasingleordinarydiskfile;itcomeswithoutaserverandisdesignedtobelightweightandsimple.Theseattributesleadtoaverylightweightdatabaseengine.SQLiteVersion3.7.8hasafootprintoflessthan350KiB(kibibyte)comparedtoitsotherSQLdatabaseengines,whicharemuchlarger.Foolproof:Thecodebaseiswellcommented,easytounderstand,andmodular.ThetestcasesandtestscriptsinSQLitehaveapproximately1084timesmorecodethanthesourcecodeofSQLitelibraryandtheyclaim100percentbranchtestcoverage.ThisleveloftestingreaffirmsthefaithinstilledinSQLitebydevelopers.

NoteInterestedreaderscanreadmoreaboutbranchtestcoveragefromWikipediaathttp://en.wikipedia.org/wiki/Code_coverage.

TheSQLitearchitectureThecore,SQLcompiler,backend,anddatabaseformtheSQLitearchitecture:

TheSQLiteinterfaceAtthetopoftheSQLitelibrarystack,accordingtodocumentation,muchofthepublicinterfacetotheSQLitelibraryisimplementedbythewen.c,legacy.c,andvdbeapi.csourcefiles.Thisisthepointofcommunicationforotherprogramsandscripts.

TheSQLcompilerTokenizerbreakstheSQLstringpassedfromtheinterfaceintotokensandhandsthetokensovertotheparser,onebyone.Tokenizerishand-codedinC.TheparserforSQLiteisgeneratedbytheLemonparsergenerator.ItisfasterthanYACCandBisonand,atthesametime,isthreadsafeandpreventsmemoryleaks.Theparserbuildsaparsetreefromthetokenspassedbythetokenizerandpassesthetreetothecodegenerator.Thegeneratorproducesvirtualmachinecodefromtheinputandpassesittothevirtualmachineasexecutables.MoreinformationabouttheLemonparsergeneratorcanbefoundathttp://en.wikipedia.org/wiki/Lemon_Parser_Generator.

ThevirtualmachineThevirtualmachine,alsoknownasVirtualDatabaseEngine(VDBE),istheheartofSQLite.Itisresponsibleforfetchingandchangingvaluesinthedatabase.Itexecutestheprogramgeneratedbythecodegeneratortomanipulatedatabasefiles.EachSQLstatementisfirstconvertedintovirtualmachinelanguageforVDBE.EachinstructionofVDBEcontainsanopcodeanduptothreeadditionaloperands.

TheSQLitebackendB-trees,alongwithPagerandtheOSInterface,formthebackendoftheSQLitearchitecture.B-treesareusedtoorganizethedata.ThepagerontheotherhandassistsB-treebycaching,modifying,androllingbackdata.B-tree,whenrequired,requestsparticularpagesfromthecache;thisrequestisprocessedbythepagerinanefficientandreliablemanner.TheOSInterface,asthenamesuggests,providesanabstractionlayertoporttodifferentoperatingsystems.IthidestheunnecessarydetailsofcommunicatingwithdifferentoperatingsystemsfromSQLitecallsandhandlesthemonbehalfofSQLite.

ThesearetheinternalsofSQLiteandanapplicationdeveloperinAndroidneednotworryabouttheinternalsofAndroidbecausetheSQLiteAndroidlibrarieshaveeffectivelyusedtheconceptofabstractionandallthecomplexitiesarehidden.OnejustneedstomastertheAPIsprovided,andthatwillcatertoallthepossibleusecasesofSQLiteinanAndroidapplication.

AquickreviewofdatabasefundamentalsAdatabase,insimplewords,isanorganizedwaytostoredatainacontinualfashion.Dataissavedintables.Atableconsistsofcolumnswithdifferentdatatypes.Everyrowinatablecorrespondstoadatarecord.YoumaythinkofatableasanExcelspreadsheet.Fromtheperspectiveofobject-orientedprogramming,everytableinadatabaseusuallydescribesanobject(representedbyaclass).Eachtablecolumnillustratesaclassattribute.Everyrecordinatablerepresentsaparticularinstanceofthatobject.

Let’slookataquickexample.Let’sassumeyouhaveaShopdatabasewithatablecalledInventory.Thistablemightbeusedtostoretheinformationaboutalltheproductsintheshops.TheInventorytablemightcontainthesecolumns:Productname(string),ProductId(number),Cost(number),Instock(0/1),andNumbersavailable(number).YoucouldthenaddarecordtothedatabaseforaproductnamedShoe:

ID Productname ProductId Cost Instock Numbersavailable

1 Carpet 340023 2310 1 4

2 Shoe 231257 235 1 2

Datainthedatabaseissupposedtobecheckedandinfluenced.Thedatawithinatablecanbeasfollows:

Added(withtheINSERTcommand)Modified(withtheUPDATEcommand)Removed(withtheDELETEcommand)

Youmaysearchforparticulardatawithinadatabasebyutilizingwhatisknownasaquery.Aquery(usingtheSELECTcommand)caninvolveonetable,oranumberoftables.Togenerateaquery,youmustdeterminethetables,datacolumns,andvaluesofthedataofinterestusingSQLcommands.EachSQLcommandisconcludedwithasemicolon(;).

WhatisanSQLitestatement?AnSQLitestatementiswritteninSQL,whichisissuedtoadatabasetoretrievedataortocreate,insert,update,ordeletedatainthedatabase.

AllSQLitestatementsstartwithanyofthekeywords:SELECT,INSERT,UPDATE,DELETE,ALTER,DROP,andsoon,andallthestatementsendwithasemicolon(;).Forinstance:

CREATETABLEtable_name(column_nameINTEGER);

TheCREATETABLEcommandisusedtocreateanewtableinanSQLitedatabase.ACREATETABLEcommanddescribesthefollowingattributesofthenewtablethatisbeingcreated:

Thenameofthenewtable.Thedatabaseinwhichthenewtableiscreated.Tablesmaybegeneratedinthemaindatabase,thetempdatabase,orinanydatabaseattached.Thenameofeachcolumninthetable.Thedeclaredtypeofeachcolumninthetable.Adefaultvalueorexpressionforeachcolumninthetable.Adefaultrelationsequencetobeusedwitheachcolumn.Preferably,aPRIMARYKEYforthetable.Thiswillsupportbothsingle-columnandcomposite(multiple-column)primarykeys.AsetofSQLconstraintsforeachtable.ConstraintssuchasUNIQUE,NOTNULL,CHECK,andFOREIGNKEYaresupported.Insomecases,thetablewillbeaWITHOUTROWIDtable.

ThefollowingisasimpleSQLitestatementtocreateatable:

StringdatabaseTable="CREATETABLE"

+TABLE_CONTACTS+"("

+KEY_ID

+"INTEGERPRIMARYKEY,"

+KEY_NAME+"TEXT,"

+KEY_NUMBER+"INTEGER"

+")";

Here,CREATETABLEisthecommandtocreateatablewiththenameTABLE_CONTACTS.KEY_ID,KEY_NAMEandKEY_NUMBERarethecolumnIDs.SQLiterequiresauniqueIDtobeprovidedforeachcolumn.INTEGERandTEXTarethedatatypesassociatedwiththecorrespondingcolumns.SQLiterequiresthetypeofdatatobestoredinacolumntobedefinedatthetimeofcreationofthetable.PRIMARYKEYisthedatacolumnconstraint(rulesenforcedondatacolumnsinthetable).

SQLitesupportsmoreattributesthatcanbeusedforcreatingatable,forinstance,letuscreateacreatetablestatementthatinputsadefaultvalueforemptycolumns.NoticethatforKEY_NAME,weareprovidingadefaultvalueasxyzandfortheKEY_NUMBERcolumn,weareprovidingadefaultvalueof100:

StringdatabaseTable=

"CREATETABLE"

+TABLE_CONTACTS+"("

+KEY_ID+"INTEGERPRIMARYKEY,"

+KEY_NAME+"TEXTDEFAULTxyz,"

+KEY_NUMBER+"INTEGERDEFAULT100"+")";

Here,whenarowisinsertedinthedatabase,thesecolumnswillbepreinitializedwiththedefaultvaluesasdefinedintheCREATESQLstatement.

Therearemorekeywords,butwedon’twantyoutogetboredwithahugelist.Wewillbecoveringotherkeywordsinthesubsequentchapters.

TheSQLitesyntaxSQLitefollowsauniquesetofrulesandguidelinescalledsyntax.

AnimportantpointtobenotedisthatSQLiteiscase-insensitive,buttherearesomecommandsthatarecase-sensitive,forexample,GLOBandglobhavedifferentmeaninginSQLite.LetuslookattheSQLiteDELETEstatement’ssyntaxforinstance.Althoughwehaveusedcapitalletters,replacingthemwithlowercaseletterswillalsoworkfine:

DELETEFROMtableWHERE{condition};

DatatypesinSQLiteSQLiteusesadynamicandweaklytypedSQLsyntax,whereasmostoftheSQLdatabasesusestatic,rigidtyping.Ifwelookatotherlanguages,JavaisastaticallytypedlanguageandPythonisadynamicallytypedlanguage.Sowhatdowemeanwhenwesaydynamicorstatic?Letuslookatanexample:

a=5

a="android"

Instaticallytypedlanguages,thiswillthrowanexception,whereasinadynamicallytypedlanguageitwillwork.InSQLite,thedatatypeofavalueisnotassociatedwithitscontainer,butwiththevalueitself.Thisisnotacauseofconcernwhendealingwithstaticallytypedsystems,whereavalueisdeterminedbyacontainer.ThisisbecauseSQLiteisbackwardscompatiblewiththemorecommonstatictypesystems.Hence,theSQLstatementsthatweuseforstaticsystemscanbeusedseamlesslyhere.

StorageclassesInSQLite,wehavestorageclassesthataremoregeneralthandatatypes.Internally,SQLitestoresdatainfivestorageclassesthatcanalsobereferredtoasprimitivedatatypes:

NULL:Thisrepresentsamissingvaluefromthedatabase.INTEGER:Thissupportsarangeofsignedintegersfrom1,2,3,4,6,or8bytesdependingonthemagnitudeofthevalue.SQLitehandlesthisautomaticallybasedonthevalue.Atthetimeofprocessinginthememory,theyareconvertedtothemostgeneral8-bytesignedintegerform.REAL:Thisisafloatingpointvalue,andSQLiteusesthisasan8-byteIEEEfloatingpointnumbertostoresuchvalues.TEXT:SQLitesupportsvariouscharacterencodings,suchasUTF-8,UTF-16BE,orUTF-16LE.Thisvalueisatextstring.BLOB:Thistypestoresalargearrayofbinarydata,exactlyhowitwasprovidedasinput.

SQLiteitselfdoesnotvalidateifthetypeswrittentothecolumnsareactuallyofthedefinedtype,forexample,youcanwriteanintegerintoastringcolumnandviceversa.Wecanevenhaveasinglecolumnwithdifferentstorageclasses:

idcol_t

------------

123

2NULL

3test

TheBooleandatatypeSQLitedoesnothaveaseparatestorageclassforBooleanandusestheIntegerclassforthispurpose.Integer0representsthefalsestatewhereas1representsatruestate.ThismeansthatthereisanindirectsupportforBooleanandwecancreateBooleantype

columnsonly.Thecatchis,itwon’tcontainthefamiliarTRUE/FALSEvalues.

TheDateandTimedatatypeAswesawfortheBooleandatatype,thereisnostorageclassfortheDateandTimedatatypesinSQLite.SQLitehasfivebuilt-indateandtimefunctionstohelpuswithit;wecanusedateandtimeasinteger,text,orrealvalues.Moreover,thevaluesareinterchangeable,dependingontheneedoftheapplication.Forexample,tocomputethecurrentdate,usethefollowingcode:

SELECTdate('now');

SQLiteinAndroidTheAndroidsoftwarestackconsistsofcoreLinuxkernel,Androidruntime,AndroidlibrariesthatsupporttheAndroidframework,andfinallyAndroidapplicationsthatrunontopofeverything.TheAndroidruntimeusesDalvikvirtualmachine(DVM)toexecutethedexcode.InnewerversionsofAndroid,thatis,fromKitKat(4.4),AndroidhasenabledanexperimentalfeatureknownasART,whichwilleventuallyreplaceDVM.ItisbasedonAheadofTime(AOT),whereasDVMisbasedonJustinTime(JIT).Inthefollowingdiagram,wecanseethatSQLiteprovidesnativedatabasesupportandispartofthelibrariesthatsupporttheapplicationframeworkalongwithlibrariessuchasSSL,OpenGLES,WebKit,andsoon.Theselibraries,writteninC/C++,runovertheLinuxkerneland,alongwiththeAndroidruntime,formsthebackboneoftheapplicationframework,asshowninthefollowingdiagram:

BeforewestartexploringSQLiteinAndroid,let’stakealookattheotherpersistentstoragealternativesinAndroid:

Sharedpreference:Dataisstoredinasharedpreferenceinthekey-valueform.ThefileitselfisanXMLfilecontainingthekey-valuepairs.Thefileispresentinthe

internalstorageofanapplication,andaccesstoitcanbepublicorprivateasneeded.AndroidprovidesAPIstowriteandreadsharedpreferences.Itisadvisedtousethisincasewehavetosaveasmallcollectionofsuchdata.AgeneralexamplewouldbesavingthelastreadpositioninaPDF,orsavingauser’spreferencetoshowaratingbox.Internal/externalstorage:Thisterminologycanbealittlemisleading;Androiddefinestwostoragespacestosavefiles.Onsomedevices,youmighthaveanexternalstoragedeviceinformofanSDcard,whereasonothers,youwillfindthatthesystemhaspartitioneditsmemoryintotwoparts,tobelabeledasinternalandexternal.PathstotheexternalaswellasinternalstoragecanbefetchedbyusingAndroidAPIs.Internalstorage,bydefault,islimitedandaccessibleonlytotheapplication,whereastheexternalstoragemayormaynotbeavailableincaseitismounted.

Tipandroid:installLocationcanbeusedinthemanifesttospecifytheinternal/externalinstallationlocationofanapplication.

SQLiteversionSinceAPIlevel1,AndroidshipswithSQLite.Atthetimeofwritingthisbook,thecurrentversionofSQLitewas3.8.4.1.Accordingtothedocumentation,theversionofSQLiteis3.4.0,butdifferentAndroidversionsareknowntoshipwithdifferentversionsofSQLite.WecaneasilyverifythisviatheuseofatoolcalledSQLite3presentintheplatform-toolsfolderinsidetheAndroidSDKinstallationfolderandAndroidEmulator:

adbshellSQLite3--version

SQLite3.7.11:API16-19

SQLite3.7.4:API11-15

SQLite3.6.22:API8-10

SQLite3.5.9:API3-7

WeneednotworryaboutthedifferentversionsofSQLiteandshouldstickto3.5.9forcompatibility,orwecangobythesayingthatAPI14isthenewminSdkVersionandswitchitwith3.7.4.Untilandunlessyouhavesomethingveryspecifictoaparticularversion,itwillhardlymatter.

NoteSomeadditionalhandySQLite3commandsareasfollows:

.dump:Toprintoutthecontentsofatable

.schema:ToprinttheSQLCREATEstatementforanexistingtable

.help:Forinstructions

DatabasepackagesTheandroid.databasepackagecontainsallthenecessaryclassesforworkingwithdatabases.Theandroid.database.SQLitepackagecontainstheSQLite-specificclasses.

APIsAndroidprovidesvariousAPIstoenableustocreate,access,modify,anddeleteadatabase.Thecompletelistcanbequiteoverwhelming;forthesakeofbrevity,wewillcoverthemostimportantandusedones.

TheSQLiteOpenHelperclassTheSQLiteOpenHelperclassisthefirstandmostessentialclassofAndroidtoworkwithSQLitedatabases;itispresentintheandroid.database.SQLitenamespace.SQLiteOpenHelperisahelperclassthatisdesignedforextensionandtoimplementthetasksandactionsyoudeemimportantwhencreating,opening,andusingadatabase.ThishelperclassisprovidedbytheAndroidframeworktoworkwiththeSQLitedatabaseandhelpsinmanagingthedatabasecreationandversionmanagement.Themodusoperandiwouldbetoextendtheclassandimplementtasksandactionsasrequiredbyourapplication.SQLiteOpenHelperhasconstructorsdefinedasfollows:

SQLiteOpenHelper(Contextcontext,Stringname,SQLiteDatabase.CursorFactory

factory,intversion)

SQLiteOpenHelper(Contextcontext,Stringname,SQLiteDatabase.CursorFactory

factory,intversion,DatabaseErrorHandlererrorHandler)

Theapplicationcontextpermitsaccesstoallthesharedresourcesandassetsfortheapplication.ThenameparameterconsistsofthedatabasefilenameintheAndroidstorage.SQLiteDatabase.CursorFactoryisafactoryclassthatcreatescursorobjectsthatactastheoutputsetforallthequeriesyouapplyagainstSQLiteunderAndroid.Theapplication-specificversionnumberforthedatabasewillbetheversionparameter(ormoreparticularly,itsschema).

TheconstructorofSQLiteOpenHelperisusedtocreateahelperobjecttocreate,open,ormanageadatabase.Thecontextistheapplicationcontextthatallowsaccesstoallthesharedresourcesandassets.Thenameparametereithercontainsthenameofadatabaseornullforanin-memorydatabase.TheSQLiteDatabase.CursorFactoryfactorycreatesacursorobjectthatactsastheresultsetforallthequeries.Theversionparameterdefinestheversionnumberofthedatabaseandisusedtoupgrade/downgradethedatabase.TheerrorHandlerparameterinthesecondconstructorisusedwhenSQLitereportsdatabasecorruption.

SQLiteOpenHelperwilltriggeritsonUpgrade()methodifourdatabaseversionnumberisnotatdefault1.ImportantmethodsoftheSQLiteOpenHelperclassareasfollows:

synchronizedvoidclose()

synchronizedSQLiteDatabasegetReadableDatabase()

synchronizedSQLiteDatabasegetWritableDatabase()

abstractvoidonCreate(SQLiteDatabasedb)

voidonOpen(SQLiteDatabasedb)

abstractvoidonUpgrade(SQLiteDatabasedb,intoldVersion,int

newVersion)

Thesynchronizedclose()methodclosesanyopendatabaseobject.Thesynchronizedkeywordpreventsthreadandmemoryconsistencyerrors.

Thenexttwomethods,getReadableDatabase()andgetWriteableDatabase(),arethemethodsinwhichthedatabaseisactuallycreatedoropened.BothreturnthesameSQLiteDatabaseobject;thedifferenceliesinthefactthatgetReadableDatabase()willreturnareadabledatabaseincaseitcannotreturnawritabledatabase,whereasgetWriteableDatabase()returnsawritabledatabaseobject.ThegetWriteableDatabase()methodwillthrowanSQLiteExceptionifadatabasecannotbeopenedforwriting.IncaseofgetReadableDatabase(),ifadatabasecannotbeopened,itwillthrowthesameexception.

WecanusetheisReadOnly()methodoftheSQLiteDatabaseclassonthedatabaseobjecttoknowthestateofthedatabase.Itreturnstrueforread-onlydatabases.

CallingeithermethodswillinvoketheonCreate()methodifthedatabasedoesn’texistyet.Otherwise,itwillinvoketheonOpen()oronUpgrade()methods,dependingontheversionnumber.TheonOpen()methodshouldchecktheisReadOnly()methodbeforeupdatingthedatabase.Onceopened,thedatabaseiscachedtoimproveperformance.Finally,weneedtocalltheclose()methodtoclosethedatabaseobject.

TheonCreate(),onOpen(),andonUpgrade()methodsaredesignedforthesubclasstoimplementtheintendedbehavior.TheonCreate()methodiscalledwhenthedatabaseiscreatedforthefirsttime.ThisistheplacewherewecreateourtablesbyusingSQLitestatements,whichwesawearlierintheexample.TheonOpen()methodistriggeredwhenthedatabasehasbeenconfiguredandafterthedatabaseschemahasbeencreated,upgraded,ordowngradedasnecessary.Read/writestatusshouldbecheckedherewiththehelpoftheisReadOnly()method.

TheonUpgrade()methodiscalledwhenthedatabaseneedstobeupgradeddependingontheversionnumbersuppliedtoit.Bydefault,thedatabaseversionis1,andasweincrementthedatabaseversionnumbersandreleasenewversions,theupgradewillbeperformed.

AsimpleexampleillustratingtheuseoftheSQLiteOpenHelperclassispresentinthecodebundleforthischapter;wewouldbeusingitforexplanation:

classSQLiteHelperClass

{

...

...

publicstaticfinalintVERSION_NUMBER=1;

sqlHelper=

newSQLiteOpenHelper(context,"ContactDatabase",null,

VERSION_NUMBER)

{

@Override

publicvoidonUpgrade(SQLiteDatabasedb,

intoldVersion,intnewVersion)

{

//droptableonupgrade

db.execSQL("DROPTABLEIFEXISTS"

+TABLE_CONTACTS);

//Createtablesagain

onCreate(db);

}

@Override

publicvoidonCreate(SQLiteDatabasedb)

{

//creatingtableduringonCreate

StringcreateContactsTable=

"CREATETABLE"

+TABLE_CONTACTS+"("

+KEY_ID+"INTEGERPRIMARYKEY,"

+KEY_NAME+"TEXT,"

+KEY_NUMBER+"INTEGER"+")";

try{

db.execSQL(createContactsTable);

}catch(SQLExceptione){

e.printStackTrace();

}

}

@Override

publicsynchronizedvoidclose()

{

super.close();

Log.d("TAG","Databaseclosed");

}

@Override

publicvoidonOpen(SQLiteDatabasedb)

{

super.onOpen(db);

Log.d("TAG","Databaseopened");

}

};

...

...

//openthedatabaseinread-onlymode

SQLiteDatabasedb=SQLiteOpenHelper.getWritableDatabase();

...

...

//openthedatabaseinread/writemode

SQLiteDatabasedb=SQLiteOpenHelper.getWritableDatabase();

TipDownloadingtheexamplecode

YoucandownloadtheexamplecodefilesforallPacktbooksyouhavepurchasedfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

TheSQLiteDatabaseclassNowthatyouarefamiliarwiththehelperclassthatkick-startstheuseofSQLitedatabaseswithinAndroid,it’stimetolookatthecoreSQLiteDatabaseclass.SQLiteDatabaseisthebaseclassrequiredtoworkwithanSQLitedatabaseinAndroidandprovidesmethodstoopen,query,update,andclosethedatabase.

Morethan50methodsareavailablefortheSQLiteDatabaseclass,eachwithitsownnuancesandusecases.Ratherthananexhaustivelist,we’llcoverthemostimportantsubsetsofmethodsandallowyoutoexploresomeoftheoverloadedmethodsatyourleisure.Atanytime,youcanrefertothefullonlineAndroiddocumentationfortheSQLiteDatabaseclassathttp://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html.

SomemethodsoftheSQLiteDatabaseclassareshowninthefollowinglist:

publiclonginsert(Stringtable,StringnullColumnHack,ContentValues

values)

publicCursorquery(Stringtable,String[]columns,Stringselection,

String[]selectionArgs,StringgroupBy,Stringhaving,StringorderBy)

publicCursorrawQuery(Stringsql,String[]selectionArgs)

publicintdelete(Stringtable,StringwhereClause,String[]

whereArgs)

publicintupdate(Stringtable,ContentValuesvalues,String

whereClause,String[]whereArgs)

LetusseetheseSQLiteDatabaseclassesinactionwithanexample.Wewillinsertanameandnumberinourtable.Thenwewillusetherawquerytofetchdatabackfromthetable.Afterthis,wewillgothroughthedelete()andupdate()methods,bothofwhichwilltakeidasaparametertoidentifywhichrowofdatainourdatabasetableweintendtodeleteorupdate:

publicvoidinsertToSimpleDataBase()

{

SQLiteDatabasedb=sqlHelper.getWritableDatabase();

ContentValuescv=newContentValues();

cv.put(KEY_NAME,"John");

cv.put(KEY_NUMBER,"0000000000");

//Insertingvaluesindifferentcolumnsofthetableusing

//ContentValues

db.insert(TABLE_CONTACTS,null,cv);

cv=newContentValues();

cv.put(KEY_NAME,"Tom");

cv.put(KEY_NUMBER,"5555555");

//Insertingvaluesindifferentcolumnsofthetableusing

//ContentValues

db.insert(TABLE_CONTACTS,null,cv);

}

...

...

publicvoidgetDataFromDatabase()

{

intcount;

db=sqlHelper.getReadableDatabase();

//Useofnormalquerytofetchdata

Cursorcr=db.query(TABLE_CONTACTS,null,null,

null,null,null,null);

if(cr!=null){

count=cr.getCount();

Log.d("DATABASE","countis:"+count);

}

//Useofrawquerytofetchdata

cr=db.rawQuery("select*from"+TABLE_CONTACTS,null);

if(cr!=null){

count=cr.getCount();

Log.d("DATABASE","countis:"+count);

}

}

...

...

publicvoiddelete(Stringname)

{

StringwhereClause=KEY_NAME+"=?";

String[]whereArgs=newString[]{name};

db=sqlHelper.getWritableDatabase();

introwsDeleted=db.delete(TABLE_CONTACTS,whereClause,whereArgs);

}

...

...

publicvoidupdate(Stringname)

{

StringwhereClause=KEY_NAME+"=?";

String[]whereArgs=newString[]{name};

ContentValuescv=newContentValues();

cv.put(KEY_NAME,"Betty");

cv.put(KEY_NUMBER,"999000");

db=sqlHelper.getWritableDatabase();

introwsUpdated=db.update(TABLE_CONTACTS,cv,whereClause,

whereArgs);

}

ContentValuesContentValuesisessentiallyasetofkey-valuepairs,wherethekeyrepresentsthecolumnforthetableandthevalueisthevaluetobeinsertedinthatcolumn.So,inthecaseofvalues.put("COL_1",1);,thecolumnisCOL_1andthevaluebeinginsertedforthatcolumnis1.

Thefollowingisanexample:

ContentValuescv=newContentValues();

cv.put(COL_NAME,"johndoe");

cv.put(COL_NUMBER,"12345000");

dataBase.insert(TABLE_CONTACTS,null,cv);

CursorAqueryrecoversaCursorobject.ACursorobjectdepictstheresultofaqueryandfundamentallypointstoonerowoftheresultofthequery.Withthismethod,Androidcanbuffertheresultsofthequeryinaproductivemanner;asitdoesn’tneedtoloadallofthedataintomemory.

Toobtaintheelementsoftheresultingquery,youcanusethegetCount()method.

Tonavigateamidindividualdatarows,youcanutilizethemoveToFirst()andmoveToNext()methods.TheisAfterLast()methodpermitsyoutoanalyzewhethertheendoftheoutputhasarrived.

TheCursorobjectprovidestypedget*()methods,forexample,thegetLong(columnIndex)andgetString(columnIndex)methodstogainentrytothecolumndatafortheongoingpositionoftheresult.columnIndexisthenumberofthecolumnyouwillbeaccessing.

TheCursorobjectalsoprovidesthegetColumnIndexOrThrow(String)methodthatpermitsyoutogetthecolumnindexforacolumnnameofthetable.

ToclosetheCursorobject,theclose()methodcallwillbeused.

Adatabasequeryreturnsacursor.Thisinterfaceprovidesrandomread-writeaccesstotheresultset.ItpointstoarowofthequeryresultthatenablesAndroidtobuffertheresultseffectivelysincenowitisnotrequiredtoloadallthedatainthememory.

Thepointerofthereturnedcursorpointstothe0thlocation,whichisknownasthefirstlocationofthecursor.WeneedtocallthemoveToFirst()methodontheCursorobject;ittakesthecursorpointertothefirstlocation.Nowwecanaccessthedatapresentinthefirstrecord.

Cursorimplementations,iffrommultiplethreads,shouldperformtheirownsynchronizationwhenusingthecursor.Acursorneedstobeclosedtofreetheresourcethe

objectholdsbycallingtheclose()method.

Someothersupportmethodswewillencounterareasfollows:

ThegetCount()method:Thisreturnsthenumbersofelementsintheresultingquery.Theget*()methods:Theseareusedtoaccessthecolumndataforthecurrentpositionoftheresult,forexample,getLong(columnIndex)andgetString(columnIndex).ThemoveToNext()method:Thismovesthecursortothenextrow.Ifthecursorisalreadypastthelastentryintheresultset,itwillreturnfalse.

SummaryWecoveredinthischaptertheknow-howofSQLitefeaturesanditsinternalarchitecture.WestartedwithadiscussiononwhatmakesSQLitesopopularbylookingatitssalientfeatures,thenwecoveredtheunderlyingarchitectureofSQLiteandwentoverdatabasefundamentalssuchassyntaxanddatatypes,andfinallymovedontoSQLiteinAndroid.WeexploredtheAndroidAPIsforusingSQLiteinAndroid.

Inthenextchapter,wewillfocusoncarryingforwardwhatwehavelearnedinthischapterandapplyittobuildAndroidapplications.WewillfocusontheUIelementsandconnectingUItothedatabasecomponents.

Chapter2.ConnectingtheDots “Youdon’tunderstandanythinguntilyoulearnitmorethanoneway.”

—-MarvinMinsky

Inthepreviouschapter,welearnedthetwoimportantAndroidclassesandtheircorrespondingmethodsinordertoworkwithanSQLitedatabase:

TheSQLiteOpenHelperclassTheSQLiteDatabaseclass

Wealsosawcodesnippetsexplainingtheirimplementation.Now,wearereadytousealltheseconceptsinanAndroidapplication.Wewillbeleveragingwhatwelearnedinthepreviouschaptertomakeafunctionalapplication.WewillfurtherlookintotheSQLstatementstoinsert,query,anddeletedatafromadatabase.

Inthischapter,wewillbebuildingandrunninganAndroidapplicationonanAndroidemulator.Wewillalsobebuildingourownfull-fledgedcontactsdatabase.WewillencounterAndroidUIcomponents,suchasButtonsandListView,whileprogressingthroughthischapter.IncasearevisitofUIcomponentsinAndroidisrequired,pleasevisitthelinkhttp://developer.android.com/design/building-blocks/index.html.

Beforewebegin,thecodeinthischapterismeanttoexplaintheconceptsrelatedtoanSQLitedatabaseinAndroidandisnotproductionready;inalotofplaces,youwillfindlackofproperexceptionhandlingorlackofpropernullchecksandsimilarpracticestoreduceverbosityinthecode.YoucandownloadthecompletecodefromPackt’swebsiteforthecurrentandfollowingchapters.Forbestresults,werecommenddownloadingthecodeandreferringtoitaswemovealongthechapter.

Inthischapter,wewillcover:

BuildingblocksDatabasehandlerandqueriesConnectingtheUIanddatabase

BuildingblocksAndroidisknowntorunonavarietyofdeviceswithdifferenthardwareandsoftwarespecifications.Atthetimeofwritingthisbook,1billionactivationmarkshavebeencrossed.ThenumberofdevicesrunningAndroidisstaggering,providinguserswitharichvarietyofoptionsindifferentformfactorsandondifferenthardwarebases.Thisaddsaroadblockwhenitcomestotestingyourapplicationondifferentdevices,becauseitishumanlyimpossibletogetholdofthemall,nottoforgetthetimeandcapitalneededtobeinvestedinit.Emulatorinitselfisagreattool;itenablesustocircumventthisproblembygivingustheflexibilitytomimicdifferenthardwarefeatures,suchasCPUarchitecture,RAM,andcamera,anddifferentsoftwareversionsrangingfromearlyCupcaketoKitKat.Wewillalsotrytoleveragethistoouradvantageinourprojectandtrytorunourapplicationontheemulator.Anaddedbenefitofusingtheemulatoristhatwewillberunningarooteddevicethatwillallowustoperformsomeactions.Wewillnotbeabletoachievetheseactionsonanormaldevice.

Let’sstartbysettingupanemulatorinEclipse:

1. GotoAndroidVirtualDeviceManagerfromtheWindowmenutostarttheemulator.

WecansetdifferenthardwarepropertiessuchastheCPUtype,front/backcamera,RAMpreferablylessthan768MBonaWindowsmachine,internal,andexternalstoragesize.

2. Whilelaunchingtheapp,enableSavetosnapshot;thiswillreducethelaunchtimethenexttimewearelaunchinganemulatorinstancefromthesnapshot:

NoteInterestedreaderswhowanttotryoutafasteremulatorcangiveGenymotionatryathttp://www.genymotion.co.

Let’sstartbuildingourAndroidapplicationnow.

3. WewillstartbycreatinganewprojectPersonalContactManager.GotoFile|New|Project.Now,navigatetoAndroidandthenselectAndroidApplicationProject.ThisstepwillgiveusanactivityfileandacorrespondingXMLfile.

Wewillcomebacktothesecomponentsafterwehavealltheblocksweneedinplace.For

ourapplication,wewillcreateadatabasecalledcontact,whichwillcontainonetable,ContactsTable.Inthepreviouschapter,wewentoverhowtocreateadatabaseusingaSQLstatement;let’sconstructadatabaseschemaforourproject.Thisisaveryimportantstepthatisbasedonourapplication’srequirements;forexample,inourcase,wearebuildingapersonalcontactmanagerandwillrequirefieldssuchasname,number,e-mail,andadisplaypicture.

ThedatabaseschemaforContactsTableisoutlined:

Column Datatype

Contact_ID Integer/primarykey/autoincrement

Name Text

Number Text

Email Text

Photo Blob

NoteAnAndroidapplicationcanhavemorethanonedatabaseandeachdatabasecanhavemorethanonetable.Eachtablestoresdatainthe2D(rowsandcolumns)format.

ThefirstcolumnisContact_ID.Itsdatatypeisintegeranditscolumnconstraintistheprimarykey.Also,thecolumnisautoincremented,whichmeansforeachrowitwillbeincrementedbyonewhendataisinsertedinthatrow.

Theprimarykeyuniquelyidentifieseachrowandcannotbenull.Eachtableinadatabasecanhaveoneprimarykeyatthemost.Theprimarykeyofonetablecanactastheforeignkeyforanothertable.Theforeignkeyservesasaconnectionbetweentworelatedtables;forinstance,ourcurrentContactsTableschemais:

ContactsTable(Contact_ID,Name,Number,Email,Photo)

Let’ssaywehaveanothertableColleagueTablewiththefollowingschema:

ColleagueTable(Colleague_ID,Contact_ID,Position,Fax)

Here,theprimarykeyofContactTable,thatis,Contact_IDcanbetermedasaforeignkeyforColleagueTable.ItservesthepurposeoflinkingtwotablesinarelationaldatabaseandhenceallowsustoperformoperationsonColleagueTable.Wewillexplorethisconceptindetailinthechaptersandexamplesahead.

NoteColumnconstraint

Constraintsaretherulesenforcedondatacolumnsinatable.Thisensurestheaccuracyandreliabilityofdatainthedatabase.

UnlikemostSQLdatabases,SQLitedoesnotrestrictthetypeofdatathatmaybeinsertedintoacolumnbasedonthedeclaredtypeofcolumns.Instead,SQLiteusesdynamictyping.Thedeclaredtypeofacolumnisusedtodeterminetheaffinityofthecolumnonly.Thereisatypeconversionalso(automatically)whenonetypeofvariableisstoredintheother.

Constraintscanbecolumnlevelortablelevel.Column-levelconstraintsareappliedonlytoonecolumn,whereastable-levelconstraintsareappliedtothewholetable.

ThefollowingarethecommonlyusedconstraintsandkeywordsavailableinSQLite:

TheNOTNULLconstraint:ThisensuresthatacolumndoesnothaveaNULLvalue.TheDEFAULTconstraint:Thisprovidesadefaultvalueforacolumnwhennoneisspecified.TheUNIQUEconstraint:Thisensuresthatallthevaluesinacolumnaredifferent.ThePRIMARYkey:Thisuniquelyidentifiesallrows/recordsinadatabasetable.TheCHECKconstraint:TheCHECKconstraintensuresthatallthevaluesinacolumnsatisfycertainconditions.TheAUTOINCREMENTkeyword:AUTOINCREMENTisakeywordusedtoautoincrementavalueofafieldinthetable.WecanautoincrementafieldvaluebyusingtheAUTOINCREMENTkeywordwhencreatingatablewithaspecificcolumnnametoautoincrementit.ThekeywordAUTOINCREMENTcanbeusedwiththeINTEGERfieldonly.

Thenextstepistoprepareourdatamodel;wewilluseourschematoframethedatamodelclass.TheContactModelclasswillhaveContact_ID,Name,Number,Email,andPhotoasfields,theyarerepresentedasid,name,contactNo,email,andbyteArrayrespectively.Theclasswillconsistofagetter/settermethodtosetandfetchpropertyvaluesasneeded.Theuseofadatamodelwillfacilitateinthecommunicationoftheactivityusedtoshow/processdataandourdatabasehandler,whichwearegoingtodefinelaterinthischapter.WewillcreateanewpackageandanewclassinitcalledtheContactModelclass.Pleasenotethatcreatinganewpackageisnotanecessarystep;itisusedtoorganizeourclassesinalogicalandeasilyaccessiblemanner.Thisclasscanbedescribedasfollows:

publicclassContactModel{

privateintid;

privateStringname,contactNo,email;

privatebyte[]byteArray;

publicbyte[]getPhoto(){

returnbyteArray;

}

publicvoidsetPhoto(byte[]array){

byteArray=array;

}

publicintgetId(){

returnid;

}

publicvoidsetId(intid){

this.id=id;

}

……………

}

TipEclipseprovidesalotofhelpfulshortcutsbutnotforgeneratinggetterandsettermethods.Wecanbindgeneratinggetterandsettermethodstoanykeybindingasperourliking.InEclipse,gotoWindow|Preferences|General|Keys,searchforgetter,andaddyourbindings.WeareusingAlt+Shift+G;youarefreetosetanyotherkeycombination.

AdatabasehandlerandqueriesWewillbuildoursupportclassthatwillcontainmethodstoread,update,anddeletedataasperourdatabaserequirements.Thisclasswillenableustocreateandupdatethedatabaseandwillactasourhubfordatamanagement.WewillusethisclasstorunSQLitequeriesandsendacrossdatatotheUI;inourcase,alistviewtodisplaytheresults:

publicclassDatabaseManager{

privateSQLiteDatabasedb;

privatestaticfinalStringDB_NAME="contact";

privatestaticfinalintDB_VERSION=1;

privatestaticfinalStringTABLE_NAME="contact_table";

privatestaticfinalStringTABLE_ROW_ID="_id";

privatestaticfinalStringTABLE_ROW_NAME="contact_name";

privatestaticfinalStringTABLE_ROW_PHONENUM="contact_number";

privatestaticfinalStringTABLE_ROW_EMAIL="contact_email";

privatestaticfinalStringTABLE_ROW_PHOTOID="photo_id";

.........

}

WewillcreateanobjectoftheSQLiteDatabaseclass,whichwewillinitializelaterwitheithergetWritableDatabase()orgetReadableDatabase().Wewilldefinetheconstantsthatwewillbeusingthroughtheclass.

NoteByconvention,constantsaredefinedincapitalsbutuseofstaticfinalindefiningaconstantisbitmorethantheconvention.Toknowmore,refertohttp://goo.gl/t0PoQj.

Wewilldefinethenameofourdatabaseascontactanddefinetheversionas1.Ifwelookbacktothepreviouschapter,wewillrecalltheimportanceofthisvalue.Aquickrecapofthisenablesustoupgradethedatabasefromthecurrentversiontothenewversion.Theusecasewillbecomeclearwiththisexample.Let’ssayinfuturethereisanewrequirement,thatis,weneedtoaddafaxnumbertoourcontactdetails.Wewillmodifyourcurrentschematoincorporatethischangeandourcontactdatabasewillcorrespondinglychange.Ifweareinstallingtheapplicationonnewdevices,therewillbenoissue;butincaseofadevicewherewealreadyhavearunninginstanceoftheapplication,wewillfaceproblems.Inthissituation,DB_VERSIONwillcomeinhandyandhelpusreplacetheoldversionofthedatabasewiththecurrentversion.Anotherapproachwouldbetouninstalltheapplicationandinstallitagain,butthatisnotencouraged.

Thetablenameandimportantfieldssuchastablecolumnswillbedefinednow.TABLE_ROW_IDisaveryimportantcolumn.Thiswillserveastheprimarykeyforthetable;itwillalsoautoincrementandcannotbenull.NOTNULLisagainacolumnconstraint,whichmayonlybeattachedtoacolumndefinitionandisnotspecifiedasatableconstraint.Notsurprisingly,aNOTNULLconstraintdictatesthattheassociatedcolumnmaynotcontainaNULLvalue.AttemptingtosetthecolumnvaluetoNULLwheninsertinganewroworupdatinganexistingone,causesaconstraintviolation.Thiswillbeusedtofinda

particularvalueinthetable.TheuniquenessoftheIDguaranteesthatwedonothaveanyconflictswithdatainthetable,sinceeachrowisuniquelyidentifiedbythekey.Therestofthetablecolumnsareprettyself-explanatory.TheconstructorfortheDatabaseManagerclassisasfollows:

publicDatabaseManager(Contextcontext){

this.context=context;

CustomSQLiteOpenHelperhelper=newCustomSQLiteOpenHelper(context);

this.db=helper.getWritableDatabase();

}

NoticethatweareusingaclasscalledCustomSQLiteOpenHelper.Wewillcomebacktothislater.WewillusetheclassobjecttogetourSQLitedatabaseinstance.

BuildingtheCreatequeryTocreateatablewiththedesiredcolumns,wewillbuildaquerystatementandexecuteit.Thestatementwillcontainthetablename,differenttablecolumns,andrespectivedatatype.Wewillnowlookatmethodsforcreatinganewdatabaseandalsoupgradinganexistingdatabaseaccordingtotheneedsoftheapplication:

privateclassCustomSQLiteOpenHelperextendsSQLiteOpenHelper{

publicCustomSQLiteOpenHelper(Contextcontext){

super(context,DB_NAME,null,DB_VERSION);

}

@Override

publicvoidonCreate(SQLiteDatabasedb){

StringnewTableQueryString="createtable"

+TABLE_NAME+"("

+TABLE_ROW_ID

+"integerprimarykeyautoincrementnotnull,"

+TABLE_ROW_NAME

+"textnotnull,"

+TABLE_ROW_PHONENUM

+"textnotnull,"

+TABLE_ROW_EMAIL

+"textnotnull,"

+TABLE_ROW_PHOTOID

+"BLOB"+");";

db.execSQL(newTableQueryString);

}

@Override

publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,

intnewVersion){

StringDROP_TABLE="DROPTABLEIFEXISTS"+

TABLE_NAME;

db.execSQL(DROP_TABLE);

onCreate(db);

}

}

CustomSQLiteOpenHelperextendsSQLiteOpenHelperandprovidesuswiththekeymethodsonCreate()andonUpgrade().WehavedefinedthisclassastheinnerclassofourDatabaseManagerclass.Thisenablesustomanageallthedatabase-relatedfunctions,namelyCRUD(Create,Read,Update,andDelete),fromoneplace.

InourCustomSQLiteOpenHelperconstructor,whichisresponsibleforcreatinganinstanceofourclass,wewillpassacontext,whichinturnwillbepassedtothesuperconstructorwiththefollowingparameters:

Contextcontext:ThisisthecontextwepassedtoourconstructorStringname:ThisisthenameofourdatabaseCursorFactoryfactory:Thisisthecursorfactoryobject,whichcanbepassedasnull

intversion:Thisisthedatabaseversionofthedatabase

ThenextimportantmethodisonCreate().WewillbuildourSQLitequerystring,whichwillcreateourdatabasetable:

"createtable"+TABLE_NAME+"("

+TABLE_ROW_ID

+"integerprimarykeyautoincrementnotnull,"

….....

+TABLE_ROW_PHOTOID+"BLOB"+");";

Thepreviousstatementisbasedonthefollowingsyntaxdiagram:

Here,thekeywordcreatetableisusedtocreateatable.Thisisfollowedbythetablename,thedeclarationofcolumns,andtheirdatatype.AfterpreparingourSQLstatement,wewillexecuteitusingtheexecSQL()methodoftheSQLitedatabase.Incasesomethingiswrongwiththequerystatementthatwebuiltearlier,wewillencountertheexception,android.database.sqlite.SQLiteException.Bydefault,thedatabaseisformedintheinternalmemoryspaceallocatedtotheapplication.Thefoldercanbefoundat/data/data/<yourpackage>/databases/.

Wecaneasilyverifywhetherourdatabaseisformedwhilerunningthispieceofcodeonanemulatororarootedphone.InEclipse,gototheDDMSperspectiveandthengotothefilemanager.Wecaneasilynavigatetothegivenfolderifwehavesufficientpermission,thatis,arooteddevice.Wecanalsopullupourdatabasewiththehelpofthefileexplorer,andwiththehelpofastandaloneSQLitemanagertool,wecanviewourdatabaseandperformCRUDoperationsonitaswell.WhatmakestheAndroidapplication’sdatabasereadablethroughanothertool?Rememberhowwediscussedcross-platforminSQLitefeaturesinthelastchapter?Inthefollowingscreenshot,noticethetablename,theSQLstatementusedtobuildit,andthecolumnnamesalongwiththeirdatatype:

NoteTheSQLiteManagertoolcanbedownloadedeitherintheChromeorFirefoxbrowser.ThefollowingisthelinkforFirefoxextension:http://goo.gl/NLu8JT.

Anotherhandywayofpullingupourdatabaseoranyotherfileisbyusingtheadbpullcommand:

adbpull/data/data/yourpackagename/databases/filelocation

AnotherinterestingpointtonoteisthatthedatatypeofTABLE_ROW_PHOTOIDisBLOB.BLOBstandsforbinarylargeobject.Itisdifferentfromotherdatatype,suchastextandinteger,asitcanstorebinarydata.Thebinarydatacanbeanimage,audio,oranyothertypeofmultimediaobject.

Itisnotadvisabletostorelargeimagesinadatabase;wecanstorefilenamesorlocations,butstoringimagesisbitofoverkill.Imagineasituationlikethiswherewestorecontactimages.Toamplifythissituation,insteadofafewhundredcontacts,makeitafewthousandcontacts.Thesizeofthedatabasewillbecomelargeandtheaccesstimewillalsoincrease.WewanttodemonstratetheuseofBLOBsbystoringcontactimages.

TheonUpgrade()methodiscalledwhenthedatabaseisupgraded.Thedatabaseisupgradedbychangingtheversionnumberofthedatabase.Here,theimplementationdependsontheneedoftheapplication.Insomecases,thewholetablemayhavetobedeletedandanewonemayneedtobecreated,andinsomeapplications,onlyslightmodificationisneeded.HowtomigratefromoneversiontoanotheriscoveredinChapter4,ThreadCarefully.

BuildingtheInsertqueryToinsertanewrowofdatainthedatabasetable,weneedtouseeithertheinsert()methodorwecanmakeaninsertquerystatementandusetheexecute()method:

publicvoidaddRow(ContactModelcontactObj){

ContentValuesvalues=prepareData(contactObj);

try{

db.insert(TABLE_NAME,null,values);

}catch(Exceptione){

Log.e("DBERROR",e.toString());

e.printStackTrace();

}

}

Incaseourtablenameiswrong,SQLitewillgivealognosuchtablemessageandtheexception,android.database.sqlite.SQLiteException.TheaddRow()methodisusedtoinsertcontactdetailsinthedatabaserow;noticethattheparameterofthemethodisanobjectofContactModel.WehavecreatedanadditionalmethodprepareData()toconstructaContentValuesobjectfromtheContactModelobject’sgettermethods:

.......................

values.put(TABLE_ROW_NAME,contactObj.getName());

values.put(TABLE_ROW_PHONENUM,contactObj.getContactNo());

....................

AfterthepreparationoftheContentValuesobject,wearegoingtousetheinsert()methodoftheSQLiteDatabaseclass:

publiclonginsert(Stringtable,StringnullColumnHack,ContentValues

values)

Theparametersoftheinsert()methodareasfollows:

table:Thedatabasetabletoinserttherowinto.values:Thiskey-valuemapcontainstheinitialcolumnvaluesforthetablerow.Columnnamesactaskeys.Valuesasthecolumnvalues.nullColumnHack:Thisisasinterestingasitsname.Here’saquotefromtheAndroiddocumentationwebsite:

“optional;maybenull.SQLdoesn’tallowinsertingacompletelyemptyrowwithoutnamingatleastonecolumnname.Ifyourprovidedvaluesareempty,nocolumnnamesareknownandanemptyrowcan’tbeinserted.Ifnotsettonull,thenullColumnHackparameterprovidesthenameofnullablecolumnnametoexplicitlyinsertNULLintothecasewhereyourvaluesareempty.”

Inshort,incaseswherewearetryingtopassanemptyContentValuestobeinserted,SQLiteneedssomecolumnthatissafetobeassignedNULL.

Alternatively,insteadoftheinsert()method,wecanpreparetheSQLstatementandexecuteitasshown:

publicvoidaddRowAlternative(ContactModelcontactObj){

StringinsertStatment="INSERTINTO"+TABLE_NAME

+"("

+TABLE_ROW_NAME+","

+TABLE_ROW_PHONENUM+","

+TABLE_ROW_EMAIL+","

+TABLE_ROW_PHOTOID

+")"

+"VALUES"

+"(?,?,?,?)";

SQLiteStatements=db.compileStatement(insertStatment);

s.bindString(1,contactObj.getName());

s.bindString(2,contactObj.getContactNo());

s.bindString(3,contactObj.getEmail());

if(contactObj.getPhoto()!=null)

{s.bindBlob(4,contactObj.getPhoto());}

s.execute();

}

Wewillbecoveringalternativesforalotofthemethodswementionedhere.Theideaistomakeyoucomfortablewithotherpossiblewaystobuildandexecutequeries.Theexplanationofthealternativepartisleftasanexerciseforyou.ThegetRowAsObject()methodwillreturnthefetchedrowfromthedatabaseintheformofaContactModelobject,asshowninthefollowingcode.ItwillrequirerowIDasaparametertouniquelyidentifywhichrowinthetablewewanttoaccess:

publicContactModelgetRowAsObject(introwID){

ContactModelrowContactObj=newContactModel();

Cursorcursor;

try{

cursor=db.query(TABLE_NAME,newString[]{

TABLE_ROW_ID,TABLE_ROW_NAME,TABLE_ROW_PHONENUM,TABLE_ROW_EMAIL,

TABLE_ROW_PHOTOID},

TABLE_ROW_ID+"="+rowID,null,

null,null,null,null);

cursor.moveToFirst();

if(!cursor.isAfterLast()){

prepareSendObject(rowContactObj,cursor);}

}catch(SQLExceptione){

Log.e("DBERROR",e.toString());

e.printStackTrace();

}

returnrowContactObj;

}

ThismethodwillreturnthefetchedrowfromthedatabaseintheformofaContactModelobject.WeareusingtheSQLiteDatabase()querymethodtofetchtherowfromourcontacttableagainsttheprovidedrowIDparameter.Themethodreturnsacursorovertheresultset:

publicCursorquery(Stringtable,String[]columns,Stringselection,

String[]selectionArgs,StringgroupBy,Stringhaving,StringorderBy,

Stringlimit)

Thefollowingaretheparametersofthepreviouscode:

table:Thisdenotesthedatabasetableagainstwhichthequerywillberun.columns:Thisisalistofthecolumnsthatarereturned;ifwepassnull,itwillreturnallthecolumns.selection:ThisiswherewedefinewhichrowsaretobereturnedandframedasaSQLWHEREclause.Passingnullwillreturnalltherows.selectionArgs:Wecanpassnullforthisparameterorwemayincludequestionmarksintheselection,whichwillbereplacedbythevaluesfromselectionArgs.groupBy:ThisisafilterframedasaSQLGROUPBYclausedeclaringhowtogrouprows.Passingnullwillcausetherowstonotbegrouped.Having:Thisisafilterthattellswhichrowgroupsaretobemadepartofthecursor,framedasaSQLHAVINGclause.Passingnullwillcausealltherowgroupstobeincluded.OrderBy:ThistellsthequeryhowtoordertherowsframedasanSQLORDERBYclause.Passingnullwillusethedefaultsortorder.limit:ThiswilllimitthenumberofrowsreturnedbythequeryframedastheLIMITclause.PassingnulldenotesanoLIMITclause.

Anotherimportantconcepthereismovingthecursoraroundtoaccessdata.Noticethefollowingmethods:cursor.moveToFirst(),cursor.isAfterLast(),andcursor.moveToNext().

Whenwetrytoretrievedata-buildingSQLquerystatements,thedatabasewillfirstcreateanobjectofthecursorobjectandreturnitsreference.Thepointerofthisreturnedreferenceispointingtothe0thlocation,whichisalsoknownas“beforefirstlocation”ofthecursor.Whenwewanttoretrievedata,wehavetofirstmovetothefirstrecord;hence,theuseofcursor.moveToFirst().Talkingabouttherestofthetwomethods,cursor.isAfterLast()returnswhetherthecursorispointingtothepositionafterthelastrowandcursor.moveToNext()movesthecursortothenextrow.

TipReadersareadvisedtogothroughmoreofthecursormethodsattheAndroiddevelopersite:http://goo.gl/fR75t8.

Alternatively,wecanusethefollowingmethod:

publicContactModelgetRowAsObjectAlternative(introwID){

ContactModelrowContactObj=newContactModel();

Cursorcursor;

try{

StringqueryStatement="SELECT*FROM"

+TABLE_NAME+"WHERE"+TABLE_ROW_ID+"=?";

cursor=db.rawQuery(queryStatement,

newString[]{String.valueOf(rowID)});

cursor.moveToFirst();

rowContactObj=newContactModel();

rowContactObj.setId(cursor.getInt(0));

prepareSendObject(rowContactObj,cursor);

}catch(SQLExceptione){

Log.e("DBERROR",e.toString());

e.printStackTrace();

}

returnrowContactObj;

}

Theupdatestatementisbasedonthefollowingsyntaxdiagram:

Beforewemovetoothermethodsinthedatamanagerclass,let’shavealookatfetchingdatafromacursorobjectintheprepareSendObject()method:

rowObj.setContactNo(cursor.getString(cursor.getColumnIndexOrThrow(TABLE_ROW

_PHONENUM)));

rowObj.setEmail(cursor.getString(cursor.getColumnIndexOrThrow(TABLE_ROW_EMA

IL)));

Herecursor.getstring()takesthecolumnindexasaparameterandreturnsthevalueoftherequestedcolumn,whereascursor.getColumnIndexOrThrow()takesthecolumnnameasaparameterandreturnsthezero-basedindexforthegivencolumnname.Instead

ofthischainingapproach,wecandirectlyusecursor.getstring().Ifweknowthecolumnnumberoftherequiredcolumntofetchdatafrom,wecanusethefollowingnotation:

cursor.getstring(2);

BuildingtheDeletequeryTodeleteaparticularrowofdatafromourdatabasetable,weneedtoprovidetheprimarykeytouniquelyidentifythedatasettoberemoved:

publicvoiddeleteRow(introwID){

try{

db.delete(TABLE_NAME,TABLE_ROW_ID

+"="+rowID,null);

}catch(Exceptione){

Log.e("DBERROR",e.toString());

e.printStackTrace();

}

}

ThismethodusestheSQLiteDatabasedelete()methodtodeletetherowofthegivenIDinthetable:

publicintdelete(Stringtable,StringwhereClause,String[]whereArgs)

Thefollowingaretheparametersoftheprecedingcodesnippet:

table:ThisisthedatabasetableagainstwhichthequerywillberunwhereClause:Thisisaclausetobeappliedwhendeletingarow;passingnullinthisclausewilldeletealltherowswhereArgs:Wemayincludequestionmarksinthewhereclause,whichwillbereplacedbythevaluesthatwillbeboundasstrings

Alternatively,wecanusethefollowingmethod:

publicvoiddeleteRowAlternative(introwId){

StringdeleteStatement="DELETEFROM"

+TABLE_NAME+"WHERE"

+TABLE_ROW_ID+"=?";

SQLiteStatements=db.compileStatement(deleteStatement);

s.bindLong(1,rowId);

s.executeUpdateDelete();

}

Thedeletestatementisbasedonthefollowingsyntaxdiagram:

BuildingtheUpdatequeryToupdateanexistingvalue,weneedtousetheupdate()methodwiththerequiredparameters:

publicvoidupdateRow(introwId,ContactModelcontactObj){

ContentValuesvalues=prepareData(contactObj);

StringwhereClause=TABLE_ROW_ID+"=?";

StringwhereArgs[]=newString[]{String.valueOf(rowId)};

db.update(TABLE_NAME,values,whereClause,whereArgs);

}

Generally,weneedtheprimarykey,inourcasetherowIdparameter,toidentifytherowtobemodified.AnSQLiteDatabaseupdate()methodisusedtomodifytheexistingdataofzeroormorerowsinadatabasetable:

publicintupdate(Stringtable,ContentValuesvalues,StringwhereClause,

String[]whereArgs)

Thefollowingaretheparametersoftheprecedingcodesnippet:

table:Thisisthequalifieddatabasetablenametobeupdated.values:Thisisamappingfromthecolumnnamestothenewcolumnvalues.whereClause:ThisistheoptionalWHEREclausetobeappliedwhenupdatingavalue/row.IftheUPDATEstatementdoesnothaveaWHEREclause,alltherowsinthetablearemodified.whereArgs:Wemayincludequestionmarksinthewhereclause,whichwillbereplacedbythevaluesthatwillbeboundasstrings.

Alternatively,youcanusethefollowingcode:

publicvoidupdateRowAlternative(introwId,ContactModelcontactObj){

StringupdateStatement="UPDATE"+TABLE_NAME+"SET"

+TABLE_ROW_NAME+"=?,"

+TABLE_ROW_PHONENUM+"=?,"

+TABLE_ROW_EMAIL+"=?,"

+TABLE_ROW_PHOTOID+"=?"

+"WHERE"+TABLE_ROW_ID+"=?";

SQLiteStatements=db.compileStatement(updateStatement);

s.bindString(1,contactObj.getName());

s.bindString(2,contactObj.getContactNo());

s.bindString(3,contactObj.getEmail());

if(contactObj.getPhoto()!=null)

{s.bindBlob(4,contactObj.getPhoto());}

s.bindLong(5,rowId);

s.executeUpdateDelete();

}

Theupdatestatementisbasedonthefollowingsyntaxdiagram:

ConnectingtheUIanddatabaseNowthatwehaveourdatabasehooksinplace,let’sconnectourUIwiththedata:

1. Thefirststepwouldbetogetthedatafromtheuser.WecanusetheexistingcontactdatafromtheAndroid’scontactapplicationbymeansofthecontentprovider.

Wewillbecoveringthisapproachinthenextchapter.Fornow,wewillbeaskingtheusertoaddanewcontact,whichwewillinsertintothedatabase:

2. WeareusingstandardAndroidUIwidgets,suchasEditText,TextView,andButtonstocollectthedataprovidedbytheuser:

privatevoidprepareSendData(){

if(TextUtils.isEmpty(contactName.getText().toString())

||TextUtils.isEmpty(

contactPhone.getText().toString())){

.............

}else{

ContactModelcontact=newContactModel();

contact.setName(contactName.getText().toString());

............

DatabaseManagerdm=newDatabaseManager(this);

if(reqType==ContactsMainActivity

.CONTACT_UPDATE_REQ_CODE){

dm.updateRowAlternative(rowId,contact);

}else{

dm.addRowAlternative(contact);

}

setResult(RESULT_OK);

finish();

}

}

prepareSendData()isthemethodthatisresponsibleforbundlingdataintoourobjectmodelandlaterinsertingitinourdatabase.NoticethatinsteadofusingnullcheckandlengthcheckoncontactName,weareusingTextUtils.isEmpty(),whichisaveryhandymethod.Thisreturnstrueifthestringisnullorofzerolength.

3. WeprepareourContactModelobjectfromthedatareceivedbytheuserfillingtheform.WecreateaninstanceofourDatabaseManagerclassandaccessouraddRow()methodpassingourcontactobjecttobeinsertedinthedatabase,aswediscussedearlier.

AnotherimportantmethodisgetBlob(),whichisusedtogettheimagedataintheBLOBformat:

privatebyte[]getBlob(){

ByteArrayOutputStreamblob=newByteArrayOutputStream();

imageBitmap.compress(Bitmap.CompressFormat.JPEG,100,blob);

byte[]byteArray=blob.toByteArray();

returnbyteArray;

}

4. WecreateanewByteArrayOutputStreamobjectblob.Bitmap’scompress()methodwillbeusedtowriteacompressedversionofthebitmaptoouroutputstreamobject:

publicbooleancompress(Bitmap.CompressFormatformat,intquality,

OutputStreamstream)

Thefollowingaretheparametersoftheprecedingcode:

format:Thisistheformatofacompressedimage,inourcase,JPEG.quality:Thisisahinttothecompressor,whichrangesfrom0to100.Thevalue0meanstocompresstoasmallersizeandlowquality,while100isfor

maximumquality.stream:Thisistheoutputstreamtowritethecompresseddatato.

5. Then,wecreateourbyte[]object,whichwillbeconstructedfromtheByteArrayOutputStreamtoByteArray()method.

NoteYouwillnoticethatwearenotcoveringallthemethods;onlythosethatarerelevanttodataoperationsandsomemethodsorcallsthatmightcauseconfusion.Thereareafewmoremethodsthatareusedtoinvokethecameraorgallerytopickaphototobeusedasthecontactimage.Youareadvisedtoexplorethemethodsinthecodeprovidedalongwiththebook.

Let’smoveontothepresentationpartwhereweuseacustomlistviewtodisplayourcontactinformationinapresentableandreadablemanner.Wearegoingtoskipabulkofthecoderelatedtothepresentationandconcentrateonthepartswherewefetchandprovidedatatoourlistview.Wewillalsoimplementacontextmenuinordertoprovideauserwiththefunctionalityofdeletingaparticularcontact.WewillbetouchingbaseonthedatabasemanagermethodssuchasgetAllData()tofetchallouraddedcontacts.WewillusedeleteRow()inordertoremoveanyunwantedcontactsfromourcontactsdatabase.Thefinaloutcomewillbesomethinglikethefollowingscreenshot:

6. Tomakeacustomlistviewsimilartotheoneshownintheprecedingscreenshot,wecreateCustomListAdapterextendingBaseAdapterandusingthecustomlayoutforthelistviewrows.NoticeinthefollowingconstructorwehaveinitializedanewarraylistandwilluseourdatabasemanagertofetchvaluesbyusingthegetAllData()methodtofetchallthedatabaseentries:

publicCustomListAdapter(Contextcontext){

contactModelList=newArrayList<ContactModel>();

_context=context;

inflater=(LayoutInflater)context.getSystemService(

Context.LAYOUT_INFLATER_SERVICE);

dm=newDatabaseManager(_context);

contactModelList=dm.getAllData();

}

AnotherveryimportantmethodisthegetView()method.Thisiswhereweinflateourcustomlayoutinaview:

convertView=inflater.inflate(R.layout.contact_list_row,null);

Wewillusetheviewholderpatterntoimprovethelistviewscrollingsmoothness:

vHolder=(ViewHolder)convertView.getTag();

7. Andfinally,setthedatatothecorrespondingviews:

vHolder.contact_email.setText(contactObj.getEmail());

NoteHoldingviewobjectsinaviewholderimprovestheperformancebyreducingcallstofindViewById().Youcanreadmoreaboutthisandhowtomakelistviewscrollingsmoothathttp://developer.android.com/training/improving-layouts/smooth-scrolling.html.

8. Wewillalsobeimplementingawaytodeletealistviewentry.Wewillusethecontextmenuforthispurpose.Wewillfirstcreateamenuiteminthemenufolderunderresofourapplicationstructure:

<?xmlversion="1.0"encoding="utf-8"?>

<menuxmlns:android="http://schemas.android.com/apk/res/android">

<item

android:id="@+id/delete_item"

android:title="Delete"/>

<item

android:id="@+id/update_item"

android:title="Update"/>

</menu>

9. Now,inourmainactivitywherewewilldisplayourlistview,wewillusethefollowingcalltoregisterourlistviewwiththecontextmenu.Inordertolaunchthecontextmenu,weneedtoperformalongpressactiononthelistviewitem:

registerForContextMenu(listReminder)

10. Thereareafewmoremethodsthatweneedtoimplementinordertoachievethedeletefunctionality:

@Override

publicvoidonCreateContextMenu(ContextMenumenu,Viewv,

ContextMenuInfomenuInfo){

super.onCreateContextMenu(menu,v,menuInfo);

MenuInflaterm=getMenuInflater();

m.inflate(R.menu.del_menu,menu);

}

ThismethodisusedtoinflatethecontextmenuwiththemenuwedefinedearlierinXML.TheMenuInfaterclassgeneratesmenuobjectsfromthemenuXMLfiles.MenuinflationreliesheavilyonthepreprocessingofXMLfilesthatisdoneatbuildtime;thisisdonetoimproveperformance.

11. Now,wewillimplementamethodtocapturetheclickonthecontextmenu:

@Override

publicbooleanonContextItemSelected(MenuItemitem){

..............

caseR.id.delete_item:

cAdapter.delRow(info.position);

cAdapter.notifyDataSetChanged();

returntrue;

caseR.id.update_item:

Intentintent=newIntent(

ContactsMainActivity.this,AddNewContactActivity.class);

......................

}

12. Here,wewillfindthepositionIDoftheclickedlistviewitemandinvokethedelRow()methodoftheCustomListAdapter,andintheend,wewillnotifytheadapterthatthedatasethaschanged:

publicvoiddelRow(intdelPosition){

dm.deleteRowAlternative(contactModelList.get(delPosition).getId());

contactModelList.remove(delPosition);

ThedelRow()methodisresponsibleforconnectingourdatabase’sdeleteRowAlternative()methodtoourcontextmenu’sdelete()method.Here,wefetchtheIDoftheobjectsetontheparticularlistviewitemandpassittothedeleteRowAlternative()methodofdatabaseManagerinordertodeletethedatafromthedatabase.Afterremovingthedatafromthedatabase,wewillinstructourlistviewtoremovethecorrespondingentryfromourcontactlist.

IntheonContextItemSelected()method,wecanalsoseetheupdate_itemincasetheuserhasclickedontheupdatebutton.Wewilllaunchtheactivitytoaddanewcontact

andaddthedatawealreadyhaveincasetheuserwantstoeditsomefields.Thecatchistoknowfromwherethecallhasbeeninitiated.Isittoaddanewentryorupdateanexistingone?Wetakethehelpofthefollowingcodetotelltheactivitythatthisactionisusedtoupdateratherthanaddanewentry:

intent.putExtra(REQ_TYPE,CONTACT_UPDATE_REQ_CODE);

SummaryInthischapter,wecoveredthestepsofbuildingupadatabase-basedapplication,fromscratchandthenfromschematoobjectmodelandthenfromobjectmodeltobuildingactualdatabases.WeunderwenttheprocessofbuildingourdatabasemanagerandfinallyimplementedtheUIdatabaseconnecttoachieveafullyfunctionalapplication.Thetopicscoveredrangedfromthebuildingblocksofthemodelclass,databaseschematoourdatabasehandler,andCRUDmethods.WealsocoveredtheimportantconceptofconnectingadatabasetotheAndroidviewswithproperhooksinplacetopickupuserdata,adddatatothedatabase,andshowrelevantinformationafterpickingupdatafromthedatabase.

Inthenextchapter,wewillfocusonbuildinguponthegroundworkwehavedonehere.WewillexploreContentProviders.WewillalsolearnhowtofetchdatafromContentProviders,howtomakeourowncontentprovider,thebestpracticesassociatedwhilebuildingthem,andmuchmore.

Chapter3.SharingisCaring “Datareallypowerseverythingthatwedo.”

—–JeffWeiner,LinkedIn

Inthelastchapter,westartedprogrammingourveryowncontactmanager.Wecameacrossvariousbuildingblocksofadatabase-centricapplication;wecovereddatabasehandlersandbuildingqueriesinordertogetmeaningfuldatafromourdatabase.WealsoexploredhowtomakeaconnectionbetweenourUIanddatabaseandpresentitinaconsumablemannerfortheenduser.

Inthischapter,wewilllearnhowtoaccessotherapplication’sdataviameansofcontentproviders.Wewillalsolearnhowtobuildourveryowncontentproviderinordertoshareourdatawithotherapplications.WewilllookintoAndroid’sproviderssuchascontactprovider.Towrapthingsup,wewillconstructatestapplicationtouseournewlyconstructedcontentprovider.

Inthischapter,wewillcoverthefollowingtopics:

Whatisacontentprovider?CreatingacontentproviderImplementingthecoremethodsUsingacontentprovider

Whatisacontentprovider?AcontentprovideristhefourthcomponentofanAndroidapplication.Itisusedtomanageaccesstoastructuredsetofdata.Contentprovidersencapsulatethedata,andprovideabstractionandthemechanismtodefinedatasecurity.However,contentprovidersareprimarilyintendedtobeusedbyotherapplicationsthataccesstheproviderusingaprovider’sclientobject.Together,providersandproviderclientsofferaconsistent,standardinterfacefordata,whichalsohandlesinterprocesscommunicationandsecuredataaccess.

Acontentproviderallowsoneapptosharedatawithotherapplications.Bydesign,anAndroidSQLitedatabasecreatedbyanapplicationisprivatetotheapplication;itisexcellentifyouconsiderthesecuritypointofview,buttroublesomewhenyouwanttosharedataacrossdifferentapplications.Thisiswhereacontentprovidercomestotherescue;youcaneasilysharedatabybuildingyourcontentprovider.Itisimportanttonotethatalthoughourdiscussionwillfocusonadatabase,acontentproviderisnotlimitedtoit.Itcanalsobeusedtoservefiledatathatnormallygoesintofiles,suchasphotos,audio,orvideos:

Intheprecedingdiagram,noticehowtheinteractionbetweenApplicationsAandBhappenswhileexchangingdata.Here,wehaveanApplicationAwhoseactivityneedstoaccessthedatabaseofApplicationB.Aswehavealreadyseen,thedatabaseofApplicationBisstoredintheinternalmemoryandcannotbedirectlyaccessedbyApplicationA.ThisiswhereContentProvidercomesintothepicture;itallowsustosharedataandmodifyaccesstootherapplications.Thecontentproviderimplementsmethodstoquery,insert,update,anddeletedataindatabases.ApplicationAnowrequeststhecontentprovidertoperformsomedesiredoperationsonbehalfofit.Wewillexplorebothsidesofthecoin,butwewillfirstuseContentProvidertofetchcontactsfromaphone’scontactdatabase,andthenwewillbuildourveryowncontentproviderforothers

topickdatafromourdatabase.

UsingexistingcontentprovidersAndroidlistsalotofstandardcontentprovidersthatwecanuse.SomeofthemareBrowser,CalendarContract,CallLog,Contacts,ContactsContract,MediaStore,userDictionary,andsoon.

Inourcurrentcontactmanagerapplication,wewilladdanewfeature.IntheUIoftheAddNewContactActivityclass,wewilladdasmallbuttontofetchcontactsfromaphone’scontactlistwithhelpfromthesystem’sexistingContentProviderandContentResolverproviders.WewillbeusingtheContactsContractproviderforthispurpose.

Whatisacontentresolver?TheContentResolverobjectintheapplication’scontextisusedtocommunicatewiththeproviderasaclient.TheContentResolverobjectcommunicateswiththeproviderobject—aninstanceofaclassthatimplementsContentProvider.Theproviderobjectreceivesdatarequestsfromclients,performstherequestedaction,andreturnstheresults.

ContentResolverisasingle,globalinstanceinourapplicationthatprovidesaccesstootherapplication’scontentproviders;wedonotneedtoworryabouthandlinginterprocesscommunication.TheContentResolvermethodsprovidethebasicCRUD(create,retrieve,update,anddelete)functionsofpersistentstorage;ithasmethodsthatcallidenticallynamedmethodsintheproviderobjectbutdoesnotknowtheimplementation.WewillcoverContentResolverinmoredetailasweprogressthroughthischapter.

Intheprecedingscreenshot,noticethenewiconontheright-handsidetoaddcontactsdirectlyfromthephonecontacts;wemodifiedtheexistingXMLtoaddtheicon.ThecorrespondingclassAddNewContactActivitywillalsobemodified:

publicvoidpickContact(){

try{

IntentcIntent=newIntent(Intent.ACTION_PICK,

ContactsContract.Contacts.CONTENT_URI);

startActivityForResult(cIntent,PICK_CONTACT);

}catch(Exceptione){

e.printStackTrace();

Log.i(TAG,"Exceptionwhilepickingcontact");

}

}

WeaddedanewmethodpickContact()toprepareanintentinordertopickcontacts.Intent.ACTION_PICKallowsustopickanitemfromadatasource;inaddition,allweneedtoknowistheUniformResourceIdentifier(URI)oftheprovider,whichinourcaseisContactsContract.Contacts.CONTENT_URI.ThisfunctionalityisalsoprovidedbyMessaging,Gallery,andContacts.IfyoulookintothecodefromChapter2,ConnectingtheDots,youwillfindwehaveusedthesamecodetopickimagesfromGallery.TheContactsscreenwillpopupallowingustobrowseorsearchforcontactswerequiretomigratetoournewapplication.NoticeonActivityResult,thatis,ournextstopwewillmodifythismethodtohandleourcorrespondingrequesttohandlecontacts.LetuslookatthecodewehavetoaddtopickcontactsfromanAndroid’scontactprovider:

{

.

.

.

elseif(requestCode==PICK_CONTACT){

if(resultCode==Activity.RESULT_OK)

{

UricontactData=data.getData();

Cursorc=getContentResolver().query(contactData,null,null,

null,null);

if(c.moveToFirst()){

Stringid=c

.getString(c

.getColumnIndexOrThrow(ContactsContract.Contacts._ID));

StringhasPhone=c

.getString(c

.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));

if(hasPhone.equalsIgnoreCase("1")){

Cursorphones=getContentResolver()

.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,

null,

ContactsContract.CommonDataKinds.Phone.CONTACT_ID

+"="+id,null,null);

phones.moveToFirst();

contactPhone.setText(phones.getString(phones

.getColumnIndex("data1")));

contactName

.setText(phones.getString(phones

.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)));

}

…..

TipToaddalittleflairtoyourapplication,downloadtheentiresetofstencils,sources,theactionbariconpack,colorswatches,andtheRobotofontfamilyfromtheAndroiddevelopersite,http://goo.gl/4Msuct.DesigningafunctionalapplicationisincompletewithoutaconsistentUIthatfollowsAndroidguidelines.

Westartbycheckingwhethertherequestcodematchesours.Then,wecross-checkresultcode.WegettheContentResolverobjectbymakingacalltogetcontentresolverontheContextobject;itisamethodoftheandroid.content.Contextclass.AsweareinanactivitythatinheritsfromContext,wedonotneedtobeexplicitinmakingacalltoit.Thesamegoesforservices.Wewillnowverifywhetherthecontactwepickedhasaphonenumberornot.Afterverifyingthenecessarydetails,wepullthedatathatwerequire,suchascontactnameandphonenumber,andsettheminrelevantfields.

CreatingacontentproviderAcontentproviderprovidesaccesstodataintwoways:oneisstructureddatathatgoesintheformofadatabase,astheexampleweareworkingoncurrently,orintheformoffiledata,thatis,datathatgoesintheformofpictures,audio,video,andsoonstoredintheprivatespaceoftheapplication.Beforewebegindiggingintohowtocreateacontentprovider,weshouldalsoretrospectwhetherweneedone.Ifwewanttoofferdatatootherapplications,allowuserstocopydatafromourapptoanother,orusethesearchframeworkinourapplication,thentheanswerisyes.

JustlikeotherAndroidcomponents(Activity,Service,orBroadcastReceiver),acontentprovideriscreatedbyextendingtheContentProviderclass.SinceContentProviderisanabstractclass,wehavetoimplementthesixabstractmethods.Thesemethodsareasfollows:

Method Usage

voidonCreate() Initializestheprovider

StringgetType(Uri)ReturnstheMIMEtypeofdatainthecontentprovider

intdelete(Uriuri,Stringselection,String[]selectionArgs)Deletesdatafromthecontentprovider

Uriinsert(Uriuri,ContentValuesvalues)Insertsnewdataintothecontentprovider

Cursorquery(Uriuri,String[]projection,Stringselection,

String[]selectionArgs,StringsortOrder)Returnsdatatothecaller

intupdate(Uriuri,ContentValuesvalues,Stringselection,String[]

selectionArgs)

Updatestheexistingdatainthecontentprovider

Thesemethodswillbedealtwithinmoredetaillaterasweprogressthroughthechapterandbuildourapplication.

UnderstandingcontentURIsEverydataaccessmethodofContentProviderhasacontentURI,asanargumentthatallowsittodeterminethetable,row,orfiletoaccess.Itgenerallyfollowsthefollowingstructure:

content://authority/Path/Id

Let’sanalyzethebreakdownofthecomponentsofthecontent://URI.Theschemeforcontentprovidersisalwayscontent.Thecolonanddouble-slash(://)actasaseparatorfromtheauthoritypart.Then,wehavetheauthoritypart.Byrule,authoritieshavetobeuniqueforeverycontentprovider.ThenamingconventiontheAndroiddocumentationrecommendsusingisthefullyqualifiedclassnameofyourcontentprovidersubclass.Generally,itisbuiltasapackagenameplusaqualifierforeachcontentproviderwepublish.

Theremainingpartisoptional,alsoreferredtoaspath,andisusedforsegregationbetweendifferenttypesofdataourcontentprovidercanprovide.AverygoodexampleistheMediaStoreproviderwhichneedstodistinguishbetweenaudio,video,andimagefiles.

Anotheroptionalpartisid,whichpointstoaspecificrecord;dependingonwhetheridispresentornot,theURIbecomesID-basedordirectory-based,respectively.AnotherwaytounderstanditwouldbethatanID-basedURIenablesustointeractwithdataindividuallyatrowlevel,whereasadirectory-basedURIenablesustointeractwithmultiplerowsofadatabase.

Forexample,considercontent://com.personalcontactmanager.provider/contacts;wewillencounterthissoonenoughaswemoveaheadwiththechapterwherewedefinehowtoaccessthecontentproviderwearecurrentlybuilding.

NoteOnasidenote,thepackagenameforapplicationsshouldalwaysbeunique;thisisbecausealltheapplicationsonPlayStoreareidentifiedbytheirpackagename.AlltheupdatesforanapplicationonPlayStoreneedtohavethesamepackagenameandbesignedwiththesamekeystoreusedinitially.Forinstance,thefollowingisthePlayStorelinkofaGmailapplication;noticethatattheendofURL,wewillfindthepackagenameoftheapplication:

play.google.com/store/apps/details?id=com.google.android.gm

DeclaringourcontractclassDeclaringacontractisaveryimportantpartofbuildingourcontentprovider.Thisclass,asthenamesuggests,willactasacontractbetweenourcontentproviderandtheapplicationthatisgoingtoaccessourcontentprovider.Itisapublicfinalclass,whichcontainsconstantdefinitionsforURIs,columnnames,andothermetadata.ItcanalsocontainJavadoc,butthebiggestadvantageisthatthedeveloperusingitneednotworryaboutthenamesoftables,columns,andconstants,leadingtolesserror-pronecode.

Thecontractclassprovidesuswiththenecessaryabstraction;wecanchangetheunderlyingoperationsasandwhenrequiredandwecanalsochangethecorrespondingdatamanipulationaffectingotherdependentapplications.Animportantthingtonoteisthatweneedtobecarefulwhilechangingthecontractinfuture;ifwearenotcareful,wemightbreaktheotherapplicationsthatareusingourcontractclass.

Ourcontractclasslookslikethefollowing:

publicfinalclassPersonalContactContract{

/**

*TheauthorityofthePersonalContactProvider

*/

publicstaticfinalStringAUTHORITY=

"com.personalcontactmanager.provider";

publicstaticfinalStringBASE_PATH="contacts";

/**

*TheUriforthetop-levelPersonalContactProvider

*authority

*/

publicstaticfinalUriCONTENT_URI=Uri.parse("content://"+AUTHORITY

+"/"+BASE_PATH);

/**

*Themimetypeofadirectoryofitems.

*/

publicstaticfinalStringCONTENT_TYPE=

ContentResolver.CURSOR_DIR_BASE_TYPE+

"/vnd.com.personalcontactmanager.provider.table";

/**

*Themimetypeofasingleitem.

*/

publicstaticfinalStringCONTENT_ITEM_TYPE=

ContentResolver.CURSOR_ITEM_BASE_TYPE+

"/vnd.com.personalcontactmanager.provider.table_item";

/**

*Aprojectionofallcolumns

*intheitemstable.

*/

publicstaticfinalString[]PROJECTION_ALL={"_id",

"contact_name","contact_number",

"contact_email","photo_id"};

/**

*Thedefaultsortorderfor

*queriescontainingNAMEfields.

*/

//publicstaticfinalStringSORT_ORDER_DEFAULT=NAME+"ASC";

publicstaticfinalclassColumns{

publicstaticStringTABLE_ROW_ID="_id";

publicstaticStringTABLE_ROW_NAME="contact_name";

publicstaticStringTABLE_ROW_PHONENUM="contact_number";

publicstaticStringTABLE_ROW_EMAIL="contact_email";

publicstaticStringTABLE_ROW_PHOTOID="photo_id";

}

}

AUTHORITYisthesymbolicnamethatidentifiestheprovideramongmanyotherprovidersregisteredaspartofanAndroidsystem.BASE_PATHisthepathofthetable.CONTENT_URIistheURIofthetableencapsulatedbytheprovider.CONTENT_TYPEistheAndroidplatform’sbaseMIMEtypeforcontentURIcontainingacursorofzeroormoreitems.CONTENT_ITEM_TYPEistheAndroidplatform’sbaseMIMEtypeforcontentURIscontainingacursorofasingleitem.PROJECTION_ALLandColumnscontainthecolumnIDsofthetable.

Withoutthisinformation,otherdeveloperswillnotbeabletoaccessyourprovidereventhoughitisopenforaccess.

NoteTherecanbemanytablesinsideaproviderandeachshouldhaveauniquepath;thepathisnotarealphysicalpathbutanidentifier.

CreatingUriMatcherdefinitionsUriMatcherisautilityclass,whichaidsinmatchingURIsincontentproviders.TheaddURI()methodtakesthecontentURIpatternsthattheprovidershouldrecognize.WeaddaURItomatch,andthecodetoreturnwhenthisURIismatched:

addURI(Stringauthority,Stringpath,intcode)

Wepassauthority,apathpattern,andanintegervaluetotheaddURI()methodofUriMatcher;itreturnstheintvalue,whichwedefinedasconstantwhenwetriedtomatchpatterns.

OurUriMatcherlookslikethefollowing:

privatestaticfinalintCONTACTS_TABLE=1;

privatestaticfinalintCONTACTS_TABLE_ITEM=2;

privatestaticfinalUriMatchermmURIMatcher=new

UriMatcher(UriMatcher.NO_MATCH);

static{

mmURIMatcher.addURI(PersonalContactContract.AUTHORITY,

PersonalContactContract.BASE_PATH,CONTACTS_TABLE);

mmURIMatcher.addURI(PersonalContactContract.AUTHORITY,

PersonalContactContract.BASE_PATH+"/#",

CONTACTS_TABLE_ITEM);

}

Noticethatitalsosupportstheuseofwildcards;wehaveusedhashtag(#)intheprecedingcodesnippet,wecanalsousewildcardssuchas*.Inourcase,withthehashtag,"content://com.personalcontactmanager.provider/contacts/2"thisexpressionmatches,butusing*"content://com.personalcontactmanager.provider/contactsitdoesn’t.

ImplementingthecoremethodsInordertobuildourcontentprovider,thenextstepwillbetoprepareourcoredatabaseaccessanddatamodifyingmethods,betterknownasCRUDmethods.Thisiswherethecorelogicofhowwewanttointeractwithourdatadependingontheinsert,query,ordeletecallsreceivedisspecified.WewillalsoimplementtheAndroidarchitecture’slifecyclemethodssuchasonCreate().

InitializingtheproviderthroughtheonCreate()methodWecreateanobjectofourdatabasemanagerclassinonCreate().Thereshouldbeminimumoperationsinoncreate()asitrunsontheMainUIthread,anditmaycauselagforsomeusers.Itisgoodpracticetoavoidlong-runningtasksinoncreate()asitincreasesthestartuptimeoftheprovider.Itisevenrecommendedtodeferdatabasecreationanddataloadinguntilourprovideractuallyreceivesarequestforthedata,thatis,tomovelong-lastingactionstotheCRUDmethods:

@Override

PublicBooleanonCreate(){

dbm=newDatabaseManager(getContext());

returnfalse;

}

Queryingrecordsthroughthequery()methodThequery()methodwillreturnacursorovertheresultset.TheURIispassedtoourUriMatchertoseewhetheritmatchesanypatternswedefinedearlier.Inourswitchcasestatement,ifitisatable-item-relatedcase,wecheckwhethertheselectionstatementisempty;incaseitis,webuildourselectionstatementuptothelastpathsegment,elseweappendtheselectiontothelastpathsegmentstatement.WeuseaDatabaseManagerobjecttoarunqueryonthedatabaseandgetacursorasaresult.Itisexpectedofthequery()methodtothrowanIllegalArgumentExceptiontoinformofanunknownURI;itisalsogoodpracticetothrowanullPointerExceptionincaseweencounteraninternalerrorduringthequeryprocess:

@Override

publicCursorquery(Uriuri,String[]projection,Stringselection,

String[]selectionArgs,StringsortOrder){

inturiType=mmURIMatcher.match(uri);

switch(uriType){

caseCONTACTS_TABLE:

break;

caseCONTACTS_TABLE_ITEM:

if(TextUtils.isEmpty(selection)){

selection=PersonalContactContract.Columns.TABLE_ROW_ID

+"="+uri.getLastPathSegment();

}else{

selection=PersonalContactContract.Columns.TABLE_ROW_ID

+"="+uri.getLastPathSegment()+

"and"+selection;

}

break;

default:

thrownewIllegalArgumentException("UnknownURI:"+uri);

}

Cursorcr=dbm.getRowAsCursor(projection,selection,

selectionArgs,sortOrder);

returncr;

}

NoteRememberthatanAndroidsystemmustbeabletocommunicatetheexceptionacrossprocessboundaries.Androidcandothisforthefollowingexceptionsthatmaybeusefulinhandlingqueryerrors:

IllegalArgumentException:YoumaychoosetothrowthisifyourproviderreceivesaninvalidcontentURINullPointerException:Thisisthrownwhentheobjectisnullandwetrytoaccessitsfieldormethod

Addingrecordsthroughtheinsert()methodAsthenamesuggests,theinsert()methodisusedtoinsertavalueinourdatabase.ItreturnstheURIoftheinsertedrowand,whilecheckingtheURI,weneedtorememberthataninsertioncanhappenatthetablelevel,hencetheoperationsinthemethodareprocessedattheURIthatmatchesthetable.Aftermatching,weusethestandardDatabaseManagerobjecttoinsertournewvalueintothedatabase.ThecontentURIforthenewrowisconstructedbyappendingthenewrow’s_IDvaluetothetable’scontentURI:

@Override

publicUriinsert(Uriuri,ContentValuesvalues){

inturiType=mmURIMatcher.match(uri);

longid;

switch(uriType){

caseCONTACTS_TABLE:

id=dbm.addRow(values);

break;

default:

thrownewIllegalArgumentException("UnknownURI:"+uri);

}

Uriur=ContentUris.withAppendedId(uri,id);

returnur;

}

Updatingrecordsthroughtheupdate()methodTheupdate()methodupdatesanexistingrowintheappropriatetable,usingthevaluesintheContentValuesargument.First,weidentifytheURI,whetheritisdirectory-basedorID-based,thenwebuildourselectionstatementaswedidinthequery()method.Now,wewillexecutethestandardupdateRow()methodofDatabaseManagerthatwedefinedearlierwhilebuildingthisapplicationinChapter2,ConnectingtheDots,whichreturnsthenumberofaffectedrows.

Theupdate()methodreturnsthenumberofrowsupdated.Basedontheselectionclause,oneormorerowscanbeupdated:

@Override

publicintupdate(Uriuri,ContentValuesvalues,Stringselection,

String[]selectionArgs){

inturiType=mmURIMatcher.match(uri);

switch(uriType){

caseCONTACTS_TABLE:

break;

caseCONTACTS_TABLE_ITEM:

if(TextUtils.isEmpty(selection)){

selection=PersonalContactContract.Columns.TABLE_ROW_ID

+"="+uri.getLastPathSegment();

}else{

selection=PersonalContactContract.Columns.TABLE_ROW_ID

+"="+uri.getLastPathSegment()

+"and"+selection;

}

break;

default:

thrownewIllegalArgumentException("UnknownURI:"+uri);

}

intcount=dbm.updateRow(values,selection,selectionArgs);

returncount;

}

Deletingrecordsthroughthedelete()methodThedelete()methodisverysimilartotheupdate()methodandtheprocessofusingitissimilar;here,thecallismadetodeletearowinsteadofupdatingit.Thedelete()methodreturnsthenumberofrowsdeleted.Basedontheselectionclause,oneormorerowscanbedeleted:

@Override

publicintdelete(Uriuri,Stringselection,String[]selectionArgs){

inturiType=mmURIMatcher.match(uri);

switch(uriType){

caseCONTACTS_TABLE:

break;

caseCONTACTS_TABLE_ITEM:

if(TextUtils.isEmpty(selection)){

selection=PersonalContactContract.Columns.TABLE_ROW_ID

+"="+uri.getLastPathSegment();

}else{

selection=PersonalContactContract.Columns.TABLE_ROW_ID

+"="+uri.getLastPathSegment()

+"and"+selection;

}

break;

default:

thrownewIllegalArgumentException("UnknownURI:"+uri);

}

intcount=dbm.deleteRow(selection,selectionArgs);

returncount;

}

GettingthereturntypeofdatathroughthegetType()methodThesignatureofthissimplemethodtakesaURIandreturnsastringvalue;everycontentprovidermustreturnthecontenttypeforitssupportedURIs.Averyinterestingfactisthatnopermissionsareneededforanapplicationtoaccessthisinformation;ifourcontentproviderrequirespermissions,orisnotexported,alltheapplicationscanstillcallthismethodregardlessoftheiraccesspermissionstoretrieveMIMEtypes.

AlltheseMIMEtypesshouldbedeclaredinthecontractclass:

@Override

publicStringgetType(Uriuri){

inturiType=mmURIMatcher.match(uri);

switch(uriType){

caseCONTACTS_TABLE:

returnPersonalContactContract.CONTENT_TYPE;

caseCONTACTS_TABLE_ITEM:

returnPersonalContactContract.CONTENT_ITEM_TYPE;

default:

thrownewIllegalArgumentException("UnknownURI:"+uri);

}

}

AddingaprovidertoamanifestAnotherimportantstepistoaddourcontentprovidertoamanifest,likewedowithotherAndroidcomponents.Wecanregistermultipleprovidershere.Theimportantbithere,otherthanandroid:authorities,isandroid:exported;itdefineswhetherthecontentproviderisavailableforotherapplicationstouse.Incaseoftrue,theproviderisavailabletootherapplications;ifitisfalse,theproviderisnotavailabletootherapplications.IfapplicationshavethesameuserID(UID)astheprovider,theywillhaveaccesstoit:

<provider

android:name="com.personalcontactmanager.provider.PersonalContactProvider"

android:authorities="com.personalcontactmanager.provider"

android:exported="true"

android:grantUriPermissions="true">

</provider>

Anotherimportantconceptispermissions.Wecanaddadditionalsecuritybyaddingreadandwritepermissions,whichtheotherapplicationhastoaddintheirmanifestXMLfileand,inturn,automaticallyinformauserthattheyaregoingtouseaparticularapplication’scontentprovidereithertoread,write,orboth.Wecanaddpermissionsinthefollowingmanner:

android:readPermission="com.personalcontactmanager.provider.READ"

UsingacontentproviderThemainreasonwebuiltacontentproviderwastoallowotherapplicationstoaccessthecomplexdatastoreinourdatabaseandperformCRUDoperations.Wewillnowbuildonemoreapplicationinordertotestournewlybuiltcontentprovider.Thetestapplicationisverysimple,comprisingofonlyoneactivityclassandonelayoutfile.Ithasstandardbuttonstoperformactions.Nothingfancy,justthetoolsforustotestthefunctionalitywejustimplemented.WewillnowdelveintotheTestMainActivityclassandlookintoitsimplementation:

publicclassTestMainActivityextendsActivity{

publicfinalStringAUTHORITY="com.personalcontactmanager.provider";

publicfinalStringBASE_PATH="contacts";

privateTextViewqueryT,insertT;

publicclassColumns{

publicfinalstaticStringTABLE_ROW_ID="_id";

publicfinalstaticStringTABLE_ROW_NAME="contact_name";

publicfinalstaticStringTABLE_ROW_PHONENUM=

"contact_number";

publicfinalstaticStringTABLE_ROW_EMAIL="contact_email";

publicfinalstaticStringTABLE_ROW_PHOTOID="photo_id";

}

Toaccessacontentprovider,weneeddetailssuchasAUTHORITYandBASE_PATHandthenamesofthecolumnsofdatabasetables;weneedtoaccessthepublicclassColumnsforthispurpose.Wehavemoretablesandwewillseemoreoftheseclasses.Generally,allthisnecessaryinformationwillbetakenfromthepublishedcontractclassofthecontentprovider.Somecontentprovidersalsorequireimplementingreadorwritepermissionsinthemanifest:

<uses-permissionandroid:name="AUTHORITY.permission.WRITE_TASKS"/>

Insomecases,thecontentproviderweneedtoaccesscanaskustoaddpermissionsinourmanifest.Whentheusersinstalltheapplication,theywillseeanaddedpermissionintheirpermissionlist:

@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test_main);

queryT=(TextView)findViewById(R.id.textQuery);

insertT=(TextView)findViewById(R.id.textInsert);

}

NoteTotryoutsomeotherapp’scontentprovider,refertohttp://goo.gl/NEX2hN.

ItlistshowyoucanusetheAny.do’scontentprovider—averyfamoustaskapplication.

WewillsetourlayoutandinitializetheviewswerequireinonCreate()ofactivity.Toquery,wefirstneedtopreparetheURIobjectthatmatchesthetable.

Contentresolvernowcomesintoplay;itactsasaresolverforthecontentURIweprepared.OurgetContentResolver.query()method,inthiscase,willfetchallthecolumnsandrows.Wewillnowmovethecursortothefirstpositioninordertoreadtheresult.Fortestingpurposes,it’sreadasastring:

publicvoidquery(Viewv){

UricontentUri=Uri.parse("content://"+AUTHORITY

+"/"+BASE_PATH);

Cursorcr=getContentResolver().query(contentUri,null,

null,null,null);

if(cr!=null){

if(cr.getCount()>0){

cr.moveToFirst();

Stringname=cr.getString(cr.getColumnIndexOrThrow(

Columns.TABLE_ROW_NAME));

queryT.setText(name);

}

}

....

....

}

Now,webuildaURItoreadaparticularrowinsteadofacompletetable.WealreadymentionedthattomakeURIID-based,weneedtoaddtheIDparttoourexistingcontenturi.Now,webuildourprojectionstringarraytobepassedasaparameterinourquery()method:

publicvoidquery(Viewv){

...

...

UrirowUri=contentUri=ContentUris.withAppendedId

(contentUri,getFirstRowId());

String[]projection=newString[]{

Columns.TABLE_ROW_NAME,Columns.TABLE_ROW_PHONENUM,

Columns.TABLE_ROW_EMAIL,Columns.TABLE_ROW_PHOTOID};

cr=getContentResolver().query(contentUri,projection,

null,null,null);

if(cr!=null){

if(cr.getCount()>0){

cr.moveToFirst();

Stringname=cr.getString(cr.getColumnIndexOrThrow(

Columns.TABLE_ROW_NAME));

queryT.setText(name);

}

}

}

ThegetFirstRowId()methodgetstheIDofthefirstrowinthetable.ItisdonebecausetheIDofthefirstrowwillnotalwaysbe1.Itchangeswhentherowsaredeleted.IfthefirstiteminthetablewithrowID1isdeleted,thentheseconditemwithrowID1becomesthefirstitem:

privateintgetFirstRowId(){

intid=1;

UricontentUri=Uri.parse("content://"+AUTHORITY+"/"

+"contacts");

Cursorcr=getContentResolver().query(contentUri,null,

null,null,null);

if(cr!=null){

if(cr.getCount()>0){

cr.moveToFirst();

id=cr.getInt(cr.getColumnIndexOrThrow(

Columns.TABLE_ROW_ID));

}

}

returnid;

}

Let’stakeacloserlookatthequery()method:

publicfinalCursorquery(Uriuri,String[]projection,Stringselection,

String[]selectionArgs,StringsortOrder)

PresentinAPIlevel1,thequery()methodreturnsacursorovertheresultsetagainsttheparameterswesupplied.Thefollowingaretheparametersoftheprecedingcode:

uri:ThisiscontentURIinourcase,usingthecontent://schemeforthecontenttoberetrieved.ItcanbeID-basedordirectory-based.projection:Thisisalistofthecolumnstobereturnedaswehavepreparedusingthecolumnnames.Passingnullwillreturnallthecolumns.selection:FormattedasaSQLWHEREclause,excludingtheWHEREitself,thisactsasafilterdeclaringwhichrowstoreturn.selectionArgs:Wemayinclude?parametermarkersinselection.AndroidSQLquerybuilderwillreplacethe?parametermarkersbythevaluesboundasstringfromselectionArgs,intheorderthattheyappearintheselection.sortOrder:Thistellsushowtoordertherows,formattedasanSQLORDERBYclause.Anullvaluewillusethedefaultsortorder.

NoteAccordingtoofficialdocumentation,thereareafewguidelinesweshouldfollowfor

optimumperformance:

Provideanexplicitprojectiontopreventreadingdatafromstoragethatisn’tgoingtobeused.Usequestionmarkparametermarkerssuchasphone=?insteadofexplicitvaluesintheselectionparameter,sothatqueriesthatdifferonlybythosevalueswillberecognizedasthesameforcachingpurposes.

Thesameprocessweusedearliertocheckfornullvaluesandanemptycursorisperformed,andfinally,arequiredvalueisextractedfromthecursor.

Now,letuslookattheinsertmethodforourtestapplication.

Westartbybuildingourcontentvalueobjectandrelevantkey-valuepairs,forinstance,puttingaphonenumberintherelevantColumns.TABLE_ROW_PHONENUMfield.Noticethatbecausedetailssuchasacolumn’snameweresharedwithusintheformofaclass,weneednotworryaboutdetailssuchastheactualcolumnname.WejustneedtoaccessitviameansoftheColumnsclass.Thisensuresthatweonlyneedtoupdatetherelevantvalues.Ifinfuturethecontentproviderundergoessomechangeandchangesthetablenames,therestofthefunctionalityandimplementationremainsthesame.Webuildourprojectionstringarraywiththecolumnnameswerequired,aswedidearlierinthecaseofqueryingthecontentproviderfordata.

WealsobuildourcontentURI;noticethatitmatchesthetableandnotindividualrows.Theinsert()methodalsoreturnsaURIunlikethequery()method,whichreturnedacursorovertheresultset:

publicvoidinsert(Viewv){

Stringname=getRandomName();

Stringnumber=getRandomNumber();

ContentValuesvalues=newContentValues();

values.put(Columns.TABLE_ROW_NAME,name);

values.put(Columns.TABLE_ROW_PHONENUM,number);

values.put(Columns.TABLE_ROW_EMAIL,name+"@gmail.com");

values.put(Columns.TABLE_ROW_PHOTOID,"abc");

String[]projection=newString[]{

Columns.TABLE_ROW_NAME,Columns.TABLE_ROW_PHONENUM,

Columns.TABLE_ROW_EMAIL,Columns.TABLE_ROW_PHOTOID};

UricontentUri=Uri.parse("content://"+AUTHORITY+"/"

+BASE_PATH);

UriinsertedRowUri=getContentResolver().insert(

contentUri,values);

//checkingtheaddedrow

Cursorcr=getContentResolver().query(insertedRowUri,

projection,null,null,null);

if(cr!=null){

if(cr.getCount()>0){

cr.moveToFirst();

name=cr.getString(cr.getColumnIndexOrThrow(

Columns.TABLE_ROW_NAME));

insertT.setText(name);

}

}

}

ThegetRandomName()andgetRandomNumber()methodsgeneratearandomnameandnumbertoinsertinthetable:

privateStringgetRandomName(){

Randomrand=newRandom();

Stringname=""+(char)(122-rand.nextInt(26))

+(char)(122-rand.nextInt(26))

+(char)(122-rand.nextInt(26))

+(char)(122-rand.nextInt(26))

+(char)(122-rand.nextInt(26))

+(char)(122-rand.nextInt(26))

+(char)(122-rand.nextInt(26))

+(char)(122-rand.nextInt(26));

returnname;

}

publicStringgetRandomNumber(){

Randomrand=newRandom();

Stringnumber=rand.nextInt(98989)*rand.nextInt(59595)+"";

returnnumber;

}

Let’stakeacloserlookattheinsert()method:

publicfinalUriinsert(Uriurl,ContentValuesvalues)

Thefollowingaretheparametersoftheprecedinglineofcode:

url:TheURLofthetabletoinsertthedataintovalues:ThevaluesforthenewlyinsertedrowintheformofaContentValuesobject,thekeyisthecolumnnameforthefield

Noticethatafterinserting,wearerunningthequery()methodagainwiththeURIthatwasreturnedbytheinsert()method.Werunthistoseethatthevalueweintendedtoinserthasbeeninserted;thisquerywillreturncolumnsaspertheprojectionoftherowwhoseIDisappended.

Sofar,wehavecoveredthequery()andinsert()methods;now,wewillcovertheupdate()method.

Weprogressedintheinsert()methodbypreparingtheContentValuesobject.Similarly,wewillprepareanobjectthatwewilluseintheupdate()methodofContentResolverto

updateanexistingrow.WewillbuildourURIinthiscaseuptotheID,asthisoperationisIDbased.UpdatetherowaspointedbytherowUriobjectanditwillreturnthenumberofrowsupdated,whichwillbethesameastheURI;inthiscase,itisrowUrithatpointstoonlyasinglerow.AnalternatemethodcouldbeusingacombinationofcontentUri(whichpointstothetable)andselection/selectionArgs.Inthiscase,therowsupdatedcouldbemorethanoneaspertheselectionclause:

publicvoidupdate(Viewv){

Stringname=getRandomName();

Stringnumber=getRandomNumber();

ContentValuesvalues=newContentValues();

values.put(Columns.TABLE_ROW_NAME,name);

values.put(Columns.TABLE_ROW_PHONENUM,number);

values.put(Columns.TABLE_ROW_EMAIL,name+"@gmail.com");

values.put(Columns.TABLE_ROW_PHOTOID,"");

UricontentUri=Uri.parse("content://"+AUTHORITY

+"/"+BASE_PATH);

UrirowUri=ContentUris.withAppendedId(

contentUri,getFirstRowId());

intcount=getContentResolver().update(rowUri,values,null,null);

}

Let’stakeacloserlookattheupdate()method:

publicfinalintupdate(Uriuri,ContentValuesvalues,Stringwhere,

String[]selectionArgs)

Thefollowingaretheparametersoftheprecedinglineofcode:

uri:ThisisthecontentURIwewishtomodifyvalues:Thisissimilartothevaluesweusedearlierwithothermethods;passinganullvaluewillremoveanexistingfieldvaluewhere:ASQLWHEREclausethatactsasafiltertorowsbeforeupdatingthem

Wecanrunthequery()methodagaintoseewhetherthechangeisreflected;thisactivityhasbeenleftasanexerciseforyou.

Thelastmethodisdelete(),whichwerequireinordertocompleteourarsenalofCRUDmethods.Thedelete()methodbeginsinasimilarfashionastherestofthemethodsdo;first,prepareourcontentURIatthedirectorylevelandthenbuilditfortheIDlevel,thatis,attheindividualrowlevel.Afterthat,wepassittothedelete()methodofContentResolver.Unlikethequery()andinsert()methodsthatreturnanintegervalue,thedelete()methoddeletesarowaspointedbyourID-basedcontentURIobjectrowUriandreturnsthenumberofrowsdeleted.Thiswillbe1inourcaseasourURIpointstoonlyonerow.AnalternatemethodcouldbeusingacombinationofcontentUri,whichpointstothetable,andselection/selectionArgs.Inthiscase,therowsdeletedcouldbemorethan1aspertheselectionclause:

publicvoiddelete(Viewv){

UricontentUri=Uri.parse("content://"+AUTHORITY

+"/"+BASE_PATH);

UrirowUri=contentUri=ContentUris.withAppendedId(

contentUri,getFirstRowId());

intcount=getContentResolver().delete(rowUri,null,

null);

}

TheUIandoutputlooklikethefollowing:

NoteIfyouwanttodiveinalittlemoreintohowanAndroidcontentprovideractuallymanagesvariouswriteandreadcallsbetweenvarioustables(hint:itusesCountDownLatch),youcancheckoutthevideoatCourserabyDr.DouglasC.Schmidtformoreinformation.Thevideocanbefoundathttps://class.coursera.org/posa-002/lecture/49.

SummaryInthischapter,wecoveredthebasicsofcontentproviders.Welearnedhowtoaccesssystem-providedcontentprovidersandevenourownversionofacontentprovider.Wewentfromcreatingabasiccontactmanagertoevolvingitintoafully-fledgedcitizenoftheAndroidecosystembyimplementingContentProviderinordertosharedataacrossotherapplications.

Inthefollowingchapter,wewillcoverLoaders,CursorAdapters,niftyhacksandtips,andsomeopensourcelibrariestomakeourlifeeasierwhileworkingwiththeSQLitedatabase.

Chapter4.ThreadCarefully “Prematureoptimizationistherootofallevil.”

—-DonaldKnuth

Wecoveredaveryimportantconceptinthepreviouschapter:contentprovider.Weprogressedinastep-by-stepmanner,coveringessentialquestionssuchashowtocreateacontentproviderandhowtouseanexistingsystemwithacontentproviderindetail.Wealsocoveredhowtousethecontentproviderwecreatedbymeansofcreatingatestapplicationtoaccessit.

Inthischapter,wewillexplorehowtouseloaders,inparticular,aloadercalledcursorloader.Wewilllookathowtointeractwithacontentproviderasynchronouslywiththehelpofanexample.WewilldiscusstheimportanttopicofsecurityintheAndroiddatabaseandhowwecanensurethatdataissecuredinanAndroidmodel.Lastbutnotleast,wewillalsoseesomecodesnippetsthatwillcovertopicssuchashowtoupgradeadatabaseandhowtoshipapreloadeddatabasewithourapplication.

Inthischapter,wewillcoverthefollowingtopics:

LoadingdatawithCursorLoaderDatasecurityGeneraltipsandlibraries

LoadingdatawithCursorLoaderCursorLoaderispartoftheloaderfamily.BeforewedivedeepintoanexampleexplaininghowtouseCursorLoader,wewillexploreabitaboutloadersandwhyitisimportantinthecurrentscenario.

LoadersIntroducedinHoneyComb(APIlevel11),loadersservethepurposeofasynchronouslyservingdatainanactivityorfragment.Theneedtohaveloadersarosefrommanythings:callstovarioustime-consumingmethodsonthemainUIthreadinordertofetchdatathatleadstoaclunkyUI,andeveninsomecases,thedreadedANRbox.Thisisdemonstratedinthefollowingscreenshot:

Forexample,themanagedQuery()method,whichwasdeprecatedinAPI11,wasawrapperaroundtheContentResolver'squery()method.

Inthepreviouschapter,whilehighlightinghowtofetchdatafromacontentproviderinsidethequerymethod,weusedgetContentResolver.query()insteadofmanagedQuery().Usingdeprecatedmethodscanleadtoproblemswithfuturereleasesandshouldbeavoided.

Loadersprovideasynchronousloadingofdataforanactivityoffragmentonanon-UIthread.Theloaderorthesubclassesofaloaderperformtheirworkinaseparatethreadanddelivertheirresultstothemainthread.Thesegregationofcallsfromthemainthreadandthepostingofresultsonthemainthreadwhileworkinginaseparatethreadensurethatwehavearesponsiveapplication.

TipPosttheloaderera,wewerefacedwithproblemssuchaswhenanactivityshouldberecreatedduetoaconfigurationchange,forinstance,rotationofadevice’sorientation.Wehadtoworryaboutdataandrefetchdatawhilecreatinganewinstance.Butwithloaders,wedon’thavetoworryaboutalltheseasloadersautomaticallyreconnecttothelastloader’scursorwhenbeingrecreatedafteradeviceconfigurationchangeandrefetchthedata.Asanaddedbonus,loadersmonitorthedatasourceanddelivernewresultswhenthecontentchanges.Inotherwords,loadersautomaticallygetupdated,andhence,thereisnoneedtorequerythecursor.ReadmoreaboutkeepingyourAndroidapplicationresponsiveandavoidingapplicationnotresponding(ANR)messagesattheAndroiddeveloperwebsite,http://developer.android.com/training/articles/perf-anr.html.

LoaderAPI’ssummaryLet’slookattheloaderAPIthatconsistsofvariousclassesandinterfaces.Inthissection,wewilllookattheimplementationaspectofloaderAPI’sclasses/interfaces:

Class/interface Description

LoaderManager

Thisisanabstractclassassociatedwithanactivityorfragmenttomanagealoader.Althoughtherecanbeoneormoreloaderinstances,onlyoneinstanceofLoaderManagerperactivityorfragmentispermitted.Itisresponsiblefordealingwiththeactivityorfragment’slifecycleandparticularlyhelpfulwhenrunninglong-runningtasks.

LoaderManager.LoaderCallbacks ThisisacallbackinterfacewemustimplementtointeractwithLoaderManager.

Loader

Thisisthebaseclassforaloader.It’sanabstractclassthatperformsasynchronousloadingofdata.WecanimplementourownsubclassinsteadofusingsubclassessuchasCursorLoader.

AsyncTaskLoader

ThisisanabstractloaderthatprovidesAsyncTasktoperformtheworkinthebackground,thatis,onaseparatethread;however,theresultisdeliveredonthemainthread.Accordingtothedocumentation,itisadvisedtosubclassAsyncTaskLoaderinsteadofdirectlysubclassingtheLoaderclass.

CursorLoaderThisisasubclassofAsyncTaskLoaderthatqueriesContentResolveronthebackgroundthreadinanon-blockingmannerandreturnsacursor.

UsingCursorLoaderLoadersprovideuswithalotofhandyfeatures;oneofthemisthatonceouractivityorfragmentimplementsaloader,itneednotworryaboutrefreshingthedata.Aloadermonitorsthedatasourceforus,reflectsanychanges,andevenperformsnewloads;allofthisisdoneasynchronously.Hence,wedonotneedtotakecareofimplementingandmanagingthreads,offloadingqueriesonthebackgroundthread,andretrievingresultsoncethequeryiscompleted.

Aloadercanbeinanyoneofthefollowingthreedistinctstates:

Startedstate:Oncestarted,loadersremaininthisstateuntilstoppedorreset.Itexecutesloads,monitorsanychange,andreflectsthesametothelisteners.Stoppedstate:Here,loaderscontinuetomonitorchangesbutdonotpasstheresulttotheclients.Resetstate:Inthisstate,loadersreleaseanyresourcestheyhaveheldanddonotperformtheprocessofexecuting,loading,ormonitoringdata.

WewillnowrelookatourpersonalcontactmanagerapplicationandmakethecorrespondingchangestoimplementCursorLoaderinourapplication.CursorLoader,asthenamesuggests,isaloaderthatqueriesContentResolverandreturnsacursor.ThisisasubclassofAsyncTaskLoaderandperformsthecursorqueryonthebackgroundthreadsothatitdoesnotblocktheapplication’sUI.Inthediagram,youcanseethevariousmethodsofaloadercallbackandhowtheycommunicatewithCursorLoaderandCursorAdapter.

Forimplementingacursorloader,weneedtoperformthefollowingsteps:

1. Tobeginwith,weneedtoimplementtheLoaderManager.LoaderCallbacks<Cursor>interface:

publicclassContactsMainActivityextendsActivityimplements

OnClickListener,LoaderManager.LoaderCallbacks<Cursor>{…}

Then,implementthemethodsthatreflectthedistinctstatesofaloader:onCreateLoader(),onLoadFinished(),andonLoaderReset().

2. Toinitiateaquery,wewillmakeacalltotheLoaderManager.initLoader()method;thisinitializesthebackgroundframework:

getLoaderManager().initLoader(CUR_LOADER,null,this);

TheCUR_LOADERvalueispassedontotheonCreateLoader()method,whichactsasanIDfortheloader.Acalltoinitloader()invokesonCreateLoader(),passingtheIDweusedtocallinitloader():

@Override

publicLoader<Cursor>onCreateLoader(intloaderID,

Bundlebundle)

{

switch(loaderID){

caseCUR_LOADER:

returnnewCursorLoader(this,PersonalContactContract.CONTENT_URI,

PersonalContactContract.PROJECTION_ALL,null,null,null);

default:returnnull;

}

}

3. WeuseaswitchcasetotaketheloaderbasedonitsIDandreturnnullforaninvalidID.WecreateaURIobjectcontentUriandpassitasaparametertotheCursorLoaderconstructor.Apointtonoteisthatwecanimplementacursorloaderusingeitherthisconstructororanemptyunspecifiedcursorloader,CursorLoader(Contextcontext).Also,wecansetvaluesviamethodssuchassetUri(Uri),setSelection(String),setSelectionArgs(String[]),setSortOrder(String),andsetProjection(String[]):

publicCursorLoader(Contextcontext,Uriuri,String[]projection,

Stringselection,String[]selectionArgs,StringsortOrder)

Thefollowingaretheparametersofthepreviouscode:

context:Thisistheparentactivitycontext.uri:WeemploycontentURI,usingthecontent://scheme,toretrievethecontent.ItcanbebasedonanIDordirectory.projection:Thisisalistofcolumnstobereturnedaswearepreparedwiththecolumnnames.Passingnullwillreturnallthecolumns.selection:ThisisformattedasaSQLWHEREclause,excludingtheWHEREitself,actingasafilterdeclaringwhichrowstoreturn.

selectionArgs:Wemayincludequestionmarksintheselection,whichwillbereplacedbythevaluesboundasastringfromselectionArgs,andtheywillappearintheorderoftheirselection.sortOrder:Thistellsushowtoorderrows,formattedasaSQLORDERBYclause.Anullvaluewillusethedefaultsortorder.

4. onCreateLoaderstartsthequeryinthebackground,andwhenthequeryisfinished,thecursorloaderobjectispassedtothebackground’sframework,whichcallsonLoadFinished(),whereweprovideouradapterinstancewiththecursorobjectdata:

@Override

publicvoidonLoadFinished(Loader<Cursor>loader,Cursordata)

{

this.mAdapter.changeCursor(data);

}

5. TheadapterisasubclassofCursorAdapter.InsteadofthetraditionalgetView()method,whichwegetbyextendingBaseAdapter,wehavethebindView()andnewView()methods.WeinflateourlistviewrowlayoutintheviewobjectinnewView,andinbindview,weperformanactionsimilartothegetView()method.Wedefineourlayoutelementsandassociatethemewiththerelevantdata:

publicclassCustomCursorAdapterextendsCursorAdapter

{

...

publicvoidbindView(Viewview,Contextarg1,Cursorcursor)

{

finalImageViewcontact_photo=(ImageView)view

.findViewById(R.id.contact_photo);

...

...

contact_email.setText(cursor.getString(cursor

.getColumnIndexOrThrow(DatabaseConstants.TABLE_ROW_EMAIL)));

setImage(cursor.getBlob(cursor

.getColumnIndex(DatabaseConstants.TABLE_ROW_PHOTOID)),

contact_photo);

}

@Override

publicViewnewView(Contextarg0,Cursorarg1,ViewGrouparg2)

{

finalViewview=LayoutInflater.from(context).inflate(

R.layout.contact_list_row,null,false);

returnview;

}

...

}

6. Thismethodisinvokedwhenthecursorloaderisbeingreset.WeclearoutanyreferencetothecursorbypassingnulltothechangeCursor()method.Wheneverthedataassociatedwithacursorchanges,thecursorloadercallsthismethodbeforeitrerunsthequerytoclearanypastreferences,therebypreventingmemoryleaks.Once

onLoaderReset()isset,thecursorloaderwillrerunitsquery:

@Override

publicvoidonLoaderReset(Loader<Cursor>loader)

{

this.mAdapter.changeCursor(null);

}

7. Nowwemoveontoourcontentproviderwherewehavetomakesmallchangestoensurethatanychangeswemaketothedatabasearereflectedinourapplication’slistview:

cr.setNotificationUri(getContext().getContentResolver(),uri);

8. WeneedtoregisterobserverinContentResolverthroughthecursorinthequerymethodofContentProvider.WedothistowatchthecontentURIforanychanges,whichcanbetheURIofaspecificdatarowortableinourcase:

getContext().getContentResolver().notifyChange(ur,null);

9. Intheinsert()method,weusethenotifyChange()methodtoinformregisteredobserversthatarowwasupdated.Bydefault,theCursorAdapterobjectswillgetthisnotification.So,nowwhenweaddanewrowofdatabyinsertinganewcontactinourapplication,theinsert()methodofcontentProviderisinvokedviaacall:

resolver.insert(PersonalContactContract.CONTENT_URI,

prepareData(contact));

10. Asimilaractionneedstobeperformedforthedelete()andupdate()methods,bothofwhichhavebeenleftasanexerciseforthereaderasmostoftheboilerplatecodeispresent.Implementingaloaderissimpleandsavesusfromalotofheadachewhenitcomestothreading,andajarringUIishighlyrecommendedtoperformthistask.

NoteloadInBackground()isanotherimportantmethod;thisreturnsacursorinstanceforaloadoperationandiscalledontheworkerthread.Ideally,loadInBackground()shouldnotdirectlyreturntheresultoftheloadoperation,butwecanachievethisbyoverridingthedeliverResult(D)method.Tocancel,weneedtocheckthevalueofisLoadInBackgroundCanceled()aswedointhecaseofAsyncTask,wherewecheckisCancelled()periodically.

DatasecuritySecurityisthelatestbuzzwordintown.TheAndroidecosystemensuresthatourdatabaseisexposedtopryingeyes;however,arooteddevicecanleaveourdatabaseexposed,aswesawinChapter2,ConnectingtheDots.Withthehelpofarooteddevice,anemulatorandtheadbpullcommandinourcase,wepulledourdatabaseforinspectionwiththeSQLitemanagertool.Anotherimportantaspectiscontentproviders;weneedtobecarefulwhilesettingpermissions.Weshouldmaketheprocessofapplyingappropriatepermissionscompulsoryinordertoinformusersaboutthecontrolthatanappestablishesoverdata,usingthecontractclass.

ContentProviderandpermissionsInChapter3,SharingisCaring,webrieflycoveredthetopicofpermissionsintheAddingaprovidertoamanifestsection.Let’selaboratealittlemoreonthis:

1. Asmentionedearlier,whileaddingthecontentprovidertothemanifest,wewillalsoaddourcustompermissions.Thiswillensuretwothings,namely,stopanunauthorizedactioninanapplicationandinformtheusersaboutpermissions:

<provider

android:name="com.personalcontactmanager.provider.PersonalContactProvid

er"

android:authorities="com.personalcontactmanager.provider"

android:readPermission="com.personalcontactmanager.provider.read"

android:exported="true"

android:grantUriPermissions="true"

>

2. Additionally,wewilladdthepermissionstagtothemanifesttoindicatethesetofpermissionsthatotherapplicationswillrequire:

<permission

android:name="com.personalcontactmanager.provider.read"

android:icon="@drawable/ic_launcher"

android:label="ContactManager"

android:protectionLevel="normal">

</permission>

3. Now,intheapplicationinwhichwewanttoaccessthecontentproviderweusethepermissiontag,inourcase,Ch4-TestAppincodebundle:

<uses-permission

android:name="com.personalcontactmanager.provider.read"/>

Whenusersinstallthisapplication,theywillgetourcustompermissionmessagealongwithotherpermissionsrequiredbytheapplication.Forthisstep,insteadofdirectlyrunningtheapplicationfromEclipse,exportanapkandinstallit:

Ifyouhavenotdefinedthepermissionintheapplicationandiftheapplicationtriestoaccessthecontentprovider,itwillgettheSecurityException:PermissionDenialmessage.

Ifthecontentproviderwecreatedisnotmeanttobeshared,wewillneedtochangetheandroid:exported="true"propertytofalse.Thiswillmakeourcontentprovidersecure,andifsomeonetriestorunamaliciousqueryonit,theywillencounterasecurityexception.

Ifwewanttosharedataonlybetweenourapplications,Androidprovidesasolution;wecanuseandroid:protectionLevelandsetthepermissiontosignatureinsteadofnormal.Forthis,boththeapps,theonethatimplementsthecontentproviderandtheonethatwantstoaccessit,havetobesignedbythesamekeywhiletheyareexported.Thisisbecauseabonussignaturepermissiondoesnotrequireuserconfirmation.Thisdoesnotconfusetheuserasitisdoneinternallyandalsodoesnotobstructtheuserexperience.

EncryptingcriticaldataWehavealreadydiscussedwhatkindofaccessrightsotherapplicationshaveonourdatabaseandhowtoefficientlyshareourcontentproviders,andwealsobrieflydiscussedwhyweshouldnotbelievethatthesystemisfoolproof.Inthemostfoolproofmethod,sensitivedatawillnotbekeptonthedevicebutontheserverinstead,anditwillusetokenstogiveaccess.Ifyouhavetostorethedataonthedevice’sdatabase,useencryption.Useauser-definedkeytoencryptanddecryptsensitivedata.

Wewillexploreawaytouseanencrypteddatabase,whichwillnotbereadableifsomeoneisabletoextractitviameansofarootorviaexploitingbackups.IfsomeonetriestoreaditusingSQLiteManagerorsomeothertool,theywillreceiveafriendlymessage,suchastheoneshowninthefollowingscreenshot;thisisthedatabasefilethatwewillcreateinamomentwithalibraryknownasSQLCipher.

SQLCipherisanopensourceextensiontoSQLitethatprovidesatransparent256-bitAESencryptionofdatabasefiles,asmentionedontheirwebsite.ItisveryeasytodeploySQLCipher.Nowwe’lllookatthestepstobuildasampleapplication:

1. First,wewilldownloadthenecessaryfilesfromhttp://sqlcipher.net/open-source.Here,theyhavelistedacommunityeditionoftheAndroid-basedSQLCipher;downloadit.

2. NowwewillcreateanewAndroidprojectinoureclipseenvironment.3. Insidethedownloadedfolder,wewillfindthelibsfolder;insideit,areasetofjars

thatwewillneedtoworkwithSQLCipher.Wewillalsonoticethatfoldersarenamedasarmeabi,armeabi-v7a,andx86,andallofthesecontainthe.sofiles.IfyouarefamiliarwithAndroidNDK,thiswillnotseemnew.The.sofileisasharedobjectfile,whichisacomponentofdynamiclibraries.Fordifferentarchitectures,werequiredifferent.sofiles,hencethethreefolders.Ifyouarerunninganx86emulator,youwillneedthex86folderinyourlibsfolder.Forsimplicity,wewillcopyallthefolderstothelibsfolder.Copytheassetfolder’scontentintoourproject’sassetfolderandnavigatetotheproject’sproperties.Itwilllooksomethinglikethefollowingscreenshot.YoucanalsoseetheseJARfilesintheproject’sclasspath.Theinitialsetupforthisprojectisnowcomplete.

Aftercompletingthenecessarysetuppart,let’smovetowritingcodetomakeasmalltestapplication:

publicclassMainActivityextendsActivity

{

TextViewshowResult;

@Override

protectedvoidonCreate(BundlesavedInstanceState)

{

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

showResult=(TextView)findViewById(R.id.showResult);

InitializeSQLCipher();

}

privatevoidInitializeSQLCipher()

{

SQLiteDatabase.loadLibs(this);

FiledatabaseFile=getDatabasePath("test.db");

databaseFile.mkdirs();

databaseFile.delete();

SQLiteDatabasedatabase=SQLiteDatabase

.openOrCreateDatabase(databaseFile,"test123",null);

database.execSQL("createtablet1(a,b)");

database.execSQL("insertintot1(a,b)values(?,?)",

newObject[]{"Iam","Encrypted"});

}

publicvoidrunQuery(Viewv)

{

FiledatabaseFile=getDatabasePath("test.db");

SQLiteDatabasedatabase=SQLiteDatabase.openOrCreateDatabase(

databaseFile,"test123",null);

Stringselection="select*fromt1";

Cursorc=database.rawQuery(selection,null);

c.moveToFirst();

showResult.setText(c.getString(c.getColumnIndex("a"))+

c.getString(c.getColumnIndex("b")));

}

}

Theprecedingcodehastwomainmethods:InitializeSQLCipher()andrunQuery().InsideInitializeSQLCipher(),weloadour.solibraryfilesbyinvokingtheloadLibs()method.

4. Nowwefindtheabsolutepathtothedatabaseandcreateamissingparentfolderifany.WithopenOrCreateDatabase(),wewillmakeacalltoopenanexistingdatabaseorcreateoneifthedatabaseisnonexistent.Wewillexecutestandarddatabasecallstocreateatablewithcolumnsaandbandinsertvaluesinarow.

NowwewillperformasimplequerytofetchthevaluesbacktotherunQuery()method.Youwillnoticethatapartfromloadingthelibrary,allthecoremethodsweusedareprettymuchstandard,sowhereisthemajorchange?GototheCh4-PersonalContactManagerexampleinthecodebundleandnoticethepackageswehaveused:

importandroid.database.Cursor;

importandroid.database.sqlite.SQLiteDatabase;

WehaveSQLCipherpackages:

importnet.sqlcipher.Cursor;

importnet.sqlcipher.database.SQLiteDatabase;

Theimplementationissimple,familiar,andeasytoimplement.Ifyoupullthedatabaseoutandtrytoreadit,youwillfindtheerrormessage,aswedisplayedearlierinascreenshot.Theuserwillfindnochange,andevenourapp’slogicremainsthesame.Inthescreenshot,youcanseetheapplicationscreenwejustbuiltwhichencryptsthedatabase:

NoteOAuthisanopenstandardforauthorization.Itprovidesclientapplicationswithasecuredelegatedaccesstoserverresourcesonbehalfofaresourceowner.Itspecifiesaprocessforresourceownerstoauthorizethird-partyaccesstotheirserverresourceswithoutsharingtheircredentials,asexplainedinWikipedia;readmoreaboutOAuthathttp://oauth.net/2/.

GeneraltipsandlibrariesWewillcoversomegeneralandnotsogeneralworkaroundsandpractices,whichcanbeputtogoodusedependingonthesituation.Forinstance,insomecases,weneedtohaveaprepopulateddatabaseofvaluesthatwewillmakeuseofinourAndroidapplicationorupgradingadatabase,whichseemstrivialbutcanbreakourapplication.

UpgradingadatabaseInChapter2,ConnectingtheDots,weusedonUpgrade()toshowhowadatabaseisupdated.Ifwegobacktotheexample,youwillnoticethatitexecutesaDropTablecommand.Whatwillhappenhereisthattheoriginaltablewillbedroppedandanewtablewillbecreatedbythecall,onCreate().Thiswillleadtoalossoftheexistingdataandhenceisnotsuitableifweneedtoalterourdatabase.TheonUpgrade()functioncanbedefinedasfollows:

publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion)

{

StringDROP_TABLE="DROPTABLEIFEXISTS"+TABLE_NAME;

db.execSQL(DROP_TABLE);

onCreate(db);

}

Onemorechallengeistoidentifytheversionweareusinghere.Theusermightberunningolderversionsoftheapplication,sowehavetokeepinmindthedifferentversionsthatanapplicationhasandwhetherthoseversionswouldbringaboutanychangesinthedatabase.Foranewuser,weneednotworrybecauseifthedatabasedoesnotexist,onCreate()willbecalled.

Tomakesurewehaveaproperupgrade,wewillusetheDB_VERSIONconstantinourCustomSQLiteOpenHelperclasstotellouronUpgrade()methodabouttheactiontobetaken:

privatestaticfinalintDB_VERSION=1;

WewillchangetheDB_VERSIONconstantto3toreflecttheupgrade:

privatestaticfinalintDB_VERSION=3;

Theconstructorwilltakecareoftherest:

publicCustomSQLiteOpenHelper(Contextcontext)

{

super(context,DB_NAME,null,DB_VERSION);

}

Whenthesuperclassconstructorisrun,itcomparestheDB_VERSIONconstantofthestoredSQLite.dbfileagainsttheDB_VERSIONwepassedasaparameterandcallstheonUpgrade()methodifneeded:

publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion)

{

switch(oldVersion){

case1:db.execSQL(DATABASE_CREATE_MAIN_TABLE);

case2:db.execSQL(DATABASE_CREATE_MAIN_TABLE);

case3:db.execSQL(DATABASE_CREATE_DEL_TABLE);

}

}

InsideouronUpgrade()method,wehaveaswitchcasetomakechanges.Noticethatwedonotusethebreakstatementbecausetheusercanbeonanolderversionandmaynothaveupdatedtheapplication,asexplainedearlier.Forinstance,let’sconsiderthatauserisonaparticularversionofanapplicationthatisrunningDB_VERSION=1andheorsheskipsthenextupdatethatcontainedDB_VERSION=2,andeventually,anewversionoftheapplicationwithDB_VERSION=3isreleased.Now,wehaveacasewheretheuserisstillusinganolderversionoftheapplicationandhasnotinstalledthenewupdateswehavereleased.So,inthiscase,whentheuserinstallstheapplication,theonUpgrade()methodwillfirstexecutecase1andthengotocase2toinstallupdatesthattheusermissed;finally,theuserwillinstalltheupdatesofthethirdversion,ensuringthatallthedatabasechangesarereflected.Noticethatthereisnobreakstatement.Thisisbecausewewanttorunallthecaseswheretheswitchstatementobtainsthevalue1andthelasttwostatementswheretheswitchcaseobtainsthevalue2.

Alternatively,wecanalsousetheifstatement.ThiswillalsobehaveasweintendedasourtestDB_VERSIONconstantwas1,whichwillsatisfyboththeconditionsandreflectthechanges:

if(oldVersion<2){db.execSQL(DATABASE_CREATE_MORE_TABLE);}

if(oldVersion<3){db.execSQL(DATABASE_CREATE_DEL_TABLE);}

DatabaseminusSQLstatementsInmostpartsofthebook,welookedaroundfornooksandcornersofAndroidandSQLite.Forsome,writingSQLstatementswouldbejustanotherdayintheoffice,whileforsome,itwillcomeacrossasaroller-coasterride.ThissectionwillcoveralibrarythatenablesustosaveandretrieveSQLitedatabaserecordswithoutwritingasingleSQLstatement.ActiveAndroidisanactiverecord-styleSQLitepersistenceforAndroid.Accordingtothedocumentation,eachdatabaserecordiswrappedneatlyintoaclasswithmethodssuchassave()anddelete().WewillbeusingtheexampleintheActiveAndroiddocumentationandbuildaworkingsamplebasedonit.Let’slookatthestepsrequiredtogetitupandrunning.

Havealookattheofficialsite,http://www.activeandroid.com/,foranoverviewanddownloadthefilesfromhttp://goo.gl/oW2kod.

Onceyoudownloadthefile,runantontherootfoldertobuildtheJARfile.Onceyourunant,youwillfindyourJARfileinthedistfolder.InEclipse,makeanewproject,addtheJARfiletothelibsfolderoftheproject,andthenaddtheJARfiletotheJavaBuildPathintheprojectproperties.

ActiveAndroidlooksoutforsomeglobalsettingsconfiguredbyperformingthefollowingsteps:

1. Wewillstartbycreatingaclass,extendingtheapplicationclass:

publicclassMyApplicationextendscom.activeandroid.app.Application

{

@Override

publicvoidonCreate()

{

super.onCreate();

ActiveAndroid.initialize(this);

}

@Override

publicvoidonTerminate()

{

super.onTerminate();

ActiveAndroid.dispose();

}

}

2. Nowwewilladdthisapplicationclasstoourmanifestfileandaddmetadatacorrespondingtoourapplication:

<application

android:name="com.active.android.MyApplication">

<meta-data

android:name="AA_DB_NAME"

android:value="test.db"/>

<meta-data

android:name="AA_DB_VERSION"

android:value="1"/>

………..

</application>

3. Withthisbasicsetupcomplete,wewillnowproceedontocreatingourdatamodel.TheActiveAndroidlibrarysupportsannotationandwewilluseitinthefollowingmodelclasses:

//Categoryclass

@Table(name="Categories")

publicclassCategoryextendsModel

{

@Column(name="Name")

publicStringname;

}

//Itemclass

@Table(name="Items")

publicclassItemextendsModel

{

//Ifnameisomitted,thenthefieldnameisused.

@Column(name="Name")

publicStringname;

@Column(name="Category")

publicCategorycategory;

publicItem()

{

super();

}

publicItem(Stringname,Categorycategory)

{

super();

this.name=name;

this.category=category;

}

}

NoteIfyouwanttoexploreannotationsandusetheminyourprojectandreduceboilerplatecode,youcancheckoutthefollowinglibrariesforAndroid:AndroidAnnotations,Square’sDagger,andButterKnife.

4. Toaddanewcategoryoritem,weneedtomakeacalltosave().Inthecodesegment,wecanseethatanitemobjectiscreatedandassociatedwithaparticularcategory,andintheend,save()iscalled:

publicvoidinsert(Viewv)

{

ItemtestItem=newItem();

testItem.category=testCategory;

testItem.name=editTextItem.getText().toString();

testItem.save();

}

Todeleteanitem,wecancallitem.delete().Similarly,tofetchvalues,wehaverelevantmethodsaswell.Thefollowingisacalltofetchallofthedataforaparticularcategory:

List<Item>getall=newSelect().from(Item.class)

.where("Category=?",testCategory.getId())

.orderBy("NameASC").execute();

ThereislotmoretobeexploredinActiveAndroid.Theyhaveschemamigrationandtypeserialization;inadditiontothis,youcanshipaprepopulateddatabasebyplacingthedatabaseintheassetfolder,andyoucanusecontentprovidersaswell.Inshort,itisawell-builtlibraryforpeoplelookingforindirectwaystocommunicatewiththedatabaseandperformdatabaseoperations.IthelpsinaccessingthedatabaseinthefamiliarformofJavamethodsinsteadofpreparingSQLstatementstoperformthesameaction.Thecompletesamplecodeisbundledinthechapter4codebundle.

ShippingwithaprepopulateddatabaseWewillbuildadatabaseandputitinsideourassetfolder,whichisaread-onlydirectory.Atruntime,wewillcheckwhetheradatabaseexists.Ifnot,wewillcopyourdatabasefromtheassetfolderto/data/data/yourpackage/databases.InChapter2,ConnectingtheDots,weusedatoolcalledSQLiteManager;havealookatthethirdscreenshotofthechapter.Wearegoingtousethesametooltobuildourdatabasenow.Ifyoupullyourdatabaseasexplainedinthatsectionorlookatthatscreenshot,youwillnoticeafewmoretablesalongwithyourdatabasetable:

Thestepstobefollowedtocreateaprepopulateddatabaseareasfollows:

1. Tomakeaprepopulateddatabase,weneedtocreateatablenamedandroid_metadataapartfromthetablewerequire.UsingtheSQLiteManagertool,wewillcreateanewdatabasenamedcontact,thenwewillcreatetheandroid_metdatatable:

CREATETABLE"android_metadata"("locale"TEXTDEFAULT'en_US')

2. Wewillinsertarowinthetable:

INSERTINTO"android_metadata"VALUES('en_US')

3. Nowwewillcreatethetableswerequire,inourcase,contact_tableusingtheSQLqueryweusedinChapter2,ConnectingtheDots.IntheDatabaseManagerclass,wewilljustreplacetheconstantswiththeactualvalues:

CREATETABLE"contact_table"("_id"integerprimarykeyautoincrement

notnull,"contact_name"textnotnull,"contact_number"textnot

null,"contact_email"textnotnull,"photo_id"BLOB)

ItisnecessarytorenametheprimaryIDfieldofourtablesto_idifitisnotalreadydefined.ThishelpsAndroidinidentifyingwheretobindtheIDfieldofourtables.

4. Letusfillafewrowsofdata.WecandothisbyrunningtheInsertqueryormanuallytypinginthevaluesusingthetool.Now,copythedatabasefileintotheassetfolder.

5. Now,inouroriginalpersonalcontactmanager,wewillmodifyourDatabaseManagerclass.Thegoodpartisthatthisistheonlyclassweneedtomodifyandtherestofthesystemwillworkasintended.

6. WhentheapplicationrunsandcreatesanewDatabaseManagerclassbypassingthecontext,wewillmakeacalltocreateDatabase()inwhichfirstofallwewillcheckwhetherthedatabasealreadyexists:

PrivateBooleancheckDataBase()

{

SQLiteDatabasecheckDB=null;

try{

StringmyPath=DB_PATH+DB_NAME;

checkDB=SQLiteDatabase.openDatabase(myPath,null,

SQLiteDatabase.OPEN_READONLY);

}catch(SQLiteExceptione){

//databasedoesn'texistyet.

}

if(checkDB!=null){

checkDB.close();

}

returncheckDB!=null?true:false;

}

7. Ifitdoesn’t,wewillcreateanemptydatabasethatwewillreplacewithourdatabase,whichwecopiedintoourassetfolder.Aftercopyingthedatabasefromtheassetfolder,wewillcreateanewSQLiteDatabaseobject:

privatevoidcopyDataBase()throwsIOException

{

InputStreammyInput=myContext.getAssets().open(DB_NAME);

StringoutFileName=DB_PATH+DB_NAME;

OutputStreammyOutput=newFileOutputStream(outFileName);

byte[]buffer=newbyte[1024];

intlength;

while((length=myInput.read(buffer))>0){

myOutput.write(buffer,0,length);

}

myOutput.flush();

myOutput.close();

myInput.close();

}

AnotherpointtonoteisthattheonCreate()methodofourCustomSQLiteOpenHelperclasswillbeemptyaswearenotcreatingadatabaseandtables,butwearecopyingone.Thesamplecodeisbundledinthechapter4codebundle.Ifthisprocesslookstedious,don’tworry;theAndroiddevelopers’communityhasasolutionforyou.SQLiteAssetHelperisanAndroidlibrarythatwillhelpyouinmanagingdatabasecreationandversionmanagement,usinganapplication’srawassetfiles.

Toimplementthis,wehavetofollowafewsimplesteps:

1. CopytheJARfileintoourproject’slibsfolder.2. AddalibrarytoJavaBuildPath.3. Copyourzippeddatabasefileintotheassetfolderof

projectassets/databases/your_database.db.zip.4. TheZIPfileshouldcontainonlyonedbfile.5. Insteadofextendingtheframework’sSQLiteOpenHelperclass,wewillextendthe

SQLiteAssetHelperclass.

6. Theyalsoprovideyouwithassistancetoupgradethedatabasefile,whichneedstobeplacedinassets/databases/<database_name>_upgrade_<from_version>-<to_version>.sql.

7. Thelibrary,documentation,anditscorrespondingsamplecanbefoundathttp://goo.gl/8XSSmR.

SummaryWecoveredamyriadofadvancedtopicsinthischapter,rangingfromloaderstothesecurityofdata.Weimplementedourcursorloadertounderstandhowaloaderworksmagicforourapplications,andwedelvedintosecuringourdatabaseandunderstandingtheconceptofpermissionswhileexposingourcontentprovidertootherapplications.Wealsocoveredsometipssuchasshippingwithaprepopulateddatabase,upgradingadatabasewithoutbreakingthesystem,andusingdatabasequerieswithoutusingSQLcommands.ThisisinnowaytheonlysetofthingswecanachievewithdatabaseandAndroid.Thischapteronlyservesasanudgetowardsthevastprogrammingpossibilitiesoutthere.

IndexA

Activeandroidabout/DatabaseminusSQLstatementsURL/DatabaseminusSQLstatementsglobalsettings,configuring/DatabaseminusSQLstatements

addRow()method/BuildingtheInsertqueryaddURI()method

about/CreatingUriMatcherdefinitionsaffinity/BuildingblocksAheadofTime(AOT)

about/SQLiteinAndroidAndroid

storage/SQLiteinAndroidandroid.database.SQLitepackage

about/DatabasepackagesAndroiddeveloperwebsite

URL/LoadersAPIs

about/APIsApplicationA

about/Whatisacontentprovider?ApplicationB

about/Whatisacontentprovider?applicationnotresponding(ANR)/Loadersarchitecture,SQLite

interface/TheSQLiteinterfaceSQLcompiler/TheSQLcompilervirtualmachine/Thevirtualmachinebackend/TheSQLitebackend

ARTabout/SQLiteinAndroid

AUTOINCREMENTkeyword/Buildingblocks

BB-trees

about/TheSQLitebackendbackend,SQLite

about/TheSQLitebackendB-trees/TheSQLitebackendPager/TheSQLitebackendOSInterface/TheSQLitebackend

BLOBclassabout/Storageclasses

Booleandatatypeabout/TheBooleandatatype

branchtestcoveragereferencelink/WhySQLite?

buildingblocks,Android/Buildingblocks

Ccase-insensitive

about/TheSQLitesyntaxclose()method

about/TheSQLiteOpenHelperclasscolumnconstraint

about/BuildingblocksURL/Buildingblocks

columnconstraint,SQLiteNOTNULLconstraint/BuildingblocksDEFAULTconstraint/BuildingblocksUNIQUEconstraint/BuildingblocksPRIMARYkey/BuildingblocksCHECKconstraint/BuildingblocksAUTOINCREMENTkeyword/Buildingblocks

constraintabout/WhatisanSQLitestatement?

content$//URIabout/UnderstandingcontentURIs

contentproviderabout/Whatisacontentprovider?using/Usingexistingcontentproviders,UsingacontentproviderContentResolverobject/Whatisacontentresolver?creating/CreatingacontentprovidercontentURI/UnderstandingcontentURIscontractclass,declaring/DeclaringourcontractclassURIMatcher,creating/CreatingUriMatcherdefinitionsinitializing,onCreate()methodused/InitializingtheproviderthroughtheonCreate()methodadding,tomanifest/Addingaprovidertoamanifest

/ContentProviderandpermissionsContentResolverobject

about/Whatisacontentresolver?contentURI

about/UnderstandingcontentURIsContentValues

about/ContentValuescontext

about/TheSQLiteOpenHelperclasscontractclass

declaring/Declaringourcontractclasscreatequery

building/BuildingtheCreatequery

CREATETABLEcommandabout/WhatisanSQLitestatement?attributes/WhatisanSQLitestatement?

criticaldata,datasecurityencrypting/Encryptingcriticaldata

CursorLoaderused,forloadingdata/LoadingdatawithCursorLoaderusing/UsingCursorLoaderstartedstate/UsingCursorLoaderstoppedstate/UsingCursorLoaderresetstate/UsingCursorLoaderimplementing/UsingCursorLoader

Cursorobjectabout/Cursor

Cursorquery(Uriuri,String[]projection,Stringselection,String[]selectionArgs,StringsortOrder)method/Creatingacontentprovider

DDalvikvirtualmachine(DVM)

about/SQLiteinAndroiddata

loading,withCursorLoader/LoadingdatawithCursorLoaderdata,loading

CursorLoader,using/UsingCursorLoaderdata,loadingwithCursorLoader

loaders,using/LoadersloaderAPI/LoaderAPI’ssummary

databaseabout/AquickreviewofdatabasefundamentalsSQLitestatement/WhatisanSQLitestatement?SQLitesyntax/TheSQLitesyntaxUI,connectingwith/ConnectingtheUIanddatabaseupgrading/Upgradingadatabaseprepopulateddatabase,creating/Shippingwithaprepopulateddatabase

databasehandler/Adatabasehandlerandqueriesdatabasepackages

about/DatabasepackagesAPIs/APIsSQLiteOpenHelperclass/TheSQLiteOpenHelperclassSQLiteDatabaseclass/TheSQLiteDatabaseclassContentValues/ContentValuesCursorobject/Cursor

datasecurityabout/Datasecuritycontentprovider/ContentProviderandpermissionspermissions/ContentProviderandpermissionscriticaldata,encrypting/Encryptingcriticaldata

datatypes,SQLiteabout/DatatypesinSQLitestorageclasses/StorageclassesBooleandatatype/TheBooleandatatypeDatedatatype/TheDateandTimedatatypeTimedatatype/TheDateandTimedatatype

Datedatatypeabout/TheDateandTimedatatype

DEFAULTconstraint/Buildingblocksdelete()method/TheSQLiteDatabaseclass

used,fordeletingrecords/Deletingrecordsthroughthedelete()methoddelete()method,SQLiteDatabase/BuildingtheDeletequeryDELETEcommand

about/Aquickreviewofdatabasefundamentalsdeletequery

building/BuildingtheDeletequerydeleteRow()method/ConnectingtheUIanddatabasedelRowmethod/ConnectingtheUIanddatabasedynamictyping/Buildingblocks

EEclipse

emulator,settingup/Buildingblocksemulator

about/Buildingblocksemulator,Eclipse

settingup,steps/Buildingblocksexternalstorage

about/SQLiteinAndroid

Ffeatures,SQLite

zero-configuration/WhySQLite?no-copyright/WhySQLite?cross-platform/WhySQLite?compact/WhySQLite?foolproof/WhySQLite?

GGenymotion

URL/Buildingblocksget*()methods

about/CursorgetBlob()method/ConnectingtheUIanddatabasegetCount()method

about/CursorgetRandomName()method/UsingacontentprovidergetRandomNumber()method/UsingacontentprovidergetReadableDatabase()method

about/TheSQLiteOpenHelperclassgetType()method

used,forgettingreturntypeofcontent/GettingthereturntypeofdatathroughthegetType()method

getView()method/ConnectingtheUIanddatabasegetWriteableDatabase()method

about/TheSQLiteOpenHelperclass

IIllegalArgumentException/Queryingrecordsthroughthequery()methodinsert()method

used,foraddingrecords/Addingrecordsthroughtheinsert()methodurlparameter/Usingacontentprovidervaluesparameter/Usingacontentprovider

INSERTcommandabout/Aquickreviewofdatabasefundamentals

insertquerybuilding/BuildingtheInsertquery

intdelete(Uriuri,Stringselection,String[]selectionArgs)method/CreatingacontentproviderINTEGERclass

about/Storageclassesinterface,SQLite

about/TheSQLiteinterfaceinternalstorage

about/SQLiteinAndroidintupdate(Uriuri,ContentValuesvalues,Stringselection,String[]selectionArgs)method/CreatingacontentproviderisAfterLast()method

about/CursorisReadOnly()method

about/TheSQLiteOpenHelperclass

JJustinTime(JIT)

about/SQLiteinAndroid

LLemonparsergenerator

URL/TheSQLcompilerLoaderAPI

classes/interfaces/LoaderAPI’ssummaryloaders

about/LoadersloadInBackgroundmethod/UsingCursorLoader

MmoveToFirst()method

about/CursormoveToNext()method

about/Cursor

NNOTNULLconstraint/BuildingblocksNULLclass

about/StorageclassesNullPointerException/Queryingrecordsthroughthequery()method

OOAuth

URL/EncryptingcriticaldataonContextItemSelected()method/ConnectingtheUIanddatabaseonCreate()method/BuildingtheCreatequery

about/TheSQLiteOpenHelperclassused,forinitializingcontentprovider/InitializingtheproviderthroughtheonCreate()method

onOpen()methodabout/TheSQLiteOpenHelperclass

onUpgrade()methodabout/TheSQLiteOpenHelperclass

/BuildingtheCreatequeryOSInterface

about/TheSQLitebackend

PPager

about/TheSQLitebackendpath

about/UnderstandingcontentURIspermissions/Addingaprovidertoamanifest,ContentProviderandpermissionsprepareData()method/BuildingtheInsertqueryprepareSendData()method/ConnectingtheUIanddatabaseprepopulateddatabase

shipping/Shippingwithaprepopulateddatabasecreating/Shippingwithaprepopulateddatabase

PRIMARYkey/Buildingblocks

Qquery

about/Aquickreviewofdatabasefundamentals,Adatabasehandlerandqueriescreatequery,building/BuildingtheCreatequeryinsertquery,building/BuildingtheInsertquerydeletequery,building/BuildingtheDeletequeryupdatequery,building/BuildingtheUpdatequery

query()methodused,forqueryingrecords/Queryingrecordsthroughthequery()methoduriparameter/Usingacontentproviderprojectionparameter/Usingacontentproviderselectionparameter/UsingacontentproviderselectionArgsparameter/UsingacontentprovidersortOrderparameter/Usingacontentprovider

RREALclass

about/Storageclassesresetstate,CursorLoader/UsingCursorLoader

SSELECTcommand

about/Aquickreviewofdatabasefundamentalssharedpreference

about/SQLiteinAndroidSQLCipher

about/EncryptingcriticaldataURL/Encryptingcriticaldatasampleapplication,steps/Encryptingcriticaldata

SQLcompilerabout/TheSQLcompiler

SQLiteabout/WhySQLite?using/WhySQLite?features/WhySQLite?architecture/TheSQLitearchitecturedatatypes/DatatypesinSQLite

SQLite3about/SQLiteversion

SQLite3command.dump/SQLiteversion.schema/SQLiteversion.help/SQLiteversion

SQLiteDatabase()querymethod/BuildingtheInsertquerySQLiteDatabaseclass

about/TheSQLiteDatabaseclassURL,fordocumentation/TheSQLiteDatabaseclass

SQLiteinAndroidabout/SQLiteinAndroidversion/SQLiteversiondatabasepackages/Databasepackages

SQLiteManagertoolURL/BuildingtheCreatequery

SQLiteOpenHelperclassabout/TheSQLiteOpenHelperclass

SQLitestatementabout/WhatisanSQLitestatement?INSERT/WhatisanSQLitestatement?SELECT/WhatisanSQLitestatement?UPDATE/WhatisanSQLitestatement?DELETE/WhatisanSQLitestatement?ALTER/WhatisanSQLitestatement?DROP/WhatisanSQLitestatement?

SQLstatementstips/DatabaseminusSQLstatements

startedstate,CursorLoader/UsingCursorLoaderstoppedstate,CursorLoader/UsingCursorLoaderstorage,Android

sharedpreference/SQLiteinAndroidexternalstorage/SQLiteinAndroidinternalstorage/SQLiteinAndroid

storageclassesabout/StorageclassesNULL/StorageclassesINTEGER/StorageclassesREAL/StorageclassesTEXT/StorageclassesBLOB/Storageclasses

StringgetType(Uri)method/Creatingacontentprovidersynchronizedkeyword

about/TheSQLiteOpenHelperclasssyntax,SQLite

about/TheSQLitesyntax

TTEXTclass

about/StorageclassesTextUtils.isEmpty()method/ConnectingtheUIanddatabaseTimedatatype

about/TheDateandTimedatatypetips,prepopulateddatabase/Generaltipsandlibraries

UUI

connecting,withdatabase/ConnectingtheUIanddatabaseUNIQUEconstraint/Buildingblocksupdate()method/TheSQLiteDatabaseclass

used,forupdatingrecords/Updatingrecordsthroughtheupdate()methoduriparameter/Usingacontentprovidervaluesparameter/UsingacontentproviderWHEREclause/Usingacontentprovider

update()method,SQLiteDatabase/BuildingtheUpdatequeryUPDATEcommand

about/Aquickreviewofdatabasefundamentalsupdatequery

building/BuildingtheUpdatequeryURI

about/UnderstandingcontentURIsUriinsert(Uriuri,ContentValuesvalues)method/Creatingacontentprovider

VVDBE

about/Thevirtualmachineversion,SQLite

about/SQLiteversionvirtualmachine

about/ThevirtualmachinevoidonCreate()method/Creatingacontentprovider