Download Android Studio Development Essentials: Android 6 Edition

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
AndroidStudioDevelopmentEssentials–Android6Edition
©2015NeilSmyth.AllRightsReserved.
Thisbookisprovidedforpersonaluseonly.Unauthorizeduse,reproductionand/or
distributionstrictlyprohibited.Allrightsreserved.
Thecontentofthisbookisprovidedforinformationalpurposesonly.Neitherthepublisher
northeauthoroffersanywarrantiesorrepresentation,expressorimplied,withregardto
theaccuracyofinformationcontainedinthisbook,nordotheyacceptanyliabilityforany
lossordamagearisingfromanyerrorsoromissions.
Thisbookcontainstrademarkedtermsthatareusedsolelyforeditorialpurposesandtothe
benefitoftherespectivetrademarkowner.Thetermsusedwithinthisbookarenot
intendedasinfringementofanytrademarks.
Rev:1.0
TableofContents
1.Introduction
1.1DownloadingtheCodeSamples
1.2Feedback
1.3Errata
2.SettingupanAndroidStudioDevelopmentEnvironment
2.1SystemRequirements
2.2InstallingtheJavaDevelopmentKit(JDK)
2.2.1WindowsJDKInstallation
2.2.2MacOSXJDKInstallation
2.3LinuxJDKInstallation
2.4DownloadingtheAndroidStudioPackage
2.5InstallingAndroidStudio
2.5.1InstallationonWindows
2.5.2InstallationonMacOSX
2.5.3InstallationonLinux
2.6TheAndroidStudioSetupWizard
2.7InstallingAdditionalAndroidSDKPackages
2.8MakingtheAndroidSDKToolsCommand-lineAccessible
2.8.1Windows7
2.8.2Windows8.1
2.8.3Windows10
2.8.4Linux
2.8.5MacOSX
2.9UpdatingtheAndroidStudioandtheSDK
2.10Summary
3.CreatinganExampleAndroidAppinAndroidStudio
3.1CreatingaNewAndroidProject
3.2DefiningtheProjectandSDKSettings
3.3CreatinganActivity
3.4ModifyingtheExampleApplication
3.5ReviewingtheLayoutandResourceFiles
3.6PreviewingtheLayout
3.7Summary
4.ATouroftheAndroidStudioUserInterface
4.1TheWelcomeScreen
4.2TheMainWindow
4.3TheToolWindows
4.4AndroidStudioKeyboardShortcuts
4.5SwitcherandRecentFilesNavigation
4.6ChangingtheAndroidStudioTheme
4.7Summary
5.CreatinganAndroidVirtualDevice(AVD)inAndroidStudio
5.1AboutAndroidVirtualDevices
5.2CreatingaNewAVD
5.3StartingtheEmulator
5.4RunningtheApplicationintheAVD
5.5Run/DebugConfigurations
5.6StoppingaRunningApplication
5.7AVDCommand-lineCreation
5.8AndroidVirtualDeviceConfigurationFiles
5.9MovingandRenaminganAndroidVirtualDevice
5.10Summary
6.TestingAndroidStudioAppsonaPhysicalAndroidDevice
6.1AnOverviewoftheAndroidDebugBridge(ADB)
6.2EnablingADBonAndroid6.0basedDevices
6.2.1MacOSXADBConfiguration
6.2.2WindowsADBConfiguration
6.2.3LinuxadbConfiguration
6.3TestingtheadbConnection
6.4Summary
7.TheBasicsoftheAndroidStudioCodeEditor
7.1TheAndroidStudioEditor
7.2SplittingtheEditorWindow
7.3CodeCompletion
7.4StatementCompletion
7.5ParameterInformation
7.6CodeGeneration
7.7CodeFolding
7.8QuickDocumentationLookup
7.9CodeReformatting
7.10Summary
8.AnOverviewoftheAndroidArchitecture
8.1TheAndroidSoftwareStack
8.2TheLinuxKernel
8.3AndroidRuntime–ART
8.4AndroidLibraries
8.4.1C/C++Libraries
8.5ApplicationFramework
8.6Applications
8.7Summary
9.TheAnatomyofanAndroidApplication
9.1AndroidActivities
9.2AndroidIntents
9.3BroadcastIntents
9.4BroadcastReceivers
9.5AndroidServices
9.6ContentProviders
9.7TheApplicationManifest
9.8ApplicationResources
9.9ApplicationContext
9.10Summary
10.UnderstandingAndroidApplicationandActivityLifecycles
10.1AndroidApplicationsandResourceManagement
10.2AndroidProcessStates
10.2.1ForegroundProcess
10.2.2VisibleProcess
10.2.3ServiceProcess
10.2.4BackgroundProcess
10.2.5EmptyProcess
10.3Inter-ProcessDependencies
10.4TheActivityLifecycle
10.5TheActivityStack
10.6ActivityStates
10.7ConfigurationChanges
10.8HandlingStateChange
10.9Summary
11.HandlingAndroidActivityStateChanges
11.1TheActivityClass
11.2DynamicStatevs.PersistentState
11.3TheAndroidActivityLifecycleMethods
11.4ActivityLifetimes
11.5Summary
12.AndroidActivityStateChangesbyExample
12.1CreatingtheStateChangeExampleProject
12.2DesigningtheUserInterface
12.3OverridingtheActivityLifecycleMethods
12.4FilteringtheLogCatPanel
12.5RunningtheApplication
12.6ExperimentingwiththeActivity
12.7Summary
13.SavingandRestoringtheStateofanAndroidActivity
13.1SavingDynamicState
13.2DefaultSavingofUserInterfaceState
13.3TheBundleClass
13.4SavingtheState
13.5RestoringtheState
13.6TestingtheApplication
13.7Summary
14.UnderstandingAndroidViews,ViewGroupsandLayouts
14.1DesigningforDifferentAndroidDevices
14.2ViewsandViewGroups
14.3AndroidLayoutManagers
14.4TheViewHierarchy
14.5CreatingUserInterfaces
14.6Summary
15.AGuidetotheAndroidStudioDesignerTool
15.1Blankvs.EmptyActivityTemplates
15.2TheAndroidStudioDesigner
15.3DesignMode
15.4TextMode
15.5SettingProperties
15.6TypeMorphing
15.7CreatingaCustomDeviceDefinition
15.8Summary
16.DesigningaUserInterfaceusingtheAndroidStudioDesignerTool
16.1AnAndroidStudioDesignerToolExample
16.2CreatingaNewActivity
16.3DesigningtheUserInterface
16.4EditingViewProperties
16.5RunningtheApplication
16.6ManuallyCreatinganXMLLayout
16.7UsingtheHierarchyViewer
16.8Summary
17.CreatinganAndroidUserInterfaceinJavaCode
17.1JavaCodevs.XMLLayoutFiles
17.2CreatingViews
17.3PropertiesandLayoutParameters
17.4CreatingtheExampleProjectinAndroidStudio
17.5AddingViewstoanActivity
17.6SettingViewProperties
17.7AddingLayoutParametersandRules
17.8UsingViewIDs
17.9ConvertingDensityIndependentPixels(dp)toPixels(px)
17.10Summary
18.UsingtheAndroidGridLayoutManagerinAndroidStudioDesigner
18.1IntroducingtheAndroidGridLayoutandSpaceClasses
18.2TheGridLayoutExample
18.3CreatingtheGridLayoutProject
18.4CreatingtheGridLayoutInstance
18.5AddingViewstoGridLayoutCells
18.6MovingandDeletingRowsandColumns
18.7ImplementingCellRowandColumnSpanning
18.8ChangingtheGravityofaGridLayoutChild
18.9Summary
19.WorkingwiththeAndroidGridLayoutusingXMLLayoutResources
19.1GridLayoutsinXMLResourceFiles
19.2AddingChildViewstotheGridLayout
19.3DeclaringCellSpanning,GravityandMargins
19.4Summary
20.AnOverviewandExampleofAndroidEventHandling
20.1UnderstandingAndroidEvents
20.2Usingtheandroid:onClickResource
20.3EventListenersandCallbackMethods
20.4AnEventHandlingExample
20.5DesigningtheUserInterface
20.6TheEventListenerandCallbackMethod
20.7ConsumingEvents
20.8Summary
21.AndroidTouchandMulti-touchEventHandling
21.1InterceptingTouchEvents
21.2TheMotionEventObject
21.3UnderstandingTouchActions
21.4HandlingMultipleTouches
21.5AnExampleMulti-TouchApplication
21.6DesigningtheActivityUserInterface
21.7ImplementingtheTouchEventListener
21.8RunningtheExampleApplication
21.9Summary
22.DetectingCommonGesturesusingtheAndroidGestureDetectorClass
22.1ImplementingCommonGestureDetection
22.2CreatinganExampleGestureDetectionProject
22.3ImplementingtheListenerClass
22.4CreatingtheGestureDetectorCompatInstance
22.5ImplementingtheonTouchEvent()Method
22.6TestingtheApplication
22.7Summary
23.ImplementingCustomGestureandPinchRecognitiononAndroid
23.1TheAndroidGestureBuilderApplication
23.2TheGestureOverlayViewClass
23.3DetectingGestures
23.4IdentifyingSpecificGestures
23.5BuildingandRunningtheGestureBuilderApplication
23.6CreatingaGesturesFile
23.7ExtractingtheGesturesFilefromtheSDCard
23.8CreatingtheExampleProject
23.9AddingtheGesturesFiletotheProject
23.10DesigningtheUserInterface
23.11LoadingtheGesturesFile
23.12RegisteringtheEventListener
23.13ImplementingtheonGesturePerformedMethod
23.14TestingtheApplication
23.15ConfiguringtheGestureOverlayView
23.16InterceptingGestures
23.17DetectingPinchGestures
23.18APinchGestureExampleProject
23.19Summary
24.AnIntroductiontoAndroidFragments
24.1WhatisaFragment?
24.2CreatingaFragment
24.3AddingaFragmenttoanActivityusingtheLayoutXMLFile
24.4AddingandManagingFragmentsinCode
24.5HandlingFragmentEvents
24.6ImplementingFragmentCommunication
24.7Summary
25.UsingFragmentsinAndroidStudio-AnExample
25.1AbouttheExampleFragmentApplication
25.2CreatingtheExampleProject
25.3CreatingtheFirstFragmentLayout
25.4CreatingtheFirstFragmentClass
25.5CreatingtheSecondFragmentLayout
25.6AddingtheFragmentstotheActivity
25.7MakingtheToolbarFragmentTalktotheActivity
25.8MakingtheActivityTalktotheTextFragment
25.9TestingtheApplication
25.10Summary
26.CreatingandManagingOverflowMenusonAndroid
26.1TheOverflowMenu
26.2CreatinganOverflowMenu
26.3DisplayinganOverflowMenu
26.4RespondingtoMenuItemSelections
26.5CreatingCheckableItemGroups
26.6CreatingtheExampleProject
26.7ModifyingtheMenuDescription
26.8ModifyingtheonOptionsItemSelected()Method
26.9TestingtheApplication
26.10Summary
27.AnimatingUserInterfaceswiththeAndroidTransitionsFramework
27.1IntroducingAndroidTransitionsandScenes
27.2UsingInterpolatorswithTransitions
27.3WorkingwithSceneTransitions
27.4CustomTransitionsandTransitionSetsinCode
27.5CustomTransitionsandTransitionSetsinXML
27.6WorkingwithInterpolators
27.7CreatingaCustomInterpolator
27.8UsingthebeginDelayedTransitionMethod
27.9Summary
28.AnAndroidTransitionTutorialusingbeginDelayedTransition
28.1CreatingtheAndroidStudioTransitionDemoProject
28.2PreparingtheProjectFiles
28.3ImplementingbeginDelayedTransitionAnimation
28.4CustomizingtheTransition
28.5Summary
29.ImplementingAndroidSceneTransitions–ATutorial
29.1AnOverviewoftheSceneTransitionProject
29.2CreatingtheAndroidStudioSceneTransitionsProject
29.3IdentifyingandPreparingtheRootContainer
29.4DesigningtheFirstScene
29.5DesigningtheSecondScene
29.6EnteringtheFirstScene
29.7LoadingScene2
29.8ImplementingtheTransitions
29.9AddingtheTransitionFile
29.10LoadingandUsingtheTransitionSet
29.11ConfiguringAdditionalTransitions
29.12Summary
30.WorkingwiththeFloatingActionButtonandSnackbar
30.1TheMaterialDesign
30.2TheDesignLibrary
30.3TheFloatingActionButton(FAB)
30.4TheSnackbar
30.5CreatingtheExampleProject
30.6ReviewingtheProject
30.7ChangingtheFloatingActionButton
30.8AddingtheListViewtotheContentLayout
30.9AddingItemstotheListView
30.10AddinganActiontotheSnackbar
30.11Summary
31.CreatingaTabbedInterfaceusingtheTabLayoutComponent
31.1AnIntroductiontotheViewPager
31.2AnOverviewoftheTabLayoutComponent
31.3CreatingtheTabLayoutDemoProject
31.4CreatingtheFirstFragment
31.5DuplicatingtheFragments
31.6AddingtheTabLayoutandViewPager
31.7CreatingthePagerAdapter
31.8PerformingtheInitializationTasks
31.9TestingtheApplication
31.10CustomizingtheTabLayout
31.11DisplayingIconTabItems
31.12Summary
32.WorkingwiththeRecyclerViewandCardViewWidgets
32.1AnOverviewoftheRecyclerView
32.2AnOverviewoftheCardView
32.3AddingtheLibrariestotheProject
32.4Summary
33.AnAndroidRecyclerViewandCardViewTutorial
33.1CreatingtheCardDemoProject
33.2RemovingtheFloatingActionButton
33.3AddingtheRecyclerViewandCardViewLibraries
33.4DesigningtheCardViewLayout
33.5AddingtheRecyclerView
33.6CreatingtheRecyclerViewAdapter
33.7AddingtheImageFiles
33.8InitializingtheRecyclerViewComponent
33.9TestingtheApplication
33.10RespondingtoCardSelections
33.11Summary
34.WorkingwiththeAppBarandCollapsingToolbarLayouts
34.1TheAnatomyofanAppBar
34.2TheExampleProject
34.3CoordinatingtheRecyclerViewandToolbar
34.4IntroducingtheCollapsingToolbarLayout
34.5ChangingtheTitleandScrimColor
34.6Summary
35.ImplementinganAndroidNavigationDrawer
35.1AnOverviewoftheNavigationDrawer
35.2OpeningandClosingtheDrawer
35.3RespondingtoDrawerItemSelections
35.4UsingtheNavigationDrawerActivityTemplate
35.5CreatingtheNavigationDrawerTemplateProject
35.6TheTemplateLayoutResourceFiles
35.7TheHeaderColoringResourceFile
35.8TheTemplateMenuResourceFile
35.9TheTemplateCode
35.10RunningtheApp
35.11Summary
36.AnAndroidStudioMaster/DetailFlowTutorial
36.1TheMaster/DetailFlow
36.2CreatingaMaster/DetailFlowActivity
36.3TheAnatomyoftheMaster/DetailFlowTemplate
36.4ModifyingtheMaster/DetailFlowTemplate
36.5ChangingtheContentModel
36.6ChangingtheDetailPane
36.7ModifyingtheWebsiteDetailFragmentClass
36.8ModifyingtheWebsiteListActivityClass
36.9AddingManifestPermissions
36.10RunningtheApplication
36.11Summary
37.AnOverviewofAndroidIntents
37.1AnOverviewofIntents
37.2ExplicitIntents
37.3ReturningDatafromanActivity
37.4ImplicitIntents
37.5UsingIntentFilters
37.6CheckingIntentAvailability
37.7Summary
38.AndroidExplicitIntents–AWorkedExample
38.1CreatingtheExplicitIntentExampleApplication
38.2DesigningtheUserInterfaceLayoutforActivityA
38.3CreatingtheSecondActivityClass
38.4DesigningtheUserInterfaceLayoutforActivityB
38.5ReviewingtheApplicationManifestFile
38.6CreatingtheIntent
38.7ExtractingIntentData
38.8LaunchingActivityBasaSub-Activity
38.9ReturningDatafromaSub-Activity
38.10TestingtheApplication
38.11Summary
39.AndroidImplicitIntents–AWorkedExample
39.1CreatingtheAndroidStudioImplicitIntentExampleProject
39.2DesigningtheUserInterface
39.3CreatingtheImplicitIntent
39.4AddingaSecondMatchingActivity
39.5AddingtheWebViewtotheUI
39.6ObtainingtheIntentURL
39.7ModifyingtheMyWebViewProjectManifestFile
39.8InstallingtheMyWebViewPackageonaDevice
39.9TestingtheApplication
39.10Summary
40.AndroidBroadcastIntentsandBroadcastReceivers
40.1AnOverviewofBroadcastIntents
40.2AnOverviewofBroadcastReceivers
40.3ObtainingResultsfromaBroadcast
40.4StickyBroadcastIntents
40.5TheBroadcastIntentExample
40.6CreatingtheExampleApplication
40.7CreatingandSendingtheBroadcastIntent
40.8CreatingtheBroadcastReceiver
40.9ConfiguringaBroadcastReceiverintheManifestFile
40.10TestingtheBroadcastExample
40.11ListeningforSystemBroadcasts
40.12Summary
41.ABasicOverviewofAndroidThreadsandThreadHandlers
41.1AnOverviewofThreads
41.2TheApplicationMainThread
41.3ThreadHandlers
41.4ABasicThreadingExample
41.5CreatingaNewThread
41.6ImplementingaThreadHandler
41.7PassingaMessagetotheHandler
41.8Summary
42.AnOverviewofAndroidStartedandBoundServices
42.1StartedServices
42.2IntentService
42.3BoundService
42.4TheAnatomyofaService
42.5ControllingDestroyedServiceRestartOptions
42.6DeclaringaServiceintheManifestFile
42.7StartingaServiceRunningonSystemStartup
42.8Summary
43.ImplementinganAndroidStartedService–AWorkedExample
43.1CreatingtheExampleProject
43.2CreatingtheServiceClass
43.3AddingtheServicetotheManifestFile
43.4StartingtheService
43.5TestingtheIntentServiceExample
43.6UsingtheServiceClass
43.7CreatingtheNewService
43.8ModifyingtheUserInterface
43.9RunningtheApplication
43.10CreatingaNewThreadforServiceTasks
43.11Summary
44.AndroidLocalBoundServices–AWorkedExample
44.1UnderstandingBoundServices
44.2BoundServiceInteractionOptions
44.3AnAndroidStudioLocalBoundServiceExample
44.4AddingaBoundServicetotheProject
44.5ImplementingtheBinder
44.6BindingtheClienttotheService
44.7CompletingtheExample
44.8TestingtheApplication
44.9Summary
45.AndroidRemoteBoundServices–AWorkedExample
45.1ClienttoRemoteServiceCommunication
45.2CreatingtheExampleApplication
45.3DesigningtheUserInterface
45.4ImplementingtheRemoteBoundService
45.5ConfiguringaRemoteServiceintheManifestFile
45.6LaunchingandBindingtotheRemoteService
45.7SendingaMessagetotheRemoteService
45.8Summary
46.AnOverviewofAndroidSQLiteDatabases
46.1UnderstandingDatabaseTables
46.2IntroducingDatabaseSchema
46.3ColumnsandDataTypes
46.4DatabaseRows
46.5IntroducingPrimaryKeys
46.6WhatisSQLite?
46.7StructuredQueryLanguage(SQL)
46.8TryingSQLiteonanAndroidVirtualDevice(AVD)
46.9AndroidSQLiteJavaClasses
46.9.1Cursor
46.9.2SQLiteDatabase
46.9.3SQLiteOpenHelper
46.9.4ContentValues
46.10Summary
47.AnAndroidTableLayoutandTableRowTutorial
47.1TheTableLayoutandTableRowLayoutViews
47.2CreatingtheDatabaseProject
47.3AddingtheTableLayouttotheUserInterface
47.4AddingandConfiguringtheTableRows
47.5AddingtheButtonBartotheLayout
47.6AdjustingtheLayoutMargins
47.7Summary
48.AnAndroidSQLiteDatabaseTutorial
48.1AbouttheDatabaseExample
48.2CreatingtheDataModel
48.3ImplementingtheDataHandler
48.3.1TheAddHandlerMethod
48.3.2TheQueryHandlerMethod
48.3.3TheDeleteHandlerMethod
48.4ImplementingtheActivityEventMethods
48.5TestingtheApplication
48.6Summary
49.UnderstandingAndroidContentProviders
49.1WhatisaContentProvider?
49.2TheContentProvider
49.2.1onCreate()
49.2.2query()
49.2.3insert()
49.2.4update()
49.2.5delete()
49.2.6getType()
49.3TheContentURI
49.4TheContentResolver
49.5The<provider>ManifestElement
49.6Summary
50.ImplementinganAndroidContentProviderinAndroidStudio
50.1CopyingtheDatabaseProject
50.2AddingtheContentProviderPackage
50.3CreatingtheContentProviderClass
50.4ConstructingtheAuthorityandContentURI
50.5ImplementingURIMatchingintheContentProvider
50.6ImplementingtheContentProvideronCreate()Method
50.7ImplementingtheContentProviderinsert()Method
50.8ImplementingtheContentProviderquery()Method
50.9ImplementingtheContentProviderupdate()Method
50.10ImplementingtheContentProviderdelete()Method
50.11DeclaringtheContentProviderintheManifestFile
50.12ModifyingtheDatabaseHandler
50.13Summary
51.AccessingCloudStorageusingtheAndroidStorageAccessFramework
51.1TheStorageAccessFramework
51.2WorkingwiththeStorageAccessFramework
51.3FilteringPickerFileListings
51.4HandlingIntentResults
51.5ReadingtheContentofaFile
51.6WritingContenttoaFile
51.7DeletingaFile
51.8GainingPersistentAccesstoaFile
51.9Summary
52.AnAndroidStorageAccessFrameworkExample
52.1AbouttheStorageAccessFrameworkExample
52.2CreatingtheStorageAccessFrameworkExample
52.3DesigningtheUserInterface
52.4DeclaringRequestCodes
52.5CreatingaNewStorageFile
52.6TheonActivityResult()Method
52.7SavingtoaStorageFile
52.8OpeningandReadingaStorageFile
52.9TestingtheStorageAccessApplication
52.10Summary
53.ImplementingVideoPlaybackonAndroidusingtheVideoViewand
MediaControllerClasses
53.1IntroducingtheAndroidVideoViewClass
53.2IntroducingtheAndroidMediaControllerClass
53.3TestingVideoPlayback
53.4CreatingtheVideoPlaybackExample
53.5DesigningtheVideoPlayerLayout
53.6ConfiguringtheVideoView
53.7AddingInternetPermission
53.8AddingtheMediaControllertotheVideoView
53.9SettinguptheonPreparedListener
53.10Summary
54.VideoRecordingandImageCaptureonAndroidusingCameraIntents
54.1CheckingforCameraSupport
54.2CallingtheVideoCaptureIntent
54.3CallingtheImageCaptureIntent
54.4CreatinganAndroidStudioVideoRecordingProject
54.5DesigningtheUserInterfaceLayout
54.6CheckingfortheCamera
54.7LaunchingtheVideoCaptureIntent
54.8HandlingtheIntentReturn
54.9TestingtheApplication
54.10Summary
55.MakingRuntimePermissionRequestsinAndroid6.0
55.1UnderstandingNormalandDangerousPermissions
55.2CreatingthePermissionsExampleProject
55.3CheckingforaPermission
55.4RequestingPermissionatRuntime
55.5ProvidingaRationaleforthePermissionRequest
55.6TestingthePermissionsApp
55.7Summary
56.AndroidAudioRecordingandPlaybackusingMediaPlayerandMediaRecorder
56.1PlayingAudio
56.2RecordingAudioandVideousingtheMediaRecorderClass
56.3AbouttheExampleProject
56.4CreatingtheAudioAppProject
56.5DesigningtheUserInterface
56.6CheckingforMicrophoneAvailability
56.7PerformingtheActivityInitialization
56.8ImplementingtherecordAudio()Method
56.9ImplementingthestopAudio()Method
56.10ImplementingtheplayAudio()method
56.11ConfiguringandRequestingPermissions
56.12TestingtheApplication
56.13Summary
57.WorkingwiththeGoogleMapsAndroidAPIinAndroidStudio
57.1TheElementsoftheGoogleMapsAndroidAPI
57.2CreatingtheGoogleMapsProject
57.3ObtainingYourDeveloperSignature
57.4TestingtheApplication
57.5UnderstandingGeocodingandReverseGeocoding
57.6AddingaMaptoanApplication
57.7RequestingCurrentLocationPermission
57.8DisplayingtheUser’sCurrentLocation
57.9ChangingtheMapType
57.10DisplayingMapControlstotheUser
57.11HandlingMapGestureInteraction
57.11.1MapZoomingGestures
57.11.2MapScrolling/PanningGestures
57.11.3MapTiltGestures
57.11.4MapRotationGestures
57.12CreatingMapMarkers
57.13ControllingtheMapCamera
57.14Summary
58.PrintingwiththeAndroidPrintingFramework
58.1TheAndroidPrintingArchitecture
58.2ThePrintServicePlugins
58.3GoogleCloudPrint
58.4PrintingtoGoogleDrive
58.5SaveasPDF
58.6PrintingfromAndroidDevices
58.7OptionsforBuildingPrintSupportintoAndroidApps
58.7.1ImagePrinting
58.7.2CreatingandPrintingHTMLContent
58.7.3PrintingaWebPage
58.7.4PrintingaCustomDocument
58.8Summary
59.AnAndroidHTMLandWebContentPrintingExample
59.1CreatingtheHTMLPrintingExampleApplication
59.2PrintingDynamicHTMLContent
59.3CreatingtheWebPagePrintingExample
59.4RemovingtheFloatingActionButton
59.5DesigningtheUserInterfaceLayout
59.6LoadingtheWebPageintotheWebView
59.7AddingthePrintMenuOption
59.8Summary
60.AGuidetoAndroidCustomDocumentPrinting
60.1AnOverviewofAndroidCustomDocumentPrinting
60.1.1CustomPrintAdapters
60.2PreparingtheCustomDocumentPrintingProject
60.3CreatingtheCustomPrintAdapter
60.4ImplementingtheonLayout()CallbackMethod
60.5ImplementingtheonWrite()CallbackMethod
60.6CheckingaPageisinRange
60.7DrawingtheContentonthePageCanvas
60.8StartingthePrintJob
60.9TestingtheApplication
60.10Summary
61.HandlingDifferentAndroidDevicesandDisplays
61.1HandlingDifferentDeviceDisplays
61.2CreatingaLayoutforeachDisplaySize
61.3ProvidingDifferentImages
61.4CheckingforHardwareSupport
61.5ProvidingDeviceSpecificApplicationBinaries
61.6Summary
62.SigningandPreparinganAndroidApplicationforRelease
62.1TheReleasePreparationProcess
62.2ChangingtheBuildVariant
62.3EnablingProGuard
62.4CreatingaKeystoreFile
62.5GeneratingaPrivateKey
62.6CreatingtheApplicationAPKFile
62.7RegisterforaGooglePlayDeveloperConsoleAccount
62.8UploadingNewAPKVersionstotheGooglePlayDeveloperConsole
62.9Summary
63.IntegratingGooglePlayIn-appBillingintoanAndroidApplication
63.1InstallingtheGooglePlayBillingLibrary
63.2CreatingtheExampleIn-appBillingProject
63.3AddingBillingPermissiontotheManifestFile
63.4AddingtheIInAppBillingService.aidlFiletotheProject
63.5AddingtheUtilityClassestotheProject
63.6DesigningtheUserInterface
63.7Implementingthe“ClickMe”Button
63.8GooglePlayDeveloperConsoleandGoogleWalletAccounts
63.9ObtainingthePublicLicenseKeyfortheApplication
63.10SettingUpGooglePlayBillingintheApplication
63.11InitiatingaGooglePlayIn-appBillingPurchase
63.12ImplementingtheonActivityResultMethod
63.13ImplementingthePurchaseFinishedListener
63.14ConsumingthePurchasedItem
63.15ReleasingtheIabHelperInstance
63.16ModifyingtheSecurity.javaFile
63.17TestingtheIn-appBillingApplication
63.18BuildingaReleaseAPK
63.19CreatingaNewIn-appProduct
63.20PublishingtheApplicationtotheAlphaDistributionChannel
63.21AddingIn-appBillingTestAccounts
63.22ConfiguringGroupTesting
63.23ResolvingProblemswithIn-AppPurchasing
63.24Summary
64.AnOverviewofGradleinAndroidStudio
64.1AnOverviewofGradle
64.2GradleandAndroidStudio
64.2.1SensibleDefaults
64.2.2Dependencies
64.2.3BuildVariants
64.2.4ManifestEntries
64.2.5APKSigning
64.2.6ProGuardSupport
64.3TheTop-levelGradleBuildFile
64.4ModuleLevelGradleBuildFiles
64.5ConfiguringSigningSettingsintheBuildFile
64.6RunningGradleTasksfromtheCommand-line
64.7Summary
65.AnAndroidStudioGradleBuildVariantsExample
65.1CreatingtheBuildVariantExampleProject
65.2ExtractingtheHelloWorldStringResource
65.3AddingtheBuildFlavorstotheModuleBuildFile
65.4AddingtheFlavorstotheProjectStructure
65.5AddingResourceFilestotheFlavors
65.6TestingtheBuildFlavors
65.7BuildVariantsandClassFiles
65.8AddingPackagestotheBuildFlavors
65.9CustomizingtheActivityClasses
65.10Summary
1.Introduction
ThegoalofthisbookistoteachtheskillsnecessarytodevelopAndroidbased
applicationsusingtheAndroidStudioIntegratedDevelopmentEnvironment(IDE)andthe
Android6SoftwareDevelopmentKit(SDK).
Beginningwiththebasics,thisbookprovidesanoutlineofthestepsnecessarytosetupan
Androiddevelopmentandtestingenvironment.AnoverviewofAndroidStudiois
includedcoveringareassuchastoolwindows,thecodeeditorandtheDesignertool.An
introductiontothearchitectureofAndroidisfollowedbyanin-depthlookatthedesignof
AndroidapplicationsanduserinterfacesusingtheAndroidStudioenvironment.More
advancedtopicssuchasdatabasemanagement,contentprovidersandintentsarealso
covered,asaretouchscreenhandling,gesturerecognition,cameraaccessandthe
playbackandrecordingofbothvideoandaudio.Thiseditionofthebookalsocovers
printing,transitionsandcloud-basedfilestorage.
Theconceptsofmaterialdesignarealsocoveredindetail,includingtheuseoffloating
actionbuttons,Snackbars,tabbedinterfaces,cardviews,navigationdrawersand
collapsingtoolbars.
InadditiontocoveringgeneralAndroiddevelopmenttechniques,thebookalsoincludes
GooglePlayspecifictopicssuchasimplementingmapsusingtheGoogleMapsAndroid
API,in-appbillingandsubmittingappstotheGooglePlayDeveloperConsole.
ChaptersalsocoveradvancedfeaturesofAndroidStudiosuchasGradlebuild
configurationandtheimplementationofbuildvariantstotargetmultipleAndroiddevice
typesfromasingleprojectcodebase.
AssumingyoualreadyhavesomeJavaprogrammingexperience,arereadytodownload
AndroidStudioandtheAndroidSDK,haveaccesstoaWindows,MacorLinuxsystem
andideasforsomeappstodevelop,youarereadytogetstarted.
1.1DownloadingtheCodeSamples
ThesourcecodeandAndroidStudioprojectfilesfortheexamplescontainedinthisbook
areavailablefordownloadat:
http://www.ebookfrenzy.com/retail/androidstudioA6/index.php
ThestepstoloadaprojectfromthecodesamplesintoAndroidStudioareasfollows:
1. FromtheWelcometoAndroidStudiodialog,selecttheOpenanexistingAndroid
Studioprojectoption.
2. Intheprojectselectiondialog,navigatetoandselectthefoldercontainingtheproject
tobeimportedandclickonOK.
1.2Feedback
Wewantyoutobesatisfiedwithyourpurchaseofthisbook.Ifyoufindanyerrorsinthe
book,orhaveanycomments,questionsorconcernspleasecontactusat
[email protected].
1.3Errata
Whilewemakeeveryefforttoensuretheaccuracyofthecontentofthisbook,itis
inevitablethatabookcoveringasubjectareaofthissizeandcomplexitymayinclude
someerrorsandoversights.Anyknownissueswiththebookwillbeoutlined,together
withsolutions,atthefollowingURL:
http://www.ebookfrenzy.com/errata/androidstudioA6.html
Intheeventthatyoufindanerrornotlistedintheerrata,pleaseletusknowbyemailing
ourtechnicalsupportteamatfeedback@ebookfrenzy.com.Theyaretheretohelpyouand
willworktoresolveanyproblemsyoumayencounter.
2.SettingupanAndroidStudioDevelopment
Environment
BeforeanyworkcanbeginonthedevelopmentofanAndroidapplication,thefirststepis
toconfigureacomputersystemtoactasthedevelopmentplatform.Thisinvolvesa
numberofstepsconsistingofinstallingtheJavaDevelopmentKit(JDK)andtheAndroid
StudioIntegratedDevelopmentEnvironment(IDE)whichalsoincludestheAndroid
SoftwareDevelopmentKit(SDK).
ThischapterwillcoverthestepsnecessarytoinstalltherequisitecomponentsforAndroid
applicationdevelopmentonWindows,MacOSXandLinuxbasedsystems.
2.1SystemRequirements
Androidapplicationdevelopmentmaybeperformedonanyofthefollowingsystemtypes:
· WindowsVista(32-bitor64-bit)
· Windows7(32-bitor64-bit)
· Windows8/Windows8.1orlater
· MacOSX10.8.5orlater(Intelbasedsystemsonly)
· Linuxsystemswithversion2.15orlaterofGNUCLibrary(glibc)
· Minimumof2GBofRAM(4GBispreferred)
· Approximately4.5GBofavailablediskspace
2.2InstallingtheJavaDevelopmentKit(JDK)
TheAndroidSDKwasdevelopedusingtheJavaprogramminglanguage.Similarly,
AndroidapplicationsarealsodevelopedusingJava.Asaresult,theJavaDevelopmentKit
(JDK)isthefirstcomponentthatmustbeinstalled.
Androiddevelopmentrequirestheinstallationofversion7oftheStandardEditionofthe
JavaPlatformDevelopmentKit.Javaisprovidedinbothdevelopment(JDK)andruntime
(JRE)packages.ForthepurposesofAndroiddevelopment,theJDKmustbeinstalled.
2.2.1WindowsJDKInstallation
ForWindowssystems,theJDKmaybeobtainedfromOracleCorporation’swebsiteusing
thefollowingURL:
http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
AssumingthatasuitableJDKisnotalreadyinstalledonyoursystem,downloadversion7
oftheJDKpackagethatmatchesthedestinationcomputersystem.Oncedownloaded,
launchtheinstallationexecutableandfollowtheonscreeninstructionstocompletethe
installationprocess.
2.2.2MacOSXJDKInstallation
JavaisnotinstalledbydefaultonrecentversionsofMacOSX.Toconfirmthepresence
orotherwiseofJava,openaTerminalwindowandenterthefollowingcommand:
java-version
AssumingthatJavaiscurrentlyinstalled,outputsimilartothefollowingwillappearinthe
terminalwindow:
javaversion“1.7.0_79-b15”
Java(TM)SERuntimeEnvironment(build1.7.0_79-b15)
JavaHotSpot(TM)64-BitServerVM(build24.79-b02,mixedmode)
IntheeventthatJavaisnotinstalled,issuingthe“java”commandintheterminalwindow
willresultintheappearanceofamessagewhichreadsasfollowstogetherwithadialogon
thedesktopprovidingaMoreInfobuttonwhich,whenclickedwilldisplaytheOracle
Javawebpage:
NoJavaruntimepresent,requestinginstall
OntheOracleJavawebpage,locateanddownloadtheJavaSE7JDKinstallation
packageforMacOSX.
Openthedownloadeddiskimage(.dmgfile)anddouble-clickontheicontoinstallthe
Javapackage(Figure2-1):
Figure2-1
TheJavaforOSXinstallerwindowwillappearandtakeyouthroughthestepsinvolved
ininstallingtheJDK.Oncetheinstallationiscomplete,returntotheTerminalwindowand
runthefollowingcommand,atwhichpointthepreviouslyoutlinedJavaversion
informationshouldappear:
java-version
2.3LinuxJDKInstallation
First,ifthechosendevelopmentsystemisrunningthe64-bitversionofUbuntuthenitis
essentialthata32-bitlibrarysupportpackagebeinstalled:
sudoapt-getinstalllib32stdc++6
AswithWindowsbasedJDKinstallation,itispossibletoinstalltheJDKonLinuxby
downloadingtheappropriatepackagefromtheOraclewebsite,theURLforwhichisas
follows:
http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
PackagesareprovidedbyOracleinRPMformat(forinstallationonRedHatLinuxbased
systemssuchasRedHatEnterpriseLinux,FedoraandCentOS)andasatararchivefor
otherLinuxdistributionssuchasUbuntu.
OnRedHatbasedLinuxsystems,downloadthe.rpmJDKfilefromtheOraclewebsite
andperformtheinstallationusingtherpmcommandinaterminalwindow.Assuming,for
example,thatthedownloadedJDKfilewasnamedjdk-7u79-linux-x64.rpm,the
commandstoperformtheinstallationwouldreadasfollows:
su
rpm–ihvjdk-7u79-linux-x64.rpm
Toinstallusingthecompressedtarpackage(tar.gz)performthefollowingsteps:
1.CreatethedirectoryintowhichtheJDKistobeinstalled(forthepurposesofthis
examplewewillassume/home/demo/java).
2.Downloadtheappropriatetar.gzpackagefromtheOraclewebsiteintothedirectory.
3.Executethefollowingcommand(where<jdk-file>isreplacedbythenameofthe
downloadedJDKfile):
tarxvfz<jdk-file>.tar.gz
4.Removethedownloadedtar.gzfile.
5.AddthepathtothebindirectoryoftheJDKinstallationtoyour$PATHvariable.For
example,assumingthattheJDKultimatelyinstalledinto/home/demo/java/jdk1.7.0_79the
followingwouldneedtobeaddedtoyour$PATHenvironmentvariable:
/home/demo/java/jdk1.7.0_79/bin
Thiscantypicallybeachievedbyaddingacommandtothe.bashrcfileinyourhome
directory(specificsmaydifferdependingontheparticularLinuxdistributioninuse).For
example,changedirectorytoyourhomedirectory,editthe.bashrcfilecontainedtherein
andaddthefollowinglineattheendofthefile(modifyingthepathtomatchthelocation
oftheJDKonyoursystem):
exportPATH=/home/demo/java/jdk1.7.0_79/bin:$PATH
Havingsavedthechange,futureterminalsessionswillincludetheJDKinthe$PATH
environmentvariable.
2.4DownloadingtheAndroidStudioPackage
MostoftheworkinvolvedindevelopingapplicationsforAndroidwillbeperformedusing
theAndroidStudioenvironment.Thecontentandexamplesinthisbookwerecreated
basedonAndroidStudioversion1.5.
AndroidStudioissubjecttofrequentupdatesanditispossible,therefore,thatamore
recentreleaseofAndroidStudioisnowavailable.Forthepurposesofcompatibilitywith
thetutorialsandexamples,however,itisrecommendedthatthisbookbeusedwith
AndroidStudioversion1.5whichmaybedownloadedfromthefollowingwebpage:
http://tools.android.com/download/studio/builds/1-5
Fromthispage,selecttheappropriatepackageforyourplatformandoperatingsystem.On
thesubsequentscreen,acceptthetermsandconditionstoinitiatethedownload.
2.5InstallingAndroidStudio
Oncedownloaded,theexactstepstoinstallAndroidStudiodifferdependingonthe
operatingsystemonwhichtheinstallationisbeingperformed.
2.5.1InstallationonWindows
LocatethedownloadedAndroidStudioinstallationexecutablefile(namedandroid-studiobundle-<version>.exe)inaWindowsExplorerwindowanddoubleclickonittostartthe
installationprocess,clickingtheYesbuttonintheUserAccountControldialogifit
appears.
OncetheAndroidStudiosetupwizardappears,workthroughthevariousscreensto
configuretheinstallationtomeetyourrequirementsintermsofthefilesystemlocation
intowhichAndroidStudioshouldbeinstalledandwhetherornotitshouldbemade
availabletootherusersofthesystem.Whenpromptedtoselectthecomponentstoinstall,
makesurethattheAndroidStudio,AndroidSDKandAndroidVirtualDeviceoptionsare
allselected.
AlthoughtherearenostrictrulesonwhereAndroidStudioshouldbeinstalledonthe
system,theremainderofthisbookwillassumethattheinstallationwasperformedintoa
sub-folderoftheuser’shomedirectorynamedAndroidStudioandthattheAndroidSDK
packageshavebeeninstalledintotheuser’sAppData\Local\Android\sdksub-folder.Once
theoptionshavebeenconfigured,clickontheInstallbuttontobegintheinstallation
process.
OnversionsofWindowswithaStartmenu,thenewlyinstalledAndroidStudiocanbe
launchedfromtheentryaddedtothatmenuduringtheinstallation.Theexecutablemaybe
pinnedtothetaskbarforeasyaccessbynavigatingtotheAndroidStudio\bindirectory,
right-clickingontheexecutableandselectingthePintoTaskbarmenuoption.Notethat
theexecutableisprovidedin32-bit(studio)and64-bit(studio64)executableversions.If
youarerunninga32-bitsystembesuretousethestudioexecutable.
2.5.2InstallationonMacOSX
AndroidStudioforMacOSXisdownloadedintheformofadiskimage(.dmg)file.
Oncetheandroid-studio-ide-<version>.dmgfilehasbeendownloaded,locateitina
FinderwindowanddoubleclickonittoopenitasshowninFigure2-2:
Figure2-2
Toinstallthepackage,simplydragtheAndroidStudioiconanddropitontothe
Applicationsfolder.TheAndroidStudiopackagewillthenbeinstalledintothe
Applicationsfolderofthesystem,aprocesswhichwilltypicallytakeafewminutesto
complete.
TolaunchAndroidStudio,locatetheexecutableintheApplicationsfolderusingaFinder
windowanddoubleclickonit.WhenattemptingtolaunchAndroidStudio,anerrordialog
mayappearindicatingthattheJVMcannotbefound.Ifthiserroroccurs,itwillbe
necessarytodownloadandinstalltheMacOSXJava6JREpackageonthesystem.This
canbedownloadedfromAppleusingthefollowinglink:
http://support.apple.com/kb/DL1572
OncetheJavaforOSXpackagehasbeeninstalled,AndroidStudioshouldlaunchwithout
anyproblems.
Forfutureeasieraccesstothetool,dragtheAndroidStudioiconfromtheFinderwindow
anddropitontothedock.
2.5.3InstallationonLinux
HavingdownloadedtheLinuxAndroidStudiopackage,openaterminalwindow,change
directorytothelocationwhereAndroidStudioistobeinstalledandexecutethefollowing
command:
unzip/<pathtopackage>/android-studio-ide-<version>-linux.zip
NotethattheAndroidStudiobundlewillbeinstalledintoasub-directorynamedandroid-
studio.Assuming,therefore,thattheabovecommandwasexecutedin/home/demo,the
softwarepackageswillbeunpackedinto/home/demo/android-studio.
TolaunchAndroidStudio,openaterminalwindow,changedirectorytotheandroidstudio/binsub-directoryandexecutethefollowingcommand:
./studio.sh
OnLinuxitmayalsobenecessarytospecifythelocationoftheJavaDevelopmentKit
usingthefollowingsteps:
1. LaunchAndroidStudioandcreateanewproject.
2. SelecttheFile->OtherSettings->DefaultProjectStructure…menuoption.
3. EnterthefullpathtothedirectorycontainingtheJDKintotheJDKLocationfield.
4. ClickApplyfollowedbyOK.
2.6TheAndroidStudioSetupWizard
ThefirsttimethatAndroidStudioislaunchedafterbeinginstalled,adialogwillappear
providingtheoptiontoimportsettingsfromapreviousAndroidStudioversion.Ifyou
havesettingsfromapreviousversionandwouldliketoimportthemintothelatest
installation,selecttheappropriateoptionandlocation.Alternatively,indicatethatyoudo
notneedtoimportanyprevioussettingsandclickontheOKbuttontoproceed.
Next,thesetupwizardmayappearasshowninFigure2-3thoughthisdialogdoesnot
appearonallplatforms:
Figure2-3
Ifthewizardappears,clickontheNextbutton,choosetheStandardinstallationoptionand
clickonNextonceagain.
AndroidStudiowillproceedtodownloadandconfigurethelatestAndroidSDKandsome
additionalcomponentsandpackages.Oncethisprocesshascompleted,clickontheFinish
buttonintheDownloadingComponentsdialogatwhichpointtheWelcometoAndroid
Studioscreenshouldthenappear:
Figure2-4
2.7InstallingAdditionalAndroidSDKPackages
ThestepsperformedsofarhaveinstalledJava,theAndroidStudioIDEandthecurrentset
ofdefaultAndroidSDKpackages.Beforeproceeding,itisworthtakingsometimeto
verifywhichpackagesareinstalledandtoinstallanymissingorupdatedpackages.
ThistaskcanbeperformedusingtheAndroidSDKSettingsscreen,whichmaybe
launchedfromwithintheAndroidStudiotoolbyselectingtheConfigure->SDK
ManageroptionfromwithintheAndroidStudiowelcomedialog.Onceinvoked,the
AndroidSDKscreenofthedefaultsettingsdialogwillappearasshowninFigure2-5:
Figure2-5
ImmediatelyafterinstallingAndroidStudioforthefirsttimeitislikelythatonlythelatest
versionoftheAndroidSDKhasbeeninstalled.ToinstallolderversionsoftheAndroid
SDKsimplyselectthecheckboxescorrespondingtotheversionsandclickontheApply
button.
ItisalsopossiblethatupdateswillbelistedasbeingavailableforthelatestSDK.To
accessdetailedinformationaboutthepackagesthatareavailableforupdate,enablethe
ShowPackageDetailsoptionlocatedinthelowerrighthandcornerofthescreen.This
willdisplayinformationsimilartothatshowninFigure2-6:
Figure2-6
Theabovefigurehighlightstheavailabilityofanupdate.Toinstalltheupdates,enablethe
checkboxtotheleftoftheitemnameandclickontheApplybutton.
InadditiontotheAndroidSDKpackages,anumberoftoolsarealsoinstalledforbuilding
Androidapplications.Toviewthecurrentlyinstalledpackagesandcheckforupdates,
remainwithintheSDKsettingsscreenandselecttheSDKToolstabasshowninFigure27:
Figure2-7
WithintheAndroidSDKToolsscreen,makesurethatthefollowingpackagesarelistedas
InstalledintheStatuscolumn:
· AndroidSDKBuild-tools
· AndroidSDKTools
· AndroidSDKPlatform-tools
· AndroidSupportRepository
· AndroidSupportLibrary
· GoogleRepository
· GoogleUSBDriver(Windowsonly)
· Intelx86EmulatorAccelerator(HAXMinstaller)
IntheeventthatanyoftheabovepackagesarelistedasNotInstalledorrequiringan
update,simplyselectthecheckboxesnexttothosepackagesandclickontheApplybutton
toinitiatetheinstallationprocess.
Oncetheinstallationiscomplete,reviewthepackagelistandmakesurethattheselected
packagesarenowlistedasInstalledintheStatuscolumn.IfanyarelistedasNotinstalled,
makesuretheyareselectedandclickontheInstallpackages…buttonagain.
AnalternativetousingtheAndroidSDKsettingspanelistoaccesstheStandaloneSDK
Managerwhichcanbelaunchedusingthelinkinthelowerlefthandcornerofthesettings
screen.TheStandaloneSDKManager(Figure2-8)providesasimilarlistofpackages
togetherwithoptionstoperformupdateandinstallationtasks:
Figure2-8
2.8MakingtheAndroidSDKToolsCommand-lineAccessible
Mostofthetime,theunderlyingtoolsoftheAndroidSDKwillbeaccessedfromwithin
theAndroidStudioenvironment.Thatbeingsaid,however,therewillalsobeinstances
whereitwillbeusefultobeabletoinvokethosetoolsfromacommandpromptor
terminalwindow.Inorderfortheoperatingsystemonwhichyouaredevelopingtobe
abletofindthesetools,itwillbenecessarytoaddthemtothesystem’sPATHenvironment
variable.
Regardlessofoperatingsystem,thePATHvariableneedstobeconfiguredtoincludethe
followingpaths(where<path_to_android_sdk_installation>representsthefilesystem
locationintowhichtheAndroidSDKwasinstalled):
<path_to_android_sdk_installation>/sdk/tools
<path_to_android_sdk_installation>/sdk/platform-tools
ThelocationoftheSDKonyoursystemcanbeidentifiedbylaunchingtheStandalone
SDKManagerandreferringtotheAndroidSDKLocation:fieldlocatedatthetopofthe
settingspanelashighlightedinFigure2-9:
Figure2-9
OncethelocationoftheSDKhasbeenidentified,thestepstoaddthistothePATH
variableareoperatingsystemdependent:
2.8.1Windows7
1. Right-clickonComputerinthedesktopstartmenuandselectPropertiesfromthe
resultingmenu.
2. Inthepropertiespanel,selecttheAdvancedSystemSettingslinkand,intheresulting
dialog,clickontheEnvironmentVariables…button.
3. IntheEnvironmentVariablesdialog,locatethePathvariableintheSystemvariables
list,selectitandclickonEdit….Locatetheendofthecurrentvariablevaluestringand
appendthepathtotheAndroidplatformtoolstotheend,usingasemicolontoseparate
thepathfromtheprecedingvalues.Forexample,assumingtheAndroidSDKwas
installedintoC:\Users\demo\AppData\Local\Android\sdk,thefollowingwouldbe
appendedtotheendofthecurrentPathvalue:
;C:\Users\demo\AppData\Local\Android\sdk\platformtools;C:\Users\demo\AppData\Local\Android\sdk\tools
4. ClickonOKineachdialogboxandclosethesystempropertiescontrolpanel.
Oncetheabovestepsarecomplete,verifythatthepathiscorrectlysetbyopeninga
CommandPromptwindow(Start->AllPrograms->Accessories->CommandPrompt)
andatthepromptenter:
echo%Path%
ThereturnedpathvariablevalueshouldincludethepathstotheAndroidSDKplatform
toolsfolders.Verifythattheplatform-toolsvalueiscorrectbyattemptingtoruntheadb
toolasfollows:
adb
Thetoolshouldoutputalistofcommandlineoptionswhenexecuted.
Similarly,checkthetoolspathsettingbyattemptingtolaunchtheAndroidSDKManager:
android
Intheeventthatamessagesimilartothefollowingmessageappearsforoneorbothofthe
commands,itismostlikelythatanincorrectpathwasappendedtothePathenvironment
variable:
‘adb’isnotrecognizedasaninternalorexternalcommand,
operableprogramorbatchfile.
2.8.2Windows8.1
1. Onthestartscreen,movethemousetothebottomrighthandcornerofthescreen
andselectSearchfromtheresultingmenu.Inthesearchbox,enterControlPanel.
WhentheControlPaneliconappearsintheresultsarea,clickonittolaunchthetool
onthedesktop.
2. WithintheControlPanel,usetheCategorymenutochangethedisplaytoLarge
Icons.FromthelistoficonsselecttheonelabeledSystem.
3. FollowthestepsoutlinedforWindows7startingfromstep2throughtostep4.
Openthecommandpromptwindow(movethemousetothebottomrighthandcornerof
thescreen,selecttheSearchoptionandentercmdintothesearchbox).SelectCommand
Promptfromthesearchresults.
WithintheCommandPromptwindow,enter:
echo%Path%
ThereturnedpathvariablevalueshouldincludethepathstotheAndroidSDKplatform
toolsfolders.Verifythattheplatform-toolsvalueiscorrectbyattemptingtoruntheadb
toolasfollows:
adb
Thetoolshouldoutputalistofcommandlineoptionswhenexecuted.
Similarly,checkthetoolspathsettingbyattemptingtolaunchtheAndroidSDKManager:
android
Intheeventthatamessagesimilartothefollowingmessageappearsforoneorbothofthe
commands,itismostlikelythatanincorrectpathwasappendedtothePathenvironment
variable:
‘adb’isnotrecognizedasaninternalorexternalcommand,
operableprogramorbatchfile.
2.8.3Windows10
Right-clickontheStartmenu,selectSystemfromtheresultingmenuandclickonthe
AdvancedsystemsettingsoptionintheSystemwindow.Followthestepsoutlinedfor
Windows7startingfromstep2throughtostep4.
2.8.4Linux
OnLinuxthiswillinvolveonceagaineditingthe.bashrcfile.AssumingthattheAndroid
SDKbundlepackagewasinstalledinto/home/demo/Android/sdk,theexportlineinthe
.bashrcfilewouldnowreadasfollows:
export
PATH=/home/demo/java/jdk1.7.0_10/bin:/home/demo/Android/sdk/platformtools:/home/demo/Android/sdk/tools:/home/demo/android-studio/bin:$PATH
Notealsothattheabovecommandaddstheandroid-studio/bindirectorytothePATH
variable.Thiswillenablethestudio.shscripttobeexecutedregardlessofthecurrent
directorywithinaterminalwindow.
2.8.5MacOSX
Anumberoftechniquesmaybeemployedtomodifythe$PATHenvironmentvariableon
MacOSX.Arguablythecleanestmethodistoaddanewfileinthe/etc/paths.ddirectory
containingthepathstobeaddedto$PATH.AssuminganAndroidSDKinstallation
locationof/Users/demo/Library/Android/sdk,thepathmaybeconfiguredbycreatinga
newfilenamedandroid-sdkinthe/etc/paths.ddirectorycontainingthefollowinglines:
/Users/demo/Library/Android/sdk/tools
/Users/demo/Library/Android/sdk/platform-tools
Notethatsincethisisasystemdirectoryitwillbenecessarytousethesudocommand
whencreatingthefile.Forexample:
sudovi/etc/paths.d/android-sdk
2.9UpdatingtheAndroidStudioandtheSDK
FromtimetotimenewversionsofAndroidStudioandtheAndroidSDKarereleased.
NewversionsoftheSDKareinstalledusingtheAndroidSDKManager.AndroidStudio
willtypicallynotifyyouwhenanupdateisreadytobeinstalled.
TomanuallycheckforAndroidStudioupdates,clickontheCheckforupdatesnowlink
locatedatthebottomoftheAndroidStudiowelcomescreen,orusetheHelp->Checkfor
Update…menuoptionaccessiblefromwithintheAndroidStudiomainwindow.
2.10Summary
PriortobeginningthedevelopmentofAndroidbasedapplications,thefirststepistoset
upasuitabledevelopmentenvironment.ThisconsistsoftheJavaDevelopmentKit(JDK),
AndroidSDKs,andAndroidStudioIDE.Inthischapter,wehavecoveredthesteps
necessarytoinstallthesepackagesonWindows,MacOSXandLinux.
3.CreatinganExampleAndroidAppin
AndroidStudio
Theprecedingchaptersofthisbookhavecoveredthestepsnecessarytoconfigurean
environmentsuitableforthedevelopmentofAndroidapplicationsusingtheAndroid
StudioIDE.Beforemovingontoslightlymoreadvancedtopics,nowisagoodtimeto
validatethatalloftherequireddevelopmentpackagesareinstalledandfunctioning
correctly.ThebestwaytoachievethisgoalistocreateanAndroidapplicationand
compileandrunit.ThischapterwillcoverthecreationofasimpleAndroidapplication
projectusingAndroidStudio.Oncetheprojecthasbeencreated,alaterchapterwill
exploretheuseoftheAndroidemulatorenvironmenttoperformatestrunofthe
application.
3.1CreatingaNewAndroidProject
Thefirststepintheapplicationdevelopmentprocessistocreateanewprojectwithinthe
AndroidStudioenvironment.Begin,therefore,bylaunchingAndroidStudiosothatthe
“WelcometoAndroidStudio”screenappearsasillustratedinFigure3-1:
Figure3-1
Oncethiswindowappears,AndroidStudioisreadyforanewprojecttobecreated.To
createthenewproject,simplyclickontheStartanewAndroidStudioprojectoptionto
displaythefirstscreenoftheNewProjectwizardasshowninFigure3-2:
Figure3-2
3.2DefiningtheProjectandSDKSettings
IntheNewProjectwindow,settheApplicationnamefieldtoAndroidSample.The
applicationnameisthenamebywhichtheapplicationwillbereferencedandidentified
withinAndroidStudioandisalsothenamethatwillbeusedwhenthecompleted
applicationgoesonsaleintheGooglePlaystore.
ThePackageNameisusedtouniquelyidentifytheapplicationwithintheAndroid
applicationecosystem.ItshouldbebasedonthereversedURLofyourdomainname
followedbythenameoftheapplication.Forexample,ifyourdomainis
www.mycompany.com,andtheapplicationhasbeennamedAndroidSample,thenthe
packagenamemightbespecifiedasfollows:
com.mycompany.androidsample
Ifyoudonothaveadomainname,youmayalsouseebookfrenzy.comforthepurposesof
testing,thoughthiswillneedtobechangedbeforeanapplicationcanbepublished:
com.ebookfrenzy.androidsample
TheProjectlocationsettingwilldefaulttoalocationinthefoldernamed
AndroidStudioProjectslocatedinyourhomedirectoryandmaybechangedbyclickingon
thebuttontotherightofthetextfieldcontainingthecurrentpathsetting.
ClickNexttoproceed.Ontheformfactorsscreen,enablethePhoneandTabletoptionand
settheminimumSDKsettingtoAPI8:Android2.2(Froyo).Thereasonforselectingan
olderSDKreleaseisthatthisensuresthatthefinishedapplicationwillbeabletorunon
thewidestpossiblerangeofAndroiddevices.ThehighertheminimumSDKselection,the
moretheapplicationwillberestrictedtonewerAndroiddevices.Ausefulchart(Figure33)canbeviewedbyclickingontheHelpmechooselink.ThisoutlinesthevariousSDK
versionsandAPIlevelsavailableforuseandthepercentageofAndroiddevicesinthe
marketplaceonwhichtheapplicationwillrunifthatSDKisusedastheminimumlevel.
IngeneralitshouldonlybenecessarytoselectamorerecentSDKwhenthatrelease
containsaspecificfeaturethatisrequiredforyourapplication.Tohelpinthedecision
process,selectinganAPIlevelfromthechartwilldisplaythefeaturesthataresupported
atthatlevel.
Figure3-3
SincetheprojectisnotintendedforGoogleTV,AndroidAutoorwearabledevices,leave
theremainingoptionsdisabledbeforeclickingNext.
3.3CreatinganActivity
Thenextstepistodefinethetypeofinitialactivitythatistobecreatedfortheapplication.
ArangeofdifferentactivitytypesisavailablewhendevelopingAndroidapplications.The
Empty,Master/DetailFlow,GoogleMapsandNavigationDraweroptionswillbecovered
extensivelyinlaterchapters.Forthepurposesofthisexample,however,simplyselectthe
optiontocreateaBlankActivity.Theblankactivityoptioncreatesatemplateuser
interfaceconsistingofanappbar,menu,contentareaandasinglefloatingactionbutton.
Figure3-4
WiththeBlankActivityoptionselected,clickNext.Onthefinalscreen(Figure3-5)name
theactivityandtitleAndroidSampleActivity.Theactivitywillconsistofasingleuser
interfacescreenlayoutwhich,forthepurposesofthisexample,shouldbenamed
activity_android_sampleasshowninFigure3-5andwithamenuresourcenamed
menu_android_sample:
Figure3-5
Finally,clickonFinishtoinitiatetheprojectcreationprocess.
3.4ModifyingtheExampleApplication
Atthispoint,AndroidStudiohascreatedaminimalexampleapplicationprojectand
openedthemainwindow.
Figure3-6
ThenewlycreatedprojectandreferencestoassociatedfilesarelistedintheProjecttool
windowlocatedonthelefthandsideofthemainprojectwindow.TheProjecttool
windowhasanumberofmodesinwhichinformationcanbedisplayed.Bydefault,this
panelwillbeinAndroidmode.Thissettingiscontrolledbythedropdownmenuatthetop
ofthepanelashighlightedinFigure3-6.IfthepanelisnotcurrentlyinAndroidmode,
clickonthismenuandswitchtoAndroidmode:
Figure3-7
Theexampleprojectcreatedforuswhenweselectedtheoptiontocreateanactivity
consistsofauserinterfacecontainingalabelthatwillread“HelloWorld”whenthe
applicationisexecuted.
Thenextstepinthistutorialistomodifytheuserinterfaceofourapplicationsothatit
displaysalargertextviewobjectwithadifferentmessagetotheoneprovidedforusby
AndroidStudio.
Theuserinterfacedesignforouractivityisstoredinafilenamed
activity_android_sample.xmlwhich,inturn,islocatedunderapp->res->layoutinthe
projectfilehierarchy.Thislayoutfileincludestheappbar(alsoknownasanactionbar)
thatappearsacrossthetopofthedevicescreen(markedAinFigure3-8)andthefloating
actionbutton(theemailbuttonmarkedB).Inadditiontotheseitems,the
activity_android_sample.xmllayoutfilecontainsareferencetoasecondfilecontaining
thecontentlayout(markedC):
Figure3-8
Bydefault,thecontentlayoutiscontainedwithinafilenamed
content_android_studio.xmlanditiswithinthisfilethatchangestothelayoutofthe
activityaremade.UsingtheProjecttoolwindow,locatethisfileasillustratedinFigure39:
Figure3-9
Oncelocated,doubleclickonthefiletoloaditintotheuserinterfaceDesignertoolwhich
willappearinthecenterpaneloftheAndroidStudiomainwindow:
Figure3-10
InthetoolbaracrossthetopoftheDesignerwindowisamenucurrentlysettoNexus4
whichisreflectedinthevisualrepresentationofthedevicewithintheDesignerpanel.A
widerangeofotherdeviceoptionsareavailableforselectionbyclickingonthismenu.
Tochangetheorientationofthedevicerepresentationbetweenlandscapeandportrait
simplyusethedropdownmenuimmediatelytotherightofthedeviceselectionmenu
showingthe
icon.
Ascanbeseeninthedevicescreen,thecontentlayoutalreadyincludesalabelthat
displaysa“HelloWorld!”message.Runningdownthelefthandsideofthepanelisa
palettecontainingdifferentcategoriesofuserinterfacecomponentsthatmaybeusedto
constructauserinterface,suchasbuttons,labelsandtextfields.Itshouldbenoted,
however,thatnotalluserinterfacecomponentsareobviouslyvisibletotheuser.Onesuch
categoryconsistsoflayouts.Androidsupportsavarietyoflayoutsthatprovidedifferent
levelsofcontroloverhowvisualuserinterfacecomponentsarepositionedandmanaged
onthescreen.Thoughitisdifficulttotellfromlookingatthevisualrepresentationofthe
userinterface,thecurrentdesignhasbeencreatedusingaRelativeLayout.Thiscanbe
confirmedbyreviewingtheinformationintheComponentTreepanelwhich,bydefault,is
locatedintheupperrighthandcorneroftheDesignerpanelandisshowninFigure3-11:
Figure3-11
Aswecanseefromthecomponenttreehierarchy,theuserinterfacelayoutisembeddedin
theactivity_android_sample.xmllayoutfileandconsistsofaRelativeLayoutparentwitha
singlechildintheformofaTextViewobject.
ThefirststepinmodifyingtheapplicationistodeletetheTextViewcomponentfromthe
design.BeginbyclickingontheTextViewobjectwithintheuserinterfaceviewsothatit
appearswithablueborderaroundit.Onceselected,presstheDeletekeyonthekeyboard
toremovetheobjectfromthelayout.
InthePalettepanel,locatetheWidgetscategory.ClickanddragtheLargeTextobjectand
dropitinthecenteroftheuserinterfacedesignwhenthegreenmarkerlinesappearto
indicatethecenterofthedisplay:
Figure3-12
TheAndroidStudioDesignertoolalsoprovidesanalternativetodragginganddropping
componentsfromthepaletteontothedesignlayout.Componentsmayalsobeaddedby
selectingtherequiredobjectfromthepaletteandthensimplyclickingonthelayoutatthe
locationwherethecomponentistobeplaced.
ThenextstepistochangethetextthatiscurrentlydisplayedbytheTextViewcomponent.
Doubleclickontheobjectinthedesignlayouttodisplaythetextandideditingpanelas
illustratedinFigure3-13.Withinthepanel,changethetextpropertyfrom“LargeText”to
“WelcometoAndroidStudio”.
Figure3-13
Atthispointitisimportanttoexplainthelightbulbthathasnowappearednexttothe
TextViewobjectinthelayout.Thisindicatesapossibleproblemandprovidessome
recommendedsolutions.Clickingontheiconinthisinstanceinformsusthattheproblem
isasfollows:
[I18N]Hardcodedstring“WelcometoAndroidStudio”,shoulduse@string
resource
ThisI18Nmessageisinformingusthatapotentialissueexistswithregardtothefuture
internationalizationoftheproject(“I18N”comesfromthefactthattheword
“internationalization”beginswithan“I”,endswithan“N”andhas18lettersinbetween).
ThewarningisremindingusthatwhendevelopingAndroidapplications,attributesand
valuessuchastextstringsshouldbestoredintheformofresourceswhereverpossible.
Doingsoenableschangestotheappearanceoftheapplicationtobemadebymodifying
resourcefilesinsteadofchangingtheapplicationsourcecode.Thiscanbeespecially
valuablewhentranslatingauserinterfacetoadifferentspokenlanguage.Ifallofthetext
inauserinterfaceiscontainedinasingleresourcefile,forexample,thatfilecanbegiven
toatranslatorwhowillthenperformthetranslationworkandreturnthetranslatedfilefor
inclusionintheapplication.Thisenablesmultiplelanguagestobetargetedwithoutthe
necessityforanysourcecodechangestobemade.Inthisinstance,wearegoingtocreate
anewresourcenamedwelcomestringandassigntoitthestring“WelcometoAndroid
Studio”.
Clickonthearrowtotherightofthewarningmessagetodisplaythemenuofpossible
solutions(Figure3-14).
Figure3-14
Fromthemenu,selecttheExtractstringresourceoptiontodisplaytheExtractResource
dialog.Inthisdialog,enterwelcomestringintotheResourcename:fieldbeforeclicking
onOK.Thestringisnowstoredasaresourceintheapp->res->values->strings.xml
file.Ifthelayoutdisplaysthenameofthestringresourceinsteadofthe“Welcometo
AndroidStudio”text,clickontherefreshbuttonlocatedinthetoolbarashighlightedin
Figure3-15:
Figure3-15
3.5ReviewingtheLayoutandResourceFiles
Beforemovingontothenextchapter,wearegoingtolookatsomeoftheinternalaspects
ofuserinterfacedesignandresourcehandling.Intheprevioussection,wemadesome
changestotheuserinterfacebymodifyingthecontent_android_sample.xmlfileusingthe
UIDesignertool.Infact,allthattheDesignerwasdoingwasprovidingauser-friendly
waytoedittheunderlyingXMLcontentofthefile.Inpractice,thereisnoreasonwhyyou
cannotmodifytheXMLdirectlyinordertomakeuserinterfacechangesand,insome
instances,thismayactuallybequickerthanusingtheDesignertool.Atthebottomofthe
DesignerpanelaretwotabslabeledDesignandTextrespectively.ToswitchtotheXML
viewsimplyselecttheTexttabasshowninFigure3-16:
Figure3-16
AscanbeseenfromthestructureoftheXMLfile,theuserinterfaceconsistsofthe
RelativeLayoutcomponent,whichinturn,istheparentoftheTextViewobject.Wecan
alsoseethatthetextpropertyoftheTextViewissettoourwelcomestringresource.
Althoughvaryingincomplexityandcontent,alluserinterfacelayoutsarestructuredin
thishierarchical,XMLbasedway.
OneofthemorepowerfulfeaturesofAndroidStudiocanbefoundtotherighthandside
oftheXMLeditingpanel.Ifthepanelisnotvisible,displayitbyselectingthePreview
buttonlocatedalongtherighthandedgeoftheAndroidStudiowindow.Thisisthe
Previewpanelandshowsthecurrentvisualstateofthelayout.Aschangesaremadetothe
XMLlayout,thesewillbereflectedinthepreviewpanel.Toseethisinaction,modifythe
XMLlayouttochangethebackgroundcoloroftheRelativeLayouttoashadeofredas
follows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
xmlns:app=“http://schemas.android.com/apk/res-auto”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
app:layout_behavior=”@string/appbar_scrolling_view_behavior”
tools:showIn=”@layout/activity_android_sample”
tools:context=”.AndroidSampleActivity”
android:background=”#ff2438”>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:textAppearance=”?android:attr/textAppearanceLarge”
android:text=”@string/welcomestring”
android:id=”@+id/textView”
android:layout_centerVertical=“true”
android:layout_centerHorizontal=“true”/>
</RelativeLayout>
Notethatthecolorofthepreviewchangesinreal-timetomatchthenewsettinginthe
XMLfile.Notealsothatasmallredsquareappearsinthelefthandmargin(alsoreferred
toasthegutter)oftheXMLeditornexttothelinecontainingthecolorsetting.Thisisa
visualcuetothefactthatthecolorredhasbeensetonaproperty.Changethecolorvalue
to#a0ff28andnotethatboththesmallsquareinthemarginandthepreviewchangeto
green.
Finally,usetheProjectviewtolocatetheapp->res->values->strings.xmlfileand
doubleclickonittoloaditintotheeditor.CurrentlytheXMLshouldreadasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<resources>
<stringname=“app_name”>AndroidSample</string>
<stringname=“action_settings”>Settings</string>
<stringname=“welcomestring”>WelcometoAndroidStudio</string>
</resources>
Asademonstrationofresourcesinaction,changethestringvaluecurrentlyassignedto
thewelcomestringresourceandthenreturntotheDesignertoolbyselectingthetabforthe
layoutfileintheeditorpanel.Notethatthelayouthaspickedupthenewresourcevalue
forthewelcomestring.
ThereisalsoaquickwaytoaccessthevalueofaresourcereferencedinanXMLfile.
WiththeDesignertoolinTextmode,clickonthe“@string/welcomestring”property
settingsothatithighlightsandthenpressCtrl+Bonthekeyboard.AndroidStudiowill
subsequentlyopenthestrings.xmlfileandtakeyoutothelineinthatfilewherethis
resourceisdeclared.Usethisopportunitytorevertthestringresourcebacktotheoriginal
“WelcometoAndroidStudio”text.
3.6PreviewingtheLayout
Sofarinthischapter,thelayouthasonlybeenpreviewedonarepresentationoftheNexus
4device.Aspreviouslydiscussed,thelayoutcanbetestedforotherdevicesbymaking
selectionsfromthedevicemenuinthetoolbaracrossthetopedgeoftheDesignerpanel.
AnotherusefuloptionprovidedbythismenuisPreviewAllScreenSizeswhich,when
selected,showsthelayoutinallcurrentlyconfigureddeviceconfigurationsas
demonstratedinFigure3-17.
Toreverttoasinglepreviewlayout,selectthedevicemenuonceagain,thistimechoosing
theRemovePreviewsoption.
Figure3-17
3.7Summary
Whilenotexcessivelycomplex,anumberofstepsareinvolvedinsettingupanAndroid
developmentenvironment.Havingperformedthosesteps,itisworthworkingthrougha
simpleexampletomakesuretheenvironmentiscorrectlyinstalledandconfigured.Inthis
chapter,wehavecreatedasimpleapplicationandthenusedtheAndroidStudioDesigner
tooltomodifytheuserinterfacelayout.Indoingso,weexploredtheimportanceofusing
resourceswhereverpossible,particularlyinthecaseofstringvalues,andbrieflytouched
onthetopicoflayouts.Finally,welookedattheunderlyingXMLthatisusedtostorethe
userinterfacedesignsofAndroidapplications.
WhileitisusefultobeabletopreviewalayoutfromwithintheAndroidStudioDesigner
tool,thereisnosubstitutefortestinganapplicationbycompilingandrunningit.Inalater
chapterentitledCreatinganAndroidVirtualDevice(AVD)inAndroidStudio,thesteps
necessarytosetupanemulatorfortestingpurposeswillbecoveredindetail.Before
runningtheapplication,however,thenextchapterwilltakeasmalldetourtoprovidea
guidedtouroftheAndroidStudiouserinterface.
4.ATouroftheAndroidStudioUser
Interface
Whilstitistemptingtoplungeintorunningtheexampleapplicationcreatedinthe
previouschapter,doingsoinvolvesusingaspectsoftheAndroidStudiouserinterface
whicharebestdescribedinadvance.
AndroidStudioisapowerfulandfeaturerichdevelopmentenvironmentthatis,toalarge
extent,intuitivetouse.Thatbeingsaid,takingthetimenowtogainfamiliaritywiththe
layoutandorganizationoftheAndroidStudiouserinterfacewillconsiderablyshortenthe
learningcurveinlaterchaptersofthebook.Withthisinmind,thischapterwillprovidean
initialoverviewofthevariousareasandcomponentsthatmakeuptheAndroidStudio
environment.
4.1TheWelcomeScreen
Thewelcomescreen(Figure4-1)isdisplayedanytimethatAndroidStudioisrunning
withnoprojectscurrentlyopen(openprojectscanbeclosedatanytimebyselectingthe
File->CloseProjectmenuoption).IfAndroidStudiowaspreviouslyexitedwhilea
projectwasstillopen,thetoolwillby-passthewelcomescreennexttimeitislaunched,
automaticallyopeningthepreviouslyactiveproject.
Figure4-1
Inadditiontoalistofrecentprojects,theQuickStartmenuprovidesarangeofoptionsfor
performingtaskssuchasopening,creatingandimportingprojectsalongwithaccessto
projectscurrentlyunderversioncontrol.Inaddition,theConfigureoptionprovidesaccess
totheSDKManageralongwithavastarrayofsettingsandconfigurationoptions.A
reviewoftheseoptionswillquicklyrevealthatthereisalmostnoaspectofAndroid
Studiothatcannotbeconfiguredandtailoredtoyourspecificneeds.
Finally,thestatusbaralongthebottomedgeofthewindowprovidesinformationaboutthe
versionofAndroidStudiocurrentlyrunning,alongwithalinktocheckifupdatesare
availablefordownload.
4.2TheMainWindow
Whenanewprojectiscreated,oranexistingoneopened,theAndroidStudiomain
windowwillappear.Whenmultipleprojectsareopensimultaneously,eachwillbe
assigneditsownmainwindow.Thepreciseconfigurationofthewindowwillvary
dependingonwhichtoolsandpanelsweredisplayedthelasttimetheprojectwasopen,
butwilltypicallyresemblethatofFigure4-2.
Figure4-2
Thevariouselementsofthemainwindowcanbesummarizedasfollows:
A–MenuBar–ContainsarangeofmenusforperformingtaskswithintheAndroid
Studioenvironment.
B–Toolbar–Aselectionofshortcutstofrequentlyperformedactions.Thetoolbar
buttonsprovidequickeraccesstoaselectgroupofmenubaractions.Thetoolbarcanbe
customizedbyright-clickingonthebarandselectingtheCustomizeMenusand
Toolbars…menuoption.
C–NavigationBar–Thenavigationbarprovidesaconvenientwaytomovearoundthe
filesandfoldersthatmakeuptheproject.Clickingonanelementinthenavigationbar
willdropdownamenulistingthesubfoldersandfilesatthatlocationreadyforselection.
ThisprovidesanalternativetotheProjecttoolwindow.
D–EditorWindow–Theeditorwindowdisplaysthecontentofthefileonwhichthe
developeriscurrentlyworking.Whatgetsdisplayedinthislocation,however,issubjectto
context.Wheneditingcode,forexample,thecodeeditorwillappear.Whenworkingona
userinterfacelayoutfile,ontheotherhand,theuserinterfaceDesignertoolwillappear.
Whenmultiplefilesareopen,eachfileisrepresentedbyatablocatedalongthetopedge
oftheeditorasshowninFigure4-3.
Figure4-3
E–StatusBar–Thestatusbardisplaysinformationalmessagesabouttheprojectandthe
activitiesofAndroidStudiotogetherwiththetoolsmenubuttonlocatedinthefarleft
corner.Hoveringoveritemsinthestatusbarwillprovideadescriptionofthatfield.Many
fieldsareinteractive,allowingtheusertoclicktoperformtasksorobtainmoredetailed
statusinformation.
F–ProjectToolWindow–Theprojecttoolwindowprovidesahierarchicaloverviewof
theprojectfilestructureallowingnavigationtospecificfilesandfolderstobeperformed.
Thedrop-downmenuinthetoolbarcanbeusedtodisplaytheprojectinanumberof
differentways.ThedefaultsettingistheAndroidviewwhichisthemodeprimarilyused
intheremainderofthisbook.
Theprojecttoolwindowisjustoneofanumberoftoolwindowsavailablewithinthe
AndroidStudioenvironment.
4.3TheToolWindows
Inadditiontotheprojectviewtoolwindow,AndroidStudioalsoincludesanumberof
otherwindowswhich,whenenabled,aredisplayedalongthebottomandsidesofthemain
window.Thetoolwindowquickaccessmenucanbeaccessedbyhoveringthemouse
pointeroverthebuttonlocatedinthefarlefthandcornerofthestatusbar(Figure4-4)
withoutclickingthemousebutton.
Figure4-4
Selectinganitemfromthequickaccessmenuwillcausethecorrespondingtoolwindow
toappearwithinthemainwindow.
Alternatively,asetoftoolwindowbarscanbedisplayedbyclickingonthequickaccess
menuiconinthestatusbar.Thesebarsappearalongtheleft,rightandbottomedgesofthe
mainwindow(asindicatedbythearrowsinFigure4-5)andcontainbuttonsforshowing
andhidingeachofthetoolwindows.Whenthetoolwindowbarsaredisplayed,asecond
clickonthebuttoninthestatusbarwillhidethem.
Figure4-5
Clickingonabuttonwilldisplaythecorrespondingtoolwindowwhileasecondclickwill
hidethewindow.Buttonsprefixedwithanumber(forexample1:Project)indicatethatthe
toolwindowmayalsobedisplayedbypressingtheAltkeyonthekeyboard(orthe
CommandkeyforMacOSX)togetherwiththecorrespondingnumber.
Thelocationofabuttoninatoolwindowbarindicatesthesideofthewindowagainst
whichthewindowwillappearwhendisplayed.Thesepositionscanbechangedby
clickinganddraggingthebuttonstodifferentlocationsinotherwindowtoolbars.
Eachtoolwindowhasitsowntoolbaralongthetopedge.Thebuttonswithinthese
toolbarsvaryfromonetooltothenext,thoughalltoolwindowscontainasettingsoption,
representedbythecogicon,whichallowsvariousaspectsofthewindowtobechanged.
Figure4-6showsthesettingsmenufortheprojectviewtoolwindow.Optionsare
available,forexample,toundockawindowandtoallowittofloatoutsideofthe
boundariesoftheAndroidStudiomainwindowandtomoveandresizethetoolpanel.
Figure4-6
Allofthewindowsalsoincludeafarrightbuttononthetoolbarprovidinganadditional
waytohidethetoolwindowfromview.Asearchoftheitemswithinatoolwindowcanbe
performedsimplybygivingthatwindowfocusbyclickinginitandthentypingthesearch
term(forexamplethenameofafileintheProjecttoolwindow).Asearchboxwillappear
inthewindow’stoolbaranditemsmatchingthesearchhighlighted.
AndroidStudiooffersawiderangeofwindowtoolwindows,themostcommonlyusedof
whichareasfollows:
Project–Theprojectviewprovidesanoverviewofthefilestructurethatmakesupthe
projectallowingforquicknavigationbetweenfiles.Generally,doubleclickingonafilein
theprojectviewwillcausethatfiletobeloadedintotheappropriateeditingtool.
Structure–Thestructuretoolprovidesahighlevelviewofthestructureofthesourcefile
currentlydisplayedintheeditor.Thisinformationincludesalistofitemssuchasclasses,
methodsandvariablesinthefile.Selectinganitemfromthestructurelistwilltakeyouto
thatlocationinthesourcefileintheeditorwindow.
Captures–Thecapturestoolwindowprovidesaccesstoperformancedatafilesthathave
beengeneratedbythemonitoringtoolscontainedwithintheAndroidMonitortool
window.
Favorites–Avarietyofprojectitemscanbeaddedtothefavoriteslist.Right-clickingon
afileintheprojectview,forexample,providesaccesstoanAddtoFavoritesmenu
option.Similarly,amethodinasourcefilecanbeaddedasafavoritebyright-clickingon
itintheStructuretoolwindow.AnythingaddedtoaFavoriteslistcanbeaccessedthrough
thisFavoritestoolwindow.
BuildVariants–Thebuildvariantstoolwindowprovidesaquickwaytoconfigure
differentbuildtargetsforthecurrentapplicationproject(forexampledifferentbuildsfor
debuggingandreleaseversionsoftheapplication,ormultiplebuildstotargetdifferent
devicecategories).
TODO–Asthenamesuggests,thistoolprovidesaplacetoreviewitemsthathaveyetto
becompletedontheproject.AndroidStudiocompilesthislistbyscanningthesourcefiles
thatmakeuptheprojecttolookforcommentsthatmatchspecifiedTODOpatterns.These
patternscanbereviewedandchangedbyselectingtheFile->Settings…menuoptionand
navigatingtotheTODOpagelistedunderEditor.
Messages–ThemessagestoolwindowrecordsoutputfromtheGradlebuildsystem
(GradleistheunderlyingsystemusedbyAndroidStudioforbuildingthevariouspartsof
projectsintorunnableapplications)andcanbeusefulforidentifyingthecausesofbuild
problemswhencompilingapplicationprojects.
AndroidMonitor–TheAndroidMonitortoolwindowprovidesaccesstotheAndroid
debuggingsystem.Withinthiswindowtaskssuchasmonitoringlogoutputfromarunning
application,takingscreenshotsandvideosoftheapplication,stoppingaprocessand
performingbasicdebuggingtaskscanbeperformed.Thetoolalsoincludesreal-time
GPU,networking,memoryandCPUusagemonitors.
AndroidModel–TheAndroidModeltoolwindowprovidesasinglelocationinwhichto
viewanexhaustivelistofthedifferentoptionsandsettingsconfiguredwithintheproject.
ThesecanrangefromthemoreobvioussettingssuchasthetargetAndroidSDKversion
tomoreobscurevaluessuchasbuildconfigurationrules.
Terminal–ProvidesaccesstoaterminalwindowonthesystemonwhichAndroidStudio
isrunning.OnWindowssystemsthisistheCommandPromptinterface,whileonLinux
andMacOSXsystemsthistakestheformofaTerminalprompt.
Run–Theruntoolwindowbecomesavailablewhenanapplicationiscurrentlyrunning
andprovidesaviewoftheresultsoftheruntogetherwithoptionstostoporrestarta
runningprocess.Ifanapplicationisfailingtoinstallandrunonadeviceoremulator,this
windowwilltypicallyprovidediagnosticinformationrelatingtotheproblem.
EventLog–Theeventlogwindowdisplaysmessagesrelatingtoeventsandactivities
performedwithinAndroidStudio.Thesuccessfulbuildofaproject,forexample,orthe
factthatanapplicationisnowrunningwillbereportedwithinthistoolwindow.
GradleConsole–TheGradleconsoleisusedtodisplayalloutputfromtheGradlesystem
asprojectsarebuiltfromwithinAndroidStudio.Thiswillincludeinformationaboutthe
successorotherwiseofthebuildprocesstogetherwithdetailsofanyerrorsorwarnings.
Gradle–TheGradletoolwindowprovidesaviewontotheGradletasksthatmakeupthe
projectbuildconfiguration.Thewindowliststhetasksthatareinvolvedincompilingthe
variouselementsoftheprojectintoanexecutableapplication.Right-clickonatoplevel
GradletaskandselecttheOpenGradleConfigmenuoptiontoloadtheGradlebuildfile
forthecurrentprojectintotheeditor.Gradlewillbecoveredingreaterdetaillaterinthis
book.
4.4AndroidStudioKeyboardShortcuts
AndroidStudioincludesanabundanceofkeyboardshortcutsdesignedtosavetimewhen
performingcommontasks.Afullkeyboardshortcutkeymaplistingcanbeviewedand
printedfromwithintheAndroidStudioprojectwindowbyselectingtheHelp->Default
KeymapReferencemenuoption.
4.5SwitcherandRecentFilesNavigation
AnotherusefulmechanismfornavigatingwithintheAndroidStudiomainwindow
involvestheuseoftheSwitcher.AccessedviatheCtrl-Tabkeyboardshortcut,the
switcherappearsasapanellistingboththetoolwindowsandcurrentlyopenfiles(Figure
4-7).
Figure4-7
Oncedisplayed,theswitcherwillremainvisibleforaslongastheCtrlkeyremains
depressed.RepeatedlytappingtheTabkeywhileholdingdowntheCtrlkeywillcycle
throughthevariousselectionoptions,whilereleasingtheCtrlkeycausesthecurrently
highlighteditemtobeselectedanddisplayedwithinthemainwindow.
Inadditiontotheswitcher,navigationtorecentlyopenedfilesisprovidedbytheRecent
Filespanel(Figure4-8).ThiscanbeaccessedusingtheCtrl-Ekeyboardshortcut(Cmd-E
onMacOSX).Oncedisplayed,eitherthemousepointercanbeusedtoselectanoption
or,alternatively,thekeyboardarrowkeyscanbeusedtoscrollthroughthefilenameand
toolwindowoptions.PressingtheEnterkeywillselectthecurrentlyhighlighteditem.
Figure4-8
4.6ChangingtheAndroidStudioTheme
TheoverallthemeoftheAndroidStudioenvironmentmaybechangedeitherfromthe
welcomescreenusingtheConfigure->Settingsoption,orviatheFile->Settings…menu
optionofthemainwindow.
Oncethesettingsdialogisdisplayed,selecttheAppearanceoptioninthelefthandpanel
andthenchangethesettingoftheThememenubeforeclickingontheApplybutton.The
themescurrentlyavailableconsistofIntelliJ,WindowsandDarcula.Figure4-9showsan
exampleofthemainwindowwiththeDarculathemeselected:
Figure4-9
4.7Summary
TheprimaryelementsoftheAndroidStudioenvironmentconsistofthewelcomescreen
andmainwindow.Eachopenprojectisassigneditsownmainwindowwhich,inturn,
consistsofamenubar,toolbar,editinganddesignarea,statusbarandacollectionoftool
windows.Toolwindowsappearonthesidesandbottomedgesofthemainwindowand
canbeaccessedeitherusingthequickaccessmenulocatedinthestatusbar,orviathe
optionaltoolwindowbars.
ThereareveryfewactionswithinAndroidStudiowhichcannotbetriggeredviaa
keyboardshortcut.Akeymapofdefaultkeyboardshortcutscanbeaccessedatanytime
fromwithintheAndroidStudiomainwindow.
5.CreatinganAndroidVirtualDevice(AVD)
inAndroidStudio
InthecourseofdevelopingAndroidappsinAndroidStudioitwillbenecessaryto
compileandrunanapplicationmultipletimes.AnAndroidapplicationmaybetestedby
installingandrunningiteitheronaphysicaldeviceorinanAndroidVirtualDevice(AVD)
emulatorenvironment.BeforeanAVDcanbeused,itmustfirstbecreatedandconfigured
tomatchthespecificationofaparticulardevicemodel.Thegoalofthischapter,therefore,
istoworkthroughthestepsinvolvedincreatingsuchavirtualdeviceusingtheNexus9
tabletasareferenceexample.
5.1AboutAndroidVirtualDevices
AVDsareessentiallyemulatorsthatallowAndroidapplicationstobetestedwithoutthe
necessitytoinstalltheapplicationonaphysicalAndroidbaseddevice.AnAVDmaybe
configuredtoemulateavarietyofhardwarefeaturesincludingoptionssuchasscreensize,
memorycapacityandthepresenceorotherwiseoffeaturessuchasacamera,GPS
navigationsupportoranaccelerometer.AspartofthestandardAndroidStudio
installation,anumberofemulatortemplatesareinstalledallowingAVDstobeconfigured
forarangeofdifferentdevices.Additionaltemplatesmaybeloadedorcustom
configurationscreatedtomatchanyphysicalAndroiddevicebyspecifyingpropertiessuch
asprocessortype,memorycapacityandthesizeandpixeldensityofthescreen.Checkthe
onlinedeveloperdocumentationforyourdevicetofindoutifemulatordefinitionsare
availablefordownloadandinstallationintotheAVDenvironment.
Whenlaunched,anAVDwillappearasawindowcontaininganemulatedAndroiddevice
environment.Figure5-1,forexample,showsanAVDsessionconfiguredtoemulatethe
GoogleNexus9model.
NewAVDsarecreatedandmanagedusingtheAndroidVirtualDeviceManager,which
maybeusedeitherincommand-linemodeorwithamoreuser-friendlygraphicaluser
interface.
Figure5-1
5.2CreatingaNewAVD
Inordertotestthebehaviorofanapplicationintheabsenceofaphysicaldevice,itwillbe
necessarytocreateanAVDforaspecificAndroiddeviceconfiguration.
TocreateanewAVD,thefirststepistolaunchtheAVDManager.Thiscanbeachieved
fromwithintheAndroidStudioenvironmentbyselectingtheTools->Android->AVD
Managermenuoptionfromwithinthemainwindow.Alternatively,thetoolmaybe
launchedfromaterminalorcommand-linepromptusingthefollowingcommand:
androidavd
Oncelaunched,thetoolwillappearasoutlinedinFigure5-2.AssuminganewAndroid
Studioinstallation,onlyaNexus5AVDwillcurrentlybelisted:
Figure5-2
ToaddanadditionalAVD,beginbyclickingontheCreateVirtualDevicebuttoninorder
toinvoketheVirtualDeviceConfigurationdialog:
Figure5-3
Withinthedialog,performthefollowingstepstocreateaNexus9compatibleemulator:
1. FromtheCategorypanel,selecttheTabletoptiontodisplaythelistofavailable
AndroidtabletAVDtemplates.
2. SelecttheNexus9deviceoptionandclickNext.
3. OntheSystemImagescreen,selectthelatestversionofAndroid(attimeofwriting
thisisMarshmallow,APIlevel23,Android6.0)forthex86ABI.Notethatifthe
systemimagehasnotyetbeeninstalledaDownloadlinkwillbeprovidednexttothe
ReleaseName.Clickthislinktodownloadandinstallthesystemimagebefore
selectingit.
4. ClickNexttoproceedandenteradescriptivename(forexampleNexus9)intothe
namefieldorsimplyacceptthedefaultname.
5. ClickFinishtocreatetheAVD.
WiththeAVDcreated,theAVDManagermaynowbeclosed.Iffuturemodificationsto
theAVDarenecessary,simplyre-opentheAVDManager,selecttheAVDfromthelist
andclickonthepenciliconintheActionscolumnofthedevicerowintheAVDManager.
5.3StartingtheEmulator
ToperformatestrunofthenewlycreatedAVDemulator,simplyselecttheemulatorfrom
theAVDManagerandclickonthelaunchbutton(thegreentriangleintheActions
column).Theemulatorwillappearinanewwindowand,afterashortperiodoftime,the
“android”logowillappearinthecenterofthescreen.Theamountoftimeittakesforthe
emulatortostartwilldependontheconfigurationofboththeAVDandthesystemon
whichitisrunning.Intheeventthatthestartuptimeonyoursystemisconsiderable,do
nothesitatetoleavetheemulatorrunning.Thesystemwilldetectthatitisalreadyrunning
andattachtoitwhenapplicationsarelaunched,therebysavingconsiderableamountsof
startuptime.
Theemulatorprobablydefaultedtoappearingfullsizeandinlandscapeorientation.Itis
usefultobeawarethatthesedefaultoptionscanbechanged.WithintheAVDManager,
selectthenewNexus9entryandclickonthepenciliconintheActionscolumnofthe
devicerow.IntheconfigurationscreenlocatetheStartupsizeandorientationsectionand
changetheScalemenuto2dpondevice=1pxonscreentoreducethesizeoftheemulator
onthescreen.Exitandrestarttheemulatorsessiontoseethischangetakeeffect.
Tosavetimeinthenextsectionofthischapter,leavetheemulatorrunningbefore
proceeding.
5.4RunningtheApplicationintheAVD
WithanAVDemulatorconfigured,theexampleAndroidSampleapplicationcreatedinthe
earlierchapternowcanbecompiledandrun.WiththeAndroidSampleprojectloadedinto
AndroidStudio,simplyclickontherunbuttonrepresentedbyagreentrianglelocatedin
theAndroidStudiotoolbarasshowninFigure5-4below,selecttheRun->Run…menu
optionorusetheShift+F10keyboardshortcut:
Figure5-4
Bydefault,AndroidStudiowillrespondtotherunrequestbydisplayingtheChoose
Devicedialog.ThisprovidestheoptiontoexecutetheapplicationonanAVDinstancethat
isalreadyrunning,ortolaunchanewAVDsessionspecificallyforthisapplication.Figure
5-5liststhepreviouslycreatedNexus9AVDasarunningdeviceasaresultofthesteps
performedintheprecedingsection.Withthisdeviceselectedinthedialog,clickonOKto
installandruntheapplicationontheemulator.
Figure5-5
Oncetheapplicationisinstalledandrunning,theuserinterfaceforthe
AndroidSampleActivityclasswillappearwithintheemulator:
Figure5-6
Intheeventthattheactivitydoesnotautomaticallylaunch,checktoseeifthelaunchicon
hasappearedamongtheappsontheemulator.Ifithas,simplyclickonittolaunchthe
application.Oncetherunprocessbegins,theRunandAndroidtoolwindowswillbecome
available.TheRuntoolwindowwilldisplaydiagnosticinformationastheapplication
packageisinstalledandlaunched.Figure5-7showstheRuntoolwindowoutputfroma
successfulapplicationlaunch:
Figure5-7
Ifproblemsareencounteredduringthelaunchprocess,theRuntoolwillprovide
informationthatwillhopefullyhelptoisolatethecauseoftheproblem.
Assumingthattheapplicationloadsintotheemulatorandrunsasexpected,wehave
safelyverifiedthattheAndroiddevelopmentenvironmentiscorrectlyinstalledand
configured.
5.5Run/DebugConfigurations
Aparticularprojectcanbeconfiguredsuchthataspecificdeviceoremulatorisused
automaticallyeachtimeitisrunfromwithinAndroidStudio.Thisavoidsthenecessityto
makeaselectionfromthedevicechoosereachtimetheapplicationisexecuted.Toreview
andmodifytheRun/Debugconfiguration,clickonthebuttontotheleftoftherunbutton
intheAndroidStudiotoolbarandselecttheEditConfigurations…optionfromthe
resultingmenu:
Figure5-8
IntheRun/DebugConfigurationsdialog,theapplicationmaybeconfiguredtoalwaysuse
apreferredemulatorbyenablingtheEmulatoroptionlistedintheTargetDevicesection
andselectingtheemulatorfromthedropdownmenu.Figure5-9,forexample,showsthe
AndroidSampleapplicationconfiguredtorunbydefaultonthepreviouslycreatedNexus
9emulator:
Figure5-9
BesuretoswitchtheTargetDevicesettingbackto“Showchooserdialog”modebefore
movingontothenextchapterofthebook.
5.6StoppingaRunningApplication
Whenbuildingandrunninganapplicationfortestingpurposes,eachtimeanewrevision
oftheapplicationiscompiledandrun,thepreviousinstanceoftheapplicationrunningon
thedeviceoremulatorwillbeterminatedautomaticallyandreplacedwiththenew
version.Itisalsopossible,however,tomanuallystoparunningapplicationfromwithin
AndroidStudio.
Tostoparunningapplication,beginbydisplayingtheAndroidMonitortoolwindow
eitherusingthewindowbarbutton,orviathequickaccessmenu(invokedbymovingthe
mousepointeroverthebuttoninthelefthandcornerofthestatusbarasshowninFigure
5-10).
Figure5-10
OncetheAndroidtoolwindowappears,selecttheandroidsampleappmenuhighlightedin
Figure5-11below:
Figure5-11
Withtheprocessselected,stopitbyclickingontheredTerminateApplicationbuttonin
theverticaltoolbartotheleftoftheprocesslist:
Figure5-12
AnalternativetousingtheAndroidtoolwindowistoopentheAndroidDeviceMonitor.
ThiscanbelaunchedviatheTools->Android->AndroidDeviceMonitormenuoption.
Oncelaunched,theprocessmaybeselectedfromthelist(Figure5-13)andterminatedby
clickingontheredStopbuttonlocatedinthetoolbarabovethelist.
Figure5-13
5.7AVDCommand-lineCreation
Aspreviouslydiscussed,inadditiontothegraphicaluserinterfaceitisalsopossibleto
createanewAVDdirectlyfromthecommand-line.Thisisachievedusingtheandroidtool
inconjunctionwithsomecommand-lineoptions.Onceinitiated,thetoolwillpromptfor
additionalinformationbeforecreatingthenewAVD.
AssumingthatthesystemhasbeenconfiguredsuchthattheAndroidSDKtoolsdirectory
isincludedinthePATHenvironmentvariable,alistofavailabletargetsforthenewAVD
maybeobtainedbyissuingthefollowingcommandinaterminalorcommandwindow:
androidlisttargets
TheresultingoutputfromtheabovecommandwillcontainalistofAndroidSDKversions
thatareavailableonthesystem.Forexample:
AvailableAndroidtargets:
–––id:1or“GoogleInc.:GoogleAPIs:23”
Name:GoogleAPIs
Type:Add-On
Vendor:GoogleInc.
Revision:1
Description:Android+GoogleAPIs
BasedonAndroid6.0(APIlevel23)
Libraries:
*com.google.android.media.effects(effects.jar)
Collectionofvideoeffects
*com.android.future.usb.accessory(usb.jar)
APIforUSBAccessories
*com.google.android.maps(maps.jar)
APIforGoogleMaps
Skins:HVGA,QVGA,WQVGA400,WQVGA432,WSVGA,WVGA800(default),
WVGA854,WXGA720,WXGA800,WXGA800-7in
Tag/ABIs:google_apis/x86
ThesyntaxforAVDcreationisasfollows:
androidcreateavd-n<name>-t<targetID>[-<option><value>]
Forexample,tocreateanewAVDnamedNexus9usingthetargetidfortheAndroid6.0
APIlevel23device(inthiscaseid1)usingtheGooglex86ABI,thefollowingcommand
maybeused:
androidcreateavd-nNexus9-t1—abi“google_apis/x86”
TheandroidtoolwillcreatethenewAVDtothespecificationsrequiredforabasic
Android6.0device,alsoprovidingtheoptiontocreateacustomconfigurationtomatch
thespecificationofaspecificdeviceifrequired.OnceanewAVDhasbeencreatedfrom
thecommandline,itmaynotshowupintheAndroidDeviceManagertooluntilthe
Refreshbuttonisclicked.
InadditiontothecreationofnewAVDs,anumberofothertasksmaybeperformedfrom
thecommandline.Forexample,alistofcurrentlyavailableAVDsmaybeobtainedusing
thelistavdcommandlinearguments:
androidlistavd
AvailableAndroidVirtualDevices:
Name:Nexus9
Path:C:\Users\Neil.android\avd\Nexus9.avd
Target:GoogleAPIs(GoogleInc.)
BasedonAndroid6.0(APIlevel23)
Tag/ABI:google_apis/x86
Skin:WVGA800
–––
Name:Nexus_5_API_23_x86
Device:Nexus5(Google)
Path:C:\Users\Neil.android\avd\Nexus_5_API_23_x86.avd
Target:GoogleAPIs(GoogleInc.)
BasedonAndroid6.0(APIlevel23)
Tag/ABI:google_apis/x86
Skin:nexus_5
Sdcard:200M
Snapshot:no
–––
Name:Nexus_9_API_23
Device:Nexus9(Google)
Path:C:\Users\Neil.android\avd\Nexus_9_API_23.avd
Target:GoogleAPIs(GoogleInc.)
BasedonAndroid6.0(APIlevel23)
Tag/ABI:google_apis/x86
Skin:nexus_9
Sdcard:C:\Users\Neil.android\avd\Nexus_9_API_23.avd\sdcard.img
Snapshot:no
Similarly,todeleteanexistingAVD,simplyusethedeleteoptionasfollows:
androiddeleteavd–n<avdname>
5.8AndroidVirtualDeviceConfigurationFiles
Bydefault,thefilesassociatedwithanAVDarestoredinthe.android/avdsub-directory
oftheuser’shomedirectory,thestructureofwhichisasfollows(where<avdname>is
replacedbythenameassignedtotheAVD):
<avdname>.avd/config.ini
<avdname>.avd/userdata.img
<avdname>.ini
Theconfig.inifilecontainsthedeviceconfigurationsettingssuchasdisplaydimensions
andmemoryspecifiedduringtheAVDcreationprocess.Thesesettingsmaybechanged
directlywithintheconfigurationfileandwillbeadoptedbytheAVDwhenitisnext
invoked.
The<avdname>.inifilecontainsareferencetothetargetAndroidSDKandthepathto
theAVDfiles.Notethatachangetotheimage.sysdirvalueintheconfig.inifilewillalso
needtobereflectedinthetargetvalueofthisfile.
5.9MovingandRenaminganAndroidVirtualDevice
ThecurrentnameorthelocationoftheAVDfilesmaybealteredfromthecommandline
usingtheandroidtool’smoveavdargument.Forexample,torenameanAVDnamed
Nexus9toNexus9B,thefollowingcommandmaybeexecuted:
androidmoveavd-nNexus9-rNexus9B
TophysicallyrelocatethefilesassociatedwiththeAVD,thefollowingcommandsyntax
shouldbeused:
androidmoveavd-n<avdname>-p<pathtonewlocation>
Forexample,tomoveanAVDfromitscurrentfilesystemlocationto/tmp/Nexus9Test:
androidmoveavd-nNexus9-p/tmp/Nexus9Test
Notethatthedestinationdirectorymustnotalreadyexistpriortoexecutingthecommand
tomoveanAVD.
5.10Summary
Atypicalapplicationdevelopmentprocessfollowsacycleofcoding,compilingand
runninginatestenvironment.Androidapplicationsmaybetestedoneitheraphysical
AndroiddeviceorusinganAndroidVirtualDevice(AVD)emulator.AVDsarecreated
andmanagedusingtheAndroidAVDManagertoolwhichmaybeusedeitherasa
commandlinetoolorusingagraphicaluserinterface.WhencreatinganAVDtosimulate
aspecificAndroiddevicemodelitisimportantthatthevirtualdevicebeconfiguredwitha
hardwarespecificationthatmatchesthatofthephysicaldevice.
6.TestingAndroidStudioAppsonaPhysical
AndroidDevice
WhilstmuchcanbeachievedbytestingapplicationsusinganAndroidVirtualDevice
(AVD),thereisnosubstituteforperformingrealworldapplicationtestingonaphysical
AndroiddeviceandthereareanumberofAndroidfeaturesthatareonlyavailableon
physicalAndroiddevices.
CommunicationwithbothAVDinstancesandconnectedAndroiddevicesishandledby
theAndroidDebugBridge(ADB).Inthischapterwewillworkthroughthestepsto
configuretheadbenvironmenttoenableapplicationtestingonaphysicalAndroiddevice
withMacOSX,WindowsandLinuxbasedsystems.
6.1AnOverviewoftheAndroidDebugBridge(ADB)
TheprimarypurposeoftheADBistofacilitateinteractionbetweenadevelopment
system,inthiscaseAndroidStudio,andbothAVDemulatorsandphysicalAndroid
devicesforthepurposesofrunninganddebuggingapplications.
TheADBconsistsofaclient,aserverprocessrunninginthebackgroundonthe
developmentsystemandadaemonbackgroundprocessrunningineitherAVDsorreal
Androiddevicessuchasphonesandtablets.
TheADBclientcantakeavarietyofforms.Forexample,aclientisprovidedintheform
ofacommand-linetoolnamedadblocatedintheAndroidSDKplatform-toolssubdirectory.Similarly,AndroidStudioalsohasabuilt-inclient.
Avarietyoftasksmaybeperformedusingtheadbcommand-linetool.Forexample,a
listingofcurrentlyactivevirtualorphysicaldevicesmaybeobtainedusingthedevices
command-lineargument.Thefollowingcommandoutputindicatesthepresenceofan
AVDonthesystembutnophysicaldevices:
$adbdevices
Listofdevicesattached
emulator-5554device
6.2EnablingADBonAndroid6.0basedDevices
BeforeADBcanconnecttoanAndroiddevice,thatdevicemustfirstbeconfiguredto
allowtheconnection.OnphoneandtabletdevicesrunningAndroid6.0orlater,thesteps
toachievethisareasfollows:
1. OpentheSettingsapponthedeviceandselecttheAbouttabletorAboutphoneoption.
2. OntheAboutscreen,scrolldowntotheBuildnumberfield(Figure6-1)andtaponit
seventimesuntilamessageappearsindicatingthatdevelopermodehasbeenenabled.
Figure6-1
3. ReturntothemainSettingsscreenandnotetheappearanceofanewoptiontitled
Developeroptions.Selectthisoptionandlocatethesettingonthedeveloperscreen
entitledUSBdebugging.EnabletheswitchnexttothisitemasillustratedinFigure6-2:
Figure6-2
4. Swipedownwardfromthetopofthescreentodisplaythenotificationspanel(Figure
6-3)andnotethatthedeviceiscurrentlyconnectedfordebugging.
Figure6-3
Atthispoint,thedeviceisnowconfiguredtoacceptdebuggingconnectionsfromadbon
thedevelopmentsystem.Allthatremainsistoconfigurethedevelopmentsystemtodetect
thedevicewhenitisattached.Whilethisisarelativelystraightforwardprocess,thesteps
involveddifferdependingonwhetherthedevelopmentsystemisrunningWindows,Mac
OSXorLinux.NotethatthefollowingstepsassumethattheAndroidSDKplatform-tools
directoryisincludedintheoperatingsystemPATHenvironmentvariableasdescribedin
thechapterentitledSettingupanAndroidStudioDevelopmentEnvironment.
6.2.1MacOSXADBConfiguration
InordertoconfiguretheADBenvironmentonaMacOSXsystem,connectthedeviceto
thecomputersystemusingaUSBcable,openaterminalwindowandexecutethe
followingcommand:
androidupdateadb
Next,restarttheadbserverbyissuingthefollowingcommandsintheterminalwindow:
$adbkill-server
$adbstart-server
*daemonnotrunning.startingitnowonport5037*
*daemonstartedsuccessfully*
Oncetheserverissuccessfullyrunning,executethefollowingcommandtoverifythatthe
devicehasbeendetected:
$adbdevices
Listofdevicesattached
74CE000600000001offline
Ifthedeviceislistedasoffline,gototheAndroiddeviceandcheckforthepresenceofthe
dialogshowninFigure6-4seekingpermissiontoAllowUSBdebugging.Enablethe
checkboxnexttotheoptionthatreadsAlwaysallowfromthiscomputer,beforeclicking
onOK.Repeatingtheadbdevicescommandshouldnowlistthedeviceasbeingavailable:
Listofdevicesattached
015d41d4454bf80cdevice
Intheeventthatthedeviceisnotlisted,tryloggingoutandthenbackintotheMacOSX
desktopand,iftheproblempersists,rebootingthesystem.
6.2.2WindowsADBConfiguration
ThefirststepinconfiguringaWindowsbaseddevelopmentsystemtoconnecttoan
AndroiddeviceusingADBistoinstalltheappropriateUSBdriversonthesystem.The
USBdriverstoinstallwilldependonthemodelofAndroidDevice.IfyouhaveaGoogle
Nexusdevice,thenitwillbenecessarytoinstallandconfiguretheGoogleUSBDriver
packageonyourWindowssystem.Detailedstepstoachievethisareoutlinedonthe
followingwebpage:
http://developer.android.com/sdk/win-usb.html
ForAndroiddevicesnotsupportedbytheGoogleUSBdriver,itwillbenecessaryto
downloadthedriversprovidedbythedevicemanufacturer.Alistingofdriverstogether
withdownloadandinstallationinformationcanbeobtainedonlineat:
http://developer.android.com/tools/extras/oem-usb.html
Withthedriversinstalledandthedevicenowbeingrecognizedasthecorrectdevicetype,
openaCommandPromptwindowandexecutethefollowingcommand:
adbdevices
Thiscommandshouldoutputinformationabouttheconnecteddevicesimilartothe
following:
Listofdevicesattached
HT4CTJT01906offline
Ifthedeviceislistedasofflineorunauthorized,gotothedevicedisplayandcheckforthe
dialogshowninFigure6-4seekingpermissiontoAllowUSBdebugging.
Figure6-4
EnablethecheckboxnexttotheoptionthatreadsAlwaysallowfromthiscomputer,before
clickingonOK.Repeatingtheadbdevicescommandshouldnowlistthedeviceasbeing
ready:
Listofdevicesattached
HT4CTJT01906device
Intheeventthatthedeviceisnotlisted,executethefollowingcommandstorestartthe
ADBserver:
adbkill-server
adbstart-server
Ifthedeviceisstillnotlisted,tryexecutingthefollowingcommand:
androidupdateadb
Notethatitmayalsobenecessarytorebootthesystem.
6.2.3LinuxadbConfiguration
Forthepurposesofthischapter,wewillonceagainuseUbuntuLinuxasareference
exampleintermsofconfiguringadbonLinuxtoconnecttoaphysicalAndroiddevicefor
applicationtesting.
BeginbyattachingtheAndroiddevicetoaUSBportontheUbuntuLinuxsystem.Once
connected,openaTerminalwindowandexecutetheLinuxlsusbcommandtolist
currentlyavailableUSBdevices:
~$lsusb
Bus001Device003:ID18d1:4e44asusNexus7[9999]
Bus001Device001:ID1d6b:0001LinuxFoundation1.1roothub
EachUSBdevicedetectedonthesystemwillbelistedalongwithavendorIDandproduct
ID.AlistofvendorIDscanbefoundonlineat
http://developer.android.com/tools/device.html#VendorIds.Theaboveoutputshowsthata
GoogleNexus7devicehasbeendetected.MakeanoteofthevendorandproductID
numbersdisplayedforyourparticulardevice(intheabovecasetheseare18D1and4E44
respectively).
Usethesudocommandtoeditthe51-android.rulesfilelocatedinthe/etc/udev/rules.d
directory.Forexample:
sudogedit/etc/udev/rules.d/51-android.rules
Withintheeditor,addtheappropriateentryfortheAndroiddevice,replacing<vendor_id>
and<product_id>withthevendorandproductIDsreturnedpreviouslybythelsusb
command:
SUBSYSTEM==“usb”,ATTR{idVendor}==”<vendor_id>”,
ATTRS{idProduct}==”<product_id>”,MODE=“0660”,OWNER=“root”,
GROUP=“androidadb”,SYMLINK+=“android%n”
Oncetheentryhasbeenadded,savethefileandexitfromtheeditor.
Next,useaneditortomodify(orcreateifitdoesnotyetexist)theadb_usb.inifile:
gedit~/.android/adb_usb.ini
Oncethefileisloadedintotheeditor,addthefollowinglines(onceagainreplacing
<vendor_id>and<product_id>withthevendorandproductIDsreturnedpreviouslyby
thelsusbcommand)beforesavingthefileandexiting:
0x<vendor_id>
0x<product_id>
Usingtheabovesyntax,theentriesfortheNexus7would,forexample,read:
0x18d1
0x4e44
Thefinaltaskistocreatetheandroidadbusergroupandaddyouruseraccounttoit.To
achievethis,executethefollowingcommandsmakingsuretoreplace<username>with
yourUbuntuuseraccountname:
sudoaddgroup—systemandroidadb
sudoadduser<username>androidadb
Oncetheabovechangeshavebeenmade,reboottheUbuntusystem.Oncethesystemhas
restarted,openaTerminalwindow,starttheadbserverandcheckthelistofattached
devices:
$adbstart-server
*daemonnotrunning.startingitnowonport5037*
*daemonstartedsuccessfully*
$adbdevices
Listofdevicesattached
015d41d4454bf80coffline
Ifthedeviceislistedasofflineorunauthorized,gototheAndroiddeviceandcheckfor
thedialogshowninFigure6-4seekingpermissiontoAllowUSBdebugging.
6.3TestingtheadbConnection
Assumingthattheadbconfigurationhasbeensuccessfulonyourchosendevelopment
platform,thenextstepistotryrunningthetestapplicationcreatedinthechapterentitled
CreatinganExampleAndroidAppinAndroidStudioonthedevice.
LaunchAndroidStudio,opentheAndroidSampleprojectand,oncetheprojecthasloaded,
clickontherunbuttonlocatedintheAndroidStudiotoolbar(Figure6-5).
Figure6-5
Assumingthattheprojecthasnotpreviouslybeenconfiguredtorunautomaticallyinan
emulatorenvironment,theChooseDevicedialogwillappearwiththeconnectedAndroid
devicelistedasacurrentlyrunningdevice.Figure6-6,forexample,listsaNexus9device
asasuitabletargetforinstallingandexecutingtheapplication.
Figure6-6
Tomakethisthedefaultdevicefortesting,enabletheUsesamedeviceforfuturelaunches
option.Withthedeviceselected,clickontheOKbuttontoinstallandruntheapplication
onthedevice.Aswiththeemulatorenvironment,diagnosticoutputrelatingtothe
installationandlaunchoftheapplicationonthedevicewillbeloggedintheRuntool
window.
6.4Summary
WhiletheAndroidVirtualDeviceemulatorprovidesanexcellenttestingenvironment,itis
importanttokeepinmindthatthereisnorealsubstituteformakingsureanapplication
functionscorrectlyonaphysicalAndroiddevice.This,afterall,iswheretheapplication
willbeusedintherealworld.
Bydefault,however,theAndroidStudioenvironmentisnotconfiguredtodetectAndroid
devicesasatargettestingdevice.Itisnecessary,therefore,toperformsomestepsinorder
tobeabletoloadapplicationsdirectlyontoanAndroiddevicefromwithintheAndroid
Studiodevelopmentenvironment.Theexactstepstoachievethisgoaldifferdependingon
thedevelopmentplatformbeingused.Inthischapter,wehavecoveredthosestepsfor
Linux,MacOSXandWindowsbasedplatforms.
7.TheBasicsoftheAndroidStudioCode
Editor
DevelopingapplicationsforAndroidinvolvesaconsiderableamountofprogramming
workwhich,bydefinition,involvestyping,reviewingandmodifyinglinesofcode.It
shouldcomeasnosurprisethatthemajorityofadeveloper’stimespentusingAndroid
Studiowilltypicallyinvolveeditingcodewithintheeditorwindow.
Themoderncodeeditorneedstogofarbeyondtheoriginalbasicsoftyping,deleting,
cuttingandpasting.Todaytheusefulnessofacodeeditorisgenerallygaugedbyfactors
suchastheamountbywhichitreducesthetypingrequiredbytheprogrammer,easeof
navigationthroughlargesourcecodefilesandtheeditor’sabilitytodetectandhighlight
programmingerrorsinreal-timeasthecodeisbeingwritten.Aswillbecomeevidentin
thischapter,thesearejustafewoftheareasinwhichtheAndroidStudioeditorexcels.
WhilenotanexhaustiveoverviewofthefeaturesoftheAndroidStudioeditor,thischapter
aimstoprovideaguidetothekeyfeaturesofthetool.Experiencedprogrammerswillfind
thatsomeofthesefeaturesarecommontomostcodeeditorsavailabletoday,whilea
numberareuniquetothisparticulareditingenvironment.
7.1TheAndroidStudioEditor
TheAndroidStudioeditorappearsinthecenterofthemainwindowwhenaJava,XMLor
othertextbasedfileisselectedforediting.Figure7-1,forexample,showsatypicaleditor
sessionwithaJavasourcecodefileloaded:
Figure7-1
Theelementsthatcomprisetheeditorwindowcanbesummarizedasfollows:
A–DocumentTabs–AndroidStudioiscapableofholdingmultiplefilesopenforediting
atanyonetime.Aseachfileisopened,itisassignedadocumenttabdisplayingthefile
nameinthetabbarlocatedalongthetopedgeoftheeditorwindow.Asmalldropdown
menuwillappearinthefarrighthandcornerofthetabbarwhenthereisinsufficientroom
todisplayallofthetabs.Clickingonthismenuwilldropdownalistofadditionalopen
files.Awavyredlineunderneathafilenameinatabindicatesthatthecodeinthefile
containsoneormoreerrorsthatneedtobeaddressedbeforetheprojectcanbecompiled
andrun.
Switchingbetweenfilesissimplyamatterofclickingonthecorrespondingtaborusing
theAlt-LeftandAlt-Rightkeyboardshortcuts.Navigationbetweenfilesmayalsobe
performedusingtheSwitchermechanism(accessibleviatheCtrl-Tabkeyboardshortcut).
TodetachaneditorpanelfromtheAndroidStudiomainwindowsothatitappearsina
separatewindow,clickonthetabanddragittoanareaonthedesktopoutsideofthemain
window.Toreturntheeditortothemainwindow,clickonthefiletabintheseparated
editorwindowanddraganddropitontotheoriginaleditortabbarinthemainwindow.
B–TheEditorGutterArea-Thegutterareaisusedbytheeditortodisplay
informationaliconsandcontrols.Sometypicalitems,amongothers,whichappearinthis
gutterareaaredebuggingbreakpointmarkers,controlstofoldandunfoldblocksofcode,
bookmarks,changemarkersandlinenumbers.Linenumbersareswitchedoffbydefault
butmaybeenabledbyright-clickinginthegutterandselectingtheShowLineNumbers
menuoption.
C–TheStatusBar–Thoughthestatusbarisactuallypartofthemainwindow,as
opposedtotheeditor,itdoescontainsomeinformationaboutthecurrentlyactiveediting
session.Thisinformationincludesthecurrentpositionofthecursorintermsoflinesand
charactersandtheencodingformatofthefile(UTF-8,ASCIIetc.).Clickingonthese
valuesinthestatusbarallowsthecorrespondingsettingtobechanged.Clickingonthe
linenumber,forexample,displaystheGotoLinedialog.
D–TheEditorArea–Thisisthemainareawherethecodeisdisplayed,enteredand
editedbytheuser.Latersectionsofthischapterwillcoverthekeyfeaturesoftheediting
areaindetail.
E–TheValidationandMarkerSidebar–AndroidStudioincorporatesafeature
referredtoas“on-the-flycodeanalysis”.Whatthisessentiallymeansisthatasyouare
typingcode,theeditorisanalyzingthecodetocheckforwarningsandsyntaxerrors.The
indicatoratthetopofthevalidationsidebarwillchangefromagreencheckmark(no
warningsorerrorsdetected)toayellowsquare(warningsdetected)orredalerticon
(errorshavebeendetected).Clickingonthisindicatorwilldisplayapopupcontaininga
summaryoftheissuesfoundwiththecodeintheeditorasillustratedinFigure7-2:
Figure7-2
Thesidebaralsodisplaysmarkersatthelocationswhereissueshavebeendetectedusing
thesamecolorcoding.Hoveringthemousepointeroveramarkerwhenthelineofcodeis
visibleintheeditorareawilldisplayapopupcontainingadescriptionoftheissue(Figure
7-3):
Figure7-3
Hoveringthemousepointeroveramarkerforalineofcodewhichiscurrentlyscrolled
outoftheviewingareaoftheeditorwilldisplaya“lens”overlaycontainingtheblockof
codewheretheproblemislocated(Figure7-4)allowingittobeviewedwithoutthe
necessitytoscrolltothatlocationintheeditor:
Figure7-4
Itisalsoworthnotingthatthelensoverlayisnotlimitedtowarningsanderrorsinthe
sidebar.Hoveringoveranypartofthesidebarwillresultinalensappearingcontainingthe
codepresentatthatlocationwithinthesourcefile.
HavingprovidedanoverviewoftheelementsthatcomprisetheAndroidStudioeditor,the
remainderofthischapterwillexplorethekeyfeaturesoftheeditingenvironmentinmore
detail.
7.2SplittingtheEditorWindow
Bydefault,theeditorwilldisplayasinglepanelshowingthecontentofthecurrently
selectedfile.Aparticularlyusefulfeaturewhenworkingsimultaneouslywithmultiple
sourcecodefilesistheabilitytosplittheeditorintomultiplepanes.Tosplittheeditor,
right-clickonafiletabwithintheeditorwindowandselecteithertheSplitVerticallyor
SplitHorizontallymenuoption.Figure7-5,forexample,showsthesplitterinactionwith
theeditorsplitintothreepanels:
Figure7-5
Theorientationofasplitpanelmaybechangedatanytimebyright-clickingonthe
correspondingtabandselectingtheChangeSplitterOrientationmenuoption.Repeat
thesestepstounsplitasinglepanel,thistimeselectingtheUnsplitoptionfromthemenu.
Allofthesplitpanelsmayberemovedbyright-clickingonanytabandselectingthe
UnsplitAllmenuoption.
Windowsplittingmaybeusedtodisplaydifferentfiles,ortoprovidemultiplewindows
ontothesamefile,allowingdifferentareasofthesamefiletobeviewedandedited
concurrently.
7.3CodeCompletion
TheAndroidStudioeditorhasaconsiderableamountofbuilt-inknowledgeofJava
programmingsyntaxandtheclassesandmethodsthatmakeuptheAndroidSDK,aswell
asknowledgeofyourowncodebase.Ascodeistyped,theeditorscanswhatisbeing
typedand,whereappropriate,makessuggestionswithregardtowhatmightbeneededto
completeastatementorreference.Whenacompletionsuggestionisdetectedbytheeditor,
apanelwillappearcontainingalistofsuggestions.InFigure7-6,forexample,theeditor
issuggestingpossibilitiesforthebeginningofaStringdeclaration:
Figure7-6
Ifnoneoftheautocompletionsuggestionsarecorrect,simplykeeptypingandtheeditor
willcontinuetorefinethesuggestionswhereappropriate.Toacceptthetopmost
suggestion,simplypresstheEnterorTabkeyonthekeyboard.Toselectadifferent
suggestion,usethearrowkeystomoveupanddownthelist,onceagainusingtheEnteror
Tabkeytoselectthehighlighteditem.
CompletionsuggestionscanbemanuallyinvokedusingtheCtrl-Spacekeyboard
sequence.Thiscanbeusefulwhenchangingawordordeclarationintheeditor.Whenthe
cursorispositionedoverawordintheeditor,thatwordwillautomaticallyhighlight.
PressingCtrl-Spacewilldisplayalistofalternatesuggestions.Toreplacethecurrentword
withthecurrentlyhighlightediteminthesuggestionlist,simplypresstheTabkey.
Inadditiontothereal-timeautocompletionfeature,theAndroidStudioeditoralsooffersa
systemreferredtoasSmartCompletion.SmartcompletionisinvokedusingtheShift-CtrlSpacekeyboardsequenceand,whenselected,willprovidemoredetailedsuggestions
basedonthecurrentcontextofthecode.PressingtheShift-Ctrl-Spaceshortcutsequencea
secondtimewillprovidemoresuggestionsfromawiderrangeofpossibilities.
Codecompletioncanbeamatterofpersonalpreferenceformanyprogrammers.In
recognitionofthisfact,AndroidStudioprovidesahighlevelofcontrolovertheauto
completionsettings.ThesecanbeviewedandmodifiedbyselectingtheFile->Settings…
menuoptionandchoosingEditor->General->CodeCompletionfromthesettingspanel
asshowninFigure7-7:
Figure7-7
7.4StatementCompletion
AnotherformofautocompletionprovidedbytheAndroidStudioeditorisstatement
completion.Thiscanbeusedtoautomaticallyfillouttheparenthesesandbracesforitems
suchasmethodsandloopstatements.StatementcompletionisinvokedusingtheShiftCtrl-Enter(Shift-Cmd-EnteronMacOSX)keyboardsequence.Considerforexamplethe
followingcode:
protectedvoidmyMethod()
Havingtypedthiscodeintotheeditor,triggeringstatementcompletionwillcausethe
editortoautomaticallyaddthebracestothemethod:
protectedvoidmyMethod(){
}
7.5ParameterInformation
Itisalsopossibletoasktheeditortoprovideinformationabouttheargumentparameters
acceptedbyamethod.Withthecursorpositionedbetweenthebracketsofamethodcall,
theCtrl-P(Cmd-PonMacOSX)keyboardsequencewilldisplaytheparametersknown
tobeacceptedbythatmethod,withthemostlikelysuggestionhighlightedinbold:
Figure7-8
7.6CodeGeneration
Inadditiontocompletingcodeasitistypedtheeditorcan,undercertainconditions,also
generatecodeforyou.ThelistofavailablecodegenerationoptionsshowninFigure7-9
canbeaccessedusingtheAlt-Insertkeyboardshortcutwhenthecursorisatthelocationin
thefilewherethecodeistobegenerated.
Figure7-9
Forthepurposesofanexample,considerasituationwherewewanttobenotifiedwhenan
Activityinourprojectisabouttobedestroyedbytheoperatingsystem.Aswillbe
outlinedinalaterchapterofthisbook,thiscanbeachievedbyoverridingtheonStop()
lifecyclemethodoftheActivitysuperclass.TohaveAndroidStudiogenerateastub
methodforthis,simplyselecttheOverrideMethods…optionfromthecodegenerationlist
andselecttheonStop()methodfromtheresultinglistofavailablemethods:
Figure7-10
Havingselectedthemethodtooverride,clickingonOKwillgeneratethestubmethodat
thecurrentcursorlocationintheJavasourcefileasfollows:
@Override
protectedvoidonStop(){
super.onStop();
}
7.7CodeFolding
Onceasourcecodefilereachesacertainsize,eventhemostcarefullyformattedandwell
organizedcodecanbecomeoverwhelminganddifficulttonavigate.AndroidStudiotakes
theviewthatitisnotalwaysnecessarytohavethecontentofeverycodeblockvisibleat
alltimes.Codenavigationcanbemadeeasierthroughtheuseofthecodefoldingfeature
oftheAndroidStudioeditor.Codefoldingiscontrolledusingmarkersappearinginthe
editorgutteratthebeginningandendofeachblockofcodeinasourcefile.Figure7-11,
forexample,highlightsthestartandendmarkersforamethoddeclarationwhichisnot
currentlyfolded:
Figure7-11
Clickingoneitherofthesemarkerswillfoldthestatementsuchthatonlythesignatureline
isvisibleasshowninFigure7-12:
Figure7-12
Tounfoldacollapsedsectionofcode,simplyclickonthe‘+’markerintheeditorgutter.
Toseethehiddencodewithoutunfoldingit,hoverthemousepointeroverthe“{…}”
indicatorasshowninFigure7-13.Theeditorwillthendisplaythelensoverlaycontaining
thefoldedcodeblock:
Figure7-13
AllofthecodeblocksinafilemaybefoldedorunfoldedusingtheCtrl-Shift-Plusand
Ctrl-Shift-Minuskeyboardsequences.
Bydefault,theAndroidStudioeditorwillautomaticallyfoldsomecodewhenasourcefile
isopened.Toconfiguretheconditionsunderwhichthishappens,selectFile->Settings…
andchoosetheEditor->General->CodeFoldingentryintheresultingsettingspanel
(Figure7-14):
Figure7-14
7.8QuickDocumentationLookup
ContextsensitiveJavaandAndroiddocumentationcanbeaccessedbyplacingthecursor
overthedeclarationforwhichdocumentationisrequiredandpressingtheCtrl-Q
keyboardshortcut(Ctrl-JonMacOSX).Thiswilldisplayapopupcontainingtherelevant
referencedocumentationfortheitem.Figure7-15,forexample,showsthedocumentation
fortheAndroidMenuclass.
Figure7-15
Oncedisplayed,thedocumentationpopupcanbemovedaroundthescreenasneeded.
Clickingonthepushpiniconlocatedintherighthandcornerofthepopuptitlebarwill
ensurethatthepopupremainsvisibleoncefocusmovesbacktotheeditor,leavingthe
documentationvisibleasareferencewhiletypingcode.
7.9CodeReformatting
Ingeneral,theAndroidStudioeditorwillautomaticallyformatcodeintermsofindenting,
spacingandnestingofstatementsandcodeblocksastheyareadded.Insituationswhere
linesofcodeneedtobereformatted(acommonoccurrence,forexample,whencutting
andpastingsamplecodefromawebsite),theeditorprovidesasourcecodereformatting
featurewhich,whenselected,willautomaticallyreformatcodetomatchtheprevailing
codestyle.
Toreformatsourcecode,presstheCtrl-Alt-Lkeyboardshortcutsequence.Todisplaythe
ReformatCodedialog(Figure7-16)usetheCtrl-Alt-Shift-L.Thisdialogprovidesthe
optiontoreformatonlythecurrentlyselectedcode,theentiresourcefilecurrentlyactive
intheeditororonlycodethathaschangedastheresultofasourcecodecontrolupdate.
Figure7-16
Thefullrangeofcodestylepreferencescanbechangedfromwithintheprojectsettings
dialog.SelecttheFile->SettingsmenuoptionandchooseCodeStyleinthelefthand
paneltoaccessalistofsupportedprogrammingandmarkuplanguages.Selectinga
languagewillprovideaccesstoavastarrayofformattingstyleoptions,allofwhichmay
bemodifiedfromtheAndroidStudiodefaulttomatchyourpreferredcodestyle.To
configurethesettingsfortheRearrangecodeoptionintheabovedialog,forexample,
unfoldtheCodeStylesection,selectJavaand,fromtheJavasettings,selectthe
Arrangementtab.
7.10Summary
TheAndroidStudioeditorgoestogreatlengthtoreducetheamountoftypingneededto
writecodeandtomakethatcodeeasiertoreadandnavigate.Inthischapterwehave
coveredanumberofthekeyeditorfeaturesincludingcodecompletion,codegeneration,
editorwindowsplitting,codefolding,reformattinganddocumentationlookup.
8.AnOverviewoftheAndroidArchitecture
Sofarinthisbook,stepshavebeentakentosetupanenvironmentsuitableforthe
developmentofAndroidapplicationsusingAndroidStudio.Aninitialstephasalsobeen
takenintotheprocessofapplicationdevelopmentthroughthecreationofasimple
AndroidStudioapplicationproject.
BeforedelvingfurtherintothepracticalmattersofAndroidapplicationdevelopment,
however,itisimportanttogainanunderstandingofsomeofthemoreabstractconceptsof
boththeAndroidSDKandAndroiddevelopmentingeneral.Gainingaclear
understandingoftheseconceptsnowwillprovideasoundfoundationonwhichtobuild
furtherknowledge.
StartingwithanoverviewoftheAndroidarchitectureinthischapter,andcontinuinginthe
nextfewchaptersofthisbook,thegoalistoprovideadetailedoverviewofthe
fundamentalsofAndroiddevelopment.
8.1TheAndroidSoftwareStack
Androidisstructuredintheformofasoftwarestackcomprisingapplications,anoperating
system,run-timeenvironment,middleware,servicesandlibraries.Thisarchitecturecan,
perhaps,bestberepresentedvisuallyasoutlinedinFigure8-1.Eachlayerofthestack,and
thecorrespondingelementswithineachlayer,aretightlyintegratedandcarefullytunedto
providetheoptimalapplicationdevelopmentandexecutionenvironmentformobile
devices.
TheremainderofthischapterwillworkthroughthedifferentlayersoftheAndroidstack,
startingatthebottomwiththeLinuxKernel.
Figure8-1
8.2TheLinuxKernel
PositionedatthebottomoftheAndroidsoftwarestack,theLinuxKernelprovidesalevel
ofabstractionbetweenthedevicehardwareandtheupperlayersoftheAndroidsoftware
stack.BasedonLinuxversion2.6,thekernelprovidespreemptivemultitasking,low-level
coresystemservicessuchasmemory,processandpowermanagementinadditionto
providinganetworkstackanddevicedriversforhardwaresuchasthedevicedisplay,WiFiandaudio.
TheoriginalLinuxkernelwasdevelopedin1991byLinusTorvaldsandwascombined
withasetoftools,utilitiesandcompilersdevelopedbyRichardStallmanattheFree
SoftwareFoundationtocreateafulloperatingsystemreferredtoasGNU/Linux.Various
LinuxdistributionshavebeenderivedfromthesebasicunderpinningssuchasUbuntuand
RedHatEnterpriseLinux.
Itisimportanttonote,however,thatAndroidusesonlytheLinuxkernel.Thatsaid,itis
worthnotingthattheLinuxkernelwasoriginallydevelopedforuseintraditional
computersintheformofdesktopsandservers.Infact,Linuxisnowmostwidely
deployedinmissioncriticalenterpriseserverenvironments.Itisatestamenttoboththe
poweroftoday’smobiledevicesandtheefficiencyandperformanceoftheLinuxkernel
thatwefindthissoftwareattheheartoftheAndroidsoftwarestack.
8.3AndroidRuntime–ART
WhenanAndroidappisbuiltwithinAndroidStudioitiscompiledintoanintermediate
bytecodeformat(referredtoasDEXformat).Whentheapplicationissubsequentlyloaded
ontothedevice,theAndroidRuntime(ART)usesaprocessreferredtoasAhead-of-Time
(AOT)compilationtotranslatethebytecodedowntothenativeinstructionsrequiredby
thedeviceprocessor.ThisformatisknownasExecutableandLinkableFormat(ELF).
Eachtimetheapplicationissubsequentlylaunched,theELFexecutableversionisrun,
resultinginfasterapplicationperformanceandimprovedbatterylife.
ThiscontrastswiththeJust-in-Time(JIT)compilationapproachusedinolderAndroid
implementationswherebythebytecodewastranslatedwithinavirtualmachine(VM)each
timetheapplicationwaslaunched.
8.4AndroidLibraries
InadditiontoasetofstandardJavadevelopmentlibraries(providingsupportforsuch
generalpurposetasksasstringhandling,networkingandfilemanipulation),theAndroid
developmentenvironmentalsoincludestheAndroidLibraries.TheseareasetofJavabasedlibrariesthatarespecifictoAndroiddevelopment.Examplesoflibrariesinthis
categoryincludetheapplicationframeworklibrariesinadditiontothosethatfacilitateuser
interfacebuilding,graphicsdrawinganddatabaseaccess.
AsummaryofsomekeycoreAndroidlibrariesavailabletotheAndroiddeveloperisas
follows:
· android.app–Providesaccesstotheapplicationmodelandisthecornerstoneofall
Androidapplications.
· android.content–Facilitatescontentaccess,publishingandmessagingbetween
applicationsandapplicationcomponents.
· android.database–Usedtoaccessdatapublishedbycontentprovidersandincludes
SQLitedatabasemanagementclasses.
· android.graphics–Alow-level2DgraphicsdrawingAPIincludingcolors,points,
filters,rectanglesandcanvases.
· android.hardware–PresentsanAPIprovidingaccesstohardwaresuchasthe
accelerometerandlightsensor.
· android.opengl–AJavainterfacetotheOpenGLES3DgraphicsrenderingAPI.
· android.os–Providesapplicationswithaccesstostandardoperatingsystemservices
includingmessages,systemservicesandinter-processcommunication.
· android.media–Providesclassestoenableplaybackofaudioandvideo.
· android.net–AsetofAPIsprovidingaccesstothenetworkstack.Includes
android.net.wifi,whichprovidesaccesstothedevice’swirelessstack.
· android.print–Includesasetofclassesthatenablecontenttobesenttoconfigured
printersfromwithinAndroidapplications.
· android.provider–Asetofconvenienceclassesthatprovideaccesstostandard
Androidcontentproviderdatabasessuchasthosemaintainedbythecalendarand
contactapplications.
· android.text–Usedtorenderandmanipulatetextonadevicedisplay.
· android.util–Asetofutilityclassesforperformingtaskssuchasstringandnumber
conversion,XMLhandlinganddateandtimemanipulation.
· android.view–Thefundamentalbuildingblocksofapplicationuserinterfaces.
· android.widget-Arichcollectionofpre-builtuserinterfacecomponentssuchas
buttons,labels,listviews,layoutmanagers,radiobuttonsetc.
· android.webkit–Asetofclassesintendedtoallowweb-browsingcapabilitiestobe
builtintoapplications.
HavingcoveredtheJava-basedlibrariesintheAndroidruntime,itisnowtimetoturnour
attentiontotheC/C++basedlibrariescontainedinthislayeroftheAndroidsoftware
stack.
8.4.1C/C++Libraries
TheAndroidruntimecorelibrariesoutlinedintheprecedingsectionareJava-basedand
providetheprimaryAPIsfordeveloperswritingAndroidapplications.Itisimportantto
note,however,thatthecorelibrariesdonotactuallyperformmuchoftheactualworkand
are,infact,essentiallyJava“wrappers”aroundasetofC/C++basedlibraries.When
makingcalls,forexample,totheandroid.opengllibrarytodraw3Dgraphicsonthe
devicedisplay,thelibraryactuallyultimatelymakescallstotheOpenGLESC++library
which,inturn,workswiththeunderlyingLinuxkerneltoperformthedrawingtasks.
C/C++librariesareincludedtofulfillawideanddiverserangeoffunctionsincluding2D
and3Dgraphicsdrawing,SecureSocketsLayer(SSL)communication,SQLitedatabase
management,audioandvideoplayback,bitmapandvectorfontrendering,display
subsystemandgraphiclayermanagementandanimplementationofthestandardCsystem
library(libc).
Inpractice,thetypicalAndroidapplicationdeveloperwillaccesstheselibrariessolely
throughtheJavabasedAndroidcorelibraryAPIs.Intheeventthatdirectaccesstothese
librariesisneeded,thiscanbeachievedusingtheAndroidNativeDevelopmentKit
(NDK),thepurposeofwhichistocallthenativemethodsofnon-Javaprogramming
languages(suchasCandC++)fromwithinJavacodeusingtheJavaNativeInterface
(JNI).
8.5ApplicationFramework
TheApplicationFrameworkisasetofservicesthatcollectivelyformtheenvironmentin
whichAndroidapplicationsrunandaremanaged.Thisframeworkimplementsthe
conceptthatAndroidapplicationsareconstructedfromreusable,interchangeableand
replaceablecomponents.Thisconceptistakenastepfurtherinthatanapplicationisalso
abletopublishitscapabilitiesalongwithanycorrespondingdatasothattheycanbefound
andreusedbyotherapplications.
TheAndroidframeworkincludesthefollowingkeyservices:
· ActivityManager–Controlsallaspectsoftheapplicationlifecycleandactivitystack.
· ContentProviders–Allowsapplicationstopublishandsharedatawithother
applications.
· ResourceManager–Providesaccesstonon-codeembeddedresourcessuchas
strings,colorsettingsanduserinterfacelayouts.
· NotificationsManager–Allowsapplicationstodisplayalertsandnotificationstothe
user.
· ViewSystem–Anextensiblesetofviewsusedtocreateapplicationuserinterfaces.
· PackageManager–Thesystembywhichapplicationsareabletofindout
informationaboutotherapplicationscurrentlyinstalledonthedevice.
· TelephonyManager–Providesinformationtotheapplicationaboutthetelephony
servicesavailableonthedevicesuchasstatusandsubscriberinformation.
· LocationManager–Providesaccesstothelocationservicesallowinganapplication
toreceiveupdatesaboutlocationchanges.
8.6Applications
LocatedatthetopoftheAndroidsoftwarestackaretheapplications.Thesecompriseboth
thenativeapplicationsprovidedwiththeparticularAndroidimplementation(forexample
webbrowserandemailapplications)andthethirdpartyapplicationsinstalledbytheuser
afterpurchasingthedevice.
8.7Summary
AgoodAndroiddevelopmentknowledgefoundationrequiresanunderstandingofthe
overallarchitectureofAndroid.Androidisimplementedintheformofasoftwarestack
architectureconsistingofaLinuxkernel,aruntimeenvironmentandcorresponding
libraries,anapplicationframeworkandasetofapplications.Applicationsare
predominantlywritteninJavaandcompileddowntobytecodeformatwithintheAndroid
Studiobuildenvironment.Whentheapplicationissubsequentlyinstalledonadevice,this
bytecodeiscompileddownbytheAndroidRuntime(ART)tothenativeformatusedby
theCPU.ThekeygoalsoftheAndroidarchitectureareperformanceandefficiency,both
inapplicationexecutionandintheimplementationofreuseinapplicationdesign.
9.TheAnatomyofanAndroidApplication
Regardlessofyourpriorprogrammingexperiences,beitWindows,MacOSX,Linuxor
eveniOSbased,thechancesaregoodthatAndroiddevelopmentisquiteunlikeanything
youhaveencounteredbefore.
Theobjectiveofthischapter,therefore,istoprovideanunderstandingofthehigh-level
conceptsbehindthearchitectureofAndroidapplications.Indoingso,wewillexplorein
detailboththevariouscomponentsthatcanbeusedtoconstructanapplicationandthe
mechanismsthatallowthesetoworktogethertocreateacohesiveapplication.
9.1AndroidActivities
Thosefamiliarwithobject-orientedprogramminglanguagessuchasJava,C++orC#will
befamiliarwiththeconceptofencapsulatingelementsofapplicationfunctionalityinto
classesthataretheninstantiatedasobjectsandmanipulatedtocreateanapplication.Since
AndroidapplicationsarewritteninJava,thisisstillverymuchthecase.Android,
however,alsotakestheconceptofre-usablecomponentstoahigherlevel.
Androidapplicationsarecreatedbybringingtogetheroneormorecomponentsknownas
Activities.Anactivityisasingle,standalonemoduleofapplicationfunctionalitythat
usuallycorrelatesdirectlytoasingleuserinterfacescreenanditscorresponding
functionality.Anappointmentsapplicationmight,forexample,haveanactivityscreen
thatdisplaysappointmentssetupforthecurrentday.Theapplicationmightalsoutilizea
secondactivityconsistingofascreenwherenewappointmentsmaybeenteredbytheuser.
Activitiesareintendedasfullyreusableandinterchangeablebuildingblocksthatcanbe
sharedamongstdifferentapplications.Anexistingemailapplication,forexample,might
containanactivityspecificallyforcomposingandsendinganemailmessage.Adeveloper
mightbewritinganapplicationthatalsohasarequirementtosendanemailmessage.
Ratherthandevelopanemailcompositionactivityspecificallyforthenewapplication,the
developercansimplyusetheactivityfromtheexistingemailapplication.
ActivitiesarecreatedassubclassesoftheAndroidActivityclassandmustbeimplemented
soastobeentirelyindependentofotheractivitiesintheapplication.Inotherwords,a
sharedactivitycannotrelyonbeingcalledataknownpointinaprogramflow(sinceother
applicationsmaymakeuseoftheactivityinunanticipatedways)andoneactivitycannot
directlycallmethodsoraccessinstancedataofanotheractivity.This,instead,isachieved
usingIntentsandContentProviders.
Bydefault,anactivitycannotreturnresultstotheactivityfromwhichitwasinvoked.If
thisfunctionalityisrequired,theactivitymustbespecificallystartedasasub-activityof
theoriginatingactivity.
9.2AndroidIntents
Intentsarethemechanismbywhichoneactivityisabletolaunchanotherandimplement
theflowthroughtheactivitiesthatmakeupanapplication.Intentsconsistofadescription
oftheoperationtobeperformedand,optionally,thedataonwhichitistobeperformed.
Intentscanbeexplicit,inthattheyrequestthelaunchofaspecificactivitybyreferencing
theactivitybyclassname,orimplicitbystatingeitherthetypeofactiontobeperformed
orprovidingdataofaspecifictypeonwhichtheactionistobeperformed.Inthecaseof
implicitintents,theAndroidruntimewillselecttheactivitytolaunchthatmostclosely
matchesthecriteriaspecifiedbytheIntentusingaprocessreferredtoasIntentResolution.
9.3BroadcastIntents
AnothertypeofIntent,theBroadcastIntent,isasystemwideintentthatissentouttoall
applicationsthathaveregisteredan“interested”BroadcastReceiver.TheAndroidsystem,
forexample,willtypicallysendoutBroadcastIntentstoindicatechangesindevicestatus
suchasthecompletionofsystemstartup,connectionofanexternalpowersourcetothe
deviceorthescreenbeingturnedonoroff.
ABroadcastIntentcanbenormal(asynchronous)inthatitissenttoallinterested
BroadcastReceiversatmoreorlessthesametime,ororderedinthatitissenttoone
receiveratatimewhereitcanbeprocessedandtheneitherabortedorallowedtobe
passedtothenextBroadcastReceiver.
9.4BroadcastReceivers
BroadcastReceiversarethemechanismbywhichapplicationsareabletorespondto
BroadcastIntents.ABroadcastReceivermustberegisteredbyanapplicationand
configuredwithanIntentFiltertoindicatethetypesofbroadcastinwhichitisinterested.
Whenamatchingintentisbroadcast,thereceiverwillbeinvokedbytheAndroidruntime
regardlessofwhethertheapplicationthatregisteredthereceiveriscurrentlyrunning.The
receiverthenhas5secondsinwhichtocompleteanytasksrequiredofit(suchas
launchingaService,makingdataupdatesorissuinganotificationtotheuser)before
returning.BroadcastReceiversoperateinthebackgroundanddonothaveauserinterface.
9.5AndroidServices
AndroidServicesareprocessesthatruninthebackgroundanddonothaveauser
interface.Theycanbestartedandsubsequentlymanagedfromactivities,Broadcast
ReceiversorotherServices.AndroidServicesareidealforsituationswhereanapplication
needstocontinueperformingtasksbutdoesnotnecessarilyneedauserinterfacetobe
visibletotheuser.AlthoughServiceslackauserinterface,theycanstillnotifytheuserof
eventsusingnotificationsandtoasts(smallnotificationmessagesthatappearonthescreen
withoutinterruptingthecurrentlyvisibleactivity)andarealsoabletoissueIntents.
ServicesaregivenahigherprioritybytheAndroidruntimethanmanyotherprocessesand
willonlybeterminatedasalastresortbythesysteminordertofreeupresources.Inthe
eventthattheruntimedoesneedtokillaService,however,itwillbeautomatically
restartedassoonasadequateresourcesonceagainbecomeavailable.AServicecanreduce
theriskofterminationbydeclaringitselfasneedingtorunintheforeground.Thisis
achievedbymakingacalltostartForeground().Thisisonlyrecommendedforsituations
whereterminationwouldbedetrimentaltotheuserexperience(forexample,iftheuseris
listeningtoaudiobeingstreamedbytheService).
ExamplesituationswhereaServicemightbeapracticalsolutioninclude,aspreviously
mentioned,thestreamingofaudiothatshouldcontinuewhentheapplicationisnolonger
active,orastockmarkettrackingapplicationthatneedstonotifytheuserwhenashare
hitsaspecifiedprice.
9.6ContentProviders
ContentProvidersimplementamechanismforthesharingofdatabetweenapplications.
Anyapplicationcanprovideotherapplicationswithaccesstoitsunderlyingdatathrough
theimplementationofaContentProviderincludingtheabilitytoadd,removeandquery
thedata(subjecttopermissions).AccesstothedataisprovidedviaaUniversalResource
Identifier(URI)definedbytheContentProvider.Datacanbesharedintheformofafile
oranentireSQLitedatabase.
ThenativeAndroidapplicationsincludeanumberofstandardContentProvidersallowing
applicationstoaccessdatasuchascontactsandmediafiles.
TheContentProviderscurrentlyavailableonanAndroidsystemmaybelocatedusinga
ContentResolver.
9.7TheApplicationManifest
Thegluethatpullstogetherthevariouselementsthatcompriseanapplicationisthe
ApplicationManifestfile.ItiswithinthisXMLbasedfilethattheapplicationoutlinesthe
activities,services,broadcastreceivers,dataprovidersandpermissionsthatmakeupthe
completeapplication.
9.8ApplicationResources
InadditiontothemanifestfileandtheDexfilesthatcontainthebytecode,anAndroid
applicationpackagewillalsotypicallycontainacollectionofresourcefiles.Thesefiles
containresourcessuchasthestrings,images,fontsandcolorsthatappearintheuser
interfacetogetherwiththeXMLrepresentationoftheuserinterfacelayouts.Bydefault,
thesefilesarestoredinthe/ressub-directoryoftheapplicationproject’shierarchy.
9.9ApplicationContext
Whenanapplicationiscompiled,aclassnamedRiscreatedthatcontainsreferencestothe
applicationresources.Theapplicationmanifestfileandtheseresourcescombinetocreate
whatisknownastheApplicationContext.Thiscontext,representedbytheAndroid
Contextclass,maybeusedintheapplicationcodetogainaccesstotheapplication
resourcesatruntime.Inaddition,awiderangeofmethodsmaybecalledonan
application’scontexttogatherinformationandmakechangestotheapplication’s
environmentatruntime.
9.10Summary
AnumberofdifferentelementscanbebroughttogetherinordertocreateanAndroid
application.Inthischapter,wehaveprovidedahigh-leveloverviewofactivities,Services,
IntentsandBroadcastReceiverstogetherwithanoverviewofthemanifestfileand
applicationresources.
Maximumreuseandinteroperabilityarepromotedthroughthecreationofindividual,
standalonemodulesoffunctionalityintheformofactivitiesandintents,whiledata
sharingbetweenapplicationsisachievedbytheimplementationofcontentproviders.
Whileactivitiesarefocusedonareaswheretheuserinteractswiththeapplication(an
activityessentiallyequatingtoasingleuserinterfacescreen),backgroundprocessingis
typicallyhandledbyServicesandBroadcastReceivers.
ThecomponentsthatmakeuptheapplicationareoutlinedfortheAndroidruntimesystem
inamanifestfilewhich,combinedwiththeapplication’sresources,representsthe
application’scontext.
Muchhasbeencoveredinthischapterthatismostlikelynewtotheaveragedeveloper.
Restassured,however,thatextensiveexplorationandpracticaluseoftheseconceptswill
bemadeinsubsequentchapterstoensureasolidknowledgefoundationonwhichtobuild
yourownapplications.
10.UnderstandingAndroidApplicationand
ActivityLifecycles
IntheprecedingfewchapterswehavelearnedthatAndroidapplicationsrunwithin
processesandthattheyarecomprisedofmultiplecomponentsintheformofactivities,
ServicesandBroadcastReceivers.Thegoalofthischapteristoexpandonthisknowledge
bylookingatthelifecycleofapplicationsandactivitieswithintheAndroidruntime
system.
Regardlessofthefanfareabouthowmuchmemoryandcomputingpowerresidesinthe
mobiledevicesoftodaycomparedtothedesktopsystemsofyesterday,itisimportantto
keepinmindthatthesedevicesarestillconsideredtobe“resourceconstrained”bythe
standardsofmoderndesktopandlaptopbasedsystems,particularlyintermsofmemory.
Assuch,akeyresponsibilityoftheAndroidsystemistoensurethattheselimited
resourcesaremanagedeffectivelyandthatboththeoperatingsystemandtheapplications
runningonitremainresponsivetotheuseratalltimes.Inordertoachievethis,Androidis
givenfullcontroloverthelifecycleandstateofboththeprocessesinwhichthe
applicationsrun,andtheindividualcomponentsthatcomprisethoseapplications.
AnimportantfactorindevelopingAndroidapplications,therefore,istogainan
understandingofboththeapplicationandactivitylifecyclemanagementmodelsof
Android,andthewaysinwhichanapplicationcanreacttothestatechangesthatarelikely
tobeimposeduponitduringitsexecutionlifetime.
10.1AndroidApplicationsandResourceManagement
EachrunningAndroidapplicationisviewedbytheoperatingsystemasaseparateprocess.
Ifthesystemidentifiesthatresourcesonthedevicearereachingcapacityitwilltakesteps
toterminateprocessestofreeupmemory.
Whenmakingadeterminationastowhichprocesstoterminateinordertofreeup
memory,thesystemtakesintoconsiderationboththepriorityandstateofallcurrently
runningprocesses,combiningthesefactorstocreatewhatisreferredtobyGoogleasan
importancehierarchy.Processesarethenterminatedstartingwiththelowestpriorityand
workingupthehierarchyuntilsufficientresourceshavebeenliberatedforthesystemto
function.
10.2AndroidProcessStates
Processeshostapplicationsandapplicationsaremadeupofcomponents.Withinan
Androidsystem,thecurrentstateofaprocessisdefinedbythehighest-rankingactive
componentwithintheapplicationthatithosts.AsoutlinedinFigure10-1,aprocesscan
beinoneofthefollowingfivestatesatanygiventime:
Figure10-1
10.2.1ForegroundProcess
Theseprocessesareassignedthehighestlevelofpriority.Atanyonetime,thereare
unlikelytobemorethanoneortwoforegroundprocessesactiveandtheseareusuallythe
lasttobeterminatedbythesystem.Aprocessmustmeetoneormoreofthefollowing
criteriatoqualifyforforegroundstatus:
· Hostsanactivitywithwhichtheuseriscurrentlyinteracting.
· HostsaServiceconnectedtotheactivitywithwhichtheuserisinteracting.
· HostsaServicethathasindicated,viaacalltostartForeground(),thattermination
wouldbedisruptivetotheuserexperience.
· HostsaServiceexecutingeitheritsonCreate(),onResume()oronStart()
callbacks.
· HostsaBroadcastReceiverthatiscurrentlyexecutingitsonReceive()method.
10.2.2VisibleProcess
Aprocesscontaininganactivitythatisvisibletotheuserbutisnottheactivitywithwhich
theuserisinteractingisclassifiedasa“visibleprocess”.Thisistypicallythecasewhenan
activityintheprocessisvisibletotheuserbutanotheractivity,suchasapartialscreenor
dialog,isintheforeground.AprocessisalsoeligibleforvisiblestatusifithostsaService
thatis,itself,boundtoavisibleorforegroundactivity.
10.2.3ServiceProcess
ProcessesthatcontainaServicethathasalreadybeenstartedandiscurrentlyexecuting.
10.2.4BackgroundProcess
Aprocessthatcontainsoneormoreactivitiesthatarenotcurrentlyvisibletotheuser,and
doesnothostaServicethatqualifiesforServiceProcessstatus.Processesthatfallinto
thiscategoryareathighriskofterminationintheeventthatadditionalmemoryneedsto
befreedforhigherpriorityprocesses.Androidmaintainsadynamiclistofbackground
processes,terminatingprocessesinchronologicalordersuchthatprocessesthatwerethe
leastrecentlyintheforegroundarekilledfirst.
10.2.5EmptyProcess
Emptyprocessesnolongercontainanyactiveapplicationsandareheldinmemoryready
toserveashostsfornewlylaunchedapplications.Thisissomewhatanalogoustokeeping
thedoorsopenandtheenginerunningonabusinanticipationofpassengersarriving.
Suchprocessesare,obviously,consideredthelowestpriorityandarethefirsttobekilled
tofreeupresources.
10.3Inter-ProcessDependencies
Thesituationwithregardtodeterminingthehighestpriorityprocessisslightlymore
complexthanoutlinedintheprecedingsectionforthesimplereasonthatprocessescan
oftenbeinter-dependent.Assuch,whenmakingadeterminationastothepriorityofa
process,theAndroidsystemwillalsotakeintoconsiderationwhethertheprocessisin
somewayservinganotherprocessofhigherpriority(forexample,aserviceprocessacting
asthecontentproviderforaforegroundprocess).Asabasicrule,theAndroid
documentationstatesthataprocesscanneverberankedlowerthananotherprocessthatit
iscurrentlyserving.
10.4TheActivityLifecycle
Aswehavepreviouslydetermined,thestateofanAndroidprocessisdeterminedlargely
bythestatusoftheactivitiesandcomponentsthatmakeuptheapplicationthatithosts.It
isimportanttounderstand,therefore,thattheseactivitiesalsotransitionthroughdifferent
statesduringtheexecutionlifetimeofanapplication.Thecurrentstateofanactivityis
determined,inpart,byitspositioninsomethingcalledtheActivityStack.
10.5TheActivityStack
ForeachapplicationthatisrunningonanAndroiddevice,theruntimesystemmaintains
anActivityStack.Whenanapplicationislaunched,thefirstoftheapplication’sactivities
tobestartedisplacedontothestack.Whenasecondactivityisstarted,itisplacedonthe
topofthestackandthepreviousactivityispusheddown.Theactivityatthetopofthe
stackisreferredtoastheactive(orrunning)activity.Whentheactiveactivityexits,itis
poppedoffthestackbytheruntimeandtheactivitylocatedimmediatelybeneathitinthe
stackbecomesthecurrentactiveactivity.Theactivityatthetopofthestackmight,for
example,simplyexitbecausethetaskforwhichitisresponsiblehasbeencompleted.
Alternatively,theusermayhaveselecteda“Back”buttononthescreentoreturntothe
previousactivity,causingthecurrentactivitytobepoppedoffthestackbytheruntime
systemandthereforedestroyed.AvisualrepresentationoftheAndroidActivityStackis
illustratedinFigure10-2:
Figure10-2
Asshowninthediagram,newactivitiesarepushedontothetopofthestackwhenthey
arestarted.Thecurrentactiveactivityislocatedatthetopofthestackuntilitiseither
pusheddownthestackbyanewactivity,orpoppedoffthestackwhenitexitsortheuser
navigatestothepreviousactivity.Intheeventthatresourcesbecomeconstrained,the
runtimewillkillactivities,startingwiththoseatthebottomofthestack.
TheActivityStackiswhatisreferredtoinprogrammingterminologyasaLast-In-FirstOut(LIFO)stackinthatthelastitemtobepushedontothestackisthefirsttobepopped
off.
10.6ActivityStates
Anactivitycanbeinoneofanumberofdifferentstatesduringthecourseofitsexecution
withinanapplication:
· Active/Running–TheactivityisatthetopoftheActivityStack,istheforeground
taskvisibleonthedevicescreen,hasfocusandiscurrentlyinteractingwiththeuser.
Thisistheleastlikelyactivitytobeterminatedintheeventofaresourceshortage.
· Paused–Theactivityisvisibletotheuserbutdoesnotcurrentlyhavefocus(typically
becausethisactivityispartiallyobscuredbythecurrentactiveactivity).Paused
activitiesareheldinmemory,remainattachedtothewindowmanager,retainallstate
informationandcanquicklyberestoredtoactivestatuswhenmovedtothetopofthe
ActivityStack.
· Stopped–Theactivityiscurrentlynotvisibletotheuser(inotherwordsitistotally
obscuredonthedevicedisplaybyotheractivities).Aswithpausedactivities,itretains
allstateandmemberinformation,butisathigherriskofterminationinlowmemory
situations.
· Killed–TheActivityhasbeenterminatedbytheruntimesysteminordertofreeup
memoryandisnolongerpresentontheActivityStack.Suchactivitiesmustbe
restartedifrequiredbytheapplication.
10.7ConfigurationChanges
Sofarinthischapter,wehavelookedattwoofthecausesforthechangeinstateofan
Androidactivity,namelythemovementofanactivitybetweentheforegroundand
background,andterminationofanactivitybytheruntimesysteminordertofreeup
memory.Infact,thereisathirdscenarioinwhichthestateofanactivitycandramatically
changeandthisinvolvesachangetothedeviceconfiguration.
Bydefault,anyconfigurationchangethatimpactstheappearanceofanactivity(suchas
rotatingtheorientationofthedevicebetweenportraitandlandscape,orchangingasystem
fontsetting)willcausetheactivitytobedestroyedandrecreated.Thereasoningbehind
thisisthatsuchchangesaffectresourcessuchasthelayoutoftheuserinterfaceand
simplydestroyingandrecreatingimpactedactivitiesisthequickestwayforanactivityto
respondtotheconfigurationchange.
10.8HandlingStateChange
Ifnothingelse,itshouldbeclearfromthischapterthatanapplicationand,bydefinition,
thecomponentscontainedthereinwilltransitionthroughmanystatesduringthecourseof
itslifespan.Ofparticularimportanceisthefactthatthesestatechanges(uptoand
includingcompletetermination)areimposedupontheapplicationbytheAndroidruntime
subjecttotheactionsoftheuserandtheavailabilityofresourcesonthedevice.
Inpractice,however,thesestatechangesarenotimposedentirelywithoutnoticeandan
applicationwill,inmostcircumstances,benotifiedbytheruntimesystemofthechanges
andgiventheopportunitytoreactaccordingly.Thiswilltypicallyinvolvesavingor
restoringbothinternaldatastructuresanduserinterfacestate,therebyallowingtheuserto
switchseamlesslybetweenapplicationsandprovidingatleasttheappearanceofmultiple,
concurrentlyrunningapplications.Thestepsinvolvedingracefullyhandlingstatechanges
willbecoveredindetailinthenextchapterentitledHandlingAndroidActivityState
Changes.
10.9Summary
Mobiledevicesaretypicallyconsideredtoberesourceconstrained,particularlyintermsof
onboardmemorycapacity.Consequently,aprimeresponsibilityoftheAndroidoperating
systemistoensurethatapplications,andtheoperatingsystemingeneral,remain
responsivetotheuser.
ApplicationsarehostedonAndroidwithinprocesses.Eachapplication,inturn,ismadeup
ofcomponentsintheformofactivitiesandServices.
TheAndroidruntimesystemhasthepowertoterminatebothprocessesandindividual
activitiesinordertofreeupmemory.Processstateistakenintoconsiderationbythe
runtimesystemwhendecidingwhetheraprocessisasuitablecandidatefortermination.
Thestateofaprocessislargelydependentuponthestatusoftheactivitieshostedbythat
process.
Thekeymessageofthischapteristhatanapplicationmovesthroughavarietyofstates
duringitsexecutionlifespanandhasverylittlecontroloveritsdestinywithintheAndroid
runtimeenvironment.Thoseprocessesandactivitiesthatarenotdirectlyinteractingwith
theuserrunahigherriskofterminationbytheruntimesystem.Anessentialelementof
Androidapplicationdevelopment,therefore,involvestheabilityofanapplicationto
respondtostatechangenotificationsfromtheoperatingsystem,atopicthatiscoveredin
thenextchapter.
11.HandlingAndroidActivityStateChanges
BasedontheinformationoutlinedinthechapterentitledUnderstandingAndroid
ApplicationandActivityLifecyclesitisnowevidentthattheactivitiesthatmakeupan
applicationpassthroughavarietyofdifferentstatesduringthecourseoftheapplication’s
lifespan.ThechangefromonestatetotheotherisimposedbytheAndroidruntimesystem
andis,therefore,largelybeyondthecontroloftheactivityitself.Thatsaid,inmost
instancestheruntimewillprovidetheactivityinquestionwithanotificationofthe
impendingstatechange,therebygivingittimetoreactaccordingly.Inmostcases,thiswill
involvesavingorrestoringdatarelatingtothestateoftheactivityanditsuserinterface.
Theprimaryobjectiveofthischapteristoprovideahigh-leveloverviewofthewaysin
whichanactivitymaybenotifiedofastatechangeandtooutlinetheareaswhereitis
advisabletosaveorrestorestateinformation.Havingcoveredthisinformation,the
chapterwillthentouchbrieflyonthesubjectofactivitylifetimes.
11.1TheActivityClass
Withfewexceptions,activitiesinanapplicationarecreatedassubclassesofeitherthe
AndroidActivityclass,oranotherclassthatis,itself,subclassedfromtheActivityclass
(forexampletheAppCompatActivityorFragmentActivityclasses).
Consider,forexample,thesimpleAndroidSampleprojectcreatedinCreatinganExample
AndroidAppinAndroidStudio.LoadthisprojectintotheAndroidStudioenvironment
andlocatetheAndroidSampleActvity.javafile(locatedinapp->java->com.<your
domain>.androidsample).Havinglocatedthefile,doubleclickonittoloaditintothe
editorwhereitshouldreadasfollows:
packagecom.ebookfrenzy.androidsample;
importandroid.os.Bundle;
importandroid.support.design.widget.FloatingActionButton;
importandroid.support.design.widget.Snackbar;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.View;
importandroid.view.Menu;
importandroid.view.MenuItem;
publicclassAndroidSampleActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_android_sample);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButtonfab=
(FloatingActionButton)findViewById(R.id.fab);
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
Snackbar.make(view,
“Replacewithyourownaction”,
Snackbar.LENGTH_LONG)
.setAction(“Action”,null).show();
}
});
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbarifitis
present.
getMenuInflater().inflate(R.menu.menu_android_sample,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
//Handleactionbaritemclickshere.Theactionbarwill
//automaticallyhandleclicksontheHome/Upbutton,solong
//asyouspecifyaparentactivityinAndroidManifest.xml.
intid=item.getItemId();
//noinspectionSimplifiableIfStatement
if(id==R.id.action_settings){
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
}
Whentheprojectwascreated,weinstructedAndroidStudioalsotocreateaninitial
activitynamedAndroidSampleActivity.Asisevidentfromtheabovecode,the
AndroidSampleActivityclassextends,andisthereforeasubclassof,the
AppCompatActivityclass.
AreviewofthereferencedocumentationfortheAppCompatActivityclasswouldreveal
thatitisitselfasubclassoftheActivityclass.ThiscanbeverifiedwithintheAndroid
StudioeditorusingtheHierarchytoolwindow.WiththeAndroidSampleActivity.javafile
loadedintotheeditor,clickonAppCompatActivityinthepublicclassdeclarationlineand
presstheCtrl-Hkeyboardshortcut.Thehierarchytoolwindowwillsubsequentlyappear
displayingtheclasshierarchyfortheselectedclass.AsillustratedinFigure11-1,
AppCompatActivityisclearlysubclassedfromtheFragmentActivityclasswhichisitself
ultimatelyasubclassoftheActivityclass:
Figure11-1
TheActivityclassanditssubclassescontainarangeofmethodsthatareintendedtobe
calledbytheAndroidruntimetonotifyanactivitythatitsstateischanging.Forthe
purposesofthischapter,wewillrefertotheseastheactivitylifecyclemethods.Anactivity
classsimplyneedstooverridethesemethodsandimplementthenecessaryfunctionality
withintheminordertoreactaccordinglytostatechanges.
OnesuchmethodisnamedonCreate()and,turningonceagaintotheabovecode
fragment,wecanseethatthismethodhasalreadybeenoverriddenandimplementedforus
intheAndroidSampleActivityclass.Inalatersectionwewillexploreindetailboth
onCreate()andtheotherrelevantlifecyclemethodsoftheActivityclass.
11.2DynamicStatevs.PersistentState
AkeyobjectiveofActivitylifecyclemanagementisensuringthatthestateoftheactivity
issavedandrestoredatappropriatetimes.Whentalkingaboutstateinthiscontextwe
meanthedatathatiscurrentlybeingheldwithintheactivityandtheappearanceofthe
userinterface.Theactivitymight,forexample,maintainadatamodelinmemorythat
needstobesavedtoadatabase,contentproviderorfile.Suchstateinformation,becauseit
persistsfromoneinvocationoftheapplicationtoanother,isreferredtoasthepersistent
state.
Theappearanceoftheuserinterface(suchastextenteredintoatextfieldbutnotyet
committedtotheapplication’sinternaldatamodel)isreferredtoasthedynamicstate,
sinceitistypicallyonlyretainedduringasingleinvocationoftheapplication(andalso
referredtoasuserinterfacestateorinstancestate).
Understandingthedifferencesbetweenthesetwostatesisimportantbecauseboththe
waystheyaresaved,andthereasonsfordoingso,differ.
Thepurposeofsavingthepersistentstateistoavoidthelossofdatathatmayresultfrom
anactivitybeingkilledbytheruntimesystemwhileinthebackground.Thedynamicstate,
ontheotherhand,issavedandrestoredforreasonsthatareslightlymorecomplex.
Consider,forthesakeofanexample,thatanapplicationcontainsanactivity(whichwe
willrefertoasActivityA)containingatextfieldandsomeradiobuttons.Duringthe
courseofusingtheapplication,theuserenterssometextintothetextfieldandmakesa
selectionfromtheradiobuttons.Beforeperforminganactiontosavethesechanges,
however,theuserthenswitchestoanotheractivitycausingActivityAtobepusheddown
theActivityStackandplacedintothebackground.Aftersometime,theruntimesystem
ascertainsthatmemoryislowandconsequentlykillsActivityAtofreeupresources.As
farastheuserisconcerned,however,ActivityAwassimplyplacedintothebackground
andisreadytobemovedtotheforegroundatanytime.OnreturningActivityAtothe
foregroundtheuserwould,quitereasonably,expecttheenteredtextandradiobutton
selectionstohavebeenretained.Inthisscenario,however,anewinstanceofActivityA
willhavebeencreatedand,ifthedynamicstatewasnotsavedandrestored,theprevious
userinputlost.
Themainpurposeofsavingdynamicstate,therefore,istogivetheperceptionofseamless
switchingbetweenforegroundandbackgroundactivities,regardlessofthefactthat
activitiesmayactuallyhavebeenkilledandrestartedwithouttheuser’sknowledge.
Themechanismsforsavingpersistentanddynamicstatewillbecomeclearerinthe
followingsectionsofthischapter.
11.3TheAndroidActivityLifecycleMethods
Aspreviouslyexplained,theActivityclasscontainsanumberoflifecyclemethodswhich
actaseventhandlerswhenthestateofanActivitychanges.Theprimarymethods
supportedbytheAndroidActivityclassareasfollows:
· onCreate(BundlesavedInstanceState)–Themethodthatiscalledwhentheactivity
isfirstcreatedandtheideallocationformostinitializationtaskstobeperformed.The
methodispassedanargumentintheformofaBundleobjectthatmaycontaindynamic
stateinformation(typicallyrelatingtothestateoftheuserinterface)fromaprior
invocationoftheactivity.
· onRestart()–Calledwhentheactivityisabouttorestartafterhavingpreviouslybeen
stoppedbytheruntimesystem.
· onStart()–AlwayscalledimmediatelyafterthecalltotheonCreate()oronRestart()
methods,thismethodindicatestotheactivitythatitisabouttobecomevisibletothe
user.ThiscallwillbefollowedbyacalltoonResume()iftheactivitymovestothetop
oftheactivitystack,oronStop()intheeventthatitispusheddownthestackbyanother
activity.
· onResume()–Indicatesthattheactivityisnowatthetopoftheactivitystackandis
theactivitywithwhichtheuseriscurrentlyinteracting.
· onPause()–Indicatesthatapreviousactivityisabouttobecometheforeground
activity.ThiscallwillbefollowedbyacalltoeithertheonResume()oronStop()
methoddependingonwhethertheactivitymovesbacktotheforegroundorbecomes
invisibletotheuser.Stepsshouldbetakenwithinthismethodtostoreanypersistent
datarequiredbytheactivity(suchasdatastoredtoacontentprovider,databaseorfile).
ThismethodshouldalsoensurethatanyCPUintensivetaskssuchasanimationare
stopped.
· onStop()–Theactivityisnownolongervisibletotheuser.Thetwopossible
scenariosthatmayfollowthiscallareacalltoonRestart()intheeventthattheactivity
movestotheforegroundagain,oronDestroy()iftheactivityisbeingterminated.
· onDestroy()–Theactivityisabouttobedestroyed,eithervoluntarilybecausethe
activityhascompleteditstasksandhascalledthefinish()methodorbecausethe
runtimeisterminatingiteithertoreleasememoryorduetoaconfigurationchange
(suchastheorientationofthedevicechanging).Itisimportanttonotethatacallwill
notalwaysbemadetoonDestroy()whenanactivityisterminated.
Inadditiontothelifecyclemethodsoutlinedabove,therearetwomethodsintended
specificallyforsavingandrestoringthedynamicstateofanactivity:
· onRestoreInstanceState(BundlesavedInstanceState)–Thismethodiscalled
immediatelyafteracalltotheonStart()methodintheeventthattheactivityis
restartingfromapreviousinvocationinwhichstatewassaved.AswithonCreate(),this
methodispassedaBundleobjectcontainingthepreviousstatedata.Thismethodis
typicallyusedinsituationswhereitmakesmoresensetorestoreapreviousstateafter
theinitializationoftheactivityhasbeenperformedinonCreate()andonStart().
· onSaveInstanceState(BundleoutState)–Calledbeforeanactivityisdestroyedso
thatthecurrentdynamicstate(usuallyrelatingtotheuserinterface)canbesaved.The
methodispassedtheBundleobjectintowhichthestateshouldbesavedandwhichis
subsequentlypassedthroughtotheonCreate()andonRestoreInstanceState()methods
whentheactivityisrestarted.Notethatthismethodisonlycalledinsituationswhere
theruntimeascertainsthatdynamicstateneedstobesaved.
Whenoverridingtheabovemethodsinanactivity,itisimportanttorememberthatwith
theexceptionofonRestoreInstanceState()andonSaveInstanceState(),themethod
implementationmustincludeacalltothecorrespondingmethodintheActivitysuper
class.Forexample,thefollowingmethodoverridestheonRestart()methodbutalso
includesacalltothesuperclassinstanceofthemethod:
protectedvoidonRestart(){
super.onRestart();
Log.i(TAG,“onRestart”);
}
Failuretomakethissuperclasscallinmethodoverrideswillresultintheruntime
throwinganexceptionduringexecutionoftheactivity.Whilecallstothesuperclassinthe
onRestoreInstanceState()andonSaveInstanceState()areoptionalthereareconsiderable
benefitsindoingso,asubjectthatwillbecoveredinthechapterentitledSavingand
RestoringtheUserInterfaceStateofanAndroidActivity.
11.4ActivityLifetimes
Thefinaltopictobecoveredinvolvesanoutlineoftheentire,visibleandforeground
lifetimesthroughwhichanactivitywilltransitionduringexecution:
· EntireLifetime–Theterm“entirelifetime”isusedtodescribeeverythingthattakes
placewithinanactivitybetweentheinitialcalltotheonCreate()methodandthecallto
onDestroy()priortotheactivityterminating.
· VisibleLifetime–Coverstheperiodsofexecutionofanactivitybetweenthecallto
onStart()andonStop().Duringthisperiodtheactivityisvisibletotheuserthoughmay
notbetheactivitywithwhichtheuseriscurrentlyinteracting.
· ForegroundLifetime–Referstotheperiodsofexecutionbetweencallstothe
onResume()andonPause()methods.
Itisimportanttonotethatanactivitymaypassthroughtheforegroundandvisible
lifetimesmultipletimesduringthecourseoftheentirelifetime.
TheconceptsoflifetimesandlifecyclemethodsareillustratedinFigure11-2:
Figure11-2
11.5Summary
AllactivitiesarederivedfromtheAndroidActivityclasswhich,inturn,containsanumber
ofeventmethodsthataredesignedtobecalledbytheruntimesystemwhenthestateofan
activitychanges.Byoverridingthesemethods,anactivitycanrespondtostatechanges
and,wherenecessary,takestepstosaveandrestorethecurrentstateofboththeactivity
andtheapplication.Activitystatecanbethoughtofastakingtwoforms.Thepersistent
statereferstodatathatneedstobestoredbetweenapplicationinvocations(forexampleto
afileordatabase).Dynamicstate,ontheotherhand,relatesinsteadtothecurrent
appearanceoftheuserinterface.
Inthischapter,wehavehighlightedthelifecyclemethodsavailabletoactivitiesand
coveredtheconceptofactivitylifetimes.Inthenextchapter,entitledAndroidActivity
StateChanges–AnExampleApplication,wewillimplementanexampleapplicationthat
putsmuchofthistheoryintopractice.
12.AndroidActivityStateChangesby
Example
Thepreviouschaptershavediscussedinsomedetailthedifferentstatesandlifecyclesof
theactivitiesthatcompriseanAndroidapplication.Inthischapter,wewillputthetheory
ofhandlingactivitystatechangesintopracticethroughthecreationofanexample
application.Thepurposeofthisexampleapplicationistoprovidearealworld
demonstrationofanactivityasitpassesthroughavarietyofdifferentstateswithinthe
Androidruntime.
Inthenextchapter,entitledSavingandRestoringtheStateofanAndroidActivity,the
exampleprojectconstructedinthischapterwillbeextendedtodemonstratethesavingand
restorationofdynamicactivitystate.
12.1CreatingtheStateChangeExampleProject
Thefirststepinthisexerciseistocreatethenewproject.BeginbylaunchingAndroid
Studioand,ifnecessary,closinganycurrentlyopenprojectsusingtheFile->Close
ProjectmenuoptionsothattheWelcomescreenappears.
SelecttheStartanewAndroidStudioprojectquickstartoptionfromthewelcomescreen
and,withintheresultingnewprojectdialog,enterStateChangeintotheApplicationname
fieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofablankactivitynamedStateChangeActivity,acorrespondinglayoutnamed
activity_state_changeandamenuresourcenamedmenu_state_change.
Uponcompletionoftheprojectcreationprocess,theStateChangeprojectshouldbelisted
intheProjecttoolwindowlocatedalongtheleft-handedgeoftheAndroidStudiomain
windowwiththecontent_state_change.xmllayoutfilepre-loadedintotheDesigneras
illustratedinFigure12-1:
Figure12-1
Thenextactiontotakeinvolvesthedesignofthecontentareaoftheuserinterfaceforthe
activity.Thisisstoredinafilenamedcontent_state_change.xmlwhichshouldalreadybe
loadedintotheDesignertool.Ifitisnot,navigatetoitintheprojecttoolwindowwhereit
canbefoundintheapp->res->layoutfolder.Oncelocated,doubleclickingonthefile
willloaditintotheAndroidStudioDesignertool.
12.2DesigningtheUserInterface
WiththeuserinterfacelayoutloadedintotheDesignertool,itisnowtimetodesignthe
userinterfacefortheexampleapplication.Insteadofthe“Helloworld!”TextView
currentlypresentintheuserinterfacedesign,theactivityactuallyrequiresanEditText
view.SelecttheTextViewobjectintheDesignercanvasandpresstheDeletekeyonthe
keyboardtoremoveitfromthedesign.
FromthePalettelocatedontheleftsideoftheDesigner,locatetheTextFieldscategory
andclickanddragaPlainTextcomponentovertothevisualrepresentationofthedevice
screen.Movethecomponenttothecenterofthedisplaysothatthecenterguidelines
appearanddropitintoplacesothatthelayoutresemblesthatofFigure12-2.
Figure12-2
Notethatthelightbulbiconhasappearednexttotheviewindicatingthatassistanceis
availabletousinconfiguringthiscomponent.WhenworkingwithEditTextviewsinan
Androiduserinterfaceitisnecessarytodeclaretheinputtypefortheview.Thissimply
definesthetypeoftextordatathatwillbeenteredbytheuser.Forexample,iftheinput
typeissettoPhone,theuserwillberestrictedtoenteringnumericaldigitsintotheview.
Alternatively,iftheinputtypeissettoTextCapCharacters,theinputwilldefaulttoupper
casecharacters.Inputtypesettingsmayalsobecombined.
Forthepurposesofthisexample,wewillsettheinputtypetosupportgeneraltextinput.
Todoso,clickonthelightbulbiconfollowedbytherightfacingarrowattheendofthe
messagedescribingtheproblem.Fromthelistofpotentialsolutions(Figure12-3)clickon
theAddinputTypeattributeoption:
Figure12-3
FromtheresultingSetAttributeValuedialog,selectthetextoptionfromthedropdown
menubeforeclickingontheOKbutton.
ThefinalstepintheuserinterfacedesignprocessistoincreasethewidthoftheTextView
component.Withthecomponentselectedinthelayout,scrolldownthelistofattributesin
thePropertiespaneluntilthewidthattributecomesintoviewandenteravalueof200dp
asoutlinedinFigure12-4:
Figure12-4
12.3OverridingtheActivityLifecycleMethods
Atthispoint,theprojectcontainsasingleactivitynamedStateChangeActivity,whichis
derivedfromtheAndroidAppCompatActivityclass.Thesourcecodeforthisactivityis
containedwithintheStateChangeActivity.javafilewhichshouldalreadybeopeninan
editorsessionandrepresentedbyatabintheeditortabbar.Intheeventthatthefileisno
longeropen,navigatetoitintheProjecttoolwindowpanel(app->java->
com.ebookfrenzy.statechange->StateChangeActivity)anddoubleclickonittoloadthe
fileintotheeditor.Onceloadedthecodeshouldreadasfollows:
packagecom.ebookfrenzy.statechange;
importandroid.os.Bundle;
importandroid.support.design.widget.FloatingActionButton;
importandroid.support.design.widget.Snackbar;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.View;
importandroid.view.Menu;
importandroid.view.MenuItem;
publicclassStateChangeActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_state_change);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButtonfab=
(FloatingActionButton)findViewById(R.id.fab);
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
Snackbar.make(view,“Replacewithyourownaction”,
Snackbar.LENGTH_LONG)
.setAction(“Action”,null).show();
}
});
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbarifitis
present.
getMenuInflater().inflate(R.menu.menu_state_change,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
//Handleactionbaritemclickshere.Theactionbarwill
//automaticallyhandleclicksontheHome/Upbutton,solong
//asyouspecifyaparentactivityinAndroidManifest.xml.
intid=item.getItemId();
//noinspectionSimplifiableIfStatement
if(id==R.id.action_settings){
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
}
SofartheonlylifecyclemethodoverriddenbytheactivityistheonCreate()methodwhich
hasbeenimplementedtocallthesuperclassinstanceofthemethodbeforesettingupthe
userinterfacefortheactivity.Wewillnowmodifythismethodsothatitoutputsa
diagnosticmessageintheAndroidStudioLogCatpaneleachtimeitexecutes.Forthis,we
willusetheLogclass,whichrequiresthatweimportandroid.util.Loganddeclareatag
thatwillenableustofilterthesemessagesinthelogoutput:
packagecom.ebookfrenzy.statechange;
importandroid.os.Bundle;
importandroid.support.design.widget.FloatingActionButton;
importandroid.support.design.widget.Snackbar;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.View;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.util.Log;
publicclassStateChangeActivityextendsAppCompatActivity{
privatestaticfinalStringTAG=“StateChange”;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_state_change);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButtonfab=(FloatingActionButton)
findViewById(R.id.fab);
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
Snackbar.make(view,“Replacewithyourownaction”,
Snackbar.LENGTH_LONG)
.setAction(“Action”,null).show();
}
});
Log.i(TAG,“onCreate”);
}
Thenexttaskistooverridesomemoremethods,witheachonecontaininga
correspondinglogcall.Theseoverridemethodsmaybeaddedmanuallyorgenerated
usingtheAlt-InsertkeyboardshortcutasoutlinedinthechapterentitledTheBasicsofthe
AndroidStudioCodeEditor.NotethattheLogcallswillstillneedtobeaddedmanuallyif
themethodsarebeingauto-generated:
@Override
protectedvoidonStart(){
super.onStart();
Log.i(TAG,“onStart”);
}
@Override
protectedvoidonResume(){
super.onResume();
Log.i(TAG,“onResume”);
}
@Override
protectedvoidonPause(){
super.onPause();
Log.i(TAG,“onPause”);
}
@Override
protectedvoidonStop(){
super.onStop();
Log.i(TAG,“onStop”);
}
@Override
protectedvoidonRestart(){
super.onRestart();
Log.i(TAG,“onRestart”);
}
@Override
protectedvoidonDestroy(){
super.onDestroy();
Log.i(TAG,“onDestroy”);
}
@Override
protectedvoidonSaveInstanceState(BundleoutState){
super.onSaveInstanceState(outState);
Log.i(TAG,“onSaveInstanceState”);
}
@Override
protectedvoidonRestoreInstanceState(BundlesavedInstanceState){
super.onRestoreInstanceState(savedInstanceState);
Log.i(TAG,“onRestoreInstanceState”);
}
12.4FilteringtheLogCatPanel
ThepurposeofthecodeaddedtotheoverriddenmethodsinStateChangeActivity.javaisto
outputlogginginformationtotheLogCatpanelwithintheAndroidMonitortoolwindow.
Thisoutputcanbeconfiguredtodisplayalleventsrelatingtothedeviceoremulator
session,orrestrictedtothoseeventsthatrelatetothecurrentlyselectedapp.Theoutput
canalsobefurtherrestrictedtoonlythoselogeventsthatmatchaspecifiedfilter.
DisplaytheAndroidMonitortoolwindowandclickonthefiltermenu(markedasBin
Figure12-5)toreviewtheavailableoptions.WhenthismenuissettoShowonlyselected
application,onlythosemessagesrelatingtotheappselectedinthemenumarkedasAwill
bedisplayedintheLogCatpanel.ChoosingNoFilter,ontheotherhand,willdisplayall
themessagesgeneratedbythedeviceoremulator.
Figure12-5
Beforerunningtheapplication,itisworthdemonstratingthecreationofafilterwhich,
whenselected,willfurtherrestrictthelogoutputtoensurethatonlythoselogmessages
containingthetagdeclaredinouractivityaredisplayed.
Fromthefiltermenu,selecttheEditFilterConfigurationmenuoption.IntheCreateNew
LogcatFilterdialog(Figure12-6),namethefilterLifecycleand,intheLogTagfield,
entertheTagvaluedeclaredinStateChangeActivity.java(intheabovecodeexamplethis
wasStateChange).
Figure12-6
EnterthepackageidentifierinthePackageNamefieldand,whenthechangesare
complete,clickontheOKbuttontocreatethefilteranddismissthedialog.Insteadof
listingNoFilters,thenewlycreatedfiltershouldnowbeselectedintheAndroidtool
window.
12.5RunningtheApplication
Foroptimalresults,theapplicationshouldberunonaphysicalAndroiddevice,detailsof
whichcanbefoundinthechapterentitledTestingAndroidStudioAppsonaPhysical
AndroidDevice.Withthedeviceconfiguredandconnectedtothedevelopmentcomputer,
clickontherunbuttonrepresentedbyagreentrianglelocatedintheAndroidStudio
toolbarasshowninFigure12-7below,selecttheRun->Run…menuoptionorusethe
Shift+F10keyboardshortcut:
Figure12-7
SelectthephysicalAndroiddevicefromtheChooseDevicedialogifitappears(assuming
thatyouhavenotalreadyconfiguredittobethedefaulttarget).AfterAndroidStudiohas
builttheapplicationandinstalleditonthedeviceitshouldstartupandberunninginthe
foreground.
AreviewoftheLogCatpanelshouldindicatewhichmethodshavesofarbeentriggered
(takingcaretoensurethattheLifecyclefiltercreatedintheprecedingsectionisselectedto
filteroutlogeventsthatarenotcurrentlyofinteresttous):
Figure12-8
12.6ExperimentingwiththeActivity
Withthediagnosticsworking,itisnowtimetoexercisetheapplicationwithaviewto
gaininganunderstandingoftheactivitylifecyclestatechanges.Tobeginwith,consider
theinitialsequenceoflogeventsintheLogCatpanel:
onCreate
onStart
onResume
Clearly,theinitialstatechangesareexactlyasoutlinedinFigure11-2.Note,however,that
acallwasnotmadetoonRestoreInstanceState()sincetheAndroidruntimedetectedthat
therewasnostatetorestoreinthissituation.
TapontheHomeiconinthebottomstatusbaronthedevicedisplayandnotethesequence
ofmethodcallsreportedinthelogasfollows:
onPause
onSaveInstanceState
onStop
Inthiscase,theruntimehasnoticedthattheactivityisnolongerintheforeground,isnot
visibletotheuserandhasstoppedtheactivity,butnotwithoutprovidinganopportunity
fortheactivitytosavethedynamicstate.Dependingonwhethertheruntimeultimately
destroyedtheactivityorsimplyrestartedit,theactivitywilleitherbenotifiedithasbeen
restartedviaacalltoonRestart()orwillgothroughthecreationsequenceagainwhenthe
userreturnstotheactivity.
AsoutlinedinUnderstandingAndroidApplicationandActivityLifecycles,thedestruction
andrecreationofanactivitycanbetriggeredbymakingaconfigurationchangetothe
device,suchasrotatingfromportraittolandscape.Toseethisinaction,simplyrotatethe
devicewhiletheStateChangeapplicationisintheforeground.Whenusingtheemulator,
devicerotationmaybesimulatedusingtheCtrl-F12keyboardshortcutorbypressingthe
number7onthekeyboardkeypadwhileNumLockisoff.Theresultingsequenceof
methodcallsinthelogshouldreadasfollows:
onPause
onSaveInstanceState
onStop
onDestroy
onCreate
onStart
onRestoreInstanceState
onResume
Clearly,theruntimesystemhasgiventheactivityanopportunitytosavestatebeforebeing
destroyedandrestarted.
12.7Summary
Theoldadagethatapictureisworthathousandwordsholdsjustastrueforexamples
whenlearninganewprogrammingparadigm.Inthischapter,wehavecreatedanexample
Androidapplicationforthepurposeofdemonstratingthedifferentlifecyclestatesthrough
whichanactivityislikelytopass.Inthecourseofdevelopingtheprojectinthischapter,
wealsolookedatamechanismforgeneratingdiagnosticlogginginformationfromwithin
anactivity.
Inthenextchapter,wewillextendtheStateChangeexampleprojecttodemonstratehow
tosaveandrestoreanactivity’sdynamicstate.
13.SavingandRestoringtheStateofan
AndroidActivity
Ifthepreviousfewchaptershaveachievedtheirobjective,itshouldnowbealittleclearer
astotheimportanceofsavingandrestoringthestateofauserinterfaceatparticularpoints
inthelifetimeofanactivity.
Inthischapter,wewillextendtheexampleapplicationcreatedinAndroidActivityState
Changes–AnExampleApplicationtodemonstratethestepsinvolvedinsavingand
restoringstatewhenanactivityisdestroyedandrecreatedbytheruntimesystem.
AkeycomponentofsavingandrestoringdynamicstateinvolvestheuseoftheAndroid
SDKBundleclass,atopicthatwillalsobecoveredinthischapter.
13.1SavingDynamicState
Anactivity,aswehavealreadylearned,isgiventheopportunitytosavedynamicstate
informationviaacallfromtheruntimesystemtotheactivity’simplementationofthe
onSaveInstanceState()method.Passedthroughasanargumenttothemethodisa
referencetoaBundleobjectintowhichthemethodwillneedtostoreanydynamicdata
thatneedstobesaved.TheBundleobjectisthenstoredbytheruntimesystemonbehalf
oftheactivityandsubsequentlypassedthroughasanargumenttotheactivity’sonCreate()
andonRestoreInstanceState()methodsifandwhentheyarecalled.Thedatacanthenbe
retrievedfromtheBundleobjectwithinthesemethodsandusedtorestorethestateofthe
activity.
13.2DefaultSavingofUserInterfaceState
Inthepreviouschapter,thediagnosticoutputfromtheStateChangeexampleapplication
showedthatanactivitygoesthroughanumberofstatechangeswhenthedeviceonwhich
itisrunningisrotatedsufficientlytotriggeranorientationchange.
LaunchtheStateChangeapplicationonceagain,thistimeenteringsometextintothe
EditTextfieldpriortoperformingthedevicerotation.Havingrotatedthedevice,the
followingstatechangesequenceshouldappearintheLogCatwindow:
onPause
onSaveInstanceState
onStop
onDestroy
onCreate
onStart
onRestoreInstanceState
onResume
Clearlythishasresultedintheactivitybeingdestroyedandre-created.Areviewofthe
userinterfaceoftherunningapplication,however,shouldshowthatthetextenteredinto
theEditTextfieldhasbeenpreserved.Giventhattheactivitywasdestroyedandrecreated,
andthatwedidnotaddanyspecificcodetomakesurethetextwassavedandrestored,
thisbehaviorrequiressomeexplanation.
InactualfactmostoftheviewwidgetsincludedwiththeAndroidSDKalreadyimplement
thebehaviornecessarytoautomaticallysaveandrestorestatewhenanactivityisrestarted.
TheonlyrequirementtoenablethisbehaviorisfortheonSaveInstanceState()and
onRestoreInstanceState()overridemethodsintheactivitytoincludecallstotheequivalent
methodsofthesuperclass:
@Override
protectedvoidonSaveInstanceState(BundleoutState){
super.onSaveInstanceState(outState);
}
@Override
protectedvoidonRestoreInstanceState(BundlesavedInstanceState){
super.onRestoreInstanceState(savedInstanceState);
}
TheautomaticsavingofstateforauserinterfaceviewcanbedisabledintheXMLlayout
filebysettingtheandroid:saveEnabledpropertytofalse.Forthepurposesofanexample,
wewilldisabletheautomaticstatesavingmechanismfortheEditTextviewintheuser
interfacelayoutandthenaddcodetotheapplicationtomanuallysaveandrestorethestate
oftheview.
ToconfiguretheEditTextviewsuchthatstatewillnotbesavedandrestoredintheevent
thattheactivityisrestarted,editthecontent_state_change.xmlfilesothattheentryforthe
viewreadsasfollows(notethattheXMLcanbeediteddirectlybyclickingontheTexttab
onthebottomedgeoftheDesignerpanel):
<EditText
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:id=”@+id/editText”
android:saveEnabled=“false”
android:inputType=“text”
android:layout_centerVertical=“true”
android:layout_centerHorizontal=“true”
android:width=“200dp”/>
Aftermakingthechange,runtheapplication,entertextandrotatethedevicetoverifythat
thetextisnolongersavedandrestoredbeforeproceeding.
13.3TheBundleClass
Forsituationswherestateneedstobesavedbeyondthedefaultfunctionalityprovidedby
theuserinterfaceviewcomponents,theBundleclassprovidesacontainerforstoringdata
usingakey-valuepairmechanism.Thekeystaketheformofstringvalues,whilethe
valuesassociatedwiththosekeyscanbeintheformofaprimitivevalueoranyobjectthat
implementstheAndroidParcelableinterface.Awiderangeofclassesalreadyimplements
theParcelableinterface.Customclassesmaybemade“parcelable”byimplementingthe
setofmethodsdefinedintheParcelableinterfacedetailsofwhichcanbefoundinthe
Androiddocumentationat:
http://developer.android.com/reference/android/os/Parcelable.html
TheBundleclassalsocontainsasetofmethodsthatcanbeusedtogetandsetkey-value
pairsforavarietyofdatatypesincludingbothprimitivetypes(includingBoolean,char,
doubleandfloatvalues)andobjects(suchasStringsandCharSequences).
Forthepurposesofthisexample,andhavingdisabledtheautomaticsavingoftextforthe
EditTextview,weneedtomakesurethatthetextenteredintotheEditTextfieldbythe
userissavedintotheBundleobjectandsubsequentlyrestored.Thiswillserveasa
demonstrationofhowtomanuallysaveandrestorestatewithinanAndroidapplication
andwillbeachievedusingtheputCharSequence()andgetCharSequence()methodsofthe
Bundleclassrespectively.
13.4SavingtheState
ThefirststepinextendingtheStateChangeapplicationistomakesurethatthetext
enteredbytheuserisextractedfromtheEditTextcomponentwithinthe
onSaveInstanceState()methodoftheStateChangeActivityactivity,andthensavedasa
key-valuepairintotheBundleobject.
InordertoextractthetextfromtheEditTextobjectwefirstneedtoidentifythatobjectin
theuserinterface.Clearly,thisinvolvesbridgingthegapbetweentheJavacodeforthe
activity(containedintheStateChangeActivity.javasourcecodefile)andtheXML
representationoftheuserinterface(containedwithinthecontent_state_change.xml
resourcefile).InordertoextractthetextenteredintotheEditTextcomponentweneedto
gainaccesstothatuserinterfaceobject.
Eachcomponentwithinauserinterfacehasassociatedwithitauniqueidentifier.By
default,theDesignertoolconstructstheIDforanewlyaddedcomponentfromtheobject
type.Ifmorethanoneviewofthesametypeiscontainedinthelayoutthetypenameis
followedbyasequentialnumber(thoughthiscan,andshould,bechangedtosomething
moremeaningfulbythedeveloper).AscanbeseenbycheckingtheComponentpanel
withintheAndroidStudiomainwindowwhenthecontent_state_change.xmlfileis
selectedandtheDesignertooldisplayed,theEditTextcomponenthasbeenassignedthe
IDeditText:
Figure13-1
AsoutlinedinthechapterentitledTheAnatomyofanAndroidApplication,allofthe
resourcesthatmakeupanapplicationarecompiledintoaclassnamedR.Amongstthose
resourcesarethosethatdefinelayouts,includingthelayoutforourcurrentactivity.Within
theRclassisasubclassnamedlayout,whichcontainsthelayoutresources,andwithin
thatsubclassisourcontent_state_changelayout.Withthisknowledge,wecanmakeacall
tothefindViewById()methodofouractivityobjecttogetareferencetotheeditTextobject
asfollows:
finalEditTexttextBox=(EditText)findViewById(R.id.editText);
HavingobtainedareferencetotheEditTextobjectandassignedittotextBox,wecannow
obtainthetextthatitcontainsbycallingtheobject’sgetText()method,which,inturn,
returnsthecurrenttextintheformofaCharSequenceobject:
CharSequenceuserText=textBox.getText();
Finally,wecansavethetextusingtheBundleobject’sputCharSequence()method,
passingthroughthekey(thiscanbeanystringvaluebutinthisinstance,wewilldeclareit
as“savedText”)andtheuserTextobjectasarguments:
outState.putCharSequence(“savedText”,userText);
BringingthisalltogethergivesusamodifiedonSaveInstanceState()methodinthe
StateChangeActivity.javafilethatreadsasfollows(notingalsotheadditionalimport
directiveforandroid.widget.EditText):
packagecom.ebookfrenzy.statechange;
importandroid.os.Bundle;
importandroid.support.design.widget.FloatingActionButton;
importandroid.support.design.widget.Snackbar;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.View;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.util.Log;
importandroid.widget.EditText;
publicclassStateChangeActivityextendsAppCompatActivity{
.
.
.
protectedvoidonSaveInstanceState(BundleoutState){
super.onSaveInstanceState(outState);
Log.i(TAG,“onSaveInstanceState”);
finalEditTexttextBox=
(EditText)findViewById(R.id.editText);
CharSequenceuserText=textBox.getText();
outState.putCharSequence(“savedText”,userText);
}
.
.
.
Nowthatstepshavebeentakentosavethestate,thenextphaseistoensurethatitis
restoredwhenneeded.
13.5RestoringtheState
Thesaveddynamicstatecanberestoredinthoselifecyclemethodsthatarepassedthe
Bundleobjectasanargument.Thisleavesthedeveloperwiththechoiceofusingeither
onCreate()oronRestoreInstanceState().Themethodtousewilldependonthenatureof
theactivity.Ininstanceswherestateisbestrestoredaftertheactivity’sinitializationtasks
havebeenperformed,theonRestoreInstanceState()methodisgenerallymoresuitable.For
thepurposesofthisexamplewewilladdcodetotheonRestoreInstanceState()methodto
extractthesavedstatefromtheBundleusingthe“savedText”key.Wecanthendisplaythe
textontheeditTextcomponentusingtheobject’ssetText()method:
@Override
protectedvoidonRestoreInstanceState(BundlesavedInstanceState){
super.onRestoreInstanceState(savedInstanceState);
Log.i(TAG,“onRestoreInstanceState”);
finalEditTexttextBox=
(EditText)findViewById(R.id.editText);
CharSequenceuserText=
savedInstanceState.getCharSequence(“savedText”);
textBox.setText(userText);
}
13.6TestingtheApplication
AllthatremainsisonceagaintobuildandruntheStateChangeapplication.Oncerunning
andintheforeground,touchtheEditTextcomponentandentersometextbeforerotating
thedevicetoanotherorientation.Whereasthetextchangeswerepreviouslylost,thenew
textisretainedwithintheeditTextcomponentthankstothecodewehaveaddedtothe
activityinthischapter.
13.7Summary
ThesavingandrestorationofdynamicstateinanAndroidapplicationissimplyamatter
ofimplementingtheappropriatecodeintheappropriatelifecyclemethods.Formostuser
interfaceviews,thisishandledautomaticallybytheActivitysuperclass.Inother
instances,thistypicallyconsistsofextractingvaluesandsettingswithinthe
onSaveInstanceState()methodandsavingthedataaskey-valuepairswithintheBundle
objectpassedthroughtotheactivitybytheruntimesystem.
StatecanberestoredineithertheonCreate()ortheonRestoreInstanceState()methodsof
theactivitybyextractingvaluesfromtheBundleobjectandupdatingtheactivitybasedon
thestoredvalues.
Inthischapter,wehaveusedthesetechniquestoupdatetheStateChangeprojectsothat
theActivityretainschangesthroughthedestructionandsubsequentrecreationofan
activity.
14.UnderstandingAndroidViews,View
GroupsandLayouts
Withthepossibleexceptionoflisteningtostreamingaudio,auser’sinteractionwithan
Androiddeviceisprimarilyvisualandtactileinnature.Allofthisinteractiontakesplace
throughtheuserinterfacesoftheapplicationsinstalledonthedevice,includingboththe
built-inapplicationsandanythirdpartyapplicationsinstalledbytheuser.Itshouldcome
asnosurprise,therefore,thatakeyelementofdevelopingAndroidapplicationsinvolves
thedesignandcreationofuserinterfaces.
Withinthischapter,thetopicofAndroiduserinterfacestructurewillbecovered,together
withanoverviewofthedifferentelementsthatcanbebroughttogethertomakeupauser
interface;namelyViews,ViewGroupsandLayouts.
14.1DesigningforDifferentAndroidDevices
Theterm“Androiddevice”coversavastarrayoftabletandsmartphoneproductswith
differentscreensizesandresolutions.Asaresult,applicationuserinterfacesmustnowbe
carefullydesignedtoensurecorrectpresentationonaswidearangeofdisplaysizesas
possible.Akeypartofthisisensuringthattheuserinterfacelayoutsresizecorrectlywhen
runondifferentdevices.Thiscanlargelybeachievedthroughcarefulplanningandtheuse
ofthelayoutmanagersoutlinedinthischapter.
ItisalsoimportanttokeepinmindthatthemajorityofAndroidbasedsmartphonesand
tabletscanbeheldbytheuserinbothportraitandlandscapeorientations.Awell-designed
userinterfaceshouldbeabletoadapttosuchchangesandmakesensiblelayout
adjustmentstoutilizetheavailablescreenspaceineachorientation.
14.2ViewsandViewGroups
EveryiteminauserinterfaceisasubclassoftheAndroidViewclass(tobeprecise
android.view.View).TheAndroidSDKprovidesasetofpre-builtviewsthatcanbeusedto
constructauserinterface.TypicalexamplesincludestandarditemssuchastheButton,
CheckBox,ProgressBarandTextViewclasses.Suchviewsarealsoreferredtoaswidgets
orcomponents.ForrequirementsthatarenotmetbythewidgetssuppliedwiththeSDK,
newviewsmaybecreatedeitherbysubclassingandextendinganexistingclass,or
creatinganentirelynewcomponentbybuildingdirectlyontopoftheViewclass.
Aviewcanalsobecomprisedofmultipleotherviews(otherwiseknownasacomposite
view).SuchviewsaresubclassedfromtheAndroidViewGroupclass
(android.view.ViewGroup)whichisitselfasubclassofView.Anexampleofsuchaviewis
theRadioGroup,whichisintendedtocontainmultipleRadioButtonobjectssuchthatonly
onecanbeinthe“on”positionatanyonetime.Intermsofstructure,compositeviews
consistofasingleparentview(derivedfromtheViewGroupclassandotherwiseknown
asacontainervieworrootelement)thatiscapableofcontainingotherviews(knownas
childviews).
AnothercategoryofViewGroupbasedcontainerviewisthatofthelayoutmanager.
14.3AndroidLayoutManagers
Inadditiontothewidgetstyleviewsdiscussedintheprevioussection,theSDKalso
includesasetofviewsreferredtoaslayouts.Layoutsarecontainerviews(and,therefore,
subclassedfromViewGroup)designedforthesolepurposeofcontrollinghowchildviews
arepositionedonthescreen.
TheAndroidSDKincludesthefollowinglayoutviewsthatmaybeusedwithinan
Androiduserinterfacedesign:
· LinearLayout–Positionschildviewsinasingleroworcolumndependingonthe
orientationselected.Aweightvaluecanbesetoneachchildtospecifyhowmuchof
thelayoutspacethatchildshouldoccupyrelativetootherchildren.
· TableLayout–Arrangeschildviewsintoagridformatofrowsandcolumns.Each
rowwithinatableisrepresentedbyaTableRowobjectchild,which,inturn,containsa
viewobjectforeachcell.
· FrameLayout–ThepurposeoftheFrameLayoutistoallocateanareaofscreen,
typicallyforthepurposesofdisplayingasingleview.Ifmultiplechildviewsareadded
theywill,bydefault,appearontopofeachotherpositionedinthetoplefthandcorner
ofthelayoutarea.Alternatepositioningofindividualchildviewscanbeachievedby
settinggravityvaluesoneachchild.Forexample,settingacenter_verticalgravityona
childwillcauseittobepositionedintheverticalcenterofthecontainingFrameLayout
view.
· RelativeLayout–Probablythemostpowerfulandflexibleofthelayoutmanagers,
thisallowschildviewstobepositionedrelativebothtoeachotherandthecontaining
layoutviewthroughthespecificationofalignmentsandmarginsonchildviews.For
example,childViewAmaybeconfiguredtobepositionedintheverticalandhorizontal
centerofthecontainingRelativeLayoutview.ViewB,ontheotherhand,mightalsobe
configuredtobecenteredhorizontallywithinthelayoutview,butpositioned30pixels
abovethetopedgeofViewA,therebymakingtheverticalpositionrelativetothatof
ViewA.TheRelativeLayoutmanagercanbeofparticularusewhendesigningauser
interfacethatmustworkonavarietyofscreensizesandorientations.
· AbsoluteLayout–AllowschildviewstobepositionedatspecificXandY
coordinateswithinthecontaininglayoutview.Useofthislayoutisdiscouragedsinceit
lackstheflexibilitytorespondtochangesinscreensizeandorientation.
· GridLayout–TheGridLayoutisarelativelynewlayoutmanagerthatwasintroduced
aspartofAndroid4.0.AGridLayoutinstanceisdividedbyinvisiblelinesthatforma
gridcontainingrowsandcolumnsofcells.Childviewsarethenplacedincellsandmay
beconfiguredtocovermultiplecellsbothhorizontallyandverticallyallowingawide
rangeoflayoutoptionstobequicklyandeasilyimplemented.Gapsbetween
componentsinaGridLayoutmaybeimplementedbyplacingaspecialtypeofview
calledaSpaceviewintoadjacentcells,orbysettingmarginparameters.
· CoordinatorLayout–IntroducedaspartoftheAndroidDesignSupportLibrarywith
Android5.0,theCoordinatorLayoutisdesignedspecificallyforcoordinatingthe
appearanceandbehavioroftheappbaracrossthetopofanapplicationscreenwith
otherviewelements.WhencreatinganewactivityusingtheBlankActivitytemplate,
theparentviewinthemainlayoutwillbeimplementedusingaCoordinatorLayout
instance.Thislayoutmanagerwillbecoveredingreaterdetailstartingwiththechapter
entitledWorkingwiththeFloatingActionButtonandSnackbar.
WhenconsideringtheuseoflayoutsintheuserinterfaceforanAndroidapplicationitis
worthkeepinginmindthat,aswillbeoutlinedinthenextsection,thesecanbenested
withineachothertocreateauserinterfacedesignofjustaboutanynecessarylevelof
complexity.
14.4TheViewHierarchy
Eachviewinauserinterfacerepresentsarectangularareaofthedisplay.Aviewis
responsibleforwhatisdrawninthatrectangleandforrespondingtoeventsthatoccur
withinthatpartofthescreen(suchasatouchevent).
Auserinterfacescreeniscomprisedofaviewhierarchywitharootviewpositionedatthe
topofthetreeandchildviewspositionedonbranchesbelow.Thechildofacontainer
viewappearsontopofitsparentviewandisconstrainedtoappearwithintheboundsof
theparentview’sdisplayarea.Consider,forexample,theuserinterfaceillustratedin
Figure14-1:
Figure14-1
Inadditiontothevisiblebuttonandcheckboxviews,theuserinterfaceactuallyincludesa
numberoflayoutviewsthatcontrolhowthevisibleviewsarepositioned.Figure14-2
showsanalternativeviewoftheuserinterface,thistimehighlightingthepresenceofthe
layoutviewsinrelationtothechildviews:
Figure14-2
Aswaspreviouslydiscussed,userinterfacesareconstructedintheformofaview
hierarchywitharootviewatthetop.Thisbeingthecase,wecanalsovisualizetheabove
userinterfaceexampleintheformoftheviewtreeillustratedinFigure14-3:
Figure14-3
Theviewhierarchydiagramgivesprobablytheclearestoverviewoftherelationship
betweenthevariousviewsthatmakeuptheuserinterfaceshowninFigure14-1.Whena
userinterfaceisdisplayedtotheuser,theAndroidruntimewalkstheviewhierarchy,
startingattherootviewandworkingdownthetreeasitrenderseachview.
14.5CreatingUserInterfaces
Withaclearerunderstandingoftheconceptsofviews,layoutsandtheviewhierarchy,the
followingfewchapterswillfocusonthestepsinvolvedincreatinguserinterfacesfor
Androidactivities.Infact,therearethreedifferentapproachestouserinterfacedesign:
usingtheAndroidStudioDesignertool,handwritingXMLlayoutresourcefilesorwriting
Javacode,eachofwhichwillbecovered.
14.6Summary
EachelementwithinauserinterfacescreenofanAndroidapplicationisaviewthatis
ultimatelysubclassedfromtheandroid.view.Viewclass.Eachviewrepresentsa
rectangularareaofthedevicedisplayandisresponsiblebothforwhatappearsinthat
rectangleandforhandlingeventsthattakeplacewithintheview’sbounds.Multipleviews
maybecombinedtocreateasinglecompositeview.Theviewswithinacompositeview
arechildrenofacontainerviewwhichisgenerallyasubclassofandroid.view.ViewGroup
(whichisitselfasubclassofandroid.view.View).Auserinterfaceiscomprisedofviews
constructedintheformofaviewhierarchy.
TheAndroidSDKincludesarangeofpre-builtviewsthatcanbeusedtocreateauser
interface.Theseincludebasiccomponentssuchastextfieldsandbuttons,inadditiontoa
rangeoflayoutmanagersthatcanbeusedtocontrolthepositioningofchildviews.Inthe
eventthatthesuppliedviewsdonotmeetaspecificrequirement,customviewsmaybe
created,eitherbyextendingorcombiningexistingviews,orbysubclassing
android.view.Viewandcreatinganentirelynewclassofview.
UserinterfacesmaybecreatedusingtheAndroidStudioDesignertool,handwritingXML
layoutresourcefilesorbywritingJavacode.Eachoftheseapproacheswillbecoveredin
thechaptersthatfollow.
15.AGuidetotheAndroidStudioDesigner
Tool
ItisdifficulttothinkofanAndroidapplicationconceptthatdoesnotrequiresomeformof
userinterface.MostAndroiddevicescomeequippedwithatouchscreenandkeyboard
(eithervirtualorphysical)andtapsandswipesaretheprimaryformofinteraction
betweentheuserandapplication.Invariablytheseinteractionstakeplacethroughthe
application’suserinterface.
Awelldesignedandimplementeduserinterface,animportantfactorincreatinga
successfulandpopularAndroidapplication,canvaryfromsimpletoextremelycomplex,
dependingonthedesignrequirementsoftheindividualapplication.Regardlessofthe
levelofcomplexity,theAndroidStudioDesignertoolsignificantlysimplifiesthetaskof
designingandimplementingAndroiduserinterfaces.
15.1Blankvs.EmptyActivityTemplates
AsoutlinedinthechapterentitledTheAnatomyofanAndroidApplication,Android
applicationsaremadeupofoneormoreactivities.Anactivityisastandalonemoduleof
applicationfunctionalitythatusuallycorrelatesdirectlytoasingleuserinterfacescreen.
Assuch,whenworkingwiththeAndroidStudioDesignerweareinvariablyworkingon
thelayoutforanactivity.
WhencreatinganewAndroidStudioproject,anumberofdifferenttemplatesareavailable
tobeusedasthestartingpointfortheuserinterfaceofthemainactivity.Themostbasicof
thesetemplatesaretheBlankActivityandEmptyActivitytemplates.Althoughtheseseem
similaratfirstglance,thereareactuallyconsiderabledifferencesbetweenthetwooptions.
TheEmptyActivitytemplatecreatesasinglelayoutfileconsistingofaRelativeLayout
managerinstancecontainingaTextViewobjectasshowninFigure15-1:
Figure15-1
TheBlankActivity,ontheotherhand,consistsoftwolayoutfiles.Thetoplevellayout
filehasaCoordinatorLayoutastherootview,aconfigurableappbar,amenu
preconfiguredwithasinglemenuitem(AinFigure15-2),afloatingactionbutton(B)and
areferencetothesecondlayoutfileinwhichthelayoutforthecontentareaoftheactivity
userinterfaceisdeclared:
Figure15-2
ClearlytheEmptyActivitytemplateisusefulifyouneedneitherafloatingactionbutton
noramenuinyouractivityanddonotneedthespecialappbarbehaviorprovidedbythe
CoordinatorLayoutsuchasoptionstomaketheappbarandtoolbarcollapsefromview
duringcertainscrollingoperations(atopiccoveredinthechapterentitledWorkingwith
theAppBarandCollapsingToolbarLayouts).TheBlankActivityisuseful,however,in
thatitprovidestheseelementsbydefault.Infact,itisoftenquickertocreateanew
activityusingtheBlankActivitytemplateanddeletetheelementsyoudonotrequirethan
tousetheEmptyActivitytemplateandmanuallyimplementbehaviorsuchascollapsing
toolbars,amenuorfloatingactionbutton.
SincenotalloftheexamplesinthisbookrequirethefeaturesoftheBlankActivity
template,however,mostoftheexamplesinthischapterwillusetheEmptyActivity
templateunlesstheexamplerequiresoneorotherofthefeaturesprovidedbytheBlank
Activitytemplate.
Forfuturereference,ifyouneedamenubutnotafloatingactionbutton,usetheBlank
Activityandfollowthesestepstodeletethefloatingactionbutton:
1. Double-clickonthemainactivitylayoutfilelocatedintheProjecttoolwindowunder
app->res->layouttoloaditintotheDesigner.Thiswillbethelayoutfileprefixed
withactivity_andnotthecontentfileprefixedwithcontent_.
2. WiththelayoutloadedintotheDesignertool,selectthefloatingactionbuttonandtap
thekeyboardDeletekeytoremovetheobjectfromthelayout.
3. LocateandedittheJavacodefortheactivity(locatedunderapp->java-><package
name>-><activityclassname>andremovethefloatingactionbuttoncodefromthe
onCreatemethodasfollows:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButtonfab=
(FloatingActionButton)findViewById(R.id.fab);
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
Snackbar.make(view,“Replacewithyourownaction”,
Snackbar.LENGTH_LONG)
.setAction(“Action”,null).show();
}
});
}
Ifyouneedafloatingactionbuttonbutnomenu,usetheBlankActivitytemplateand
followthesesteps:
1. EdittheactivityclassfileanddeletetheonCreateOptionsMenuand
onOptionsItemSelectedmethods.
2. Selecttheres->menuitemintheProjecttoolwindowandtapthekeyboardDelete
keytoremovethefolderandcorrespondingmenuresourcefilesfromtheproject.
15.2TheAndroidStudioDesigner
Ashasbeendemonstratedinpreviouschapters,theDesignertoolprovidesa“whatyou
seeiswhatyouget”(WYSIWYG)environmentinwhichviewscanbeselectedfroma
paletteandthenplacedontoacanvasrepresentingthedisplayofanAndroiddevice.Once
aviewhasbeenplacedonthecanvas,itcanbemoved,deletedandresized(subjecttothe
constraintsoftheparentview).Further,awidevarietyofpropertiesrelatingtotheselected
viewmaybemodifiedusingthePropertiespanel.
Underthesurface,theDesignertoolactuallyconstructsanXMLresourcefilecontaining
thedefinitionoftheuserinterfacethatisbeingdesigned.Assuch,theDesignertool
operatesintwodistinctmodesreferredtoasDesignmodeandTextmode.
15.3DesignMode
Indesignmode,theuserinterfacecanbevisuallymanipulatedbydirectlyworkingwith
theviewpaletteandthegraphicalrepresentationofthelayout.Figure15-3highlightsthe
keyareasoftheAndroidStudioDesignertoolindesignmode:
Figure15-3
A–Palette–Thepaletteprovidesaccesstotherangeofviewcomponentsprovidedby
theAndroidSDK.Thesearegroupedintocategoriesforeasynavigation.Itemsmaybe
addedtothelayouteitherbydraggingaviewcomponentfromthepaletteanddroppingit
atthedesiredpositiononthelayout,orbyclickingonawidgetinthepaletteandthen
clickingatthelocationonthelayoutwhereitistobepositioned.
B–DeviceScreen–Thedevicescreenprovidesavisual“whatyouseeiswhatyouget”
representationoftheuserinterfacelayoutasitisbeingdesigned.Thislayoutallowsfor
directmanipulationofthedesignintermsofallowingviewstobeselected,deleted,
movedandresized.Thedevicemodelrepresentedbythelayoutcanbechangedatany
timeusingamenulocatedinthetoolbar.
Notethatifyoufindtheimageofthedeviceframesurroundingthedisplaylayout
distracting,itcanbeturnedoffbyselectingthesettingsmenuinthetoolbar(represented
bythegearicon)andtogglingofftheIncludeDeviceFrames(ifavailable)option.
C–ComponentTree–Asoutlinedinthepreviouschapter(UnderstandingAndroid
Views,ViewGroupsandLayouts)userinterfacesareconstructedusingahierarchical
structure.Thecomponenttreeprovidesavisualoverviewofthehierarchyoftheuser
interfacedesign.Selectinganelementfromthecomponenttreewillcausethe
correspondingviewinthelayouttobeselected.Similarly,selectingaviewfromthe
devicescreenlayoutwillselectthatviewinthecomponenttreehierarchy.
D–Properties–Allofthecomponentviewslistedinthepalettehaveassociatedwith
themasetofpropertiesthatcanbeusedtoadjustthebehaviorandappearanceofthat
view.TheDesigner’spropertiespanelprovidesaccesstothepropertiesofthecurrently
selectedviewinthelayoutallowingchangestobemade.
E–Toolbar–TheDesignertoolbarprovidesquickaccesstoawiderangeofoptions
including,amongstotheroptions,theabilitytozoominandoutofthedevicescreen
layout,changethedevicemodelcurrentlydisplayed,rotatethelayoutbetweenportraitand
landscapeandswitchtoadifferentAndroidSDKAPIlevel.Thetoolbaralsohasasetof
contextsensitivebuttonswhichwillappearwhenrelevantviewtypesareselectedinthe
devicescreenlayout.
F–ModeSwitchingTabs–ThetabslocatedalongtheloweredgeoftheDesigner
provideawaytoswitchbackandforthbetweentheDesignertool’stextanddesign
modes.
15.4TextMode
ItisimportanttokeepinmindwhenusingtheAndroidStudioDesignertoolthatallitis
reallydoingisprovidingauserfriendlyapproachtocreatingXMLlayoutresourcefiles.
Atanytimeduringthedesignprocess,theunderlyingXMLcanbeviewedanddirectly
editedsimplybyclickingontheTexttablocatedatthebottomoftheDesignertoolpanel.
Toreturntodesignmode,simplyclickontheDesigntab.
Figure15-4highlightsthekeyareasoftheAndroidStudioDesignertoolintextmode:
Figure15-4
A–Editor–TheeditorpaneldisplaystheXMLthatmakesupthecurrentuserinterface
layoutdesign.ThisisthefullAndroidStudioeditorenvironmentcontainingallofthe
featurespreviouslyoutlinedintheTheBasicsoftheAndroidStudioCodeEditorchapter
ofthisbook.
B–Preview–AschangesaremadetotheXMLintheeditor,thesechangesarevisually
reflectedinthepreviewwindow.ThisprovidesinstantvisualfeedbackontheXML
changesastheyaremadeintheeditor,therebyavoidingtheneedtoswitchbackandforth
betweentextanddesignmodetoseechanges.Whilethepreviewdoesnotallowdirect
manipulationoftheviews,doubleclickingonaviewinthepreviewwillautomatically
switchtodesignmodeandpreselectthecorrespondingviewinthedevicescreenlayout.
C–Toolbar–Thetoolbarintextmodeprovidesasubsetofthosefunctionsavailablein
designmodewiththeadditionofabuttontotakeascreenshotofthecurrentdevicescreen
layout.
D-ModeSwitchingTabs–ThetabslocatedalongtheloweredgeoftheDesigner
provideawaytoswitchbackandforthbetweentheDesignertool’stextanddesign
modes.
15.5SettingProperties
Thepropertiespanelprovidesaccesstoalloftheavailablesettingsforthecurrently
selectedcomponent.Clickinginthepanelandtypingcharacterswillbeginasearch
processtohighlightandselecttheclosestmatchtothetypedcharacters.
Whilethepropertiespanelprovidesaccesstothefulllistofpropertiesforthecurrently
selectedcomponent,quickaccesstoasubsetofcommonpropertiescanbegainedby
doubleclickingonthecomponentviewinthelayout.DoubleclickingonaTextView
component,forexample,providesquickaccesstothetextandidpropertiesfortheviewas
showninFigure15-5:
Figure15-5
Someproperties,bothinthemainpropertiesandquickaccesspanelscontainabutton
displayingthreedots.Thisindicatesthatasettingsdialogisavailabletoassistinselecting
asuitablepropertyvalue.Todisplaythedialog,simplyclickonthebutton.Propertiesfor
whichafinitenumberofvalidoptionsareavailablewillpresentadropdownmenu
(Figure15-6)fromwhichaselectionmaybemade.
Figure15-6
Bydefault,thepropertiespanelonlyliststhemostcommonlyusedproperties.Accessto
thefullrangeofpropertiesforthecurrentlyselectedcomponenttypeisavailableby
switchingthepanelintoexpertmode.Thismodecanbetoggledonandoffbyclickingon
thefunnelbuttoninthetoolbarasindicatedbythearrowinFigure15-7:
Figure15-7
Inmostinstances,accesstotheexpertmodepropertieswillnotbeneeded,thoughitis
usefultobeawareofthismodeintheeventthatthepropertyyouarelookingforisnot
listedinstandardmode.
15.6TypeMorphing
ThemorphingfeatureoftheDesignertoolallowsacomponentviewthatisalreadypartof
theuserinterfacelayouttobechangedfromonetypetoanother.Morphingcan,for
example,beusedtochangeaTextViewcomponentintoanEditTextviewcomponent.
Whilemorphingislimitedintermsofthetypesofconversionthatcanbeperformed(itis
notpossible,forexample,tomorphaTextViewintoaProgressBar),thisdoesprovidea
quickeralternativetodeletingandaddingcomponentsinmanycases.
Tomorphacomponent,simplyright-clickontheviewinthelayoutandselectthe
Morphingmenuoptionfollowedbythetypetowhichtheviewistobechanged.Figure
15-8,forexample,showsthemorphingoptionsavailableforanImageButtoncomponent:
Figure15-8
15.7CreatingaCustomDeviceDefinition
ThedevicemenuintheDesignertoolbar(Figure15-9)providesalistofpreconfigured
devicetypeswhich,whenselected,willappearasthedevicescreencanvas.Inadditionto
thepre-configureddevicetypes,anyAVDinstancesthathavepreviouslybeenconfigured
withintheAndroidStudioenvironmentwillalsobelistedwithinthemenu.Toadd
additionaldeviceconfigurations,displaythedevicemenu,selecttheAddDevice
Definition…optionandfollowthestepsoutlinedinthechapterentitledCreatingan
AndroidVirtualDevice(AVD)inAndroidStudio.
Figure15-9
15.8Summary
AkeypartofdevelopingAndroidapplicationsinvolvesthecreationoftheuserinterface.
WithintheAndroidStudioenvironment,thisisperformedusingtheDesignertoolwhich
operatesintwomodes.Indesignmode,viewcomponentsareselectedfromapaletteand
positionedonalayoutrepresentinganAndroiddevicescreenandconfiguredusingalist
ofproperties.Intextmode,theunderlyingXMLthatrepresentstheuserinterfacelayout
canbedirectlyedited,withchangesreflectedinapreviewscreen.Thesemodescombine
toprovideanextensiveandintuitiveuserinterfacedesignenvironment.
16.DesigningaUserInterfaceusingthe
AndroidStudioDesignerTool
Byfartheeasiestandmostproductivemechanismfordesigningauserinterfaceforan
AndroidapplicationistomakeuseoftheAndroidStudioDesignertool.Thegoalofthis
chapteristoprovideanoverviewofhowtocreateauserinterfaceusingthistool.The
exerciseincludedinthischapterwillalsobeusedasanopportunitytooutlinethecreation
ofanactivitystartingwitha“bare-bones”AndroidStudioproject.
HavingcoveredtheuseoftheAndroidStudioDesigner,thechapterwillalsoprovidean
overviewoftheconceptsbehindmanuallywritingandeditingXMLlayoutresourcefiles
beforeintroducingtheHierarchyViewertool.
16.1AnAndroidStudioDesignerToolExample
ThefirststepinthisphaseoftheexampleistocreateanewAndroidStudioproject.
Begin,therefore,bylaunchingAndroidStudioandclosinganypreviouslyopenedprojects
byselectingtheFile->CloseProjectmenuoption.WithintheAndroidStudiowelcome
screenclickontheStartanewAndroidStudioprojectquickstartoptiontodisplaythefirst
screenofthenewprojectdialog.
EnterLayoutSampleintotheApplicationnamefieldandebookfrenzy.comastheCompany
DomainsettingbeforeclickingontheNextbuttonandsettheminimumSDKtoAPI8:
Android2.2(Froyo).
Inpreviousexamples,wehaverequestedthatAndroidStudiocreateatemplateactivityfor
theproject.Wewill,however,beusingthistutorialtolearnhowtocreateanentirelynew
activityandcorrespondinglayoutresourcefilemanually,soclickNextonceagainand
makesurethattheAddNoActivityoptionisselectedbeforeclickingonFinishtocreate
thenewproject.
16.2CreatingaNewActivity
Oncetheprojectcreationprocessiscomplete,theAndroidStudiomainwindowshould
appearwithablankbackgroundcontainingamessagethatreads“Nofilesareopen”.
Thenextstepintheprojectistocreateanewactivity.Thiswillbeavaluablelearning
exercisesincetherearemanyinstancesinthecourseofdevelopingAndroidapplications
wherenewactivitiesneedtobecreatedfromthegroundup.
BeginbydisplayingtheProjecttoolwindowusingtheAlt-1keyboardshortcut.Once
displayed,unfoldthehierarchybyclickingontherightfacingarrowsnexttotheentriesin
theProjectwindow.Theobjectivehereistogainaccesstotheapp->java->
com.ebookfrenzy.layoutsamplefolderintheprojecthierarchy.Oncethepackagenameis
visible,right-clickonitandselecttheNew->Activity->EmptyActivitymenuoptionas
illustratedinFigure16-1:
Figure16-1
IntheresultingNewActivitydialog,namethenewactivityLayoutSampleActivityandthe
layoutactivity_layout_sample.Theactivitywill,ofcourse,needalayoutresourcefileso
makesurethattheGenerateLayoutFileoptionisenabled.
Inorderforanapplicationtobeabletorunonadeviceitneedstohaveanactivity
designatedasthelauncheractivity.Withoutalauncheractivity,theoperatingsystemwill
notknowwhichactivitytostartupwhentheapplicationfirstlaunchesand,assuch,the
applicationwillfailtostart.Sincethisexampleonlyhasoneactivity,itneedstobe
designatedasthelauncheractivityfortheapplicationsomakesurethattheLauncher
ActivityoptionisenabledbeforeclickingontheFinishbutton.
AtthispointAndroidStudioshouldhaveaddedtwofilestotheproject.TheJavasource
codefilefortheactivityshouldbelocatedintheapp->java->
com.ebookfrenzy.layoutsamplefolder.
Inaddition,theXMLlayoutfilefortheuserinterfaceshouldhavebeencreatedintheapp
->res->layoutfolder.NotethattheEmptyActivitytemplatewaschosenforthisactivity
sothelayoutiscontainedentirelywithintheactivity_layout_sample.xmlfileandthereis
noseparatecontentlayoutfile.
Finally,thenewactivityshouldhavebeenaddedtotheAndroidManifest.xmlfileand
designatedasthelauncheractivity.Themanifestfilecanbefoundintheprojectwindow
undertheapp->manifestsfolderandshouldcontainthefollowingXML:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.layoutsample”>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:supportsRtl=“true”
android:theme=”@style/AppTheme”>
<activityandroid:name=”.LayoutSampleActivity”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
16.3DesigningtheUserInterface
Locateanddoubleclickontheactivity_layout_sample.xmllayoutfilelocatedintheapp>res->layoutfoldertoloaditintotheDesignertool.
BydefaultthelayoutshouldcontainasinglecomponentviewintheformoftheTextView
displayingthe“HelloWorld!”message.Selectthiscomponentandremoveitbypressing
thekeyboarddeletekey.
FromwithintheWidgetspalettecategory,dragaButtonviewobjectintothecenterofthe
displayview.Notethatgreenhorizontalandverticaldashedlinesappeartoindicatethe
centeraxesofthedisplay.Oncecentered,releasethemousebuttontodroptheviewinto
position.ClickanddragaPlainTextobjectfromtheTextFieldssectionofthepaletteand
positionitsothatitappearsabovethebuttonasillustratedinFigure16-2:
Figure16-2
Clickonthelightbulbicontodisplaythehintsmenuandclickonthemessagethatreads
“ThistextfielddoesnotspecifyaninputTypeorahint”andselecttextastheinputtypein
theSetAttributeValuedialog.ClickonOKtodismissthedialogandsettheattribute.
16.4EditingViewProperties
Onceaviewobjecthasbeenplacedintotheuserinterface,thepropertiesofthatviewmay
alsobemodifiedfromwithintheDesignertool.Inthefirstinstance,thewidthofthe
EditTextobjectmaybeinadequateforallowingtheusertoentertext.Inordertochange
this,selecttheEditTextobjectandlocateandselecttheWidthpropertyintheProperties
panel.Inthevaluefield,enteravalueforthedesiredwidthofthefield.Thevaluemust
includeaunitofmeasurementfromthefollowinglist:
· in–Inches.
· mm–Millimeters.
· pt–Points(1/72ofaninch).
· dp–Density-independentpixels.Anabstractunitofmeasurementbasedonthe
physicaldensityofthedevicedisplayrelativetoa160dpidisplaybaseline.
· sp–Scale-independentpixels.Similartodpbutscaledbasedontheuser’sfont
preference.
· px–Actualscreenpixels.Useisnotrecommendedsincedifferentdisplayswillhave
differentpixelsperinch.Usedpinpreferencetothisunit.
Forthepurposesofthisexample,wewillusedensityindependentpixelsasourunitof
measurement,soenter350dpintothedialogandclickontheOKbutton.Thewidthofthe
EditTextviewshouldchangeaccordingly.
Next,doubleclickontheButtonviewandintheresultingpanel,changethetextproperty
from“NewButton”to“PressMe”.ClickonthelightbulbiconfollowedbytheI18N
messagetodisplaytheExtractResourcedialog.Nametheresourcebutton_stringand
clickonOKtocreateastringresourceforthebutton.
Theverysimpleuserinterfacedesignisnowcomplete.Designingamorecomplexuser
interfacelayoutisacontinuationofthestepsoutlinedabove.Simplydraganddropviews
ontothedisplay,positionandsetpropertiesasneededandnestlayoutsasrequired.
16.5RunningtheApplication
Allthatremainsistotestthattheapplicationruns.Clickontherunbuttoninthemain
windowtoolbar.SelecteitherthesimulatororaphysicalAndroiddeviceandwaitforthe
applicationtostart.Assumingtheabsenceoferrors,theapplicationandactivityshould
launchandappearexactlyasdesignedusingtheDesignertool.
16.6ManuallyCreatinganXMLLayout
Whilethedesignoflayoutsusingthelayouttoolgreatlyimprovesproductivity,itisstill
possibletocreateXMLlayoutsbymanuallyeditingtheXML.ThestructureofanXML
layoutfileisactuallyquitestraightforwardandfollowsthehierarchicalapproachofthe
viewtree.ThefirstlineofanXMLresourcefileshouldideallyincludethefollowing
standarddeclaration:
<?xmlversion=“1.0”encoding=“utf-8”?>
Thisdeclarationshouldbefollowedbytherootelementofthelayout,typicallyacontainer
viewsuchasalayoutmanager.Thisisrepresentedbybothopeningandclosingtagsand
anypropertiesthatneedtobesetontheview.ThefollowingXML,forexample,declares
aRelativeLayoutviewastherootelementandsetsmatch_parentpropertiessuchthatit
fillsalltheavailablespaceofthedevicedisplaywithpaddingoneachsideof64density
independentpixels:
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=“16dp”
android:paddingRight=“16dp”
android:paddingTop=“16dp”
android:paddingBottom=“16dp”
tools:context=“com.ebookfrenzy.layoutsample.LayoutSampleActivity”>
</RelativeLayout>
AnychildrenthatneedtobeaddedtotheRelativeLayoutparentmustbenestedwithinthe
openingandclosingtags.Inthefollowingexample,aButtonandanEditTextfieldhave
beenaddedaschildrenoftheRelativeLayoutview:
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=“16dp”
android:paddingRight=“16dp”
android:paddingTop=“16dp”
android:paddingBottom=“16dp”
tools:context=“com.ebookfrenzy.layoutsample.LayoutSampleActivity”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/button_string”
android:id=”@+id/button”
android:layout_centerVertical=“true”
android:layout_centerHorizontal=“true”/>
<EditText
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:id=”@+id/editText”
android:layout_above=”@+id/button”
android:layout_centerHorizontal=“true”
android:layout_marginBottom=“56dp”
android:inputType=“text”
android:width=“350dp”/>
</RelativeLayout>
Also,notethatthetwochildviewshaveanumberofpropertiesdeclared.Inthecaseofthe
Buttonview,ithasbeenassignedID“button”andconfiguredtodisplaythetextthatis
representedbyastringresourcenamedbutton_string.Additionally,ithasbeencentered
verticallyandhorizontallywithintheparentviewandwrap_contentheightandwidth
propertieshavebeendeclaredsothatthebuttonissizedtoaccommodatethecontent(in
thiscasethetextshownonthebutton).
TheEditTextview,ontheotherhand,hasawidthdeclaredas350dp,iscentered
horizontallywithintheparentandpositioned56dpabovethebuttonview.
ThisXMLfileis,ofcourse,thelayoutthatwascreatedusingtheDesignertoolinthis
chapter.
WhentowriteXMLmanuallyasopposedtousingtheDesignertoolindesignmodeisa
matterofpersonalpreference.Thereare,however,advantagestousingdesignmode.
First,designmodewillgenerallybequickergiventhatitavoidsthenecessitytotypelines
ofXML.Additionally,designmodeavoidsthenecessitytolearntheintricaciesofthe
variouspropertyvaluesoftheAndroidSDKviewclasses.Ratherthancontinuallyreferto
theAndroiddocumentationtofindthecorrectkeywordsandvalues,mostpropertiescan
belocatedbyreferringtothePropertiespanel.
Alltheadvantagesofdesignmodeaside,itisimportanttokeepinmindthatthetwo
approachestouserinterfacedesignareinnowaymutuallyexclusive.Asanapplication
developer,itisquitelikelythatyouwillendupcreatinguserinterfaceswithindesign
modewhileperformingfine-tuningandlayouttweaksofthedesignbydirectlyeditingthe
generatedXMLresources.Bothviewsoftheinterfacedesignare,afterall,asinglemouse
clickapartwithintheAndroidStudioenvironmentmakingiteasytoswitchbackandforth
asneeded.Infact,ausefultiptorememberisthatselectingaviewinthedisplaylayout
andpressingtheCtrl-Bkeyboardshortcutwillautomaticallyjumptoandhighlightthe
XMLforthatviewintheresourcefile.
16.7UsingtheHierarchyViewer
AusefultoolforcloselyinspectingtheviewhierarchyofanactivityistheHierarchy
Viewer.Themainpurposeofthetoolistoprovideadetailedoverviewoftheentireview
treeforactivitieswithincurrentlyrunningapplicationsandtoprovidesomeinsightinto
thelayoutrenderingperformance.
Thehierarchyviewercanonlybeusedtoinspectapplicationsthatareeitherrunning
withinanAndroidemulator,oronadevicerunningadevelopmentversionofAndroid.To
runthetoolontheLayoutSampleapplicationcreatedinthischapter,launchtheapplication
onanAndroidVirtualDeviceemulatorandwaituntilithasloadedandisvisibleonthe
emulatordisplay.Oncerunning,selecttheTools->Android->AndroidDeviceMonitor
menuoption.IntheDDMSwindow,selecttheWindow->OpenPerspective…menu
optionandchooseHierarchyViewfromtheresultingdialogbeforeclickingontheOK
button.
WhentheHierarchyViewerappears,itwillconsistofanumberofdifferentpanels.The
lefthandpanel,illustratedinFigure16-3,listsallthewindowscurrentlyactiveonthe
deviceoremulatorsuchasthenavigationbar,statusbarandlauncher.Thewindowlisted
inboldisthecurrentforegroundwindow,whichshould,inthiscase,be
LayoutSampleActivity.
Figure16-3
SelectingthelayoutsamplewindowwillcausethehierarchytoloadintotheTreeView
panelasshowninFigure16-4(notethattheremaybeashortdelaybetweenselectionof
thewindowandthehierarchydiagramappearing):
Figure16-4
Whileitispossibletozoominandoutofthetreeviewusingthescaleatthebottomofthe
panelorbyspinningthemousewheel,inmostcasesthetreewillbetoolargetoview
entirelywithintheTreeViewpanel.Tomovetheviewwindowaroundthetreesimply
clickanddragintheTreeViewpanel,ormovethelenswithintheTreeOverviewpanel
(Figure16-5):
Figure16-5
Whenreviewingthetreeview,keepinmindthatsomeviewsinadditiontothoseincluded
intheactivitylayoutwillbedisplayed.Thesearetheviewsandlayoutsthat,forexample,
displaytheactionbaracrossthetopofthescreenandprovideanareafortheactivitytobe
displayed.
SelectinganodeintheTreeViewwillcausethecorrespondingelementintheuser
interfacerepresentationtobehighlightedinredintheLayoutView.InFigure16-6the
RelativeLayoutviewiscurrentlyselected:
Figure16-6
Similarly,selectingviewsfromtheLayoutViewwillcausethecorrespondingnodeinthe
TreeViewtohighlightandmoveintoview.
AdditionalinformationaboutaviewcanbeobtainedbyselectingthenodewithintheTree
View.Apanelwillthenpopupnexttothenodeandcanbedismissedbyperforminga
right-clickonthenode.Doubleclickingonanodewilldisplayadialogcontaininga
renderingofhowtheviewappearswithintheapplicationuserinterface.Figure16-7,for
example,showsthebuttonfromtheLayoutSampleapplication:
Figure16-7
Optionsarealsoavailablewithinthetooltoperformtaskssuchasinvalidatingaselected
layoutview(therebyforcingittoberedrawn)andtosavethetreeviewasaPNGimage
file.Thehierarchyviewercanalsobeusedtodisplayinformationaboutthespeedwith
whichthechildviewsofaselectednodearerenderedwhentheuserinterfaceiscreated.
Todisplaythisperformanceinformation,selectthenodetoactastherootviewandclick
onthetoolbarbuttonindicatedinFigure16-8.Whenenabled,thecoloreddotswithinthe
nodesindicatetheperformanceineachcategory(measure,layoutanddraw)withred
indicatingslowerperformancefortheviewrelativetootherviewsintheactivity.
Containerviewswithlargernumbersofchildviewsmaydisplayredstatussimplybecause
theviewhastowaitforeachchildtorender.Thisisnotnecessarilyanindicationofa
performanceproblemwiththatview.
Figure16-8
16.8Summary
TheAndroidStudioDesignertoolprovidesavisuallyintuitivemethodfordesigninguser
interfaces.Usingadraganddropparadigmcombinedwithasetofpropertyeditors,the
toolprovidesconsiderableproductivitybenefitstotheapplicationdeveloper.
UserinterfacedesignsmayalsobeimplementedbymanuallywritingtheXMLlayout
resourcefiles,theformatofwhichiswellstructuredandeasilyunderstood.
ThefactthattheDesignertoolgeneratesXMLresourcefilesmeansthatthesetwo
approachestointerfacedesigncanbecombinedtoprovidea“bestofbothworlds”
approachtouserinterfacedevelopment.
Finally,adetailedoverviewoftheviewtreeforanactivityandanindicationofthe
performanceofeachviewwithinthatactivitycanbeobtainedusingtheHierarchyViewer
tool.
17.CreatinganAndroidUserInterfacein
JavaCode
Upuntilthispointinthebook,alluserinterfacedesigntaskshavebeenperformedusing
theAndroidStudioDesignertool,eitherintextordesignmode.Analternativetowriting
XMLresourcefilesorusingAndroidStudioDesigneristowriteJavacodetodirectly
create,configureandmanipulatetheviewobjectsthatcomprisetheuserinterfaceofan
Androidactivity.Withinthecontextofthischapter,wewillexploresomeofthe
advantagesanddisadvantagesofwritingJavacodetocreateauserinterfacebefore
describingsomeofthekeyconceptssuchasviewproperties,layoutparametersandrules.
Finally,anexampleprojectwillbecreatedandusedtodemonstratesomeofthetypical
stepsinvolvedinthisapproachtoAndroiduserinterfacecreation.
17.1JavaCodevs.XMLLayoutFiles
ThereareanumberofkeyadvantagestousingXMLresourcefilestodesignauser
interfaceasopposedtowritingJavacode.Infact,Googlegoestoconsiderablelengthsin
theAndroiddocumentationtoextolthevirtuesofXMLresourcesoverJavacode.As
discussedinthepreviouschapter,onekeyadvantagetotheXMLapproachincludesthe
abilitytousetheAndroidStudioDesignertool,which,itself,generatesXMLresources.A
secondadvantageisthatonceanapplicationhasbeencreated,changestouserinterface
screenscanbemadebysimplymodifyingtheXMLfile,therebyavoidingthenecessityto
recompiletheapplication.Also,evenwhenhandwritingXMLlayouts,itispossibletoget
instantfeedbackontheappearanceoftheuserinterfaceusingthepreviewfeatureofthe
AndroidStudioDesignertool.InordertotesttheappearanceofaJavacreateduser
interfacethedeveloperwill,inevitably,repeatedlycyclethroughaloopofwritingcode,
compilingandtestinginordertocompletethedesignwork.
IntermsofthestrengthsoftheJavacodingapproachtolayoutcreation,perhapsthemost
significantadvantagethatJavahasoverXMLresourcefilescomesintoplaywhendealing
withdynamicuserinterfaces.XMLresourcefilesareinherentlymostusefulwhen
definingstaticlayouts,inotherwordslayoutsthatareunlikelytochangesignificantly
fromoneinvocationofanactivitytothenext.Javacode,ontheotherhand,isidealfor
creatinguserinterfacesdynamicallyatrun-time.Thisisparticularlyusefulinsituations
wheretheuserinterfacemayappeardifferentlyeachtimetheactivityexecutessubjectto
externalfactors.
AknowledgeofworkingwithuserinterfacecomponentsinJavacodecanalsobeuseful
whendynamicchangestoastaticXMLresourcebasedlayoutneedtobeperformedin
real-timeastheactivityisrunning.
Finally,somedeveloperssimplyprefertowriteJavacodethantouselayouttoolsand
XML,regardlessoftheadvantagesofferedbythelatterapproaches.
17.2CreatingViews
Aspreviouslyestablished,theAndroidSDKincludesatoolboxofviewclassesdesigned
tomeetmostofthebasicuserinterfacedesignneeds.ThecreationofaviewinJavais
simplyamatterofcreatinginstancesoftheseclasses,passingthroughasanargumenta
referencetotheactivitywithwhichthatviewistobeassociated.
Thefirstview(typicallyacontainerviewtowhichadditionalchildviewscanbeadded)is
displayedtotheuserviaacalltothesetContentView()methodoftheactivity.Additional
viewsmaybeaddedtotherootviewviacallstotheobject’saddView()method.
WhenworkingwithJavacodetomanipulateviewscontainedinXMLlayoutresource
files,itisnecessarytoobtaintheIDoftheview.Thesameruleholdstrueforviews
createdinJava.Assuch,itisnecessarytoassignanIDtoanyviewforwhichcertain
typesofaccesswillberequiredinsubsequentJavacode.Thisisachievedviaacalltothe
setId()methodoftheviewobjectinquestion.Inlatercode,theIDforaviewmaybe
obtainedviaasubsequentcalltotheobject’sgetId()method.
17.3PropertiesandLayoutParameters
Eachviewclasshasassociatedwithitarangeofproperties.Thesepropertysettingsare
setdirectlyontheviewinstancesandgenerallydefinehowtheviewobjectwillappearor
behave.ExamplesofpropertiesarethetextthatappearsonaButtonobject,orthe
backgroundcolorofaRelativeLayoutview.EachviewclasswithintheAndroidSDKhas
apre-definedsetofmethodsthatallowtheusertosetandgetthesepropertyvalues.The
Buttonclass,forexample,hasasetText()methodwhichcanbecalledfromwithinJava
codetosetthetextdisplayedonthebuttontoaspecificstringvalue.Thebackground
colorofaRelativeLayoutobject,ontheotherhand,canbesetwithacalltotheobject’s
setBackgroundColor()method.
Whilepropertysettingsareinternaltoviewobjectsanddictatehowaviewappearsand
behaves,LayoutParametersareusedtocontrolhowaviewappearsrelativetoitsparent
viewandothersiblingviews.Layoutparametersarenotsetinquitethesamewayas
properties,butratherstoredinaViewGroup.LayoutParamsinstance(orasubclassthereof)
whichistheneitherpassedthroughasanargumentwhentheviewisaddedtotheparent
view,orassignedtothechildviewviaacalltotheview’ssetLayoutParams()method.
ALayoutParamsobjectforaviewistypicallycreatedbydeclaringhowtheviewshould
besizedinrelationtotheparentview(i.e.whetheritshouldfilltheparentorbesizedto
fitthecontentitneedstodisplay).OncetheLayoutParamsobjectexists,additionalrules,
suchaswhethertheviewshouldbealignedwithanotherview,canbeaddedviacallsto
theaddRule()methodoftheLayoutParamsobject.
TherearesubclassesofViewGroup.LayoutParamsforeachofthelayouttypes
(AbsoluteLayout.LayoutParams,RelativeLayout.LayoutParamsandsoon).
HavingcoveredthetheoryofuserinterfacecreationfromwithinJavacode,theremainder
ofthischapterwillworkmethodicallythroughthecreationofanexampleapplicationwith
theobjectiveofputtingthistheoryintopractice.
17.4CreatingtheExampleProjectinAndroidStudio
LaunchAndroidStudioandselecttheStartanewAndroidStudioprojectoptionfromthe
quickstartlistinthewelcomescreen.Ifanyexistingprojectsarealreadyopen,closethem
firstusingtheFile->Closemenuoption.
Inthenewprojectconfigurationdialog,enterJavaLayoutintotheApplicationnamefield
andebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedJavaLayoutActivitywithacorrespondinglayout
namedactivity_java_layout.
Oncetheprojecthasbeencreated,theJavaLayoutActivity.javafileshouldautomatically
loadintotheeditingpanel.Aswehavecometoexpect,AndroidStudiohascreateda
templateactivityandoverriddentheonCreate()method,providinganideallocationfor
Javacodetobeaddedtocreateauserinterface.
17.5AddingViewstoanActivity
TheonCreate()methodiscurrentlydesignedtousearesourcelayoutfilefortheuser
interface.Begin,therefore,bydeletingthislinefromthemethod:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_java_layout);
}
ThenextmodificationtotheonCreate()methodistowritesomeJavacodetoadda
RelativeLayoutobjectwithasingleButtonviewchildtotheactivity.Thisinvolvesthe
creationofnewinstancesoftheRelativeLayoutandButtonclasses.TheButtonviewthen
needstobeaddedasachildtotheRelativeLayoutviewwhich,inturn,isdisplayedviaa
calltothesetContentView()methodoftheactivityinstance:
packagecom.ebookfrenzy.javalayout;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.Button;
importandroid.widget.RelativeLayout;
publicclassJavaLayoutActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
ButtonmyButton=newButton(this);
RelativeLayoutmyLayout=newRelativeLayout(this);
myLayout.addView(myButton);
setContentView(myLayout);
}
}
.
.
.
}
Whennewinstancesofuserinterfaceobjectsarecreatedinthisway,theconstructor
methodsmustbepassedthecontextwithinwhichtheobjectisbeingcreatedwhich,inthis
case,isthecurrentactivity.Sincetheabovecoderesideswithintheactivityclass,the
contextissimplyreferencedbythestandardJavathiskeyword:
ButtonmyButton=newButton(this);
Oncetheaboveadditionshavebeenmade,compileandruntheapplication(eitherona
physicaldeviceoranemulator).Oncelaunched,thevisibleresultwillbeabutton
containingnotextappearinginthetoplefthandcorneroftheRelativeLayoutviewas
showninFigure17-1:
Figure17-1
17.6SettingViewProperties
Forthepurposesofthisexercise,weneedthebackgroundoftheRelativeLayoutviewto
beblueandtheButtonviewtodisplaytextthatreads“PressMe”onayellowbackground.
BothofthesetaskscanbeachievedbysettingpropertiesontheviewsintheJavacodeas
outlinedinthefollowingcodefragment:
packagecom.ebookfrenzy.javalayout;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.Button;
importandroid.widget.RelativeLayout;
importandroid.graphics.Color;
publicclassJavaLayoutActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
ButtonmyButton=newButton(this);
myButton.setText(“PressMe”);
myButton.setBackgroundColor(Color.YELLOW);
RelativeLayoutmyLayout=newRelativeLayout(this);
myLayout.setBackgroundColor(Color.BLUE);
myLayout.addView(myButton);
setContentView(myLayout);
}
.
.
.
}
Whentheapplicationisnowcompiledandrun,thelayoutwillreflectthepropertysettings
suchthatthelayoutwillappearwithabluebackgroundandthebuttonwilldisplaythe
assignedtextonayellowbackground.
17.7AddingLayoutParametersandRules
Bydefault,theRelativeLayoutviewhasplacedthebuttonviewinthetopleftcornerof
thedisplay.Inordertoinstructthelayoutviewtoplacethebuttoninadifferentlocation,
inthiscasecenteredbothhorizontallyandvertically,itwillbenecessarytocreatea
LayoutParamsobjectandinitializeitwiththeappropriatevalues.
Typically,anewLayoutParamsinstanceiscreatedbypassingthroughtheheightand
widthvaluesfortheview.ThesevaluesshouldbesettoeitherMATCH_PARENT,
WRAP_CONTENTorspecificsizevalues.TheMATCH_PARENTsettinginstructsthe
parentlayouttoexpandthechildviewsothatitmatchesthesizeoftheparent.
WRAP_CONTENT,ontheotherhand,instructstheparenttosizethechildviewsothatit
isonlylargeenoughtodisplayanycontentitmaybeconfiguredtoshowtotheuser.
ThecodetocreateaLayoutParamsobjectforourbuttonwouldreadasfollows:
RelativeLayout.LayoutParamsbuttonParams=
newRelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
TheabovecodecreatesanewRelativeLayoutLayoutParamsobjectnamedbuttonParams
andsetstheheightandwidthsuchthatthebuttonwillonlybelargeenoughtodisplaythe
“PressMe”textpreviouslyconfiguredviathepropertysetting.
NowthattheLayoutParamsobjecthasbeencreated,thenextstepistoaddsome
additionalrulestotheparameterstoinstructthelayoutparenttocenterthebutton
verticallyandhorizontally.ThisisachievedbycallingtheaddRule()methodofthe
buttonParamsobject,passingthroughtheappropriatevaluesasarguments:
buttonParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
buttonParams.addRule(RelativeLayout.CENTER_VERTICAL);
SimplycreatinganewLayoutParamsobjectandconfiguringitisonlyusefulifthatobject
isthenassignedtothechildview.OnewaytoachievethisistopasstheLayoutParams
objectthroughasanargumentwhenthechildviewisaddedtotheparent:
myLayout.addView(myButton,buttonParams);
Alternatively,theparameterscanbeassignedtothechildviaacalltothe
setLayoutParams()methodoftheview:
myButton.setLayoutParams(buttonParams);
BringingthesetogetherresultsinamodifiedonCreate()methodthatreadsasfollows:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
ButtonmyButton=newButton(this);
myButton.setText(“Pressme”);
myButton.setBackgroundColor(Color.YELLOW);
RelativeLayoutmyLayout=newRelativeLayout(this);
myLayout.setBackgroundColor(Color.BLUE);
RelativeLayout.LayoutParamsbuttonParams=
newRelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
buttonParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
buttonParams.addRule(RelativeLayout.CENTER_VERTICAL);
myLayout.addView(myButton,buttonParams);
setContentView(myLayout);
}
Havingmadetheabovechanges,compileandruntheapplicationonceagain,thistime
notingthatthebuttonisnowcenteredwithintheRelativeLayoutviewasillustratedin
Figure17-2:
Figure17-2
Inordertogainaclearerunderstandingoftheheightandwidthlayoutparametersettings,
temporarilymodifythebuttonParamscreationcodetoreadasfollows,thenre-compile
andruntheapplication:
RelativeLayout.LayoutParamsbuttonParams=
newRelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT);
WithboththeheightandwidthparameterssettoMATCH_PARENT,thebuttonisnow
sizedtomatchtheparentviewandconsequentlyfillstheentiredisplay.
Beforecontinuing,reverttheheightandwidthsettingstoWRAP_CONTENT.
17.8UsingViewIDs
SofarinthistutorialithasnotbeennecessarytouseviewIDs.Inordertodemonstratethe
useofIDsinJavacode,theexamplewillnowbeextendedtoaddanotherviewintheform
ofanEditTextviewthatwillbeconfiguredtobealigned80pixelsabovetheexisting
buttonandcenteredhorizontally.Thefollowingcodemodificationsaddthenewviewto
theactivityandthensetIDsofmyButtonIdandmyEditTextIdontheButtonandEditText
viewsrespectively.LayoutparametersarethencreatedfortheEditTextviewsothatitis
alignedwiththebutton(notethecalltogetId()onthebuttontogettheviewID)and
centeredhorizontallywithinthelayout.Finally,themarginspropertyoftheEditTextfield
isconfiguredtosetthebottommarginto80pixelsbeforeaddingthenewviewtoparent
layout.
TheviewIDsfirstneedtobedeclaredasvalueresources.Rightclickontheapp->res->
valuesfolder,selecttheNew->Valuesresourcefilemenuoptionandnamethenew
resourcefileid.xml.Withtheresourcefilecreated,edititsothatitreadsasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<resources>
<itemname=“myButtonId”type=“id”/>
<itemname=“myEditTextId”type=“id”/>
</resources>
Next,edittheJavaLayoutActivity.javafileandaddthecodetocreatetheEditTextview
andassigntheviewIDs:
packagecom.ebookfrenzy.javalayout;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.widget.Button;
importandroid.widget.RelativeLayout;
importandroid.graphics.Color;
importandroid.widget.EditText;
publicclassJavaLayoutActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
ButtonmyButton=newButton(this);
myButton.setText(“Pressme”);
myButton.setBackgroundColor(Color.YELLOW);
RelativeLayoutmyLayout=newRelativeLayout(this);
myLayout.setBackgroundColor(Color.BLUE);
EditTextmyEditText=newEditText(this);
myButton.setId(R.id.myButtonId);
myEditText.setId(R.id.myEditTextId);
RelativeLayout.LayoutParamsbuttonParams=
newRelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
RelativeLayout.LayoutParamstextParams=
newRelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
textParams.addRule(RelativeLayout.ABOVE,myButton.getId());
textParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
textParams.setMargins(0,0,0,80);
buttonParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
buttonParams.addRule(RelativeLayout.CENTER_VERTICAL);
myLayout.addView(myButton,buttonParams);
myLayout.addView(myEditText,textParams);
setContentView(myLayout);
}
.
.
.
}
AtestrunoftheapplicationshouldshowtheEditTextfieldcenteredabovethebuttonwith
amarginof80pixels.
17.9ConvertingDensityIndependentPixels(dp)toPixels(px)
ThefinaltaskinthisexerciseistosetthewidthoftheEditTextviewto200dp.As
outlinedinthechapterentitledDesigninganAndroidUserInterfaceusingtheGraphical
LayoutTool,whensettingsizesandpositionsinuserinterfacelayoutsitisbettertouse
densityindependentpixels(dp)ratherthanpixels(px).Whenthemarginwassetinthe
abovesection,thevaluewasdeclaredinpxinsteadofdp.Thereasonforthiswasthat
suchmethodcallsonlyacceptvaluesinpixels.Inordertosetapositionusingdp,
therefore,itisnecessarytoconvertadpvaluetoapxvalueatruntime,takinginto
considerationthedensityofthedevicedisplay.Inorder,therefore,tosetthewidthofthe
EditTextviewto350dp,thefollowingcodeneedstobeaddedtotheonCreate()method:
packagecom.ebookfrenzy.javalayout;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.Button;
importandroid.widget.RelativeLayout;
importandroid.graphics.Color;
importandroid.widget.EditText;
importandroid.content.res.Resources;
importandroid.util.TypedValue;
publicclassJavaLayoutActivityextendsAppCompatActivity{
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
ButtonmyButton=newButton(this);
myButton.setText(“Pressme”);
EditTextmyEditText=newEditText(this);
myButton.setId(R.id.myButtonId);
myEditText.setId(R.id.myEditTextId);
RelativeLayoutmyLayout=newRelativeLayout(this);
myLayout.setBackgroundColor(Color.BLUE);
RelativeLayout.LayoutParamsbuttonParams=
newRelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
buttonParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
buttonParams.addRule(RelativeLayout.CENTER_VERTICAL);
RelativeLayout.LayoutParamstextParams=
newRelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
textParams.addRule(RelativeLayout.ABOVE,myButton.getId());
textParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
textParams.setMargins(0,0,0,80);
Resourcesr=getResources();
intpx=(int)TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,200,
r.getDisplayMetrics());
myEditText.setWidth(px);
myLayout.addView(myButton,buttonParams);
myLayout.addView(myEditText,textParams);
setContentView(myLayout);
}
}
CompileandruntheapplicationonemoretimeandnotethatthewidthoftheEditText
viewhaschangedasillustratedinFigure17-3:
Figure17-3
17.10Summary
AsanalternativetowritingXMLlayoutresourcefilesorusingtheAndroidStudio
Designertool,AndroiduserinterfacesmayalsobedynamicallycreatedinJavacode.
CreatinglayoutsinJavacodeconsistsofcreatinginstancesofviewclassesandsetting
propertiesonthoseobjectstodefinerequiredappearanceandbehavior.
Howaviewispositionedandsizedrelativetoitsparentviewandanysiblingviewsis
definedusinglayoutparameters,whicharestoredinLayoutParamsobjects.Oncea
LayoutParamsobjecthasbeencreatedandinitializedwithheightandwidthbehavior
settings,additionalrulesmaythenbeaddedtoconfiguretheparametersfurther.
Theexampleactivitycreatedinthischapterhas,ofcourse,createdthesameuserinterface
(thechangeinbackgroundcolornotwithstanding)asthatcreatedinthepreviouschapter
usingtheGraphicalLayouttoolandXMLresources.Ifnothingelse,thischaptershould
haveprovidedanappreciationoftheleveltowhichtheAndroidStudioDesignertooland
XMLresourcesshieldthedeveloperfrommanyofthecomplexitiesofcreatingAndroid
userinterfacelayouts.
Thereare,however,instanceswhereitmakessensetocreateauserinterfaceinJava.This
approachismostuseful,forexample,whencreatingdynamicuserinterfacelayouts.
18.UsingtheAndroidGridLayoutManager
inAndroidStudioDesigner
AusefullayoutmanagerthatwasintroducedaspartoftheAndroid4.0SDKisthe
GridLayoutmanagerclass.Asthenamesuggests,thisclassallowschildviewstobe
arrangedinagridlayout.ThereareanumberofwaystoimplementtheGridLayoutwithin
theuserinterfaceofanAndroidapplication,includingthroughtheuseoflayoutresources
andJavacode.Perhapstheeasiestapproach,however,istomakeuseofsomeGridLayout
specificfeaturesbuiltintotheAndroidStudioDesignertool.
ThischapterwillintroducethebasicsoftheGridLayoutclassbeforeexploringthe
creationofaGridLayout-baseduserinterfaceusingAndroidStudioDesigner.Directly
creatingGridLayoutuserinterfacedesignsusingXMLlayoutresourcesasanalternative
tousingtheDesignertoolwillbecoveredinthenextchapter,entitledWorkingwiththe
AndroidGridLayoutusingXMLLayoutResources.
18.1IntroducingtheAndroidGridLayoutandSpaceClasses
ThepurposeoftheGridLayoutistoallowchildviewstobepositionedinagrid
arrangement.TheGridLayoutessentiallyconsistsofanumberofinvisiblehorizontaland
verticalgridlinesthatservetodividethelayoutviewintoaseriesofrowsandcolumns,
witheachintersectingrowandcolumnformingacellwhichcan,inturn,containoneor
moreviews.Thegridlinesarereferredtoasindices,whicharenumberedstartingat0for
thelineattheleadingedgeofthelayout.Rowandcolumnnumberingalsostartsat0
beginninginthetoplefthandcornerofthegrid.
Thepositioningofaviewwithinacellcanbedefinedthroughtheuseofgravitysettings
onthatchildview.Thegravityofachildviewcan,forexample,beconfiguredsuchthat
theviewappearscentered,fillstheentirecellorispositionedinaspecificcornerofthe
cellwithinwhichitresides.
Inaddition,achildviewofaGridLayoutparentmayalsobeconfiguredtospanmultiple
rowsandcolumnsthroughtheuseoftherowSpanandcolumnSpanpropertiesofthechild.
AnotherusefulclassthatcanbeusedinconjunctionwiththeGridLayoutistheSpace
class.Thisisaverysimpleclass,thesolepurposeofwhichistocreategapswithin
layouts.InthecaseoftheGridLayoutclass,aSpaceviewcanbeplacedinanycellmuch
likeanyotherviewobject.
InadditiontousingtheSpaceclasstocreategaps,thespacingaroundviewsincellscan
becontrolledviathevariousmarginlayoutproperties(top,bottom,leftandright)ofeach
child.
18.2TheGridLayoutExample
GiventhevisualnatureofboththeGridLayoutclassandtheAndroidStudioDesigner
tool,thebestwaytogainaleveloffamiliaritywiththeconceptsinvolvedistowork
throughanexample.Theremainderofthischapter,therefore,willcreateanexample
applicationthatdemonstratessomeofthekeyfeaturesoftheGridLayoutclasswithinthe
contextoftheDesignertool.
18.3CreatingtheGridLayoutProject
BeginbylaunchingAndroidStudioandcreatinganewproject.WithintheNewProject
dialog,enterGridLayoutSampleintotheApplicationnamefieldandebookfrenzy.comas
theCompanyDomainsettingbeforeclickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI14:Android4.0(IceCreamSandwich)whichistheoldestSDKversion
containingGridLayoutsupport.Continuetoproceedthroughthescreens,requestingthe
creationofanemptyactivitynamedGridLayoutSampleActivitywithacorresponding
layoutnamedactivity_grid_layout_sample.
ClickontheFinishbuttontoinitiatetheprojectcreationprocess.
18.4CreatingtheGridLayoutInstance
Withintheprojecttoolwindow,navigatetotheapp->res->layoutfolderanddouble
clickontheactivity_grid_layout_sample.xmlfiletoloaditintotheAndroidStudio
Designer.
Withthelayoutdisplayed,selectthe“HelloWorld!”TextViewobjectandpressthe
keyboardDeletekeytoremoveitfromthelayout.
ThelayoutdesigncurrentlyconsistssolelyofaRelativeLayoutmanagertowhichwecan
nowaddaGridLayoutmanagerinstance.FromtheLayoutssectionoftheDesigner
palette,selectaGridLayoutmanageranddraganddropitonthedevicescreen.Thelayout
shouldfillthescreenwithpaddingmarginsoneachsideasshowninFigure18-1:
Figure18-1
Forthepurposesofthistutorial,theGridLayoutisonlyrequiredtobelargeenoughto
accommodatethechildviewscontainedtherein.Assuch,theheightandwidthparameters
oftheGridLayoutviewneedtobechangedfrommatch_parenttowrap_content.Oneway
tomakethischangeistolocatethepropertiesintheDesignertool’sPropertiespanel.A
quickeroptionistoselecttheGridLayoutinstanceandusethetwobuttonsintheDesigner
toolbar.Setthewidthpropertybyclickingonthelayoutwidthbuttonashighlightedin
Figure18-2:
Figure18-2
Havingsetthewidthpropertytowrap_content,clickontheadjacenttoolbarbuttonto
similarlysetthelayout_heightproperty.Thesesettingswillhavetheeffectofshrinking
thelayoutdowntoasmallsquarereadytoaccommodatesomechildviews.Ifthe
GridLayoutinstanceisstillpositionedinthecenterofthescreen,clickanddragittothe
upperlefthandcorner.
18.5AddingViewstoGridLayoutCells
ToplaceviewobjectsintothecellsofaGridLayout,simplyclickontherequiredviewin
thepaletteanddragittotheGridLayoutinstanceinthedevicescreenlayoutcanvas.As
theviewpassesovertheGridLayout,informationwillappearindicatingtheplacementof
theviewifitisdroppedatthatpoint.BeginbyclickinganddraggingaButtonviewfrom
thepaletteovertothetoplefthandcorneroftheGridLayoutasillustratedinFigure18-3:
Figure18-3
Asisevidentfromthemessagedisplayedbythetool,releasingtheviewatthispointwill
positionitinthecellreferencedbytheco-ordinatesof0,0.
ReleasetheviewatthispointandnotethattheButtonisplacedinthecellasshownin
Figure18-4:
Figure18-4
Notealsothatgreenbarshaveappearedinthemarginsontheupperandright-handsides
ofthemainDesignerarea.Thesebarsrepresenttherowsandcolumnspresentinthe
GridLayout(currentlythelayouthasonerowandonecolumn).
RepeattheabovestepstoplaceasecondbuttonintotheGridLayoutsothatitispositioned
inrowzeroandaftercolumn0suchthatitappearsimmediatelytotherightofthefirst
button.
DragathirdButtonviewtoapositiontotheleftofthefirstButtonviewuntilthemessage
showninFigure18-5appearsprovidingtheoptiontoshiftcolumn0rightandinsertthe
newviewintothecellat0,0:
Figure18-5
Whentheviewisdroppedatthisposition,theexistingcolumnswillbeshiftedoneplace
totheright,anewcolumnwillbeinsertedatthelefthandedgeoftheGridLayoutandthe
newbuttonplacedintothecell.
Followingthesamesequenceofsteps,addoneButtonviewonthesecondrowofthegrid
positionedafterrow0incolumn0sothatthelayoutresemblesthatofFigure18-6:
Figure18-6
18.6MovingandDeletingRowsandColumns
Notethatasadditionalrowsandcolumnshavebeenadded,additionalgreenbarshave
appearedinthemarginstorepresentthoserowsandcolumns.Thesebarsarenotpurely
informational.Entirerowsandcolumnscanbemovedbyclickinganddraggingthe
correspondinggreenblocktoanewpositionwithinthesameaxis.Right-clickingona
blockandselectingDeletefromtheresultingmenuwillremovetheentireroworcolumn
fromtheGridLayout.
18.7ImplementingCellRowandColumnSpanning
Forthenextphaseofthistutorial,twooftheButtonviewswillbemodifiedtospan
multiplecells.Tobeginwith,thefarrighthandbuttoninthetoprowwillbemodifiedto
spanbothrows.Toachievethis,selectthebuttonfromthelayoutandlocatethe
layout:rowSpanpropertylistedinthePropertiespanel.Oncelocated,enterthevalueof2
intothefieldashighlightedinFigure18-7:
Figure18-7
Next,selectthesoleButtonviewonthesecondrow,thistimechangingthecorresponding
layout:columnSpanvalueinthePropertiespanelto2.
Thesetwoviewsarenowconfiguredtospanmultiplecells.Areviewofthelayoutcanvas,
however,showsthattheviewshaveremainedthesamesizeeventhoughthecellsinwhich
theyresidenowspanmultiplerowsorcolumns.Thereasonforthisisthatthegravity
settingsfortheseviewsneedtobechanged.
18.8ChangingtheGravityofaGridLayoutChild
ThegravitypropertiesofthechildviewsofaGridLayoutdictatethesizeandpositioning
ofthoseviewsrelativetothecellinwhichthoseviewsarecontained.Bydefault,the
viewsaddedsofarinthischapterhavebeensetuptobealignedwiththetoplefthand
cornerofthecontainingcells.Inordertomaketheabovecellspanningvisible,thegravity
ofthetwoButtonviewsneedtobechangedsothattheviewsoccupythespaceavailable.
Oneoptionistocenterthechildviewwithinthecell.Beginbyselectingthetoprighthand
Buttonview.InthePropertiespanel,locatethelayout:gravitylineandclickonthecenter
valuefieldtodropdownalistofavailableoptions.Fromthelist,selecttheverticalvalue
asshowninFigure18-8:
Figure18-8
AsubsetofgravitypropertiesmayalsobeconfiguredusingtheDesignertoolbarGravity
buttonasillustratedinFigure18-9:
Figure18-9
Havingsetthisproperty,theButtonviewwillnowbecenteredverticallybetweenthetwo
cells:
Figure18-10
Analternativeoptiontocenteringisforthechildviewtofillthespaceavailable.To
achievethis,changethefillsettingofthelayout:gravitypropertytofill_vertical.Itis
importanttonotethatdifferentcategoriesofgravitypropertiesarecombinedastheyare
selected.Fortheselectedcell,forexample,wehavenowenabledbothcenter_verticaland
fill_verticalgravityproperties.Thiscanbeverifiedbyreferringtothelayout:gravityline
inthePropertiespanelasindicatedinFigure18-11:
Figure18-11
Sincethecenter_verticalpropertyisnolongerrequired,turnitoffbydroppingdownthe
centerpropertymenu(Figure18-12)andselectingthe<unset>option:
Figure18-12
Addagravitypropertytothesecondrowbutton,thistimeenablingthefill_horizontal
gravitysetting.Afterchangingthegravityflags,thetwobuttonsshouldhaveexpandedto
fillthespannedcells:
Figure18-13
18.9Summary
TheGridLayoutclassallowsviewstobearrangedinagridlayoutwhereoneormore
viewsarecontainedwithingridcells.Eachchildcanbeconfiguredtospanmultiplecells
bothhorizontallyandvertically.Thepositionandsizingbehaviorofachildviewwithina
cellorrangeofcellsiscontrolledthroughtheconfigurationofgravitysettings.
GridLayoutsmaybeimplementedeitherusingtheAndroidStudioDesignerindesign
mode,orthroughtheuseofJavacodeorXMLlayoutresources.Thischapterhascovered
thedesignmodeapproach.ThenextchapterwilllookattheuseofXMLlayoutresources
toimplementGridLayoutconfigurations.
19.WorkingwiththeAndroidGridLayout
usingXMLLayoutResources
Thepreviouschapter(entitledUsingtheAndroidGridLayoutManagerinAndroidStudio
Designer)introducedthebasicconceptsoftheAndroidGridLayoutmanagerbefore
explaininghowtocreateaGridLayoutbaseduserinterfacedesignusingthedesignmode
oftheAndroidStudioDesignertool.VisuallydesigningaGridLayoutbaseduserinterface
isnot,however,theonlyavailablemethodofworkingwiththeGridLayoutclass.Such
layoutsmayalsobeimplementedbydirectlywritingJavacodeormanuallycreating
elementsinanXMLlayoutfile,thelatterofwhichisthetopicofthischapter.
19.1GridLayoutsinXMLResourceFiles
AGridLayoutisdeclaredwithinanXMLfileusingthe<GridLayout>elementtag.For
example:
<GridLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:id=”@+id/GridLayout1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:orientation=“vertical”
tools:context=”.GridLayoutActivity”>
</GridLayout>
TheaboveXMLsyntaxapplieswhentheGridLayoutistherootelementofauser
interfacedesign(inotherwordsthetopmostlayoutintheviewhierarchy).Insituations
wheretheGridLayoutisnestedasachildofanothercontainerthesyntaxchangesslightly.
ThefollowingXML,forexample,showsaGridLayoutinstanceembeddedina
RelativeLayoutmanagerwheretheRelativeLayoutistherootelementoftheuser
interface:
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=”.GridLayoutActivity”>
<GridLayout
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentTop=“true”
android:layout_alignParentLeft=“true”
android:layout_alignParentStart=“true”
android:orientation=“vertical”>
</GridLayout>
</RelativeLayout>
Thenumberofrowsandcolumnswithinthegridcanbedeclaredusingthe
android:rowCountandandroid:columnCountproperties.Typically,however,ifthe
numberofcolumnsisdeclaredtheGridLayoutwillinferthenumberofrowsbasedonthe
numberofoccupiedcellsmakingtheuseoftherowCountpropertyunnecessary.
Similarly,theorientationoftheGridLayoutmayoptionallybedefinedviathe
android:orientationproperty.ThefollowingexampleXMLdeclaresa2x2GridLayout
configurationinhorizontalorientation:
<GridLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:id=”@+id/GridLayout1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:columnCount=“2”
android:rowCount=“2”
android:orientation=“horizontal”
tools:context=”.GridLayoutActivity”>
</GridLayout>
19.2AddingChildViewstotheGridLayout
ChildviewscanbeaddedtoaGridLayoutbydeclaringtheelementswithinthe
<GridLayout>structureintheXMLfile.Ifnorowandcolumnvaluesaredeclaredfora
childitispositionedautomaticallybytheGridLayoutclassbasedontheconfigurationof
thelayoutandthepositionoftheviewintheXMLfile.ThefollowingXMLplacesfour
buttonswithintheaboveGridLayout,witheachviewplacedinthetoplefthandcornerof
theencapsulatingcell:
<GridLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:id=”@+id/GridLayout1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:columnCount=“2”
android:rowCount=“2”
android:orientation=“horizontal”
tools:context=”.GridLayoutActivity”>
<Button
android:id=”@+id/button1”
android:layout_gravity=“start|top”
android:text=“Button”/>
<Button
android:id=”@+id/button2”
android:layout_gravity=“start|top”
android:text=“Button”/>
<Button
android:id=”@+id/button3”
android:layout_gravity=“start|top”
android:text=“Button”/>
<Button
android:id=”@+id/button4”
android:layout_gravity=“start|top”
android:text=“Button”/>
</GridLayout>
TheabovelayoutwouldbevisuallyrepresentedasillustratedinFigure19-1:
Figure19-1
NotethatafterenteringtheaboveXMLcodeitmaybenecessarytoclickontherefresh
buttonlocatedintheDesignertoolbarinorderfortheuserinterfacetoappearcorrectly.
Aviewcanbeplacedwithinaspecificcellbyspecifyingtheintersectingrowandcolumn
numberofthedestinationcell.ThefollowingButtonview,forexample,willbeplacedin
thecelllocatedatrow1,column2oftheparentGridLayout:
<Button
android:id=”@+id/button5”
android:layout_column=“2”
android:layout_row=“1”
android:layout_gravity=“start|top”
android:text=“Button”/>
19.3DeclaringCellSpanning,GravityandMargins
ThechildofaGridLayoutcanbeconfiguredtospanmultiplecellsusingthe
android:layout_rowSpanandandroid:layout_columnSpanproperties.Thegravityofthe
childiscontrolledviatheandroid:layout_gravityproperty.
IntheXMLfragmentbelow,aButtonviewisconfiguredtospan3columnsand2rows
andtofillthespaceavailablebothhorizontallyandvertically:
<Button
android:id=”@+id/button4”
android:layout_columnSpan=“3”
android:layout_rowSpan=“2”
android:layout_gravity=“fill”
android:text=“Button”/>
Themarginsaroundaviewwithinacellcanbedeclaredforallsidesoftheviewusingthe
android:layout_marginmarginproperty.Alternatively,marginsforindividualsidesmay
bedefinedusingthetopMargin,bottomMargin,leftMarginandrightMarginproperties.
ThefollowingButtonviewdeclaresa10dpmarginonallfoursides:
<Button
android:id=”@+id/button3”
android:layout_gravity=“left|top”
android:layout_margin=“10dp”
android:text=“Button”/>
Bringingthisknowledgetogether,wecannowreviewtheXMLlayoutfortheGridLayout
baseduserinterfacecreatedinthepreviouschapter:
<GridLayout
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“NewButton”
android:id=”@+id/button4”
android:layout_row=“1”
android:layout_column=“0”
android:layout_columnSpan=“2”
android:layout_gravity=“fill_horizontal”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“NewButton”
android:id=”@+id/button”
android:layout_row=“0”
android:layout_column=“1”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“NewButton”
android:id=”@+id/button3”
android:layout_row=“0”
android:layout_column=“0”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“NewButton”
android:id=”@+id/button2”
android:layout_row=“0”
android:layout_column=“2”
android:layout_rowSpan=“2”
android:layout_gravity=“fill_vertical”/>
</GridLayout>
TheaboveresourcefilefragmentappearsexactlyasitwascreatedwithintheAndroid
StudioDesignertoolintheprecedingchapter.Ascanbeseen,itcontainsaGridLayout
managerandfourButtonviewswithcellpositionsspecifiedbyrowandcolumn
properties.Finally,therowandcolumnspanvalues,alongwithgravitypropertiesareused
tomaketwoofthebuttonsspanmultiplecells.Whendeployedwithinanapplication,this
XMLlayoutwouldappearasillustratedinFigure18-13.
19.4Summary
AsanalternativetousingtheAndroidStudioDesignertoolindesignmodetocreate
GridLayoutbaseduserinterfaceelements,thesameobjectivemayalsobeachievedby
manuallycreatingXMLlayoutresources.Havingcoveredtheuseofthegraphicallayout
approachinthepreviouschapter,thischapterhascoveredtheconceptsofdeclaringa
GridLayoutandpositioningandconfiguringchildviewswithinanXMLresourcefile.
20.AnOverviewandExampleofAndroid
EventHandling
Muchhasbeencoveredinthepreviouschaptersrelatingtothedesignofuserinterfaces
forAndroidapplications.Anareathathasyettobecovered,however,involvesthewayin
whichauser’sinteractionwiththeuserinterfacetriggerstheunderlyingactivityto
performatask.Inotherwords,weknowfromthepreviouschaptershowtocreateauser
interfacecontainingabuttonview,butnothowtomakesomethinghappenwithinthe
applicationwhenitistouchedbytheuser.
Theprimaryobjectiveofthischapter,therefore,istoprovideanoverviewofevent
handlinginAndroidapplicationstogetherwithanAndroidStudiobasedexampleproject.
Oncethebasicsofeventhandlinghavebeencovered,thenextchapterwillcovertouch
eventhandlingintermsofdetectingmultipletouchesandtouchmotion.
20.1UnderstandingAndroidEvents
EventsinAndroidcantakeavarietyofdifferentforms,butareusuallygeneratedin
responsetoanexternalaction.Themostcommonformofevents,particularlyfordevices
suchastabletsandsmartphones,involvesomeformofinteractionwiththetouchscreen.
Sucheventsfallintothecategoryofinputevents.
TheAndroidframeworkmaintainsaneventqueueintowhicheventsareplacedasthey
occur.Eventsarethenremovedfromthequeueonafirst-in,first-out(FIFO)basis.Inthe
caseofaninputeventsuchasatouchonthescreen,theeventispassedtotheview
positionedatthelocationonthescreenwherethetouchtookplace.Inadditiontothe
eventnotification,theviewisalsopassedarangeofinformation(dependingontheevent
type)aboutthenatureoftheeventsuchasthecoordinatesofthepointofcontactbetween
theuser’sfingertipandthescreen.
Inordertobeabletohandletheeventthatithasbeenpassed,theviewmusthaveinplace
aneventlistener.TheAndroidViewclass,fromwhichalluserinterfacecomponentsare
derived,containsarangeofeventlistenerinterfaces,eachofwhichcontainsanabstract
declarationforacallbackmethod.Inordertobeabletorespondtoaneventofaparticular
type,aviewmustregistertheappropriateeventlistenerandimplementthecorresponding
callback.Forexample,ifabuttonistorespondtoaclickevent(theequivalenttotheuser
touchingandreleasingthebuttonviewasthoughclickingonaphysicalbutton)itmust
bothregistertheView.onClickListenereventlistener(viaacalltothetargetview’s
setOnClickListener()method)andimplementthecorrespondingonClick()callback
method.Intheeventthata“click”eventisdetectedonthescreenatthelocationofthe
buttonview,theAndroidframeworkwillcalltheonClick()methodofthatviewwhenthat
eventisremovedfromtheeventqueue.Itis,ofcourse,withintheimplementationofthe
onClick()callbackmethodthatanytasksshouldbeperformedorothermethodscalledin
responsetothebuttonclick.
20.2Usingtheandroid:onClickResource
Beforeexploringeventlistenersinmoredetailitisworthnotingthatashortcutis
availablewhenallthatisrequiredisforacallbackmethodtobecalledwhenauser
“clicks”onabuttonviewintheuserinterface.Considerauserinterfacelayoutcontaining
abuttonviewnamedbutton1withtherequirementthatwhentheusertouchesthebutton,a
methodcalledbuttonClick()declaredintheactivityclassiscalled.Allthatisrequiredto
implementthisbehavioristowritethebuttonClick()method(whichtakesasanargument
areferencetotheviewthattriggeredtheclickevent)andaddasinglelinetothe
declarationofthebuttonviewintheXMLfile.Forexample:
<Button
android:id=”@+id/button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:onClick=“buttonClick”
android:text=“Clickme”/>
Thisprovidesasimplewaytocaptureclickevents.Itdoesnot,however,providetherange
ofoptionsofferedbyeventhandlers,whicharethetopicoftherestofthischapter.When
workingwithinAndroidStudioDesigner,theonClickpropertycanbefoundand
configuredinthePropertiespanelwhenasuitableviewtypeisselectedinthedevice
screenlayout.
20.3EventListenersandCallbackMethods
Intheexampleactivityoutlinedlaterinthischapterthestepsinvolvedinregisteringan
eventlistenerandimplementingthecallbackmethodwillbecoveredindetail.Before
doingso,however,itisworthtakingsometimetooutlinetheeventlistenersthatare
availableintheAndroidframeworkandthecallbackmethodsassociatedwitheachone.
· onClickListener–Usedtodetectclickstyleeventswherebytheusertouchesandthen
releasesanareaofthedevicedisplayoccupiedbyaview.CorrespondstotheonClick()
callbackmethodwhichispassedareferencetotheviewthatreceivedtheeventasan
argument.
· onLongClickListener–Usedtodetectwhentheusermaintainsthetouchoveraview
foranextendedperiod.CorrespondstotheonLongClick()callbackmethodwhichis
passedasanargumenttheviewthatreceivedtheevent.
· onTouchListener–Usedtodetectanyformofcontactwiththetouchscreenincluding
individualormultipletouchesandgesturemotions.CorrespondingwiththeonTouch()
callback,thistopicwillbecoveredingreaterdetailinthechapterentitledAndroid
TouchandMulti-touchEventHandling.Thecallbackmethodispassedasarguments
theviewthatreceivedtheeventandaMotionEventobject.
· onCreateContextMenuListener–Listensforthecreationofacontextmenuasthe
resultofalongclick.CorrespondstotheonCreateContextMenu()callbackmethod.The
callbackispassedthemenu,theviewthatreceivedtheeventandamenucontext
object.
· onFocusChangeListener–Detectswhenfocusmovesawayfromthecurrentviewas
theresultofinteractionwithatrack-ballornavigationkey.Correspondstothe
onFocusChange()callbackmethodwhichispassedtheviewthatreceivedtheevent
andaBooleanvaluetoindicatewhetherfocuswasgainedorlost.
· onKeyListener–Usedtodetectwhenakeyonadeviceispressedwhileaviewhas
focus.CorrespondstotheonKey()callbackmethod.Passedasargumentsaretheview
thatreceivedtheevent,theKeyCodeofthephysicalkeythatwaspressedanda
KeyEventobject.
20.4AnEventHandlingExample
Intheremainderofthischapter,wewillworkthroughthecreationofasimpleAndroid
Studioprojectdesignedtodemonstratetheimplementationofaneventlistenerand
correspondingcallbackmethodtodetectwhentheuserhasclickedonabutton.Thecode
withinthecallbackmethodwillupdateatextviewtoindicatethattheeventhasbeen
processed.
CreateanewprojectinAndroidStudio,enteringEventExampleintotheApplicationname
fieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedEventExampleActivitywithcorrespondinglayout
filenamedactivity_event_example.
20.5DesigningtheUserInterface
TheuserinterfacelayoutfortheEventExampleActivityclassinthisexampleistoconsist
ofaRelativeLayoutview,aButtonandalargetextTextViewasillustratedinFigure20-1.
Figure20-1
Locateandselecttheactivity_event_example.xmlfilecreatedbyAndroidStudio(located
intheProjecttoolwindowunderapp->res->layouts)anddoubleclickonittoloadit
intotheDesignertool.SwitchfromDesignmodetoTextmodeusingthetabatthebottom
oftheDesignerpanelanddeletethecurrentcontentofthefile.Withablankcanvas,either
useDesignmodetovisuallydesigntheuserinterfacefromFigure20-1(makingsureto
changetheIDsoftheButtonandTextViewobjectstomyButtonandmyTextView
respectively),ordirectlyenterthefollowingXMLusingTextmode:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:id=”@+id/myLayout”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<Button
android:id=”@+id/myButton”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerHorizontal=“true”
android:layout_centerVertical=“true”
android:text=“PressMe”/>
<TextView
android:id=”@+id/myTextView”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_above=”@+id/myButton”
android:layout_centerHorizontal=“true”
android:layout_marginBottom=“41dp”
android:text=“Status”
android:textAppearance=”?android:attr/textAppearanceLarge”/>
</RelativeLayout>
SwitchtoDesignmode,andselecttheStatuslabelinthedevicescreenlayout.Whenthe
lightbulbiconappears,clickonitandselecttheI18Nhardcodedstringwarningmessage
todisplaytheExtractResourcedialog.Withinthedialog,nametheresourcestring
status_stringandclickontheOKbutton.Repeatthesestepsforthebuttonview,thistime
namingthestringresourcebutton_string.IftheDesignertoolreportsrenderingproblems,
clickontherefreshbuttonlocatedintheDesignertoolbar.
Withtheuserinterfacelayoutnowcompleted,thenextstepistoregistertheeventlistener
andcallbackmethod.
20.6TheEventListenerandCallbackMethod
Forthepurposesofthisexample,anonClickListenerneedstoberegisteredforthe
myButtonview.ThisisachievedbymakingacalltothesetOnClickListener()methodof
thebuttonview,passingthroughanewonClickListenerobjectasanargumentand
implementingtheonClick()callbackmethod.Sincethisisataskthatonlyneedstobe
performedwhentheactivityiscreated,agoodlocationistheonCreate()methodofthe
EventExampleActivityclass.
IftheEventExampleActivity.javafileisalreadyopenwithinaneditorsession,selectitby
clickingonthetabintheeditorpanel.AlternativelylocateitwithintheProjecttool
windowbynavigatingto(app->java->com.ebookfrenzy.eventexample->
EventExampleActivity)anddoubleclickonittoloaditintothecodeeditor.Onceloaded,
locatethetemplateonCreate()methodandmodifyittoobtainareferencetothebutton
view,registertheeventlistenerandimplementtheonClick()callbackmethod:
packagecom.ebookfrenzy.eventexample;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.Button;
importandroid.widget.TextView;
publicclassEventExampleextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_event_example);
Buttonbutton=(Button)findViewById(R.id.myButton);
button.setOnClickListener(
newButton.OnClickListener(){
publicvoidonClick(Viewv){
}
}
);
}
.
.
.
}
Theabovecodehasnowregisteredtheeventlisteneronthebuttonandimplementedthe
onClick()method.Iftheapplicationweretoberunatthispoint,however,therewouldbe
noindicationthattheeventlistenerinstalledonthebuttonwasworkingsincethereis,as
yet,nocodeimplementedwithinthebodyoftheonClick()callbackmethod.Thegoalfor
theexampleistohaveamessageappearontheTextViewwhenthebuttonisclicked,so
somefurthercodechangesneedtobemade:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_event_example);
Buttonbutton=(Button)findViewById(R.id.myButton);
button.setOnClickListener(
newButton.OnClickListener(){
publicvoidonClick(Viewv){
TextViewmyTextView=
(TextView)findViewById(R.id.myTextView);
myTextView.setText(“Buttonclicked”);
}
}
);
}
Completethisphaseofthetutorialbycompilingandrunningtheapplicationoneitheran
AVDemulatororphysicalAndroiddevice.Ontouchingandreleasingthebuttonview
(otherwiseknownas“clicking”)thetextviewshouldchangetodisplaythe“Button
clicked”text.
20.7ConsumingEvents
Thedetectionofstandardclicks(asopposedtolongclicks)onviewsisaverysimplecase
ofeventhandling.Theexamplewillnowbeextendedtoincludethedetectionoflong
clickeventswhichoccurwhentheuserclicksandholdsaviewonthescreenand,indoing
so,coverthetopicofeventconsumption.
ConsiderthecodefortheonClick()methodintheabovesectionofthischapter.The
callbackisdeclaredasvoidand,assuch,doesnotreturnavaluetotheAndroid
frameworkafterithasfinishedexecuting.
TheonLongClick()callbackmethodoftheonLongClickListenerinterface,ontheother
hand,isrequiredtoreturnaBooleanvaluetotheAndroidframework.Thepurposeofthis
returnvalueistoindicatetotheAndroidruntimewhetherornotthecallbackhas
consumedtheevent.Ifthecallbackreturnsatruevalue,theeventisdiscardedbythe
framework.If,ontheotherhand,thecallbackreturnsafalsevaluetheAndroidframework
willconsidertheeventstilltobeactiveandwillconsequentlypassitalongtothenext
matchingeventlistenerthatisregisteredonthesameview.
Aswithmanyprogrammingconceptsthisis,perhaps,bestdemonstratedwithanexample.
Thefirststepistoaddaneventlistenerandcallbackmethodforlongclickstothebutton
viewintheexampleactivity:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_event_example);
Buttonbutton=(Button)findViewById(R.id.myButton);
button.setOnClickListener(
newButton.OnClickListener(){
publicvoidonClick(Viewv){
TextViewmyTextView=
(TextView)findViewById(R.id.myTextView);
myTextView.setText(“Buttonclicked”);
}
}
);
button.setOnLongClickListener(
newButton.OnLongClickListener(){
publicbooleanonLongClick(Viewv){
TextViewmyTextView=
(TextView)findViewById(R.id.myTextView);
myTextView.setText(“Longbuttonclick”);
returntrue;
}
}
);
}
Clearly,whenalongclickisdetected,theonLongClick()callbackmethodwilldisplay
“Longbuttonclick”onthetextview.Note,however,thatthecallbackmethodalsoreturns
avalueoftruetoindicatethatithasconsumedtheevent.Compileandruntheapplication
andpressandholdafingertipoverthebuttonviewuntilthe“Longbuttonclick”text
appearsinthetextview.Onreleasingthebutton,thetextviewcontinuestodisplaythe
“Longbuttonclick”textindicatingthattheonClick()callbackmethodwasnotcalled.
Next,modifythecodesuchthattheonLongClick()methodnowreturnsafalsevalue:
button.setOnLongClickListener(
newButton.OnLongClickListener(){
publicbooleanonLongClick(Viewv){
TextViewmyTextView=
(TextView)findViewById(R.id.myTextView);
myTextView.setText(“Longbuttonclick”);
returnfalse;
}
}
);
Onceagain,compileandruntheapplicationandperformalongclickonthebuttonuntil
thelongclickmessageappears.Uponreleasingthebuttonthistime,however,notethatthe
onClick()callbackisalsotriggeredandthetextchangesto“Buttonclick”.Thisisbecause
thefalsevaluereturnedbytheonLongClick()callbackmethodindicatedtotheAndroid
frameworkthattheeventwasnotconsumedbythemethodandwaseligibletobepassed
ontothenextregisteredlistenerontheview.Inthiscase,theruntimeascertainedthatthe
onClickListeneronthebuttonwasalsointerestedineventsofthistypeandsubsequently
calledtheonClick()callbackmethod.
20.8Summary
Auserinterfaceisoflittlepracticaluseiftheviewsitcontainsdonotdoanythingin
responsetouserinteraction.Androidbridgesthegapbetweentheuserinterfaceandthe
backendcodeoftheapplicationthroughtheconceptsofeventlistenersandcallback
methods.TheAndroidViewclassdefinesasetofeventlisteners,whichcanberegistered
onviewobjects.Eacheventlisteneralsohasassociatedwithitacallbackmethod.
Whenaneventtakesplaceonaviewinauserinterface,thateventisplacedintoanevent
queueandhandledonafirstin,firstoutbasisbytheAndroidruntime.Iftheviewon
whichtheeventtookplacehasregisteredalistenerthatmatchesthetypeofevent,the
correspondingcallbackmethodiscalled.Thecallbackmethodthenperformsanytasks
requiredbytheactivitybeforereturning.Somecallbackmethodsarerequiredtoreturna
Booleanvaluetoindicatewhethertheeventneedstobepassedontoanyotherevent
listenersregisteredontheviewordiscardedbythesystem.
Havingcoveredthebasicsofeventhandling,thenextchapterwillexploreinsomedepth
thetopicoftoucheventswithaparticularemphasisonhandlingmultipletouches.
21.AndroidTouchandMulti-touchEvent
Handling
MostAndroidbaseddevicesuseatouchscreenastheprimaryinterfacebetweenuserand
device.Thepreviouschapterintroducedthemechanismbywhichatouchonthescreen
translatesintoanactionwithinarunningAndroidapplication.Thereis,however,much
moretotoucheventhandlingthanrespondingtoasinglefingertaponaviewobject.Most
Androiddevicescan,forexample,detectmorethanonetouchatatime.Noraretouches
limitedtoasinglepointonthedevicedisplay.Touchescan,ofcourse,bedynamicasthe
userslidesoneormorepointsofcontactacrossthesurfaceofthescreen.
Touchescanalsobeinterpretedbyanapplicationasagesture.Consider,forexample,that
ahorizontalswipeistypicallyusedtoturnthepageofaneBook,orhowapinching
motioncanbeusedtozoominandoutofanimagedisplayedonthescreen.
Theobjectiveofthischapteristohighlightthehandlingoftouchesthatinvolvemotion
andtoexploretheconceptofinterceptingmultipleconcurrenttouches.Thetopicof
identifyingdistinctgestureswillbecoveredinthenextchapter.
21.1InterceptingTouchEvents
Toucheventscanbeinterceptedbyaviewobjectthroughtheregistrationofan
onTouchListenereventlistenerandtheimplementationofthecorrespondingonTouch()
callbackmethod.Thefollowingcode,forexample,ensuresthatanytouchesona
RelativeLayoutviewinstancenamedmyLayoutresultinacalltotheonTouch()method:
myLayout.setOnTouchListener(
newRelativeLayout.OnTouchListener(){
publicbooleanonTouch(Viewv,MotionEventm){
//Performtaskshere
returntrue;
}
}
);
Asindicatedinthecodeexample,theonTouch()callbackisrequiredtoreturnaBoolean
valueindicatingtotheAndroidruntimesystemwhetherornottheeventshouldbepassed
ontoothereventlistenersregisteredonthesameviewordiscarded.Themethodispassed
bothareferencetotheviewonwhichtheeventwastriggeredandanobjectoftype
MotionEvent.
21.2TheMotionEventObject
TheMotionEventobjectpassedthroughtotheonTouch()callbackmethodisthekeyto
obtaininginformationabouttheevent.Informationcontainedwithintheobjectincludes
thelocationofthetouchwithintheviewandthetypeofactionperformed.The
MotionEventobjectisalsothekeytohandlingmultipletouches.
21.3UnderstandingTouchActions
Animportantaspectoftoucheventhandlinginvolvesbeingabletoidentifythetypeof
actionperformedbytheuser.Thetypeofactionassociatedwithaneventcanbeobtained
bymakingacalltothegetActionMasked()methodoftheMotionEventobjectwhichwas
passedthroughtotheonTouch()callbackmethod.Whenthefirsttouchonaviewoccurs,
theMotionEventobjectwillcontainanactiontypeofACTION_DOWNtogetherwiththe
coordinatesofthetouch.Whenthattouchisliftedfromthescreen,anACTION_UPevent
isgenerated.AnymotionofthetouchbetweentheACTION_DOWNandACTION_UP
eventswillberepresentedbyACTION_MOVEevents.
Whenmorethanonetouchisperformedsimultaneouslyonaview,thetouchesare
referredtoaspointers.Inamulti-touchscenario,pointersbeginandendwithevent
actionsoftypeACTION_POINTER_UPandACTION_POINTER_DOWNrespectively.
Inordertoidentifytheindexofthepointerthattriggeredtheevent,thegetActionIndex()
callbackmethodoftheMotionEventobjectmustbecalled.
21.4HandlingMultipleTouches
Thepreviouschapterbeganexploringeventhandlingwithinthenarrowcontextofasingle
touchevent.Inpractice,mostAndroiddevicespossesstheabilitytorespondtomultiple
consecutivetouches(thoughitisimportanttonotethatthenumberofsimultaneous
touchesthatcanbedetectedvariesdependingonthedevice).
Aspreviouslydiscussed,eachtouchinamulti-touchsituationisconsideredbythe
Androidframeworktobeapointer.Eachpointer,inturn,isreferencedbyanindexvalue
andassignedanID.Thecurrentnumberofpointerscanbeobtainedviaacalltothe
getPointerCount()methodofthecurrentMotionEventobject.TheIDforapointerata
particularindexinthelistofcurrentpointersmaybeobtainedviaacalltothe
MotionEventgetPointerId()method.Forexample,thefollowingcodeexcerptobtainsa
countofpointersandtheIDofthepointeratindex0:
publicbooleanonTouch(Viewv,MotionEventm){
intpointerCount=m.getPointerCount();
intpointerId=m.getPointerId(0);
returntrue;
}
Notethatthepointercountwillalwaysbegreaterthanorequalto1whenanonTouch()
methodiscalled(sinceatleastonetouchmusthaveoccurredforthecallbacktobe
triggered).
Atouchonaview,particularlyoneinvolvingmotionacrossthescreen,willgeneratea
streamofeventsbeforethepointofcontactwiththescreenislifted.Assuch,itislikely
thatanapplicationwillneedtotrackindividualtouchesovermultipletouchevents.While
theIDofaspecifictouchgesturewillnotchangefromoneeventtothenext,itis
importanttokeepinmindthattheindexvaluewillchangeasothertoucheventscomeand
go.Whenworkingwithatouchgestureovermultipleevents,therefore,itisessentialthat
theIDvaluebeusedasthetouchreferenceinordertomakesurethesametouchisbeing
tracked.Whencallingmethodsthatrequireanindexvalue,thisshouldbeobtainedby
convertingtheIDforatouchtothecorrespondingindexvalueviaacalltothe
findPointerIndex()methodoftheMotionEventobject.
21.5AnExampleMulti-TouchApplication
Theexampleapplicationcreatedintheremainderofthischapterwilltrackuptotwotouch
gesturesastheymoveacrossalayoutview.Astheeventsforeachtoucharetriggered,the
coordinates,indexandIDforeachtouchwillbedisplayedonthescreen.
CreateanewprojectinAndroidStudio,enteringMotionEventintotheApplicationname
fieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedMotionEventActivitywithacorrespondinglayout
filenamedactivity_motion_event.
ClickontheFinishbuttontoinitiatetheprojectcreationprocess.
21.6DesigningtheActivityUserInterface
Theuserinterfacefortheapplication’ssoleactivityistoconsistofaRelativeLayoutview
containingtwoTextViewobjects.WithintheProjecttoolwindow,navigatetoapp->res>layoutanddoubleclickontheactivity_motion_event.xmllayoutresourcefiletoloadit
intotheAndroidStudioDesignertool.Oneoptionistodesignthelayoutillustratedin
Figure21-1usingaRelativeLayoutastherootviewandkeepinginmindthatthelower
TextViewcomponentiscenteredhorizontallywithintheparentviewandtheupper
TextViewispositionedarelativedistanceabovethelowerTextView.Whenthelayout
designiscomplete,doubleclickontheRelativeLayout(essentiallythebackgroundofthe
layout)andsettheIDtoRelativeLayout1.RepeatthesestepstoassignIDstextView1and
textView2totheTextViewcomponents.
Alternatively,switchtotheXMLeditorbyselectingtheTexttabatthebottomofthe
DesignerpanelandreplacethecurrentXMLwiththefollowing:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:id=”@+id/RelativeLayout1”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<TextView
android:id=”@+id/textView2”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerHorizontal=“true”
android:layout_centerVertical=“true”
android:text=“TouchTwoStatus”/>
<TextView
android:id=”@+id/textView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_above=”@+id/textView2”
android:layout_alignStart=”@+id/textView2”
android:layout_marginBottom=“47dp”
android:text=“TouchOneStatus”/>
</RelativeLayout>
SwitchtoDesignmode,andselecttheupperTextViewinthedevicescreenlayout.When
thelightbulbiconappears,clickonitandselecttheI18Nhardcodedstringwarning
messagetodisplaytheExtractResourcedialog.Withinthedialog,nametheresource
stringstatus1_stringandclickontheOKbutton.Repeatthesestepsforthesecond
TextViewcomponent,thistimenamingthestringresourcestatus2_string.
Figure21-1
21.7ImplementingtheTouchEventListener
Inordertoreceivetoucheventnotificationsitwillbenecessarytoregisteratouchlistener
ontheRelativeLayout1viewwithintheonCreate()methodoftheMotionEventActivity
activityclass.SelecttheMotionEventActivity.javatabfromtheAndroidStudioeditor
paneltodisplaythesourcecode.WithintheonCreate()method,addcodetoidentifythe
RelativeLayoutviewobject,registerthetouchlistenerandimplementtheonTouch()
callbackmethodwhich,inthiscase,isgoingtocallasecondmethodnamed
handleTouch()towhichispassedtheMotionEventobject:
packagecom.ebookfrenzy.motionevent;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.widget.RelativeLayout;
importandroid.widget.TextView;
publicclassMotionEventActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_motion_event);
RelativeLayoutmyLayout=
(RelativeLayout)findViewById(R.id.RelativeLayout1);
myLayout.setOnTouchListener(
newRelativeLayout.OnTouchListener(){
publicbooleanonTouch(Viewv,
MotionEventm){
handleTouch(m);
returntrue;
}
}
);
}
.
.
.
}
ThefinaltaskbeforetestingtheapplicationistoimplementthehandleTouch()method
calledbytheonTouch()callbackmethod.Thecodeforthismethodreadsasfollows:
voidhandleTouch(MotionEventm)
{
TextViewtextView1=(TextView)findViewById(R.id.textView1);
TextViewtextView2=(TextView)findViewById(R.id.textView2);
intpointerCount=m.getPointerCount();
for(inti=0;i<pointerCount;i++)
{
intx=(int)m.getX(i);
inty=(int)m.getY(i);
intid=m.getPointerId(i);
intaction=m.getActionMasked();
intactionIndex=m.getActionIndex();
StringactionString;
switch(action)
{
caseMotionEvent.ACTION_DOWN:
actionString=“DOWN”;
break;
caseMotionEvent.ACTION_UP:
actionString=“UP”;
break;
caseMotionEvent.ACTION_POINTER_DOWN:
actionString=“PNTRDOWN”;
break;
caseMotionEvent.ACTION_POINTER_UP:
actionString=“PNTRUP”;
break;
caseMotionEvent.ACTION_MOVE:
actionString=“MOVE”;
break;
default:
actionString=””;
}
StringtouchStatus=“Action:”+actionString+”Index:
”+actionIndex+”ID:”+id+”X:”+x+”Y:”+y;
if(id==0)
textView1.setText(touchStatus);
else
textView2.setText(touchStatus);
}
}
Beforecompilingandrunningtheapplication,itisworthtakingthetimetowalkthrough
thiscodesystematicallytohighlightthetasksthatarebeingperformed.
ThecodebeginsbyobtainingreferencestothetwoTextViewobjectsintheuserinterface
andidentifyinghowmanypointersarecurrentlyactiveontheview:
TextViewtextView1=(TextView)findViewById(R.id.textView1);
TextViewtextView2=(TextView)findViewById(R.id.textView2);
intpointerCount=m.getPointerCount();
Next,thepointerCountvariableisusedtoinitiateaforloopwhichperformsasetoftasks
foreachactivepointer.ThefirstfewlinesoftheloopobtaintheXandYcoordinatesof
thetouchtogetherwiththecorrespondingeventID,actiontypeandactionindex.Lastly,a
stringvariableisdeclared:
for(inti=0;i<pointerCount;i++)
{
intx=(int)m.getX(i);
inty=(int)m.getY(i);
intid=m.getPointerId(i);
intaction=m.getActionMasked();
intactionIndex=m.getActionIndex();
StringactionString;
Sinceactiontypesequatetointegervalues,aswitchstatementisusedtoconverttheaction
typetoamoremeaningfulstringvalue,whichisstoredinthepreviouslydeclared
actionStringvariable:
switch(action)
{
caseMotionEvent.ACTION_DOWN:
actionString=“DOWN”;
break;
caseMotionEvent.ACTION_UP:
actionString=“UP”;
break;
caseMotionEvent.ACTION_POINTER_DOWN:
actionString=“PNTRDOWN”;
break;
caseMotionEvent.ACTION_POINTER_UP:
actionString=“PNTRUP”;
break;
caseMotionEvent.ACTION_MOVE:
actionString=“MOVE”;
break;
default:
actionString=””;
}
Lastly,thestringmessageisconstructedusingtheactionStringvalue,theactionindex,
touchIDandXandYcoordinates.TheIDvalueisthenusedtodecidewhetherthestring
shouldbedisplayedonthefirstorsecondTextViewobject:
StringtouchStatus=“Action:”+actionString+”Index:“
+actionIndex+”ID:”+id+”X:”+x+”Y:”+y;
if(id==0)
textView1.setText(touchStatus);
else
textView2.setText(touchStatus);
21.8RunningtheExampleApplication
SincetheAndroidemulatorenvironmentdoesnotsupportmulti-touch,compileandrun
theapplicationonaphysicalAndroiddevice.Oncelaunched,experimentwithsingleand
multipletouchesonthescreenandnotethatthetextviewsupdatetoreflecttheeventsas
illustratedinFigure21-2:
Figure21-2
21.9Summary
ActivitiesreceivenotificationsoftoucheventsbyregisteringanonTouchListenerevent
listenerandimplementingtheonTouch()callbackmethodwhich,inturn,ispasseda
MotionEventobjectwhencalledbytheAndroidruntime.Thisobjectcontainsinformation
aboutthetouchsuchasthetypeoftouchevent,thecoordinatesofthetouchandacountof
thenumberoftouchescurrentlyincontactwiththeview.
Whenmultipletouchesareinvolved,eachpointofcontactisreferredtoasapointerwith
eachassignedanindexandanID.Whiletheindexofatouchcanchangefromoneevent
toanother,theIDwillremainunchangeduntilthetouchends.
ThischapterhasworkedthroughthecreationofanexampleAndroidapplicationdesigned
todisplaythecoordinatesandactiontypeofuptotwosimultaneoustouchesonadevice
display.
Havingcoveredtouchesingeneral,thenextchapter(entitledDetectingCommonGestures
usingtheAndroidGestureDetectorClass)willlookfurtherattouchscreeneventhandling
throughtheimplementationofgesturerecognition.
22.DetectingCommonGesturesusingthe
AndroidGestureDetectorClass
Theterm“gesture”isusedtodefineacontiguoussequenceofinteractionsbetweenthe
touchscreenandtheuser.Atypicalgesturebeginsatthepointthatthescreenisfirst
touchedandendswhenthelastfingerorpointingdeviceleavesthedisplaysurface.When
correctlyharnessed,gesturescanbeimplementedasaformofcommunicationbetween
userandapplication.SwipingmotionstoturnthepagesofaneBook,orapinching
movementinvolvingtwotouchestozoominoroutofanimageareprimeexamplesofthe
waysinwhichgesturescanbeusedtointeractwithanapplication.
TheAndroidSDKprovidesmechanismsforthedetectionofbothcommonandcustom
gestureswithinanapplication.Commongesturesinvolveinteractionssuchasatap,
doubletap,longpressoraswipingmotionineitherahorizontaloraverticaldirection
(referredtoinAndroidnomenclatureasafling).
ThegoalofthischapteristoexploretheuseoftheAndroidGestureDetectorclassto
detectcommongesturesperformedonthedisplayofanAndroiddevice.Thenextchapter,
entitledImplementingCustomGestureandPinchRecognitiononAndroid,willcoverthe
detectionofmorecomplex,customgesturessuchascircularmotionsandpinches.
22.1ImplementingCommonGestureDetection
WhenauserinteractswiththedisplayofanAndroiddevice,theonTouchEvent()method
ofthecurrentlyactiveapplicationiscalledbythesystemandpassedMotionEventobjects
containingdataabouttheuser’scontactwiththescreen.Thisdatacanbeinterpretedto
identifyifthemotiononthescreenmatchesacommongesturesuchasataporaswipe.
ThiscanbeachievedwithverylittleprogrammingeffortbymakinguseoftheAndroid
GestureDetectorCompatclass.Thisclassisdesignedspecificallytoreceivemotionevent
informationfromtheapplicationandtotriggermethodcallsbasedonthetypeofcommon
gesture,ifany,detected.
Thebasicstepsindetectingcommongesturesareasfollows:
1. DeclarationofaclasswhichimplementstheGestureDetector.OnGestureListener
interfaceincludingtherequiredonFling(),onDown(),onScroll(),onShowPress(),
onSingleTapUp()andonLongPress()callbackmethods.Notethatthiscanbeeitheran
entirelynewclass,ortheenclosingactivityclass.Intheeventthatdoubletapgesture
detectionisrequired,theclassmustalsoimplementthe
GestureDetector.OnDoubleTapListenerinterfaceandincludethecorresponding
onDoubleTap()method.
2. CreationofaninstanceoftheAndroidGestureDetectorCompatclass,passingthrough
aninstanceoftheclasscreatedinstep1asanargument.
3. AnoptionalcalltothesetOnDoubleTapListener()methodofthe
GestureDetectorCompatinstancetoenabledoubletapdetectionifrequired.
4. ImplementationoftheonTouchEvent()callbackmethodontheenclosingactivity
which,inturn,mustcalltheonTouchEvent()methodoftheGestureDetectorCompat
instance,passingthroughthecurrentmotioneventobjectasanargumenttothe
method.
Onceimplemented,theresultisasetofmethodswithintheapplicationcodethatwillbe
calledwhenagestureofaparticulartypeisdetected.Thecodewithinthesemethodscan
thenbeimplementedtoperformanytasksthatneedtobeperformedinresponsetothe
correspondinggesture.
Intheremainderofthischapter,wewillworkthroughthecreationofanexampleproject
intendedtoputtheabovestepsintopractice.
22.2CreatinganExampleGestureDetectionProject
Thegoalofthisprojectistodetectthefullrangeofcommongesturescurrentlysupported
bytheGestureDetectorCompatclassandtodisplaystatusinformationtotheuser
indicatingthetypeofgesturethathasbeendetected.
CreateanewprojectinAndroidStudio,enteringCommonGesturesintotheApplication
namefieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingonthe
Nextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedCommonGesturesActivitywithacorresponding
layoutresourcefilenamedactivity_common_gestures.
ClickontheFinishbuttontoinitiatetheprojectcreationprocess.
Oncethenewprojecthasbeencreated,navigatetotheapp->res->layout->
activity_common_gestures.xmlfileintheProjecttoolwindowanddoubleclickonitto
loaditintotheDesignertool.
WithintheDesignertool,doubleclickonthe“Hello,World!”TextViewcomponentand,
inthepropertypopupwindow,entergestureStatusTextastheID.Finally,movethe
TextViewsothatitispositionedinthecenterofthedisplay.
22.3ImplementingtheListenerClass
Aspreviouslyoutlined,itisnecessarytocreateaclassthatimplementsthe
GestureDetector.OnGestureListenerinterfaceand,ifdoubletapdetectionisrequired,the
GestureDetector.OnDoubleTapListenerinterface.Whilethiscanbeanentirelynewclass,
itisalsoperfectlyvalidtoimplementthiswithinthecurrentactivityclass.Forthe
purposesofthisexample,therefore,wewillmodifytheCommonGesturesActivityclassto
implementtheselistenerinterfaces.EdittheCommonGesturesActivity.javafilesothatit
readsasfollowstodeclaretheinterfacesandtoextractandstoreareferencetothe
TextViewcomponentintheuserinterface:
packagecom.ebookfrenzy.commongestures;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.GestureDetector;
importandroid.widget.TextView;
publicclassCommonGesturesActivityextendsAppCompatActivity
implementsGestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener
{
privateTextViewgestureText;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_common_gestures);
gestureText=
(TextView)findViewById(R.id.gestureStatusText);
}
.
.
.
}
Declaringthattheclassimplementsthelistenerinterfacesmandatesthatthecorresponding
methodsalsobeimplementedintheclass:
packagecom.ebookfrenzy.commongestures;
mportandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.GestureDetector;
importandroid.widget.TextView;
importandroid.view.MotionEvent;
publicclassCommonGesturesActivityextendsAppCompatActivity
implementsGestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener{
privateTextViewgestureText;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_common_gestures);
gestureText=
(TextView)findViewById(R.id.gestureStatusText);
}
@Override
publicbooleanonDown(MotionEventevent){
gestureText.setText(“onDown”);
returntrue;
}
@Override
publicbooleanonFling(MotionEventevent1,MotionEventevent2,
floatvelocityX,floatvelocityY){
gestureText.setText(“onFling”);
returntrue;
}
@Override
publicvoidonLongPress(MotionEventevent){
gestureText.setText(“onLongPress”);
}
@Override
publicbooleanonScroll(MotionEvente1,MotionEvente2,
floatdistanceX,floatdistanceY){
gestureText.setText(“onScroll”);
returntrue;
}
@Override
publicvoidonShowPress(MotionEventevent){
gestureText.setText(“onShowPress”);
}
@Override
publicbooleanonSingleTapUp(MotionEventevent){
gestureText.setText(“onSingleTapUp”);
returntrue;
}
@Override
publicbooleanonDoubleTap(MotionEventevent){
gestureText.setText(“onDoubleTap”);
returntrue;
}
@Override
publicbooleanonDoubleTapEvent(MotionEventevent){
gestureText.setText(“onDoubleTapEvent”);
returntrue;
}
@Override
publicbooleanonSingleTapConfirmed(MotionEventevent){
gestureText.setText(“onSingleTapConfirmed”);
returntrue;
}
.
.
.
}
Notethatmanyofthesemethodsreturntrue.ThisindicatestotheAndroidFramework
thattheeventhasbeenconsumedbythemethodanddoesnotneedtobepassedtothe
nexteventhandlerinthestack.
22.4CreatingtheGestureDetectorCompatInstance
Withtheactivityclassnowupdatedtoimplementthelistenerinterfaces,thenextstepisto
createaninstanceoftheGestureDetectorCompatclass.Sincethisonlyneedstobe
performedonceatthepointthattheactivityiscreated,thebestplaceforthiscodeisinthe
onCreate()method.Sincewealsowanttodetectdoubletaps,thecodealsoneedstocall
thesetOnDoubleTapListener()methodoftheGestureDetectorCompatinstance:
packagecom.ebookfrenzy.commongestures;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.GestureDetector;
importandroid.widget.TextView;
importandroid.view.MotionEvent;
importandroid.support.v4.view.GestureDetectorCompat;
publicclassCommonGesturesActivityextendsAppCompatActivity
implementsGestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener{
privateTextViewgestureText;
privateGestureDetectorCompatgDetector;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_common_gestures);
gestureText=
(TextView)findViewById(R.id.gestureStatusText);
this.gDetector=newGestureDetectorCompat(this,this);
gDetector.setOnDoubleTapListener(this);
}
.
.
}
22.5ImplementingtheonTouchEvent()Method
Iftheapplicationweretobecompiledandrunatthispoint,nothingwouldhappenif
gestureswereperformedonthedevicedisplay.Thisisbecausenocodehasbeenaddedto
intercepttoucheventsandtopassthemthroughtotheGestureDetectorCompatinstance.
Inordertoachievethis,itisnecessarytooverridetheonTouchEvent()methodwithinthe
activityclassandimplementitsuchthatitcallstheonTouchEvent()methodofthe
GestureDetectorCompatinstance.RemainingintheCommonGesturesActivity.javafile,
therefore,implementthismethodsothatitreadsasfollows:
@Override
publicbooleanonTouchEvent(MotionEventevent){
this.gDetector.onTouchEvent(event);
//Besuretocallthesuperclassimplementation
returnsuper.onTouchEvent(event);
}
22.6TestingtheApplication
CompileandruntheapplicationoneitheraphysicalAndroiddeviceoranAVDemulator.
Oncelaunched,experimentwithswipes,presses,scrollingmotionsanddoubleandsingle
taps.NotethatthetextviewupdatestoreflecttheeventsasillustratedinFigure22-1:
Figure22-1
22.7Summary
Anyphysicalcontactbetweentheuserandthetouchscreendisplayofadevicecanbe
considereda“gesture”.Lackingthephysicalkeyboardandmousepointerofatraditional
computersystem,gesturesarewidelyusedasamethodofinteractionbetweenuserand
application.Whileagesturecanbecomprisedofjustaboutanysequenceofmotions,
thereisawidelyusedsetofgestureswithwhichusersoftouchscreendeviceshave
becomefamiliar.Anumberoftheseso-called“commongestures”canbeeasilydetected
withinanapplicationbymakinguseoftheAndroidGestureDetectorclasses.Inthis
chapter,theuseofthistechniquehasbeenoutlinedbothintheoryandthroughthe
implementationofanexampleproject.
Havingcoveredcommongesturesinthischapter,thenextchapterwilllookatdetectinga
widerrangeofgesturetypesincludingtheabilitytobothdesignanddetectyourown
gestures.
23.ImplementingCustomGestureandPinch
RecognitiononAndroid
Thepreviouschapterlookedatthestepsinvolvedindetectingwhatarereferredtoas
“commongestures”fromwithinanAndroidapplication.Inpractice,however,agesture
canconceivablyinvolvejustaboutanysequenceoftouchmotionsonthedisplayofan
Androiddevice.Inrecognitionofthisfact,theAndroidSDKallowscustomgesturesof
justaboutanynaturetobedefinedbytheapplicationdeveloperandusedtotriggerevents
whenperformedbytheuser.Thisisamultistageprocess,thedetailsofwhicharethetopic
ofthischapter.
23.1TheAndroidGestureBuilderApplication
TheAndroidSDKallowsdeveloperstodesigncustomgestureswhicharethenstoredina
gesturefilebundledwithanAndroidapplicationpackage.Thesecustomgesturefilesare
mosteasilycreatedusingtheGestureBuilderapplicationwhichisbundledwiththe
samplespackagesuppliedaspartoftheAndroidSDK.Thecreationofagesturesfile
involveslaunchingtheGestureBuilderapplication,eitheronaphysicaldeviceor
emulator,and“drawing”thegesturesthatwillneedtobedetectedbytheapplication.
Oncethegestureshavebeendesigned,thefilecontainingthegesturedatacanbepulled
offtheSDcardofthedeviceoremulatorandaddedtotheapplicationproject.Withinthe
applicationcode,thefileisthenloadedintoaninstanceoftheGestureLibraryclasswhere
itcanbeusedtosearchformatchestoanygesturesperformedbytheuseronthedevice
display.
23.2TheGestureOverlayViewClass
Inordertofacilitatethedetectionofgestureswithinanapplication,theAndroidSDK
providestheGestureOverlayViewclass.Thisisatransparentviewthatcanbeplacedover
otherviewsintheuserinterfaceforthesolepurposeofdetectinggestures.
23.3DetectingGestures
GesturesaredetectedbyloadingthegesturesfilecreatedusingtheGestureBuilderapp
andthenregisteringaGesturePerformedListenereventlisteneronaninstanceofthe
GestureOverlayViewclass.Theenclosingclassisthendeclaredtoimplementboththe
OnGesturePerformedListenerinterfaceandthecorrespondingonGesturePerformed
callbackmethodrequiredbythatinterface.Intheeventthatagestureisdetectedbythe
listener,acalltotheonGesturePerformedcallbackmethodistriggeredbytheAndroid
runtimesystem.
23.4IdentifyingSpecificGestures
Whenagestureisdetected,theonGesturePerformedcallbackmethodiscalledandpassed
asargumentsareferencetotheGestureOverlayViewobjectonwhichthegesturewas
detected,togetherwithaGestureobjectcontaininginformationaboutthegesture.
WithaccesstotheGestureobject,theGestureLibrarycanthenbeusedtocomparethe
detectedgesturetothosecontainedinthegesturesfilepreviouslyloadedintothe
application.TheGestureLibraryreportstheprobabilitythatthegestureperformedbythe
usermatchesanentryinthegesturesfilebycalculatingapredictionscoreforeach
gesture.Apredictionscoreof1.0orgreaterisgenerallyacceptedtobeagoodmatch
betweenagesturestoredinthefileandthatperformedbytheuseronthedevicedisplay.
23.5BuildingandRunningtheGestureBuilderApplication
TheGestureBuilderapplicationisbundledbydefaultwiththeAVDemulatorprofilefor
mostversionsoftheSDK.Itisnot,however,pre-installedonmostphysicalAndroid
devices.Iftheutilityispre-installed,itwillbelistedalongwiththeotherappsinstalledin
thedeviceorAVDinstance.Intheeventthatitisnotinstalled,thesourcecodeforthe
utilityisincludedamongstthestandardAndroidSDKsamplesandconsequentlymaybe
importedasanAndroidStudioprojectandcompiledandrunonanyAndroiddeviceor
emulator.
ToinstallandbuildtheGestureBuilderutility,beginbyinstallingtheSDKsamples.Todo
this,opentheAndroidSDKManagerbyselectingtheTools->Android->SDKManager
menubaroptionfromtheprojectmainwindow.
OncetheSDKsettingsdialoghasloadedclickontheLaunchStandaloneSDKManager
linkinthebottomlefthandcornerofthepaneltoopenthefullSDKManagertool.Once
thestandaloneSDKManagerisrunning,locatetheSamplesforSDKpackagelocated
beneaththesectionfortheAndroidversionforwhichyouarecurrentlydeveloping(for
exampleAndroid6.0(API23)).Ifthepackageisnotalreadyinstalled,setthecheckbox
nexttothepackageandclickontheInstall1Packagebuttontoinitiatetheinstallationas
highlightedinFigure23-1:
Figure23-1
Oncetheinstallationiscomplete,theSDKsampleswillbeinstalledinthefollowing
directory(where<path_to_installation>representsthelocationonyoursystemwherethe
SDKwasoriginallyinstalledasshownatthetopoftheSDKManagerwindow):
<path_to_installation>/sdk/samples/android-23
ThesourcecodefortheGestureBuilderapplicationislocatedwithinthisdirectoryina
foldernamedGestureBuilderwithinthelegacysub-folder.
TheGestureBuilderprojectisbasedonAndroid5.0.1(API21)sousetheSDKManager
toolonceagaintoensurethatthatthisversionoftheAndroidSDKisinstalledbefore
proceeding.
FromtheAndroidStudiomainwindowforanexistingproject,selecttheFile->New->
ImportProject…menuoptionand,withintheresultingdialog,navigatetoandselectthe
GestureBuilderfolderwithintheSDKsamplesdirectoryandclickonOK.Notethaton
someWindowssystemsitmaybenecessarytoclickonthe“ShowHiddenFilesand
Directories”buttoninthefilenavigatortoolbarasshowninFigure23-2inordertodisplay
thefoldercontainingtheSDKsamples:
Figure23-2
ConfirmthedestinationdirectoryandclickonNextfollowedbyFinishtoacceptthe
defaultsettingsonthefinalscreen.Atthispoint,AndroidStudiowillimporttheproject
intothedesignatedfolderandconvertittomatchtheAndroidStudioprojectfileandbuild
structure.
Onceimported,installandruntheGestureBuilderutilityonanAndroiddeviceattachedto
thedevelopmentsystem.
23.6CreatingaGesturesFile
OncetheGestureBuilderapplicationhasloaded,itshouldindicatethatnogestureshave
yetbeencreated.Tocreateanewgesture,clickontheAddgesturebuttonlocatedatthe
bottomofthedevicescreen,enterthenameCircleGestureintotheNametextboxand
then“draw”agestureusingacircularmotiononthescreenasillustratedinFigure23-3.
Assumingthatthegestureappearsasrequired(representedbytheyellowlineonthe
devicescreen),clickontheDonebuttontoaddthegesturetothegesturesfile:
Figure23-3
Afterthegesturehasbeensaved,theGestureBuilderappwilldisplayalistofcurrently
definedgestures,which,atthispoint,willconsistsolelyofthenewCircleGesture.
Repeatthegesturecreationprocesstoaddafurthergesturetothefile.Thisshouldinvolve
atwo-strokegesturecreatinganXonthescreennamedXGesture.Whencreatinggestures
involvingmultiplestrokes,besuretoallowaslittletimeaspossiblebetweeneachstroke
sothatthebuilderknowsthatthestrokesarepartofthesamegesture.Oncethisgesture
hasbeenadded,thelistwithintheGestureBuilderapplicationshouldresemblethat
outlinedinFigure23-4:
Figure23-4
23.7ExtractingtheGesturesFilefromtheSDCard
AseachgesturewascreatedwithintheGestureBuilderapplication,itwasaddedtoafile
namedgestureslocatedontheSDCardoftheemulatorordeviceonwhichtheappwas
running.BeforethisfilecanbeaddedtoanAndroidStudioproject,however,itmustfirst
bepulledofftheSDCardandsavedtothelocalfilesystem.Thisismosteasilyachieved
byusingtheadbcommand-linetool.OpenaTerminalorCommandPromptwindow,
therefore,andexecutethefollowingcommand:
adbdevices
Intheeventthattheadbcommandisnotfound,refertoSettingupanAndroidStudio
DevelopmentEnvironmentforguidanceonaddingthistothePATHenvironmentvariable
ofyoursystem.
Onceexecuted,thecommandwilllistallactivephysicaldevicesandAVDinstances
attachedtothesystem.Thefollowingoutput,forexample,indicatesthatbothaphysical
deviceandoneAVDemulatorhavebeendetectedonthedevelopmentcomputersystem:
Listofdevicesattached
HT4CTJT01906device
emulator-5554device
Inordertopullthegesturesfilefromtheemulatorintheaboveexampleandplaceitinto
thecurrentworkingdirectoryoftheTerminalorCommandPromptwindow,thefollowing
commandwouldneedtobeexecuted:
adb-semulator-5554pull/sdcard/gestures.
Alternatively,thegesturesfilecanbepulledfromadeviceconnectedviaadbusingthe
followingcommand(wherethe–dflagisusedtoindicateaphysicaldevice):
adb-dpull/sdcard/gestures.
OncethegesturesfilehasbeencreatedandpulledofftheSDCard,itisreadytobeadded
toanAndroidStudioprojectasaresourcefile.Thenextstep,therefore,istocreateanew
project.
23.8CreatingtheExampleProject
CreateanewprojectinAndroidStudio,enteringCustomGesturesintotheApplication
namefieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingonthe
Nextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedCustomGesturesActivitywithacorresponding
layoutfilenamedactivity_custom_gestures.
ClickontheFinishbuttontoinitiatetheprojectcreationprocess.
23.9AddingtheGesturesFiletotheProject
WithintheAndroidStudioProjecttoolwindow,locateandright-clickontheresfolder
(locatedunderapp)andselectNew->Directoryfromtheresultingmenu.IntheNew
Directorydialog,enterrawasthefoldernameandclickontheOKbutton.Usingthe
appropriatefileexplorerutilityforyouroperatingsystemtype,locatethegesturesfile
previouslypulledfromtheSDCardandcopyandpasteitintothenewrawfolderinthe
Projecttoolwindow.
23.10DesigningtheUserInterface
Thisexampleapplicationcallsforaverysimpleuserinterfaceconsistingofa
LinearLayoutviewwithaGestureOverlayViewlayeredontopofittointerceptany
gesturesperformedbytheuser.Locatetheapp->res->layout->
activity_custom_gestures.xmlfileanddoubleclickonittoloaditintotheDesignertool.
Bydefault,AndroidStudiohasprovidedaRelativeLayoutcomponentastherootelement
oftheuserinterfacelayoutsothiswillneedtobedeletedandreplacedwitha
LinearLayout.
SwitchtheDesignertooltoTextmodeusingtheTexttabalongthebottomedgeofthe
panelandmodifytheXMLforthelayoutsothatitmatchesthefollowinglisting:
<?xmlversion=“1.0”encoding=“utf-8”?>
<LinearLayout
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
xmlns:android=“http://schemas.android.com/apk/res/android”>
</LinearLayout>
ReturntoDesignmode,locatetheExpertsectionofthePaletteanddraganddropa
GestureOverlayViewobjectontothelayoutcanvas.SelecttheGestureOverlayView
instanceinthelayoutandusethePropertiespanelorDesignertoolbarbuttonstochange
thelayout:widthandlayout:heightpropertiestomatch_parentsothattheviewfillsthe
availablespace.
DoubleclickontheGestureOverlayViewinstanceandusethepopuppropertypanelto
changetheIDtogOverlay.Whencompleted,theactivity_custom_gestures.xmlfileshould
readasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<LinearLayout
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
xmlns:android=“http://schemas.android.com/apk/res/android”>
<android.gesture.GestureOverlayView
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:id=”@+id/gOverlay”
android:layout_gravity=“center_horizontal”>
</android.gesture.GestureOverlayView>
</LinearLayout>
23.11LoadingtheGesturesFile
Nowthatthegesturesfilehasbeenaddedtotheproject,thenextstepistowritesome
codesothatthefileisloadedwhentheactivitystartsup.Forthepurposesofthisproject,
thecodetoachievethiswillbeplacedintheonCreate()methodofthe
CustomGesturesActivityclasslocatedintheCustomGesturesActivity.javasourcefileas
follows:
packagecom.ebookfrenzy.customgestures;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.gesture.GestureLibraries;
importandroid.gesture.GestureLibrary;
importandroid.gesture.GestureOverlayView;
importandroid.gesture.GestureOverlayView.OnGesturePerformedListener;
publicclassCustomGesturesActivityextendsAppCompatActivity
implementsOnGesturePerformedListener{
privateGestureLibrarygLibrary;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_gestures);
gLibrary=
GestureLibraries.fromRawResource(this,R.raw.gestures);
if(!gLibrary.load()){
finish();
}
}
.
.
.
}
Inadditiontosomenecessaryimportdirectives,theabovecodechangestotheonCreate()
methodalsocreateaGestureLibraryinstancenamedgLibraryandthenloadsintoitthe
contentsofthegesturesfilelocatedintherawresourcesfolder.Theactivityclasshasalso
beenmodifiedtoimplementtheOnGesturePerformedListenerinterface,whichrequires
theimplementationoftheonGesturePerformedcallbackmethod(whichwillbecreatedin
alatersectionofthischapter).
23.12RegisteringtheEventListener
Inorderfortheactivitytoreceivenotificationthattheuserhasperformedagestureonthe
screen,itisnecessarytoregistertheOnGesturePerformedListenereventlisteneronthe
gLayoutview,areferencetowhichcanbeobtainedusingthefindViewByIdmethodas
outlinedinthefollowingcodefragment:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_gestures);
gLibrary=
GestureLibraries.fromRawResource(this,R.raw.gestures);
if(!gLibrary.load()){
finish();
}
GestureOverlayViewgOverlay=
(GestureOverlayView)findViewById(R.id.gOverlay);
gOverlay.addOnGesturePerformedListener(this);
}
23.13ImplementingtheonGesturePerformedMethod
Allthatremainsbeforeaninitialtestrunoftheapplicationcanbeperformedisto
implementtheOnGesturePerformedcallbackmethod.Thisisthemethodwhichwillbe
calledwhenagestureisperformedontheGestureOverlayViewinstance:
packagecom.ebookfrenzy.customgestures;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.gesture.GestureLibraries;
importandroid.gesture.GestureLibrary;
importandroid.gesture.GestureOverlayView;
importandroid.gesture.GestureOverlayView.OnGesturePerformedListener;
importandroid.gesture.Prediction;
importandroid.widget.Toast;
importandroid.gesture.Gesture;
importjava.util.ArrayList;
publicclassCustomGesturesActivityextendsAppCompatActivity
implementsOnGesturePerformedListener{
privateGestureLibrarygLibrary;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_gestures);
gLibrary=
GestureLibraries.fromRawResource(this,R.raw.gestures);
if(!gLibrary.load()){
finish();
}
GestureOverlayViewgOverlay=
(GestureOverlayView)findViewById(R.id.gOverlay);
gOverlay.addOnGesturePerformedListener(this);
}
publicvoidonGesturePerformed(GestureOverlayViewoverlay,Gesture
gesture){
ArrayList<Prediction>predictions=
gLibrary.recognize(gesture);
if(predictions.size()>0&&predictions.get(0).score>1.0){
Stringaction=predictions.get(0).name;
Toast.makeText(this,action,Toast.LENGTH_SHORT).show();
}
}
.
.
.
}
WhenagestureonthegestureoverlayviewobjectisdetectedbytheAndroidruntime,the
onGesturePerformedmethodiscalled.Passedthroughasargumentsareareferencetothe
GestureOverlayViewobjectonwhichthegesturewasdetectedtogetherwithanobjectof
typeGesture.TheGestureclassisdesignedtoholdtheinformationthatdefinesaspecific
gesture(essentiallyasequenceoftimedpointsonthescreendepictingthepathofthe
strokesthatcompriseagesture).
TheGestureobjectispassedthroughtotherecognize()methodofourgLibraryinstance,
thepurposeofwhichistocomparethecurrentgesturewitheachgestureloadedfromthe
gesturesfile.Oncethistaskiscomplete,therecognize()methodreturnsanArrayList
objectcontainingaPredictionobjectforeachcomparisonperformed.Thelistisrankedin
orderfromthebestmatch(atposition0inthearray)totheworst.Containedwithineach
predictionobjectisthenameofthecorrespondinggesturefromthegesturesfileanda
predictionscoreindicatinghowcloselyitmatchesthecurrentgesture.
Thecodeintheabovemethod,therefore,takesthepredictionatposition0(theclosest
match)makessureithasascoreofgreaterthan1.0andthendisplaysaToastmessage(an
Androidclassdesignedtodisplaynotificationpopupstotheuser)displayingthenameof
thematchinggesture.
23.14TestingtheApplication
BuildandruntheapplicationoneitheranemulatororaphysicalAndroiddeviceand
performthecircleandswipegesturesonthedisplay.Whenperformed,thetoast
notificationshouldappearcontainingthenameofthegesturethatwasperformed.Note,
however,thatwhenattemptingtoperformtheXGesturethatthegestureisnotrecognized.
Also,notethatwhenagestureisrecognized,itisoutlinedonthedisplaywithabright
yellowlinewhilegesturesaboutwhichtheoverlayisuncertainappearasafadedyellow
line.Whileusefulduringdevelopment,thisisprobablynotidealforarealworld
application.Clearly,therefore,thereisstillsomemoreconfigurationworktodo.
23.15ConfiguringtheGestureOverlayView
Bydefault,theGestureOverlayViewisconfiguredtodisplayyellowlinesduringgestures
andtorecognizeonlysinglestrokegestures.Multi-strokegesturescanbedetectedby
settingtheandroid:gestureStrokeTypepropertytomultiple.
Similarly,thecolorusedtodrawrecognizedandunrecognizedgesturescanbedefinedvia
theandroid:gestureColorandandroid:uncertainGestureColorproperties.Forexample,to
hidethegesturelinesandrecognizemulti-strokegestures,modifythe
activity_custom_gestures.xmlfileintheexampleprojectasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<LinearLayout
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
xmlns:android=“http://schemas.android.com/apk/res/android”>
<android.gesture.GestureOverlayView
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:id=”@+id/gOverlay”
android:layout_gravity=“center_horizontal”
android:gestureColor=”#00000000”
android:uncertainGestureColor=”#00000000”
android:gestureStrokeType=“multiple”>
</android.gesture.GestureOverlayView>
</LinearLayout>
Onre-runningtheapplication,gesturesshouldnowbeinvisible(sincetheyaredrawnin
whiteonthewhitebackgroundoftheLinearLayoutview).
23.16InterceptingGestures
TheGestureOverlayViewis,aspreviouslydescribed,atransparentoverlaythatmaybe
positionedoverthetopofotherviews.Thisleadstothequestionastowhetherevents
interceptedbythegestureoverlayshouldthenbepassedontotheunderlyingviewswhen
agesturehasbeenrecognized.Thisiscontrolledviathe
android:eventsInterceptionEnabledpropertyoftheGestureOverlayViewinstance.When
settotrue,thegestureeventsarenotpassedtotheunderlyingviewswhenagestureis
recognized.Thiscanbeaparticularlyusefulsettingwhengesturesarebeingperformed
overaviewthatmightbeconfiguredtoscrollinresponsetocertaingestures.Settingthis
propertytotruewillavoidgesturesalsobeinginterpretedasinstructionstotheunderlying
viewtoscrollinaparticulardirection.
23.17DetectingPinchGestures
Beforemovingonfromtouchhandlingingeneralandgesturerecognitioninparticular,the
lasttopicofthischapteristhatofhandlingpinchgestures.Whileitispossibletocreate
anddetectawiderangeofgesturesusingthestepsoutlinedintheprevioussectionsofthis
chapteritis,infact,notpossibletodetectapinchinggesture(wheretwofingersareused
inastretchingandpinchingmotion,typicallytozoominandoutofavieworimage)
usingthetechniquesdiscussedsofar.
ThesimplestmethodfordetectingpinchgesturesistousetheAndroid
ScaleGestureDetectorclass.Ingeneralterms,detectingpinchgesturesinvolvesthe
followingthreesteps:
1. DeclarationofanewclasswhichimplementstheSimpleOnScaleGestureListener
interfaceincludingtherequiredonScale(),onScaleBegin()andonScaleEnd()callback
methods.
2. CreationofaninstanceoftheScaleGestureDetectorclass,passingthroughaninstance
oftheclasscreatedinstep1asanargument.
3. ImplementingtheonTouchEvent()callbackmethodontheenclosingactivitywhich,in
turn,callstheonTouchEvent()methodoftheScaleGestureDetectorclass.
Intheremainderofthischapter,wewillcreateaverysimpleexampledesignedto
demonstratetheimplementationofpinchgesturerecognition.
23.18APinchGestureExampleProject
CreateanewprojectinAndroidStudio,enteringPinchExampleintotheApplicationname
fieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedPinchExampleActivitywithalayoutresourcefile
namedactivity_pinch_example.
Withintheactivity_pinch_example.xmlfile,locatetheTextViewobjectanddoubleclick
onittochangetheIDtomyTextView.
LocateandloadthePinchExampleActivity.javafileintotheAndroidStudioeditorand
modifythefileasfollows:
packagecom.ebookfrenzy.pinchexample;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.MotionEvent;
importandroid.view.ScaleGestureDetector;
importandroid.view.ScaleGestureDetector.SimpleOnScaleGestureListener;
importandroid.widget.TextView;
publicclassPinchExampleActivityextendsAppCompatActivity{
TextViewscaleText;
ScaleGestureDetectorscaleGestureDetector;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pinch_example);
scaleText=(TextView)findViewById(R.id.myTextView);
scaleGestureDetector=
newScaleGestureDetector(this,
newMyOnScaleGestureListener());
}
@Override
publicbooleanonTouchEvent(MotionEventevent){
scaleGestureDetector.onTouchEvent(event);
returntrue;
}
publicclassMyOnScaleGestureListenerextends
SimpleOnScaleGestureListener{
@Override
publicbooleanonScale(ScaleGestureDetectordetector){
floatscaleFactor=detector.getScaleFactor();
if(scaleFactor>1){
scaleText.setText(“ZoomingOut”);
}else{
scaleText.setText(“ZoomingIn”);
}
returntrue;
}
@Override
publicbooleanonScaleBegin(ScaleGestureDetectordetector){
returntrue;
}
@Override
publicvoidonScaleEnd(ScaleGestureDetectordetector){
}
}
.
.
.
}
ThecodebeginsbydeclaringTextViewandScaleGestureDetectorvariables.Anewclass
namedMyOnScaleGestureListenerisdeclaredwhichextendstheAndroid
SimpleOnScaleGestureListenerclass.Thisinterfacerequiresthatthreemethods
(onScale(),onScaleBegin()andonScaleEnd())beimplemented.Inthisinstancethe
onScale()methodidentifiesthescalefactoranddisplaysamessageonthetextview
indicatingthetypeofpinchgesturedetected.
WithintheonCreate()method,areferencetothetextviewobjectisobtainedandassigned
tothescaleTextvariable.Next,anewScaleGestureDetectorinstanceiscreated,passing
throughareferencetotheenclosingactivityandaninstanceofournew
MyOnScaleGestureListenerclassasarguments.Finally,anonTouchEvent()callback
methodisimplementedfortheactivity,whichsimplycallsthecorresponding
onTouchEvent()methodoftheScaleGestureDetectorobject,passingthroughthe
MotionEventobjectasanargument.
CompileandruntheapplicationonaphysicalAndroiddeviceandperformpinching
gesturesonthescreen,notingthatthetextviewdisplayseitherthezoominorzoomout
messagedependingonthepinchingmotion.
23.19Summary
Agestureisessentiallythemotionofpointsofcontactonatouchscreeninvolvingoneor
morestrokesandcanbeusedasamethodofcommunicationbetweenuserand
application.AndroidallowsgesturestobedesignedusingtheGestureBuilderapplication.
Oncecreated,gesturescanbesavedtoagesturesfileandloadedintoanactivityat
applicationruntimeusingtheGestureLibrary.
Gesturescanbedetectedonareasofthedisplaybyoverlayingexistingviewswith
instancesofthetransparentGestureOverlayViewclassandimplementingan
OnGesturePerformedListenereventlistener.UsingtheGestureLibrary,arankedlistof
matchesbetweenagestureperformedbytheuserandthegesturesstoredinagesturesfile
maybegenerated,usingapredictionscoretodecidewhetheragestureisacloseenough
match.
PinchgesturesmaybedetectedthroughtheimplementationoftheScaleGestureDetector
class,anexampleofwhichwasalsoprovidedinthischapter.
24.AnIntroductiontoAndroidFragments
Asyouprogressthroughthechaptersofthisbookitwillbecomeincreasinglyevidentthat
manyofthedesignconceptsbehindtheAndroidsystemwereconceivedwiththegoalof
promotingreuseof,andinteractionbetweenthedifferentelementsthatmakeupan
application.Onesuchareathatwillbeexploredinthischapterinvolvestheuseof
Fragments.
Thischapterwillprovideanoverviewofthebasicsoffragmentsintermsofwhattheyare
andhowtheycanbecreatedandusedwithinapplications.Thenextchapterwillwork
throughatutorialdesignedtoshowfragmentsinactionwhendevelopingapplicationsin
AndroidStudio,includingtheimplementationofcommunicationbetweenfragments.
24.1WhatisaFragment?
Afragmentisaself-contained,modularsectionofanapplication’suserinterfaceand
correspondingbehaviorthatcanbeembeddedwithinanactivity.Fragmentscanbe
assembledtocreateanactivityduringtheapplicationdesignphase,andaddedto,or
removedfromanactivityduringapplicationruntimetocreateadynamicallychanging
userinterface.
Fragmentsmayonlybeusedaspartofanactivityandcannotbeinstantiatedasstandalone
applicationelements.Thatbeingsaid,however,afragmentcanbethoughtofasa
functional“sub-activity”withitsownlifecyclesimilartothatofafullactivity.
FragmentsarestoredintheformofXMLlayoutfilesandmaybeaddedtoanactivity
eitherbyplacingappropriate<fragment>elementsintheactivity’slayoutfile,ordirectly
throughcodewithintheactivity’sclassimplementation.
BeforestartingtouseFragmentsinanAndroidapplication,itisimportanttobeawarethat
FragmentswerenotintroducedtoAndroiduntilversion3.0oftheAndroidSDK.An
applicationthatusesFragmentsmust,therefore,makeuseoftheandroid-support-v4
AndroidSupportLibraryinordertobecompatiblewitholderAndroidversions.Thesteps
toachievethiswillbecoveredinthenextchapter,entitledUsingFragmentsinAndroid
Studio-AWorkedExample.
24.2CreatingaFragment
ThetwocomponentsthatmakeupafragmentareanXMLlayoutfileandacorresponding
Javaclass.TheXMLlayoutfileforafragmenttakesthesameformatasalayoutforany
otheractivitylayoutandcancontainanycombinationandcomplexityoflayoutmanagers
andviews.ThefollowingXMLlayout,forexample,isforafragmentconsistingsimplyof
aRelativeLayoutwitharedbackgroundcontainingasingleTextView:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:background=”@color/red”>
<TextView
android:id=”@+id/textView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerHorizontal=“true”
android:layout_centerVertical=“true”
android:text=”@string/fragone_label_text”
android:textAppearance=”?android:attr/textAppearanceLarge”/>
</RelativeLayout>
ThecorrespondingclasstogowiththelayoutmustbeasubclassoftheAndroidFragment
class.IftheapplicationistobecompatiblewithdevicesrunningversionsofAndroid
predatingversion3.0thentheclassfilemustimportandroid.support.v4.app.Fragment.
Theclassshould,ataminimum,overridetheonCreateView()methodwhichisresponsible
forloadingthefragmentlayout.Forexample:
packagecom.example.myfragmentdemo;
importandroid.os.Bundle;
importandroid.support.v4.app.Fragment;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
publicclassFragmentOneextendsFragment{
@Override
publicViewonCreateView(LayoutInflaterinflater,
ViewGroupcontainer,
BundlesavedInstanceState){
//Inflatethelayoutforthisfragment
returninflater.inflate(R.layout.fragment_one_layout,
container,false);
}
}
InadditiontotheonCreateView()method,theclassmayalsooverridethestandard
lifecyclemethods.
NotethatinordertomaketheabovefragmentcompatiblewithAndroidversionspriorto
version3.0,theFragmentclassfromthev4supportlibraryhasbeenimported.
Oncethefragmentlayoutandclasshavebeencreated,thefragmentisreadytobeused
withinapplicationactivities.
24.3AddingaFragmenttoanActivityusingtheLayoutXMLFile
FragmentsmaybeincorporatedintoanactivityeitherbywritingJavacodeorby
embeddingthefragmentintotheactivity’sXMLlayoutfile.Regardlessoftheapproach
used,akeypointtobeawareofisthatwhenthesupportlibraryisbeingusedfor
compatibilitywitholderAndroidreleases,anyactivitiesusingfragmentsmustbe
implementedasasubclassofFragmentActivityinsteadofthetraditionalActivityclass:
packagecom.example.myfragmentdemo;
importandroid.os.Bundle;
importandroid.support.v4.app.FragmentActivity;
importandroid.view.Menu;
publicclassFragmentDemoActivityextendsFragmentActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment_demo);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.activity_fragment_demo,
menu);
returntrue;
}
}
Fragmentsareembeddedintoactivitylayoutfilesusingthe<fragment>element.The
followingexamplelayoutembedsthefragmentcreatedintheprevioussectionofthis
chapterintoanactivitylayout:
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=”.FragmentDemoActivity”>
<fragment
android:id=”@+id/fragment_one”
android:name=“com.example.myfragmentdemo.myfragmentdemo.FragmentOne”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_alignParentLeft=“true”
android:layout_centerVertical=“true”
tools:layout=”@layout/fragment_one_layout”/>
</RelativeLayout>
Thekeypropertieswithinthe<fragment>elementareandroid:name,whichmust
referencetheclassassociatedwiththefragment,andtools:layout,whichmustreference
theXMLresourcefilecontainingthelayoutofthefragment.
Onceaddedtothelayoutofanactivity,fragmentsmaybeviewedandmanipulatedwithin
theAndroidStudioDesignertool.Figure24-1,forexample,showstheabovelayoutwith
theembeddedfragmentwithintheAndroidStudioDesigner:
Figure24-1
24.4AddingandManagingFragmentsinCode
Theeaseofaddingafragmenttoanactivityviatheactivity’sXMLlayoutfilecomesat
thecostoftheactivitynotbeingabletoremovethefragmentatruntime.Inorderto
achievefulldynamiccontroloffragmentsduringruntime,thoseactivitiesmustbeadded
viacode.Thishastheadvantagethatthefragmentscanbeadded,removedandevenmade
toreplaceoneanotherdynamicallywhiletheapplicationisrunning.
Whenusingcodetomanagefragments,thefragmentitselfwillstillconsistofanXML
layoutfileandacorrespondingclass.Thedifferencecomeswhenworkingwiththe
fragmentwithinthehostingactivity.Thereisastandardsequenceofstepswhenaddinga
fragmenttoanactivityusingcode.Thesestepsareasfollows:
1. Createaninstanceofthefragment’sclass.
2. Passanyadditionalintentargumentsthroughtotheclass.
3. Obtainareferencetothefragmentmanagerinstance.
4. CallthebeginTransaction()methodonthefragmentmanagerinstance.This
returnsafragmenttransactioninstance.
5. Calltheadd()methodofthefragmenttransactioninstance,passingthroughas
argumentstheresourceidoftheviewthatistocontainthefragmentandthefragment
classinstance.
6. Callthecommit()methodofthefragmenttransaction.
Thefollowingcode,forthesakeofanexample,addsafragmentdefinedbythe
FragmentOneclasssothatitappearsinthecontainerviewwithanidofLinearLayout1:
FragmentOnefirstFragment=newFragmentOne();
firstFragment.setArguments(getIntent().getExtras());
FragmentManagerfragManager=getSupportFragmentManager();
FragmentTransactiontransaction=
fragManager.beginTransaction();
transaction.add(R.id.LinearLayout1,firstFragment);
transaction.commit();
Theabovecodebreaksdowneachstepintoaseparatestatementforthepurposesof
clarity.Thelastfourlinescan,however,beabbreviatedintoasinglelineofcodeas
follows:
getSupportFragmentManager().beginTransaction()
.add(R.id.LinearLayout1,firstFragment).commit();
Onceaddedtoacontainer,afragmentmaysubsequentlyberemovedviaacalltothe
remove()methodofthefragmenttransactioninstance,passingthroughareferencetothe
fragmentinstancethatistoberemoved:
transaction.remove(firstFragment);
Similarly,onefragmentmaybereplacedwithanotherbyacalltothereplace()methodof
thefragmenttransactioninstance.Thistakesasargumentstheidoftheviewcontaining
thefragmentandaninstanceofthenewfragment.Thereplacedfragmentmayalsobe
placedonwhatisreferredtoasthebackstacksothatitcanbequicklyrestoredinthe
eventthattheusernavigatesbacktoit.Thisisachievedbymakingacalltothe
addToBackStack()methodofthefragmenttransactionobjectbeforemakingthecommit()
methodcall:
FragmentTwosecondFragment=newFragmentTwo();
transaction.replace(R.id.LinearLayout1,secondFragment);
transaction.addToBackStack(null);
transaction.commit();
24.5HandlingFragmentEvents
Aspreviouslydiscussed,afragmentisverymuchlikeasub-activitywithitsownlayout,
classandlifecycle.Theviewcomponents(suchasbuttonsandtextviews)withina
fragmentareabletogenerateeventsjustlikethoseinaregularactivity.Thisraisesthe
questionastowhichclassreceivesaneventfromaviewinafragment;thefragmentitself,
ortheactivityinwhichthefragmentisembedded.Theanswertothisquestiondependson
howtheeventhandlerisdeclared.
InthechapterentitledAnOverviewandExampleofAndroidEventHandling,two
approachestoeventhandlingwerediscussed.Thefirstmethodinvolvedconfiguringan
eventlistenerandcallbackmethodwithinthecodeoftheactivity.Forexample:
Buttonbutton=(Button)findViewById(R.id.myButton);
button.setOnClickListener(
newButton.OnClickListener(){
publicvoidonClick(Viewv){
//Codetobeperformedwhen
//thebuttonisclicked
}
}
);
Inthecaseofinterceptingclickevents,thesecondapproachinvolvedsettingthe
android:onClickpropertywithintheXMLlayoutfile:
<Button
android:id=”@+id/button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:onClick=“onClick”
android:text=“Clickme”/>
Thegeneralruleforeventsgeneratedbyaviewinafragmentisthatiftheeventlistener
wasdeclaredinthefragmentclassusingtheeventlistenerandcallbackmethodapproach,
thentheeventwillbehandledfirstbythefragment.Iftheandroid:onClickresourceis
used,however,theeventwillbepasseddirectlytotheactivitycontainingthefragment.
24.6ImplementingFragmentCommunication
Onceoneormorefragmentsareembeddedwithinanactivity,thechancesaregoodthat
someformofcommunicationwillneedtotakeplacebothbetweenthefragmentsandthe
activityandbetweenonefragmentandanother.Infact,fragmentsshouldnot
communicatedirectlywitheachother.Allcommunicationshouldtakeplaceviathe
encapsulatingactivity.
Inorderforanactivitytocommunicatewithafragment,theactivitymustidentifythe
fragmentobjectviatheIDassignedtoitusingthefindViewById()method.Oncethis
referencehasbeenobtained,theactivitycansimplycallthepublicmethodsofthe
fragmentobject.
Communicatingintheotherdirection(fromfragmenttoactivity)isalittlemore
complicated.Inthefirstinstance,thefragmentmustdefinealistenerinterface,whichis
thenimplementedwithintheactivityclass.Forexample,thefollowingcodedeclaresan
interfacenamedToolbarListeneronafragmentclassnamedToolbarFragment.Thecode
alsodeclaresavariableinwhichareferencetotheactivitywilllaterbestored:
publicclassToolbarFragmentextendsFragment{
ToolbarListeneractivityCallback;
publicinterfaceToolbarListener{
publicvoidonButtonClick(intposition,Stringtext);
}
.
.
}
TheabovecodedictatesthatanyclassthatimplementstheToolbarListenerinterfacemust
alsoimplementacallbackmethodnamedonButtonClickwhich,inturn,acceptsaninteger
andaStringasarguments.
Next,theonAttach()methodofthefragmentclassneedstobeoverriddenand
implemented.ThismethodiscalledautomaticallybytheAndroidsystemwhenthe
fragmenthasbeeninitializedandassociatedwithanactivity.Themethodispasseda
referencetotheactivityinwhichthefragmentiscontained.Themethodmuststorealocal
referencetothisactivityandverifythatitimplementstheToolbarListenerinterface:
@Override
publicvoidonAttach(Activityactivity){
super.onAttach(activity);
try{
activityCallback=(ToolbarListener)activity;
}catch(ClassCastExceptione){
thrownewClassCastException(activity.toString()
+”mustimplementToolbarListener”);
}
}
Uponexecutionofthisexample,areferencetotheactivitywillbestoredinthelocal
activityCallbackvariable,andanexceptionwillbethrownifthatactivitydoesnot
implementtheToolbarListenerinterface.
Thenextstepistocallthecallbackmethodoftheactivityfromwithinthefragment.When
andhowthishappensisentirelydependentonthecircumstancesunderwhichtheactivity
needstobecontactedbythefragment.Forthesakeofanexample,thefollowingcode
callsthecallbackmethodontheactivitywhenabuttonisclicked:
publicvoidbuttonClicked(Viewview){
activityCallback.onButtonClick(arg1,arg2);
}
AllthatremainsistomodifytheactivityclasssothatitimplementstheToolbarListener
interface.Forexample:
publicclassFragmentExampleActivityextendsFragmentActivity
implements
ToolbarFragment.ToolbarListener{
publicvoidonButtonClick(Stringarg1,intarg2){
//Implementcodeforcallbackmethod
}
.
.
}
Aswecanseefromtheabovecode,theactivitydeclaresthatitimplementsthe
ToolbarListenerinterfaceoftheToolbarFragmentclassandthenproceedstoimplement
theonButtonClick()methodasrequiredbytheinterface.
24.7Summary
Fragmentsprovideapowerfulmechanismforcreatingre-usablemodulesofuserinterface
layoutandapplicationbehavior,which,oncecreated,canbeembeddedinactivities.A
fragmentconsistsofauserinterfacelayoutfileandaclass.Fragmentsmaybeutilizedin
anactivityeitherbyaddingthefragmenttotheactivity’slayoutfile,orbywritingcodeto
managethefragmentsatruntime.Fragmentsaddedtoanactivityincodecanberemoved
andreplaceddynamicallyatruntime.Allcommunicationbetweenfragmentsshouldbe
performedviatheactivitywithinwhichtheactivitiesareembedded.
Havingcoveredthebasicsoffragmentsinthischapter,thenextchapterwillworkthrough
atutorialdesignedtoreinforcethetechniquesoutlinedinthischapter.
25.UsingFragmentsinAndroidStudio-An
Example
Asoutlinedinthepreviouschapter,fragmentsprovideaconvenientmechanismfor
creatingreusablemodulesofapplicationfunctionalityconsistingofbothsectionsofauser
interfaceandthecorrespondingbehavior.Oncecreated,fragmentscanbeembedded
withinactivities.
Havingexploredtheoveralltheoryoffragmentsinthepreviouschapter,theobjectiveof
thischapteristocreateanexampleAndroidapplicationusingAndroidStudiodesignedto
demonstratetheactualstepsinvolvedinbothcreatingandusingfragments,andalso
implementingcommunicationbetweenonefragmentandanotherwithinanactivity.
25.1AbouttheExampleFragmentApplication
Theapplicationcreatedinthischapterwillconsistofasingleactivityandtwofragments.
Theuserinterfaceforthefirstfragmentwillcontainatoolbarofsortsconsistingofan
EditTextview,aSeekBarandaButton,allcontainedwithinaRelativeLayoutview.The
secondfragmentwillconsistsolelyofaTextViewobject,alsocontainedwithina
RelativeLayoutview.
Thetwofragmentswillbeembeddedwithinthemainactivityoftheapplicationand
communicationimplementedsuchthatwhenthebuttoninthefirstfragmentispressed,the
textenteredintotheEditTextviewwillappearontheTextViewofthesecondfragment
usingafontsizedictatedbythepositionoftheSeekBarinthefirstfragment.
SincethisapplicationisintendedtoworkonearlierversionsofAndroid,itwillalsobe
necessarytomakeuseoftheappropriateAndroidsupportlibrary.
25.2CreatingtheExampleProject
CreateanewprojectinAndroidStudio,enteringFragmentExampleintotheApplication
namefieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingonthe
Nextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedFragmentExampleActivitywithacorresponding
layoutresourcefilenamedactivity_fragment_example.
ClicktheFinishbuttontobegintheprojectcreationprocess.
25.3CreatingtheFirstFragmentLayout
Thenextstepistocreatetheuserinterfaceforthefirstfragmentthatwillbeusedwithin
ouractivity.
Thisuserinterfacewill,ofcourse,resideinanXMLlayoutfilesobeginbynavigatingto
thelayoutfolderlocatedunderapp->resintheProjecttoolwindow.Oncelocated,rightclickonthelayoutentryandselecttheNew->Layoutresourcefilemenuoptionas
illustratedinFigure25-1:
Figure25-1
Intheresultingdialog,namethelayouttoolbar_fragmentandchangetherootelement
fromLinearLayouttoRelativeLayoutbeforeclickingonOKtocreatethenewresource
file.
ThenewresourcefilewillappearwithintheDesignertoolreadytobedesigned.Switch
theDesignertoTextmodeandmodifytheXMLsothatitreadsasoutlinedinthe
followinglistingtoaddthreenewviewelementstothelayout:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<Button
android:id=”@+id/button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_below=”@+id/seekBar1”
android:layout_centerHorizontal=“true”
android:layout_marginTop=“17dp”
android:text=“ChangeText”/>
<EditText
android:id=”@+id/editText1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentTop=“true”
android:layout_centerHorizontal=“true”
android:layout_marginTop=“16dp”
android:ems=“10”
android:inputType=“text”>
<requestFocus/>
</EditText>
<SeekBar
android:id=”@+id/seekBar1”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_alignParentLeft=“true”
android:layout_below=”@+id/editText1”
android:layout_marginTop=“14dp”/>
</RelativeLayout>
Oncethechangeshavebeenmade,switchtheDesignertoolbacktoDesignmode.Select
thebuttonviewandclickonthelightbulbiconfollowedbytheI18Nmessagetodisplay
theExtractResourcedialog.Nametheresourcebutton_textandclickonOKtocreatea
stringresourceforthebutton.
Uponcompletionofthesesteps,theuserinterfacelayoutshouldresemblethatofFigure
25-2:
Figure25-2
Withthelayoutforthefirstfragmentimplemented,thenextstepistocreateaclasstogo
withit.
25.4CreatingtheFirstFragmentClass
Inadditiontoauserinterfacelayout,afragmentalsoneedstohaveaclassassociatedwith
ittodotheactualworkbehindthescenes.Addaclassforthispurposetotheprojectby
unfoldingtheapp->javafolderundertheFragmentExampleprojectintheProjecttool
windowandright-clickingonthepackagenamegiventotheprojectwhenitwascreated
(inthisinstancecom.ebookfrenzy.fragmentexample).Fromtheresultingmenu,selectthe
New->JavaClassoption.IntheresultingCreateNewClassdialog,nametheclass
ToolbarFragmentandclickonOKtocreatethenewclass.
Oncetheclasshasbeencreated,itshould,bydefault,appearintheeditingpanelwhereit
willreadasfollows:
packagecom.ebookfrenzy.fragmentexample;
/**
*Createdby<name>on<date>.
*/
publicclassToolbarFragment{
}
Forthetimebeing,theonlychangestothisclassaretoaddsomeimportdirectivesandto
overridetheonCreateView()methodtomakesurethelayoutfileisinflatedanddisplayed
whenthefragmentisusedwithinanactivity.Theclassdeclarationalsoneedstoindicate
thattheclassextendstheAndroidFragmentclass:
packagecom.ebookfrenzy.fragmentexample;
importandroid.os.Bundle;
importandroid.support.v4.app.Fragment;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
publicclassToolbarFragmentextendsFragment{
@Override
publicViewonCreateView(LayoutInflaterinflater,
ViewGroupcontainer,Bundle
savedInstanceState){
//Inflatethelayoutforthisfragment
Viewview=inflater.inflate(R.layout.toolbar_fragment,
container,false);
returnview;
}
}
Laterinthischapter,morefunctionalitywillbeaddedtothisclass.Beforethat,however,
weneedtocreatethesecondfragment.
25.5CreatingtheSecondFragmentLayout
AddasecondnewAndroidXMLlayoutresourcefiletotheproject,onceagainselectinga
RelativeLayoutastherootelement.Namethelayouttext_fragmentandclickOK.When
thelayoutloadsintotheDesignertool,changetoTextmodeandmodifytheXMLtoadda
TextViewtothefragmentlayoutasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<TextView
android:id=”@+id/textView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerHorizontal=“true”
android:layout_centerVertical=“true”
android:text=“FragmentTwo”
android:textAppearance=”?android:attr/textAppearanceLarge”/>
</RelativeLayout>
OncetheXMLchangeshavebeenmade,switchbacktoDesignmode,selectthe
TextViewcomponentandclickonthelightbulbiconfollowedbytheI18Nmessageto
displaytheExtractResourcedialog.Nametheresourcetext_labelandclickonOKto
createastringresourceforthebutton.Uponcompletionofthesesteps,theuserinterface
layoutforthissecondfragmentshouldresemblethatofFigure25-3:
Figure25-3
Aswiththefirstfragment,thisonewillalsoneedtohaveaclassassociatedwithit.Rightclickonapp->java->com.ebookfrenzy.fragmentexampleintheProjecttoolwindow.
Fromtheresultingmenu,selecttheNew->JavaClassoption.Namethefragment
TextFragmentandclickOKtocreatetheclass.
EditthenewTextFragment.javaclassfileandmodifyittoimplementtheonCreateView()
methodanddesignatetheclassasextendingtheAndroidFragmentclass:
packagecom.ebookfrenzy.fragmentexample;
importandroid.os.Bundle;
importandroid.support.v4.app.Fragment;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
publicclassTextFragmentextendsFragment{
@Override
publicViewonCreateView(LayoutInflaterinflater,
ViewGroupcontainer,
BundlesavedInstanceState){
Viewview=inflater.inflate(R.layout.text_fragment,
container,false);
returnview;
}
}
Nowthatthebasicstructureofthetwofragmentshasbeenimplemented,theyarereadyto
beembeddedintheapplication’smainactivity.
25.6AddingtheFragmentstotheActivity
ThemainactivityfortheapplicationhasassociatedwithitanXMLlayoutfilenamed
activity_fragment_example.xml.Forthepurposesofthisexample,thefragmentswillbe
addedtotheactivityusingthe<fragment>elementwithinthisfile.UsingtheProjecttool
window,navigatetotheapp->res->layoutsectionoftheFragmentExampleprojectand
doubleclickontheactivity_fragment_example.xmlfiletoloaditintotheAndroidStudio
Designertool.
WiththeDesignertoolinDesignmode,selectanddeletethedefaultTextViewobjectfrom
thelayoutandscrolldownthepaletteuntiltheCustomsectioncomesintoview.Clickon
the<fragment>entrytodisplayalistofFragmentsavailablewithinthecurrentprojectas
illustratedinFigure25-4:
Figure25-4
SelecttheToolbarFragmententryfromthelistandclickontheOKbuttontodismissthe
Fragmentsdialog.Movethemousepointertothetopcenteredgeoftheparentlayoutarea
sothatthecenterHorizontalandalignParentTopoptionsaredisplayed(Figure25-5).
Figure25-5
Oncecorrectlypositioned,releasethefragmenttoaddittothelayout.Onceadded,a
messagepanelwillappear(Figure25-6)indicatingthattheDesignertoolneedstoknow
whichfragmenttodisplayduringthepreviewsession.DisplaytheToolbarFragment
fragmentbyclickingontheUse@layout/toolbar_fragmentlinkwithinthemessage:
Figure25-6
Clickonthe<fragment>entryintheCustomsectionofthepaletteonceagain,thistime
selectingtheTextFragmententryfromthefragmentdialogbeforeclickingontheOK
button.MovethemousepointertothecenterofthelayoutsothatthecenterHorizontal
andcenterVerticalpropertiesareactivatedandreleasethefragment.Whentherendering
messageappears,clickontheUse@layout/text_fragmentoption.Notethatthefragments
arenowvisibleinthelayoutasdemonstratedinFigure25-7:
Figure25-7
Beforeproceedingtothenextstep,doubleclickontheTextFragmentinstanceinthe
layoutand,withintheresultingpanel,changetheidofthefragmenttotext_fragment.
25.7MakingtheToolbarFragmentTalktotheActivity
Whentheusertouchesthebuttoninthetoolbarfragment,thefragmentclassisgoingto
needtogetthetextfromtheEditTextviewandthecurrentvalueoftheSeekBarandsend
themtothetextfragment.AsoutlinedinAnIntroductiontoAndroidFragments,
fragmentsshouldnotcommunicatewitheachotherdirectly,insteadusingtheactivityin
whichtheyareembeddedasanintermediary.
Thefirststepinthisprocessistomakesurethatthetoolbarfragmentrespondstothe
buttonbeingclicked.Wealsoneedtoimplementsomecodetokeeptrackofthevalueof
theSeekBarview.Forthepurposesofthisexample,wewillimplementtheselisteners
withintheToolbarFragmentclass.SelecttheToolbarFragment.javafileandmodifyitso
thatitreadsasshowninthefollowinglisting:
packagecom.ebookfrenzy.fragmentexample;
importandroid.os.Bundle;
importandroid.support.v4.app.Fragment;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.content.Context;
importandroid.widget.Button;
importandroid.widget.EditText;
importandroid.widget.SeekBar;
importandroid.widget.SeekBar.OnSeekBarChangeListener;
publicclassToolbarFragmentextendsFragmentimplements
OnSeekBarChangeListener{
privatestaticintseekvalue=10;
privatestaticEditTextedittext;
@Override
publicViewonCreateView(LayoutInflaterinflater,
ViewGroupcontainer,Bundle
savedInstanceState){
//Inflatethelayoutforthisfragment
Viewview=inflater.inflate(R.layout.toolbar_fragment,
container,false);
edittext=(EditText)view.findViewById(R.id.editText1);
finalSeekBarseekbar=
(SeekBar)view.findViewById(R.id.seekBar1);
seekbar.setOnSeekBarChangeListener(this);
finalButtonbutton=
(Button)view.findViewById(R.id.button1);
button.setOnClickListener(newView.OnClickListener(){
publicvoidonClick(Viewv){
buttonClicked(v);
}
});
returnview;
}
publicvoidbuttonClicked(Viewview){
}
@Override
publicvoidonProgressChanged(SeekBarseekBar,intprogress,
booleanfromUser){
seekvalue=progress;
}
@Override
publicvoidonStartTrackingTouch(SeekBararg0){
}
@Override
publicvoidonStopTrackingTouch(SeekBararg0){
}
}
Beforemovingon,weneedtotakesometimetoexplaintheabovecodechanges.First,
theclassisdeclaredasimplementingtheOnSeekBarChangeListenerinterface.Thisis
becausetheuserinterfacecontainsaSeekBarinstanceandthefragmentneedstoreceive
notificationswhentheuserslidesthebartochangethefontsize.Implementationofthe
OnSeekBarChangeListenerinterfacerequiresthattheonProgressChanged(),
onStartTrackingTouch()andonStopTrackingTouch()methodsbeimplemented.These
methodshavebeenimplementedbutonlytheonProgressChanged()methodisactually
requiredtoperformatask,inthiscasestoringthenewvalueinavariablenamed
seekvaluewhichhasbeendeclaredatthestartoftheclass.Alsodeclaredisavariablein
whichtostoreareferencetotheEditTextobject.
TheonCreateView()methodhasbeenmodifiedtoobtainreferencestotheEditText,
SeekBarandButtonviewsinthelayout.Onceareferencetothebuttonhasbeenobtained
itisusedtosetupanonClickListeneronthebuttonwhichisconfiguredtocallamethod
namedbuttonClicked()whenaclickeventisdetected.Thismethodisalsothen
implemented,thoughatthispointitdoesnotdoanything.
Thenextphaseofthisprocessistosetupthelistenerthatwillallowthefragmenttocall
theactivitywhenthebuttonisclicked.Thisfollowsthemechanismoutlinedinthe
previouschapter:
publicclassToolbarFragmentextendsFragmentimplements
OnSeekBarChangeListener{
privatestaticintseekvalue=10;
privatestaticEditTextedittext;
ToolbarListeneractivityCallback;
publicinterfaceToolbarListener{
publicvoidonButtonClick(intposition,Stringtext);
}
@Override
publicvoidonAttach(Contextcontext){
super.onAttach(context);
try{
activityCallback=(ToolbarListener)context;
}catch(ClassCastExceptione){
thrownewClassCastException(context.toString()
+”mustimplementToolbarListener”);
}
}
@Override
publicViewonCreateView(LayoutInflaterinflater,
ViewGroupcontainer,BundlesavedInstanceState){
//Inflatethelayoutforthisfragment
Viewview=
inflater.inflate(R.layout.toolbar_fragment,
container,false);
edittext=(EditText)
view.findViewById(R.id.editText1);
finalSeekBarseekbar=
(SeekBar)view.findViewById(R.id.seekBar1);
seekbar.setOnSeekBarChangeListener(this);
finalButtonbutton=
(Button)view.findViewById(R.id.button1);
button.setOnClickListener(newView.OnClickListener(){
publicvoidonClick(Viewv){
buttonClicked(v);
}
});
returnview;
}
publicvoidbuttonClicked(Viewview){
activityCallback.onButtonClick(seekvalue,
edittext.getText().toString());
}
.
.
.
}
TheaboveimplementationwillresultinamethodnamedonButtonClick()belongingtothe
activityclassbeingcalledwhenthebuttonisclickedbytheuser.Allthatremains,
therefore,istodeclarethattheactivityclassimplementsthenewlycreated
ToolbarListenerinterfaceandtoimplementtheonButtonClick()method.
SincetheAndroidSupportLibraryisbeingusedforfragmentsupportinearlierAndroid
versions,theactivityalsoneedstobechangedtosubclassfromFragmentActivityinstead
ofAppCompatActivity.Bringingtheserequirementstogetherresultsinthefollowing
modifiedFragmentExampleActivity.javafile:
packagecom.ebookfrenzy.fragmentexample;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v4.app.FragmentActivity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.MenuItem;
publicclassFragmentExampleActivityextendsFragmentActivity
implementsToolbarFragment.ToolbarListener{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment_example);
}
publicvoidonButtonClick(intfontsize,Stringtext){
}
.
.
.
}
Withthecodechangesastheycurrentlystand,thetoolbarfragmentwilldetectwhenthe
buttonisclickedbytheuserandcallamethodontheactivitypassingthroughthecontent
oftheEditTextfieldandthecurrentsettingoftheSeekBarview.Itisnowthejobofthe
activitytocommunicatewiththeTextFragmentandtopassalongthesevaluessothatthe
fragmentcanupdatetheTextViewobjectaccordingly.
25.8MakingtheActivityTalktotheTextFragment
AsoutlinedinAnIntroductiontoAndroidFragments,anactivitycancommunicatewitha
fragmentbyobtainingareferencetothefragmentclassinstanceandthencallingpublic
methodsontheobject.Assuch,withintheTextFragmentclasswewillnowimplementa
publicmethodnamedchangeTextProperties()whichtakesasargumentsanintegerforthe
fontsizeandastringforthenewtexttobedisplayed.Themethodwillthenusethese
valuestomodifytheTextViewobject.WithintheAndroidStudioeditingpanel,locateand
modifytheTextFragment.javafiletoaddthisnewmethodandtoaddcodetothe
onCreateView()methodtoobtaintheIDoftheTextViewobject:
packagecom.ebookfrenzy.fragmentexample;
importandroid.os.Bundle;
importandroid.support.v4.app.Fragment;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.TextView;
publicclassTextFragmentextendsFragment{
privatestaticTextViewtextview;
@Override
publicViewonCreateView(LayoutInflaterinflater,
ViewGroupcontainer,
BundlesavedInstanceState){
Viewview=inflater.inflate(R.layout.text_fragment,
container,false);
textview=(TextView)view.findViewById(R.id.textView1);
returnview;
}
publicvoidchangeTextProperties(intfontsize,Stringtext)
{
textview.setTextSize(fontsize);
textview.setText(text);
}
}
WhentheTextFragmentfragmentwasplacedinthelayoutoftheactivity,itwasgivenan
IDoftext_fragment.UsingthisID,itisnowpossiblefortheactivitytoobtainareference
tothefragmentinstanceandcallthechangeTextProperties()methodontheobject.Edit
theFragmentExampleActivity.javafileandmodifytheonButtonClick()methodasfollows:
publicvoidonButtonClick(intfontsize,Stringtext){
TextFragmenttextFragment=
(TextFragment)
getSupportFragmentManager().findFragmentById(R.id.text_fragment);
textFragment.changeTextProperties(fontsize,text);
}
25.9TestingtheApplication
Withthecodingforthisprojectnowcomplete,thelastremainingtaskistorunthe
application.Whentheapplicationislaunched,themainactivitywillstartandwill,inturn,
createanddisplaythetwofragments.Whentheusertouchesthebuttoninthetoolbar
fragment,theonButtonClick()methodoftheactivitywillbecalledbythetoolbar
fragmentandpassedthetextfromtheEditTextviewandthecurrentvalueoftheSeekBar.
TheactivitywillthencallthechangeTextProperties()methodofthesecondfragment,
whichwillmodifytheTextViewtoreflectthenewtextandfontsize:
Figure25-8
25.10Summary
Thegoalofthischapterhasbeentoworkthroughthecreationofanexampleproject
intendedspecificallytodemonstratethestepsinvolvedinusingfragmentswithinan
Androidapplication.TopicscoveredincludedtheuseoftheAndroidSupportLibraryfor
compatibilitywithAndroidversionspredatingtheintroductionoffragments,theinclusion
offragmentswithinanactivitylayoutandtheimplementationofinter-fragment
communication.
26.CreatingandManagingOverflowMenus
onAndroid
Anareaofuserinterfacedesignthathasnotyetbeencoveredinthisbookrelatestothe
conceptofmenuswithinanAndroidapplication.Menusprovideamechanismforoffering
additionalchoicestotheuserbeyondtheviewcomponentsthatarepresentintheuser
interfacelayout.Whilethereareanumberofdifferentmenusystemsavailabletothe
Androidapplicationdeveloper,thischapterwillfocusonthemorecommonlyused
Overflowmenu.
26.1TheOverflowMenu
Theoverflowmenu(alsoreferredtoastheoptionsmenu)isamenuthatisaccessibleto
theuserfromthedevicedisplayandallowsthedevelopertoincludeotherapplication
optionsbeyondthoseincludedintheuserinterfaceoftheapplication.Thelocationofthe
overflowmenuisdependentupontheversionofAndroidthatisrunningonthedevice.On
adevicerunningAndroid2.3.3,forexample,theoverflowmenuisrepresentedbythe
menuiconlocatedinthecenter(betweenthebackandsearchbuttons)ofthebottomsoft
keytoolbarasillustratedinFigure26-1:
Figure26-1
WiththeAndroid4.0releaseandlater,ontheotherhand,theoverflowmenubuttonis
locatedinthetoprighthandcorner(Figure26-2)intheactiontoolbarrepresentedbythe
stackofthreesquares:
Figure26-2
26.2CreatinganOverflowMenu
TheitemsinamenucanbedeclaredwithinanXMLfile,whichistheninflatedand
displayedtotheuserondemand.Thisinvolvestheuseofthe<menu>element,containing
an<item>sub-elementforeachmenuitem.ThefollowingXML,forexample,definesa
menuconsistingoftwomenuitemsrelatingtocolorchoices:
<menuxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
tools:context=
“.MenuExampleActivity”>
<item
android:id=”@+id/menu_red”
android:orderInCategory=“1”
app:showAsAction=“never”
android:title=”@string/red_string”/>
<item
android:id=”@+id/menu_green”
android:orderInCategory=“2”
app:showAsAction=“never”
android:title=”@string/green_string”/>
</menu>
IntheaboveXML,theandroid:orderInCategorypropertydictatestheorderinwhichthe
menuitemswillappearwithinthemenuwhenitisdisplayed.Theapp:showAsAction
property,ontheotherhand,controlstheconditionsunderwhichthecorrespondingitem
appearsasanitemwithintheactionbaritself.IfsettoifRoom,forexample,theitemwill
appearintheactionbarifthereisenoughroom.Figure26-3showstheeffectofsetting
thispropertytoifRoomforbothmenuitems:
Figure26-3
Thispropertyshouldbeusedsparinglytoavoidoverclutteringtheactionbar.
Bydefault,amenuXMLfileiscreatedbyAndroidStudiowhenanewAndroid
applicationprojectiscreated.Thisfileislocatedintheapp->res->menuprojectfolder
andcontainsasinglemenuitementitled“Settings”:
<menuxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
tools:context=”.MainActivity”>
<itemandroid:id=”@+id/action_settings”
android:title=”@string/action_settings”
android:orderInCategory=“100”
app:showAsAction=“never”/>
</menu>
Thismenuisalreadyconfiguredtobedisplayedwhentheuserselectstheoverflowmenu
ontheuserinterfacewhentheappisrunning,sosimplymodifythisonetomeetyour
needs.
26.3DisplayinganOverflowMenu
AnoverflowmenuiscreatedbyoverridingtheonCreateOptionsMenu()methodofthe
correspondingactivityandtheninflatingthemenu’sXMLfile.Forexample,thefollowing
codecreatesthemenucontainedwithinamenuXMLfilenamedmenu_menu_example:
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_menu_example,menu);
returntrue;
}
AswiththemenuXMLfile,AndroidStudiowillalreadyhaveoverriddenthismethodin
themainactivityofanewlycreatedAndroidapplicationproject.Intheeventthatan
overflowmenuisnotrequiredinyouractivity,eitherremoveorcommentoutthismethod.
26.4RespondingtoMenuItemSelections
Onceamenuhasbeenimplemented,thequestionarisesastohowtheapplicationreceives
notificationwhentheusermakesmenuitemselections.Allthatanactivityneedstodoto
receivemenuselectionnotificationsistooverridetheonOptionsItemSelected()method.
Passedasanargumenttothismethodisareferencetotheselectedmenuitem.The
getItemId()methodmaythenbecalledontheitemtoobtaintheIDwhichmay,inturn,be
usedtoidentifywhichitemwasselected.Forexample:
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
switch(item.getItemId()){
caseR.id.menu_red:
//Reditemwasselected
returntrue;
caseR.id.menu_green:
//Greenitemwasselected
returntrue;
default:
returnsuper.onOptionsItemSelected(item);
}
}
26.5CreatingCheckableItemGroups
Inadditiontoconfiguringindependentmenuitems,itisalsopossibletocreategroupsof
menuitems.Thisisofparticularusewhencreatingcheckablemenuitemswherebyonly
oneoutofanumberofchoicescanbeselectedatanyonetime.Menuitemscanbe
assignedtoagroupbywrappingtheminthe<group>tag.Thegroupisdeclaredas
checkableusingtheandroid:checkableBehaviorproperty,settingthevaluetoeithersingle,
allornone.ThefollowingXMLdeclaresthattwomenuitemsmakeupagroupwherein
onlyoneitemmaybeselectedatanygiventime:
<menuxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”>
<groupandroid:checkableBehavior=“single”>
<item
android:id=”@+id/menu_red”
android:orderInCategory=“1”
app:showAsAction=“never”
android:title=”@string/red_string”/>
<item
android:id=”@+id/menu_green”
android:orderInCategory=“2”
app:showAsAction=“never”
android:title=”@string/green_string”/>
</group>
</menu>
Whenamenugroupisconfiguredtobecheckable,asmallcircleappearsnexttotheitem
inthemenuasillustratedinFigure26-4.Itisimportanttobeawarethatthesettingand
unsettingofthisindicatordoesnottakeplaceautomatically.Itis,therefore,the
responsibilityoftheapplicationtocheckanduncheckthemenuitem.
Figure26-4
Continuingthecolorexampleusedpreviouslyinthischapter,thiswouldbeimplemented
asfollows:
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
switch(item.getItemId()){
caseR.id.menu_red:
if(item.isChecked())item.setChecked(false);
elseitem.setChecked(true);
returntrue;
caseR.id.menu_green:
if(item.isChecked())item.setChecked(false);
elseitem.setChecked(true);
returntrue;
default:
returnsuper.onOptionsItemSelected(item);
}
}
26.6CreatingtheExampleProject
Toseetheoverflowmenuinaction,createanewprojectinAndroidStudio,entering
MenuExampleintotheApplicationnamefieldandebookfrenzy.comastheCompany
DomainsettingbeforeclickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofablankactivitynamedMenuExampleActivitywithcorrespondinglayout
andmenuresourcefilesnamedactivity_menu_exampleandmenu_menu_example.
Whentheprojecthasbeencreated,navigatetotheapp->res->layoutfolderinthe
Projecttoolwindowanddoubleclickonthecontent_menu_example.xmlfiletoloaditinto
theAndroidStudioDesignertool.SwitchthetooltoDesignmode,doubleclickonthe
backgroundofthelayout(thearearepresentingtheRelativeLayoutview)andenter
layoutViewintotheidfieldofthepopuppanel.
26.7ModifyingtheMenuDescription
WithintheProjecttoolwindow,locatetheproject’sapp->res->menu->
menu_menu_example.xmlfileanddoubleclickonittoloaditintotheeditingpanel.
DeletethedefaultSettingsmenuitemaddedbyAndroidStudioandthenaddnewitemsas
acheckablegroupsothattheXMLfileisstructuredasfollows:
<menuxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
tools:context=”.MenuExampleActivity”>
<groupandroid:checkableBehavior=“single”>
<item
android:id=”@+id/menu_red”
android:orderInCategory=“1”
app:showAsAction=“never”
android:title=”@string/red_string”/>
<item
android:id=”@+id/menu_green”
android:orderInCategory=“2”
app:showAsAction=“never”
android:title=”@string/green_string”/>
<item
android:id=”@+id/menu_yellow”
android:orderInCategory=“3”
app:showAsAction=“never”
android:title=”@string/yellow_string”/>
<item
android:id=”@+id/menu_blue”
android:orderInCategory=“4”
app:showAsAction=“never”
android:title=”@string/blue_string”/>
</group>
</menu>
Locateanddoubleclickontheapp->res->values->strings.xmlfile.Withinthefile,
addnewstringresourcesforthecolornamesasreferencedbythemenuitems:
<?xmlversion=“1.0”encoding=“utf-8”?>
<resources>
<stringname=“app_name”>MenuExample</string>
<stringname=“hello_world”>Helloworld!</string>
<stringname=“menu_settings”>Settings</string>
<stringname=“red_string”>Red</string>
<stringname=“green_string”>Green</string>
<stringname=“yellow_string”>Yellow</string>
<stringname=“blue_string”>Blue</string>
</resources>
26.8ModifyingtheonOptionsItemSelected()Method
Whenitemsareselectedfromthemenu,theoverriddenonOptionsItemsSelected()method
oftheapplication’sactivitywillbecalled.Theroleofthismethodwillbetoidentify
whichitemwasselectedandchangethebackgroundcolorofthelayoutviewtothe
correspondingcolor.Locateanddoubleclickontheapp->java->
com.ebookfrenzy.menuexample->MenuExampleActivityfileandmodifythemethodas
follows:
packagecom.ebookfrenzy.menuexample;
importandroid.os.Bundle;
importandroid.support.design.widget.FloatingActionButton;
importandroid.support.design.widget.Snackbar;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.View;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.widget.RelativeLayout;
publicclassMenuExampleActivityextendsAppCompatActivity{
.
.
.
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
RelativeLayoutmainLayout=
(RelativeLayout)findViewById(R.id.layoutView);
switch(item.getItemId()){
caseR.id.menu_red:
if(item.isChecked())item.setChecked(false);
elseitem.setChecked(true);
mainLayout.setBackgroundColor(android.graphics.Color.RED);
returntrue;
caseR.id.menu_green:
if(item.isChecked())item.setChecked(false);
elseitem.setChecked(true);
mainLayout.setBackgroundColor(android.graphics.Color.GREEN);
returntrue;
caseR.id.menu_yellow:
if(item.isChecked())item.setChecked(false);
elseitem.setChecked(true);
mainLayout.setBackgroundColor(android.graphics.Color.YELLOW);
returntrue;
caseR.id.menu_blue:
if(item.isChecked())item.setChecked(false);
elseitem.setChecked(true);
mainLayout.setBackgroundColor(android.graphics.Color.BLUE);
returntrue;
default:
returnsuper.onOptionsItemSelected(item);
}
}
.
.
}
26.9TestingtheApplication
BuildandruntheapplicationoneitheranemulatororphysicalAndroiddevice.Usingthe
overflowmenu,selectmenuitemsandverifythatthelayoutbackgroundcolorchanges
appropriately.Notethatthecurrentlyselectedcolorisdisplayedasthecheckediteminthe
menu.
Figure26-5
26.10Summary
OnearlierversionsofAndroid,theoverflowmenuisaccessiblefromthesoftkeytoolbar
atthebottomofthescreen.OnAndroid4.0andlater,themenuisaccessedfromthefar
rightoftheactionstoolbaratthetopofthedisplay.Thismenuprovidesalocationfor
applicationstoprovideadditionaloptionstotheuser.
ThestructureofthemenuismosteasilydefinedwithinanXMLfileandtheapplication
activityreceivesnotificationsofmenuitemselectionsbyoverridingandimplementingthe
onOptionsItemSelected()method.
27.AnimatingUserInterfaceswiththe
AndroidTransitionsFramework
TheAndroidTransitionsframeworkwasintroducedaspartoftheAndroid4.4KitKat
releaseandisdesignedtomakeiteasyforyou,asanAndroiddeveloper,toaddanimation
effectstotheviewsthatmakeupthescreensofyourapplications.Aswillbeoutlinedin
boththisandsubsequentchapters,animatedeffectssuchasmakingtheviewsinauser
interfacegentlyfadeinandoutofsightandglidesmoothlytonewpositionsonthescreen
canbeimplementedwithjustafewsimplelinesofcodewhenusingtheTransitions
frameworkinAndroidStudio.
27.1IntroducingAndroidTransitionsandScenes
Transitionsallowthechangesmadetothelayoutandappearanceoftheviewsinauser
interfacetobeanimatedduringapplicationruntime.Whilethereareanumberofdifferent
waystoimplementTransitionsfromwithinapplicationcode,perhapsthemostpowerful
mechanisminvolvestheuseofScenes.Ascenerepresentseithertheentirelayoutofauser
interfacescreen,orasubsetofthelayout(representedbyaViewGroup).
Toimplementtransitionsusingthisapproach,scenesaredefinedthatreflectthetwo
differentuserinterfacestates(thesecanbethoughtofasthe“before”and“after”scenes).
Onescene,forexample,mightconsistofaTextEdit,ButtonandTextViewpositionednear
thetopofthescreen.ThesecondscenemightremovetheButtonviewandmovethe
remainingTextEditandTextViewobjectstothebottomofthescreentomakeroomforthe
introductionofaMapViewinstance.Usingthetransitionframework,thechangesbetween
thesetwoscenescanbeanimatedsothattheButtonfadesfromview,theTextEditand
TextViewslidetothenewlocationsandthemapgentlyfadesintoview.
ScenescanbecreatedincodefromViewGroups,orimplementedinlayoutresourcefiles
thatareloadedintoSceneinstancesatapplicationruntime.
Transitionscanalsobeimplementeddynamicallyfromwithinapplicationcode.Usingthis
approach,scenesarecreatedbyreferencingcollectionsofuserinterfaceviewsintheform
ofViewGroupswithtransitionsthenbeingperformedonthoseelementsusingthe
TransitionManagerclass,whichprovidesarangeofmethodsfortriggeringandmanaging
thetransitionsbetweenscenes.
PerhapsthesimplestformoftransitioninvolvestheuseofthebeginDelayedTransition()
methodoftheTransitionManagerclass.WhencalledandpassedtheViewGroup
representingascene,anysubsequentchangestoanyviewswithinthatscene(suchas
moving,resizing,addingordeletingviews)willbeanimatedbytheTransitionframework.
TheactualanimationishandledbytheTransitionframeworkviainstancesofthe
Transitionclass.Transitioninstancesareresponsiblefordetectingchangestothesize,
positionandvisibilityoftheviewswithinasceneandanimatingthosechanges
accordingly.
Bydefault,transitionswillbeanimatedusingasetofcriteriadefinedbythe
AutoTransitionclass.CustomtransitionscanbecreatedeitherviasettingsinXML
transitionfilesordirectlywithincode.Multipletransitionscanbecombinedtogetherina
TransitionSetandconfiguredtobeperformedeitherinparallelorsequentially.
27.2UsingInterpolatorswithTransitions
TheTransitionsframeworkmakesextensiveuseoftheAndroidAnimationframeworkto
implementanimationeffects.Thisfactislargelyincidentalwhenusingtransitionssince
mostofthisworkhappensbehindthescenes,therebyshieldingthedeveloperfromsome
ofthecomplexitiesoftheAnimationframework.Oneareawheresomeknowledgeofthe
AnimationframeworkisbeneficialwhenusingTransitions,however,involvestheconcept
ofinterpolators.
InterpolatorsareafeatureoftheAndroidAnimationframeworkthatallowanimationsto
bemodifiedinanumberofpre-definedways.AtpresenttheAnimationframework
providesthefollowinginterpolators,allofwhichareavailableforuseincustomizing
transitions:
· AccelerateDecelerateInterpolator–Bydefault,animationisperformedataconstant
rate.TheAccelerateDecelerateInterpolatorcanbeusedtocausetheanimationtobegin
slowlyandthenspeedupinthemiddlebeforeslowingdowntowardstheendofthe
sequence.
· AccelerateInterpolator–Asthenamesuggests,theAccelerateInterpolatorbeginsthe
animationslowlyandacceleratesataspecifiedratewithnodecelerationattheend.
· AnticipateInterpolator–TheAnticipateInterpolatorprovidesaneffectsimilartothat
ofaslingshot.Theanimatedviewmovesintheoppositedirectiontotheconfigured
animationforashortdistancebeforebeingflungforwardinthecorrectdirection.The
amountofbackwardforcecanbecontrolledthroughthespecificationofatension
value.
· AnticipateOvershootInterpolator–Combinestheeffectprovidedbythe
AnticipateInterpolatorwiththeanimatedobjectovershootingandthenreturningtothe
destinationpositiononthescreen.
· BounceInterpolator–Causestheanimatedviewtobounceonarrivalatitsdestination
position.
· CycleInterpolator–Configurestheanimationtoberepeatedaspecifiednumberof
times.
· DecelerateInterpolator–TheDecelerateInterpolatorcausestheanimationtobegin
quicklyandthendeceleratebyaspecifiedfactorasitnearstheend.
· LinearInterpolator–Usedtospecifythattheanimationistobeperformedata
constantrate.
· OvershootInterpolator–Causestheanimatedviewtoovershootthespecified
destinationpositionbeforereturning.Theovershootcanbeconfiguredbyspecifyinga
tensionvalue.
Aswillbedemonstratedinthisandlaterchapters,interpolatorscanbespecifiedbothin
codeandXMLfiles.
27.3WorkingwithSceneTransitions
ScenescanberepresentedbythecontentofanAndroidStudioXMLlayoutfile.The
followingXML,forexample,couldbeusedtorepresentasceneconsistingofthreebutton
viewswithinaRelativeLayoutparent:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:id=”@+id/RelativeLayout1”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“vertical”>
<Button
android:id=”@+id/button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentLeft=“true”
android:layout_alignParentTop=“true”
android:onClick=“goToScene2”
android:text=”@string/one_string”/>
<Button
android:id=”@+id/button2”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentRight=“true”
android:layout_alignParentTop=“true”
android:onClick=“goToScene1”
android:text=”@string/two_string”/>
<Button
android:id=”@+id/button3”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerHorizontal=“true”
android:layout_centerVertical=“true”
android:text=”@string/three_string”/>
</RelativeLayout>
Assumingthattheabovelayoutresidesinafilenamedscene1_layout.xmllocatedinthe
res/layoutfolderoftheproject,thelayoutcanbeloadedintoasceneusingthe
getSceneForLayout()methodoftheSceneclass.Forexample:
Scenescene1=Scene.getSceneForLayout(rootContainer,
R.layout.scene1_layout,this);
Notethatthemethodcallrequiresareferencetotherootcontainer.Thisistheviewatthe
topoftheviewhierarchyinwhichthesceneistobedisplayed.
Todisplayascenetotheuserwithoutanytransitionanimation,theenter()methodis
calledonthesceneinstance:
scene1.enter();
TransitionsbetweentwoscenesusingthedefaultAutoTransitionclasscanbetriggered
usingthego()methodoftheTransitionManagerclass:
TransitionManager.go(scene2);
Sceneinstancescanbecreatedeasilyincodebybundlingtheviewelementsintooneor
moreViewGroupsandthencreatingascenefromthosegroups.Forexample:
Scenescene1=Scene(viewGroup1);
Scenescene2=Scene(viewGroup2,viewGroup3);
27.4CustomTransitionsandTransitionSetsinCode
Theexamplesoutlinedsofarinthischapterhaveusedthedefaulttransitionsettingsin
whichresizing,fadingandmotionareanimatedusingpre-configuredbehavior.Thesecan
bemodifiedbycreatingcustomtransitionswhicharethenreferencedduringthetransition
process.Animationsarecategorizedaseitherchangebounds(relatingtochangesinthe
positionandsizeofaview)andfade(relatingtothevisibilityorotherwiseofaview).
AsingleTransitioncanbecreatedasfollows:
TransitionmyChangeBounds=newChangeBounds();
Thisnewtransitioncanthenbeusedwhenperformingatransition:
TransitionManager.go(scene2,myChangeBounds);
MultipletransitionsmaybebundledtogetherintoaTransitionSetinstance.Thefollowing
code,forexample,createsanewTransitionSetobjectconsistingofbothchangebounds
andfadetransitioneffects:
TransitionSetmyTransition=newTransitionSet();
myTransition.addTransition(newChangeBounds());
myTransition.addTransition(newFade());
Transitionscanbeconfiguredtotargetspecificviews(referencedbyviewID).For
example,thefollowingcodewillconfigurethepreviousfadetransitiontotargetonlythe
viewwithanIDthatmatchesmyButton1:
TransitionSetmyTransition=newTransitionSet();
myTransition.addTransition(newChangeBounds());
Transitionfade=newFade();
fade.addTarget(R.id.myButton1);
myTransition.addTransition(fade);
Additionalaspectsofthetransitionmayalsobecustomized,suchasthedurationofthe
animation.Thefollowingcodespecifiesthedurationoverwhichtheanimationistobe
performed:
TransitionchangeBounds=newChangeBounds();
changeBounds.setDuration(2000);
AswithTransitioninstances,onceaTransitionSetinstancehasbeencreated,itcanbe
usedinatransitionviatheTransitionManagerclass.Forexample:
TransitionManager.go(scene1,myTransition);
27.5CustomTransitionsandTransitionSetsinXML
Whilecustomtransitionscanbeimplementedincode,itisofteneasiertodosoviaXML
transitionfilesusingthe<fade>and<changeBounds>tagstogetherwithsomeadditional
options.ThefollowingXMLincludesasinglechangeBoundstransition:
<?xmlversion=“1.0”encoding=“utf-8”?>
<changeBounds/>
Aswiththecodebasedapproachtoworkingwithtransitions,eachtransitionentryina
resourcefilemaybecustomized.TheXMLbelow,forexample,configuresadurationfor
achangeboundstransition:
<changeBoundsandroid:duration=“5000”>
Multipletransitionsmaybebundledtogetherusingthe<transitionSet>element:
<?xmlversion=“1.0”encoding=“utf-8”?>
<transitionSet
xmlns:android=“http://schemas.android.com/apk/res/android”>
<fade
android:duration=“2000”
android:fadingMode=“fade_out”/>
<changeBounds
android:duration=“5000”>
<targets>
<targetandroid:targetId=”@id/button2”/>
</targets>
</changeBounds>
<fade
android:duration=“2000”
android:fadingMode=“fade_in”/>
</transitionSet>
TransitionscontainedwithinanXMLresourcefileshouldbestoredintheres/transition
folderoftheprojectinwhichtheyarebeingusedandmustbeinflatedbeforebeing
referencedinthecodeofanapplication.Thefollowingcode,forexample,inflatesthe
transitionresourcescontainedwithinafilenamedtransition.xmlandassignstheresultsto
areferencenamedmyTransition:
TransitionmyTransition=TransitionInflater.from(this)
.inflateTransition(R.transition.transition);
Onceinflated,thenewtransitioncanbereferencedintheusualway:
TransitionManager.go(scene1,myTransition);
Bydefault,transitioneffectswithinaTransitionSetareperformedinparallel.Toinstruct
theTransitionframeworktoperformtheanimationssequentially,addtheappropriate
android:transitionOrderingpropertytothetransitionSetelementoftheresourcefile:
<?xmlversion=“1.0”encoding=“utf-8”?>
<transitionSet
xmlns:android=“http://schemas.android.com/apk/res/android”
android:transitionOrdering=“sequential”>
<fade
android:duration=“2000”
android:fadingMode=“fade_out”/>
<changeBounds
android:duration=“5000”>
</changeBounds>
</transitionSet>
Changethevaluefrom“sequential”to“together”toindicatethattheanimationsequences
aretobeperformedinparallel.
27.6WorkingwithInterpolators
Aspreviouslydiscussed,interpolatorscanbeusedtomodifythebehaviorofatransitionin
avarietyofwaysandmaybespecifiedeitherincodeorviathesettingswithinatransition
XMLresourcefile.
Whenworkingincode,newinterpolatorinstancescanbecreatedbycallingthe
constructormethodoftherequiredinterpolatorclassand,whereappropriate,passing
throughvaluestofurthermodifytheinterpolatorbehavior:
· AccelerateDecelerateInterpolator()
· AccelerateInterpolator(floatfactor)
· AnticipateInterpolator(floattension)
· AnticipateOvershootInterpolator(floattension)
· BounceInterpolator()
· CycleInterpolator(floatcycles)
· DecelerateInterpolator(floatfactor)
· LinearInterpolator()
· OvershootInterpolator(floattension)
Oncecreated,aninterpolatorinstancecanbeattachedtoatransitionusingthe
setInterpolator()methodoftheTransitionclass.Thefollowingcode,forexample,addsa
bounceinterpolatortoachangeboundstransition:
TransitionchangeBounds=newChangeBounds();
changeBounds.setInterpolator(newBounceInterpolator());
Similarly,thefollowingcodeaddsanaccelerateinterpolatortothesametransition,
specifyinganaccelerationfactorof1.2:
changeBounds.setInterpolator(newAccelerateInterpolator(1.2f));
InthecaseofXMLbasedtransitionresources,adefaultinterpolatorisdeclaredusingthe
followingsyntax:
android:interpolator=”@android:anim/<interpolator_element>”
Intheabovesyntax,<interpolator_element>mustbereplacedbytheresourceIDofthe
correspondinginterpolatorselectedfromthefollowinglist:
· accelerate_decelerate_interpolator
· accelerate_interpolator
· anticipate_interpolator
· anticipate_overshoot_interpolator
· bounce_interpolator
· cycle_interpolator
· decelerate_interpolator
· linear_interpolator
· overshoot_interpolator
ThefollowingXMLfragment,forexample,addsabounceinterpolatortoachangebounds
transitioncontainedwithinatransitionset:
<?xmlversion=“1.0”encoding=“utf-8”?>
<transitionSet
xmlns:android=“http://schemas.android.com/apk/res/android”
android:transitionOrdering=“sequential”>
<changeBounds
android:interpolator=”@android:anim/bounce_interpolator”
android:duration=“2000”/>
<fade
android:duration=“1000”
android:fadingMode=“fade_in”/>
</transitionSet>
ThisapproachtoaddinginterpolatorstotransitionswithinXMLresourcesworkswell
whenthedefaultbehavioroftheinterpolatorisrequired.Thetaskbecomesalittlemore
complexwhenthedefaultbehaviorofaninterpolatorneedstobechanged.Take,forthe
sakeofanexample,thecycleinterpolator.Thepurposeofthisinterpolatoristomakean
animationortransitionrepeataspecifiednumberoftimes.Intheabsenceofacycles
attributesetting,thecycleinterpolatorwillperformonlyonecycle.Unfortunatelythereis
nowaytodirectlyspecifythenumberofcycles(oranyotherinterpolatorattributeforthat
matter)whenaddinganinterpolatorusingtheabovetechnique.Instead,acustom
interpolatormustbecreatedandthenreferencedwithinthetransitionfile.
27.7CreatingaCustomInterpolator
AcustominterpolatormustbedeclaredinaseparateXMLfileandstoredwithinthe
res/animfolderoftheproject.ThenameoftheXMLfilewillbeusedbytheAndroid
systemastheresourceIDforthecustominterpolator.
WithinthecustominterpolatorXMLresourcefile,thesyntaxshouldreadasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<interpolatorElement
xmlns:android=“http://schemas.android.com/apk/res/android”
android:attribute=”value”/>
Intheabovesyntax,interpolatorElementmustbereplacedwiththeelementnameofthe
requiredinterpolatorselectedfromthefollowinglist:
· accelerateDecelerateInterpolator
· accelerateInterpolator
· anticipateInterpolator
· anticipateOvershootInterpolator
· bounceInterpolator
· cycleInterpolator
· decelerateInterpolator
· linearInterpolator
· overshootInterpolator
Theattributekeywordisreplacedbythenameattributeoftheinterpolatorforwhichthe
valueistobechanged(forexampletensiontochangethetensionattributeofanovershoot
interpolator).Finally,valuerepresentsthevaluetobeassignedtothespecifiedattribute.
ThefollowingXML,forexample,containsacustomcycleinterpolatorconfiguredto
cycle7times:
<?xmlversion=“1.0”encoding=“utf-8”?>
<cycleInterpolator
xmlns:android=“http://schemas.android.com/apk/res/android”
android:cycles=“7”/>
AssumingthattheaboveXMLwasstoredinaresourcefilenamedmy_cycle.xmlstoredin
theres/animprojectfolder,thecustominterpolatorcouldbeaddedtoatransitionresource
fileusingthefollowingXMLsyntax:
<changeBounds
xmlns:android=“http://schemas.android.com/apk/res/android”
android:duration=“5000”
android:interpolator=”@anim/my_cycle”>
27.8UsingthebeginDelayedTransitionMethod
PerhapsthesimplestformofTransitionbaseduserinterfaceanimationinvolvestheuseof
thebeginDelayedTransition()methodoftheTransitionManagerclass.Thismethodis
passedareferencetotherootviewoftheviewgrouprepresentingthesceneforwhich
animationisrequired.Subsequentchangestotheviewswithinthatsubviewwillthenbe
animatedusingthedefaulttransitionsettings:
myLayout=(ViewGroup)findViewById(R.id.myLayout);
TransitionManager.beginDelayedTransition(myLayout);
//Makechangestothescene
Ifbehaviorotherthanthedefaultanimationbehaviorisrequired,simplypassasuitably
configuredTransitionorTransitionSetinstancethroughtothemethodcall:
TransitionManager.beginDelayedTransition(myLayout,myTransition);
27.9Summary
TheAndroid4.4KitKatSDKreleaseintroducedtheTransitionFramework,thepurposeof
whichistosimplifythetaskofaddinganimationtotheviewsthatmakeuptheuser
interfaceofanAndroidapplication.Withsomesimpleconfigurationandafewlinesof
code,animationeffectssuchasmovement,visibilityandresizingofviewscanbe
animatedbymakinguseoftheTransitionframework.Anumberofdifferentapproachesto
implementingtransitionsareavailableinvolvingacombinationofJavacodeandXML
resourcefiles.Theanimationeffectsoftransitionsmayalsobeenhancedthroughtheuse
ofarangeofinterpolators.
HavingcoveredsomeofthetheoryofTransitionsinAndroid,thenexttwochapterswill
putthistheoryintopracticebyworkingthroughsomeexampleAndroidStudiobased
transitionimplementations.
28.AnAndroidTransitionTutorialusing
beginDelayedTransition
Thepreviouschapter,entitledAnimatingUserInterfaceswiththeAndroidTransitions
Framework,providedanintroductiontotheanimationofuserinterfacesusingthe
AndroidTransitionsframework.Thischapterusesatutorialbasedapproachto
demonstrateAndroidtransitionsinactionusingthebeginDelayedTransition()methodof
theTransitionManagerclass.
Thenextchapterwillcreateamorecomplexexamplethatuseslayoutfilesandtransition
resourcefilestoanimatethetransitionfromonescenetoanotherwithinanapplication.
28.1CreatingtheAndroidStudioTransitionDemoProject
CreateanewprojectinAndroidStudio,enteringTransitionDemointotheApplication
namefieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingonthe
Nextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI19:Android4.4(KitKat).Continuetoproceedthroughthescreens,
requestingthecreationofanemptyactivitynamedTransitionDemoActivitywithalayout
resourcefilenamedactivity_transition_demo.
28.2PreparingtheProjectFiles
Thefirstexampletransitionanimationwillbeimplementedthroughtheuseofthe
beginDelayedTransition()methodoftheTransitionManagerclass.Thefirststep,however,
istoassignanIDtotheparentlayoutviewelementintheuserinterfacelayoutcreatedfor
usbyAndroidStudio.IfAndroidStudiodoesnotautomaticallyloadthefile,locateand
doubleclickontheapp->res->layout->activity_transition_demo.xmlfileinthe
ProjecttoolwindowpaneltoloaditintotheDesignertool.Onceloaded,switchtoDesign
modeanddoubleclickonthebackgroundoftheviewcanvasthatrepresentstheparent
RelativeLayoutcontainerand,intheresultingpanel,namethelayoutviewmyLayoutand
pressEntertocommitthechange.
SelecttheTextViewobjectthatcurrentlydisplaystheHelloworld!textandpressthe
keyboarddeletekeytoremoveitfromthelayout.DragaButtonfromtheWidgetsection
oftheDesignerpaletteandpositionitinthetoplefthandcornerofthedevicescreen
layout.Oncepositioned,doubleclickonitandspecifyanIDofmyButton1.
28.3ImplementingbeginDelayedTransitionAnimation
Theobjectivefortheinitialphaseofthistutorialistoimplementatouchhandlersothat
whentheusertapsonthelayoutviewthebuttonviewmovestothelowerrighthand
cornerofthescreenandincreasesinsize.
LocatetheTransitionDemoActivity.javafile(locatedintheProjecttoolwindowunderapp
->java->com.ebookfrenzy.transitiondemo)andmodifytheonCreate()methodto
implementtheonTouchhandler:
packagecom.ebookfrenzy.transitiondemo;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.RelativeLayout;
publicclassTransitionDemoActivityextendsAppCompatActivity{
ViewGroupmyLayout;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transition_demo);
myLayout=(ViewGroup)findViewById(R.id.myLayout);
myLayout.setOnTouchListener(
newRelativeLayout.OnTouchListener(){
publicbooleanonTouch(Viewv,
MotionEventm){
handleTouch();
returntrue;
}
}
);
}
.
.
.
}
TheabovecodesimplysetsupatouchlistenerontheRelativeLayoutcontainerand
configuresittocallamethodnamedhandleTouch()whenatouchisdetected.Thenext
task,therefore,istoimplementthehandleTouch()methodasfollows:
publicvoidhandleTouch(){
Viewview=findViewById(R.id.myButton1);
RelativeLayout.LayoutParamsparams=new
RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,
RelativeLayout.TRUE);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM,
RelativeLayout.TRUE);
view.setLayoutParams(params);
ViewGroup.LayoutParamslparams=view.getLayoutParams();
lparams.width=500;
lparams.height=350;
view.setLayoutParams(lparams);
}
Thismethodobtainsareferencetothebuttonviewintheuserinterfacelayoutandcreates
anewsetoflayoutparameterrulesdesignedtomovethebuttontothebottomrighthand
corneroftheparentlayoutandtoincreasethebutton’sdimensions.Oncecreated,these
newparametersareappliedtothebutton.
Testthecodesofarbycompilingandrunningtheapplication.Oncelaunched,touchthe
background(notthebutton)andnotethatthebuttonmovesandresizesasillustratedin
Figure28-1:
Figure28-1
Althoughthelayoutchangestookeffect,theydidsoinstantlyandwithoutanyformof
animation.ThisiswherethecalltothebeginDelayedTransition()methodofthe
TransitionManagerclasscomesin.Allthatisneededtoaddanimationtothislayout
changeistheadditionofasinglelineofcodebeforethelayoutchangesareimplemented.
RemainingwithintheTransitionDemoActivity.javafile,modifythecodeasfollows:
packagecom.ebookfrenzy.transitiondemo;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.RelativeLayout;
importandroid.transition.TransitionManager;
publicclassTransitionDemoActivityextendsAppCompatActivity{
.
.
.
publicvoidhandleTouch(){
Viewview=findViewById(R.id.myButton1);
TransitionManager.beginDelayedTransition(myLayout);
RelativeLayout.LayoutParamsparams=new
RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,
RelativeLayout.TRUE);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM,
RelativeLayout.TRUE);
params.width=500;
params.height=350;
view.setLayoutParams(params);
}
.
.
.
}
Compileandruntheapplicationonceagainandnotethatthetransitionisnowanimated.
28.4CustomizingtheTransition
ThefinaltaskinthisexampleistomodifythechangeBoundstransitionsothatitis
performedoveralongerdurationandincorporatesabounceeffectwhentheviewreaches
itsnewscreenlocation.ThisinvolvesthecreationofaTransitioninstancewith
appropriatedurationinterpolatorsettingswhichis,inturn,passedthroughasanargument
tothebeginDelayedTransition()method:
packagecom.ebookfrenzy.transitiondemo;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.RelativeLayout;
importandroid.transition.TransitionManager;
importandroid.transition.ChangeBounds;
importandroid.transition.Transition;
importandroid.view.animation.BounceInterpolator;
publicclassTransitionDemoActivityextendsAppCompatActivity{
.
.
.
publicvoidhandleTouch(){
Viewview=findViewById(R.id.myButton1);
TransitionchangeBounds=newChangeBounds();
changeBounds.setDuration(3000);
changeBounds.setInterpolator(newBounceInterpolator());
TransitionManager.beginDelayedTransition(myLayout,
changeBounds);
RelativeLayout.LayoutParamsparams=new
RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,
RelativeLayout.TRUE);
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM,
RelativeLayout.TRUE);
params.width=500;
params.height=350;
view.setLayoutParams(params);
}
Whentheapplicationisnowexecuted,theanimationwillslowtomatchthenewduration
settingandthebuttonwillbounceonarrivalatthebottomrighthandcornerofthedisplay.
28.5Summary
Themostbasicformoftransitionanimationinvolvestheuseofthe
beginDelayedTransition()methodoftheTransitionManagerclass.Oncecalled,any
changesinsizeandpositionoftheviewsinthenextuserinterfacerenderingframe,and
withinadefinedviewgroup,willbeanimatedusingthespecifiedtransitions.Thischapter
hasworkedthroughasimpleAndroidStudioexamplethatdemonstratestheuseofthis
approachtoimplementingtransitions.
29.ImplementingAndroidSceneTransitions
–ATutorial
ThischapterwillbuildonthetheoryoutlinedinthechapterentitledAnimatingUser
InterfaceswiththeAndroidTransitionsFrameworkbyworkingthroughthecreationofa
projectdesignedtodemonstratetransitioningfromonescenetoanotherusingtheAndroid
Transitionframework.
29.1AnOverviewoftheSceneTransitionProject
Theapplicationcreatedinthischapterwillconsistoftwoscenes,eachrepresentedbyan
XMLlayoutresourcefile.Atransitionwillthenbeusedtoanimatethechangesfromone
scenetoanother.Thefirstscenewillconsistofthreebuttonviews.Thesecondscenewill
containtwoofthebuttonsfromthefirstscenepositionedatdifferentlocationsonthe
screen.Thethirdbuttonwillbeabsentfromthesecondscene.Oncethetransitionhasbeen
implemented,movementofthefirsttwobuttonswillbeanimatedwithabounceeffect.
Thethirdbuttonwillgentlyfadeintoviewastheapplicationtransitionsbacktothefirst
scenefromthesecond.
29.2CreatingtheAndroidStudioSceneTransitionsProject
CreateanewprojectinAndroidStudio,enteringSceneTransitionsintotheApplication
namefieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingonthe
Nextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI19:Android4.4(KitKat).Continuetoproceedthroughthescreens,
requestingthecreationofanemptyactivitynamedSceneTransitionsActivitywitha
correspondinglayoutfilenamedactivity_scene_transitions.
29.3IdentifyingandPreparingtheRootContainer
Whenworkingwithtransitionsitisimportanttoidentifytherootcontainerforthescenes.
Thisisessentiallytheparentlayoutcontainerintowhichthescenesaregoingtobe
displayed.Whentheprojectwascreated,AndroidStudiocreatedalayoutresourcefilein
theapp->res->layoutfoldernamedactivity_scene_transitions.xmlandcontaininga
singleRelativeLayoutcontainerandTextView.Whentheapplicationislaunched,thisis
thefirstlayoutthatwillbedisplayedtotheuseronthedevicescreenandforthepurposes
ofthisexample,theRelativeLayoutcontainerwithinthislayoutwillactastheroot
containerforthetwoscenes.
Beginbylocatingtheactivity_scene_transitions.xmllayoutresourcefileandloadingit
intotheAndroidStudioDesignertool.SwitchtoDesignmodeifnecessary,selectthe
“HelloWorld!”TextViewobjectanddeleteitfromthelayoutusingthekeyboarddelete
key.Doubleclickonthelayoutbackgroundandintheresultingpanelchangetheview’s
IDpropertytorootContainer.
29.4DesigningtheFirstScene
Thefirstsceneisgoingtoconsistofarelativelayoutcontainingthreebuttonviews.
Createthislayoutresourcefilebyright-clickingontheapp->res->layoutentryinthe
ProjecttoolwindowandselectingtheNew->Layoutresourcefile…menuoption.Inthe
resultingdialog,namethefilescene1_layoutandselectRelativeLayoutastherootelement
beforeclickingonOK.
WhenthenewlycreatedlayoutfilehasloadedintotheDesignertooldragaButtonview
fromtheWidgetssectionofthepaletteontothelayoutcanvasandpositionitinthetopleft
handcornerofthelayoutviewsothatthealignParentLeftandalignParentTopproperties
aredisplayedasillustratedinFigure29-1.DroptheButtonviewatthisposition,double
clickonitandchangethetextpropertyto“One”.Selectthelightbulbicon,clickonthe
I18Nmessageandextractthestringtoaresourcenamedone_string.
Figure29-1
DragasecondButtonviewfromthepaletteandpositionitinthetoprighthandcornerof
thelayoutviewsothatthealignParentRightandalignParentToppropertiesaredisplayed
beforedroppingtheviewintoplace.Repeatingthestepsforthefirstbutton,assigntext
thatreads“Two”tothebuttonandextractitintoastringresourcenamedandtwo_string.
DragathirdButtonviewandpositionitsothatitiscenteredbothhorizontallyand
verticallywithinthelayout,thistimeconfiguringastringresourcenamedthree_stringthat
reads“Three”.
Oncompletionoftheabovesteps,thelayoutforthefirstsceneshouldresemblethat
showninFigure29-2:
Figure29-2
SwitchtheDesignertooltoTextmodetodirectlyedittheXMLresourcesforthelayout.
VerifythattheXMLmatchesthatlistedbelowbeforeaddingtheonClickpropertiestothe
firstandsecondbuttons.Thesemethodswillbeimplementedlatertotriggerthe
transitionsfromonescenetoanother:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/one_string”
android:id=”@+id/button”
android:layout_alignParentTop=“true”
android:layout_alignParentLeft=“true”
android:layout_alignParentStart=“true”
android:onClick=“goToScene2”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/two_string”
android:id=”@+id/button2”
android:layout_alignParentTop=“true”
android:layout_alignParentRight=“true”
android:layout_alignParentEnd=“true”
android:onClick=“goToScene1”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/three_string”
android:id=”@+id/button3”
android:layout_centerVertical=“true”
android:layout_centerHorizontal=“true”/>
</RelativeLayout>
29.5DesigningtheSecondScene
Thesecondsceneissimplyamodifiedversionofthefirstscene.Thefirstandsecond
buttonswillstillbepresentbutwillbelocatedinthebottomrightandlefthandcornersof
thelayoutrespectively.Thethirdbutton,ontheotherhand,willnolongerbepresentin
thesecondscene.
Forthepurposesofavoidingduplicatedeffort,thelayoutfileforthesecondscenewillbe
createdbycopyingandmodifyingthescene1_layout.xmlfile.WithintheProjecttool
window,locatetheapp->res->layout->scene1_layout.xmlfile,right-clickonitand
selecttheCopymenuoption.Right-clickonthelayoutfolder,thistimeselectingthePaste
menuoptionandchangethenameofthefiletoscene2_layout.xmlwhenpromptedtodo
so.
Doubleclickonthenewscene2_layout.xmlfiletoloaditintotheDesignertoolandswitch
toDesignmodeifnecessary.Selectanddeletethe“Three”buttonandmovethefirstand
secondbuttonstothebottomrightandbottomleftlocationsasillustratedinFigure29-3:
Figure29-3
SwitchDesignertoTextmodeandverifythattheXMLmatchesthatlistedbelow:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/one_string”
android:id=”@+id/button”
android:onClick=“goToScene2”
android:layout_alignParentBottom=“true”
android:layout_alignParentRight=“true”
android:layout_alignParentEnd=“true”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/two_string”
android:id=”@+id/button2”
android:onClick=“goToScene1”
android:layout_alignParentBottom=“true”
android:layout_alignParentLeft=“true”
android:layout_alignParentStart=“true”/>
</RelativeLayout>
29.6EnteringtheFirstScene
Iftheapplicationweretoberunnow,onlytheblanklayoutrepresentedbythe
activity_scene_transitions.xmlfilewouldbedisplayed.Somecodemust,therefore,be
addedtotheonCreate()methodlocatedintheSceneTransitionsActivity.javafilesothat
thefirstsceneispresentedwhentheactivityiscreated.Thiscanbeachievedasfollows:
packagecom.ebookfrenzy.scenetransitions;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.transition.Scene;
importandroid.transition.Transition;
importandroid.transition.TransitionManager;
importandroid.view.ViewGroup;
importandroid.view.View;
publicclassSceneTransitionsActivityextendsAppCompatActivity{
ViewGrouprootContainer;
Scenescene1;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scene_transitions);
rootContainer=
(ViewGroup)findViewById(R.id.rootContainer);
scene1=Scene.getSceneForLayout(rootContainer,
R.layout.scene1_layout,this);
scene1.enter();
}
.
.
}
Thecodeaddedtotheactivityclassdeclaressomevariablesinwhichtostorereferencesto
therootcontainerandfirstsceneandobtainsareferencetotherootcontainerview.The
getSceneForLayout()methodoftheSceneclassisthenusedtocreateascenefromthe
layoutcontainedinthescene1_layout.xmlfiletoconvertthatlayoutintoascene.The
sceneisthenenteredviatheenter()methodcallsothatitisdisplayedtotheuser.
Compileandruntheapplicationatthispointandverifythatscene1isdisplayedafterthe
applicationhaslaunched.
29.7LoadingScene2
Beforeimplementingthetransitionbetweenthefirstandsecondsceneitisfirstnecessary
toaddsomecodetoloadthelayoutfromthescene2_layout.xmlfileintoaSceneinstance.
RemainingintheSceneTransitionsActivity.javafile,therefore,addthiscodeasfollows:
publicclassSceneTransitionsActivityextendsAppCompatActivity{
ViewGrouprootContainer;
Scenescene1;
Scenescene2;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scene_transitions);
rootContainer=
(ViewGroup)findViewById(R.id.rootContainer);
scene1=Scene.getSceneForLayout(rootContainer,
R.layout.scene1_layout,this);
scene2=Scene.getSceneForLayout(rootContainer,
R.layout.scene2_layout,this);
scene1.enter();
}
.
.
}
29.8ImplementingtheTransitions
ThefirstandsecondbuttonshavebeenconfiguredtocallmethodsnamedgoToScene2and
goToScene1respectivelywhenselected.Asthemethodnamessuggest,itisthe
responsibilityofthesemethodstotriggerthetransitionsbetweenthetwoscenes.Add
thesetwomethodswithintheSceneTransitionsActivity.javafilesothattheyreadas
follows:
publicvoidgoToScene2(Viewview)
{
TransitionManager.go(scene2);
}
publicvoidgoToScene1(Viewview)
{
TransitionManager.go(scene1);
}
Runtheapplicationandnotethatselectingthefirsttwobuttonscausesthelayouttoswitch
betweenthetwoscenes.Sincewehaveyettoconfigureanytransitions,theselayout
changesarenotyetanimated.
29.9AddingtheTransitionFile
Allofthetransitioneffectsforthisprojectwillbeimplementedwithinasingletransition
XMLresourcefile.AsoutlinedinthechapterentitledAnimatingUserInterfaceswiththe
AndroidTransitionsFramework,transitionresourcefilesmustbeplacedintheapp->res
->transitionfolderoftheproject.Begin,therefore,byright-clickingontheresfolderin
theProjecttoolwindowandselectingtheNew->Directorymenuoption.Intheresulting
dialog,namethenewfoldertransitionandclickontheOKbutton.Right-clickonthenew
transitionfolder,thistimeselectingtheNew->Fileoptionandnamethenewfile
transition.xml.
Withthenewlycreatedtransition.xmlfileselectedandloadedintotheeditingpanel,add
thefollowingXMLcontenttoaddatransitionsetthatenablesthechangebounds
transitionanimationwithadurationattributesetting:
<?xmlversion=“1.0”encoding=“utf-8”?>
<transitionSet
xmlns:android=“http://schemas.android.com/apk/res/android”>
<changeBounds
android:duration=“2000”>
</changeBounds>
</transitionSet>
29.10LoadingandUsingtheTransitionSet
Althoughatransitionresourcefilehasbeencreatedandpopulatedwithachangebounds
transition,thiswillhavenoeffectuntilsomecodeisaddedtoloadthetransitionsintoa
TransitionManagerinstanceandreferenceitinthescenechanges.Thechangestoachieve
thisareasfollows:
packagecom.ebookfrenzy.scenetransitions;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.transition.Scene;
importandroid.transition.Transition;
importandroid.transition.TransitionInflater;
importandroid.transition.TransitionManager;
importandroid.view.ViewGroup;
importandroid.view.View;
publicclassSceneTransitionsActivityextendsAppCompatActivity{
ViewGrouprootContainer;
Scenescene1;
Scenescene2;
TransitiontransitionMgr;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scene_transitions);
rootContainer=
(ViewGroup)findViewById(R.id.rootContainer);
transitionMgr=TransitionInflater.from(this)
.inflateTransition(R.transition.transition);
scene1=Scene.getSceneForLayout(rootContainer,
R.layout.scene1_layout,this);
scene2=Scene.getSceneForLayout(rootContainer,
R.layout.scene2_layout,this);
scene1.enter();
}
publicvoidgoToScene2(Viewview)
{
TransitionManager.go(scene2,transitionMgr);
}
publicvoidgoToScene1(Viewview)
{
TransitionManager.go(scene1,transitionMgr);
}
.
.
}
Whentheapplicationisnowrunthetwobuttonswillgentlyglidetotheirnewpositions
duringthetransition.
29.11ConfiguringAdditionalTransitions
Withthetransitionfileintegratedintotheproject,anynumberofadditionaltransitions
maybeaddedtothefilewithouttheneedtomakeanyfurtherchangestotheJavasource
codeoftheactivity.Take,forexample,thefollowingchangestothetransition.xmlfileto
addabounceinterpolatortothechangeboundstransition,introduceafade-intransition
targetedatthethirdbuttonandtochangethetransitionssuchthattheyareperformed
sequentially:
<?xmlversion=“1.0”encoding=“utf-8”?>
<transitionSet
xmlns:android=“http://schemas.android.com/apk/res/android”
android:transitionOrdering=“sequential”>
<fade
android:duration=“2000”
android:fadingMode=“fade_in”>
<targets>
<targetandroid:targetId=”@id/button3”/>
</targets>
</fade>
<changeBounds
android:duration=“2000”
android:interpolator=”@android:anim/bounce_interpolator”>
</changeBounds>
</transitionSet>
Buttonsoneandtwowillnowbounceonarrivingattheenddestinationsandbuttonthree
willgentlyfadebackintoviewwhentransitioningtoscene1fromscene2.
Takesometimetoexperimentwithdifferenttransitionsandinterpolatorsbymaking
changestothetransition.xmlfileandre-runningtheapplication.
29.12Summary
Scenebasedtransitionsprovideaflexibleapproachtoanimatinguserinterfacelayout
changeswithinanAndroidapplication.Thischapterhasdemonstratedthestepsinvolved
inanimatingthetransitionbetweenthescenesrepresentedbytwolayoutresourcefiles.In
addition,theexamplealsousedatransitionXMLresourcefiletoconfigurethetransition
animationeffectsbetweenthetwoscenes.
30.WorkingwiththeFloatingActionButton
andSnackbar
Oneoftheobjectivesofthischapteristoprovideanoverviewoftheconceptsofmaterial
design.OriginallyintroducedaspartofAndroid5.0,materialdesignisasetofdesign
guidelinesthatdictatehowtheAndroiduserinterface,andthatoftheappsrunningon
Android,appearandbehave.
Aspartoftheimplementationofthematerialdesignconcepts,Googlealsointroducedthe
AndroidDesignSupportLibrary.Thislibrarycontainsanumberofdifferentcomponents
thatallowmanyofthekeyfeaturesofmaterialdesigntobebuiltintoAndroid
applications.Twoofthesecomponents,thefloatingactionbuttonandSnackbar,willalso
becoveredinthischapterpriortointroducingmanyoftheothercomponentsin
subsequentchapters.
30.1TheMaterialDesign
TheoverallappearanceoftheAndroidenvironmentisdefinedbytheprinciplesof
materialdesign.MaterialdesignwascreatedbytheAndroidteamatGoogleanddictates
thattheelementsthatmakeuptheuserinterfaceofAndroidandtheappsthatrunonit
appearandbehaveinacertainwayintermsofbehavior,shadowing,animationandstyle.
Oneofthetenetsofthematerialdesignisthattheelementsofauserinterfaceappearto
havephysicaldepthandasensethatitemsareconstructedinlayersofphysicalmaterial.A
button,forexample,appearstoberaisedabovethesurfaceofthelayoutinwhichit
residesthroughtheuseofshadowingeffects.Pressingthebuttoncausesthebuttontoflex
andliftasthoughmadeofathinmaterialthatrippleswhenreleased.
Materialdesignalsodictatesthelayoutandbehaviorofmanystandarduserinterface
elements.Akeyexampleisthewayinwhichtheappbarlocatedatthetopofthescreen
shouldappearandthewayinwhichitshouldbehaveinrelationtoscrollingactivities
takingplacewithinthemaincontentoftheactivity.
Infact,materialdesigncoversawiderangeofareasfromrecommendedcolorstylestothe
wayinwhichobjectsareanimated.Afulldescriptionofthematerialdesignconceptsand
guidelinescanbefoundonlineatthefollowinglinkandisrecommendedreadingforall
Androiddevelopers:
https://www.google.com/design/spec/material-design/introduction.html
30.2TheDesignLibrary
ManyofthebuildingblocksneededtoimplementAndroidapplicationsthatadoptthe
principlesofmaterialdesignarecontainedwithintheAndroidDesignSupportLibrary.
Thislibrarycontainsacollectionofuserinterfacecomponentsthatcanbeincludedin
Androidapplicationstoimplementmuchofthelook,feelandbehaviorofmaterialdesign.
Twoofthecomponentsfromthislibrary,thefloatingactionbuttonandSnackbarwillbe
coveredinthischapterwhileotherswillbeintroducedinlaterchapters.
30.3TheFloatingActionButton(FAB)
Thefloatingactionbuttonisabuttonwhichappearstofloatabovethesurfaceoftheuser
interfaceofanappandisgenerallyusedtopromotethemostcommonactionwithinauser
interfacescreen.Afloatingactionbuttonmight,forexample,beplacedonascreento
allowtheusertoaddanentrytoalistofcontactsortosendanemailfromwithintheapp.
Figure30-1,forexample,highlightsthefloatingactionbuttonthatallowstheusertoadda
newcontactwithinthestandardAndroidContactsapp:
Figure30-1
Toconformwiththematerialdesignguidelines,thereareanumberofrulesthatshouldbe
followedwhenusingfloatingactionbuttons.Floatingactionbuttonsmustbecircularand
canbeeither56x56dp(Default)or40x40dp(Mini)insize.Thebuttonshouldbe
positionedaminimumof16dpfromtheedgeofthescreenonphonesand24dpon
desktopsandtabletdevices.Regardlessofthesize,thebuttonmustcontainaninterior
iconthatis24x24dpinsizeanditisrecommendedthateachuserinterfacescreenhave
onlyonefloatingactionbutton.
Floatingactionbuttonscanbeanimatedordesignedtomorphintootheritemswhen
touched.Afloatingactionbuttoncould,forexample,rotatewhentappedormorphinto
anotherelementsuchasatoolbarorpanellistingrelatedactions.
30.4TheSnackbar
TheSnackbarcomponentprovidesawaytopresenttheuserwithinformationintheform
ofapanelthatappearsatthebottomofthescreenasshowninFigure30-2.Snackbar
instancescontainabrieftextmessageandanoptionalactionbuttonwhichwillperforma
taskwhentappedbytheuser.Oncedisplayed,aSnackbarwilleithertimeout
automaticallyorcanberemovedmanuallybytheuserviaaswipingaction.Duringthe
appearanceoftheSnackbartheappwillcontinuetofunctionandrespondtouser
interactionsinthenormalmanner.
Figure30-2
Intheremainderofthischapteranexampleapplicationwillbecreatedthatmakesuseof
thebasicfeaturesofthefloatingactionbuttonandSnackbartoaddentriestoalistof
items.
30.5CreatingtheExampleProject
CreateanewprojectinAndroidStudio,enteringFabExampleintotheApplicationname
fieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).
Althoughitispossibletomanuallyaddafloatingactionbuttontoanactivity,itismuch
easiertousetheBlankActivitytemplatewhichincludesafloatingactionbuttonby
default.Continuetoproceedthroughthescreens,therefore,requestingthecreationofa
blankactivitynamedFabExampleActivitywithcorrespondinglayoutandmenufiles
namedactivity_fab_exampleandmenu_fab_examplerespectively.
ClickontheFinishbuttontoinitiatetheprojectcreationprocess.
30.6ReviewingtheProject
Sincetheblankactivitytemplatewasselected,theactivitycontainstwolayoutfiles.The
activity_fab_example.xmlfileconsistsofaCoordinatorLayoutmanagercontainingentries
foranappbar,atoolbarandafloatingactionbutton.
Thecontent_fab_example.xmlfilerepresentsthelayoutofthecontentareaoftheactivity
andcontainsaRelativeLayoutinstanceandaTextView.Thisfileisembeddedintothe
activity_fab_example.xmlfileviathefollowingincludedirective:
<includelayout=”@layout/content_fab_example”/>
Thefloatingactionbuttonelementwithintheactivity_fab_example.xmlfilereadsas
follows:
<android.support.design.widget.FloatingActionButton
android:id=”@+id/fab”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“bottom|end”
android:layout_margin=”@dimen/fab_margin”
android:src=”@android:drawable/ic_dialog_email”/>
Thisdeclaresthatthebuttonistoappearinthebottomrighthandcornerofthescreenwith
marginsrepresentedbythefab_marginidentifierinthevalues/dimens.xmlfile(whichin
thiscaseissetto16dp).TheXMLfurtherdeclaresthattheinterioriconforthebuttonisto
taketheformofthestandarddrawablebuilt-inemailicon.
TheblanktemplatehasalsoconfiguredthefloatingactionbuttontodisplayaSnackbar
instancewhentappedbytheuser.Thecodetoimplementthiscanbefoundinthe
onCreate()methodoftheFabExampleActivity.javafileandreadsasfollows:
FloatingActionButtonfab=
(FloatingActionButton)findViewById(R.id.fab);
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
Snackbar.make(view,“Replacewithyourownaction”,
Snackbar.LENGTH_LONG)
.setAction(“Action”,null).show();
}
});
Thecodeobtainsareferencetothefloatingactionbuttonviathebutton’sidandaddstoit
anonClickListenerhandlertobecalledwhenthebuttonistapped.Thismethodsimply
displaysaSnackbarinstanceconfiguredwithamessagebutnoactions.
Finally,openthemodulelevelbuild.gradlefile(GradleScripts->build.gradle(Module:
App))andnotethattheAndroiddesignsupportlibraryhasbeenaddedasadependency:
compile‘com.android.support:design:23.1.0’
Whentheprojectiscompiledandrunthefloatingactionbuttonwillappearatthebottom
ofthescreenasshowninFigure30-3:
Figure30-3
TappingthefloatingactionbuttonwilltriggertheonClickListenerhandlermethodcausing
theSnackbartoappearatthebottomofthescreen:
Figure30-4
WhentheSnackbarappearsonanarrowerdevice(asisthecaseinFigure30-4above)
notethatthefloatingactionbuttonismoveduptomakeroomfortheSnackbartoappear.
ThisishandledforusautomaticallybytheCoordinatorLayoutcontainerinthe
activity_fab_example.xmllayoutresourcefile.
30.7ChangingtheFloatingActionButton
Sincetheobjectiveofthisexampleistoconfigurethefloatingactionbuttontoaddentries
toalisttheemailiconcurrentlydisplayedonthebuttonneedstobechangedtosomething
moreindicativeoftheactionbeingperformed.Theiconthatwillbeusedforthebuttonis
namedic_add_entry.jpgandcanbefoundintheproject_iconsfolderofthesamplecode
downloadavailablefromthefollowingURL:
http://www.ebookfrenzy.com/retail/androidstudioA6/index.php
Locatethisimageinthefilesystemnavigatorforyouroperatingsystemandcopythe
imagefile.Right-clickontheapp->res->drawableentryintheProjecttoolwindowand
selectPastefromthemenutoaddthefiletothefolder:
Figure30-5
Next,edittheactivity_fab_example.xmlfileandchangetheimagesourcefortheiconfrom
@android:drawable/ic_dialog_emailto@drawable/ic_add_entryasfollows:
<android.support.design.widget.FloatingActionButton
android:id=”@+id/fab”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“bottom|end”
android:layout_margin=”@dimen/fab_margin”
android:src=”@drawable/ic_add_entry”/>
Withinthelayoutpreview,theinterioriconforthebuttonwillnowhavechangedtoaplus
sign.
ThebackgroundcolorofthefloatingactionbuttonisdefinedbytheaccentColorproperty
oftheprevailingthemeusedbytheapplication.Thecolorassignedtothisvalueis
declaredinthecolors.xmlfilelocatedunderapp->res->valuesintheProjecttool
window.EditthisfileandaddanewcolornamedcolorBrightAccent:
<?xmlversion=“1.0”encoding=“utf-8”?>
<resources>
<colorname=“colorPrimary”>#3F51B5</color>
<colorname=“colorPrimaryDark”>#303F9F</color>
<colorname=“colorAccent”>#FF4081</color>
<colorname=“colorBrightAccent”>#FFFF00</color>
</resources>
Next,editthestyles.xmlfilelocatedunderapp->res->values->stylesandchangethe
colorAccententrytousethenewcolorBrightAccentvalue:
<resources>
<!—Baseapplicationtheme.—>
<stylename=“AppTheme”
parent=“Theme.AppCompat.Light.DarkActionBar”>
<!—Customizeyourthemehere.—>
<itemname=“colorPrimary”>@color/colorPrimary</item>
<itemname=“colorPrimaryDark”>@color/colorPrimaryDark</item>
<itemname=“colorAccent”>@color/colorBrightAccent</item>
</style>
<stylename=“AppTheme.NoActionBar”>
<itemname=“windowActionBar”>false</item>
<itemname=“windowNoTitle”>true</item>
</style>
<stylename=“AppTheme.AppBarOverlay”
parent=“ThemeOverlay.AppCompat.Dark.ActionBar”/>
<stylename=“AppTheme.PopupOverlay”
parent=“ThemeOverlay.AppCompat.Light”/>
</resources>
Returntotheactivity_fab_example.xmlandverifythatthefloatingactionbuttonnow
appearswithayellowbackground.
30.8AddingtheListViewtotheContentLayout
ThenextstepinthistutorialistoaddtheListViewinstancetothe
content_fab_example.xmlfile.TheListViewclassprovidesawaytodisplayitemsinalist
formatandcanbefoundintheContainerssectionoftheDesignertoolpalette.
Loadthecontent_fab_example.xmlfileintotheDesignertool,selectDesignmodeif
necessaryandselectanddeletethedefaultTextViewobject.LocatetheListViewobjectin
thepaletteanddraganddropitontothecenterofthelayoutcanvassothatitappearsas
illustratedinFigure30-6:
Figure30-6
WiththeListViewselected,usethetoolbarbuttonsorPropertiestoolwindowtosetthe
layout:widthandlayout:heightpropertiestomatch_parent.Doubleclickonthenewly
addedListViewobjectandchangetheIDtolistViewifitisnotalreadysettothisvalue.
30.9AddingItemstotheListView
Eachtimethefloatingactionbuttonistappedbytheuser,anewitemwillbeaddedtothe
ListViewintheformoftheprevailingtimeanddate.Toachievethis,somechangesneed
tobemadetotheFabExampleActivity.javafile.
BeginbymodifyingtheonCreate()methodtoobtainareferencetotheListViewandto
initializeanadapterinstancetoallowustoadditemstothelistintheformofanarray:
importandroid.os.Bundle;
importandroid.support.design.widget.FloatingActionButton;
importandroid.support.design.widget.Snackbar;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.View;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.widget.ArrayAdapter;
importandroid.widget.ListView;
importjava.util.ArrayList;
publicclassFabExampleActivityextendsAppCompatActivity{
ArrayList<String>listItems=newArrayList<String>();
ArrayAdapter<String>adapter;
privateListViewmyListView;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fab_example);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
myListView=(ListView)findViewById(R.id.listView);
adapter=newArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
listItems);
myListView.setAdapter(adapter);
FloatingActionButtonfab=(FloatingActionButton)
findViewById(R.id.fab);
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
Snackbar.make(view,“Replacewithyourownaction”,
Snackbar.LENGTH_LONG)
.setAction(“Action”,null).show();
}
});
}
.
.
.
}
TheListViewneedsanarrayofitemstodisplay,anadaptertomanagetheitemsinthat
arrayandalayoutdefinitiontodictatehowitemsaretobepresentedtotheuser.
Intheabovecodechanges,theitemsarestoredinanArrayListinstanceassignedtoan
adapterthattakestheformofanArrayAdapter.Theitemsaddedtothelistwillbe
displayedintheListViewusingthesimple_list_item_1layout,abuilt-inlayoutthatis
providedwithAndroidtodisplaysimplestringbaseditemsinaListViewinstance.
Next,edittheonClickListenercodeforthefloatingactionbuttontodisplayadifferent
messageintheSnackbarandtocallamethodtoaddanitemtothelist:
FloatingActionButtonfab=(FloatingActionButton)
findViewById(R.id.fab);
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
addListItem();
Snackbar.make(view,“Itemaddedtolist“,
Snackbar.LENGTH_LONG)
.setAction(“Action”,null).show();
}
});
RemainingwithintheFabExampleActivity.javafile,addtheaddListItem()methodas
follows:
packagecom.ebookfrenzy.fabexample;
.
.
.
importjava.text.SimpleDateFormat;
importjava.util.Date;
importjava.util.Locale;
publicclassFabExampleActivityextendsAppCompatActivity{
.
.
privatevoidaddListItem(){
SimpleDateFormatdateformat=
newSimpleDateFormat(“HH:mm:ssMM/dd/yyyy”,
Locale.US);
listItems.add(dateformat.format(newDate()));
adapter.notifyDataSetChanged();
}
.
.
}
ThecodeintheaddListItem()methodidentifiesandformatsthecurrentdateandtimeand
addsittothelistitemsarray.ThearrayadapterassignedtotheListViewisthennotified
thatthelistdatahaschanged,causingtheListViewtoupdatetodisplaythelatestlist
items.
Compileandruntheappandtestthattappingthefloatingactionbuttonaddsnewtimeand
dateentriestotheListView,displayingtheSnackbareachtimeasshowninFigure30-7:
Figure30-7
30.10AddinganActiontotheSnackbar
ThefinaltaskinthisprojectistoaddanactiontotheSnackbarthatallowstheuserto
undothemostrecentadditiontothelist.EdittheFabExampleActivity.javafileandmodify
theSnackbarcreationcodetoaddanactiontitled“Undo”configuredwithan
onClickListenernamedundoOnClickListener:
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
addListItem();
Snackbar.make(view,“Itemaddedtolist”,
Snackbar.LENGTH_LONG)
.setAction(“Undo“,undoOnClickListener).show();
}
});
WithintheFabExampleActivity.javafileaddthelistenerhandler:
View.OnClickListenerundoOnClickListener=newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
listItems.remove(listItems.size()-1);
adapter.notifyDataSetChanged();
Snackbar.make(view,“Itemremoved”,Snackbar.LENGTH_LONG)
.setAction(“Action”,null).show();
}
};
ThecodeintheonClickmethodidentifiesthelocationofthelastiteminthelistarrayand
removesitfromthelistbeforetriggeringthelistviewtoperformanupdate.Anew
Snackbaristhendisplayedindicatingthatthelastitemhasbeenremovedfromthelist.
Runtheapponceagainandaddsomeitemstothelist.Onthefinaladdition,taptheUndo
buttonintheSnackbar(Figure30-8)toremovethelastitemfromthelist:
Figure30-8
ItisalsoworthnotingthattheUndobuttonappearsusingthesamecolorBrightAccent
colorassignedtotheaccentColorpropertyinthestyles.xmlfileearlierinthechapter.
30.11Summary
Thischapterhasprovidedageneraloverviewofmaterialdesign,thefloatingactionbutton
andSnackbarbeforeworkingthroughanexampleprojectthatmakesuseofthesefeatures
BoththefloatingactionbuttonandtheSnackbararepartofthematerialdesignapproach
touserinterfaceimplementationinAndroid.Thefloatingactionbuttonprovidesawayto
promotethemostcommonactionwithinaparticularscreenofanAndroidapplication.
TheSnackbarallowsanapplicationtopresentinformationtotheuserandtoallowthe
usertotakeactionfromwithinthebar.
31.CreatingaTabbedInterfaceusingthe
TabLayoutComponent
ThepreviouschapteroutlinedtheconceptofmaterialdesigninAndroidandintroduced
twoofthecomponentsprovidedbythedesignsupportlibraryintheformofthefloating
actionbuttonandtheSnackbar.Thischapterwilldemonstratehowtouseanotherofthe
designlibrarycomponents,theTabLayout,whichcanbecombinedwiththeViewPager
classtocreateatabbasedinterfacewithinanAndroidactivity.
31.1AnIntroductiontotheViewPager
Althoughnotpartofthedesignsupportlibrary,theViewPagerisausefulcompanionclass
whenusedinconjunctionwiththeTabLayoutcomponenttoimplementatabbeduser
interface.TheprimaryroleoftheViewPageristoallowtheusertoflipthroughdifferent
pagesofinformationwhereeachpageismosttypicallyrepresentedbyalayoutfragment.
ThefragmentsthatareassociatedwiththeViewPageraremanagedbyaninstanceofthe
FragmentPagerAdapterclass.
AtaminimumthepageradapterassignedtoaViewPagermustimplementtwomethods.
Thefirst,namedgetCount(),mustreturnthetotalnumberofpagefragmentsavailableto
bedisplayedtotheuser.Thesecondmethod,getItem(),ispassedapagenumberandmust
returnthecorrespondingfragmentobjectreadytobepresentedtotheuser.
31.2AnOverviewoftheTabLayoutComponent
Aspreviouslydiscussed,TabLayoutisoneofthecomponentsintroducedaspartof
materialdesignandisincludedinthedesignsupportlibrary.Thepurposeofthe
TabLayoutistopresenttheuserwitharowoftabswhichcanbeselectedtodisplay
differentpagestotheuser.Thetabscanbefixedorscrollable,wherebytheusercanswipe
leftorrighttoviewmoretabsthanwillcurrentlyfitonthedisplay.Theinformation
displayedonatabcanbetext-based,animageoracombinationoftextandimages.Figure
31-1,forexample,showsthetabbarfortheAndroidphoneappconsistingofthreetabs
displayingimages:
Figure31-1
Figure31-2,ontheotherhand,showsaTabLayoutconfigurationconsistingoffourtabs
displayingtextinascrollableconfiguration:
Figure31-2
Theremainderofthischapterwillworkthroughthecreationofanexampleprojectthat
demonstratestheuseoftheTabLayoutcomponenttogetherwithaViewPagerandfour
fragments.
31.3CreatingtheTabLayoutDemoProject
CreateanewprojectinAndroidStudio,enteringTabLayoutDemointotheApplication
namefieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingonthe
Nextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).
Continuethroughtheconfigurationscreensrequestingthecreationofablankactivity
namedTabLayoutDemoActivitywithcorrespondinglayoutandmenufilesnamed
activity_tab_layout_demoandmenu_tab_layout_demorespectively.ClickontheFinish
buttontoinitiatetheprojectcreationprocess.
Oncetheprojecthasbeencreated,loadthecontent_tab_layout_demo.xmlfileintothe
Designertool,selectandthendeletethe“HelloWorld”TextViewobject.
31.4CreatingtheFirstFragment
EachofthetabsontheTabLayoutwilldisplayadifferentfragmentwhenselected.Create
thefirstofthesefragmentsbyright-clickingontheapp->java->
com.ebookfrenzy.tablayoutdemoentryintheProjecttoolwindowandselectingtheNew->
Fragment->Fragment(Blank)option.Intheresultingdialog,enterTab1Fragmentinto
theFragmentName:fieldandfragment_tab1intotheFragmentLayoutName:field.
EnabletheCreatelayoutXML?optionanddisableboththeIncludefragmentfactory
methodsandIncludeinterfacecallbacksoptionsbeforeclickingontheFinishbuttonto
createthenewfragment:
Figure31-3
Loadthenewlycreatedfragment_tab1.xmlfile(locatedunderapp->res->layout)into
theDesignertool,switchtoTextmodeandchangetheFrameLayoutcontainertoa
RelativeLayout:
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=“com.ebookfrenzy.tablayoutdemo.Tab1Fragment”>
<!—TODO:Updateblankfragmentlayout—>
<TextViewandroid:layout_width=“match_parent”
android:layout_height=“match_parent”
android:text=”@string/hello_blank_fragment”/>
</RelativeLayout>
SwitchtoDesignmodeandselectanddeletetheTextViewfromthelayout.Fromthe
Palette,locatetheLargeTextwidgetanddraganddropitsothatitispositionedintothe
centerofthelayout.Editthetextontheobjectsothatitreads“Tab1Fragment”atwhich
pointthelayoutshouldmatchthatofFigure31-4:
Figure31-4
31.5DuplicatingtheFragments
Sofartheprojectcontainsoneofthefourrequiredfragments.Ratherthancreatethe
remainingthreefragmentsusingthepreviousstepsitisquickertoduplicatethefirst
fragment.EachfragmentconsistsofalayoutXMLfileandaJavaclassfile,eachofwhich
needstobeduplicated.
Right-clickonthefragment_tab1.xmlfileintheProjecttoolwindowandselecttheCopy
optionfromtheresultingmenu.Right-clickonthelayoutsentry,thistimeselectingthe
Pasteoption.Intheresultingdialog,namethenewlayoutfilefragment_tab2.xmlbefore
clickingtheOKbutton.Editthenewfragment_tab2.xmlfileandchangethetextonthe
TextViewto“Tab2Fragment”.
ToduplicatetheTab1Fragmentclassfile,right-clickontheclasslistedunderapp->java>com.ebookfrenzy.tablayoutdemoandselectCopy.Right-clickonthe
com.ebookfrenzy.tablayoutdemoentryandselectPaste.IntheCopyClassdialog,enter
Tab2FragmentintotheNewname:fieldandclickonOK.Editthenew
Tab2Fragment.javafileandchangetheonCreateView()methodtoinflatethe
fragment_tab2layoutfile:
@Override
publicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,
BundlesavedInstanceState){
//Inflatethelayoutforthisfragment
returninflater.inflate(R.layout.fragment_tab2,container,false);
}
Performtheaboveduplicationstepstwicemoretocreatethefragmentlayoutandclass
filesfortheremainingtwofragments.Oncompletionofthesestepstheprojectstructure
shouldmatchthatofFigure31-5:
Figure31-5
31.6AddingtheTabLayoutandViewPager
WiththefragmentcreationprocessnowcompletethenextstepistoaddtheTabLayout
andViewPagertothemainactivitylayoutfile.Edittheactivity_tab_layout_demo.xmlfile
andaddtheseelementsasoutlinedinthefollowingXMLlisting.NotethattheTabLayout
componentisembeddedintotheAppBarLayoutelementwhiletheViewPagerisplaced
aftertheAppBarLayout:
<?xmlversion=“1.0”encoding=“utf-8”?>
<android.support.design.widget.CoordinatorLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:fitsSystemWindows=“true”
tools:context=”.TabLayoutDemoActivity”>
<android.support.design.widget.AppBarLayout
android:layout_height=“wrap_content”
android:layout_width=“match_parent”
android:theme=”@style/AppTheme.AppBarOverlay”>
<android.support.v7.widget.Toolbarandroid:id=”@+id/toolbar”
android:layout_width=“match_parent”
android:layout_height=”?attr/actionBarSize”
android:background=”?attr/colorPrimary”
app:popupTheme=”@style/AppTheme.PopupOverlay”/>
<android.support.design.widget.TabLayout
android:id=”@+id/tab_layout”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
app:tabMode=“fixed”
app:tabGravity=“fill”/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id=”@+id/pager”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
app:layout_behavior=”@string/appbar_scrolling_view_behavior”
/>
<includelayout=”@layout/content_tab_layout_demo”/>
<android.support.design.widget.FloatingActionButton
android:id=”@+id/fab”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“bottom|end”
android:layout_margin=”@dimen/fab_margin”
android:src=”@android:drawable/ic_dialog_email”/>
</android.support.design.widget.CoordinatorLayout>
31.7CreatingthePagerAdapter
ThisexamplewillusetheViewPagerapproachtohandlingthefragmentsassignedtothe
TabLayouttabs.WiththeViewPageraddedtothelayoutresourcefile,anewclasswhich
subclassesFragmentPagerAdapternowneedstobeaddedtotheprojecttomanagethe
fragmentsthatwillbedisplayedwhenthetabitemsareselectedbytheuser.
Addanewclasstotheprojectbyright-clickingonthecom.ebookfrenzy.tablayoutdemo
entryintheProjecttoolwindowandselectingtheNew->JavaClassmenuoption.Inthe
newclassdialog,enterTabPagerAdapterintotheName:fieldandclickOK.
EdittheTabPagerAdapter.javafilesothatitreadsasfollows:
packagecom.ebookfrenzy.tablayoutdemo;
importandroid.support.v4.app.Fragment;
importandroid.support.v4.app.FragmentManager;
importandroid.support.v4.app.FragmentPagerAdapter;
publicclassTabPagerAdapterextendsFragmentPagerAdapter{
inttabCount;
publicTabPagerAdapter(FragmentManagerfm,intnumberOfTabs){
super(fm);
this.tabCount=numberOfTabs;
}
@Override
publicFragmentgetItem(intposition){
switch(position){
case0:
Tab1Fragmenttab1=newTab1Fragment();
returntab1;
case1:
Tab2Fragmenttab2=newTab2Fragment();
returntab2;
case2:
Tab3Fragmenttab3=newTab3Fragment();
returntab3;
case3:
Tab4Fragmenttab4=newTab4Fragment();
returntab4;
default:
returnnull;
}
}
@Override
publicintgetCount(){
returntabCount;
}
}
TheclassisdeclaredasextendingtheFragmentPagerAdapterclassandaconstructoris
implementedallowingthenumberofpagesrequiredtobepassedtotheclasswhenan
instanceiscreated.ThegetItem()methodwillbecalledwhenaspecificpageisrequired.
Aswitchstatementisusedtoidentifythepagenumberbeingrequestedandtoreturna
correspondingfragmentinstance.Finally,thegetCount()methodsimplyreturnsthecount
valuepassedthroughwhentheobjectinstancewascreated.
31.8PerformingtheInitializationTasks
TheremainingtasksinvolveinitializingtheTabLayout,ViewPagerandTabPagerAdapter
instances.AllofthesetaskswillbeperformedintheonCreate()methodofthe
TabLayoutDemoActivity.javafile.EditthisfileandmodifytheonCreate()methodsothat
itreadsasfollows:
packagecom.ebookfrenzy.tablayoutdemo;
importandroid.os.Bundle;
importandroid.support.design.widget.FloatingActionButton;
importandroid.support.design.widget.Snackbar;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.View;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.support.design.widget.TabLayout;
importandroid.support.v4.view.PagerAdapter;
importandroid.support.v4.view.ViewPager;
publicclassTabLayoutDemoActivityextendsAppCompatActivity{
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout_demo);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
TabLayouttabLayout=
(TabLayout)findViewById(R.id.tab_layout);
tabLayout.addTab(tabLayout.newTab().setText(“Tab1Item”));
tabLayout.addTab(tabLayout.newTab().setText(“Tab2Item”));
tabLayout.addTab(tabLayout.newTab().setText(“Tab3Item”));
tabLayout.addTab(tabLayout.newTab().setText(“Tab4Item”));
finalViewPagerviewPager=
(ViewPager)findViewById(R.id.pager);
finalPagerAdapteradapter=newTabPagerAdapter
(getSupportFragmentManager(),
tabLayout.getTabCount());
viewPager.setAdapter(adapter);
viewPager.addOnPageChangeListener(new
TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.setOnTabSelectedListener(new
TabLayout.OnTabSelectedListener(){
@Override
publicvoidonTabSelected(TabLayout.Tabtab){
viewPager.setCurrentItem(tab.getPosition());
}
@Override
publicvoidonTabUnselected(TabLayout.Tabtab){
}
@Override
publicvoidonTabReselected(TabLayout.Tabtab){
}
});
.
.
.
}
.
.
.
}
ThecodebeginsbyobtainingareferencetotheTabLayoutobjectthatwasaddedtothe
activity_tab_layout_demo.xmlfileandcreatingfourtabs,assigningthetexttoappearon
each:
TabLayouttabLayout=
(TabLayout)findViewById(R.id.tab_layout);
tabLayout.addTab(tabLayout.newTab().setText(“Tab1Item”));
tabLayout.addTab(tabLayout.newTab().setText(“Tab2Item”));
tabLayout.addTab(tabLayout.newTab().setText(“Tab3Item”));
tabLayout.addTab(tabLayout.newTab().setText(“Tab4Item”));
AreferencetotheViewPagerinstanceinthelayoutfileisthenobtainedandaninstanceof
theTabPagerAdapterclasscreated.NotethatthecodetocreatetheTabPagerAdapter
instancepassesthroughthenumberoftabsthathavebeenassignedtotheTabLayout
component.TheTabPagerAdapterinstanceisthenassignedastheadapterforthe
ViewPagerandtheTabLayoutcomponentaddedtothepagechangelistener:
finalViewPagerviewPager=(ViewPager)findViewById(R.id.pager);
finalPagerAdapteradapter=newTabPagerAdapter
(getSupportFragmentManager(),
tabLayout.getTabCount());
viewPager.setAdapter(adapter);
viewPager.addOnPageChangeListener(new
TabLayout.TabLayoutOnPageChangeListener(tabLayout));
Finally,theonTabSelectedListenerisconfiguredontheTabLayoutinstanceandthe
onTabSelected()methodimplementedtosetthecurrentpageontheViewPagerbasedon
thecurrentlyselectedtabnumber.Forthesakeofcompletenesstheotherlistenermethods
areaddedasstubs:
tabLayout.setOnTabSelectedListener(new
TabLayout.OnTabSelectedListener()
{
@Override
publicvoidonTabSelected(TabLayout.Tabtab){
viewPager.setCurrentItem(tab.getPosition());
}
@Override
publicvoidonTabUnselected(TabLayout.Tabtab){
}
@Override
publicvoidonTabReselected(TabLayout.Tabtab){
}
});
31.9TestingtheApplication
Compileandruntheapponadeviceoremulatorandmakesurethatselectingatabcauses
thecorrespondingfragmenttoappearinthecontentareaofthescreen:
Figure31-6
31.10CustomizingtheTabLayout
TheTabLayoutinthisexampleprojectisconfiguredusingfixedmode.Thismodeworks
wellforalimitednumberoftabswithshorttitles.Agreaternumberoftabsorlongertitles
canquicklybecomeaproblemwhenusingfixedmodeasillustratedbyFigure31-7:
Figure31-7
InanefforttofitthetabsintotheavailabledisplaywidththeTabLayouthasusedmultiple
linesoftext.Evenso,thesecondlineisclearlytruncatedmakingitimpossibletoseethe
fulltitle.ThebestsolutiontothisproblemistoswitchtheTabLayouttoscrollablemode.
Inthismodethetitlesappearinfulllength,singlelineformatallowingtheusertoswipeto
scrollhorizontallythroughtheavailableitemsasdemonstratedinFigure31-8:
Figure31-8
ToswitchaTabLayouttoscrollablemode,simplychangetheapp:tabModepropertyinthe
activity_tab_layout_demo.xmllayoutresourcefilefrom“fixed”to“scrollable”:
<android.support.design.widget.TabLayout
android:id=”@+id/tab_layout”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
app:tabMode=”scrollable”
app:tabGravity=“fill”/>
</android.support.design.widget.AppBarLayout>
Wheninfixedmode,theTabLayoutmaybeconfiguredtocontrolhowthetabitemsare
displayedtotakeuptheavailablespaceonthescreen.Thisiscontrolledviathe
app:tabGravityproperty,theresultsofwhicharemorenoticeableonwiderdisplayssuch
astabletsinlandscapeorientation.Whensetto“fill”,forexample,theitemswillbe
distributedevenlyacrossthewidthoftheTabLayoutasshowninFigure31-9:
Figure31-9
Changingthepropertyvalueto“center”willcausetheitemstobepositionedrelativeto
thecenterofthetabbar:
Figure31-10
Beforeproceedingtothefinalstepinthischapter,revertthetabModeandtabGravity
propertiesintheactivity_tab_layout_demo.xmlfileto“fixed”and“fill”respectively.
31.11DisplayingIconTabItems
Thelaststepinthistutorialistoreplacethetextbasedtabswithicons.Toachievethis,
modifytheonCreate()methodintheTabLayoutDemoActivity.javafiletoassignsome
built-indrawableiconstothetabitems:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_layout_demo);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
TabLayouttabLayout=(TabLayout)findViewById(R.id.tab_layout);
tabLayout.addTab(tabLayout.newTab().setIcon(
android.R.drawable.ic_dialog_email));
tabLayout.addTab(tabLayout.newTab().setIcon(
android.R.drawable.ic_dialog_dialer));
tabLayout.addTab(tabLayout.newTab().setIcon(
android.R.drawable.ic_dialog_map));
tabLayout.addTab(tabLayout.newTab().setIcon(
android.R.drawable.ic_dialog_info));
finalViewPagerviewPager=
(ViewPager)findViewById(R.id.pager);
.
.
.
}
InsteadofusingthesetText()methodofthetabitem,thecodeisnowcallingthesetIcon()
methodandpassingthroughadrawableiconreference.Whencompiledandrun,thetab
barshouldnowappearasshowninFigure31-11:
Figure31-11
31.12Summary
TabLayoutisoneofthecomponentsintroducedaspartoftheAndroidmaterialdesign
implementation.ThepurposeoftheTabLayoutcomponentistopresentaseriesoftab
itemswhich,whenselected,displaydifferentcontenttotheuser.Thetabitemscandisplay
text,imagesoracombinationofboth.WhencombinedwiththeViewPagerclassand
fragments,tablayoutscanbecreatedwithrelativeeasewitheachtabitemselection
displayingadifferentfragment.
32.WorkingwiththeRecyclerViewand
CardViewWidgets
TheRecyclerViewandCardViewwidgetsworktogethertoprovidescrollablelistsof
informationtotheuserinwhichtheinformationispresentedintheformofindividual
cards.Detailsofbothclasseswillbecoveredinthischapterbeforeworkingthroughthe
designandimplementationofanexampleproject.
32.1AnOverviewoftheRecyclerView
MuchliketheListViewclassoutlinedinthechapterentitledWorkingwiththeFloating
ActionButtonandSnackBar,thepurposeoftheRecyclerViewistoallowinformationto
bepresentedtotheuserintheformofascrollablelist.TheRecyclerView,however,
providesanumberofadvantagesovertheListView.Inparticular,theRecyclerViewis
significantlymoreefficientinthewayitmanagestheviewsthatmakeupalist,essentially
reusingexistingviewsthatmakeuplistitemsastheyscrolloffthescreeninsteadif
creatingnewones(hencethename“recycler”).Thisbothincreasestheperformanceand
reducestheresourcesusedbyalist,afeaturethatisofparticularbenefitwhenpresenting
largeamountsofdatatotheuser.
UnliketheListView,theRecyclerViewalsoprovidesachoiceofthreebuilt-inlayout
managerstocontrolthewayinwhichthelistitemsarepresentedtotheuser:
· LinearLayoutManager–Thelistitemsarepresentedaseitherahorizontalorvertical
scrollinglist.
Figure32-1
· GridLayoutManager–Thelistitemsarepresentedingridformat.Thismanageris
bestusedwhenthelistitemsofareofuniformsize.
Figure32-2
· StaggeredGridLayoutManager-Thelistitemsarepresentedinastaggeredgrid
format.Thismanagerisbestusedwhenthelistitemsarenotofuniformsize.
Figure32-3
Forsituationswherenoneofthethreebuilt-inmanagersprovidethenecessarylayout,
customlayoutmanagersmaybeimplementedbysubclassingthe
RecyclerView.LayoutManagerclass.
EachlistitemdisplayedinaRecyclerViewiscreatedasaninstanceoftheViewHolder
class.TheViewHolderinstancecontainseverythingnecessaryfortheRecyclerViewto
displaythelistitem,includingtheinformationtobedisplayedandtheviewlayoutusedto
displaytheitem.
AswiththeListView,theRecyclerViewdependsonanadaptertoactastheintermediary
betweentheRecyclerViewinstanceandthedatathatistobedisplayedtotheuser.The
adapteriscreatedasasubclassoftheRecyclerView.Adapterclassandmustataminimum
implementthefollowingmethods,whichwillbecalledatvariouspointsbythe
RecyclerViewobjecttowhichtheadapterisassigned:
· getItemCount()–Thismethodmustreturnacountofthenumberofitemsthatareto
bedisplayedinthelist.
· onCreateViewHolder()–ThismethodcreatesandreturnsaViewHolderobject
initializedwiththeviewthatistobeusedtodisplaythedata.Thisviewistypically
createdbyinflatingtheXMLlayoutfile.
· onBindViewHolder()–ThismethodispassedtheViewHolderobjectcreatedbythe
onCreateViewHolder()methodtogetherwithanintegervalueindicatingthelistitem
thatisabouttobedisplayed.ContainedwithintheViewHolderobjectisthelayout
assignedbytheonCreateViewHolder()method.Itistheresponsibilityofthe
onBindViewHolder()methodtopopulatetheviewsinthelayoutwiththetextand
graphicscorrespondingtothespecifieditemandtoreturntheobjecttothe
RecyclerViewwhereitwillbepresentedtotheuser.
AddingaRecyclerViewtoalayoutissimplyamatterofaddingtheappropriateelementto
theXMLlayoutfileoftheactivityinwhichitistoappear.Forexample:
<?xmlversion=“1.0”encoding=“utf-8”?>
<android.support.design.widget.CoordinatorLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:fitsSystemWindows=“true”
tools:context=”.CardStuffActivity”>
<android.support.v7.widget.RecyclerView
android:id=”@+id/recycler_view”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
app:layout_behavior=”@string/appbar_scrolling_view_behavior”/>
<android.support.design.widget.AppBarLayout
android:layout_height=“wrap_content”
android:layout_width=“match_parent”
android:theme=”@style/AppTheme.AppBarOverlay”>
<android.support.v7.widget.Toolbarandroid:id=”@+id/toolbar”
android:layout_width=“match_parent”
android:layout_height=”?attr/actionBarSize”
android:background=”?attr/colorPrimary”
app:popupTheme=”@style/AppTheme.PopupOverlay”/>
</android.support.design.widget.AppBarLayout>
.
.
.
}
IntheaboveexampletheRecyclerViewhasbeenembeddedintotheCoordinatorLayoutof
amainactivitylayoutfilealongwiththeAppBarandToolbar.Thisprovidessome
additionalfeatures,suchasconfiguringtheToolbarandAppBartoscrolloffthescreen
whentheuserscrollsupwithintheRecyclerView(atopiccoveredinmoredetailinthe
chapterentitledWorkingwiththeAppBarandCollapsingToolbarLayouts).
32.2AnOverviewoftheCardView
TheCardViewclassisauserinterfaceviewthatallowsinformationtobepresentedin
groupsusingacardmetaphor.CardsareusuallypresentedinlistsusingaRecyclerView
instanceandmaybeconfiguredtoappearwithshadoweffectsandroundedcorners.
Figure32-4,forexample,showsthreeCardViewinstancesconfiguredtodisplayalayout
consistingofanImageViewandtwoTextViews:
Figure32-4
TheuserinterfacelayouttobepresentedwithaCardViewinstanceisdefinedwithinan
XMLlayoutresourcefileandloadedintotheCardViewatruntime.TheCardViewlayout
cancontainalayoutofanycomplexityusingthestandardlayoutmanagerssuchas
RelativeLayoutandLinearLayout.ThefollowingXMLlayoutfilerepresentsacardview
layoutconsistingofaRelativeLayoutandasingleImageView.Thecardisconfiguredto
beelevatedtocreateshadowingeffectandtoappearwithroundedcorners:
<?xmlversion=“1.0”encoding=“utf-8”?>
<android.support.v7.widget.CardView
xmlns:card_view=“http://schemas.android.com/apk/res-auto”
xmlns:android=“http://schemas.android.com/apk/res/android”
android:id=”@+id/card_view”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_margin=“5dp”
card_view:cardCornerRadius=“12dp”
card_view:cardElevation=“3dp”
card_view:contentPadding=“4dp”>
<RelativeLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:padding=“16dp”>
<ImageView
android:layout_width=“100dp”
android:layout_height=“100dp”
android:id=”@+id/item_image”
android:layout_alignParentLeft=“true”
android:layout_alignParentTop=“true”
android:layout_marginRight=“16dp”/>
</RelativeLayout>
</android.support.v7.widget.CardView>
WhencombinedwiththeRecyclerViewtocreateascrollablelistofcards,the
onCreateViewHolder()methodoftherecyclerviewinflatesthelayoutresourcefileforthe
card,assignsittotheViewHolderinstanceandreturnsittotheRecyclerViewinstance.
32.3AddingtheLibrariestotheProject
InordertousetheRecyclerViewandCardViewcomponents,thecorrespondinglibraries
mustbeaddedtotheGradlebuilddependenciesfortheproject.Withinthemodulelevel
build.gradlefile,therefore,thefollowinglinesneedtobeaddedtothedependencies
section:
dependencies{
.
.
compile‘com.android.support:recyclerview-v7:23.1.1’
compile‘com.android.support:cardview-v7:23.1.1’
}
32.4Summary
ThischapterhasintroducedtheAndroidRecyclerViewandCardViewcomponents.The
RecyclerViewprovidesaresourceefficientwaytodisplayscrollablelistsofviewswithin
anAndroidapp.TheCardViewisusefulwhenpresentinggroupsofdata(suchasalistof
namesandaddresses)intheformofcards.Aspreviouslyoutlined,anddemonstratedin
thetutorialcontainedinthenextchapter,theRecyclerViewandCardViewareparticularly
usefulwhencombined.
33.AnAndroidRecyclerViewandCardView
Tutorial
InthischapteranexampleprojectwillbecreatedthatmakesuseofboththeCardView
andRecyclerViewcomponentstocreateascrollablelistofcards.Thecompletedappwill
displayalistofcardscontainingimagesandtext.Inadditiontodisplayingthelistof
cards,theprojectwillbeimplementedsuchthatselectingacardcausesamessagestobe
displayedtotheuserindicatingwhichcardwastapped.
33.1CreatingtheCardDemoProject
CreateanewprojectinAndroidStudio,enteringCardDemointotheApplicationname
fieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo)andcontinuetoproceedthroughthescreens.
Inthenextchapter,thescrollhandlingfeaturesoftheAppBar,Toolbarand
CoordinatorLayoutlayoutwillbedemonstratedusingthisproject.Ontheactivity
selectionscreen,therefore,requestthecreationofablankactivitynamed
CardDemoActivitywithcorrespondinglayoutandmenufilesnamedactivity_card_demo
andmenu_card_demorespectively.ClickontheFinishbuttontoinitiatetheproject
creationprocess.
Oncetheprojecthasbeencreated,loadthecontent_card_demo.xmlfileintotheDesigner
toolandselectanddeletethe“HelloWorld”TextViewobject.
33.2RemovingtheFloatingActionButton
Sincetheblankactivitywasselected,thelayoutincludesafloatingactionbuttonwhichis
notrequiredforthisproject.Loadtheactivity_card_demo.xmllayoutfileintotheDesigner
tool,selectthefloatingactionbuttonandtapthekeyboarddeletekeytoremovetheobject
fromthelayout.EdittheCardDemoActivity.javafileandremovethefloatingactionbutton
codefromtheonCreatemethodasfollows:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_card_demo);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButtonfab=
(FloatingActionButton)findViewById(R.id.fab);
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
Snackbar.make(view,“Replacewithyourownaction”,
Snackbar.LENGTH_LONG)
.setAction(“Action”,null).show();
}
});
}
33.3AddingtheRecyclerViewandCardViewLibraries
WithintheProjecttoolwindowlocateandselectthemodulelevelbuild.gradlefileand
modifythedependenciessectionofthefiletoaddthesupportlibrarydependenciesforthe
RecyclerViewandCardView:
dependencies{
compilefileTree(dir:‘libs’,include:[‘*.jar’])
testCompile‘junit:junit:4.12’
compile‘com.android.support:appcompat-v7:23.1.1’
compile‘com.android.support:design:23.1.1’
compile‘com.android.support:recyclerview-v7:23.1.1’
compile‘com.android.support:cardview-v7:23.1.1’
}
Whenpromptedtodoso,resyncthenewGradlebuildconfigurationbyclickingonthe
SyncNowlinkinthewarningbar.
33.4DesigningtheCardViewLayout
ThelayoutoftheviewscontainedwithinthecardswillbedefinedwithinaseparateXML
layoutfile.WithintheProjecttoolwindowrightclickontheapp->res->layoutentry
andselecttheNew->Layoutresourcefilemenuoption.IntheNewResourceDialog
entercard_layoutintotheFilename:fieldandandroid.support.v7.widget.CardViewinto
therootelementfieldbeforeclickingontheOKbutton.
Loadthecard_layout.xmlfileintotheDesignertool,switchtoTextmodeandmodifythe
layoutsothatitreadsasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<android.support.v7.widget.CardView
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:card_view=“http://schemas.android.com/apk/res-auto”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:id=”@+id/card_view”
android:layout_margin=“5dp”
card_view:cardBackgroundColor=”#81C784”
card_view:cardCornerRadius=“12dp”
card_view:cardElevation=“3dp”
card_view:contentPadding=“4dp”>
<RelativeLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:padding=“16dp”>
<ImageView
android:layout_width=“100dp”
android:layout_height=“100dp”
android:id=”@+id/item_image”
android:layout_alignParentLeft=“true”
android:layout_alignParentTop=“true”
android:layout_marginRight=“16dp”
/>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:id=”@+id/item_title”
android:layout_toRightOf=”@+id/item_image”
android:layout_alignParentTop=“true”
android:textSize=“30sp”
/>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:id=”@+id/item_detail”
android:layout_toRightOf=”@+id/item_image”
android:layout_below=”@+id/item_title”
/>
</RelativeLayout>
</android.support.v7.widget.CardView>
33.5AddingtheRecyclerView
Selecttheactivity_card_demo.xmllayoutfileandmodifyittoaddtheRecyclerView
componentimmediatelybeforetheAppBarLayout:
<?xmlversion=“1.0”encoding=“utf-8”?>
<android.support.design.widget.CoordinatorLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:fitsSystemWindows=“true”
tools:contextcom.ebookfrenzy.carddemo.CardDemoActivity”>
<android.support.v7.widget.RecyclerView
android:id=”@+id/recycler_view”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
app:layout_behavior=”@string/appbar_scrolling_view_behavior”/>
<android.support.design.widget.AppBarLayout
android:layout_height=“wrap_content”
android:layout_width=“match_parent”
android:theme=”@style/AppTheme.AppBarOverlay”>
.
.
.
33.6CreatingtheRecyclerViewAdapter
Asoutlinedinthepreviouschapter,theRecyclerViewneedstohaveanadaptertohandle
thecreationofthelistitems.Addthisnewclasstotheprojectbyright-clickingontheapp
->java->com.ebookfrenzy.carddemoentryintheProjecttoolwindowandselectingthe
New->JavaClassmenuoption.IntheCreateNewClassdialog,enterRecyclerAdapter
intotheName:fieldbeforeclickingontheOKbuttontocreatethenewJavaclassfile.
EditthenewRecyclerAdapter.javafiletoaddsomeimportdirectivesandtodeclarethat
theclassnowextendsRecyclerView.Adapter.Ratherthancreateaseparateclassto
providethedatatobedisplayed,somebasicarrayswillalsobeaddedtotheadaptertoact
asthedatafortheapp:
packagecom.ebookfrenzy.carddemo;
importandroid.support.v7.widget.RecyclerView;
importandroid.util.Log;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.ImageView;
importandroid.widget.TextView;
importjava.util.ArrayList;
publicclassRecyclerAdapterextends
RecyclerView.Adapter<RecyclerAdapter.ViewHolder>{
privateString[]titles={“ChapterOne”,
“ChapterTwo”,
“ChapterThree”,
“ChapterFour”,
“ChapterFive”,
“ChapterSix”,
“ChapterSeven”,
“ChapterEight”};
privateString[]details={“Itemonedetails”,
“Itemtwodetails”,“Itemthreedetails”,
“Itemfourdetails”,“Itemfiledetails”,
“Itemsixdetails”,“Itemsevendetails”,
“Itemeightdetails”};
privateint[]images={R.drawable.android_image_1,
R.drawable.android_image_2,
R.drawable.android_image_3,
R.drawable.android_image_4,
R.drawable.android_image_5,
R.drawable.android_image_6,
R.drawable.android_image_7,
R.drawable.android_image_8};
}
WithintheRecyclerAdapterclasswenowneedourownimplementationofthe
ViewHolderclassconfiguredtoreferencetheviewelementsinthecard_layout.xmlfile.
RemainingwithintheRecyclerAdapter.javafileimplementthisclassasfollows:
.
.
.
publicclassRecyclerAdapterextends
RecyclerView.Adapter<RecyclerAdapter.ViewHolder>{
.
.
.
classViewHolderextendsRecyclerView.ViewHolder{
publicImageViewitemImage;
publicTextViewitemTitle;
publicTextViewitemDetail;
publicViewHolder(ViewitemView){
super(itemView);
itemImage=
(ImageView)itemView.findViewById(R.id.item_image);
itemTitle=
(TextView)itemView.findViewById(R.id.item_title);
itemDetail=
(TextView)itemView.findViewById(R.id.item_detail);
}
}
.
.
.
}
TheViewHolderclasscontainsanImageViewandtwoTextViewvariablestogetherwitha
constructormethodthatinitializesthosevariableswithreferencestothethreeviewitems
inthecard_layout.xmlfile.
ThenextitemtobeaddedtotheRecyclerAdapter.javafileistheimplementationofthe
onCreateViewHolder()method:
@Override
publicViewHolderonCreateViewHolder(ViewGroupviewGroup,inti){
Viewv=LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.card_layout,viewGroup,false);
ViewHolderviewHolder=newViewHolder(v);
returnviewHolder;
}
ThismethodwillbecalledbytheRecyclerViewtoobtainaViewHolderobject.Itinflates
theviewhierarchycard_layout.xmlfileandcreatesaninstanceofourViewHolderclass
initializedwiththeviewhierarchybeforereturningittotheRecyclerView.
ThepurposeoftheonBindViewHolder()methodistopopulatetheviewhierarchywithin
theViewHolderobjectwiththedatatobedisplayed.ItispassedtheViewHolderobject
andanintegervalueindicatingthelistitemthatistobedisplayed.Thismethodshould
nowbeadded,usingtheitemnumberasanindexintothedataarrays.Thisdataisthen
displayedonthelayoutviewsusingthereferencescreatedintheconstructormethodofthe
ViewHolderclass:
@Override
publicvoidonBindViewHolder(ViewHolderviewHolder,inti){
viewHolder.itemTitle.setText(titles[i]);
viewHolder.itemDetail.setText(details[i]);
viewHolder.itemImage.setImageResource(images[i]);
}
ThefinalrequirementfortheadapterclassisanimplementationofthegetItem()method
which,inthiscase,simplyreturnsthenumberofitemsinthetitlesarray:
@Override
publicintgetItemCount(){
returntitles.length;
}
33.7AddingtheImageFiles
InadditiontothetwoTextViews,thecardlayoutalsocontainsanImageViewonwhich
theRecycleradapterhasbeenconfiguredtodisplayimages.Beforetheprojectcanbe
testedtheseimagesmustbeadded.Theimagesthatwillbeusedfortheprojectarenamed
android_image_<n>.jpgandcanbefoundintheproject_iconsfolderofthesamplecode
downloadavailablefromthefollowingURL:
http://www.ebookfrenzy.com/retail/androidstudioA6/index.php
Locatetheseimagesinthefilesystemnavigatorforyouroperatingsystemandselectand
copytheeightimages.Rightclickontheapp->res->drawableentryintheProjecttool
windowandselectPastetoaddthefilestothefolder:
Figure33-1
33.8InitializingtheRecyclerViewComponent
AtthispointtheprojectconsistsofaRecyclerViewinstance,anXMLlayoutfileforthe
CardViewinstancesandanadapterfortheRecyclerView.Thelaststepbeforetestingthe
progresssofaristoinitializetheRecyclerViewwithalayoutmanager,createaninstance
oftheadapterandassignthatinstancetotheRecyclerViewobject.Forthepurposesofthis
example,theRecyclerViewwillbeconfiguredtousetheLinearLayoutManagerlayout
option.EdittheCardDemoActivity.javafileandmodifytheonCreate()methodto
implementthisinitializationcode:
packagecom.ebookfrenzy.carddemo;
importandroid.os.Bundle;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.support.v7.widget.LinearLayoutManager;
importandroid.support.v7.widget.RecyclerView;
publicclassCardDemoActivityextendsAppCompatActivity{
RecyclerViewrecyclerView;
RecyclerView.LayoutManagerlayoutManager;
RecyclerView.Adapteradapter;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_card_demo);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
recyclerView=
(RecyclerView)findViewById(R.id.recycler_view);
layoutManager=newLinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
adapter=newRecyclerAdapter();
recyclerView.setAdapter(adapter);
}
.
.
}
33.9TestingtheApplication
Compileandruntheapponaphysicaldeviceoremulatorsessionandscrollthroughthe
differentcarditemsinthelist:
Figure33-2
33.10RespondingtoCardSelections
Thelastphaseofthisprojectistomakethecardsinthelistselectablesothatclickingona
cardtriggersaneventwithintheapp.Forthisexample,thecardswillbeconfiguredto
presentamessageonthedisplaywhentappedbytheuser.Torespondtoclicks,the
ViewHolderclassneedstobemodifiedtoassignanonClickListeneroneachitemview.
EdittheRecyclerAdapter.javafileandmodifytheViewHolderclassdeclarationsothatit
readsasfollows:
importandroid.support.design.widget.Snackbar;
.
.
.
classViewHolderextendsRecyclerView.ViewHolder{
publicintcurrentItem;
publicImageViewitemImage;
publicTextViewitemTitle;
publicTextViewitemDetail;
publicViewHolder(ViewitemView){
super(itemView);
itemImage=(ImageView)itemView.findViewById(R.id.item_image);
itemTitle=(TextView)itemView.findViewById(R.id.item_title);
itemDetail=
(TextView)itemView.findViewById(R.id.item_detail);
itemView.setOnClickListener(newView.OnClickListener(){
@OverridepublicvoidonClick(Viewv){
}
});
}
}
WithinthebodyoftheonClickhandler,codecannowbeaddedtodisplayamessage
indicatingthatthecardhasbeenclicked.Giventhattheactionsperformedasaresultofa
clickwilllikelydependonwhichcardwastappeditisalsoimportanttoidentifythe
selectedcard.ThisinformationcanbeobtainedviaacalltothegetAdapterPosition()
methodoftheRecyclerView.ViewHolderclass.Remainingwithinthe
RecyclerAdapter.javafile,addcodetotheonClickhandlersoitreadsasfollows:
@override
publicvoidonClick(Viewv){
intposition=getAdapterPosition();
Snackbar.make(v,“Clickdetectedonitem”+position,
Snackbar.LENGTH_LONG)
.setAction(“Action”,null).show();
}
});
Thelasttaskistoenablethematerialdesignrippleeffectthatappearswhenitemsare
tappedwithinAndroidapplications.Thissimplyinvolvestheadditionofsomeproperties
tothedeclarationoftheCardViewinstanceinthecard_layout.xmlfileasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<android.support.v7.widget.CardView
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:card_view=“http://schemas.android.com/apk/res-auto”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:id=”@+id/card_view”
android:layout_margin=“5dp”
card_view:cardBackgroundColor=”#81C784”
card_view:cardCornerRadius=“12dp”
card_view:cardElevation=“3dp”
card_view:contentPadding=“4dp”
android:foreground=”?selectableItemBackground”
android:clickable=“true”>
Runtheapponceagainandverifythattappingacardinthelisttriggersboththestandard
rippleeffectatthepointofcontactandtheappearanceofaSnackbarreportingthenumber
oftheselecteditem.
33.11Summary
ThischapterhasworkedthroughthestepsinvolvedincombiningtheCardViewand
RecyclerViewcomponentstodisplayascrollablelistofcardbaseditems.Theexample
alsocoveredthedetectionofclicksonlistitems,includingtheidentificationofthe
selecteditemandtheenablingoftherippleeffectvisualfeedbackonthetappedCardView
instance.
34.WorkingwiththeAppBarandCollapsing
ToolbarLayouts
Inthischapterwewillbeexploringthewaysinwhichtheappbarwithinanactivity
layoutcanbecustomizedandmadetoreacttothescrollingeventstakingplacewithin
otherviewsonthescreen.BymakinguseoftheCoordinatorLayoutinconjunctionwith
theAppBarLayoutandCollapsingToolbarLayoutcontainers,theappbarcanbe
configuredtodisplayanimageandtoanimateinandoutofview.Anupwardscrolling
motiononalist,forexample,canbeconfiguredsothattheappbarrecedesfromviewand
thenreappearswhenadownwardscrollingmotionisperformed.
Beginningwithanoverviewoftheelementsthatcancompriseanappbar,thischapterwill
thenworkthroughavarietyofexamplesofappbarconfiguration.
34.1TheAnatomyofanAppBar
Theappbaristheareathatappearsatthetopofthedisplaywhenanappisrunningand
canbeconfiguredtocontainavarietyofdifferentitemsincludingthestatusbar,toolbar,
tabbarandaflexiblespacearea.Figure34-1,forexample,showsanappbarcontaininga
statusbar,toolbarandtabbar:
Figure34-1
Theflexiblespaceareacanbefilledbyablankbackgroundcoloror,asshowninFigure
34-2,animagedisplayedonanImageViewobject:
Figure34-2
Aswillbedemonstratedintheremainderofthischapter,ifthemaincontentareaofthe
activityuserinterfacelayoutcontainsscrollablecontent,theelementsoftheappbarcan
beconfiguredtoexpandandcontractasthecontentonthescreenisscrolled.
34.2TheExampleProject
Forthepurposesofthisexample,changeswillbemadetotheCardDemoprojectcreated
inthepreviouschapterentitledAnAndroidRecyclerViewandCardViewTutorial.Begin
bylaunchingAndroidStudioandloadingthisproject.
Oncetheprojecthasloaded,runtheappandnotewhenscrollingthelistupwardsthatthe
toolbarremainsvisibleasshowninFigure34-3:
Figure34-3
Thefirststepistomakesomeconfigurationchangessothatthetoolbarcontractsduring
anupwardscrollingmotion,andthenexpandsonadownwardscroll.
34.3CoordinatingtheRecyclerViewandToolbar
Loadtheactivity_card_demo.xmlfileintotheDesignertool,switchtotextmodeand
reviewtheXMLlayoutdesign,thehierarchyofwhichisrepresentedbythediagramin
Figure34-4:
Figure34-4
AtthetoplevelofthehierarchyistheCoordinatorLayoutwhich,asthenamesuggests,
coordinatestheinteractionsbetweenthevariouschildviewelementsitcontains.As
highlightedinWorkingwiththeFloatingActionButtonandSnackBar,forexample,the
CoordinatorLayoutautomaticallyslidesthefloatingactionbuttonupwardsto
accommodatetheappearanceofaSnackbarwhenitappears,thenmovesthebuttonback
downafterthebarisdismissed.
TheCoordinatorLayoutcansimilarlybeusedtocauseelementsoftheappbartoslidein
andoutofviewbasedonthescrollingactionofcertainviewswithintheviewhierarchy.
OnesuchelementwithinthelayouthierarchyshowninFigure34-4istheRecyclerView.
Toachievethiscoordinatedbehavior,itisnecessarytosetpropertiesonboththeelement
onwhichscrollingtakesplaceandtheelementswithwhichthescrollingistobe
coordinated.
Onthescrollingelement(inthiscasetheRecyclerView)theandroid:layout_behavior
propertymustbesettoappbar_scrolling_view_behavior.Withinthe
activity_card_demo.xmlfile,locatetheRecyclerViewelementandnotethatthisproperty
wasalreadysetinthepreviouschapter:
<android.support.v7.widget.RecyclerView
android:id=”@+id/recycler_view”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
app:layout_behavior=”@string/appbar_scrolling_view_behavior”/>
TheonlychildofAppBarLayoutintheviewhierarchyistheToolbar.Tomakethetoolbar
reacttothescrolleventstakingplaceintheRecyclerViewtheapp:layout_scrollFlags
propertymustbesetonthiselement.Thevalueassignedtothispropertywilldependon
thenatureoftheinteractionrequiredandmustconsistofoneormoreofthefollowing:
· scroll–Indicatesthattheviewistobescrolledoffthescreen.Ifthisisnotsettheview
willremainpinnedatthetopofthescreenduringscrollingevents.
· enterAlways–Whenusedinconjunctionwiththescrolloption,anupwardscrolling
motionwillcausetheviewtoretract.Anydownwardscrollingmotioninthismodewill
causetheviewtore-appear.
· enterAlwaysCollapsed–Whensetonaview,thatviewwillnotexpandfromthe
collapsedstateuntilthedownwardscrollingmotionreachesthelimitofthelist.Ifthe
minHeightpropertyisset,theviewwillappearduringtheinitialscrollingmotionbut
onlyuntiltheminimumheightisreached.Itwillthenremainatthatheightandwillnot
expandfullyuntilthetopofthelistisreached.Notethisoptiononlyworkswhenused
inconjunctionwithboththeenterAlwaysandscrolloptions.Forexample:
app:layout_scrollFlags=“scroll|enterAlways|enterAlwaysCollapsed”
android:minHeight=“20dp”
· exitUntilCollapsed–Whenset,theviewwillcollapseduringanupwardscrolling
motionuntiltheminHeightthresholdismet,atwhichpointitwillremainatthatheight
untilthescrolldirectionchanges.
Forthepurposesofthisexample,thescrollandenterAlwaysoptionswillbesetonthe
Toolbarasfollows:
<android.support.v7.widget.Toolbar
android:id=”@+id/toolbar”
android:layout_width=“match_parent”
android:layout_height=”?attr/actionBarSize”
android:background=”?attr/colorPrimary”
app:popupTheme=”@style/AppTheme.PopupOverlay”
app:layout_scrollFlags=“scroll|enterAlways”/>
Withtheappropriatepropertiesset,runtheapponceagainandmakeanupwardscrolling
motionintheRecyclerViewlist.Thisshouldcausethetoolbartocollapseoutofview
(Figure34-5).Adownwardscrollingmotionshouldcausethetoolbartore-appear.
Figure34-5
34.4IntroducingtheCollapsingToolbarLayout
TheCollapsingToolbarLayoutcontainerenhancesthestandardtoolbarbyprovidinga
greaterrangeofoptionsandlevelofcontroloverthecollapsingoftheappbarandits
childreninresponsetocoordinatedscrollingactions.TheCollapsingToolbarLayoutclass
isintendedtobeaddedasachildoftheAppBarLayoutandprovidesfeaturessuchas
automaticallyadjustingthefontsizeofthetoolbartitleasthetoolbarcollapsesand
expands.Aparallaxmodeallowsdesignatedcontentintheappbartofadefromviewasit
collapseswhileapinmodeallowselementsoftheappbartoremaininfixedposition
duringthecontraction.
Ascrimoptionisalsoavailabletodesignatethecolortowhichthetoolbarshould
transitionduringthecollapsesequence.
Toseethesefeaturesinaction,theappbarcontainedintheactivity_card_demo.xmlfile
willbemodifiedtousetheCollapsingToolbarLayoutclasstogetherwiththeadditionofan
ImageViewtobetterdemonstratetheeffectofparallaxmode.Thenewviewhierarchythat
makesuseoftheCollapsingToolbarLayoutisrepresentedbythediagraminFigure34-6:
Figure34-6
Loadtheactivity_card_demo.xmlfileintotheDesignertoolinTextmodeandmodifythe
layoutsothatitreadsasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<android.support.design.widget.CoordinatorLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:fitsSystemWindows=“true”
tools:context=”.CardDemoActivity”>
<android.support.v7.widget.RecyclerView
android:id=”@+id/recycler_view”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
app:layout_behavior=”@string/appbar_scrolling_view_behavior”/>
<android.support.design.widget.AppBarLayout
android:layout_height=“200dp”
android:layout_width=“match_parent”
android:theme=”@style/AppTheme.AppBarOverlay”>
<android.support.design.widget.CollapsingToolbarLayout
android:id=”@+id/collapsing_toolbar”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
app:layout_scrollFlags=“scroll|enterAlways”
android:fitsSystemWindows=“true”
app:contentScrim=”?attr/colorPrimary”
app:expandedTitleMarginStart=“48dp”
app:expandedTitleMarginEnd=“64dp”>
<ImageView
android:id=”@+id/backdrop”
android:layout_width=“match_parent”
android:layout_height=“200dp”
android:scaleType=“centerCrop”
android:fitsSystemWindows=“true”
app:layout_collapseMode=“parallax”
android:src=”@drawable/appbar_image”/>
<android.support.v7.widget.Toolbar
android:id=”@+id/toolbar”
android:layout_width=“match_parent”
android:layout_height=”?attr/actionBarSize”
android:background=”?attr/colorPrimary”
app:popupTheme=”@style/ThemeOverlay.AppCompat.Light”
app:layout_scrollFlags=“scroll|enterAlways”
app:layout_collapseMode=“pin”/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<includelayout=”@layout/content_card_demo”/>
</android.support.design.widget.CoordinatorLayout>
Inadditiontoaddingthenewelementstothelayoutabove,theapp:popupThemeattribute
hasalsobeenchangedtousetheAndroidAppCompatLightstyle.Thisstylehasthe
advantageofprovidingatransparenttoolbarallowingmoreoftheimagetobevisiblein
theappbar.
Usingthefilesystemnavigatorforyouroperatingsystem,locatetheappbar_image.jpg
imagefileintheproject_iconsfolderofthecodesampledownloadforthebookandcopy
it.Right-clickontheapp->res->drawablesentryintheProjecttoolwindowandselect
Pastefromtheresultingmenu.
Whenrun,theappbarshouldappearasillustratedinFigure34-7:
Figure34-7
Scrollingthelistupwardswillcausetheappbartograduallycollapse.Duringthe
contraction,theimagewillfadetothecolordefinedbythescrimpropertywhilethetitle
textfontsizereducesatacorrespondingrateuntilonlythetoolbarisvisible:
Figure34-8
Thetoolbarhasremainedinplaceastheflexibleareacollapsesbecausethetoolbar
elementintheactivity_card_demo.xmlfilewasconfiguredtousepinmode:
app:layout_collapseMode=“pin”
Hadthecollapsemodebeensettoparallaxthetoolbarwouldhaveretractedalongwiththe
imageview.
Continuingtheupwardscrollingmotionwillcausethetoolbartoalsocollapseleaving
onlythestatusbarvisible:
Figure34-9
SincethescrollflagspropertyfortheCollapsingToolbarLayoutelementincludethe
enterAlwaysoption,adownwardscrollingmotionwillcausetheappbartoexpandonce
again.
34.5ChangingtheTitleandScrimColor
Asafinaltask,edittheCardDemoActivity.javafileandaddsomecodetotheonCreate()
methodtochangethetitletextonthecollapsinglayoutmanagerinstanceandtoseta
differentscrimcolor(notethatthescrimcolormayalsobesetwithinthelayoutresource
file):
packagecom.ebookfrenzy.carddemo;
importandroid.graphics.Color;
importandroid.os.Bundle;
importandroid.support.v7.widget.LinearLayoutManager;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.RecyclerView;
importandroid.support.v7.widget.Toolbar;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.support.design.widget.CollapsingToolbarLayout;
importandroid.graphics.Color;
.
.
.
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_card_demo);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
CollapsingToolbarLayoutcollapsingToolbarLayout=
(CollapsingToolbarLayout)findViewById(R.id.collapsing_toolbar);
collapsingToolbarLayout.setTitle(“MyToolbarTitle”);
collapsingToolbarLayout.setContentScrimColor(Color.GREEN);
recyclerView=
(RecyclerView)findViewById(R.id.recycler_view);
layoutManager=newLinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
adapter=newRecyclerAdapter();
recyclerView.setAdapter(adapter);
}
Runtheapponelasttimeandnotethatthenewtitleappearsintheappbarandthat
scrollingnowcausesthetoolbartotransitiontogreenasitretractsfromview.
34.6Summary
TheappbarthatappearsatthetopofmostAndroidappscanconsistofanumberof
differentelementsincludingatoolbar,tablayoutandevenanimageview.When
embeddedinaCoordinatorLayoutparentanumberofdifferentoptionsareavailableto
controlthewayinwhichtheappbarbehavesinresponsetoscrollingeventsinthemain
contentoftheactivity.Forgreatercontroloverthisbehavior,theCollapsingToolbarLayout
managerprovidesarangeofadditionallevelsofcontroloverthewaytheappbarcontent
expandsandcontractsinrelationtoscrollingactivity.
35.ImplementinganAndroidNavigation
Drawer
Inthis,thefinalofthisseriesofchaptersdedicatedtotheAndroidmaterialdesign
components,thetopicofthenavigationdrawerwillbecovered.Comprisingthe
DrawerLayout,NavigationViewandActionBarDrawerToggleclasses,anavigationdrawer
takestheformofapanelappearingfromtheleft-handedgeofscreenwhenselectedbythe
userandcontainingarangeofoptionsandsub-optionswhichcanbeselectedtoperform
taskswithintheapplication.
35.1AnOverviewoftheNavigationDrawer
Thenavigationdrawerisapanelthatslidesoutfromtheleftofthescreenandcontainsa
rangeofoptionsavailableforselectionbytheuser,typicallyintendedtofacilitate
navigationtosomeotherpartoftheapplication.Figure35-1,forexample,showsthe
navigationdrawerbuiltintotheGooglePlayapp:
Figure35-1
Anavigationdrawerismadeupofthefollowingcomponents:
· AninstanceoftheDrawerLayoutcomponent.
· AninstanceoftheNavigationViewcomponentembeddedasachildofthe
DrawerLayout.
· Amenuresourcefilecontainingtheoptionstobedisplayedwithinthenavigation
drawer.
· Anoptionallayoutresourcefilecontainingthecontenttoappearintheheadersection
ofthenavigationdrawer.
· AlistenerassignedtotheNavigationViewtodetectwhenanitemhasbeenselectedby
theuser.
· AnActionBarDrawerToggleinstancetoconnectandsynchronizethenavigation
drawertotheappbar.TheActionBarDrawerTogglealsodisplaysthedrawerindicator
intheappbarwhichpresentsthedrawerwhentapped.
ThefollowingXMLlistingshowsanexamplenavigationdrawerimplementationwhich
alsocontainsanincludedirectiveforasecondlayoutfilecontainingthestandardappbar
layout.
<?xmlversion=“1.0”encoding=“utf-8”?>
<android.support.v4.widget.DrawerLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:id=”@+id/drawer_layout”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:fitsSystemWindows=“true”
tools:openDrawer=“start”>
<include
layout=”@layout/app_bar_main”
android:layout_width=“match_parent”
android:layout_height=“match_parent”/>
<android.support.design.widget.NavigationView
android:id=”@+id/nav_view”
android:layout_width=“wrap_content”
android:layout_height=“match_parent”
android:layout_gravity=“start”
android:fitsSystemWindows=“true”
app:headerLayout=”@layout/nav_header_main”
app:menu=”@menu/activity_main_drawer”/>
</android.support.v4.widget.DrawerLayout>
35.2OpeningandClosingtheDrawer
Whentheusertapsthedrawerindicatorintheappbar,thedrawerwillautomatically
appear.Whetherthedraweriscurrentlyopenmaybeidentifiedviaacalltothe
isDrawerOpen()methodoftheDrawerLayoutobjectpassingthroughagravitysetting:
if(drawer.isDrawerOpen(GravityCompat.START)){
//Drawerisopen
}
TheGravityCompat.STARTsettingindicatesadraweropenalongthex-axisofthelayout.
AnopendrawermaybeclosedviaacalltothecloseDrawer()method:
drawer.closeDrawer(GravityCompat.START);
Conversely,thedrawermaybeopenedusingtheopenDrawer()method:
drawer.openDrawer(GravityCompat.START);
35.3RespondingtoDrawerItemSelections
Handlingselectionswithinanavigationdrawerisatwo-stepprocess.Thefirststepisto
specifyanobjecttoactastheitemselectionlistener.Thisisachievedbyobtaininga
referencetotheNavigationViewinstanceinthelayoutandmakingacalltoits
setNavigationItemSelectedListener()method,passingthroughareferencetotheobjectthat
istoactasthelistener.Typicallythelistenerwillbeconfiguredtobethecurrentactivity,
forexample:
NavigationViewnavigationView=
(NavigationView)findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
ThesecondstepistoimplementtheonNavigationItemSelected()methodwithinthe
designatedlistener.Thismethodiscalledeachtimeaselectionismadewithinthe
navigationdrawerandispassedasanargumentareferencetotheselectedmenuitem
whichcanbeusedtoextractandidentifytheselecteditemid:
@Override
publicbooleanonNavigationItemSelected(MenuItemitem){
//Handlenavigationviewitemclickshere.
intid=item.getItemId();
if(id==R.id.nav_camera){
}elseif(id==R.id.nav_gallery){
}elseif(id==R.id.nav_slideshow){
}elseif(id==R.id.nav_manage){
}elseif(id==R.id.nav_share){
}elseif(id==R.id.nav_send){
}
DrawerLayoutdrawer=
(DrawerLayout)findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
returntrue;
}
Ifitisappropriatetodoso,andasoutlinedintheaboveexample,itisalsoimportantto
closethedraweraftertheitemhasbeenselected.
35.4UsingtheNavigationDrawerActivityTemplate
Whileitispossibletoimplementanavigationdrawerwithinanyactivity,theeasiest
approachistoselecttheNavigationDrawerActivitytemplatewhencreatinganewproject
oraddinganewactivitytoanexistingproject:
Figure35-2
Thistemplatecreatesallofthecomponentsandrequirementsnecessarytoimplementa
navigationdrawer,requiringonlythatthedefaultsettingsbeadjustedwherenecessary.
35.5CreatingtheNavigationDrawerTemplateProject
CreateanewprojectinAndroidStudio,enteringNavDrawerDemointotheApplication
namefieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingonthe
Nextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofaNavigationDrawerActivitynamedNavDrawerActivitywitha
correspondinglayoutfilenamedactivity_nav_drawer.ClickontheFinishbuttonto
initiatetheprojectcreationprocess.
35.6TheTemplateLayoutResourceFiles
Oncetheprojecthasbeencreated,itwillcontainthefollowingXMLresourcefileslocated
underapp->res->layoutintheProjecttoolwindow:
· activity_nav_drawer.xml–Thisisthetoplevellayoutresourcefile.Itcontainsthe
DrawerLayoutcontainerandtheNavigationViewchild.TheNavigationView
declarationinthisfileindicatesthatthelayoutforthedrawerheaderiscontained
withinthenav_header_nav_drawer.xmlfileandthatthemenuoptionsforthedrawer
arelocatedintheactivity_nav_drawer_drawer.xmlfile.Inaddition,itincludesa
referencetotheapp_bar_nav_drawer.xmlfile.
· app_bar_nav_drawer.xml–Thislayoutresourcefileisincludedbythe
activity_nav_drawer.xmlfileandisthestandardappbarlayoutfilebuiltwithina
CoordinatorLayoutcontainerascoveredintheprecedingchapters.Aswithprevious
examplesthisfilealsocontainsadirectivetoincludethecontentfilewhich,inthis
case,isnamedcontent_nav_drawer.xml.
· content_nav_drawer.xml–Thestandardlayoutforthecontentareaoftheactivity
layout.ThislayoutconsistsofaRelativeLayoutcontaineranda“HelloWorld!”
TextView.
· nav_header_nav_drawer.xml–ReferencedbytheNavigationViewelementinthe
activity_nav_drawer.xmlfilethisisaplaceholderheaderlayoutforthedrawer.
35.7TheHeaderColoringResourceFile
Inadditiontothelayoutresourcefiles,theside_nav_bar.xmlfilelocatedunderapp->
drawablesmaybemodifiedtochangethecolorsappliedtothedrawerheader.Bydefault
thisfiledeclaresarectangularcolorgradienttransitioninghorizontallyfromdarktolight
green.
35.8TheTemplateMenuResourceFile
Themenuofoptionspresentedwithinthenavigationdrawercanbefoundinthe
activity_nav_drawer_drawer.xmlfilelocatedunderapp->res->menuintheprojecttool
window.Bydefault,themenuconsistsofarangeoftextbasedtitleswithaccompanying
icons(thefilesforwhicharealllocatedinthedrawablesfolder).Formoredetailson
menuresourcefiles,refertothechapterentitledCreatingandManagingOverflowMenus
onAndroid.
35.9TheTemplateCode
TheonCreate()methodlocatedintheNavDrawerActivity.javafileperformsmuchofthe
initializationworkrequiredforthenavigationdrawer:
DrawerLayoutdrawer=(DrawerLayout)findViewById(R.id.drawer_layout);
ActionBarDrawerToggletoggle=newActionBarDrawerToggle(
this,drawer,toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close);
drawer.setDrawerListener(toggle);
toggle.syncState();
NavigationViewnavigationView=(NavigationView)
findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
ThecodeobtainsareferencetotheDrawerLayoutobjectandthencreatesan
ActionBarDrawerToggleobject,initializingitwithareferencetothecurrentactivity,the
DrawerLayoutobject,thetoolbarcontainedwithintheappbarandtwostringsdescribing
thedraweropeningandclosingactionsforaccessibilitypurposes.The
ActionBarDrawerToggleobjectisthenassignedasthelistenerforthedrawerand
synchronized.
ThecodethenobtainsareferencetotheNavigationViewinstancebeforedeclaringthe
currentactivityasthelistenerforanyitemselectionsmadewithinthenavigationdrawer.
Sincethecurrentactivityisnowdeclaredasthedrawerlistener,the
onNavigationItemSelected()methodisalsoimplementedintheNavDrawerActivity.java
file.Theimplementationofthismethodintheactivitymatchesthatoutlinedearlierinthis
chapter.
Finally,anadditionalmethodnamedonBackPressed()hasbeenaddedtotheactivityby
AndroidStudio.Thismethodisaddedtohandlesituationswherebytheactivityhasa
“back”buttontoreturntoapreviousactivityscreen.Thecodeinthismethodensuresthat
thedrawerisclosedbeforetheappswitchesbacktothepreviousactivityscreen:
@Override
publicvoidonBackPressed(){
DrawerLayoutdrawer=
(DrawerLayout)findViewById(R.id.drawer_layout);
if(drawer.isDrawerOpen(GravityCompat.START)){
drawer.closeDrawer(GravityCompat.START);
}else{
super.onBackPressed();
}
}
35.10RunningtheApp
Compileandruntheprojectandnotetheappearanceofthedrawerindicatoras
highlightedinFigure35-3:
Figure35-3
Taptheindicatorandnotethattheiconrotatesasthenavigationdrawerappears:
Figure35-4
35.11Summary
Thenavigationdrawerisapanelthatextendsfromtheleft-handedgeofanactivityscreen
whenanindicatorisselectedbytheuser.Thedrawercontainsmenuoptionsavailablefor
selectionandservesasausefulapplicationnavigationtoolthatconformstothematerial
designguidelines.Althoughitispossibletoaddanavigationdrawertoanyactivity,the
quickesttechniqueistousetheAndroidStudioNavigationDrawerActivitytemplateand
thencustomizeitforspecificrequirements.Thischapterhasoutlinedthecomponentsthat
makeupanavigationdrawerandhighlightedhowtheseareimplementedwithinthe
template.
36.AnAndroidStudioMaster/DetailFlow
Tutorial
ThischapterwillexplaintheconceptoftheMaster/Detailuserinterfacedesignbefore
exploringindetailtheelementsthatmakeuptheMaster/DetailFlowtemplateincluded
withAndroidStudio.Anexampleapplicationwillthenbecreatedthatdemonstratesthe
stepsinvolvedinmodifyingthetemplatetomeetthespecificneedsoftheapplication
developer.
36.1TheMaster/DetailFlow
Amaster/detailflowisaninterfacedesignconceptwherebyalistofitems(referredtoas
themasterlist)isdisplayedtotheuser.Onselectinganitemfromthelist,additional
informationrelatingtothatitemisthenpresentedtotheuserwithinadetailpane.An
emailapplicationmight,forexample,consistofamasterlistofreceivedmessages
consistingoftheaddressofthesenderandthesubjectofthemessage.Uponselectionofa
messagefromthemasterlist,thebodyoftheemailmessagewouldappearwithinthe
detailpane.
OntabletsizedAndroiddevicedisplaysinlandscapeorientation,themasterlistappearsin
anarrowverticalpanelalongthelefthandedgeofthescreen.Theremainderofthe
displayisdevotedtothedetailpaneinanarrangementreferredtoastwo-panemode.
Figure36-1forexample,showsthemaster/detailtwo-panearrangementwithmasteritems
listedandthecontentofitemonedisplayedinthedetailpane:
Figure36-1
Onsmaller,phonesizedAndroiddevices,themasterlisttakesuptheentirescreenandthe
detailpaneappearsonaseparatescreenwhichappearswhenaselectionismadefromthe
masterlist.Inthismode,thedetailscreenincludesanactionbarentrytoreturntothe
masterlist.Figure36-2forexample,illustratesboththemasteranddetailscreensforthe
sameitemlistona4”phonescreen:
Figure36-2
36.2CreatingaMaster/DetailFlowActivity
Inthenextsectionofthischapter,thedifferentelementsthatcomprisetheMaster/Detail
Flowtemplatewillbecoveredinsomedetail.Thisisbestachievedbycreatingaproject
usingtheMaster/DetailFlowtemplatetousewhileworkingthroughtheinformation.This
projectwillsubsequentlybeusedasthebasisforthetutorialattheendofthechapter.
CreateanewprojectinAndroidStudio,enteringMasterDetailFlowintotheApplication
namefieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingonthe
Nextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI1:Android4.0(IceCreamSandwich).WhenselectingaminimumSDKof
lessthanAPI14,AndroidStudiocreatesaMaster/DetailFlowprojecttemplatethatuses
anoutdatedandlessefficientapproachtohandlingthelistofitemsdisplayedinthemaster
panel.Aftertheprojecthasbeencreated,theminSdkVersionsettinginthebuild.gradle
(module:app)filelocatedunderGradleScriptsintheProjecttoolwindowmaybe
changedtoAPI8totargetolderAndroidversionsifrequired.
WhentheactivityconfigurationscreenoftheNewProjectdialogappears,selectthe
Master/DetailFlowoptionasillustratedinFigure36-3beforeclickingonNextonce
again:
Figure36-3
Thenextscreen(Figure36-4)providestheopportunitytoconfiguretheobjectsthatwill
bedisplayedwithinthemaster/detailactivity.Inthetutoriallaterinthischapter,the
masterlistwillcontainanumberofwebsitenameswhich,whenselected,willloadthe
chosenwebsiteintoawebviewwithinthedetailpane.Withtheserequirementsinmind,
settheObjectKindfieldto“Website”andtheObjectKindPluralandTitlesettingsto
“Websites”.
Figure36-4
Finally,clickfinishtocreatethenewMaster/DetailFlowbasedapplicationproject.
36.3TheAnatomyoftheMaster/DetailFlowTemplate
OnceanewprojecthasbeencreatedusingtheMaster/DetailFlowtemplate,anumberof
JavaandXMLlayoutresourcefileswillhavebeencreatedautomatically.Itisimportantto
gainanunderstandingofthesedifferentfilesinordertobeabletoadaptthetemplateto
specificrequirements.AreviewoftheprojectwithintheAndroidStudioProjecttool
windowwillrevealthefollowingfiles,where<item>isreplacedbytheObjectKind
namethatwasspecifiedwhentheprojectwascreated(thisbeing“Website”inthecaseof
theMasterDetailFlowexampleproject):
· activity_<item>_list.xml–Thetoplevellayoutfileforthemasterlist,thisfileis
loadedbythe<item>ListActivityclass.Thislayoutcontainsatoolbar,afloatingaction
buttonandincludesthe<item>_list.xmlfile.
· <item>ListActivity.java–Theactivityclassresponsiblefordisplayingandmanaging
themasterlist(declaredintheactivity_<item>_list.xmlfile)andforbothdisplaying
andrespondingtotheselectionofitemswithinthatlist.
· <item>_list.xml–Thelayoutfileusedtodisplaythemasterlistofitemsinsingle-pane
modewherethemasterlistanddetailpaneappearondifferentscreens.Thisfile
consistsofaRecyclerViewobjectconfiguredtousetheLinearLayoutManager.The
RecyclerViewelementdeclaresthateachiteminthemasterlististobedisplayedusing
thelayoutdeclaredwithinthe<item>_list_content.xmlfile.
· <item>_list.xml(w900dp)–Thelayoutfileforthemasterlistinthetwo-panemode
usedontabletsinlandscape(wherethemasterlistanddetailpaneappearsidebyside).
ThisfilecontainsahorizontalLinearLayoutparentwithinwhichresideaRecyclerView
todisplaythemasterlistandaFrameLayouttocontainthecontentofthedetailpane.
Aswiththesingle-panevariantofthisfile,theRecyclerViewelementdeclaresthat
eachiteminthelistbedisplayedusingthelayoutcontainedwithinthe
<item>_list_content.xmlfile.
· <item>_content_list.xml–Thisfilecontainsthelayouttobeusedforeachiteminthe
masterlist.BydefaultthisconsistsoftwoTextViewobjectsembeddedinahorizontal
LinearLayoutbutmaybechangedtomeetspecificapplicationneeds.
· activity_<item>_detail.xml–Thetoplevellayoutfileusedforthedetailpanewhen
runninginsingle-panemode.Thislayoutcontainsanappbar,collapsingtoolbar,
scrollingviewandafloatingactionbutton.Atruntimethislayoutfileisloadedand
displayedbythe<item>DetailActivityclass.
· <item>DetailActivity.java–Thisclassdisplaysthelayoutdefinedinthe
activity_<item>_detail.xmlfile.Theclassalsoinitializesanddisplaysthefragment
containingthedetailcontentdefinedintheitem_detail.xmland
<item>DetailFragment.javafiles.
· <item>_detail.xml–Thelayoutfilethataccompaniesthe<item>DetailFragment
classandcontainsthelayoutforthecontentareaofthedetailpane.Bydefaultthis
containsasingleTextViewobjectbutmaybechangedtomeetyourspecificapplication
needs.Insingle-panemode,thisfragmentisloadedintothelayoutdefinedbythe
activity_<item>_detail.xmlfile.Intwo-panemode,thislayoutisloadedintothe
FrameLayoutareaofthe<item>_list.xml(w900dp)filesothatitappearsadjacentto
themasterlist.
· <item>DetailFragment.java–Thefragmentclassfileresponsiblefordisplayingthe
<item>_detail.xmllayoutandpopulatingitwiththecontenttobedisplayedinthe
detailpane.Thisfragmentisinitializedanddisplayedwithinthe
<item>DetailActivity.javafiletoprovidethecontentdisplayedwithinthe
activity_<item>_detail.xmllayoutforsingle-panemodeandthe<item>_list.xml
(w900dp)layoutfortwo-panemode.
· DummyContent.java–Aclassfileintendedtoprovidesampledataforthetemplate.
Thisclasscaneitherbemodifiedtomeetapplicationneeds,orreplacedentirely.By
default,thecontentprovidedbythisclasssimplyconsistsofanumberofstringitems.
36.4ModifyingtheMaster/DetailFlowTemplate
WhilethestructureoftheMaster/DetailFlowtemplatecanappearconfusingatfirst,the
conceptswillbecomeclearerasthedefaulttemplateismodifiedintheremainderofthis
chapter.Aswillbecomeevident,muchofthefunctionalityprovidedbythetemplatecan
remainunchangedformanymaster/detailimplementationrequirements.
Intherestofthischapter,theMasterDetailFlowprojectwillbemodifiedsuchthatthe
masterlistdisplaysalistofwebsitenamesandthedetailpanealteredtocontaina
WebViewobjectinsteadofthecurrentTextView.Whenawebsiteisselectedbytheuser,
thecorrespondingwebpagewillsubsequentlyloadanddisplayinthedetailpane.
36.5ChangingtheContentModel
ThecontentfortheexampleasitcurrentlystandsisdefinedbytheDummyContentclass
file.Begin,therefore,byselectingtheDummyContent.javafile(locatedintheProjecttool
windowintheapp->java->com.ebookfrenzy.masterdetailflow->dummyfolder)and
reviewingthecode.Atthebottomofthefileisadeclarationforaclassnamed
DummyItemwhichiscurrentlyabletostoretwoStringobjectsrepresentingacontent
stringandanID.Theupdatedproject,ontheotherhand,willneedeachitemobjectto
containanIDstring,astringforthewebsitename,andastringforthecorresponding
URLofthewebsite.Toaddthesefeatures,modifytheDummyItemclasssothatitreadsas
follows:
publicstaticclassDummyItem{
publicStringid;
publicStringwebsite_name;
publicStringwebsite_url;
publicDummyItem(Stringid,Stringwebsite_name,
Stringwebsite_url)
{
this.id=id;
this.website_name=website_name;
this.website_url=website_url;
}
@Override
publicStringtoString(){
returnwebsite_name;
}
}
NotethattheencapsulatingDummyContentclasscurrentlycontainsaforloopthatadds
25itemsbymakingmultiplecallstomethodsnamedcreateDummyItem()and
makeDetails().Muchofthiscodewillnolongerberequiredandshouldbedeletedfrom
theclassasfollows:
publicstaticMap<String,DummyItem>ITEM_MAP=newHashMap<String,
DummyItem>();
privatestaticfinalintCOUNT=25;
static{
//Addsomesampleitems.
for(inti=1;i<=COUNT;i++){
addItem(createDummyItem(i));
}
}
privatestaticvoidaddItem(DummyItemitem){
ITEMS.add(item);
ITEM_MAP.put(item.id,item);
}
privatestaticDummyItemcreateDummyItem(intposition){
returnnewDummyItem(String.valueOf(position),“Item”+position,
makeDetails(position));
}
privatestaticStringmakeDetails(intposition){
StringBuilderbuilder=newStringBuilder();
builder.append(“DetailsaboutItem:“).append(position);
for(inti=0;i<position;i++){
builder.append(“\nMoredetailsinformationhere.”);
}
returnbuilder.toString();
}
Thiscodeneedstobemodifiedtoinitializethedatamodelwiththerequiredwebsitedata:
publicstaticMap<String,DummyItem>ITEM_MAP=
newHashMap<String,DummyItem>();
static{
//Add3sampleitems.
addItem(newDummyItem(“1”,“eBookFrenzy”,
“http://www.ebookfrenzy.com”));
addItem(newDummyItem(“2”,“Amazon”,
“http://www.amazon.com”));
addItem(newDummyItem(“3”,“NewYorkTimes”,
“http://www.nytimes.com”));
}
ThecodenowtakesadvantageofthemodifiedDummyItemclasstostoreanID,website
nameandURLforeachitem.
36.6ChangingtheDetailPane
Thedetailinformationshowntotheuserwhenanitemisselectedfromthemasterlistis
currentlydisplayedviathelayoutcontainedinthewebsite_detail.xmlfile.Bydefaultthis
containsasingleviewintheformofaTextView.SincetheTextViewclassisnotcapable
ofdisplayingawebpage,thisneedstobechangedtoaWebViewobjectforthepurposes
ofthistutorial.Toachievethis,navigatetotheapp->res->layout->website_detail.xml
fileintheProjecttoolwindowanddoubleclickonittoloaditintotheDesignertool.
SwitchtoTextmodeanddeletethecurrentXMLcontentfromthefile.Replacethis
contentwiththefollowingXML:
<WebViewxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:id=”@+id/website_detail”
tools:context=
“com.ebookfrenzy.masterdetailflow.WebsiteDetailFragment”>
</WebView>
SwitchtoDesignmodeandverifythatthelayoutnowmatchesthatshowninFigure36-5:
Figure36-5
36.7ModifyingtheWebsiteDetailFragmentClass
AtthispointtheuserinterfacedetailpanehasbeenmodifiedbutthecorrespondingJava
classisstilldesignedforworkingwithaTextViewobjectinsteadofaWebView.Loadthe
sourcecodeforthisclassbydoubleclickingontheWebsiteDetailFragment.javafileinthe
Projecttoolwindow.
InordertoloadthewebpageURLcorrespondingtothecurrentlyselecteditemonlytwo
linesofcodeneedstobechanged.Oncethischangehasbeenmade,thecodeshouldread
asfollows(notealsotheadditionoftheimportdirectivefortheandroid.webkit.WebView
library):
packagecom.ebookfrenzy.masterdetailflow;
packagecom.ebookfrenzy.masterdetailflow;
importandroid.app.Activity;
importandroid.support.design.widget.CollapsingToolbarLayout;
importandroid.os.Bundle;
importandroid.support.v4.app.Fragment;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.TextView;
importandroid.webkit.WebView;
importcom.ebookfrenzy.masterdetailflow.dummy.DummyContent;
publicclassWebSiteDetailFragmentextendsFragment{
.
.
.
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
if(getArguments().containsKey(ARG_ITEM_ID)){
//Loadthedummycontentspecifiedbythefragment
//arguments.Inareal-worldscenario,useaLoader
//toloadcontentfromacontentprovider.
mItem=
DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
Activityactivity=this.getActivity();
CollapsingToolbarLayoutappBarLayout=
(CollapsingToolbarLayout)activity.findViewById(R.id.toolbar_layout);
if(appBarLayout!=null){
appBarLayout.setTitle(mItem.website_name);
}
}
}
@Override
publicViewonCreateView(LayoutInflaterinflater,
ViewGroupcontainer,BundlesavedInstanceState){
ViewrootView=inflater.inflate(
R.layout.fragment_website_detail,container,false);
//ShowthedummycontentastextinaTextView.
if(mItem!=null){
((WebView)rootView.findViewById(R.id.website_detail))
.loadUrl(mItem.website_url);
}
returnrootView;
}
}
TheabovechangesmodifytheonCreate()methodtodisplaythewebsitenameontheapp
bar:
appBarLayout.setTitle(mItem.website_name);
TheonCreateView()methodisthenmodifiedtofindtheviewwiththeIDof
website_detail(thiswasformallytheTextViewbutisnowaWebView),extractstheURL
ofthewebsitefromtheselecteditemandinstructstheWebViewobjecttoloadthatpage.
36.8ModifyingtheWebsiteListActivityClass
AminorchangealsoneedstobemadetotheWebsiteListActivity.javafiletomakesure
thatthewebsitenamesappearinthemasterlist.Editthisfile,locatethe
onBindViewHolder()methodandmodifythesetText()methodcalltoreferencetheweb
sitenameasfollows:
publicvoidonBindViewHolder(finalViewHolderholder,intposition){
holder.mItem=mValues.get(position);
holder.mIdView.setText(mValues.get(position).id);
holder.mContentView.setText(mValues.get(position).website_name);
.
.
.
}
36.9AddingManifestPermissions
Thefinalstepistoaddinternetpermissiontotheapplicationviathemanifestfile.This
willenabletheWebViewobjecttoaccesstheinternetanddownloadwebpages.Navigate
toandloadtheAndroidManifest.xmlfileintheProjecttoolwindow(app->manifests)
anddoubleclickonittoloaditintotheeditor.Onceloaded,addtheappropriate
permissionlinetothefile:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.masterdetailflow.masterdetailflow”>
<uses-permissionandroid:name=“android.permission.INTERNET”/>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>.
.
.
36.10RunningtheApplication
CompileandruntheapplicationonasuitablyconfiguredemulatororanattachedAndroid
device.Dependingonthesizeofthedisplay,theapplicationwillappeareitherinsmall
screenortwo-panemode.Regardless,themasterlistshouldappearprimedwiththenames
ofthethreewebsitesdefinedinthecontentmodel.Selectinganitemshouldcausethe
correspondingwebsitetoappearinthedetailpaneasillustratedintwo-panemodein
Figure36-6:
Figure36-6
36.11Summary
Amaster/detailuserinterfaceconsistsofamasterlistofitemswhich,whenselected,
displayadditionalinformationaboutthatselectionwithinadetailpane.TheMaster/Detail
FlowisatemplateprovidedwithAndroidStudiothatallowsamaster/detailarrangement
tobecreatedquicklyandwithrelativeease.Asdemonstratedinthischapter,withminor
modificationstothedefaulttemplatefiles,awiderangeofmaster/detailbased
functionalitycanbeimplementedwithminimalcodinganddesigneffort.
37.AnOverviewofAndroidIntents
Bythisstageofthebook,itshouldbeclearthatAndroidapplicationsarecomprised,
amongotherthings,ofoneormoreactivities.Anareathathasyettobecoveredin
extensivedetail,however,isthemechanismbywhichoneactivitycantriggerthelaunch
ofanotheractivity.AsoutlinedbrieflyinthechapterentitledTheAnatomyofanAndroid
Application,thisisachievedprimarilyusingIntents.
PriortoworkingthroughsomeAndroidStudiobasedexampleimplementationsofintents
inthefollowingchapters,thegoalofthischapteristoprovideanoverviewofintentsin
theformofexplicitintentsandimplicitintentstogetherwithanintroductiontointent
filters.
37.1AnOverviewofIntents
Intents(android.content.Intent)arethemessagingsystembywhichoneactivityisableto
launchanotheractivity.Anactivitycan,forexample,issueanintenttorequestthelaunch
ofanotheractivitycontainedwithinthesameapplication.Intentsalso,however,go
beyondthisconceptbyallowinganactivitytorequesttheservicesofanyother
appropriatelyregisteredactivityonthedeviceforwhichpermissionsareconfigured.
Consider,forexample,anactivitycontainedwithinanapplicationthatrequiresaweb
pagetobeloadedanddisplayedtotheuser.Ratherthantheapplicationhavingtocontaina
secondactivitytoperformthistask,thecodecansimplysendanintenttotheAndroid
runtimerequestingtheservicesofanyactivitythathasregisteredtheabilitytodisplaya
webpage.Theruntimesystemwillmatchtherequesttoavailableactivitiesonthedevice
andeitherlaunchtheactivitythatmatchesor,intheeventofmultiplematches,allowthe
usertodecidewhichactivitytouse.
Intentsalsoallowforthetransferofdatafromthesendingactivitytothereceiving
activity.Inthepreviouslyoutlinedscenario,forexample,thesendingactivitywouldneed
tosendtheURLofthewebpagetobedisplayedtothesecondactivity.Similarly,the
receivingactivitymayalsobeconfiguredtoreturnresultsdatatothesendingactivity
whentherequiredtasksarecompleted.
Thoughnotcovereduntillaterchapters,itisalsoworthhighlightingthefactthat,in
additiontolaunchingactivities,intentsarealsousedtolaunchandcommunicatewith
servicesandbroadcastreceivers.
Intentsarecategorizedaseitherexplicitorimplicit.
37.2ExplicitIntents
Anexplicitintentrequeststhelaunchofaspecificactivitybyreferencingthecomponent
name(whichisactuallytheJavaclassname)ofthetargetactivity.Thisapproachismost
commonwhenlaunchinganactivityresidinginthesameapplicationasthesending
activity(sincetheJavaclassnameisknowntotheapplicationdeveloper).
AnexplicitintentisissuedbycreatinganewinstanceoftheIntentclass,passingthrough
theactivitycontextandthecomponentnameoftheactivitytobelaunched.Acallisthen
madetothestartActivity()method,passingthroughtheintentobjectasanargument.For
example,thefollowingcodefragmentissuesanintentfortheactivitywiththeclassname
ActivityBtobelaunched:
Intenti=newIntent(this,ActivityB.class);
startActivity(i);
Datamaybetransmittedtothereceivingactivitybyaddingittotheintentobjectbeforeit
isstartedviacallstotheputExtra()methodoftheintentobject.Datamustbeaddedinthe
formofkey-valuepairs.ThefollowingcodeextendsthepreviousexampletoaddString
andintegervalueswiththekeys“myString”and“myInt”respectivelytotheintent:
Intenti=newIntent(this,ActivityB.class);
i.putExtra(“myString”,“ThisisamessageforActivityB”);
i.putExtra(“myInt”,100);
startActivity(i);
ThedataisreceivedatthetargetactivityaspartofaBundleobjectwhichcanbeobtained
viaacalltogetIntent().getExtras().ThegetIntent()methodoftheActivityclassreturns
theintentthatstartedtheactivity,whilethegetExtras()method(oftheIntentclass)returns
aBundleobjectforthatintentcontainingthedata.Forexample,toextractthedatavalues
passedtoActivityB:
Bundleextras=getIntent().getExtras();
If(extras!=null){
StringmyString=extras.getString(“myString”);
intmyInt=extras.getInt(“myInt”);
}
Whenusingintentstolaunchotheractivitieswithinthesameapplication,itisessential
thatthoseactivitiesbelistedintheapplicationmanifestfile.Thefollowing
AndroidManifest.xmlcontentsarecorrectlyconfiguredforanapplicationcontaining
activitiesnamedActivityAandActivityB:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.intent1.intent1”>
<application
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”>
<activity
android:label=”@string/app_name”
android:name=“com.ebookfrenzy.intent1.intent1.ActivityA”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”
/>
</intent-filter>
</activity>
<activity
android:name=“ActivityB”
android:label=“ActivityB”>
</activity>
</application>
</manifest>
37.3ReturningDatafromanActivity
Astheexampleintheprevioussectionstands,whiledataistransferredtoActivityB,there
isnowayfordatatobereturnedtothefirstactivity(whichwewillcallActivityA).This
can,however,beachievedbylaunchingActivityBasasub-activityofActivityA.An
activityisstartedasasub-activitybystartingtheintentwithacalltothe
startActivityForResult()methodinsteadofusingstartActivity().Inadditiontotheintent
object,thismethodisalsopassedarequestcodevaluewhichcanbeusedtoidentifythe
returndatawhenthesub-activityreturns.Forexample:
startActivityForResult(i,REQUEST_CODE);
Inordertoreturndatatotheparentactivity,thesub-activitymustimplementthefinish()
method,thepurposeofwhichistocreateanewintentobjectcontainingthedatatobe
returned,andthencallingthesetResult()methodoftheenclosingactivity,passingthrough
aresultcodeandtheintentcontainingthereturndata.Theresultcodeistypically
RESULT_OK,orRESULT_CANCELED,butmayalsobeacustomvaluesubjecttothe
requirementsofthedeveloper.Intheeventthatasub-activitycrashes,theparentactivity
willreceiveaRESULT_CANCELEDresultcode.
Thefollowingcode,forexample,illustratesthecodeforatypicalsub-activityfinish()
method:
publicvoidfinish(){
Intentdata=newIntent();
data.putExtra(“returnString1”,“Messagetoparentactivity”);
setResult(RESULT_OK,data);
super.finish();
}
Inordertoobtainandextractthereturneddata,theparentactivitymustimplementthe
onActivityResult()method,forexample:
protectedvoidonActivityResult(intrequestCode,intresultCode,Intent
data)
{
StringreturnString;
if(resultCode==RESULT_OK&&requestCode==REQUEST_CODE){
if(data.hasExtra(“returnString1”)){
returnString=data.getExtras().getString(“returnString1”);
}
}
}
Notethattheabovemethodchecksthereturnedrequestcodevaluetomakesurethatit
matchesthatpassedthroughtothestartActivityForResult()method.Whenstarting
multiplesub-activitiesitisespeciallyimportanttousetherequestcodetotrackwhich
activityiscurrentlyreturningresults,sinceallwillcallthesameonActivityResult()method
onexit.
37.4ImplicitIntents
Unlikeexplicitintents,whichreferencetheJavaclassnameoftheactivitytobelaunched,
implicitintentsidentifytheactivitytobelaunchedbyspecifyingtheactiontobe
performedandthetypeofdatatobehandledbythereceivingactivity.Forexample,an
actiontypeofACTION_VIEWaccompaniedbytheURLofawebpageintheformofa
URIobjectwillinstructtheAndroidsystemtosearchfor,andsubsequentlylaunch,aweb
browsercapableactivity.Thefollowingimplicitintentwill,whenexecutedonanAndroid
device,resultinthedesignatedwebpageappearinginaChromewebbrowseractivity:
Intenti=newIntent(Intent.ACTION_VIEW,
Uri.parse(“http://www.ebookfrenzy.com”));
Whentheaboveimplicitintentisissuedbyanactivity,theAndroidsystemwillsearchfor
activitiesonthedevicethathaveregisteredtheabilitytohandleACTION_VIEWrequests
onhttpschemedatausingaprocessreferredtoasintentresolution.Intheeventthata
singlematchisfound,thatactivitywillbelaunched.Ifmorethanonematchisfound,the
userwillbepromptedtochoosefromtheavailableactivityoptions.
37.5UsingIntentFilters
Intentfiltersarethemechanismbywhichactivities“advertise”supportedactionsanddata
handlingcapabilitiestotheAndroidintentresolutionprocess.Continuingtheexamplein
theprevioussection,anactivitycapableofdisplayingwebpageswouldincludeanintent
filtersectioninitsmanifestfileindicatingsupportforACTION_VIEWtypeintent
requestsonhttpschemedata.
Itisimportanttonotethatboththesendingandreceivingactivitiesmusthaverequested
permissionforthetypeofactiontobeperformed.Thisisachievedbyadding<usespermission>tagstothemanifestfilesforbothactivities.Forexample,thefollowing
manifestlinesrequestpermissiontoaccesstheinternetandcontactsdatabase:
<uses-permissionandroid:name=“android.permission.READ_CONTACTS”/>
<uses-permissionandroid:name=“android.permission.INTERNET”/>
ThefollowingAndroidManifest.xmlfileillustratesaconfigurationforanactivitynamed
WebViewActivitywithintentfiltersandpermissionsenabledforinternetaccess:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfreny.WebView”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdkandroid:minSdkVersion=“10”/>
<uses-permissionandroid:name=“android.permission.INTERNET”/>
<application
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”>
<activity
android:label=”@string/app_name”
android:name=”.WebViewActivity”>
<intent-filter>
<actionandroid:name=“android.intent.action.VIEW”/>
<categoryandroid:name=“android.intent.category.DEFAULT”
/>
<dataandroid:scheme=“http”/>
</intent-filter>
</activity>
</application>
</manifest>
37.6CheckingIntentAvailability
Itisgenerallyunwisetoassumethatanactivitywillbeavailableforaparticularintent,
especiallysincetheabsenceofamatchingactionwilltypicallyresultintheapplication
crashing.Fortunately,itispossibletoidentifytheavailabilityofanactivityforaspecific
intentbeforeitissenttotheruntimesystem.Thefollowingmethodcanbeusedtoidentify
theavailabilityofanactivityforaspecifiedintentactiontype:
publicstaticbooleanisIntentAvailable(Contextcontext,Stringaction)
{
finalPackageManagerpackageManager=context.getPackageManager();
finalIntentintent=newIntent(action);
List<ResolveInfo>list=
packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
returnlist.size()>0;
}
37.7Summary
IntentsarethemessagingmechanismbywhichoneAndroidactivitycanlaunchanother.
Anexplicitintentreferencesaspecificactivitytobelaunchedbyreferencingthereceiving
activitybyclassname.Explicitintentsaretypically,thoughnotexclusively,usedwhen
launchingactivitiescontainedwithinthesameapplication.Animplicitintentspecifiesthe
actiontobeperformedandthetypeofdatatobehandledandletstheAndroidruntime
findamatchingintenttolaunch.Implicitintentsaregenerallyusedwhenlaunching
activitiesthatresideindifferentapplications.
Anactivitycansenddatatothereceivingactivitybybundlingdataintotheintentobjectin
theformofkey-valuepairs.Datamayonlybereturnedfromanactivityifitisstartedasa
sub-activityofthesendingactivity.
ActivitiesadvertisecapabilitiestotheAndroidintentresolutionprocessthroughthe
specificationofintent-filtersintheapplicationmanifestfile.Bothsendingandreceiving
activitiesmustalsorequestappropriatepermissionstoperformtaskssuchasaccessingthe
devicecontactdatabaseortheinternet.
Havingcoveredthetheoryofintents,thenextfewchapterswillworkthroughthecreation
ofsomeexamplesinAndroidStudiothatputbothexplicitandimplicitintentsintoaction.
38.AndroidExplicitIntents–AWorked
Example
ThechapterentitledAnOverviewofAndroidIntentscoveredthetheoryofusingintentsto
launchactivities.Thischapterwillputthistheoryintopracticethroughthecreationofan
exampleapplication.
TheexampleAndroidStudioapplicationprojectcreatedinthischapterwilldemonstrate
theuseofanexplicitintenttolaunchanactivity,includingthetransferofdatabetween
sendingandreceivingactivities.Thenextchapter(AndroidImplicitIntents–AWorked
Example)willdemonstratetheuseofimplicitintents.
38.1CreatingtheExplicitIntentExampleApplication
LaunchAndroidStudioandcreateanewproject,enteringExplicitIntentintothe
Applicationnamefieldandebookfrenzy.comastheCompanyDomainsettingbefore
clickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedActivityAwithacorrespondinglayoutnamed
activity_a.
ClickFinishtocreatethenewproject.
38.2DesigningtheUserInterfaceLayoutforActivityA
TheuserinterfaceforActivityAwillconsistofaRelativeLayoutviewcontainingEditText
(PlainText),TextViewandButtonviewsnamededitText1,textView1andbutton1
respectively.UsingtheProjecttoolwindow,locatetheactivity_a.xmlresourcefilefor
ActivityA(locatedunderapp->res->layout)anddoubleclickonittoloaditintothe
AndroidStudioDesignertool.EitherdesignthelayoutinDesignmode,orswitchtoText
modeandenterthefollowingXML.Notethatthe“AskQuestion”textdisplayedonthe
buttonviewhasbeenextractedtoastringresourcenamedask_text.
IfdesigningtheuserinterfaceusingtheDesignertoolinDesignmode,besuretoeditthe
XMLafterwardstoaddtheonClickhandlerpropertyforthebutton1view,andnotethe
layoutwidthpropertyoftheEditViewcomponenthasbeensetto200dp:
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=”.ActivityA”>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:textAppearance=”?android:attr/textAppearanceLarge”
android:text=“LargeText”
android:id=”@+id/textView1”
android:layout_centerVertical=“true”
android:layout_centerHorizontal=“true”/>
<EditText
android:layout_width=“200dp”
android:layout_height=“wrap_content”
android:id=”@+id/editText1”
android:layout_above=”@+id/textView1”
android:layout_centerHorizontal=“true”
android:layout_marginBottom=“77dp”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“AskQuestion”
android:id=”@+id/button1”
android:layout_below=”@+id/textView1”
android:layout_centerHorizontal=“true”
android:onClick=“onClick”
android:layout_marginTop=“56dp”/>
</RelativeLayout>
AscoveredinAnOverviewandExampleofAndroidEventHandling,the
android:onClick=“onClick”resourcelineintheaboveXMLlistingusesanalternative
waytohandlesimpleclickeventswithouthavingtoimplementaneventlistenerinthe
Javacodeoftheactivity.ByspecifyingtheonClickeventtypeandthemethodtobecalled
whensuchaneventisdetected,itisquickandeasytowireupbasicclickeventhandling
onaview.OncetheonClick()methodhasbeenimplementedintheActivityA.javafile,it
willbecalledwheneverthebuttonistouchedbytheuser.
Oncethelayoutiscomplete,theuserinterfaceshouldresemblethatillustratedinFigure
38-1:
Figure38-1
38.3CreatingtheSecondActivityClass
Whenthe“AskQuestion”buttonistouchedbytheuser,anintentwillbeissuedrequesting
thatasecondactivitybelaunchedintowhichananswercanbeenteredbytheuser.The
nextstep,therefore,istocreatethesecondactivity.WithintheProjecttoolwindow,rightclickonthecom.ebookfrenzy.explicitintentpackagenamelocatedinapp->javaandselect
theNew->Activity->EmptyActivitymenuoptiontodisplaytheNewAndroidActivity
dialogasshowninFigure38-2:
Figure38-2
EnterActivityBintotheActivityNameandTitlefieldsandnamethelayoutfileactivity_b.
Sincethisactivitywillnotbestartedwhentheapplicationislaunched(itwillinsteadbe
launchedviaanintentbyActivityAwhenthebuttonispressed),itisimportanttomake
surethattheLauncherActivityoptionisdisabledbeforeclickingontheFinishbutton.
38.4DesigningtheUserInterfaceLayoutforActivityB
TheonlyelementsthatarerequiredfortheuserinterfaceofthesecondactivityareaPlain
TextEditText,TextViewandButtonview.Withtheserequirementsinmind,modifythe
activity_b.xmllayoutintheDesignertool,eithervisuallyusingDesignmodeorbydirectly
inputtingthefollowingXMLinTextmode.Notethatthetextonthebutton(whichreads
“AnswerQuestion”)hasbeenextractedtoastringresourcenamedanswer_text:
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=”.ActivityB”>
<Button
android:id=”@+id/button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_below=”@+id/editText1”
android:layout_centerHorizontal=“true”
android:layout_marginTop=“86dp”
android:onClick=“onClick”
android:text=“AnswerQuestion”/>
<TextView
android:id=”@+id/textView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentTop=“true”
android:layout_centerHorizontal=“true”
android:layout_marginTop=“35dp”
android:text=“LargeText”
android:textAppearance=”?android:attr/textAppearanceLarge”/>
<EditText
android:id=”@+id/editText1”
android:layout_width=“300dp”
android:layout_height=“wrap_content”
android:layout_below=”@+id/textView1”
android:layout_centerHorizontal=“true”
android:layout_marginTop=“66dp”
android:ems=“10”
android:inputType=“text”/>
</RelativeLayout>
IfdesigningthelayoutusingtheDesignertoolinDesignmode,notethattheonClick
propertyonthebuttonviewhasbeenconfiguredtocallamethodnamedonClick(),the
widthoftheEditTextviewhasbeensetto300dpandtheviewshavebeenassignedIDs
button1,TextView1andeditText1.Thecompletedlayoutshouldresemblethatillustratedin
Figure38-3:
Figure38-3
38.5ReviewingtheApplicationManifestFile
InorderforActivityAtobeabletolaunchActivityBusinganintent,itisnecessarythatan
entryforActivityBbepresentintheAndroidManifest.xmlfile.Locatethisfilewithinthe
Projecttoolwindow(app->manifests),doubleclickonittoloaditintotheeditorand
verifythatAndroidStudiohasautomaticallyaddedanentryfortheactivity:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.explicitintent”>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:supportsRtl=“true”
android:theme=”@style/AppTheme”>
<activityandroid:name=”.ActivityA”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<activityandroid:name=”.ActivityB”></activity>
</application>
</manifest>
Withthesecondactivitycreatedandlistedinthemanifestfile,itisnowtimetowrite
somecodeintheActivityAclasstoissuetheintent.
38.6CreatingtheIntent
TheobjectiveforActivityAistocreateandstartanintentwhentheusertouchesthe“Ask
Question”button.Aspartoftheintentcreationprocess,thequestionstringenteredbythe
userintotheEditTextviewwillbeaddedtotheintentobjectasakey-valuepair.Whenthe
userinterfacelayoutwascreatedforActivityA,thebuttonobjectwasconfiguredtocalla
methodnamedonClick()when“clicked”bytheuser.Thismethodnowneedstobeadded
totheActivityAclassActivityA.javasourcefileasfollows:
packagecom.ebookfrenzy.explicitintent;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.content.Intent;
importandroid.view.View;
importandroid.widget.EditText;
importandroid.widget.TextView;
publicclassActivityAextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
}
publicvoidonClick(Viewview){
Intenti=newIntent(this,ActivityB.class);
finalEditTexteditText1=(EditText)
findViewById(R.id.editText1);
StringmyString=editText1.getText().toString();
i.putExtra(“qString”,myString);
startActivity(i);
}
.
.
.
}
ThecodefortheonClick()methodfollowsthetechniquesoutlinedinAnOverviewof
AndroidIntents.First,anewIntentinstanceiscreated,passingthroughthecurrentactivity
andtheclassnameofActivityBasarguments.Next,thetextenteredintotheEditText
objectisaddedtotheintentobjectasakey-valuepairandtheintentstartedviaacallto
startActivity(),passingthroughtheintentobjectasanargument.
Compileandruntheapplicationandtouchthe“AskQuestion”buttontolaunchActivityB
andthebackbutton(locatedinthetoolbaralongthebottomofthedisplay)toreturnto
ActivityA.
38.7ExtractingIntentData
NowthatActivityBisbeinglaunchedfromActivityA,thenextstepistoextracttheString
datavalueincludedintheintentandassignittotheTextViewobjectintheActivityBuser
interface.ThisinvolvesaddingsomecodetotheonCreate()methodofActivityBinthe
ActivityB.javasourcefile:
packagecom.ebookfrenzy.explicitintent;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.content.Intent;
importandroid.view.View;
importandroid.widget.TextView;
importandroid.widget.EditText;
publicclassActivityBextendsAppCompatActivity{
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activityb);
Bundleextras=getIntent().getExtras();
if(extras==null){
return;
}
StringqString=extras.getString(“qString”);
finalTextViewtextView=(TextView)
findViewById(R.id.textView1);
textView.setText(qString);
}
}
CompileandruntheapplicationeitherwithinanemulatororonaphysicalAndroid
device.EnteraquestionintothetextboxinActivityAbeforetouchingthe“AskQuestion”
button.ThequestionshouldnowappearontheTextViewcomponentintheActivityBuser
interface.
38.8LaunchingActivityBasaSub-Activity
InorderforActivityBtobeabletoreturndatatoActivityA,ActivityBmustbestartedasa
sub-activityofActivityA.ThismeansthatthecalltostartActivity()intheActivityA
onClick()methodneedstobereplacedwithacalltostartActivityForResult().Unlikethe
startActivity()method,whichtakesonlytheintentobjectasanargument,
startActivityForResult()requiresthatarequestcodealsobepassedthrough.Therequest
codecanbeanynumbervalueandisusedtoidentifywhichsub-activityisassociatedwith
whichsetofreturndata.Forthepurposesofthisexample,arequestcodeof5willbe
used,givingusamodifiedActivityAclassthatreadsasfollows:
publicclassActivityAextendsAppCompatActivity{
privatestaticfinalintrequest_code=5;
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
publicvoidonClick(Viewview){
Intenti=newIntent(this,ActivityB.class);
finalEditTexteditText1=(EditText)
findViewById(R.id.editText1);
StringmyString=editText1.getText().toString();
i.putExtra(“qString”,myString);
startActivityForResult(i,request_code);
}
}
Whenthesub-activityexits,theonActivityResult()methodoftheparentactivityiscalled
andpassedasargumentstherequestcodeassociatedwiththeintent,aresultcode
indicatingthesuccessorotherwiseofthesub-activityandanintentobjectcontainingany
datareturnedbythesub-activity.RemainingwithintheActivityAclasssourcefile,this
methodneedstobeimplementedasfollows:
protectedvoidonActivityResult(intrequestCode,intresultCode,Intent
data){
if((requestCode==request_code)&&
(resultCode==RESULT_OK)){
TextViewtextView1=
(TextView)findViewById(R.id.textView1);
StringreturnString=
data.getExtras().getString(“returnData”);
textView1.setText(returnString);
}
}
Thecodeintheabovemethodbeginsbycheckingthattherequestcodematchestheone
usedwhentheintentwasissuedandensuringthattheactivitywassuccessful.Thereturn
dataisthenextractedfromtheintentanddisplayedontheTextViewobject.
38.9ReturningDatafromaSub-Activity
ActivityBisnowlaunchedasasub-activityofActivityA,whichhas,inturn,been
modifiedtohandledatareturnedfromActivityB.Allthatremainsistomodify
ActivityB.javatoimplementthefinish()methodandtoaddcodefortheonClick()method,
whichiscalledwhenthe“AnswerQuestion”buttonistouched.Thefinish()methodis
triggeredwhenanactivityexits(forexamplewhentheuserselectsthebackbuttononthe
device):
publicvoidonClick(Viewview){
finish();
}
@Override
publicvoidfinish(){
Intentdata=newIntent();
EditTexteditText1=(EditText)findViewById(R.id.editText1);
StringreturnString=editText1.getText().toString();
data.putExtra(“returnData”,returnString);
setResult(RESULT_OK,data);
super.finish();
}
Allthatthefinish()methodneedstodoiscreateanewintent,addthereturndataasakeyvaluepairandthencallthesetResult()method,passingthrougharesultcodeandthe
intentobject.TheonClick()methodsimplycallsthefinish()method.
38.10TestingtheApplication
Compileandruntheapplication,enteraquestionintothetextfieldonActivityAand
touchthe“AskQuestion”button.WhenActivityBappears,entertheanswertothe
questionanduseeitherthebackbuttonorthe“SubmitAnswer”buttontoreturnto
ActivityAwheretheanswershouldappearinthetextviewobject.
38.11Summary
Havingcoveredthebasicsofintentsinthepreviouschapter,thegoalofthischapterhas
beentoworkthroughthecreationofanapplicationprojectinAndroidStudiodesignedto
demonstratetheuseofexplicitintentstogetherwiththeconceptsofdatatransferbetween
aparentactivityandsub-activity.
Thenextchapterwillworkthroughanexampledesignedtodemonstrateimplicitintentsin
action.
39.AndroidImplicitIntents–AWorked
Example
Inthischapter,anexampleapplicationwillbecreatedinAndroidStudiodesignedto
demonstrateapracticalimplementationofimplicitintents.Thegoalwillbetocreateand
sendanintentrequestingthatthecontentofaparticularwebpagebeloadedanddisplayed
totheuser.Sincetheexampleapplicationitselfwillnotcontainanactivitycapableof
performingthistask,animplicitintentwillbeissuedsothattheAndroidintentresolution
algorithmcanbeengagedtoidentifyandlaunchasuitableactivityfromanother
application.ThisismostlikelytobeanactivityfromtheChromewebbrowserbundled
withtheAndroidoperatingsystem.
Havingsuccessfullylaunchedthebuilt-inbrowser,anewprojectwillbecreatedthatalso
containsanactivitycapableofdisplayingwebpages.Thiswillbeinstalledontothedevice
oremulatorandusedtodemonstratewhathappenswhentwoactivitiesmatchthecriteria
foranimplicitintent.
39.1CreatingtheAndroidStudioImplicitIntentExampleProject
LaunchAndroidStudioandcreateanewproject,enteringImplicitIntentintothe
Applicationnamefieldandebookfrenzy.comastheCompanyDomainsettingbefore
clickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedImplicitIntentActivitywithacorrespondinglayout
resourcefilenamedactivity_implicit_intent.
ClickFinishtocreatethenewproject.
39.2DesigningtheUserInterface
TheuserinterfacefortheImplicitIntentActivityclassisverysimple,consistingsolelyofa
RelativeLayoutviewandabutton.WithintheProjecttoolwindow,locatetheapp->res>layout->activity_implicit_intent.xmlfileanddoubleclickonittoloaditintothe
Designertool.VisuallyconstructtheuserinterfaceinDesignmodesothatitresembles
Figure39-1,orenterthefollowingXMLinTextmode.Notethatinbothcases,thetexton
thebutton(“ShowWebPage”)hasbeenextractedtoastringresourcenamedbutton_text:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=“com.ebookfrenzy.implicitintent.ImplicitIntentActivity”>
<Button
android:id=”@+id/button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerHorizontal=“true”
android:layout_centerVertical=“true”
android:text=”@string/button_text”
android:onClick=“showWebPage”/>
</RelativeLayout>
Figure39-1
NotetheinclusionoftheonClickpropertywhichwillensurethatamethodnamed
showWebPage()(whichhasnotyetbeenimplemented)willbecalledwhenthebuttonis
“clicked”bytheuser.
39.3CreatingtheImplicitIntent
Asoutlinedabove,theimplicitintentwillbecreatedandissuedfromwithinamethod
namedshowWebPage()which,inturn,needstobeimplementedinthe
ImplicitIntentActivityclass,thecodeforwhichresidesintheImplicitIntentActivity.java
sourcefile.LocatethisfileintheProjecttoolwindowanddoubleclickonittoloaditinto
aneditingpane.Onceloaded,modifythecodetoaddtheshowWebPage()methodtogether
withafewrequisiteimports:
packagecom.ebookfrenzy.implicitintent;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.net.Uri;
importandroid.content.Intent;
importandroid.view.View;
publicclassImplicitIntentActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_implicit_intent);
}
publicvoidshowWebPage(Viewview){
Intentintent=newIntent(Intent.ACTION_VIEW,
Uri.parse(“http://www.ebookfrenzy.com”));
startActivity(intent);
}
.
.
.
}
Thetasksperformedbythismethodareactuallyverysimple.First,anewintentobjectis
created.Insteadofspecifyingtheclassnameoftheintent,however,thecodesimply
indicatesthenatureoftheintent(todisplaysomethingtotheuser)usingthe
ACTION_VIEWoption.TheintentobjectalsoincludesaURIcontainingtheURLtobe
displayed.ThisindicatestotheAndroidintentresolutionsystemthattheactivityis
requestingthatawebpagebedisplayed.Theintentisthenissuedviaacalltothe
startActivity()method.
CompileandruntheapplicationoneitheranemulatororaphysicalAndroiddeviceand,
oncerunning,touchtheShowWebPagebutton.Whentouched,awebbrowserview
shouldappearandloadthewebpagedesignatedbytheURL.Asuccessfulimplicitintent
hasnowbeenexecuted.
39.4AddingaSecondMatchingActivity
Theremainderofthischapterwillbeusedtodemonstratetheeffectofthepresenceof
morethanoneactivityinstalledonthedevicematchingtherequirementsforanimplicit
intent.Toachievethis,asecondapplicationwillbecreatedandinstalledonthedeviceor
emulator.Begin,therefore,bycreatinganewprojectwithinAndroidStudiowiththe
applicationnamesettoMyWebView,usingthesameSDKconfigurationoptionsused
whencreatingtheImplicitIntentprojectearlierinthischapter.Selectanemptyactivity
and,whenprompted,nametheactivityMyWebViewActivityandthelayoutandresource
fileactivity_my_web_view.
39.5AddingtheWebViewtotheUI
TheuserinterfaceforthesoleactivitycontainedwithinthenewMyWebViewprojectis
goingtoconsistofaninstanceoftheAndroidWebViewwidget.WithintheProjecttool
window,locatetheactivity_my_web_view.xmlfilecontainingtheuserinterfacedescription
fortheactivityanddoubleclickonittoloaditintotheDesignertool.WiththeDesigner
toolinDesignmode,selectthedefaultTextViewwidgetandremoveitfromthelayout
usingthekeyboarddeletekey.SwitchDesignertoTextmodeandedittheXMLtoremove
thepaddingpropertiesandTextViewelementsothatthefilereadsasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=”.MyWebViewActivity”>
<TextView
android:text=”@string/hello_world”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
</RelativeLayout>
ReturntheDesignertooltoDesignmodeanddraganddropaWebViewobjectfromthe
WidgetssectionofthepaletteontotheexistingRelativeLayoutview.Setthelayoutheight
andwidthpropertiestomatch_parentusingeithertheDesignertoolbarbuttonsorthe
PropertiespanelsothattheWebViewfillstheentiredisplayareaasillustratedinFigure
39-2:
Figure39-2
Double-clickontheWebViewinstanceandchangetheIDtowebView1.
39.6ObtainingtheIntentURL
Whentheimplicitintentobjectiscreatedtodisplayawebbrowserwindow,theURLof
thewebpagetobedisplayedwillbebundledintotheintentobjectwithinaUriobject.
ThetaskoftheonCreate()methodwithintheMyWebViewActivityclassistoextractthis
Urifromtheintentobject,convertitintoaURLstringandassignittotheWebView
object.Toimplementthisfunctionality,modifytheonCreate()methodin
MyWebViewActivity.javasothatitreadsasfollows:
packagecom.ebookfrenzy.mywebview;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importjava.net.URL;
importandroid.net.Uri;
importandroid.content.Intent;
importandroid.webkit.WebView;
publicclassMyWebViewActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_web_view);
Intentintent=getIntent();
Uridata=intent.getData();
URLurl=null;
try{
url=newURL(data.getScheme(),
data.getHost(),
data.getPath());
}catch(Exceptione){
e.printStackTrace();
}
WebViewwebView=(WebView)findViewById(R.id.webView1);
webView.loadUrl(url.toString());
}
.
.
.
}
ThenewcodeaddedtotheonCreate()methodperformsthefollowingtasks:
· Obtainsareferencetotheintentwhichcausedthisactivitytobelaunched
· ExtractstheUridatafromtheintentobject
· ConvertstheUridatatoaURLobject
· ObtainsareferencetotheWebViewobjectintheuserinterface
· LoadstheURLintothewebview,convertingtheURLtoaStringintheprocess
ThecodingpartoftheMyWebViewprojectisnowcomplete.Allthatremainsistomodify
themanifestfile.
39.7ModifyingtheMyWebViewProjectManifestFile
ThereareanumberofchangesthatmustbemadetotheMyWebViewmanifestfilebefore
itcanbetested.Inthefirstinstance,theactivitywillneedtoseekpermissiontoaccessthe
internet(sinceitwillberequiredtoloadawebpage).Thisisachievedbyaddingthe
appropriatepermissionlinetothemanifestfile:
<uses-permissionandroid:name=“android.permission.INTERNET”/>
Further,areviewofthecontentsoftheintentfiltersectionoftheAndroidManifest.xmlfile
fortheMyWebViewprojectwillrevealthefollowingsettings:
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
IntheaboveXML,theandroid.intent.action.MAINentryindicatesthatthisactivityisthe
pointofentryfortheapplicationwhenitislaunchedwithoutanydatainput.The
android.intent.category.LAUNCHERdirective,ontheotherhand,indicatesthatthe
activityshouldbelistedwithintheapplicationlauncherscreenofthedevice.
Sincetheactivityisnotrequiredtobelaunchedastheentrypointtoanapplication,cannot
berunwithoutdatainput(inthiscaseaURL)andisnotrequiredtoappearinthe
launcher,neithertheMAINnorLAUNCHERdirectivesarerequiredinthemanifestfile
forthisactivity.
TheintentfilterfortheMyWebViewActivityactivitydoes,however,needtobemodifiedto
indicatethatitiscapableofhandlingACTION_VIEWintentactionsforhttpdata
schemes.
Androidalsorequiresthatanyactivitiescapableofhandlingimplicitintentsthatdonot
includeMAINandLAUNCHERentriesalsoincludetheso-calleddefaultcategoryinthe
intentfilter.Themodifiedintentfiltersectionshould,therefore,readasfollows:
<intent-filter>
<actionandroid:name=“android.intent.action.VIEW”/>
<categoryandroid:name=“android.intent.category.DEFAULT”/>
<dataandroid:scheme=“http”/>
</intent-filter>
Bringingtheserequirementstogetherresultsinthefollowingcomplete
AndroidManifest.xmlfile:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.mywebview”>
<uses-permissionandroid:name=“android.permission.INTERNET”/>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.MyWebViewActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.VIEW”/>
<category
android:name=“android.intent.category.DEFAULT”/>
<dataandroid:scheme=“http”/>
</intent-filter>
</activity>
</application>
</manifest>
LoadtheAndroidManifest.xmlfileintothemanifesteditorbydoubleclickingonthefile
nameintheProjecttoolwindow.Onceloaded,modifytheXMLtomatchtheabove
changes.
Havingmadetheappropriatemodificationstothemanifestfile,thenewactivityisready
tobeinstalledonthedevice.
39.8InstallingtheMyWebViewPackageonaDevice
BeforetheMyWebViewActivitycanbeusedastherecipientofanimplicitintent,itmust
firstbeinstalledontothedevice.Thisisachievedbyrunningtheapplicationinthenormal
manner.Becausethemanifestfilecontainsneithertheandroid.intent.action.MAINnorthe
android.intent.category.LAUNCHERdirectivestheapplicationwillinstallbutnotlaunch.
Thesuccessfulinstallationofthepackagewill,however,bereportedintheAndroid
StudioRuntoolwindow:
Installingcom.ebookfrenzy.mywebview
DEVICESHELLCOMMAND:pminstall-r
“/data/local/tmp/com.ebookfrenzy.mywebview”
pkg:/data/local/tmp/com.ebookfrenzy.mywebview
Success
Couldnotidentifylaunchactivity:DefaultActivitynotfound
Oncetheinstallationiscompleted,theactivityisreadytobeincludedintheAndroid
framework’sintentresolutionprocess.
39.9TestingtheApplication
InordertotestMyWebView,simplyre-launchtheImplicitIntentapplicationcreated
earlierinthischapterandtouchtheShowWebPagebutton.Thistime,however,theintent
resolutionprocesswillfindtwoactivitieswithintentfiltersmatchingtheimplicitintent.
Assuch,thesystemwilldisplayadialog(Figure39-3)providingtheuserwiththechoice
ofactivitytolaunch.
Figure39-3
SelectingtheMyWebViewoptionfollowedbytheJustoncebuttonshouldcausetheintent
tobehandledbyournewMyWebViewActivity,whichwillsubsequentlyappearanddisplay
thedesignatedwebpage.
IfthewebpageloadsintotheChromebrowserwithouttheaboveselectiondialog
appearing,itmaybethatChromehasbeenconfiguredasthedefaultbrowseronthe
device.ThiscanbechangedbygoingtoSettings->Appsonthedeviceandchoosingthe
ALLcategory.ScrolldownthelistofappsandselectChrome.OntheChromeappinfo
screen,taptheOpenbydefaultoptionfollowedbytheClearDefaultsbutton.
39.10Summary
Implicitintentsprovideamechanismbywhichoneactivitycanrequesttheserviceof
anothersimplybyspecifyinganactiontypeand,optionally,thedataonwhichthataction
istobeperformed.Inordertobeeligibleasatargetcandidateforanimplicitintent,
however,anactivitymustbeconfiguredtoextracttheappropriatedatafromtheinbound
intentobjectandbeincludedinacorrectlyconfiguredmanifestfile,includingappropriate
permissionsandintentfilters.Whenmorethanonematchingactivityforanimplicitintent
isfoundduringanintentresolutionsearch,theuserispromptedtomakeachoiceasto
whichtouse.
Withinthischapteranexamplehasbeencreatedtodemonstrateboththeissuingofan
implicitintent,andthecreationofanexampleactivitycapableofhandlingsuchanintent.
40.AndroidBroadcastIntentsandBroadcast
Receivers
Inadditiontoprovidingamechanismforlaunchingapplicationactivities,intentsarealso
usedasawaytobroadcastsystemwidemessagestoothercomponentsonthesystem.This
involvestheimplementationofBroadcastIntentsandBroadcastReceivers,bothofwhich
arethetopicofthischapter.
40.1AnOverviewofBroadcastIntents
BroadcastintentsareIntentobjectsthatarebroadcastviaacalltothesendBroadcast(),
sendStickyBroadcast()orsendOrderedBroadcast()methodsoftheActivityclass(thelatter
beingusedwhenresultsarerequiredfromthebroadcast).Inadditiontoprovidinga
messagingandeventsystembetweenapplicationcomponents,broadcastintentsarealso
usedbytheAndroidsystemtonotifyinterestedapplicationsaboutkeysystemevents
(suchastheexternalpowersupplyorheadphonesbeingconnectedordisconnected).
Whenabroadcastintentiscreated,itmustincludeanactionstringinadditiontooptional
dataandacategorystring.Aswithstandardintents,dataisaddedtoabroadcastintent
usingkey-valuepairsinconjunctionwiththeputExtra()methodoftheintentobject.The
optionalcategorystringmaybeassignedtoabroadcastintentviaacalltothe
addCategory()method.
Theactionstring,whichidentifiesthebroadcastevent,mustbeuniqueandtypicallyuses
theapplication’sJavapackagenamesyntax.Forexample,thefollowingcodefragment
createsandsendsabroadcastintentincludingauniqueactionstringanddata:
Intentintent=newIntent();
intent.setAction(“com.example.Broadcast”);
intent.putExtra(“MyData”,1000);
sendBroadcast(intent);
Theabovecodewouldsuccessfullylaunchthecorrespondingbroadcastreceiverona
devicerunninganAndroidversionearlierthan3.0.OnmorerecentversionsofAndroid,
however,theintentwouldnotbereceivedbythebroadcastreceiver.Thisisbecause
Android3.0introducedalaunchcontrolsecuritymeasurethatpreventscomponentsof
stoppedapplicationsfrombeinglaunchedviaanintent.Anapplicationisconsideredtobe
inastoppedstateiftheapplicationhaseitherjustbeeninstalledandnotpreviously
launched,orbeenmanuallystoppedbytheuserusingtheapplicationmanageronthe
device.Togetaroundthis,however,aflagcanbeaddedtotheintentbeforeitissentto
indicatethattheintentistobeallowedtostartacomponentofastoppedapplication.This
flagisFLAG_INCLUDE_STOPPED_PACKAGESandwouldbeusedasoutlinedinthe
followingadaptationofthepreviouscodefragment:
Intentintent=newIntent();
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
intent.setAction(“com.example.Broadcast”);
intent.putExtra(“MyData”,1000);
sendBroadcast(intent);
40.2AnOverviewofBroadcastReceivers
Anapplicationlistensforspecificbroadcastintentsbyregisteringabroadcastreceiver.
BroadcastreceiversareimplementedbyextendingtheAndroidBroadcastReceiverclass
andoverridingtheonReceive()method.Thebroadcastreceivermaythenberegistered,
eitherwithincode(forexamplewithinanactivity),orwithinamanifestfile.Partofthe
registrationimplementationinvolvesthecreationofintentfilterstoindicatethespecific
broadcastintentsthereceiverisrequiredtolistenfor.Thisisachievedbyreferencingthe
actionstringofthebroadcastintent.Whenamatchingbroadcastisdetected,the
onReceive()methodofthebroadcastreceiveriscalled,atwhichpointthemethodhas5
secondswithinwhichtoperformanynecessarytasksbeforereturning.Itisimportantto
notethatabroadcastreceiverdoesnotneedtoberunningallthetime.Intheeventthata
matchingintentisdetected,theAndroidruntimesystemwillautomaticallystartupthe
broadcastreceiverbeforecallingtheonReceive()method.
ThefollowingcodeoutlinesatemplateBroadcastReceiversubclass:
packagecom.example.broadcastdetector;
importandroid.content.BroadcastReceiver;
importandroid.content.Context;
importandroid.content.Intent;
publicclassMyReceiverextendsBroadcastReceiver{
publicMyReceiver(){
}
@Override
publicvoidonReceive(Contextcontext,Intentintent){
//Implementcodeheretobeperformedwhen
//broadcastisdetected
}
}
Whenregisteringabroadcastreceiverwithinamanifestfile,a<receiver>entrymustbe
addedcontainingoneormoreintentfilters,eachcontainingtheactionstringofthe
broadcastintentforwhichthereceiverisrequiredtolisten.
Thefollowingexamplemanifestfileregisterstheaboveexamplebroadcastreceiverto
listenforbroadcastintentscontaininganactionstringofcom.example.Broadcast:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=”com.example.broadcastdetector.broadcastdetector“
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdkandroid:minSdkVersion=“17”/>
<application
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”>
<receiverandroid:name=“MyReceiver”>
<intent-filter>
<actionandroid:name=“com.example.Broadcast”>
</action>
</intent-filter>
</receiver>
</application>
</manifest>
Thesameeffectcanbeachievedbyregisteringthebroadcastreceiverincodeusingthe
registerReceiver()methodoftheActivityclasstogetherwithanappropriatelyconfigured
IntentFilterobject:
IntentFilterfilter=newIntentFilter(“com.example.Broadcast”);
MyReceiverreceiver=newMyReceiver();
registerReceiver(receiver,filter);
Whenabroadcastreceiverregisteredincodeisnolongerrequired,itmaybeunregistered
viaacalltotheunregisterReceiver()methodoftheactivityclass,passingthrougha
referencetothereceiverobjectasanargument.Forexample,thefollowingcodewill
unregistertheabovebroadcastreceiver:
unregisterReceiver(receiver);
Itisimportanttokeepinmindthatsomesystembroadcastintentscanonlybedetectedby
abroadcastreceiverifitisregisteredincoderatherthaninthemanifestfile.Checkthe
AndroidIntentclassdocumentationforadetailedoverviewofthesystembroadcast
intentsandcorrespondingrequirementsonlineat:
http://developer.android.com/reference/android/content/Intent.html
40.3ObtainingResultsfromaBroadcast
WhenabroadcastintentissentusingthesendBroadcast()method,thereisnowayforthe
initiatingactivitytoreceiveresultsfromanybroadcastreceiversthatpickupthe
broadcast.Intheeventthatreturnresultsarerequired,itisnecessarytousethe
sendOrderedBroadcast()methodinstead.Whenabroadcastintentissentusingthis
method,itisdeliveredinsequentialordertoeachbroadcastreceiverwitharegistered
interest.
ThesendOrderedBroadcast()methodiscalledwithanumberofargumentsincludinga
referencetoanotherbroadcastreceiver(knownastheresultreceiver)whichistobe
notifiedwhenallotherbroadcastreceivershavehandledtheintent,togetherwithasetof
datareferencesintowhichthosereceiverscanplaceresultdata.Whenallbroadcast
receivershavebeengiventheopportunitytohandlethebroadcast,theonReceive()method
oftheresultreceiveriscalledandpassedtheresultdata.
40.4StickyBroadcastIntents
Bydefault,broadcastintentsdisappearoncetheyhavebeensentandhandledbyany
interestedbroadcastreceivers.Abroadcastintentcan,however,bedefinedasbeing
“sticky”.Astickyintent,andthedatacontainedtherein,remainspresentinthesystem
afterithascompleted.Thedatastoredwithinastickybroadcastintentcanbeobtainedvia
thereturnvalueofacalltotheregisterReceiver()method,usingtheusualarguments
(referencestothebroadcastreceiverandintentfilterobject).ManyoftheAndroidsystem
broadcastsaresticky,aprimeexamplebeingthosebroadcastsrelatingtobatterylevel
status.
AstickybroadcastmayberemovedatanytimeviaacalltotheremoveStickyBroadcast()
method,passingthroughasanargumentareferencetothebroadcastintenttoberemoved.
40.5TheBroadcastIntentExample
TheremainderofthischapterwillworkthroughthecreationofanAndroidStudiobased
exampleofbroadcastintentsinaction.Inthefirstinstance,asimpleapplicationwillbe
createdforthepurposeofissuingacustombroadcastintent.Acorrespondingbroadcast
receiverwillthenbecreatedthatwilldisplayamessageonthedisplayoftheAndroid
devicewhenthebroadcastisdetected.Finally,thebroadcastreceiverwillbemodifiedto
detectnotificationbythesystemthatexternalpowerhasbeendisconnectedfromthe
device.
40.6CreatingtheExampleApplication
LaunchAndroidStudioandcreateanewproject,enteringSendBroadcastintothe
Applicationnamefieldandebookfrenzy.comastheCompanyDomainsettingbefore
clickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedSendBroadcastActivitywithacorresponding
layoutresourcefilenamedactivity_send_broadcast.
Oncethenewprojecthasbeencreated,locateandloadtheactivity_send_broadcast.xml
layoutfilelocatedintheProjecttoolwindowunderapp->res->layoutand,withthe
DesignertoolinDesignmode,replacetheTextViewobjectwithaButtonview.Movethe
buttontothecenterofthedisplay,doubleclickonitandsetthetextpropertysothatit
reads“SendBroadcast”.Oncethetextvaluehasbeenset,usethelightbulbmenuiconto
extractthestringtoaresourcenamedbutton_text.
Withthebuttonstillselectedinthelayout,locatetheonClickpropertyintheProperties
panelandconfigureittocallamethodnamedbroadcastIntent.Ausefulshortcutfor
findingpropertiesistoclickonthetoolbarofthePropertiespanelandstarttypingthefirst
fewcharactersofthepropertyname.Thiswilltriggerthesearchsystemandbegin
selectingthebestmatchesforthepropertyname.
40.7CreatingandSendingtheBroadcastIntent
HavingcreatedtheframeworkfortheSendBroadcastapplication,itisnowtimeto
implementthecodetosendthebroadcastintent.Thisinvolvesimplementingthe
broadcastIntent()methodspecifiedpreviouslyastheonClicktargetoftheButtonviewin
theuserinterface.LocateanddoubleclickontheSendBroadcastActivity.javafileand
modifyittoaddthecodetocreateandsendthebroadcastintent.Oncemodified,the
sourcecodeforthisclassshouldreadasfollows:
packagecom.ebookfrenzy.sendbroadcast;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.content.Intent;
importandroid.view.View;
publicclassSendBroadcastActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_send_broadcast);
}
publicvoidbroadcastIntent(Viewview)
{
Intentintent=newIntent();
intent.setAction(“com.ebookfrenzy.sendbroadcast”);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
sendBroadcast(intent);
}
.
.
.
}
Notethat,inthisinstance,theactionstringfortheintentis
com.ebookfrenzy.sendbroadcast.Whenthebroadcastreceiverclassiscreatedinlater
sectionsofthischapter,itisessentialthattheintentfilterdeclarationmatchthisaction
string.
Thisconcludesthecreationoftheapplicationtosendthebroadcastintent.Allthatremains
istobuildamatchingbroadcastreceiver.
40.8CreatingtheBroadcastReceiver
Inordertocreatethebroadcastreceiver,anewclassneedstobecreatedwhichsubclasses
theBroadcastReceiversuperclass.Createanewprojectwiththeapplicationnamesetto
BroadcastReceiverandthecompanydomainnamesettocom.ebookfrenzy,thistime
selectingtheAddNoActivityoptionbeforeclickingonFinish.
WithintheProjecttoolwindow,navigatetoapp->javaandright-clickonthepackage
name.Fromtheresultingmenu,selecttheNew->Other->BroadcastReceivermenu
option,nametheclassMyReceiverandmakesuretheExportedandEnabledoptionsare
selected.ThesesettingsallowtheAndroidsystemtolaunchthereceiverwhenneededand
ensurethattheclasscanreceivemessagessentbyotherapplicationsonthedevice.With
theclassconfigured,clickonFinish.
Oncecreated,AndroidStudiowillautomaticallyloadthenewMyReceiver.javaclassfile
intotheeditorwhereitshouldreadasfollows:
packagecom.ebookfrenzy.broadcastreceiver;
importandroid.content.BroadcastReceiver;
importandroid.content.Context;
importandroid.content.Intent;
publicclassMyReceiverextendsBroadcastReceiver{
publicMyReceiver(){
}
@Override
publicvoidonReceive(Contextcontext,Intentintent){
//TODO:ThismethodiscalledwhentheBroadcastReceiveris
receiving
//anIntentbroadcast.
thrownewUnsupportedOperationException(“Notyetimplemented”);
}
}
Ascanbeseeninthecode,AndroidStudiohasgeneratedatemplateforthenewclassand
generatedastubfortheonReceive()method.Anumberofchangesnowneedtobemade
totheclasstoimplementtherequiredbehavior.RemainingintheMyReceiver.javafile,
therefore,modifythecodesothatitreadsasfollows:
packagecom.ebookfrenzy.broadcastreceiver;
importandroid.content.BroadcastReceiver;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.widget.Toast;
publicclassMyReceiverextendsBroadcastReceiver{
publicMyReceiver(){
}
@Override
publicvoidonReceive(Contextcontext,Intentintent){
Toast.makeText(context,“BroadcastIntentDetected.”,
Toast.LENGTH_LONG).show();
}
}
Thecodeforthebroadcastreceiverisnowcomplete.
40.9ConfiguringaBroadcastReceiverintheManifestFile
IncommonwithotherAndroidprojects,BroadcastReceiverhasassociatedwithita
manifestfilenamedAndroidManifest.xml.
Thisfileneedstopublicizethepresenceofthebroadcastreceiverandmustincludean
intentfiltertospecifythebroadcastintentsinwhichthereceiverisinterested.Whenthe
BroadcastReceiverclasswascreatedintheprevioussection,AndroidStudioautomatically
addeda<receiver>elementtothemanifestfile.Allthatremains,therefore,istoaddan
<intent-filter>elementwithinthe<receiver>declarationappropriatelyconfiguredforthe
customactionstring:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.broadcastreceiver”>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:supportsRtl=“true”
android:theme=”@style/AppTheme”>
<receiver
android:name=”.MyReceiver”
android:enabled=“true”
android:exported=“true”>
<intent-filter>
<action
android:name=“com.ebookfrenzy.sendbroadcast”>
</action>
</intent-filter>
</receiver>
</application>
</manifest>
Withthemanifestfilecompleted,thebroadcastexampleisreadytobetested.
40.10TestingtheBroadcastExample
Inordertotestthebroadcastsenderandreceiver,beginbyrunningtheBroadcastReceiver
applicationonaphysicalAndroiddeviceorAVD.Thesuccessfulinstallationofthe
packagewillbereportedintheAndroidStudioRuntoolwindow:
Installingcom.ebookfrenzy.broadcastreceiver
DEVICESHELLCOMMAND:pminstall-r
“/data/local/tmp/com.ebookfrenzy.broadcastreceiver”
pkg:/data/local/tmp/com.ebookfrenzy.broadcastreceiver
Success
Oncethereceiverisinstalled,runtheSendBroadcastapplicationonthesamedeviceor
AVDandwaitforittoappearonthedisplay.Oncerunning,touchthebutton,atwhich
pointthetoastmessagereading“BroadcastIntentDetected.”shouldpopupforafew
secondsbeforefadingaway.
Intheeventthatthetoastmessagedoesnotappear,doublecheckthatthe
BroadcastReceiverapplicationinstalledcorrectlyandthattheintentfilterinthemanifest
filematchestheactionstringusedwhentheintentwasbroadcast.
40.11ListeningforSystemBroadcasts
ThefinalstageofthisexampleistomodifytheintentfilterfortheBroadcastReceiverto
listenalsoforthesystemintentthatisbroadcastwhenexternalpowerisdisconnected
fromthedevice.Theactionthatthereceiverneedstobelisteningforinthiscontextis
android.intent.action.ACTION_POWER_DISCONNECTED.Themodifiedmanifestfile
fortheBroadcastReceiverprojectshould,therefore,nowreadasfollows:
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.broadcastreceiver.broadcastreceiver”>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:supportsRtl=“true”
android:theme=”@style/AppTheme”>
<receiver
android:name=”.MyReceiver”
android:enabled=“true”
android:exported=“true”>
<intent-filter>
<action
android:name=“com.ebookfrenzy.sendbroadcast”>
</action>
<action
android:name=“android.intent.action.ACTION_POWER_DISCONNECTED”>
</action>
</intent-filter>
</receiver>
</application>
</manifest>
SincetheonReceive()methodisnowgoingtobelisteningfortwotypesofbroadcast
intent,itisworthwhilemodifyingthecodesothattheactionstringofthecurrentintentis
alsodisplayedinthetoastmessage.Thisstringcanbeobtainedviaacalltothe
getAction()methodoftheintentobjectpassedasanargumenttotheonReceive()method:
publicvoidonReceive(Contextcontext,Intentintent){
Stringmessage=“Broadcastintentdetected“
+intent.getAction();
Toast.makeText(context,message,
Toast.LENGTH_LONG).show();
}
Testthereceiverbyre-installingthemodifiedBroadcastReceiverpackage.Touchingthe
buttonintheSendBroadcastapplicationshouldnowresultinanewmessagecontaining
thecustomactionstring:
Broadcastintentdetectedcom.ebookfrenzy.sendbroadcast
Next,removetheUSBconnectorthatiscurrentlysupplyingpowertotheAndroiddevice,
atwhichpointthereceivershouldreportthefollowinginthetoastmessage:
Broadcastintentdetected
android.intent.action.ACTION_POWER_DISCONNECTED
40.12Summary
Broadcastintentsareamechanismbywhichanintentcanbeissuedforconsumptionby
multiplecomponentsonanAndroidsystem.Broadcastsaredetectedbyregisteringa
BroadcastReceiver,which,inturn,isconfiguredtolistenforintentsthatmatchparticular
actionstrings.Ingeneral,broadcastreceiversremaindormantuntilwokenupbythe
systemwhenamatchingintentisdetected.BroadcastintentsarealsousedbytheAndroid
systemtoissuenotificationsofeventssuchasalowbatterywarningortheconnectionor
disconnectionofexternalpowertothedevice.
InadditiontoprovidinganoverviewofBroadcastintentsandreceivers,thischapterhas
alsoworkedthroughanexampleofsendingbroadcastintentsandtheimplementationofa
broadcastreceivertolistenforbothcustomandsystembroadcastintents.
41.ABasicOverviewofAndroidThreadsand
ThreadHandlers
Thenextchapterwillbethefirstinaseriesofchaptersintendedtointroducetheuseof
AndroidServicestoperformapplicationtasksinthebackground.Itisimpossible,
however,tounderstandfullythestepsinvolvedinimplementingserviceswithoutfirst
gainingabasicunderstandingoftheconceptofthreadinginAndroidapplications.Threads
andthreadhandlersare,therefore,thetopicofthischapter.
41.1AnOverviewofThreads
Threadsarethecornerstoneofanymultitaskingoperatingsystemandcanbethoughtofas
mini-processesrunningwithinamainprocess,thepurposeofwhichistoenableatleast
theappearanceofparallelexecutionpathswithinapplications.
41.2TheApplicationMainThread
WhenanAndroidapplicationisfirststarted,theruntimesystemcreatesasinglethreadin
whichallapplicationcomponentswillrunbydefault.Thisthreadisgenerallyreferredto
asthemainthread.Theprimaryroleofthemainthreadistohandletheuserinterfacein
termsofeventhandlingandinteractionwithviewsintheuserinterface.Anyadditional
componentsthatarestartedwithintheapplicationwill,bydefault,alsorunonthemain
thread.
Anycomponentwithinanapplicationthatperformsatimeconsumingtaskusingthemain
threadwillcausetheentireapplicationtoappeartolockupuntilthetaskiscompleted.
Thiswilltypicallyresultintheoperatingsystemdisplayingan“Applicationisnot
responding”warningtotheuser.Clearly,thisisfarfromthedesiredbehaviorforany
application.Insuchasituation,thiscanbeavoidedsimplybylaunchingthetasktobe
performedinaseparatethread,allowingthemainthreadtocontinueunhinderedwith
othertasks.
41.3ThreadHandlers
ClearlyoneofthekeyrulesofAndroiddevelopmentisnevertoperformtime-consuming
operationsonthemainthreadofanapplication.Thesecond,equallyimportantruleisthat
thecodewithinaseparatethreadmustnever,underanycircumstances,directlyupdateany
aspectoftheuserinterface.Anychangestotheuserinterfacemustalwaysbeperformed
fromwithinthemainthread.ThereasonforthisisthattheAndroidUItoolkitisnot
thread-safe.Attemptstoworkwithnon-thread-safecodefromwithinmultiplethreadswill
typicallyresultinintermittentproblemsandunpredictableapplicationbehavior.
Intheeventthatthecodeexecutinginathreadneedstointeractwiththeuserinterface,it
mustdosobysynchronizingwiththemainUIthread.Thisisachievedbycreatinga
handlerwithinthemainthread,which,inturn,receivesmessagesfromanotherthreadand
updatestheuserinterfaceaccordingly.
41.4ABasicThreadingExample
Theremainderofthischapterwillworkthroughsomesimpleexamplesintendedto
provideabasicintroductiontothreads.Thefirststepwillbetohighlighttheimportanceof
performingtime-consumingtasksinaseparatethreadfromthemainthread.Begin,
therefore,bycreatinganewprojectinAndroidStudio,enteringThreadExampleintothe
Applicationnamefieldandebookfrenzy.comastheCompanyDomainsettingbefore
clickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedThreadExampleActivity,usingthedefaultforthe
layoutresourcefiles.
ClickFinishtocreatethenewproject.
Loadtheactivity_thread_example.xmlfilefortheprojectintotheDesignertool.Double
clickontheTextViewcomponentandchangetheIDfortheviewtomyTextView.Next,
clickanddragtheTextViewsothatitispositionedinthecenterofthedisplaycanvas.
AddaButtonviewtotheuserinterface,positioneddirectlybeneaththeexistingTextView
objectasillustratedinFigure41-1.
Doubleclickonthebuttonviewandchangethetextto“PressMe”.Extractthestringtoa
resourcenamedbutton_text.Withthebuttonviewstillselectedinthelayout,locatethe
onClickpropertyinthePropertiespanelandenterbuttonClickasthemethodname.
Figure41-1
Oncecompleted,theXMLfilecontentshouldbesimilartothefollowinglisting:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=”.ThreadExampleActivity”>
<TextView
android:text=”@string/hello_world”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerVertical=“true”
android:layout_centerHorizontal=“true”
android:id=”@+id/myTextView”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/button_string”
android:id=”@+id/button”
android:layout_below=”@+id/myTextView”
android:layout_centerHorizontal=“true”
android:layout_marginTop=“45dp”
android:onClick=“buttonClick”/>
</RelativeLayout>
Next,loadtheThreadExampleActivity.javafileintoaneditingpanelandaddcodeto
implementthebuttonClick()methodwhichwillbecalledwhentheButtonviewistouched
bytheuser.Sincethegoalhereistodemonstratetheproblemofperforminglengthytasks
onthemainthread,thecodewillsimplypausefor20secondsbeforedisplayingdifferent
textontheTextViewobject:
packagecom.ebookfrenzy.threadexample;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.TextView;
publicclassThreadExampleActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread_example);
}
publicvoidbuttonClick(Viewview)
{
longendTime=System.currentTimeMillis()+20*1000;
while(System.currentTimeMillis()<endTime){
synchronized(this){
try{
wait(endTime-System.currentTimeMillis());
}catch(Exceptione){
}
}
}
TextViewmyTextView=
(TextView)findViewById(R.id.myTextView);
myTextView.setText(“ButtonPressed”);
}
.
.
.
}
Withthecodechangescomplete,runtheapplicationoneitheraphysicaldeviceoran
emulator.Oncetheapplicationisrunning,touchtheButton,atwhichpointtheapplication
willappeartofreeze.Itwill,forexample,notbepossibletotouchthebuttonasecond
timeandinsomesituationstheoperatingsystemwill,asdemonstratedinFigure41-2,
reporttheapplicationasbeingunresponsive:
Figure41-2
Clearly,anythingthatisgoingtotaketimetocompletewithinthebuttonClick()method
needstobeperformedwithinaseparatethread.
41.5CreatingaNewThread
Inordertocreateanewthread,thecodetobeexecutedinthatthreadneedstobeplaced
withintheRun()methodofaRunnableinstance.AnewThreadobjectthenneedstobe
created,passingthroughareferencetotheRunnableinstancetotheconstructor.Finally,
thestart()methodofthethreadobjectneedstobecalledtostartthethreadrunning.To
performthetaskwithinthebuttonClick()method,therefore,thefollowingchangesneedto
bemade:
publicvoidbuttonClick(Viewview)
{
Runnablerunnable=newRunnable(){
publicvoidrun(){
longendTime=System.currentTimeMillis()
+20*1000;
while(System.currentTimeMillis()<endTime){
synchronized(this){
try{
wait(endTime-
System.currentTimeMillis());
}catch(Exceptione){}
}
}
}
};
Threadmythread=newThread(runnable);
mythread.start();
}
Whentheapplicationisnowrun,touchingthebuttoncausesthedelaytobeperformedin
anewthreadleavingthemainthreadtocontinuehandlingtheuserinterface,including
respondingtoadditionalbuttonpresses.Infact,eachtimethebuttonistouched,anew
threadwillbecreated,allowingthetasktobeperformedmultipletimesconcurrently.
AcloseinspectionoftheupdatedcodeforthebuttonClick()methodwillrevealthatthe
codetoupdatetheTextViewhasbeenremoved.Aspreviouslystated,updatingauser
interfaceelementfromwithinathreadotherthanthemainthreadviolatesakeyruleof
Androiddevelopment.Inordertoupdatetheuserinterface,therefore,itwillbenecessary
toimplementaHandlerforthethread.
41.6ImplementingaThreadHandler
Threadhandlersareimplementedinthemainthreadofanapplicationandareprimarily
usedtomakeupdatestotheuserinterfaceinresponsetomessagessentbyotherthreads
runningwithintheapplication’sprocess.
HandlersaresubclassedfromtheAndroidHandlerclassandcanbeusedeitherby
specifyingaRunnabletobeexecutedwhenrequiredbythethread,orbyoverridingthe
handleMessage()callbackmethodwithintheHandlersubclasswhichwillbecalledwhen
messagesaresenttothehandlerbyathread.
Forthepurposesofthisexample,ahandlerwillbeimplementedtoupdatetheuser
interfacefromwithinthepreviouslycreatedthread.FromwithinAndroidStudio,loadthe
ThreadExampleActivity.javafileintotheAndroidStudioeditorandmodifythecodeto
addaHandlerinstancetotheactivity:
packagecom.ebookfrenzy.threadexample;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.TextView;
importandroid.os.Handler;
importandroid.os.Message;
publicclassThreadExampleActivityextendsAppCompatActivity{
Handlerhandler=newHandler(){
@Override
publicvoidhandleMessage(Messagemsg){
TextViewmyTextView=
(TextView)findViewById(R.id.myTextView);
myTextView.setText(“ButtonPressed”);
}
};
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread_example);
}
.
.
.
}
Theabovecodechangeshavedeclaredahandlerandimplementedwithinthathandlerthe
handleMessage()callbackwhichwillbecalledwhenthethreadsendsthehandlera
message.Inthisinstance,thecodesimplydisplaysastringontheTextViewobjectinthe
userinterface.
AllthatnowremainsistomodifythethreadcreatedinthebuttonClick()methodtosenda
messagetothehandlerwhenthedelayhascompleted:
publicvoidbuttonClick(Viewview)
{
Runnablerunnable=newRunnable(){
publicvoidrun(){
longendTime=System.currentTimeMillis()+
20*1000;
while(System.currentTimeMillis()<endTime){
synchronized(this){
try{
wait(endTime-
System.currentTimeMillis());
}catch(Exceptione){}
}
}
handler.sendEmptyMessage(0);
}
};
Threadmythread=newThread(runnable);
mythread.start();
}
NotethattheonlychangethathasbeenmadeistomakeacalltothesendEmptyMessage()
methodofthehandler.Sincethehandlerdoesnotcurrentlydoanythingwiththecontent
ofanymessagesitreceivesitisnotnecessarytocreateandsendamessageobjecttothe
handler.
Compileandruntheapplicationand,onceexecuting,touchthebutton.Aftera20second
delay,thenewtextwillappearintheTextViewobjectintheuserinterface.
41.7PassingaMessagetotheHandler
WhilethepreviousexampletriggeredacalltothehandleMessage()handlercallback,it
didnottakeadvantageofthemessageobjecttosenddatatothehandler.Inthisphaseof
thetutorial,theexamplewillbefurthermodifiedtopassdatabetweenthethreadandthe
handler.First,theupdatedthreadinthebuttonClick()methodwillobtainthedateandtime
fromthesysteminstringformatandstorethatinformationinaBundleobject.Acallwill
thenbemadetotheobtainMessage()methodofthehandlerobjecttogetamessageobject
fromthemessagepool.Finally,thebundlewillbeaddedtothemessageobjectbefore
beingsentviaacalltothesendMessage()methodofthehandlerobject:
packagecom.ebookfrenzy.threadexample;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.TextView;
importandroid.os.Handler;
importandroid.os.Message;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importjava.util.Locale;
publicclassThreadExampleActivityextendsAppCompatActivity{
.
.
.
publicvoidbuttonClick(Viewview)
{
Runnablerunnable=newRunnable(){
publicvoidrun(){
Messagemsg=handler.obtainMessage();
Bundlebundle=newBundle();
SimpleDateFormatdateformat=
newSimpleDateFormat(“HH:mm:ssMM/dd/yyyy”,
Locale.US);
StringdateString=
dateformat.format(newDate());
bundle.putString(“myKey”,dateString);
msg.setData(bundle);
handler.sendMessage(msg);
}
};
Threadmythread=newThread(runnable);
mythread.start();
}
.
.
.
}
Next,updatethehandleMessage()methodofthehandlertoextractthedateandtime
stringfromthebundleobjectinthemessageanddisplayitontheTextViewobject:
Handlerhandler=newHandler(){
@Override
publicvoidhandleMessage(Messagemsg){
Bundlebundle=msg.getData();
Stringstring=bundle.getString(“myKey”);
TextViewmyTextView=
(TextView)findViewById(R.id.myTextView);
myTextView.setText(string);
}
};
Finally,compileandruntheapplicationandtestthattouchingthebuttonnowcausesthe
currentdateandtimetoappearontheTextViewobject.
41.8Summary
ThegoalofthischapterhasbeentoprovideanoverviewofthreadingwithinAndroid
applications.Whenanapplicationisfirstlaunchedinaprocess,theruntimesystemcreates
amainthreadinwhichallsubsequentlylaunchedapplicationcomponentsrunbydefault.
Theprimaryroleofthemainthreadistohandletheuserinterface,soanytimeconsuming
tasksperformedinthatthreadwillgivetheappearancethattheapplicationhaslockedup.
Itisessential,therefore,thattaskslikelytotaketimetocompletebestartedinaseparate
thread.
BecausetheAndroiduserinterfacetoolkitisnotthread-safe,changestotheuserinterface
shouldnotbemadeinanythreadotherthanthemainthread.Userinterfacechangescan
beimplementedbycreatingahandlerinthemainthreadtowhichmessagesmaybesent
fromwithinother,non-mainthreads.
42.AnOverviewofAndroidStartedand
BoundServices
TheAndroidServiceclassisdesignedspecificallytoallowapplicationstoinitiateand
performbackgroundtasks.Unlikebroadcastreceivers,whichareintendedtoperforma
taskquicklyandthenexit,servicesaredesignedtoperformtasksthattakealongtimeto
complete(suchasdownloadingafileoveraninternetconnectionorstreamingmusicto
theuser)butdonotrequireauserinterface.
Inthischapter,anoverviewofthedifferenttypesofservicesavailablewillbecovered,
includingstartedservices,boundservicesandintentservices.Oncethesebasicshavebeen
covered,subsequentchapterswillworkthroughanumberofexamplesofservicesin
action.
42.1StartedServices
Startedservicesarelaunchedbyotherapplicationcomponents(suchasanactivityoreven
abroadcastreceiver)andpotentiallyrunindefinitelyinthebackgrounduntiltheserviceis
stopped,orisdestroyedbytheAndroidruntimesysteminordertofreeupresources.A
servicewillcontinuetoruniftheapplicationthatstarteditisnolongerintheforeground,
andevenintheeventthatthecomponentthatoriginallystartedtheserviceisdestroyed.
Bydefault,aservicewillrunwithinthesamemainthreadastheapplicationprocessfrom
whichitwaslaunched(referredtoasalocalservice).Itisimportant,therefore,thatany
CPUintensivetasksbeperformedinanewthreadwithintheservice.Instructingaservice
torunwithinaseparateprocess(andthereforeknownasaremoteservice)requiresa
configurationchangewithinthemanifestfile.
Unlessaserviceisspecificallyconfiguredtobeprivate(onceagainviaasettinginthe
manifestfile),thatservicecanbestartedbyothercomponentsonthesameAndroid
device.ThisisachievedusingtheIntentmechanisminthesamewaythatoneactivitycan
launchanotherasoutlinedinprecedingchapters.
StartedservicesarelaunchedviaacalltothestartService()method,passingthroughasan
argumentanIntentobjectidentifyingtheservicetobestarted.Whenastartedservicehas
completeditstasks,itshouldstopitselfviaacalltostopSelf().Alternatively,arunning
servicemaybestoppedbyanothercomponentviaacalltothestopService()method,
passingthroughasanargumentthematchingIntentfortheservicetobestopped.
ServicesaregivenahighprioritybytheAndroidsystemandaretypicallyamongthelast
tobeterminatedinordertofreeupresources.
42.2IntentService
Aspreviouslyoutlined,servicesrunbydefaultwithinthesamemainthreadasthe
componentfromwhichtheyarelaunched.Assuch,anyCPUintensivetasksthatneedto
beperformedbytheserviceshouldtakeplacewithinanewthread,therebyavoiding
impactingtheperformanceofthecallingapplication.
TheIntentServiceclassisaconvenienceclass(subclassedfromtheServiceclass)thatsets
upaworkerthreadforhandlingbackgroundtasksandhandleseachrequestinan
asynchronousmanner.Oncetheservicehashandledallqueuedrequests,itsimplyexits.
AllthatisrequiredwhenusingtheIntentServiceclassisthattheonHandleIntent()method
beimplementedcontainingthecodetobeexecutedforeachrequest.
Forservicesthatdonotrequiresynchronousprocessingofrequests,IntentServiceisthe
recommendedoption.Servicesrequiringsynchronoushandlingofrequestswill,however,
needtosubclassfromtheServiceclassandmanuallyimplementandmanagethreadingto
handleanyCPUintensivetasksefficiently.
42.3BoundService
Aboundserviceissimilartoastartedservicewiththeexceptionthatastartedservicedoes
notgenerallyreturnresultsorpermitinteractionwiththecomponentthatlaunchedit.A
boundservice,ontheotherhand,allowsthelaunchingcomponenttointeractwith,and
receiveresultsfrom,theservice.Throughtheimplementationofinterprocess
communication(IPC),thisinteractioncanalsotakeplaceacrossprocessboundaries.An
activitymight,forexample,startaservicetohandleaudioplayback.Theactivitywill,in
allprobability,includeauserinterfaceprovidingcontrolstotheuserforthepurposeof
pausingplaybackorskippingtothenexttrack.Similarly,theservicewillquitelikelyneed
tocommunicateinformationtothecallingactivitytoindicatethatthecurrentaudiotrack
hascompletedandtoprovidedetailsofthenexttrackthatisabouttostartplaying.
Acomponent(alsoreferredtointhiscontextasaclient)startsandbindstoabound
serviceviaacalltothebindService()methodandmultiplecomponentsmaybindtoa
servicesimultaneously.Whentheservicebindingisnolongerrequiredbyaclient,acall
shouldbemadetotheunbindService()method.Whenthelastboundclientunbindsfroma
service,theservicewillbeterminatedbytheAndroidruntimesystem.Itisimportantto
keepinmindthataboundservicemayalsobestartedviacalltostartService().Once
started,componentsmaythenbindtoitviabindService()calls.Whenaboundserviceis
launchedviaacalltostartService()itwillcontinuetorunevenafterthelastclientunbinds
fromit.
AboundservicemustincludeanimplementationoftheonBind()methodwhichiscalled
bothwhentheserviceisinitiallycreatedandwhenotherclientssubsequentlybindtothe
runningservice.Thepurposeofthismethodistoreturntobindingclientsanobjectof
typeIBindercontainingtheinformationneededbytheclienttocommunicatewiththe
service.
Intermsofimplementingthecommunicationbetweenaclientandaboundservice,the
recommendedtechniquedependsonwhethertheclientandserviceresideinthesameor
differentprocessesandwhetherornottheserviceisprivatetotheclient.Local
communicationcanbeachievedbyextendingtheBinderclassandreturninganinstance
fromtheonBind()method.Interprocesscommunication,ontheotherhand,requires
MessengerandHandlerimplementation.Detailsofbothoftheseapproacheswillbe
coveredinlaterchapters.
42.4TheAnatomyofaService
Aservicemust,ashasalreadybeenmentioned,becreatedasasubclassoftheAndroid
Serviceclass(morespecificallyandroid.app.Service)orasub-classthereof(suchas
android.app.IntentService).Aspartofthesubclassingprocedure,oneormoreofthe
followingsuperclasscallbackmethodsmustbeoverridden,dependingontheexactnature
oftheservicebeingcreated:
· onStartCommand()–Thisisthemethodthatiscalledwhentheserviceisstartedby
anothercomponentviaacalltothestartService()method.Thismethoddoesnotneed
tobeimplementedforboundservices.
· onBind()–Calledwhenacomponentbindstotheserviceviaacalltothe
bindService()method.Whenimplementingaboundservice,thismethodmustreturnan
IBinderobjectfacilitatingcommunicationwiththeclient.Inthecaseofstarted
services,thismethodmustbeimplementedtoreturnaNULLvalue.
· onCreate()–Intendedasalocationtoperforminitializationtasks,thismethodis
calledimmediatelybeforethecalltoeitheronStartCommand()orthefirstcalltothe
onBind()method.
· onDestroy()–Calledwhentheserviceisbeingdestroyed.
· onHandleIntent()–AppliesonlytoIntentServicesubclasses.Thismethodiscalledto
handletheprocessingfortheservice.Itisexecutedinaseparatethreadfromthemain
application.
NotethattheIntentServiceclassincludesitsownimplementationsofthe
onStartCommand()andonBind()callbackmethodssothesedonotneedtobe
implementedinsubclasses.
42.5ControllingDestroyedServiceRestartOptions
TheonStartCommand()callbackmethodisrequiredtoreturnanintegervaluetodefine
whatshouldhappenwithregardtotheserviceintheeventthatitisdestroyedbythe
Androidruntimesystem.Possiblereturnvaluesforthesemethodsareasfollows:
· START_NOT_STICKY–Indicatestothesystemthattheserviceshouldnotbe
restartedintheeventthatitisdestroyedunlesstherearependingintentsawaiting
delivery.
· START_STICKY–Indicatesthattheserviceshouldberestartedassoonaspossible
afterithasbeendestroyedifthedestructionoccurredaftertheonStartCommand()
methodreturned.Intheeventthatnopendingintentsarewaitingtobedelivered,the
onStartCommand()callbackmethodiscalledwithaNULLintentvalue.Theintent
beingprocessedatthetimethattheservicewasdestroyedisdiscarded.
· START_REDELIVER_INTENT–Indicatesthat,iftheservicewasdestroyedafter
returningfromtheonStartCommand()callbackmethod,theserviceshouldberestarted
withthecurrentintentredeliveredtotheonStartCommand()methodfollowedbyany
pendingintents.
42.6DeclaringaServiceintheManifestFile
Inorderforaservicetobeuseable,itmustfirstbedeclaredwithinamanifestfile.This
involvesembeddinganappropriatelyconfigured<service>elementintoanexisting
<application>entry.Ataminimum,the<service>elementmustcontainaproperty
declaringtheclassnameoftheserviceasillustratedinthefollowingXMLfragment:
<application
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”>
<activity
android:label=”@string/app_name”
android:name=”.TestActivity”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”
/>
</intent-filter>
</activity>
<serviceandroid:name=“MyService>
</service>
</application>
</manifest>
Bydefault,servicesaredeclaredaspublic,inthattheycanbeaccessedbycomponents
outsideoftheapplicationpackageinwhichtheyreside.Inordertomakeaserviceprivate,
theandroid:exportedpropertymustbedeclaredasfalsewithinthe<service>elementof
themanifestfile.Forexample:
<serviceandroid:name=“MyService”
android:exported=”false“>
</service>
Aspreviouslydiscussed,servicesrunwithinthesameprocessasthecallingcomponentby
default.Inordertoforceaservicetorunwithinitsownprocess,addanandroid:process
propertytothe<service>element,declaringanamefortheprocessprefixedwithacolon
(:):
<serviceandroid:name=“MyService”
android:exported=“false”
android:process=”:myprocess”>
</service>
Thecolonprefixindicatesthatthenewprocessisprivatetothelocalapplication.Ifthe
processnamebeginswithalowercaseletterinsteadofacolon,however,theprocesswill
beglobalandavailableforusebyothercomponents.
Finally,usingthesameintentfiltermechanismsoutlinedforactivities,aservicemayalso
advertisecapabilitiestootherapplicationsrunningonthedevice.Formoredetailson
IntentFilters,refertothechapterentitledAnOverviewofAndroidIntents.
42.7StartingaServiceRunningonSystemStartup
Giventhebackgroundnatureofservices,itisnotuncommonforaservicetoneedtobe
startedwhenanAndroidbasedsystemfirstbootsup.Thiscanbeachievedbycreatinga
broadcastreceiverwithanintentfilterconfiguredtolistenforthesystem
android.intent.action.BOOT_COMPLETEDintent.Whensuchanintentisdetected,the
broadcastreceiverwouldsimplyinvokethenecessaryserviceandthenreturn.Notethat
suchabroadcastreceiverwillneedtorequestthe
android.permission.RECEIVE_BOOT_COMPLETEDpermissioninordertofunction.
42.8Summary
Androidservicesareapowerfulmechanismthatallowsapplicationstoperformtasksin
thebackground.Aservice,oncelaunched,willcontinuetorunregardlessofwhetherthe
callingapplicationisnolongertheforegroundtaskandevenintheeventthatthe
componentthatinitiatedtheserviceisdestroyed.
ServicesaresubclassedfromtheAndroidServiceclassandfallintothecategoryofeither
startedservicesorboundservices.Startedservicesrununtiltheyarestoppedordestroyed
anddonotinherentlyprovideamechanismforinteractionordataexchangewithother
components.Boundservices,ontheotherhand,provideacommunicationinterfaceto
otherclientcomponentsand,generally,rununtilthelastclientunbindsfromtheservice.
Bydefault,servicesrunlocallywithinthesameprocessandmainthreadasthecalling
application.Anewthreadshould,therefore,becreatedwithintheserviceforthepurpose
ofhandlingCPUintensivetasks.Remoteservicesmaybestartedwithinaseparateprocess
bymakingaminorconfigurationchangetothecorresponding<service>entryinthe
applicationmanifestfile.
TheIntentServiceclass(itselfasubclassoftheAndroidServiceclass)providesa
convenientmechanismforhandlingasynchronousservicerequestswithinaseparate
workerthread.
43.ImplementinganAndroidStartedService
–AWorkedExample
ThepreviouschaptercoveredaconsiderableamountofinformationrelatingtoAndroid
servicesand,atthispoint,theconceptofservicesmayseemsomewhatoverwhelming.In
ordertoreinforcetheinformationinthepreviouschapter,thischapterwillworkthrough
anAndroidStudiotutorialintendedtograduallyintroducetheconceptsofstartedservice
implementation.
Withinthischapter,asampleapplicationwillbecreatedandusedasthebasisfor
implementinganAndroidservice.Inthefirstinstance,theservicewillbecreatedusing
theIntentServiceclass.Thisexamplewillsubsequentlybeextendedtodemonstratethe
useoftheServiceclass.Finally,thestepsinvolvedinperformingtaskswithinaseparate
threadwhenusingtheServiceclasswillbeimplemented.Havingcoveredstartedservices
inthischapter,thenextchapter,entitledAndroidLocalBoundServices–AWorked
Example,willfocusontheimplementationofboundservicesandclient-service
communication.
43.1CreatingtheExampleProject
LaunchAndroidStudioandfollowtheusualstepstocreateanewproject,entering
ServiceExampleintotheApplicationnamefieldandebookfrenzy.comastheCompany
DomainsettingbeforeclickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedServiceExampleActivityusingthedefaultvalues
fortheremainingoptions.
43.2CreatingtheServiceClass
Beforewritinganycode,thefirststepistoaddanewclasstotheprojecttocontainthe
service.Thefirsttypeofservicetobedemonstratedinthistutorialistobebasedonthe
IntentServiceclass.Asoutlinedintheprecedingchapter(AnOverviewofAndroidStarted
andBoundServices),thepurposeoftheIntentServiceclassistoprovidethedeveloper
withaconvenientmechanismforcreatingservicesthatperformtasksasynchronously
withinaseparatethreadfromthecallingapplication.
Addanewclasstotheprojectbyright-clickingonthecom.ebookfrenzy.serviceexample
packagenamelocatedunderapp->javaintheProjecttoolwindowandselectingtheNew
->JavaClassmenuoption.WithintheresultingCreateNewClassdialog,namethenew
classMyIntentService.Finally,clickontheOKbuttontocreatethenewclass.
ReviewthenewMyIntentService.javafileintheAndroidStudioeditorwhereitshould
readasfollows:
packagecom.ebookfrenzy.serviceexample;
/**
*Createdby<name>on<date>.
*/
publicclassMyIntentService{
}
TheclassneedstobemodifiedsothatitsubclassestheIntentServiceclass.When
subclassingtheIntentServiceclass,therearetworulesthatmustbefollowed.First,a
constructorfortheclassmustbeimplementedwhichcallsthesuperclassconstructor,
passingthroughtheclassnameoftheservice.Second,theclassmustoverridethe
onHandleIntent()method.ModifythecodeintheMyIntentService.javafile,therefore,so
thatitreadsasfollows:
packagecom.ebookfrenzy.serviceexample;
importandroid.app.IntentService;
importandroid.content.Intent;
publicclassMyIntentServiceextendsIntentService{
@Override
protectedvoidonHandleIntent(Intentarg0){
}
publicMyIntentService(){
super(“MyIntentService”);
}
}
AllthatremainsatthispointistoimplementsomecodewithintheonHandleIntent()
methodsothattheserviceactuallydoessomethingwheninvoked.Ordinarilythiswould
involveperformingataskthattakessometimetocompletesuchasdownloadingalarge
fileorplayingaudio.Forthepurposesofthisexample,however,thehandlerwillsimply
outputamessagetotheAndroidStudioLogCatpanel:
packagecom.ebookfrenzy.serviceexample;
importandroid.app.IntentService;
importandroid.content.Intent;
importandroid.util.Log;
publicclassMyIntentServiceextendsIntentService{
privatestaticfinalStringTAG=
“ServiceExample”;
@Override
protectedvoidonHandleIntent(Intentarg0){
Log.i(TAG,“IntentServicestarted”);
}
publicMyIntentService(){
super(“MyIntentService”);
}
}
43.3AddingtheServicetotheManifestFile
Beforeaservicecanbeinvoked,itmustfirstbeaddedtothemanifestfileofthe
applicationtowhichitbelongs.Ataminimum,thisinvolvesaddinga<service>element
togetherwiththeclassnameoftheservice.
DoubleclickontheAndroidManifest.xmlfile(app->manifests)forthecurrentprojectto
loaditintotheeditorandmodifytheXMLtoaddtheserviceelementasshowninthe
followinglisting:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.serviceexample”>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:supportsRtl=“true”
android:theme=”@style/AppTheme”>
<activityandroid:name=”.ServiceExampleActivity”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<serviceandroid:name=”.MyIntentService”/>
</application>
</manifest>
43.4StartingtheService
Nowthattheservicehasbeenimplementedanddeclaredinthemanifestfile,thenextstep
istoaddcodetostarttheservicewhentheapplicationlaunches.Asistypicallythecase,
theideallocationforsuchcodeistheonCreate()callbackmethodoftheactivityclass
(which,inthiscase,canbefoundintheServiceExampleActivity.javafile).Locateandload
thisfileintotheeditorandmodifytheonCreate()methodtoaddthecodetostartthe
service:
packagecom.ebookfrenzy.serviceexample;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.content.Intent;
publicclassServiceExampleActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_service_example);
Intentintent=newIntent(this,MyIntentService.class);
startService(intent);
}
.
.
.
}
AllthattheaddedcodeneedstodoistocreateanewIntentobjectprimedwiththeclass
nameoftheservicetostartandthenuseitasanargumenttothestartService()method.
43.5TestingtheIntentServiceExample
TheexampleIntentServicebasedserviceisnowcompleteandreadytobetested.Sincethe
messagedisplayedbytheservicewillappearintheLogCatpanel,itisimportantthatthis
isconfiguredintheAndroidStudioenvironment.
BeginbydisplayingtheAndroidMonitortoolwindowusingeitherthetoolsmenubutton
locatedinthefarleftcornerofthestatusbarortheAlt-6keyboardshortcut.Withinthe
toolwindow,makesurethatthelogcattabisselectedbeforeaccessingthemenuinthe
upperrighthandcornerofthepanel(whichwillprobablycurrentlyreadShowonly
selectedapplication).Fromthismenu,selecttheEditFilterConfigurationmenuoption.
IntheCreateNewLogcatFilterdialognamethefilterServiceExampleand,inthebyLog
Tagfield,entertheTAGvaluedeclaredinServiceExampleActivity.java(intheabovecode
examplethiswasServiceExample).
Whenthechangesarecomplete,clickontheOKbuttontocreatethefilteranddismissthe
dialog.ThenewlycreatedfiltershouldnowbeselectedintheAndroidtoolwindow.
Withthefilterconfigured,runtheapplicationonaphysicaldeviceorAVDemulator
sessionandnotethatthe“IntentServiceStarted”messageappearsintheLogCatpanel
(notethatitmaybenecessarytochangethefiltermenusettingbacktoServiceExample
aftertheapplicationhaslaunched):
11-0413:02:51.71418811-18830/?I/ServiceExample:IntentService
started
Hadtheservicebeentaskedwithalong-termactivity,theservicewouldhavecontinuedto
runinthebackgroundinaseparatethreaduntilthetaskwascompleted,allowingthe
applicationtocontinuefunctioningandrespondingtotheuser.Sinceallourservicedid
waslogamessage,itwillhavesimplystoppeduponcompletion.
43.6UsingtheServiceClass
WhiletheIntentServiceclassallowsaservicetobeimplementedwithminimalcoding,
therearesituationswheretheflexibilityandsynchronousnatureoftheServiceclasswill
berequired.Aswillbecomeevidentinthissection,thisinvolvessomeadditional
programmingworktoimplement.
Inordertoavoidintroducingtoomanyconceptsatonce,andasademonstrationofthe
risksinherentinperformingtime-consumingservicetasksinthesamethreadasthecalling
application,theexampleservicecreatedherewillnotruntheservicetaskwithinanew
thread,insteadrelyingonthemainthreadoftheapplication.Creationandmanagementof
anewthreadwithinaservicewillbecoveredinthenextphaseofthetutorial.
43.7CreatingtheNewService
Forthepurposesofthisexample,anewclasswillbeaddedtotheprojectthatwill
subclassfromtheServiceclass.Right-click,therefore,onthepackagenamelistedunder
app->javaintheProjecttoolwindowandselecttheNew->Service->Servicemenu
option.CreateanewclassnamedMyServicewithboththeExportedandEnabledoptions
selected.
Theminimalrequirementinordertocreateanoperationalserviceistoimplementthe
onStartCommand()callbackmethodwhichwillbecalledwhentheserviceisstartingup.
Inaddition,theonBind()methodmustreturnanullvaluetoindicatetotheAndroid
systemthatthisisnotaboundservice.Forthepurposesofthisexample,the
onStartCommand()methodwillloopthreetimesperforminga10-secondwaitoneach
loop.Forthesakeofcompleteness,stubversionsoftheonCreate()andonDestroy()
methodswillalsobeimplementedinthenewMyService.javafileasfollows:
packagecom.ebookfrenzy.serviceexample;
importandroid.app.Service;
importandroid.content.Intent;
importandroid.os.IBinder;
importandroid.util.Log;
publicclassMyServiceextendsService{
publicMyService(){
}
privatestaticfinalStringTAG=
“ServiceExample”;
@Override
publicvoidonCreate(){
Log.i(TAG,“ServiceonCreate”);
}
@Override
publicintonStartCommand(Intentintent,intflags,intstartId){
Log.i(TAG,“ServiceonStartCommand”);
for(inti=0;i<3;i++){
longendTime=System.currentTimeMillis()+
10*1000;
while(System.currentTimeMillis()<endTime){
synchronized(this){
try{
wait(endTime-System.currentTimeMillis());
}catch(Exceptione){
}
}
}
Log.i(TAG,“Servicerunning”);
}
returnService.START_STICKY;
}
@Override
publicIBinderonBind(Intentarg0){
Log.i(TAG,“ServiceonBind”);
returnnull;
}
@Override
publicvoidonDestroy(){
Log.i(TAG,“ServiceonDestroy”);
}
}
Withtheserviceimplemented,loadtheAndroidManifest.xmlfileintotheeditorandverify
thatAndroidStudiohasaddedanappropriateentryforthenewservicewhichshouldread
asfollows:
<service
android:name=”.MyService”
android:enabled=“true”
android:exported=“true”>
</service>
43.8ModifyingtheUserInterface
Aswillbecomeevidentwhentheapplicationruns,failingtocreateanewthreadforthe
servicetoperformtaskscreatesaserioususabilityproblem.Inordertobeableto
appreciatefullythemagnitudeofthisissue,itisgoingtobenecessarytoaddaButton
viewtotheuserinterfaceoftheServiceExampleActivityactivityandconfigureittocalla
methodwhen“clicked”bytheuser.
Locateandloadtheactivity_service_example.xmlfileintheProjecttoolwindow(app->
res->layout->activity_service_example.xml).DeletetheTextViewandaddaButton
viewtothelayout.Doubleclickonthenewbuttonandchangethetexttoread“Start
Service”.Usethelightbulbiconmenutoextractthestringtoaresourcenamed
button_text.
WiththenewButtonstillselected,locatetheonClickpropertyinthePropertiespaneland
assigntoitamethodnamedbuttonClick.
Oncompletion,theXMLfortheuserinterfacelayoutshouldresemblethefollowing
listing:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=“com.ebookfrenzy.serviceexample.serviceexample.ServiceExampleAc
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/button_string”
android:id=”@+id/button”
android:layout_alignParentTop=“true”
android:layout_alignParentLeft=“true”
android:layout_alignParentStart=“true”
android:onClick=“buttonClick”/>
</RelativeLayout>
Next,edittheServiceExampleActivity.javafiletoaddthebuttonClick()methodand
removethecodefromtheonCreate()methodthatwaspreviouslyaddedtolaunchthe
MyIntentServiceservice:
packagecom.ebookfrenzy.serviceexample;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.content.Intent;
importandroid.view.View;
publicclassServiceExampleActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_service_example);
Intentintent=newIntent(this,MyIntentService.class);
startService(intent);
}
publicvoidbuttonClick(Viewview)
{
Intentintent=newIntent(this,MyService.class);
startService(intent);
}
}
AllthatthebuttonClick()methoddoesiscreateanintentobjectforthenewserviceand
thenstartitrunning.
43.9RunningtheApplication
Runtheapplicationand,onceloaded,touchtheStartServicebutton.WithintheLogCat
window(usingtheServiceExamplefiltercreatedpreviously)thelogmessageswillappear
indicatingthattheonCreate()methodwascalledandthattheloopinthe
onStartCommand()methodisexecuting.
Beforethefinalloopmessageappears,attempttotouchtheStartServicebuttonasecond
time.Notethatthebuttonisunresponsive.Afterapproximately20seconds,thesystem
maydisplayawarningdialogcontainingthemessage“ServiceExampleisn’tresponding”.
Thereasonforthisisthatthemainthreadoftheapplicationiscurrentlybeingheldupby
theservicewhileitperformstheloopingtask.Notonlydoesthispreventtheapplication
fromrespondingtotheuser,butalsotothesystem,whicheventuallyassumesthatthe
applicationhaslockedupinsomeway.
Clearly,thecodefortheserviceneedstobemodifiedtoperformtasksinaseparatethread
fromthemainthread.
43.10CreatingaNewThreadforServiceTasks
AsoutlinedinABasicOverviewofAndroidThreadsandThreadHandlers,whenan
Androidapplicationisfirststarted,theruntimesystemcreatesasinglethreadinwhichall
applicationcomponentswillrunbydefault.Thisthreadisgenerallyreferredtoasthemain
thread.Theprimaryroleofthemainthreadistohandletheuserinterfaceintermsof
eventhandlingandinteractionwithviewsintheuserinterface.Anyadditional
componentsthatarestartedwithintheapplicationwill,bydefault,alsorunonthemain
thread.
Asdemonstratedintheprevioussection,anycomponentthatundertakesatimeconsuming
operationonthemainthreadwillcausetheapplicationtobecomeunresponsiveuntilthat
taskiscomplete.Itisnotsurprising,therefore,thatAndroidprovidesanAPIthatallows
applicationstocreateanduseadditionalthreads.Anytasksperformedinaseparatethread
fromthemainthreadareessentiallyperformedinthebackground.Suchthreadsare
typicallyreferredtoasbackgroundorworkerthreads.
Averysimplesolutiontothisprobleminvolvesperformingtheservicetaskwithinanew
thread.ThefollowingonStartCommand()methodfromtheMyService.javafile,for
example,hasbeenmodifiedtolaunchthetaskwithinanewthreadusingthemostbasicof
threadhandlingexamples:
@Override
publicintonStartCommand(Intentintent,intflags,intstartId){
Log.i(TAG,“ServiceonStartCommand”+startId);
finalintcurrentId=startId;
Runnabler=newRunnable(){
publicvoidrun(){
for(inti=0;i<3;i++)
{
longendTime=System.currentTimeMillis()+
10*1000;
while(System.currentTimeMillis()<endTime){
synchronized(this){
try{
wait(endTimeSystem.currentTimeMillis());
}catch(Exceptione){
}
}
}
Log.i(TAG,“Servicerunning”+currentId);
}
stopSelf();
}
};
Threadt=newThread(r);
t.start();
returnService.START_STICKY;
}
Whentheapplicationisnowrun,itshouldbepossibletotouchtheStartServicebutton
multipletimes.Eachtimeanewthreadwillbecreatedbytheservicetoprocessthetask.
TheLogCatoutputwillnowalsoincludeanumberreferencingthestartIdofeachservice
request.
Withtheservicenowhandlingrequestsoutsideofthemainthread,theapplicationremains
responsivetoboththeuserandtheAndroidsystem.
43.11Summary
ThischapterhasworkedthroughanexampleimplementationofanAndroidstarted
serviceusingtheIntentServiceandServiceclasses.Theexamplealsodemonstratedthe
useofthreadswithinaservicetoavoidmakingthemainthreadoftheapplication
unresponsive.
44.AndroidLocalBoundServices–A
WorkedExample
Asoutlinedinsomedetailinthepreviouschapters,boundservices,unlikestarted
services,provideamechanismforimplementingcommunicationbetweenanAndroid
serviceandoneormoreclientcomponents.Theobjectiveofthischapteristobuildonthe
overviewofboundservicesprovidedinAnOverviewofAndroidStartedandBound
Servicesbeforeembarkingonanexampleimplementationofalocalboundservicein
action.
44.1UnderstandingBoundServices
Incommonwithstartedservices,boundservicesareprovidedtoallowapplicationsto
performtasksinthebackground.Unlikestartedservices,however,multipleclient
componentsmaybindtoaboundserviceand,oncebound,interactwiththatserviceusing
avarietyofdifferentmechanisms.
Boundservicesarecreatedassub-classesoftheAndroidServiceclassandmust,ata
minimum,implementtheonBind()method.Clientcomponentsbindtoaserviceviaacall
tothebindService()method.Thefirstbindrequesttoaboundservicewillresultinacall
tothatservice’sonBind()method(subsequentbindrequestdonottriggeranonBind()
call).ClientswishingtobindtoaservicemustalsoimplementaServiceConnection
subclasscontainingonServiceConnected()andonServiceDisconnected()methodswhich
willbecalledoncetheclient-serverconnectionhasbeenestablishedordisconnected
respectively.InthecaseoftheonServiceConnected()method,thiswillbepassedan
IBinderobjectcontainingtheinformationneededbytheclienttointeractwiththeservice.
44.2BoundServiceInteractionOptions
Therearetworecommendedmechanismsforimplementinginteractionbetweenclient
componentsandaboundservice.Intheeventthattheboundserviceislocalandprivateto
thesameapplicationastheclientcomponent(inotherwordsitrunswithinthesame
processandisnotavailabletocomponentsinotherapplications),therecommended
mechanismistocreateasubclassoftheBinderclassandextendittoprovideaninterface
totheservice.AninstanceofthisBinderobjectisthenreturnedbytheonBind()method
andsubsequentlyusedbytheclientcomponenttodirectlyaccessmethodsanddataheld
withintheservice.
Insituationswheretheboundserviceisnotlocaltotheapplication(inotherwords,itis
runninginadifferentprocessfromtheclientcomponent),interactionisbestachieved
usingaMessenger/Handlerimplementation.
Intheremainderofthischapter,anexamplewillbecreatedwiththeaimofdemonstrating
thestepsinvolvedincreating,startingandinteractingwithalocal,privateboundservice.
44.3AnAndroidStudioLocalBoundServiceExample
Theexampleapplicationcreatedintheremainderofthischapterwillconsistofasingle
activityandaboundservice.Thepurposeoftheboundserviceistoobtainthecurrenttime
fromthesystemandreturnthatinformationtotheactivitywhereitwillbedisplayedtothe
user.Theboundservicewillbelocalandprivatetothesameapplicationastheactivity.
LaunchAndroidStudioandfollowtheusualstepstocreateanewproject,entering
LocalBoundintotheApplicationnamefieldandebookfrenzy.comastheCompany
DomainsettingbeforeclickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedLocalBoundActivitywiththeremainingfieldsset
tothedefaultvalues.
Oncetheprojecthasbeencreated,thenextstepistoaddanewclasstoactasthebound
service.
44.4AddingaBoundServicetotheProject
Toaddanewclasstotheproject,right-clickonthepackagename(locatedunderapp->
java->com.ebookfrenzy.localbound)withintheProjecttoolwindowandselecttheNew>Service->Servicemenuoption.SpecifyBoundServiceastheclassnameandmakesure
thatboththeExportedandEnabledoptionsareselectedbeforeclickingonFinishtocreate
theclass.BydefaultAndroidStudiowillloadtheBoundService.javafileintotheeditor
whereitwillreadasfollows:
packagecom.ebookfrenzy.localbound;
importandroid.app.Service;
importandroid.content.Intent;
importandroid.os.IBinder;
publicclassBoundServiceextendsService{
publicBoundService(){
}
@Override
publicIBinderonBind(Intentintent){
//TODO:Returnthecommunicationchanneltotheservice.
thrownewUnsupportedOperationException(“Notyetimplemented”);
}
}
44.5ImplementingtheBinder
Aspreviouslyoutlined,localboundservicescancommunicatewithboundclientsby
passinganappropriatelyconfiguredBinderobjecttotheclient.Thisisachievedby
creatingaBindersubclasswithintheboundserviceclassandextendingitbyaddingone
ormorenewmethodsthatcanbecalledbytheclient.Inmostcases,thissimplyinvolves
implementingamethodthatreturnsareferencetotheboundserviceinstance.Witha
referencetothisinstance,theclientcanthenaccessdataandcallmethodswithinthe
boundservicedirectly.
Forthepurposesofthisexample,therefore,somechangesareneededtothetemplate
BoundServiceclasscreatedintheprecedingsection.Inthefirstinstance,aBindersubclass
needstobedeclared.ThisclasswillcontainasinglemethodnamedgetService()which
willsimplyreturnareferencetothecurrentserviceobjectinstance(representedbythethis
keyword).Withtheserequirementsinmind,edittheBoundService.javafileandmodifyit
asfollows:
packagecom.ebookfrenzy.localbound;
importandroid.app.Service;
importandroid.content.Intent;
importandroid.os.IBinder;
importandroid.os.Binder;
publicclassBoundServiceextendsService{
privatefinalIBindermyBinder=newMyLocalBinder();
publicBoundService(){
}
@Override
publicIBinderonBind(Intentintent){
//TODO:Returnthecommunicationchanneltotheservice.
thrownewUnsupportedOperationException(“Notyetimplemented”);
}
publicclassMyLocalBinderextendsBinder{
BoundServicegetService(){
returnBoundService.this;
}
}
}
Havingmadethechangestotheclass,itisworthtakingamomenttorecapthesteps
performedhere.First,anewsubclassofBinder(namedMyLocalBinder)isdeclared.This
classcontainsasinglemethodforthesolepurposeofreturningareferencetothecurrent
instanceoftheBoundServiceclass.AnewinstanceoftheMyLocalBinderclassiscreated
andassignedtothemyBinderIBinderreference(sinceBinderisasubclassofIBinder
thereisnotypemismatchinthisassignment).
Next,theonBind()methodneedstobemodifiedtoreturnareferencetothemyBinder
objectandanewpublicmethodimplementedtoreturnthecurrenttimewhencalledby
anyclientsthatbindtotheservice:
packagecom.ebookfrenzy.localbound;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importjava.util.Locale;
importandroid.app.Service;
importandroid.content.Intent;
importandroid.os.IBinder;
importandroid.os.Binder;
publicclassBoundServiceextendsService{
privatefinalIBindermyBinder=newMyLocalBinder();
publicBoundService(){
}
@Override
publicIBinderonBind(Intentintent){
returnmyBinder;
}
publicStringgetCurrentTime(){
SimpleDateFormatdateformat=
newSimpleDateFormat(“HH:mm:ssMM/dd/yyyy”,
Locale.US);
return(dateformat.format(newDate()));
}
publicclassMyLocalBinderextendsBinder{
BoundServicegetService(){
returnBoundService.this;
}
}
}
Atthispoint,theboundserviceiscompleteandisreadytobeaddedtotheproject
manifestfile.LocateanddoubleclickontheAndroidManifest.xmlfilefortheLocalBound
projectintheProjecttoolwindowand,onceloadedintotheManifestEditor,verifythat
AndroidStudiohasalreadyaddeda<service>entryfortheserviceasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.localbound.localbound”>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.LocalBoundActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<service
android:name=”.BoundService”
android:enabled=“true”
android:exported=“true”>
</service>
</application>
</manifest>
Thenextphaseistoimplementthenecessarycodewithintheactivitytobindtothe
serviceandcallthegetCurrentTime()method.
44.6BindingtheClienttotheService
Forthepurposesofthistutorial,theclientistheLocalBoundActivityinstanceofthe
runningapplication.Aspreviouslynoted,inordertosuccessfullybindtoaserviceand
receivetheIBinderobjectreturnedbytheservice’sonBind()method,itisnecessaryto
createaServiceConnectionsubclassandimplementonServiceConnected()and
onServiceDisconnected()callbackmethods.EdittheLocalBoundActivity.javafileand
modifyitasfollows:
packagecom.ebookfrenzy.localbound;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.os.IBinder;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.content.ComponentName;
importandroid.content.ServiceConnection;
importcom.ebookfrenzy.localbound.BoundService.MyLocalBinder;
publicclassLocalBoundActivityextendsAppCompatActivity{
BoundServicemyService;
booleanisBound=false;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_local_bound);
}
privateServiceConnectionmyConnection=newServiceConnection()
{
publicvoidonServiceConnected(ComponentNameclassName,
IBinderservice){
MyLocalBinderbinder=(MyLocalBinder)service;
myService=binder.getService();
isBound=true;
}
publicvoidonServiceDisconnected(ComponentNamearg0){
isBound=false;
}
};
.
.
}
TheonServiceConnected()methodwillbecalledwhentheclientbindssuccessfullytothe
service.ThemethodispassedasanargumenttheIBinderobjectreturnedbytheonBind()
methodoftheservice.ThisargumentiscasttoanobjectoftypeMyLocalBinderandthen
thegetService()methodofthebinderobjectiscalledtoobtainareferencetotheservice
instance,which,inturn,isassignedtomyService.ABooleanflagisusedtoindicatethat
theconnectionhasbeensuccessfullyestablished.
TheonServiceDisconnected()methodiscalledwhentheconnectionendsandsimplysets
theBooleanflagtofalse.
Havingestablishedtheconnection,thenextstepistomodifytheactivitytobindtothe
service.ThisinvolvesthecreationofanintentandacalltothebindService()method,
whichcanbeperformedintheonCreate()methodoftheactivity:
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_local_bound);
Intentintent=newIntent(this,BoundService.class);
bindService(intent,myConnection,Context.BIND_AUTO_CREATE);
}
44.7CompletingtheExample
AllthatremainsistoimplementamechanismforcallingthegetCurrentTime()method
anddisplayingtheresulttotheuser.Asisnowcustomary,AndroidStudiowillhave
createdatemplateactivity_local_bound.xmlfilefortheactivitycontainingonlya
TextView.LoadthisfileintotheDesignertoolusingDesignmode,double-clickonthe
TextViewcomponentandchangetheIDtomyTextView.MovetheTextViewtothecenter
ofthedisplaycanvas,addaButtonviewbeneaththeTextViewandchangethetextonthe
buttontoread“ShowTime”,extractingthetexttoastringresourcenamedbutton_string.
Oncompletionofthesechanges,thelayoutshouldresemblethatillustratedinFigure44-1:
Figure44-1
Next,switchDesignerintoTextmodeandmodifythebuttonelementtodeclarean
onClickpropertyconfiguredtocallamethodnamedshowTime:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=”.LocalBoundActivity”>
<TextView
android:text=”@string/hello_world”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_centerVertical=“true”
android:layout_centerHorizontal=“true”
android:id=”@+id/myTextView”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/button_string”
android:id=”@+id/button”
android:layout_below=”@+id/myTextView”
android:layout_centerHorizontal=“true”
android:onClick=“showTime”
android:layout_marginTop=“44dp”/>
</RelativeLayout>
Finally,editthecodeintheLocalBoundActivity.javafiletoimplementtheshowTime()
method.ThismethodsimplycallsthegetCurrentTime()methodoftheservice(which,
thankstotheonServiceConnected()method,isnowavailablefromwithintheactivityvia
themyServicereference)andassignstheresultingstringtotheTextView:
packagecom.ebookfrenzy.localbound;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.os.IBinder;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.content.ComponentName;
importandroid.content.ServiceConnection;
importcom.ebookfrenzy.localbound.BoundService.MyLocalBinder;
importandroid.view.View;
importandroid.widget.TextView;
publicclassLocalBoundActivityextendsAppCompatActivity{
BoundServicemyService;
booleanisBound=false;
publicvoidshowTime(Viewview)
{
StringcurrentTime=myService.getCurrentTime();
TextViewmyTextView=
(TextView)findViewById(R.id.myTextView);
myTextView.setText(currentTime);
}
.
.
.
}
44.8TestingtheApplication
Withthecodechangescomplete,performatestrunoftheapplication.Oncevisible,touch
thebuttonandnotethatthetextviewchangestodisplaythecurrentdateandtime.The
examplehassuccessfullystartedandboundtoaserviceandthencalledamethodofthat
servicetocauseatasktobeperformedandresultsreturnedtotheactivity.
44.9Summary
Whenaboundserviceislocalandprivatetoanapplication,componentswithinthat
applicationcaninteractwiththeservicewithouttheneedtoresorttointer-process
communication(IPC).Ingeneralterms,theservice’sonBind()methodreturnsanIBinder
objectcontainingareferencetotheinstanceoftherunningservice.Theclientcomponent
implementsaServiceConnectionsubclasscontainingcallbackmethodsthatarecalled
whentheserviceisconnectedanddisconnected.TheformermethodispassedtheIBinder
objectreturnedbytheonBind()methodallowingpublicmethodswithintheservicetobe
called.
Havingcoveredtheimplementationoflocalboundservices,thenextchapterwillfocuson
usingIPCtointeractwithremoteboundservices.
45.AndroidRemoteBoundServices–A
WorkedExample
Inthis,thefinalchapterdedicatedtoAndroidservices,anexampleapplicationwillbe
developedtodemonstratetheuseofamessengerandhandlerconfigurationtofacilitate
interactionbetweenaclientandremoteboundservice.
45.1ClienttoRemoteServiceCommunication
Asoutlinedinthepreviouschapter,interactionbetweenaclientandalocalservicecanbe
implementedbyreturningtotheclientanIBinderobjectcontainingareferencetothe
serviceobject.Inthecaseofremoteservices,however,thisapproachdoesnotwork
becausetheremoteserviceisrunninginadifferentprocessand,assuch,cannotbe
reacheddirectlyfromtheclient.
Inthecaseofremoteservices,aMessengerandHandlerconfigurationmustbecreated
thatallowsmessagestobepassedacrossprocessboundariesbetweenclientandservice.
Specifically,theservicecreatesaHandlerinstancethatwillbecalledwhenamessageis
receivedfromtheclient.Intermsofinitialization,itisthejoboftheHandlertocreatea
Messengerobjectwhich,inturn,createsanIBinderobjecttobereturnedtotheclientin
theonBind()method.ThisIBinderobjectisusedbytheclienttocreateaninstanceofthe
Messengerobjectand,subsequently,tosendmessagestotheservicehandler.Eachtimea
messageissentbytheclient,thehandleMessage()methodofthehandleriscalled,passing
throughthemessageobject.
Thesimpleexamplecreatedinthischapterwillconsistofanactivityandaboundservice
runninginseparateprocesses.TheMessenger/Handlermechanismwillbeusedtosenda
stringtotheservice,whichwillthendisplaythatstringinaToastmessage.
45.2CreatingtheExampleApplication
LaunchAndroidStudioandfollowthestepstocreateanewproject,entering
RemoteBoundintotheApplicationnamefieldandebookfrenzy.comastheCompany
DomainsettingbeforeclickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedRemoteBoundActivitywithacorrespondinglayout
resourcefilenamedactivity_remote_bound.
45.3DesigningtheUserInterface
Locatetheactivity_remote_bound.xmlfileintheProjecttoolwindowanddoubleclickon
ittoloaditintotheDesignertool.WiththeDesignertoolinDesignmode,deletethe
defaultTextViewinstanceanddraganddropaButtonwidgetfromthepalettesothatitis
positionedinthecenterofthelayout.Doubleclickonthebuttonandchangethetext
propertytoread“SendMessage”.Extractthestringusingthelightbulbmenutoanew
stringresourcenamedbutton_text.
Next,switchDesignertoTextmodeandmodifytheButtonelementtodeclareanonClick
property:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=“com.ebookfrenzy.remotebound.remotebound.RemoteBoundActivity”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/button_string”
android:id=”@+id/button”
android:layout_centerVertical=“true”
android:layout_centerHorizontal=“true”
android:onClick=“sendMessage”/>
</RelativeLayout>
45.4ImplementingtheRemoteBoundService
Inordertoimplementtheremoteboundserviceforthisexample,addanewclasstothe
projectbyright-clickingonthepackagename(locatedunderapp->java)withinthe
ProjecttoolwindowandselecttheNew->Service->Servicemenuoption.Specify
RemoteServiceastheclassnameandmakesurethatboththeExportedandEnabled
optionsareselectedbeforeclickingonFinishtocreatetheclass.
Thenextstepistoimplementthehandlerclassforthenewservice.Thisisachievedby
extendingtheHandlerclassandimplementingthehandleMessage()method.Thismethod
willbecalledwhenamessageisreceivedfromtheclient.Itwillbepassedasanargument
aMessageobjectcontaininganydatathattheclientneedstopasstotheservice.Inthis
instance,thiswillbeaBundleobjectcontainingastringtobedisplayedtotheuser.The
modifiedclassintheRemoteService.javafileshouldreadasfollowsoncethishasbeen
implemented:
packagecom.ebookfrenzy.remotebound;
importandroid.app.Service;
importandroid.content.Intent;
importandroid.os.IBinder;
importandroid.os.Bundle;
importandroid.os.Handler;
importandroid.os.Message;
importandroid.widget.Toast;
importandroid.os.Messenger;
publicclassRemoteServiceextendsService{
publicRemoteService(){
}
classIncomingHandlerextendsHandler{
@Override
publicvoidhandleMessage(Messagemsg){
Bundledata=msg.getData();
StringdataString=data.getString(“MyString”);
Toast.makeText(getApplicationContext(),
dataString,Toast.LENGTH_SHORT).show();
}
}
@Override
publicIBinderonBind(Intentintent){
//TODO:Returnthecommunicationchanneltotheservice.
thrownewUnsupportedOperationException(“Notyetimplemented”);
}
}
Withthehandlerimplemented,theonlyremainingtaskintermsoftheservicecodeisto
modifytheonBind()methodsuchthatitreturnsanIBinderobjectcontainingaMessenger
objectwhich,inturn,containsareferencetothehandler:
finalMessengermyMessenger=newMessenger(newIncomingHandler());
@Override
publicIBinderonBind(Intentintent){
returnmyMessenger.getBinder();
}
Thefirstlineoftheabovecodefragmentcreatesanewinstanceofourhandlerclassand
passesitthroughtotheconstructorofanewMessengerobject.WithintheonBind()
method,thegetBinder()methodofthemessengerobjectiscalledtoreturnthe
messenger’sIBinderobject.
45.5ConfiguringaRemoteServiceintheManifestFile
Inordertoportraythecommunicationbetweenaclientandremoteserviceaccurately,it
willbenecessarytoconfiguretheservicetoruninaseparateprocessfromtherestofthe
application.Thisisachievedbyaddinganandroid:processpropertywithinthe<service>
tagfortheserviceinthemanifestfile.Inordertolauncharemoteserviceitisalso
necessarytoprovideanintentfilterfortheservice.Toimplementthesechanges,modify
theAndroidManifest.xmlfiletoaddtherequiredentries:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.remotebound”>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.RemoteBoundActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<service
android:name=”.RemoteService”
android:enabled=“true”
android:exported=“true”
android:process=”:my_process”>
</service>
</service>
</application>
</manifest>
45.6LaunchingandBindingtotheRemoteService
Aswithalocalboundservice,theclientcomponentneedstoimplementaninstanceofthe
ServiceConnectionclasswithonServiceConnected()andonServiceDisconnected()
methods.Alsoincommonwithlocalservices,theonServiceConnected()methodwillbe
passedtheIBinderobjectreturnedbytheonBind()methodoftheremoteservicewhich
willbeusedtosendmessagestotheserverhandler.Inthecaseofthisexample,theclient
isRemoteBoundActivity,thecodeforwhichislocatedinRemoteBoundActivity.java.Load
thisfileandmodifyittoaddtheServiceConnectionclassandavariabletostorea
referencetothereceivedMessengerobjecttogetherwithaBooleanflagtoindicate
whetherornottheconnectionisestablished:
packagecom.ebookfrenzy.remotebound;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.os.IBinder;
importandroid.os.Message;
importandroid.os.Messenger;
importandroid.os.RemoteException;
importandroid.content.ComponentName;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.content.ServiceConnection;
importandroid.view.View;
publicclassRemoteBoundActivityextendsAppCompatActivity{
MessengermyService=null;
booleanisBound;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_remote_bound);
}
privateServiceConnectionmyConnection=
newServiceConnection(){
publicvoidonServiceConnected(ComponentNameclassName,
IBinderservice){
myService=newMessenger(service);
isBound=true;
}
publicvoidonServiceDisconnected(ComponentName
className){
myService=null;
isBound=false;
}
};
.
.
.
}
Next,somecodeneedstobeaddedtobindtotheremoteservice.Thisinvolvescreatingan
intentthatmatchestheintentfilterfortheserviceasdeclaredinthemanifestfileandthen
makingacalltothebindService()method,providingtheintentandareferencetothe
ServiceConnectioninstanceasarguments.Forthepurposesofthisexample,thiscodewill
beimplementedintheactivity’sonCreate()method:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_remote_bound);
Intentintent=newIntent(getApplicationContext(),
RemoteService.class);
bindService(intent,myConnection,Context.BIND_AUTO_CREATE);
}
45.7SendingaMessagetotheRemoteService
AllthatremainsbeforetestingtheapplicationistoimplementthesendMessage()method
intheRemoteBoundActivityclasswhichisconfiguredtobecalledwhenthebuttoninthe
userinterfaceistouchedbytheuser.Thismethodneedstocheckthattheserviceis
connected,createabundleobjectcontainingthestringtobedisplayedbytheserver,addit
toaMessageobjectandsendittotheserver:
publicvoidsendMessage(Viewview)
{
if(!isBound)return;
Messagemsg=Message.obtain();
Bundlebundle=newBundle();
bundle.putString(“MyString”,“MessageReceived”);
msg.setData(bundle);
try{
myService.send(msg);
}catch(RemoteExceptione){
e.printStackTrace();
}
}
Withthecodechangescomplete,compileandruntheapplication.Onceloaded,touchthe
buttonintheuserinterface,atwhichpointaToastmessageshouldappearthatreads
“MessageReceived”.
45.8Summary
Inordertoimplementinteractionbetweenaclientandremoteboundserviceitis
necessarytoimplementahandler/messagecommunicationframework.Thebasicconcepts
behindthistechniquehavebeencoveredinthischaptertogetherwiththeimplementation
ofanexampleapplicationdesignedtodemonstratecommunicationbetweenaclientanda
boundservice,eachrunninginaseparateprocess.
46.AnOverviewofAndroidSQLite
Databases
Mobileapplicationsthatdonotneedtostoreatleastsomeamountofpersistentdataare
fewandfarbetween.Theuseofdatabasesisanessentialaspectofmostapplications,
rangingfromapplicationsthatarealmostentirelydatadriven,tothosethatsimplyneedto
storesmallamountsofdatasuchastheprevailingscoreofagame.
Theimportanceofpersistentdatastoragebecomesevenmoreevidentwhentakinginto
considerationthesomewhattransientlifecycleofthetypicalAndroidapplication.Withthe
ever-presentriskthattheAndroidruntimesystemwillterminateanapplicationcomponent
tofreeupresources,acomprehensivedatastoragestrategytoavoiddatalossisakey
factorinthedesignandimplementationofanyapplicationdevelopmentstrategy.
ThischapterwillprovideanoverviewoftheSQLitedatabasemanagementsystem
bundledwiththeAndroidoperatingsystem,togetherwithanoutlineoftheAndroidSDK
classesthatareprovidedtofacilitatepersistentSQLitebaseddatabasestoragefromwithin
anAndroidapplication.BeforedelvingintothespecificsofSQLiteinthecontextof
Androiddevelopment,however,abriefoverviewofdatabasesandSQLwillbecovered.
46.1UnderstandingDatabaseTables
DatabaseTablesprovidethemostbasiclevelofdatastructureinadatabase.Eachdatabase
cancontainmultipletablesandeachtableisdesignedtoholdinformationofaspecific
type.Forexample,adatabasemaycontainacustomertablethatcontainsthename,
addressandtelephonenumberforallthecustomersofaparticularbusiness.Thesame
databasemayalsoincludeaproductstableusedtostoretheproductdescriptionswith
associatedproductcodesfortheitemssoldbythebusiness.
Eachtableinadatabaseisassignedanamethatmustbeuniquewithinthatparticular
database.Atablename,onceassignedtoatableinonedatabase,mayonlybere-used
withinthecontextofadifferentdatabase.
46.2IntroducingDatabaseSchema
DatabaseSchemadefinethecharacteristicsofthedatastoredinadatabasetable.For
example,thetableschemaforacustomerdatabasetablemightdefinethatthecustomer
nameisastringofnomorethan20charactersinlength,andthatthecustomerphone
numberisanumericaldatafieldofacertainformat.
Schemaarealsousedtodefinethestructureofentiredatabasesandtherelationship
betweenthevarioustablescontainedineachdatabase.
46.3ColumnsandDataTypes
Itishelpfulatthisstagetobegintoviewadatabasetableasbeingsimilartoaspreadsheet
wheredataisstoredinrowsandcolumns.
Eachcolumnrepresentsadatafieldinthecorrespondingtable.Forexample,thename,
addressandtelephonedatafieldsofatableareallcolumns.
Eachcolumn,inturn,isdefinedtocontainacertaindatatypewhichdictatesthetypeof
datathecolumncancontain.Acolumndesignedtostorenumberswould,therefore,be
definedasanumericaldatatype.
46.4DatabaseRows
Eachnewrecordthatissavedtoatableisstoredinarow.Eachrow,inturn,consistsof
thecolumnsofdataassociatedwiththesavedrecord.
Onceagain,considerthespreadsheetanalogydescribedearlierinthischapter.Eachentry
inacustomertableisequivalenttoarowinaspreadsheetandeachcolumncontainsthe
dataforeachcustomer(name,address,telephoneetc).Whenanewcustomerisaddedto
thetable,anewrowiscreatedandthedataforthatcustomerstoredinthecorresponding
columnsofthenewrow.
Rowsarealsosometimesreferredtoasrecordsorentriesandthesetermscangenerallybe
usedinterchangeably.
46.5IntroducingPrimaryKeys
Eachdatabasetablemustcontainoneormorecolumnsthatcanbeusedtoidentifyeach
rowinthetableuniquely.ThisisknownindatabaseterminologyasthePrimaryKey.For
example,atablemayuseabankaccountnumbercolumnastheprimarykey.
Alternatively,acustomertablemayusethecustomer’ssocialsecuritynumberasthe
primarykey.
Primarykeysallowthedatabasemanagementsystemtoidentifyaspecificrowinatable
uniquely.Withoutaprimarykeyitwouldnotbepossibletoretrieveordeleteaspecific
rowinatablebecausetherecanbenocertaintythatthecorrectrowhasbeenselected.For
example,supposeatableexistedwherethecustomer’slastnamehadbeendefinedasthe
primarykey.Imaginethentheproblemthatmightariseifmorethanonecustomernamed
“Smith”wererecordedinthedatabase.Withoutsomeguaranteedwaytoidentifya
specificrowuniquely,itwouldbeimpossibletoensurethecorrectdatawasbeing
accessedatanygiventime.
Primarykeyscancompriseasinglecolumnormultiplecolumnsinatable.Toqualifyasa
singlecolumnprimarykey,notworowscancontainmatchingprimarykeyvalues.When
usingmultiplecolumnstoconstructaprimarykey,individualcolumnvaluesdonotneed
tobeunique,butallthecolumnscombinedmustbeunique.
46.6WhatisSQLite?
SQLiteisanembedded,relationaldatabasemanagementsystem(RDBMS).Most
relationaldatabases(OracleandMySQLbeingprimeexamples)arestandaloneserver
processesthatrunindependently,andincooperationwith,applicationsthatrequire
databaseaccess.SQLiteisreferredtoasembeddedbecauseitisprovidedintheformofa
librarythatislinkedintoapplications.Assuch,thereisnostandalonedatabaseserver
runninginthebackground.Alldatabaseoperationsarehandledinternallywithinthe
applicationthroughcallstofunctionscontainedintheSQLitelibrary.
ThedevelopersofSQLitehaveplacedthetechnologyintothepublicdomainwiththe
resultthatitisnowawidelydeployeddatabasesolution.
SQLiteiswrittenintheCprogramminglanguageandassuch,theAndroidSDKprovides
aJavabased“wrapper”aroundtheunderlyingdatabaseinterface.Thisessentiallyconsists
ofasetofclassesthatmaybeutilizedwithintheJavacodeofanapplicationtocreateand
manageSQLitebaseddatabases.
ForadditionalinformationaboutSQLiterefertohttp://www.sqlite.org.
46.7StructuredQueryLanguage(SQL)
DataisaccessedinSQLitedatabasesusingahigh-levellanguageknownasStructured
QueryLanguage.ThisisusuallyabbreviatedtoSQLandpronouncedsequel.SQLisa
standardlanguageusedbymostrelationaldatabasemanagementsystems.SQLite
conformsmostlytotheSQL-92standard.
SQLisessentiallyaverysimpleandeasytouselanguagedesignedspecificallytoenable
thereadingandwritingofdatabasedata.BecauseSQLcontainsasmallsetofkeywords,it
canbelearnedquickly.Inaddition,SQLsyntaxismoreorlessidenticalbetweenmost
DBMSimplementations,sohavinglearnedSQLforonesystem,itislikelythatyourskills
willtransfertootherdatabasemanagementsystems.
WhilesomebasicSQLstatementswillbeusedwithinthischapter,adetailedoverviewof
SQLisbeyondthescopeofthisbook.Thereare,however,manyotherresourcesthat
provideafarbetteroverviewofSQLthanwecouldeverhopetoprovideinasingle
chapterhere.
46.8TryingSQLiteonanAndroidVirtualDevice(AVD)
ForreadersunfamiliarwithdatabasesingeneralandSQLiteinparticular,divingrightinto
creatinganAndroidapplicationthatusesSQLitemayseemalittleintimidating.
Fortunately,AndroidisshippedwithSQLitepre-installed,includinganinteractive
environmentforissuingSQLcommandsfromwithinanadbshellsessionconnectedtoa
runningAndroidAVDemulatorinstance.ThisisbothausefulwaytolearnaboutSQLite
andSQL,andalsoaninvaluabletoolforidentifyingproblemswithdatabasescreatedby
applicationsrunninginanemulator.
TolaunchaninteractiveSQLitesession,beginbyrunninganAVDsession.Thiscanbe
achievedfromwithinAndroidStudiobylaunchingtheAndroidVirtualDeviceManager
(Tools->Android->AVDManager),selectingapreviouslyconfiguredAVDandclicking
onStart.
OncetheAVDisupandrunning,openaTerminalorCommand-Promptwindowand
connecttotheemulatorusingtheadbcommand-linetoolasfollows(notethatthe–eflag
directsthetooltolookforanemulatorwithwhichtoconnect,ratherthanaphysical
device):
adb–eshell
Onceconnected,theshellenvironmentwillprovideacommandpromptatwhich
commandsmaybeentered:
root@android:/#
DatastoredinSQLitedatabasesareactuallystoredindatabasefilesonthefilesystemof
theAndroiddeviceonwhichtheapplicationisrunning.Bydefault,thefilesystempath
forthesedatabasefilesisasfollows:
/data/data/<packagename>/databases/<databasefilename>.db
Forexample,ifanapplicationwiththepackagenamecom.example.MyDBAppcreatesa
databasenamedmydatabase.db,thepathtothefileonthedevicewouldreadasfollows:
/data/data/com.example.MyDBApp/databases/mydatabase.db
Forthepurposesofthisexercise,therefore,changedirectoryto/data/datawithintheadb
shellandcreateasub-directoryhierarchysuitableforsomeSQLiteexperimentation:
cd/data/data
mkdircom.example.dbexample
cdcom.example.dbexample
mkdirdatabases
cddatabases
Withasuitablelocationcreatedforthedatabasefile,launchtheinteractiveSQLitetoolas
follows:
root@android:/data/data/databases#sqlite3./mydatabase.db
sqlite3./mydatabase.db
SQLiteversion3.7.4
Enter“.help”forinstructions
EnterSQLstatementsterminatedwitha“;”
sqlite>
Atthesqlite>prompt,commandsmaybeenteredtoperformtaskssuchascreatingtables
andinsertingandretrievingdata.Forexample,tocreateanewtableinourdatabasewith
fieldstoholdID,name,addressandphonenumberfieldsthefollowingstatementis
required:
createtablecontacts(_idintegerprimarykeyautoincrement,name
text,addresstext,phonetext);
Notethateachrowinatablemusthaveaprimarykeythatisuniquetothatrow.Inthe
aboveexample,wehavedesignatedtheIDfieldastheprimarykey,declareditasbeingof
typeintegerandaskedSQLitetoincrementautomaticallythenumbereachtimearowis
added.Thisisacommonwaytomakesurethateachrowhasauniqueprimarykey.On
mostotherplatforms,thechoiceofnamefortheprimarykeyisarbitrary.Inthecaseof
Android,however,itisessentialthatthekeybenamed_idinorderforthedatabasetobe
fullyaccessibleusingalloftheAndroiddatabaserelatedclasses.Theremainingfieldsare
eachdeclaredasbeingoftypetext.
Tolistthetablesinthecurrentlyselecteddatabase,usethe.tablesstatement:
sqlite>.tables
contacts
Toinsertrecordsintothetable:
sqlite>insertintocontacts(name,address,phone)values(“Bill
Smith”,“123MainStreet,California”,“123-555-2323”);
sqlite>insertintocontacts(name,address,phone)values(“Mike
Parks”,“10UppingStreet,Idaho”,“444-444-1212”);
Toretrieveallrowsfromatable:
sqlite>select*fromcontacts;
1|BillSmith|123MainStreet,California|123-555-2323
2|MikeParks|10UppingStreet,Idaho|444-444-1212
Toextractarowthatmeetsspecificcriteria:
sqlite>select*fromcontactswherename=“MikeParks”;
2|MikeParks|10UppingStreet,Idaho|444-444-1212
Toexitfromthesqlite3interactiveenvironment:
sqlite>.exit
WhenrunninganAndroidapplicationintheemulatorenvironment,anydatabasefileswill
becreatedonthefilesystemoftheemulatorusingthepreviouslydiscussedpath
convention.Thishastheadvantagethatyoucanconnectwithadb,navigatetothelocation
ofthedatabasefile,loaditintothesqlite3interactivetoolandperformtasksonthedatato
identifypossibleproblemsoccurringintheapplicationcode.
Itisalsoimportanttonotethat,whileitispossibletoconnectwithanadbshelltoa
physicalAndroiddevice,theshellisnotgrantedsufficientprivilegesbydefaulttocreate
andmanageSQLitedatabases.Debuggingofdatabaseproblemsis,therefore,best
performedusinganAVDsession.
46.9AndroidSQLiteJavaClasses
SQLiteis,aspreviouslymentioned,writtenintheCprogramminglanguagewhile
AndroidapplicationsareprimarilydevelopedusingJava.Tobridgethis“languagegap”,
theAndroidSDKincludesasetofclassesthatprovideaJavalayerontopoftheSQLite
databasemanagementsystem.Theremainderofthischapterwillprovideabasicoverview
ofeachofthemajorclasseswithinthiscategory.Moredetailsoneachclasscanbefound
intheonlineAndroiddocumentation.
46.9.1Cursor
Aclassprovidedspecificallytoprovideaccesstotheresultsofadatabasequery.For
example,aSQLSELECToperationperformedonadatabasewillpotentiallyreturn
multiplematchingrowsfromthedatabase.ACursorinstancecanbeusedtostepthrough
theseresults,whichmaythenbeaccessedfromwithintheapplicationcodeusingavariety
ofmethods.Somekeymethodsofthisclassareasfollows:
· close()–Releasesallresourcesusedbythecursorandclosesit.
· getCount()–Returnsthenumberofrowscontainedwithintheresultset.
· moveToFirst()–Movestothefirstrowwithintheresultset.
· moveToLast()–Movestothelastrowintheresultset.
· moveToNext()–Movestothenextrowintheresultset.
· move()–Movesbyaspecifiedoffsetfromthecurrentpositionintheresultset.
· get<type>()–Returnsthevalueofthespecified<type>containedatthespecified
columnindexoftherowatthecurrentcursorposition(variationsconsistofgetString(),
getInt(),getShort(),getFloat()andgetDouble()).
46.9.2SQLiteDatabase
Thisclassprovidestheprimaryinterfacebetweentheapplicationcodeandunderlying
SQLitedatabasesincludingtheabilitytocreate,deleteandperformSQLbasedoperations
ondatabases.Somekeymethodsofthisclassareasfollows:
· insert()–Insertsanewrowintoadatabasetable.
· delete()–Deletesrowsfromadatabasetable.
· query()–Performsaspecifieddatabasequeryandreturnsmatchingresultsviaa
Cursorobject.
· execSQL()–ExecutesasingleSQLstatementthatdoesnotreturnresultdata.
· rawQuery()–ExecutesanSQLquerystatementandreturnsmatchingresultsinthe
formofaCursorobject.
46.9.3SQLiteOpenHelper
Ahelperclassdesignedtomakeiteasiertocreateandupdatedatabases.Thisclassmust
besubclassedwithinthecodeoftheapplicationseekingdatabaseaccessandthefollowing
callbackmethodsimplementedwithinthatsubclass:
· onCreate()–Calledwhenthedatabaseiscreatedforthefirsttime.Thismethodis
passedasanargumenttheSQLiteDatabaseobjectforthenewlycreateddatabase.This
istheideallocationtoinitializethedatabaseintermsofcreatingatableandinserting
anyinitialdatarows.
· onUpgrade()–Calledintheeventthattheapplicationcodecontainsamorerecent
databaseversionnumberreference.Thisistypicallyusedwhenanapplicationis
updatedonthedeviceandrequiresthatthedatabaseschemaalsobeupdatedtohandle
storageofadditionaldata.
Inadditiontotheabovemandatorycallbackmethods,theonOpen()method,calledwhen
thedatabaseisopened,mayalsobeimplementedwithinthesubclass.
Theconstructorforthesubclassmustalsobeimplementedtocallthesuperclass,passing
throughtheapplicationcontext,thenameofthedatabaseandthedatabaseversion.
NotablemethodsoftheSQLiteOpenHelperclassinclude:
· getWritableDatabase()–Opensorcreatesadatabaseforreadingandwriting.Returns
areferencetothedatabaseintheformofaSQLiteDatabaseobject.
· getReadableDatabase()–Createsoropensadatabaseforreadingonly.Returnsa
referencetothedatabaseintheformofaSQLiteDatabaseobject.
· close()–Closesthedatabase.
46.9.4ContentValues
ContentValuesisaconvenienceclassthatallowskey/valuepairstobedeclaredconsisting
oftablecolumnidentifiersandthevaluestobestoredineachcolumn.Thisclassisof
particularusewheninsertingorupdatingentriesinadatabasetable.
46.10Summary
SQLiteisalightweight,embeddedrelationaldatabasemanagementsystemthatis
includedaspartoftheAndroidframeworkandprovidesamechanismforimplementing
organizedpersistentdatastorageforAndroidapplications.InadditiontotheSQLite
database,theAndroidframeworkalsoincludesarangeofJavaclassesthatmaybeusedto
createandmanageSQLitebaseddatabasesandtables.
Thegoalofthischapterhasbeentoprovideanoverviewofdatabasesingeneraland
SQLiteinparticularwithinthecontextofAndroidapplicationdevelopment.Thenext
chapterswillworkthroughthecreationofanexampleapplicationintendedtoputthis
theoryintopracticeintheformofastep-by-steptutorial.Sincetheuserinterfaceforthe
exampleapplicationwillrequireaformsbasedlayout,thefirstchapter,entitledAn
AndroidTableLayoutandTableRowTutorial,willdetourslightlyfromthecoretopicby
introducingthebasicsoftheTableLayoutandTableRowviews.
47.AnAndroidTableLayoutandTableRow
Tutorial
Whentheworkbeganonthenextchapterofthisbook(AnAndroidSQLiteDatabase
Tutorial)itwasoriginallyintendedthatitwouldincludethestepstodesigntheuser
interfacelayoutforthedatabaseexampleapplication.Itquicklybecameevident,however,
thatthebestwaytoimplementtheuserinterfacewastomakeuseoftheAndroid
TableLayoutandTableRowviewsandthatthistopicareadeservedaself-contained
chapter.Asaresult,thischapterwillfocussolelyontheuserinterfacedesignofthe
databaseapplicationcompletedinthenextchapter,andindoingso,takesometimeto
introducethebasicconceptsoftablelayoutsinAndroidStudio.
47.1TheTableLayoutandTableRowLayoutViews
ThepurposeoftheTableLayoutcontainerviewistoallowuserinterfaceelementstobe
organizedonthescreeninatableformatconsistingofrowsandcolumns.Eachrowwithin
aTableLayoutisoccupiedbyaTableRowinstance,which,inturn,isdividedintocells,
witheachcellcontainingasinglechildview(whichmayitselfbeacontainerwith
multipleviewchildren).
Thenumberofcolumnsinatableisdictatedbytherowwiththemostcolumnsand,by
default,thewidthofeachcolumnisdefinedbythewidestcellinthatcolumn.Columns
maybeconfiguredtobeshrinkableorstretchable(orboth)suchthattheychangeinsize
relativetotheparentTableLayout.Inaddition,asinglecellmaybeconfiguredtospan
multiplecolumns.
ConsidertheuserinterfacelayoutshowninFigure47-1:
Figure47-1
Fromthevisualappearanceofthelayout,itisdifficulttoidentifytheTableLayout
structureusedtodesigntheinterface.ThehierarchicaltreeillustratedinFigure47-2,
however,makesthestructurealittleeasiertounderstand:
Figure47-2
Clearly,thelayoutconsistsofaparentLinearLayoutviewwithTableLayoutand
LinearLayoutchildren.TheTableLayoutcontainsthreeTableRowchildrenrepresenting
threerowsinthetable.TheTableRowscontaintwochildviews,witheachchild
representingthecontentsofacolumncell.TheLinearLayoutchildviewcontainsthree
Buttonchildren.
ThelayoutshowninFigure47-2istheexactlayoutthatisrequiredforthedatabase
examplethatwillbecompletedinthenextchapter.Theremainderofthischapter,
therefore,willbeusedtoworkstepbystepthroughthedesignofthisuserinterfaceusing
theAndroidStudioDesignertool.
47.2CreatingtheDatabaseProject
StartAndroidStudioandcreateanewproject,enteringDatabaseintotheApplication
namefieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingonthe
Nextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedDatabaseActivitywithacorrespondinglayoutfile
namedactivity_database.
47.3AddingtheTableLayouttotheUserInterface
Locatetheactivity_database.xmlfileintheProjecttoolwindow(app->res->layout)
anddoubleclickonittoloaditintotheDesignertool.Bydefault,AndroidStudiohas
usedaRelativeLayoutastherootlayoutelementintheuserinterface.Thisneedstobe
replacedbyaverticallyorientedLinearLayout.WiththeDesignertoolinTextmode,
replacetheXMLwiththefollowing:
<?xmlversion=“1.0”encoding=“utf-8”?>
<LinearLayout
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
xmlns:android=“http://schemas.android.com/apk/res/android”>
</LinearLayout>
SwitchtoDesignmodeand,referringtotheLayoutssectionofthePalette,draganddropa
TableLayoutviewsothatitispositionedatthetopoftheLinearLayoutcanvasareaas
illustratedinFigure47-3:
Figure47-3
Oncetheseinitialstepsarecomplete,theComponentTreeforthelayoutshouldresemble
thatshowninFigure47-4:
Figure47-4
47.4AddingandConfiguringtheTableRows
NowthattheTableLayouthasbeenaddedtotheuserinterfacelayout,threeTableRow
instancesneedtobeaddedaschildren.FromtheDesignerpalette,locatetheTableRow
entrylistedunderLayoutsanddraganddropaninstancedirectlyontothetopofthe
TableLayoutentryintheComponentTreepanel.Repeatthissteptoaddtwomore
TableRowssothatthecomponenttreematchesFigure47-5:
Figure47-5
FromwithintheWidgetssectionofthepalette,draganddroptwoLargeTextTextView
objectsontotheuppermostTableRowentryintheComponentTree(Figure47-6):
Figure47-6
DoubleclickontheleftmostTextViewwithinthescreenlayoutand,inthequickproperty
settingspanel,changethetextpropertyto“ProductID”.Repeatthisstepfortherightmost
TextView,thistimechangingthetextto“Notassigned”andspecifyinganidvalueof
productID.ExtractthetextforeachTextViewtonewstringresourcesusingthelightbulb
icondisplayedwhentheviewisselected.
DraganddropanotherLargeTextviewandaPlainTextEditTextviewfromtheText
FieldssectionofthepaletteontothesecondTableRowentryintheComponentTree.
ChangethetextontheTextViewtoProductNameandtheIDoftheEditTextobjectto
productName.
DraganddropanotherLargeTextviewandaNumber(Decimal)TextFieldontothethird
TableRow.ChangethetextontheTextViewtoProductQuantityandtheIDoftheText
FieldobjecttoproductQuantity.
Beforeproceeding,besuretoextractallofthetextpropertiesaddedintheabovestepsto
stringresources.
47.5AddingtheButtonBartotheLayout
ThenextstepistoaddaLinearLayout(Horizontal)viewtotheparentLinearLayoutview,
positionedimmediatelybelowtheTableLayoutview.DraganddropaLinearLayout
(Horizontal)instancefromtheLayoutssectionoftheDesignerpaletteanddropitdirectly
ontotheLinearLayout(Vertical)entryintheComponentTreepanel.
DraganddropthreeButtonobjectsontothenewLinearLayoutandassignstringresources
foreachbuttonthatread“Add”,“Find”and“Delete”respectively.
WiththenewhorizontalLinearLayoutviewselectedintheComponentTree,clickonthe
GravitybuttonintheDesignertoolbar(Figure47-7)andselecttheCentergravityoption
sothatthebuttonsarecenteredhorizontallywithinthedisplay:
Figure47-7
Beforeproceeding,alsocheckthehierarchyofthelayoutintheComponentTreepanel,
takingextracaretoensuretheviewIDnamesmatchthoseinthefollowingfigure:
Figure47-8
47.6AdjustingtheLayoutMargins
Allthatremainsistoadjustsomeofthelayoutsettings.Beginbyclickingonthefirst
TableRowentryintheComponentTreepanelsothatitisselected.HolddowntheCtrlkeyonthekeyboardandclickinthesecondandthirdTableRowssothatallthreeitems
areselected.InthePropertiespanel,locatethelayout:marginpropertycategoryand,once
located,unfoldthecategoryandchangetheallsettingto10dpasshowninFigure47-9:
Figure47-9
WithmarginssetonallfoursidesofthethreeTableRows,theuserinterfaceshouldappear
asillustratedinFigure47-1.Forthesakeofcompleteness,andforcomparisonpurposesin
theeventthatyourlayoutdoesnotmatchthatinFigure47-1,thefull
activity_database.xmlstructureforthislayoutisoutlinedbelow.
<?xmlversion=“1.0”encoding=“utf-8”?>
<LinearLayout
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
xmlns:android=“http://schemas.android.com/apk/res/android”>
<TableLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_gravity=“center_horizontal”>
<TableRow
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_margin=“10dp”>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:textAppearance=”?
android:attr/textAppearanceLarge”
android:text=”@string/prodid_string”
android:id=”@+id/textView”/>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:textAppearance=”?android:attr/textAppearanceLarge”
android:text=”@string/not_assigned_string”
android:id=”@+id/productID”/>
</TableRow>
<TableRow
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_margin=“10dp”>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:textAppearance=”?android:attr/textAppearanceLarge”
android:text=”@string/prodname_string”
android:id=”@+id/textView2”/>
<EditText
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:id=”@+id/productName”/>
</TableRow>
<TableRow
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_margin=“10dp”>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:textAppearance=”?android:attr/textAppearanceLarge”
android:text=”@string/quantity_text”
android:id=”@+id/textView3”/>
<EditText
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:inputType=“number”
android:ems=“10”
android:id=”@+id/productQuantity”/>
</TableRow>
</TableLayout>
<LinearLayout
android:orientation=“horizontal”
android:layout_width=“wrap_content”
android:layout_height=“match_parent”
android:layout_gravity=“center_horizontal”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/add_string”
android:id=”@+id/button”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/find_string”
android:id=”@+id/button2”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/delete_string”
android:id=”@+id/button3”/>
</LinearLayout>
</LinearLayout>
47.7Summary
TheAndroidTableLayoutcontainerviewprovidesawaytoarrangeviewcomponentsina
rowandcolumnconfiguration.WhiletheTableLayoutviewprovidestheoverall
container,eachrow,andthecellscontainedtherein,areimplementedviainstancesofthe
TableRowview.Inthischapter,auserinterfacehasbeendesignedinAndroidStudio
usingtheTableLayoutandTableRowcontainers.Thenextchapterwilladdthe
functionalitybehindthisuserinterfacetoimplementtheSQLitedatabasecapabilities.
48.AnAndroidSQLiteDatabaseTutorial
ThechapterentitledAnOverviewofAndroidSQLiteDatabasescoveredthebasic
principlesofintegratingrelationaldatabasestorageintoAndroidapplicationsusingthe
SQLitedatabasemanagementsystem.Thepreviouschaptertookaminordetourintothe
territoryofdesigningTableLayoutswithintheAndroidStudioDesignertool,inthecourse
ofwhich,theuserinterfaceforanexampledatabaseapplicationwascreated.Inthis
chapter,workontheDatabaseapplicationprojectwillbecontinuedwiththeultimate
objectiveofcompletingthedatabaseexample.
48.1AbouttheDatabaseExample
Asisprobablyevidentfromtheuserinterfacelayoutdesignedintheprecedingchapter,
theexampleprojectisasimpledataentryandretrievalapplicationdesignedtoallowthe
usertoadd,queryanddeletedatabaseentries.Theideabehindthisapplicationistoallow
thetrackingofproductinventory.
ThenameofthedatabasewillbeproductID.dbwhich,inturn,willcontainasingletable
namedproducts.EachrecordinthedatabasetablewillcontainauniqueproductID,a
productdescriptionandthequantityofthatproductitemcurrentlyinstock,corresponding
tocolumnnamesof“productid”,“productname”and“productquantity”respectively.The
productidcolumnwillactastheprimarykeyandwillbeautomaticallyassignedand
incrementedbythedatabasemanagementsystem.
ThedatabaseschemafortheproductstableisoutlinedinTable48-1:
Column
DataType
productid
Integer/PrimaryKey/AutoIncrement
productname
Text
productquantity
Integer
Table48-1
48.2CreatingtheDataModel
Oncecompleted,theapplicationwillconsistofanactivityandadatabasehandlerclass.
ThedatabasehandlerwillbeasubclassofSQLiteOpenHelperandwillprovideanabstract
layerbetweentheunderlyingSQLitedatabaseandtheactivityclass,withtheactivity
callingonthedatabasehandlertointeractwiththedatabase(adding,removingand
queryingdatabaseentries).Inordertoimplementthisinteractioninastructuredway,a
thirdclasswillneedtobeimplementedtoholdthedatabaseentrydataasitispassed
betweentheactivityandthehandler.Thisisactuallyaverysimpleclasscapableof
holdingproductID,productnameandproductquantityvalues,togetherwithgetterand
settermethodsforaccessingthesevalues.Instancesofthisclasscanthenbecreatedwithin
theactivityanddatabasehandlerandpassedbackandforthasneeded.Essentially,this
classcanbethoughtofasrepresentingthedatabasemodel.
WithinAndroidStudio,navigatewithintheProjecttoolwindowtoapp->javaandrightclickonthepackagename.Fromthepopupmenu,choosetheNew->JavaClassoption
and,intheCreateNewClassdialog,nametheclassProductbeforeclickingontheOK
button.
OncecreatedtheProduct.javasourcefilewillautomaticallyloadintotheAndroidStudio
editor.Onceloaded,modifythecodetoaddtheappropriatedatamembersandmethods:
packagecom.ebookfrenzy.database;
publicclassProduct{
privateint_id;
privateString_productname;
privateint_quantity;
publicProduct(){
}
publicProduct(intid,Stringproductname,intquantity){
this._id=id;
this._productname=productname;
this._quantity=quantity;
}
publicProduct(Stringproductname,intquantity){
this._productname=productname;
this._quantity=quantity;
}
publicvoidsetID(intid){
this._id=id;
}
publicintgetID(){
returnthis._id;
}
publicvoidsetProductName(Stringproductname){
this._productname=productname;
}
publicStringgetProductName(){
returnthis._productname;
}
publicvoidsetQuantity(intquantity){
this._quantity=quantity;
}
publicintgetQuantity(){
returnthis._quantity;
}
}
Thecompletedclasscontainsprivatedatamembersfortheinternalstorageofdata
columnsfromdatabaseentriesandasetofmethodstogetandsetthosevalues.
48.3ImplementingtheDataHandler
ThedatahandlerwillbeimplementedbysubclassingfromtheAndroid
SQLiteOpenHelperclassand,asoutlinedinAnOverviewofAndroidSQLiteDatabases,
addingtheconstructor,onCreate()andonUpgrade()methods.Sincethehandlerwillbe
requiredtoadd,queryanddeletedataonbehalfoftheactivitycomponent,corresponding
methodswillalsoneedtobeaddedtotheclass.
Beginbyaddingasecondnewclasstotheprojecttoactasthehandler,thistimenamed
MyDBHandler.Oncethenewclasshasbeencreated,modifyitsothatitreadsasfollows:
packagecom.ebookfrenzy.database;
importandroid.database.sqlite.SQLiteDatabase;
importandroid.database.sqlite.SQLiteOpenHelper;
publicclassMyDBHandlerextendsSQLiteOpenHelper{
@Override
publicvoidonCreate(SQLiteDatabasedb){
}
@Override
publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,
intnewVersion){
}
}
Havingnowpre-populatedthesourcefilewithtemplateonCreate()andonUpgrade()
methodsthenexttaskistoaddaconstructormethod.Modifythecodetodeclareconstants
forthedatabasename,tablename,tablecolumnsanddatabaseversionandtoaddthe
constructormethodasfollows:
packagecom.ebookfrenzy.database;
importandroid.database.sqlite.SQLiteDatabase;
importandroid.database.sqlite.SQLiteOpenHelper;
importandroid.content.Context;
importandroid.content.ContentValues;
importandroid.database.Cursor;
publicclassMyDBHandlerextendsSQLiteOpenHelper{
privatestaticfinalintDATABASE_VERSION=1;
privatestaticfinalStringDATABASE_NAME=“productDB.db”;
publicstaticfinalStringTABLE_PRODUCTS=“products”;
publicstaticfinalStringCOLUMN_ID=“_id”;
publicstaticfinalStringCOLUMN_PRODUCTNAME=“productname”;
publicstaticfinalStringCOLUMN_QUANTITY=“quantity”;
publicMyDBHandler(Contextcontext,Stringname,
SQLiteDatabase.CursorFactoryfactory,intversion){
super(context,DATABASE_NAME,factory,DATABASE_VERSION);
}
@Override
publicvoidonCreate(SQLiteDatabasedb){
}
@Override
publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,
intnewVersion){
}
}
Next,theonCreate()methodneedstobeimplementedsothattheproductstableiscreated
whenthedatabaseisfirstinitialized.ThisinvolvesconstructingaSQLCREATE
statementcontaininginstructionstocreateanewtablewiththeappropriatecolumnsand
thenpassingthatthroughtotheexecSQL()methodoftheSQLiteDatabaseobjectpassedas
anargumenttoonCreate():
@Override
publicvoidonCreate(SQLiteDatabasedb){
StringCREATE_PRODUCTS_TABLE=“CREATETABLE”+
TABLE_PRODUCTS+“(“
+COLUMN_ID+”INTEGERPRIMARYKEY,”+COLUMN_PRODUCTNAME
+”TEXT,”+COLUMN_QUANTITY+”INTEGER”+“)”;
db.execSQL(CREATE_PRODUCTS_TABLE);
}
TheonUpgrade()methodiscalledwhenthehandlerisinvokedwithagreaterdatabase
versionnumberfromtheonepreviouslyused.Theexactstepstobeperformedinthis
instancewillbeapplicationspecific,soforthepurposesofthisexamplewewillsimply
removetheolddatabaseandcreateanewone:
@Override
publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,
intnewVersion){
db.execSQL(“DROPTABLEIFEXISTS”+TABLE_PRODUCTS);
onCreate(db);
}
AllthatnowremainstobeimplementedintheMyDBHandler.javahandlerclassarethe
methodstoadd,queryandremovedatabasetableentries.
48.3.1TheAddHandlerMethod
ThemethodtoinsertdatabaserecordswillbenamedaddProduct()andwilltakeasan
argumentaninstanceofourProductdatamodelclass.AContentValuesobjectwillbe
createdinthebodyofthemethodandprimedwithkey-valuepairsforthedatacolumns
extractedfromtheProductobject.Next,areferencetothedatabasewillbeobtainedviaa
calltogetWritableDatabase()followedbyacalltotheinsert()methodofthereturned
databaseobject.Finally,oncetheinsertionhasbeenperformed,thedatabaseneedstobe
closed:
publicvoidaddProduct(Productproduct){
ContentValuesvalues=newContentValues();
values.put(COLUMN_PRODUCTNAME,product.getProductName());
values.put(COLUMN_QUANTITY,product.getQuantity());
SQLiteDatabasedb=this.getWritableDatabase();
db.insert(TABLE_PRODUCTS,null,values);
db.close();
}
48.3.2TheQueryHandlerMethod
ThemethodtoquerythedatabasewillbenamedfindProduct()andwilltakeasan
argumentaStringobjectcontainingthenameoftheproducttobelocated.Usingthis
string,aSQLSELECTstatementwillbeconstructedtofindallmatchingrecordsinthe
table.Forthepurposesofthisexample,onlythefirstmatchwillthenbereturned,
containedwithinanewinstanceofourProductdatamodelclass:
publicProductfindProduct(Stringproductname){
Stringquery=“Select*FROM”+TABLE_PRODUCTS+”WHERE”+
COLUMN_PRODUCTNAME+”="”+productname+“"”;
SQLiteDatabasedb=this.getWritableDatabase();
Cursorcursor=db.rawQuery(query,null);
Productproduct=newProduct();
if(cursor.moveToFirst()){
cursor.moveToFirst();
product.setID(Integer.parseInt(cursor.getString(0)));
product.setProductName(cursor.getString(1));
product.setQuantity(Integer.parseInt(cursor.getString(2)));
cursor.close();
}else{
product=null;
}
db.close();
returnproduct;
}
48.3.3TheDeleteHandlerMethod
ThedeletionmethodwillbenameddeleteProduct()andwillacceptasanargumentthe
entrytobedeletedintheformofaProductobject.ThemethodwilluseaSQLSELECT
statementtosearchfortheentrybasedontheproductnameand,iflocated,deleteitfrom
thetable.ThesuccessorotherwiseofthedeletionwillbereflectedinaBooleanreturn
value:
publicbooleandeleteProduct(Stringproductname){
booleanresult=false;
Stringquery=“Select*FROM”+TABLE_PRODUCTS+”WHERE”+
COLUMN_PRODUCTNAME+”="”+productname+“"”;
SQLiteDatabasedb=this.getWritableDatabase();
Cursorcursor=db.rawQuery(query,null);
Productproduct=newProduct();
if(cursor.moveToFirst()){
product.setID(Integer.parseInt(cursor.getString(0)));
db.delete(TABLE_PRODUCTS,COLUMN_ID+”=?”,
newString[]{String.valueOf(product.getID())});
cursor.close();
result=true;
}
db.close();
returnresult;
}
48.4ImplementingtheActivityEventMethods
ThefinaltaskpriortotestingtheapplicationistowireuponClickeventhandlersonthe
threebuttonsintheuserinterfaceandtoimplementcorrespondingmethodsforthose
events.Locateandloadtheactivity_database.xmlfileintotheDesignertool,switchto
TextmodeandlocateandmodifythethreebuttonelementstoaddonClickproperties:
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/add_string”
android:id=”@+id/button”
android:onClick=“newProduct”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/find_string”
android:id=”@+id/button2”
android:onClick=“lookupProduct”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/delete_string”
android:id=”@+id/button3”
android:onClick=“removeProduct”/>
LoadtheDatabaseActivity.javasourcefileintotheeditorandimplementthecodeto
identifytheviewsintheuserinterfaceandtoimplementthethree“onClick”target
methods:
packagecom.ebookfrenzy.database;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.EditText;
importandroid.widget.TextView;
publicclassDatabaseActivityextendsAppCompatActivity{
TextViewidView;
EditTextproductBox;
EditTextquantityBox;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_database);
idView=(TextView)findViewById(R.id.productID);
productBox=(EditText)findViewById(R.id.productName);
quantityBox=
(EditText)findViewById(R.id.productQuantity);
}
publicvoidnewProduct(Viewview){
MyDBHandlerdbHandler=newMyDBHandler(this,null,null,1);
intquantity=
Integer.parseInt(quantityBox.getText().toString());
Productproduct=
newProduct(productBox.getText().toString(),quantity);
dbHandler.addProduct(product);
productBox.setText(””);
quantityBox.setText(””);
}
publicvoidlookupProduct(Viewview){
MyDBHandlerdbHandler=newMyDBHandler(this,null,null,1);
Productproduct=
dbHandler.findProduct(productBox.getText().toString());
if(product!=null){
idView.setText(String.valueOf(product.getID()));
quantityBox.setText(String.valueOf(product.getQuantity()));
}else{
idView.setText(“NoMatchFound”);
}
}
publicvoidremoveProduct(Viewview){
MyDBHandlerdbHandler=newMyDBHandler(this,null,
null,1);
booleanresult=dbHandler.deleteProduct(
productBox.getText().toString());
if(result)
{
idView.setText(“RecordDeleted”);
productBox.setText(””);
quantityBox.setText(””);
}
else
idView.setText(“NoMatchFound”);
}
.
.
.
}
48.5TestingtheApplication
Withthecodingchangescompleted,compileandruntheapplicationeitherinanAVD
sessionoronaphysicalAndroiddevice.Oncetheapplicationisrunning,enteraproduct
nameandquantityvalueintotheuserinterfaceformandtouchtheAddbutton.Oncethe
recordhasbeenaddedthetextboxeswillclear.Repeatthesestepstoaddasecondproduct
tothedatabase.Next,enterthenameofoneofthenewlyaddedproductsintotheproduct
namefieldandtouchtheFindbutton.TheformshouldupdatewiththeproductIDand
quantityfortheselectedproduct.TouchtheDeletebuttontodeletetheselectedrecord.A
subsequentsearchbyproductnameshouldindicatethattherecordnolongerexists.
48.6Summary
Thepurposeofthischapterhasbeentoworkstepbystepthroughapracticalapplication
ofSQLitebaseddatabasestorageinAndroidapplications.Asanexercisetodevelopyour
newdatabaseskillsetfurther,considerextendingtheexampletoincludetheabilityto
updateexistingrecordsinthedatabasetable.
49.UnderstandingAndroidContent
Providers
Thepreviouschapterworkedthroughthecreationofanexampleapplicationdesignedto
storedatausingaSQLitedatabase.Whenimplementedinthisway,thedataisprivateto
theapplicationand,assuch,inaccessibletootherapplicationsrunningonthesamedevice.
Whilethismaybethedesiredbehaviorformanytypesofapplication,situationswill
inevitablyarisewherebythedatastoredonbehalfofanapplicationcouldbeofbenefitto
otherapplications.Aprimeexampleofthisisthedatastoredbythebuilt-inContacts
applicationonanAndroiddevice.WhiletheContactsapplicationisprimarilyresponsible
forthemanagementoftheuser’saddressbookdetails,thisdataisalsomadeaccessibleto
anyotherapplicationsthatmightneedaccesstothisdata.Thissharingofdatabetween
Androidapplicationsisachievedthroughtheimplementationofcontentproviders.
49.1WhatisaContentProvider?
AcontentproviderprovidesaccesstostructureddatabetweendifferentAndroid
applications.Thisdataisexposedtoapplicationseitherastablesofdata(inmuchthe
samewayasaSQLitedatabase)orasahandletoafile.Thisessentiallyinvolvesthe
implementationofaclient/serverarrangementwherebytheapplicationseekingaccessto
thedataistheclientandthecontentprovideristheserver,performingactionsand
returningresultsonbehalfoftheclient.
Asuccessfulcontentproviderimplementationinvolvesanumberofdifferentelements,
eachofwhichwillbecoveredindetailintheremainderofthischapter.
49.2TheContentProvider
Acontentprovideriscreatedasasubclassoftheandroid.content.ContentProviderclass.
Typically,theapplicationresponsibleformanagingthedatatobesharedwillimplementa
contentprovidertofacilitatethesharingofthatdatawithotherapplications.
Thecreationofacontentproviderinvolvestheimplementationofasetofmethodsto
managethedataonbehalfofother,clientapplications.Thesemethodsareasfollows:
49.2.1onCreate()
Thismethodiscalledwhenthecontentproviderisfirstcreatedandshouldbeusedto
performanyinitializationtasksrequiredbythecontentprovider.
49.2.2query()
Thismethodwillbecalledwhenaclientrequeststhatdataberetrievedfromthecontent
provider.Itistheresponsibilityofthismethodtoidentifythedatatoberetrieved(either
singleormultiplerows),performthedataextractionandreturntheresultswrappedina
Cursorobject.
49.2.3insert()
Thismethodiscalledwhenanewrowneedstobeinsertedintotheproviderdatabase.
Thismethodmustidentifythedestinationforthedata,performtheinsertionandreturnthe
fullURIofthenewlyaddedrow.
49.2.4update()
Themethodcalledwhenexistingrowsneedtobeupdatedonbehalfoftheclient.The
methodusestheargumentspassedthroughtoupdatetheappropriatetablerowsandreturn
thenumberofrowsupdatedasaresultoftheoperation.
49.2.5delete()
Calledwhenrowsaretobedeletedfromatable.Thismethoddeletesthedesignatedrows
andreturnsacountofthenumberofrowsdeleted.
49.2.6getType()
ReturnstheMIMEtypeofthedatastoredbythecontentprovider.
Itisimportantwhenimplementingthesemethodsinacontentprovidertokeepinmind
that,withtheexceptionoftheonCreate()method,theycanbecalledfrommanyprocesses
simultaneouslyandmust,therefore,bethreadsafe.
Onceacontentproviderhasbeenimplemented,theissuethatthenarisesishowthe
providerisidentifiedwithintheAndroidsystem.ThisiswherethecontentURIcomesinto
play.
49.3TheContentURI
AnAndroiddevicewillpotentiallycontainanumberofcontentproviders.Thesystem
must,therefore,providesomewayofidentifyingoneproviderfromanother.Similarly,a
singlecontentprovidermayprovideaccesstomultipleformsofcontent(typicallyinthe
formofdatabasetables).Clientapplications,therefore,needawaytospecifythe
underlyingdataforwhichaccessisrequired.Thisisachievedthroughtheuseofcontent
URIs.
ThecontentURIisessentiallyusedtoidentifyspecificdatawithinaspecificcontent
provider.TheAuthoritysectionoftheURIidentifiesthecontentproviderandusually
takestheformofthepackagenameofthecontentprovider.Forexample:
com.example.mydbapp.myprovider
Aspecificdatabasetablewithintheproviderdatastructuremaybereferencedby
appendingthetablenametotheauthority.Forexample,thefollowingURIreferencesa
tablenamedproductswithinthecontentprovider:
com.example.mydbapp.myprovider/products
Similarly,aspecificrowwithinthespecifiedtablemaybereferencedbyappendingthe
rowIDtotheURI.ThefollowingURI,forexample,referencestherowintheproducts
tableinwhichthevaluestoredinthe_IDcolumnequals3:
com.example.mydbapp.myprovider/products/3
Whenimplementingtheinsert,query,updateanddeletemethodsinthecontentprovider,it
willbetheresponsibilityofthesemethodstoidentifywhethertheincomingURIis
targetingaspecificrowinatable,orreferencesmultiplerowsandactaccordingly.This
canpotentiallybeacomplextaskgiventhataURIcanextendtomultiplelevels.This
processcan,however,beeasedsignificantlybymakinguseoftheUriMatcherclassas
willbeoutlinedinthenextchapter.
49.4TheContentResolver
AccesstoacontentproviderisachievedviaaContentResolverobject.Anapplicationcan
obtainareferencetoitscontentresolverbymakingacalltothegetContentResolver()
methodoftheapplicationcontext.
Thecontentresolverobjectcontainsasetofmethodsthatmirrorthoseofthecontent
provider(insert,query,deleteetc).Theapplicationsimplymakescallstothemethods,
specifyingtheURIofthecontentonwhichtheoperationistobeperformed.Thecontent
resolverandcontentproviderobjectsthencommunicatetoperformtherequestedtaskon
behalfoftheapplication.
49.5The<provider>ManifestElement
InorderforacontentprovidertobevisiblewithinanAndroidsystem,itmustbedeclared
withintheAndroidmanifestfilefortheapplicationinwhichitresides.Thisisachieved
usingthe<provider>element,whichmustcontainthefollowingitems:
· android:authority–ThefullauthorityURIofthecontentprovider.Forexample
com.example.mydbapp.mydbapp.myprovider.
· android:name–Thenameoftheclassthatimplementsthecontentprovider.Inmost
cases,thiswillusethesamevalueastheauthority.
Similarly,the<provider>elementmaybeusedtodefinethepermissionsthatmustbeheld
byclientapplicationsinordertoqualifyforaccesstotheunderlyingdata.Ifno
permissionsaredeclared,thedefaultbehaviorisforpermissiontobeallowedforall
applications.
Permissionscanbesettocovertheentirecontentprovider,orlimitedtospecifictables
andrecords.
49.6Summary
Thedatabelongingtoanapplicationistypicallyprivatetotheapplicationandinaccessible
tootherapplications.Insituationswherethedataneedstobeshared,itisnecessarytoset
upacontentprovider.Thischapterhascoveredthebasicelementsthatcombinetoenable
datasharingbetweenapplicationsandoutlinedtheconceptsofthecontentprovider,
contentURIandcontentresolver.
Inthenextchapter,theAndroidStudioDatabaseexampleapplicationcreatedpreviously
willbeextendedtomaketheunderlyingproductdataavailableviaacontentprovider.
50.ImplementinganAndroidContent
ProviderinAndroidStudio
Asoutlinedinthepreviouschapter,contentprovidersprovideamechanismthroughwhich
thedatastoredbyoneAndroidapplicationcanbemadeaccessibletootherapplications.
Havingprovidedatheoreticaloverviewofcontentproviders,thischapterwillcontinuethe
coverageofcontentprovidersbyextendingtheDatabaseprojectcreatedinthechapter
entitledAnAndroidSQLiteDatabaseTutorialtoimplementcontentproviderbasedaccess
tothedatabase.
50.1CopyingtheDatabaseProject
InordertokeeptheoriginalDatabaseprojectintact,wewillmakeabackupcopyofthe
projectbeforemodifyingittoimplementcontentprovidersupportfortheapplication.If
theDatabaseprojectiscurrentlyopenwithinAndroidStudio,closeitusingtheFile->
CloseProjectmenuoption.
Usingthefilesystemexplorerforyouroperatingsystemtype,navigatetothedirectory
containingyourAndroidStudioprojects(typicallythiswillbeafoldernamed
AndroidStudioProjectslocatedinyourhomedirectory).Withinthisfolder,copythe
DatabaseprojectfoldertoanewfoldernamedDatabaseOriginal.
WithintheAndroidStudiowelcomescreen,selecttheOpenanexistingAndroidStudio
projectoptionfromtheQuickStartlistandnavigatetoandselecttheoriginalDatabase
projectsothatitloadsintothemainwindow.
50.2AddingtheContentProviderPackage
ThenextstepistoaddanewpackagetotheDatabaseprojectintowhichthecontent
providerclasswillbecreated.AddthisnewpackagebynavigatingwithintheProjecttool
windowtoapp->java,right-clickingonthejavaentryandselectingtheNew->Package
menuoption.WhentheChooseDestinationDirectorydialogappears,selectthe
..\app\src\main\javaoptionfromtheDirectoryStructurepanelandclickonOK.
IntheNewPackagedialog,enterthefollowingpackagenameintothenamefieldbefore
clickingontheOKbutton:
com.ebookfrenzy.database.provider
ThenewpackageshouldnowbelistedwithintheProjecttoolwindowasillustratedin
Figure50-1:
Figure50-1
50.3CreatingtheContentProviderClass
AsdiscussedinUnderstandingAndroidContentProviders,contentprovidersarecreated
bysubclassingtheandroid.content.ContentProviderclass.Consequently,thenextstepis
toaddaclasstothenewproviderpackagetoserveasthecontentproviderforthis
application.LocatethenewpackageintheProjecttoolwindow,right-clickonitandselect
theNew->Other->ContentProvidermenuoption.IntheNewContentProviderdialog,
enterMyContentProviderintotheClassNamefieldandthefollowingintotheURI
Authoritiesfield:
com.ebookfrenzy.database.provider.MyContentProvider
Makesurethatthenewcontentproviderclassisbothexportedandenabledbefore
clickingonFinishtocreatethenewclass.
Oncethenewclasshasbeencreated,theMyContentProvider.javafileshouldbelisted
beneaththeproviderpackageintheProjecttoolwindowandautomaticallyloadedintothe
editorwhereitshouldappearasoutlinedinthefollowinglisting:
packagecom.ebookfrenzy.database.provider;
importandroid.content.ContentProvider;
importandroid.content.ContentValues;
importandroid.database.Cursor;
importandroid.net.Uri;
publicclassMyContentProviderextendsContentProvider{
publicMyContentProvider(){
}
@Override
publicintdelete(Uriuri,Stringselection,String[]
selectionArgs){
//Implementthistohandlerequeststodeleteoneormore
rows.
thrownewUnsupportedOperationException(“Notyetimplemented”);
}
@Override
publicStringgetType(Uriuri){
//TODO:ImplementthistohandlerequestsfortheMIMEtypeof
thedata
//atthegivenURI.
thrownewUnsupportedOperationException(“Notyetimplemented”);
}
@Override
publicUriinsert(Uriuri,ContentValuesvalues){
//TODO:Implementthistohandlerequeststoinsertanewrow.
thrownewUnsupportedOperationException(“Notyetimplemented”);
}
@Override
publicbooleanonCreate(){
//TODO:Implementthistoinitializeyourcontentprovideron
startup.
returnfalse;
}
@Override
publicCursorquery(Uriuri,String[]projection,Stringselection,
String[]selectionArgs,StringsortOrder){
//TODO:Implementthistohandlequeryrequestsfromclients.
thrownewUnsupportedOperationException(“Notyetimplemented”);
}
@Override
publicintupdate(Uriuri,ContentValuesvalues,Stringselection,
String[]selectionArgs){
//TODO:Implementthistohandlerequeststoupdateoneor
morerows.
thrownewUnsupportedOperationException(“Notyetimplemented”);
}
}
Asisevidentfromaquickreviewofthecodeinthisfile,AndroidStudiohasalready
populatedtheclasswithstubsforeachofthemethodsthatasubclassofContentProvider
isrequiredtoimplement.Itwillsoonbenecessarytobeginimplementingthesemethods,
butfirstsomeconstantsrelatingtotheprovider’scontentauthorityandURIneedtobe
declared.
50.4ConstructingtheAuthorityandContentURI
Asoutlinedinthepreviouschapter,allcontentprovidersmusthaveassociatedwiththem
anauthorityandacontenturi.Inpractice,theauthorityistypicallythefullpackagename
ofthecontentproviderclassitself,inthiscase
com.ebookfrenzy.database.database.provider.MyContentProviderasdeclaredwhenthe
newContentProviderclasswascreatedintheprevioussection.
ThecontentURIwillvarydependingonapplicationrequirements,butforthepurposesof
thisexamplewillcomprisetheauthoritywiththenameofthedatabasetableappendedat
theend.WithintheMyContentProvider.javafile,makethefollowingmodifications:
packagecom.ebookfrenzy.database.provider;
importandroid.content.ContentProvider;
importandroid.content.ContentValues;
importandroid.database.Cursor;
importandroid.net.Uri;
importandroid.content.UriMatcher;
publicclassMyContentProviderextendsContentProvider{
privatestaticfinalStringAUTHORITY=
“com.ebookfrenzy.database.provider.MyContentProvider”;
privatestaticfinalStringPRODUCTS_TABLE=“products”;
publicstaticfinalUriCONTENT_URI=
Uri.parse(“content://”+AUTHORITY+“/”+PRODUCTS_TABLE);
publicMyContentProvider(){
}
.
.
.
}
TheabovestatementsbeginbycreatinganewStringobjectnamedAUTHORITYand
assigningtheauthoritystringtoit.Similarly,asecondStringobjectnamed
PRODUCTS_TABLEiscreatedandinitializedwiththenameofourdatabasetable
(products).
Finally,thesetwostringelementsarecombined,prefixedwithcontent://andconvertedto
aUriobjectusingtheparse()methodoftheUriclass.Theresultisassignedtoavariable
namedCONTENT_URI.
50.5ImplementingURIMatchingintheContentProvider
Whenthemethodsofthecontentproviderarecalled,theywillbepassedasanargumenta
URIindicatingthedataonwhichtheoperationistobeperformed.ThisURImaytakethe
formofareferencetoaspecificrowinaspecifictable.ItisalsopossiblethattheURIwill
bemoregeneral,forexamplespecifyingonlythedatabasetable.Itistheresponsibilityof
eachmethodtoidentifytheUritypeandtoactaccordingly.Thistaskcanbeeased
considerablybymakinguseofaUriMatcherinstance.OnceaUriMatcherinstancehas
beencreated,itcanbeconfiguredtoreturnaspecificintegervaluecorrespondingtothe
typeofURIitdetectswhenaskedtodoso.Forthepurposesofthistutorial,wewillbe
configuringourUriMatcherinstancetoreturnavalueof1whentheURIreferencesthe
entireproductstable,andavalueof2whentheURIreferencestheIDofaspecificrowin
theproductstable.BeforeworkingoncreatingtheURIMatcherinstance,wewillfirst
createtwointegervariablestorepresentthetwoURItypes:
packagecom.ebookfrenzy.database.provider;
importandroid.content.ContentProvider;
importandroid.content.ContentValues;
importandroid.database.Cursor;
importandroid.net.Uri;
importandroid.content.UriMatcher;
publicclassMyContentProviderextendsContentProvider{
privatestaticfinalStringAUTHORITY=
“com.ebookfrenzy.database.provider.MyContentProvider”;
privatestaticfinalStringPRODUCTS_TABLE=“products”;
publicstaticfinalUriCONTENT_URI=
Uri.parse(“content://”+AUTHORITY+“/”+PRODUCTS_TABLE);
publicstaticfinalintPRODUCTS=1;
publicstaticfinalintPRODUCTS_ID=2;
.
.
}
WiththeUritypevariablesdeclared,itisnowtimetoaddcodetocreateaUriMatcher
instanceandconfigureittoreturntheappropriatevariables:
publicclassMyContentProviderextendsContentProvider{
privatestaticfinalStringAUTHORITY=
“com.ebookfrenzy.database.provider.MyContentProvider”;
privatestaticfinalStringPRODUCTS_TABLE=“products”;
publicstaticfinalUriCONTENT_URI=
Uri.parse(“content://”+AUTHORITY+“/”+PRODUCTS_TABLE);
publicstaticfinalintPRODUCTS=1;
publicstaticfinalintPRODUCTS_ID=2;
privatestaticfinalUriMatchersURIMatcher=
newUriMatcher(UriMatcher.NO_MATCH);
static{
sURIMatcher.addURI(AUTHORITY,PRODUCTS_TABLE,PRODUCTS);
sURIMatcher.addURI(AUTHORITY,PRODUCTS_TABLE+“/#”,
PRODUCTS_ID);
}
.
.
}
TheUriMatcherinstance(namedsURIMatcher)isnowprimedtoreturnthevalueof
PRODUCTSwhenjusttheproductstableisreferencedinaURI,andPRODUCTS_ID
whentheURIincludestheIDofaspecificrowinthetable.
50.6ImplementingtheContentProvideronCreate()Method
Whenthecontentproviderclassiscreatedandinitialized,acallwillbemadetothe
onCreate()methodoftheclass.Itiswithinthismethodthatanyinitializationtasksforthe
classneedtobeperformed.Forthepurposesofthisexample,allthatneedstobe
performedisforaninstanceoftheMyDBHandlerclassimplementedinAnAndroid
SQLiteDatabaseTutorialtobecreated.Oncethisinstancehasbeencreated,itwillneed
tobeaccessiblefromtheothermethodsintheclass,soadeclarationforthedatabase
handleralsoneedstobedeclared,resultinginthefollowingcodechangestothe
MyContentProvider.javafile:
packagecom.ebookfrenzy.database.provider;
importcom.ebookfrenzy.database.MyDBHandler;
importandroid.content.ContentProvider;
importandroid.content.ContentValues;
importandroid.database.Cursor;
importandroid.net.Uri;
importandroid.content.UriMatcher;
importandroid.database.sqlite.SQLiteDatabase;
importandroid.database.sqlite.SQLiteQueryBuilder;
importandroid.text.TextUtils;
publicclassMyContentProviderextendsContentProvider{
privateMyDBHandlermyDB;
.
.
.
@Override
publicbooleanonCreate(){
myDB=newMyDBHandler(getContext(),null,null,1);
returnfalse;
}
}
50.7ImplementingtheContentProviderinsert()Method
Whenaclientapplicationoractivityrequeststhatdatabeinsertedintotheunderlying
database,theinsert()methodofthecontentproviderclasswillbecalled.Atthispoint,
however,allthatexistsintheMyContentProvider.javafileoftheprojectisastubmethod,
whichreadsasfollows:
@Override
publicUriinsert(Uriuri,ContentValuesvalues){
//TODO:Implementthistohandlerequeststoinsertanewrow.
thrownewUnsupportedOperationException(“Notyetimplemented”);
}
PassedasargumentstothemethodareaURIspecifyingthedestinationoftheinsertion
andaContentValuesobjectcontainingthedatatobeinserted.
Thismethodnowneedstobemodifiedtoperformthefollowingtasks:
· UsethesUriMatchertoidentifytheURItype.
· ThrowanexceptioniftheURIisnotvalid.
· ObtainareferencetoawritableinstanceoftheunderlyingSQLitedatabase.
· PerformaSQLinsertoperationtoinsertthedataintothedatabasetable.
· Notifythecorrespondingcontentresolverthatthedatabasehasbeenmodified.
· ReturntheURIofthenewlyaddedtablerow.
Bringingtheserequirementstogetherresultsinamodifiedinsert()method,whichreadsas
follows:
@Override
publicUriinsert(Uriuri,ContentValuesvalues){
inturiType=sURIMatcher.match(uri);
SQLiteDatabasesqlDB=myDB.getWritableDatabase();
longid=0;
switch(uriType){
casePRODUCTS:
id=sqlDB.insert(MyDBHandler.TABLE_PRODUCTS,
null,values);
break;
default:
thrownewIllegalArgumentException(“UnknownURI:“
+uri);
}
getContext().getContentResolver().notifyChange(uri,null);
returnUri.parse(PRODUCTS_TABLE+“/”+id);
}
50.8ImplementingtheContentProviderquery()Method
Whenacontentprovideriscalledupontoreturndata,thequery()methodoftheprovider
classwillbecalled.Whencalled,thismethodispassedsomeorallofthefollowing
arguments:
· URI–TheURIspecifyingthedatasourceonwhichthequeryistobeperformed.This
cantaketheformofageneralquerywithmultipleresults,oraspecificquerytargeting
theIDofasingletablerow.
· Projection–Arowwithinadatabasetablecancomprisemultiplecolumnsofdata.In
thecaseofthisapplication,forexample,thesecorrespondtotheID,productnameand
productquantity.TheprojectionargumentissimplyaStringarraycontainingthename
foreachofthecolumnsthatistobereturnedintheresultdataset.
· Selection–The“where”elementoftheselectiontobeperformedaspartofthequery.
Thisargumentcontrolswhichrowsareselectedfromthespecifieddatabase.For
example,ifthequerywasrequiredtoselectonlyproductsnamed“CatFood”thenthe
selectionstringpassedtothequery()methodwouldreadproductname=“CatFood”.
· SelectionArgs–AnyadditionalargumentsthatneedtobepassedtotheSQLquery
operationtoperformtheselection.
· SortOrder–Thesortorderfortheselectedrows.
Whencalled,thequery()methodisrequiredtoperformthefollowingoperations:
· UsethesUriMatchertoidentifytheUritype.
· ThrowanexceptioniftheURIisnotvalid.
· ConstructaSQLquerybasedonthecriteriapassedtothemethod.Forconvenience,
theSQLiteQueryBuilderclasscanbeusedinconstructionofthequery.
· Executethequeryoperationonthedatabase.
· Notifythecontentresolveroftheoperation.
· ReturnaCursorobjectcontainingtheresultsofthequery.
Withtheserequirementsinmind,thecodeforthequery()methodinthe
MyContentProvider.javafileshouldnowreadasoutlinedinthefollowinglisting:
@Override
publicCursorquery(Uriuri,String[]projection,Stringselection,
String[]selectionArgs,StringsortOrder){
SQLiteQueryBuilderqueryBuilder=newSQLiteQueryBuilder();
queryBuilder.setTables(MyDBHandler.TABLE_PRODUCTS);
inturiType=sURIMatcher.match(uri);
switch(uriType){
casePRODUCTS_ID:
queryBuilder.appendWhere(MyDBHandler.COLUMN_ID+“=”
+uri.getLastPathSegment());
break;
casePRODUCTS:
break;
default:
thrownewIllegalArgumentException(“UnknownURI”);
}
Cursorcursor=queryBuilder.query(myDB.getReadableDatabase(),
projection,selection,selectionArgs,null,null,
sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(),
uri);
returncursor;
}
50.9ImplementingtheContentProviderupdate()Method
Theupdate()methodofthecontentprovideriscalledwhenchangesarebeingrequestedto
existingdatabasetablerows.ThemethodispassedaURI,thenewvaluesintheformofa
ContentValuesobjectandtheusualselectionargumentstrings.
Whencalled,theupdate()methodwouldtypicallyperformthefollowingsteps:
· UsethesUriMatchertoidentifytheURItype.
· ThrowanexceptioniftheURIisnotvalid.
· ObtainareferencetoawritableinstanceoftheunderlyingSQLitedatabase.
· Performtheappropriateupdateoperationonthedatabasedependingontheselection
criteriaandtheURItype.
· Notifythecontentresolverofthedatabasechange.
· Returnacountofthenumberofrowsthatwerechangedasaresultoftheupdate
operation.
Ageneral-purposeupdate()method,andtheonewewilluseforthisproject,wouldreadas
follows:
@Override
publicintupdate(Uriuri,ContentValuesvalues,Stringselection,
String[]selectionArgs){
inturiType=sURIMatcher.match(uri);
SQLiteDatabasesqlDB=myDB.getWritableDatabase();
introwsUpdated=0;
switch(uriType){
casePRODUCTS:
rowsUpdated=
sqlDB.update(MyDBHandler.TABLE_PRODUCTS,
values,
selection,
selectionArgs);
break;
casePRODUCTS_ID:
Stringid=uri.getLastPathSegment();
if(TextUtils.isEmpty(selection)){
rowsUpdated=
sqlDB.update(MyDBHandler.TABLE_PRODUCTS,
values,
MyDBHandler.COLUMN_ID+“=”+id,
null);
}else{
rowsUpdated=
sqlDB.update(MyDBHandler.TABLE_PRODUCTS,
values,
MyDBHandler.COLUMN_ID+“=”+id
+”and“
+selection,
selectionArgs);
}
break;
default:
thrownewIllegalArgumentException(“UnknownURI:“
+uri);
}
getContext().getContentResolver().notifyChange(uri,
null);
returnrowsUpdated;
}
50.10ImplementingtheContentProviderdelete()Method
Incommonwithanumberofothercontentprovidermethods,thedelete()methodis
passedaURI,aselectionstringandanoptionalsetofselectionarguments.Atypical
delete()methodwillalsoperformthefollowing,andbynowlargelyfamiliar,taskswhen
called:
· UsethesUriMatchertoidentifytheURItype.
· ThrowanexceptioniftheURIisnotvalid.
· ObtainareferencetoawritableinstanceoftheunderlyingSQLitedatabase.
· Performtheappropriatedeleteoperationonthedatabasedependingontheselection
criteriaandtheUritype.
· Notifythecontentresolverofthedatabasechange.
· Returnthenumberofrowsdeletedasaresultoftheoperation.
Atypicaldelete()methodis,inmanyways,verysimilartotheupdate()methodandmay
beimplementedasfollows:
@Override
publicintdelete(Uriuri,Stringselection,String[]selectionArgs)
{
inturiType=sURIMatcher.match(uri);
SQLiteDatabasesqlDB=myDB.getWritableDatabase();
introwsDeleted=0;
switch(uriType){
casePRODUCTS:
rowsDeleted=sqlDB.delete(MyDBHandler.TABLE_PRODUCTS,
selection,
selectionArgs);
break;
casePRODUCTS_ID:
Stringid=uri.getLastPathSegment();
if(TextUtils.isEmpty(selection)){
rowsDeleted=sqlDB.delete(MyDBHandler.TABLE_PRODUCTS,
MyDBHandler.COLUMN_ID+“=”+id,
null);
}else{
rowsDeleted=sqlDB.delete(MyDBHandler.TABLE_PRODUCTS,
MyDBHandler.COLUMN_ID+“=”+id
+”and”+selection,
selectionArgs);
}
break;
default:
thrownewIllegalArgumentException(“UnknownURI:”+uri);
}
getContext().getContentResolver().notifyChange(uri,null);
returnrowsDeleted;
}
Withthesemethodsimplemented,thecontentproviderclass,intermsoftherequirements
forthisexampleatleast,iscomplete.Thenextstepistomakesurethatthecontent
providerisdeclaredintheprojectmanifestfilesothatitisvisibletoanycontentresolvers
seekingaccesstoit.
50.11DeclaringtheContentProviderintheManifestFile
Unlessacontentproviderisdeclaredinthemanifestfileoftheapplicationtowhichit
belongs,itwillnotbepossibleforacontentresolvertolocateandaccessit.Asoutlined,
contentprovidersaredeclaredusingthe<provider>tagandthemanifestentrymust
correctlyreferencethecontentproviderauthorityandcontentURI.
Forthepurposesofthisproject,therefore,locatetheAndroidManifest.xmlfileforthe
DatabaseProviderprojectwithintheProjecttoolwindowanddoubleclickonittoloadit
intotheeditingpanel.Withintheeditingpanel,makesurethatthecontentprovider
declarationhasalreadybeenaddedbyAndroidStudiowhentheMyContentProviderclass
wasaddedtotheproject:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.database”>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.DatabaseActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<providerandroid:name=”.provider.MyContentProvider”
android:authorities=
“com.ebookfrenzy.database.provider.MyContentProvider”
android:enabled=“true”
android:exported=“true”>
</provider>
</application>
</manifest>
Allthatremainsbeforetestingtheapplicationistomodifythedatabasehandlerclassto
usethecontentproviderinsteadofdirectlyaccessingthedatabase.
50.12ModifyingtheDatabaseHandler
Whenthisapplicationwasoriginallycreated,itwasdesignedtouseadatabasehandlerto
accesstheunderlyingdatabasedirectly.Nowthatacontentproviderhasbeen
implemented,thedatabasehandlerneedstobemodifiedsothatalldatabaseoperationsare
performedusingthecontentproviderviaacontentresolver.
ThefirststepistomodifytheMyDBHandler.javaclasssothatitobtainsareferencetoa
ContentResolverinstance.Thiscanbeachievedintheconstructormethodoftheclass:
packagecom.ebookfrenzy.database;
importcom.ebookfrenzy.database.provider.MyContentProvider;
importandroid.database.sqlite.SQLiteDatabase;
importandroid.database.sqlite.SQLiteOpenHelper;
importandroid.content.Context;
importandroid.content.ContentValues;
importandroid.database.Cursor;
importandroid.content.ContentResolver;
publicclassMyDBHandlerextendsSQLiteOpenHelper{
privateContentResolvermyCR;
privatestaticfinalintDATABASE_VERSION=1;
privatestaticfinalStringDATABASE_NAME=“productDB.db”;
publicstaticfinalStringTABLE_PRODUCTS=“products”;
publicstaticfinalStringCOLUMN_ID=“_id”;
publicstaticfinalStringCOLUMN_PRODUCTNAME=“productname”;
publicstaticfinalStringCOLUMN_QUANTITY=“quantity”;
publicMyDBHandler(Contextcontext,Stringname,
SQLiteDatabase.CursorFactoryfactory,intversion){
super(context,DATABASE_NAME,factory,DATABASE_VERSION);
myCR=context.getContentResolver();
}
.
.
.
}
Next,theaddProduct(),findProduct()andremoveProduct()methodsneedtobere-written
tousethecontentresolverandcontentproviderfordatamanagementpurposes:
publicvoidaddProduct(Productproduct){
ContentValuesvalues=newContentValues();
values.put(COLUMN_PRODUCTNAME,product.getProductName());
values.put(COLUMN_QUANTITY,product.getQuantity());
myCR.insert(MyContentProvider.CONTENT_URI,values);
}
publicProductfindProduct(Stringproductname){
String[]projection={COLUMN_ID,
COLUMN_PRODUCTNAME,COLUMN_QUANTITY};
Stringselection=“productname="”+productname+“"”;
Cursorcursor=myCR.query(MyContentProvider.CONTENT_URI,
projection,selection,null,
null);
Productproduct=newProduct();
if(cursor.moveToFirst()){
cursor.moveToFirst();
product.setID(Integer.parseInt(cursor.getString(0)));
product.setProductName(cursor.getString(1));
product.setQuantity(Integer.parseInt(cursor.getString(2)));
cursor.close();
}else{
product=null;
}
returnproduct;
}
publicbooleandeleteProduct(Stringproductname){
booleanresult=false;
Stringselection=“productname="”+productname+“"”;
introwsDeleted=myCR.delete(MyContentProvider.CONTENT_URI,
selection,null);
if(rowsDeleted>0)
result=true;
returnresult;
}
Withthedatabasehandlerclassupdatedtouseacontentresolverandcontentprovider,the
applicationisnowreadytobetested.Compileandruntheapplicationandperformsome
operationstoadd,findandremoveproductentries.Intermsofoperationandfunctionality,
theapplicationshouldbehaveexactlyasitdidwhendirectlyaccessingthedatabase,
exceptthatitisnowusingthecontentprovider.
Withthecontentprovidernowimplementedanddeclaredinthemanifestfile,anyother
applicationscanpotentiallyaccessthatdata(sincenopermissionsweredeclaredthe
defaultfullaccessisineffect).Theonlyinformationthattheotherapplicationsneedto
knowtogainaccessisthecontentURIandthenamesofthecolumnsintheproducts
table.
50.13Summary
Thegoalofthischapterhasbeentoprovideamoredetailedoverviewoftheexactsteps
involvedinimplementinganAndroidcontentproviderwithaparticularemphasisonthe
structureandimplementationofthequery,insert,deleteandupdatemethodsofthecontent
providerclass.Practicaluseofthecontentresolverclasstoaccessdatainthecontent
providerwasalsocovered,andtheDatabaseprojectcreatedintheSQLitetutorial
modifiedtomakeuseofbothacontentproviderandcontentresolver.
51.AccessingCloudStorageusingthe
AndroidStorageAccessFramework
Recentyearshaveseenthewideadoptionofremotestorageservices(otherwiseknownas
“cloudstorage”)tostoreuserfilesanddata.Drivingthisgrowtharetwokeyfactors.One
isthatmostmobiledevicesnowprovidecontinuous,highspeedinternetconnectivity,
therebymakingthetransferofdatafastandaffordable.Thesecondfactoristhat,relative
totraditionalcomputersystems(suchasdesktopsandlaptops),thesemobiledevicesare
constrainedintermsofinternalstorageresources.AhighspecificationAndroidtablet
today,forexample,typicallycomeswith64Gbofstoragecapacity.Whencomparedwitha
mid-rangelaptopsystemwitha750Gbdiskdrive,theneedfortheseamlessremote
storageoffilesisakeyrequirementformanymobileapplicationstoday.
Inrecognitionofthisfact,GoogleintroducedtheStorageAccessFrameworkaspartofthe
Android4.4SDK.Thischapterwillprovideahighleveloverviewofthestorageaccess
frameworkinpreparationforthemoredetailorientedtutorialcontainedinthenext
chapter,entitledAnAndroidStorageAccessFrameworkExample.
51.1TheStorageAccessFramework
Fromtheperspectiveoftheuser,theStorageAccessFrameworkprovidesaneasytouse
userinterfacethatallowstheusertobrowse,select,deleteandcreatefileshostedby
storageservices(alsoreferredtoasdocumentproviders)fromwithinAndroid
applications.Usingthisbrowsinginterface(alsoreferredtoasthepicker),userscan,for
example,browsethroughthefiles(suchasdocuments,audio,imagesandvideos)hosted
bytheirchosendocumentproviders.Figure51-1,forexample,showsthepickeruser
interfaceinactiondisplayingacollectionoffileshostedbyadocumentproviderservice:
Figure51-1
Intermsofdocumentproviders,thesecanrangefromcloud-basedservicestolocal
documentprovidersrunningonthesamedeviceastheclientapplication.Atthetimeof
writing,themostprominentdocumentproviderscompatiblewiththeStorageAccess
FrameworkareBoxand,unsurprisingly,GoogleDrive.Itishighlylikelythatothercloud
storageprovidersandapplicationdeveloperswillsoonalsoprovideservicesthatconform
totheAndroidStorageAccessFramework.Figure51-2,forexample,illustratessome
documentprovideroptionslistedbythepickerinterface:
Figure51-2
Asshownintheabovefigure,inadditiontocloudbaseddocumentprovidersthepicker
alsoprovidesaccesstointernalstorageonthedeviceprovidingarangeoffilestorage
optionstotheapplicationuser.
ThroughasetofIntentsincludedwithAndroid4.4,Androidapplicationdeveloperscan
incorporatethesestoragecapabilitiesintoapplicationswithjustafewlinesofcode.A
particularlycompellingaspectoftheStorageAccessFrameworkfromthepointofviewof
thedeveloperisthattheunderlyingdocumentproviderselectedbytheuseriscompletely
transparenttotheapplication.Oncethestoragefunctionalityhasbeenimplementedusing
theframeworkwithinanapplication,itwillworkwithalldocumentproviderswithoutany
codemodifications.
51.2WorkingwiththeStorageAccessFramework
Android4.4introducedanewsetofIntentsdesignedtointegratethefeaturesofthe
StorageAccessFrameworkintoAndroidapplications.TheseintentsdisplaytheStorage
AccessFrameworkpickeruserinterfacetotheuserandreturntheresultsoftheinteraction
totheapplicationviaacalltotheonActivityResult()methodoftheactivitythatlaunched
theintent.WhentheonActivityResult()methodiscalled,itispassedtheUrioftheselected
filetogetherwithavalueindicatingthesuccessorotherwiseoftheoperation.
TheStorageAccessFrameworkintentscanbesummarizedasfollows:
· ACTION_OPEN_DOCUMENT–Providestheuserwithaccesstothepickeruser
interfacesothatfilesmaybeselectedfromthedocumentprovidersconfiguredonthe
device.SelectedfilesarepassedbacktotheapplicationintheformofUriobjects.
· ACTION_CREATE_DOCUMENT–Allowstheusertoselectadocumentprovider,
alocationonthatprovider’sstorageandafilenameforanewfile.Onceselected,the
fileiscreatedbytheStorageAccessFrameworkandtheUriofthatfilereturnedtothe
applicationforfurtherprocessing.
51.3FilteringPickerFileListings
Thefileslistedwithinthepickeruserinterfacewhenanintentisstartedmaybefiltered
usingavarietyofoptions.Consider,forexample,thefollowingcodetostartan
ACTION_OPEN_DOCUMENTintent:
privatestaticfinalintOPEN_REQUEST_CODE=41;
Intentintent=newIntent(Intent.ACTION_OPEN_DOCUMENT);
startActivityForResult(intent,OPEN_REQUEST_CODE);
Whenexecuted,theabovecodewillcausethepickeruserinterfacetobedisplayed,
allowingtheusertobrowseandselectanyfileshostedbyavailabledocumentproviders.
Onceafilehasbeenselectedbytheuser,areferencetothatfilewillbeprovidedtothe
applicationintheformofaUriobject.Theapplicationcanthenopenthefileusingthe
openFileDescriptor(Uri,String)method.Thereissomerisk,however,thatnotallfiles
listedbyadocumentprovidercanbeopenedinthisway.Theexclusionofsuchfiles
withinthepickercanbeachievedbymodifyingtheintentusingthe
CATEGORY_OPENABLEoption.Forexample:
privatestaticfinalintOPEN_REQUEST_CODE=41;
Intentintent=newIntent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(intent,OPEN_REQUEST_CODE);
Whenthepickerisnowdisplayed,fileswhichcannotbeopenedusingthe
openFileDescriptor()methodwillbelistedbutnotselectablebytheuser.
Anotherusefulapproachtofilteringallowsthefilesavailableforselectiontoberestricted
byfiletype.Thisinvolvesspecifyingthetypesofthefilestheapplicationisableto
handle.Animageeditingapplicationmight,forexample,onlywanttoprovidetheuser
withtheoptionofselectingimagefilesfromthedocumentproviders.Thisisachievedby
configuringtheintentobjectwiththeMIMEtypesofthefilesthataretobeselectableby
theuser.Thefollowingcode,forexample,specifiesthatonlyimagefilesaresuitablefor
selectioninthepicker:
Intentintent=newIntent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(“image/*”);
startActivityForResult(intent,OPEN_REQUEST_CODE);
ThiscouldbefurtherrefinedtolimitselectiontoJPEGimages:
intent.setType(“image/jpeg”);
Alternatively,anaudioplayerappmightonlybeabletohandleaudiofiles:
intent.setType(“audio/*”);
TheaudioappmightbelimitedevenfurtherinonlysupportingtheplaybackofMP4
basedaudiofiles:
intent.setType(“audio/mp4”);
AwiderangeofMIMEtypesettingsareavailableforusewhenworkingwiththeStorage
AccessFramework,themorecommonofwhichcanbefoundlistedonlineat:
http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types
51.4HandlingIntentResults
Whenanintentreturnscontroltotheapplication,itdoessobycallingthe
onActivityResult()methodoftheactivitywhichstartedtheintent.Thismethodispassed
therequestcodethatwashandedtotheintentatlaunchtime,aresultcodeindicating
whetherornottheintentwassuccessfulandaresultdataobjectcontainingtheUriofthe
selectedfile.Thefollowingcode,forexample,mightbeusedasthebasisforhandlingthe
resultsfromtheACTION_OPEN_DOCUMENTintentoutlinedintheprevioussection:
publicvoidonActivityResult(intrequestCode,intresultCode,
IntentresultData){
UricurrentUri=null;
if(resultCode==Activity.RESULT_OK)
{
if(requestCode==OPEN_REQUEST_CODE)
{
if(resultData!=null){
currentUri=resultData.getData();
readFileContent(currentUri);
}
}
}
Theabovemethodverifiesthattheintentwassuccessful,checksthattherequestcode
matchesthatforafileopenrequestandthenextractstheUrifromtheintentdata.TheUri
canthenbeusedtoreadthecontentofthefile.
51.5ReadingtheContentofaFile
Theexactstepsrequiredtoreadthecontentofafilehostedbyadocumentproviderwill
dependtoalargeextentonthetypeofthefile.Thestepstoreadlinesfromatextfile,for
example,differfromthoseforimageoraudiofiles.
AnimagefilecanbeassignedtoaBitmapobjectbyextractingthefiledescriptorfromthe
UriobjectandthendecodingtheimageintoaBitmapFactoryinstance.Forexample:
ParcelFileDescriptorpFileDescriptor=
getContentResolver().openFileDescriptor(uri,“r”);
FileDescriptorfileDescriptor=
pFileDescriptor.getFileDescriptor();
Bitmapimage=BitmapFactory.decodeFileDescriptor(fileDescriptor);
pFileDescriptor.close();
myImageView.setImageBitmap(image);
Notethatthefiledescriptorisopenedin“r”mode.Thisindicatesthatthefileistobe
openedforreading.Otheroptionsare“w”forwriteaccessand“rwt”forreadandwrite
accesswhereexistingcontentinthefileistruncatedbythenewcontent.
Readingthecontentofatextfilerequiresslightlymoreworkandtheuseofan
InputStreamobject.Thefollowingcode,forexample,readsthelinesfromatextfile:
InputStreaminputStream=getContentResolver().openInputStream(uri);
BufferedReaderreader=newBufferedReader(newInputStreamReader(
inputStream));
Stringreadline;
while((readline=reader.readLine())!=null){
//Dosomethingwitheachlineinthefile
}
inputStream.close();
51.6WritingContenttoaFile
Writingtoanopenfilehostedbyadocumentproviderissimilartoreadingwiththe
exceptionthatanoutputstreamisusedinsteadofaninputstream.Thefollowingcode,for
example,writestexttotheoutputstreamofthestoragebasedfilereferencedbythe
specifiedUri:
try{
ParcelFileDescriptorpFileDescriptor=
this.getContentResolver().
openFileDescriptor(uri,“w”);
FileOutputStreamfileOutputStream=
newFileOutputStream(pFileDescriptor.getFileDescriptor());
StringtextContent=“Somesampletext”;
fileOutputStream.write(textContent.getBytes());
fileOutputStream.close();
pFileDescriptor.close();
}catch(FileNotFoundExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}
First,thefiledescriptorisextractedfromtheUri,thistimerequestingwritepermissionto
thetargetfile.Thefiledescriptorissubsequentlyusedtoobtainareferencetothefile’s
outputstream.Thecontent(inthisexamplesometext)isthenwrittentotheoutputstream
beforethefiledescriptorandoutputstreamareclosed.
51.7DeletingaFile
Whetherafilecanbedeletedfromstoragedependsonwhetherornotthefile’sdocument
providersupportsdeletionofthefile.Assumingdeletionispermitted,itmaybeperformed
onadesignatedUriasfollows:
if(DocumentsContract.deleteDocument(getContentResolver(),uri))
//Deletionwassuccessful
else
//Deletionfailed
51.8GainingPersistentAccesstoaFile
WhenanapplicationgainsaccesstoafileviatheStorageAccessFramework,theaccess
willremainvaliduntiltheAndroiddeviceonwhichtheapplicationisrunningisrestarted.
Persistentaccesstoaspecificfilecanbeobtainedby“taking”thenecessarypermissions
fortheUri.Thefollowingcode,forexample,persistsreadandwritepermissionsforthe
filereferencedbythefileUriUriinstance:
finalinttakeFlags=intent.getFlags()
&(Intent.FLAG_GRANT_READ_URI_PERMISSION
|Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(fileUri,takeFlags);
Oncethepermissionsforthefilehavebeentakenbytheapplication,andassumingtheUri
hasbeensavedbytheapplication,theusershouldbeabletocontinueaccessingthefile
afteradevicerestartwithouttheuserhavingtoreselectthefilefromthepickerinterface.
If,atanytime,thepersistentpermissionsarenolongerrequired,theycanbereleasedviaa
calltothereleasePersistableUriPermission()methodofthecontentresolver:
finalintreleaseFlags=intent.getFlags()
&(Intent.FLAG_GRANT_READ_URI_PERMISSION
|Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().releasePersistableUriPermission(fileUri,
releaseFlags);
51.9Summary
Itisinterestingtoconsiderhowperceptionsofstoragehavechangedinrecentyears.Once
synonymouswithhighcapacityinternalharddiskdrives,theterm“storage”isnowjustas
likelytorefertostoragespacehostedremotelyinthecloudandaccessedoveraninternet
connection.Thisisincreasinglythecasewiththewideadoptionofresourceconstrained,
“always-connected”mobiledeviceswithminimalinternalstoragecapacity.
TheAndroidStorageAccessFrameworkprovidesasimplemechanismforbothusersand
applicationdeveloperstoseamlesslygainaccesstofilesstoredinthecloud.Throughthe
useofasetofintentsintroducedintoAndroid4.4andabuilt-inuserinterfaceforselecting
documentprovidersandfiles,comprehensivecloudbasedstoragecannowbeintegrated
intoAndroidapplicationswithaminimalamountofcoding.
52.AnAndroidStorageAccessFramework
Example
Aspreviouslydiscussed,theStorageAccessFrameworkconsiderablyeasestheprocessof
integratingcloudbasedstorageaccessintoAndroidapplications.Consistingofapicker
userinterfaceandasetofnewintents,accesstofilesstoredondocumentproviderssuch
asGoogleDriveandBoxcannowbebuiltintoAndroidapplicationswithrelativeease.
WiththebasicsoftheAndroidStorageAccessFrameworkcoveredinthepreceding
chapter,thischapterwillworkthroughthecreationofanexampleapplicationwhichuses
theStorageAccessFrameworktostoreandmanagefiles.
52.1AbouttheStorageAccessFrameworkExample
TheAndroidapplicationcreatedinthischapterwilltaketheformofarudimentarytext
editordesignedtocreateandstoretextfilesremotelyontoacloudbasedstorageservice.
Inpractice,theexamplewillworkwithanycloudbaseddocumentstorageproviderthatis
compatiblewiththeStorageAccessFramework,thoughforthepurposeofthisexample
theuseofGoogleDriveisassumed.
Infunctionalterms,theapplicationwillpresenttheuserwithamulti-linetextviewinto
whichtextmaybeenteredandedited,togetherwithasetofbuttonsallowingstorage
basedtextfilestobecreated,openedandsaved.
52.2CreatingtheStorageAccessFrameworkExample
CreateanewprojectinAndroidStudio,enteringStorageDemointotheApplicationname
fieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI19:Android4.4(KitKat).Continuetoproceedthroughthescreens,
requestingthecreationofanemptyactivitynamedStorageDemoActivitywitha
correspondinglayoutnamedactivity_storage_demo.
52.3DesigningtheUserInterface
TheuserinterfacewillneedtobecomprisedofthreeButtonviewsandasingleEditText
view.WithintheProjecttoolwindow,navigatetotheactivity_storage_demo.xmllayout
filelocatedinapp->res->layoutanddoubleclickonittoloaditintotheDesignertool.
WiththetoolinDesignmode,selectanddeletetheHelloWorld!TextViewobject.
SwitchtheDesignertooltoTextmodeandremovethepaddingelementsfromthefileso
thatitnowreadsasoutlinedinthefollowinglisting:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=”.StorageDemoActivity”>
ReturntoDesignmodeanddraganddropthreeButtonobjectsontothelayoutcanvas.
PositiontheButtonviewsandchangethetextpropertiesto“New”,“Open”and“Save”so
thattheuserinterfacelayoutmatchesthatshowninFigure52-1,rememberingtoextract
thestringsasresourcevalues:
Figure52-1
SwitchbacktoTextmodeanddirectlymodifythelayouttoaddonClickhandlerstothe
buttonsandamultilineEditTextviewwithverticalscrollingenabled:
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
tools:context=”.StorageDemoActivity”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/new_string”
android:id=”@+id/button”
android:layout_alignParentTop=“true”
android:layout_alignParentStart=“true”
android:onClick=“newFile”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/open_string”
android:id=”@+id/button2”
android:layout_alignBottom=”@+id/button”
android:layout_toRightOf=”@+id/button”
android:onClick=“openFile”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/save_string”
android:id=”@+id/button3”
android:layout_alignParentTop=“true”
android:layout_toRightOf=”@+id/button2”
android:onClick=“saveFile”/>
<EditText
android:id=”@+id/fileText”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_alignParentLeft=“true”
android:layout_below=”@+id/button”
android:inputType=“textMultiLine”
android:scrollbars=“vertical”/>
</RelativeLayout>
52.4DeclaringRequestCodes
WorkingwithfilesintheStorageAccessFrameworkinvolvestriggeringavarietyof
intentsdependingonthespecificactiontobeperformed.Invariablythiswillresultinthe
frameworkdisplayingthestoragepickeruserinterfacesothattheusercanspecifythe
storagelocation(suchasadirectoryonGoogleDriveandthenameofafile).Whenthe
workoftheintentiscomplete,theapplicationwillbenotifiedbyacalltoamethodnamed
onActivityResult().
SinceallintentsfromasingleactivitywillresultinacalltothesameonActivityResult()
method,amechanismisrequiredtoidentifywhichintenttriggeredthecall.Thiscanbe
achievedbypassingarequestcodethroughtotheintentwhenitislaunched.Thiscodeis
thenpassedontotheonActivityResult()methodbytheintentsenablingthemethodto
identifywhichactionhasbeenrequestedbytheuser.BeforeimplementingtheonClick
handlerstocreate,saveandopenfiles,thefirststepistodeclaresomerequestcodesfor
thesethreeactions.
LocateandloadtheStorageDemoActivity.javafileintotheeditoranddeclareconstant
valuesforthethreeactionstobeperformedbytheapplication.Alsoaddsomecodeto
obtainareferencetothemultilineEditTextobjectwhichwillbereferencedinlater
methods:
packagecom.ebookfrenzy.storagedemo;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.EditText;
publicclassStorageDemoActivityextendsAppCompatActivity{
privatestaticEditTexttextView;
privatestaticfinalintCREATE_REQUEST_CODE=40;
privatestaticfinalintOPEN_REQUEST_CODE=41;
privatestaticfinalintSAVE_REQUEST_CODE=42;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_storage_demo);
textView=(EditText)findViewById(R.id.fileText);
}
.
.
.
}
52.5CreatingaNewStorageFile
WhentheNewbuttonisselected,theapplicationwillneedtotriggeran
ACTION_CREATE_DOCUMENTintentconfiguredtocreateafilewithaplaintext
MIMEtype.Whentheuserinterfacewasdesigned,theNewbuttonwasconfiguredtocall
amethodnamednewFile().Itiswithinthismethodthattheappropriateintentneedstobe
launched.
RemainingintheStorageDemoActivity.javafile,implementthismethodasfollows:
packagecom.ebookfrenzy.storagedemo;
importandroid.app.Activity;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.EditText;
importandroid.content.Intent;
importandroid.view.View;
publicclassStorageDemoActivityextendsAppCompatActivity{
publicclassStorageDemoActivityextendsActivity{
privatestaticEditTexttextView;
privatestaticfinalintCREATE_REQUEST_CODE=40;
privatestaticfinalintOPEN_REQUEST_CODE=41;
privatestaticfinalintSAVE_REQUEST_CODE=42;
.
.
.
publicvoidnewFile(Viewview)
{
Intentintent=newIntent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(“text/plain”);
intent.putExtra(Intent.EXTRA_TITLE,“newfile.txt”);
startActivityForResult(intent,CREATE_REQUEST_CODE);
}
.
.
}
ThiscodecreatesanewACTION_CREATE_INTENTIntentobject.Thisintentisthen
configuredsothatonlyfilesthatcanbeopenedwithafiledescriptorarereturned(viathe
Intent.CATEGORY_OPENABLEcategorysetting).
NextthecodespecifiesthatthefiletobeopenedistohaveaplaintextMIMEtypeanda
placeholderfilenameisprovided(whichcanbechangedbytheuserinthepicker
interface).Finally,theintentisstarted,passingthroughthepreviouslydeclared
CREATE_REQUEST_CODE.
Whenthismethodisexecutedandtheintenthascompletedtheassignedtask,acallwill
bemadetotheapplication’sonActivityResult()methodandpassed,amongstother
arguments,theUriofthenewlycreateddocumentandtherequestcodethatwasused
whentheintentwasstarted.Nowisanidealopportunitytobegintoimplementthis
method.
52.6TheonActivityResult()Method
TheonActivityResult()methodwillbesharedbyalloftheintentsthatwillbecalledduring
thelifecycleoftheapplication.Ineachcasethemethodwillbepassedarequestcode,a
resultcodeandasetofresultdatawhichcontainstheUriofthestoragefile.Themethod
willneedtobeimplementedsuchthatitchecksforthesuccessorotherwiseoftheintent
action,identifiesthetypeofactionperformedandextractsthefileUrifromtheresults
data.Atthispointinthetutorial,themethodonlyneedstohandlethecreationofanew
fileontheselecteddocumentprovider,somodifytheStorageDemoActivity.javafiletoadd
thismethodasfollows:
publicvoidonActivityResult(intrequestCode,intresultCode,
IntentresultData){
if(resultCode==Activity.RESULT_OK)
{
if(requestCode==CREATE_REQUEST_CODE)
{
if(resultData!=null){
textView.setText(””);
}
}
}
}
Thecodeinthismethodislargelystraightforward.Theresultoftheactivityischecked
and,ifsuccessful,therequestcodeiscomparedtotheCREATE_REQUEST_CODEvalue
toverifythattheuseriscreatinganewfile.Thatbeingthecase,theedittextviewis
clearedofanyprevioustexttosignifythecreationofanewfile.
CompileandruntheapplicationandselecttheNewbutton.TheStorageAccess
Frameworkshouldsubsequentlydisplaythe“Saveto”storagepickeruserinterfaceas
illustratedinFigure52-2.
Figure52-2
FromthismenuselecttheDriveoptionfollowedbyMyDriveandnavigatetoasuitable
locationonyourGoogleDrivestorageintowhichtosavethefile.Inthetextfieldatthe
bottomofthepickerinterfacechangethenamefrom“newfile.txt”toasuitablename(but
keepingthe.txtextension)beforeselectingtheSaveoption.
Oncethenewfilehasbeencreated,theappshouldreturntothemainactivityanda
notificationwillappearwithinthenotificationspanelwhichreads“1fileuploaded”
Figure52-3
AtthispointitshouldbepossibletologintoyourGoogleDriveaccountinabrowser
windowandfindthenewlycreatedfileintherequestedlocation(intheeventthatthefile
ismissing,makesurethattheAndroiddeviceonwhichtheapplicationisrunninghasan
activeinternetconnection).AccesstoGoogleDriveonthedevicemayalsobeverifiedby
runningtheGoogleDriveappwhichisinstalledbydefaultonmanyAndroiddevicesand
availablefordownloadfromtheGooglePlaystore.
52.7SavingtoaStorageFile
Nowthattheapplicationisabletocreatenewstoragebasedfiles,thenextstepistoadd
theabilitytosaveanytextenteredbytheusertoafile.Theuserinterfaceisconfiguredto
callthesaveFile()methodwhentheSavebuttonisselectedbytheuser.Thismethodwill
beresponsibleforstartinganewintentoftypeACTION_OPEN_DOCUMENTwhichwill
resultinthepickeruserinterfaceappearingsothattheusercanchoosethefiletowhich
thetextistobestored.Sinceweareonlyworkingwithplaintextfiles,theintentneedsto
beconfiguredtorestricttheuser’sselectionoptionstoexistingfilesthatmatchthe
text/plainMIMEtype.HavingidentifiedtheactionstobeperformedbythesaveFile()
method,thiscannowbeaddedtotheStorageDemoActivity.javaclassfileasfollows:
publicvoidsaveFile(Viewview)
{
Intentintent=newIntent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(“text/plain”);
startActivityForResult(intent,SAVE_REQUEST_CODE);
}
SincetheSAVE_REQUEST_CODEwaspassedthroughtotheintent,the
onActivityResult()methodmustnowbeextendedtohandlesaveactions:
packagecom.ebookfrenzy.storagedemo;
importandroid.app.Activity;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.EditText;
importandroid.content.Intent;
importandroid.view.View;
importandroid.net.Uri;
publicclassStorageDemoActivityextendsAppCompatActivity{
.
.
publicvoidonActivityResult(intrequestCode,intresultCode,
IntentresultData){
UricurrentUri=null;
if(resultCode==Activity.RESULT_OK)
{
if(requestCode==CREATE_REQUEST_CODE)
{
if(resultData!=null){
textView.setText(””);
}
}elseif(requestCode==SAVE_REQUEST_CODE){
if(resultData!=null){
currentUri=resultData.getData();
writeFileContent(currentUri);
}
}
}
}
.
.
}
Themethodnowchecksforthesaverequestcode,extractstheUriofthefileselectedby
theuserinthestoragepickerandcallsamethodnamedwriteFileContent(),passing
throughtheUriofthefiletowhichthetextistobewritten.Remaininginthe
StorageDemoActivity.javafile,implementthismethodnowsothatitreadsasfollows:
packagecom.ebookfrenzy.storagedemo;
importjava.io.FileNotFoundException;
importjava.io.FileOutputStream;
importjava.io.IOException;
importandroid.app.Activity;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.EditText;
importandroid.content.Intent;
importandroid.view.View;
importandroid.net.Uri;
importandroid.os.ParcelFileDescriptor;
publicclassStorageDemoActivityextendsAppCompatActivity{
.
.
privatevoidwriteFileContent(Uriuri)
{
try{
ParcelFileDescriptorpfd=
this.getContentResolver().
openFileDescriptor(uri,“w”);
FileOutputStreamfileOutputStream=
newFileOutputStream(pfd.getFileDescriptor());
StringtextContent=
textView.getText().toString();
fileOutputStream.write(textContent.getBytes());
fileOutputStream.close();
pfd.close();
}catch(FileNotFoundExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}
}
.
.
}
ThemethodbeginsbyobtainingandopeningthefiledescriptorfromtheUriofthefile
selectedbytheuser.Sincethecodewillneedtowritetothefile,thedescriptorisopened
inwritemode(“w”).Thefiledescriptoristhenusedasthebasisforcreatinganoutput
streamthatwillenabletheapplicationtowritetothefile.
Thetextenteredbytheuserisextractedfromtheedittextobjectandwrittentotheoutput
streambeforeboththefiledescriptorandstreamareclosed.Codeisalsoaddedtohandle
anyIOexceptionsencounteredduringthefilewritingprocess.
Withthenewmethodadded,compileandruntheapplication,entersometextintothetext
areaandselecttheSavebutton.Fromthepickerinterface,locatethepreviouslycreated
filefromtheGoogleDrivestoragetosavethetexttothatfile.ReturntoyourGoogle
Driveaccountinabrowserwindowandselectthetextfiletodisplaythecontents.Thefile
shouldnowcontainthetextenteredwithintheStorageDemoapplicationontheAndroid
device.
52.8OpeningandReadingaStorageFile
Havingwrittenthecodetocreateandsavetextfiles,thefinaltaskistoaddsome
functionalitytoopenandreadafilefromthestorage.Thiswillinvolvewritingthe
openFile()onClickeventhandlermethodandimplementingitsothatitstartsan
ACTION_OPEN_DOCUMENTintent:
publicvoidopenFile(Viewview)
{
Intentintent=newIntent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(“text/plain”);
startActivityForResult(intent,OPEN_REQUEST_CODE);
}
Inthiscode,theintentisconfiguredtofilterselectiontofileswhichcanbeopenedbythe
application.Whentheactivityisstarteditispassedtheopenrequestcodeconstantwhich
willnowneedtobehandledwithintheonActivityResult()method:
publicvoidonActivityResult(intrequestCode,intresultCode,
IntentresultData){
UricurrentUri=null;
if(resultCode==Activity.RESULT_OK)
{
if(requestCode==CREATE_REQUEST_CODE)
{
if(resultData!=null){
textView.setText(””);
}
}elseif(requestCode==SAVE_REQUEST_CODE){
if(resultData!=null){
currentUri=resultData.getData();
writeFileContent(currentUri);
}
}elseif(requestCode==OPEN_REQUEST_CODE){
if(resultData!=null){
currentUri=resultData.getData();
try{
Stringcontent=
readFileContent(currentUri);
textView.setText(content);
}catch(IOExceptione){
//Handleerrorhere
}
}
}
}
}
ThenewcodeaddedabovetohandletheopenrequestobtainstheUriofthefileselected
bytheuserfromthepickeruserinterfaceandpassesitthroughtoamethodnamed
readFileContent()whichisexpectedtoreturnthecontentoftheselectedfileintheform
ofaStringobject.Theresultingstringisthenassignedtothetextpropertyoftheedittext
view.Clearly,thenexttaskistoimplementthereadFileContent()method:
packagecom.ebookfrenzy.storagedemo;
importjava.io.FileNotFoundException;
importjava.io.FileOutputStream;
importjava.io.IOException;
importjava.io.BufferedReader;
importjava.io.InputStream;
importjava.io.InputStreamReader;
importandroid.app.Activity;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.EditText;
importandroid.content.Intent;
importandroid.view.View;
importandroid.net.Uri;
importandroid.os.ParcelFileDescriptor;
publicclassStorageDemoActivityextendsAppCompatActivity{
.
.
.
privateStringreadFileContent(Uriuri)throwsIOException{
InputStreaminputStream=
getContentResolver().openInputStream(uri);
BufferedReaderreader=
newBufferedReader(newInputStreamReader(
inputStream));
StringBuilderstringBuilder=newStringBuilder();
Stringcurrentline;
while((currentline=reader.readLine())!=null){
stringBuilder.append(currentline+“\n”);
}
inputStream.close();
returnstringBuilder.toString();
}
.
.
}
Thismethodbeginsbyextractingthefiledescriptorfortheselectedtextfileandopeningit
forreading.TheinputstreamassociatedwiththeUriisthenopenedandusedastheinput
sourceforaBufferedReaderinstance.Eachlinewithinthefileisthenreadandstoredina
StringBuilderobject.Onceallthelineshavebeenread,theinputstreamandfiledescriptor
arebothclosedandthefilecontentisreturnedasaStringobject.
52.9TestingtheStorageAccessApplication
Withthecodingphasecompletetheapplicationisnowreadytobefullytested.Beginby
launchingtheapplicationonaphysicalAndroiddeviceandselectingthe“New”button.
Withintheresultingstoragepickerinterface,selectaGoogleDrivelocationandnamethe
textfilestoragedemo.txtbeforeselectingtheSaveoptionlocatedtotherightofthefile
namefield.
Whencontrolreturnstoyourapplicationlookforthefileuploadingnotification,then
entersometextintothetextareabeforeselectingthe“Save”button.Selectthepreviously
createdstoragedemo.txtfilefromthepickertosavethecontenttothefile.Onreturningto
theapplication,deletethetextandselectthe“Open”button,onceagainchoosingthe
storagedemo.txtfile.Whencontrolisreturnedtotheapplication,thetextviewshould
havebeenpopulatedwiththecontentofthetextfile.
ItisimportanttonotethattheStorageAccessFrameworkwillcachestoragefileslocally
intheeventthattheAndroiddevicelacksanactiveinternetconnection.Onceconnectivity
isre-established,however,anycacheddatawillbesynchronizedwiththeremotestorage
service.Asafinaltestoftheapplication,therefore,logintoyourGoogleDriveaccountin
abrowserwindow,navigatetothestoragedemo.txtfileandclickonittoviewthecontent
whichshould,allbeingwell,containthetextsavedbytheapplication.
52.10Summary
ThischapterhasworkedthroughthecreationofanexampleAndroidStudioapplicationin
theformofaveryrudimentarytexteditordesignedtousecloudbasedstoragetocreate,
saveandopenfilesusingtheAndroidStorageAccessFramework.
53.ImplementingVideoPlaybackonAndroid
usingtheVideoViewandMediaController
Classes
Oneoftheprimaryusesforsmartphonesandtabletsistoenabletheusertoaccessand
consumecontent.Onekeyformofcontentwidelyused,especiallyinthecaseoftablet
devices,isvideo.
TheAndroidSDKincludestwoclassesthatmaketheimplementationofvideoplayback
onAndroiddevicesextremelyeasytoimplementwhendevelopingapplications.This
chapterwillprovideanoverviewofthesetwoclasses,VideoViewandMediaController,
beforeworkingthroughthecreationofasimplevideoplaybackapplication.
53.1IntroducingtheAndroidVideoViewClass
ByfarthesimplestwaytodisplayvideowithinanAndroidapplicationistousethe
VideoViewclass.Thisisavisualcomponentwhich,whenaddedtothelayoutofan
activity,providesasurfaceontowhichavideomaybeplayed.Androidcurrentlysupports
thefollowingvideoformats:
· H.263
· H.264AVC
· MPEG-4SP
· VP8
TheVideoViewclasshasawiderangeofmethodsthatmaybecalledinordertomanage
theplaybackofvideo.Someofthemorecommonlyusedmethodsareasfollows:
· setVideoPath(Stringpath)–Specifiesthepath(asastring)ofthevideomediatobe
played.ThiscanbeeithertheURLofaremotevideofileoravideofilelocaltothe
device.
· setVideoUri(Uriuri)–PerformsthesametaskasthesetVideoPath()methodbuttakes
aUriobjectasanargumentinsteadofastring.
· start()–Startsvideoplayback.
· stopPlayback()–Stopsthevideoplayback.
· pause()–Pausesvideoplayback.
· isPlaying()–ReturnsaBooleanvalueindicatingwhetheravideoiscurrentlyplaying.
· setOnPreparedListener(MediaPlayer.OnPreparedListener)–Allowsacallback
methodtobecalledwhenthevideoisreadytoplay.
· setOnErrorListener(MediaPlayer.OnErrorListener)-Allowsacallbackmethodto
becalledwhenanerroroccursduringthevideoplayback.
· setOnCompletionListener(MediaPlayer.OnCompletionListener)-Allowsa
callbackmethodtobecalledwhentheendofthevideoisreached.
· getDuration()–Returnsthedurationofthevideo.Willtypicallyreturn-1unless
calledfromwithintheOnPreparedListener()callbackmethod.
· getCurrentPosition()–Returnsanintegervalueindicatingthecurrentpositionof
playback.
· setMediaController(MediaController)–DesignatesaMediaControllerinstance
allowingplaybackcontrolstobedisplayedtotheuser.
53.2IntroducingtheAndroidMediaControllerClass
IfavideoissimplyplayedusingtheVideoViewclass,theuserwillnotbegivenany
controlovertheplayback,whichwillrununtiltheendofthevideoisreached.Thisissue
canbeaddressedbyattachinganinstanceoftheMediaControllerclasstotheVideoView
instance.TheMediaControllerwillthenprovideasetofcontrolsallowingtheuserto
managetheplayback(suchaspausingandseekingbackwards/forwardsinthevideo
timeline).
Thepositionofthecontrolsisdesignatedbyanchoringthecontrollerinstancetoaspecific
viewintheuserinterfacelayout.Onceattachedandanchored,thecontrolswillappear
brieflywhenplaybackstartsandmaysubsequentlyberestoredatanypointbytheuser
tappingontheviewtowhichtheinstanceisanchored.
Someofthekeymethodsofthisclassareasfollows:
· setAnchorView(Viewview)–Designatestheviewtowhichthecontrolleristobe
anchored.Thiscontrolsthelocationofthecontrolsonthescreen.
· show()–Displaysthecontrols.
· show(inttimeout)–Controlsaredisplayedforthedesignatedduration(in
milliseconds).
· hide()–Hidesthecontrollerfromtheuser.
· isShowing()–ReturnsaBooleanvalueindicatingwhetherthecontrolsarecurrently
visibletotheuser.
53.3TestingVideoPlayback
Atthetimeofwriting,itisnotpossibletotestvideoplaybackwhenusingtheAndroid
AVDemulators.Totestthevideoplaybackfunctionalityofanapplicationitwillbe
necessarytodeployitontoaphysicaldevice.
53.4CreatingtheVideoPlaybackExample
Theremainderofthischapterisdedicatedtoworkingthroughanexampleapplication
intendedtousetheVideoViewandMediaControllerclassestoplayawebbasedMPEG-4
videofile.
CreateanewprojectinAndroidStudio,enteringVideoPlayerintotheApplicationname
fieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedVideoPlayerActivitywithacorrespondinglayout
namedactivity_video_player.
53.5DesigningtheVideoPlayerLayout
Theuserinterfaceforthemainactivitywillsimplyconsistsolelyofaninstanceofthe
VideoViewclass.UsetheProjecttoolwindowtolocatetheapp->res->layout->
activity_video_player.xmlfile,doubleclickonitandswitchtheDesignertooltoDesign
mode.Deletethe“Helloworld!”TextView,switchtoTextmodeandremovethepadding
propertiesfromthelayoutsothattheXMLreadsasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=”com.ebookfrenzy.videoplayer.VideoPlayerActivity”>
</RelativeLayout>
SwitchbacktoDesignmodeand,fromtheContainerssectionofthePalettepanel,drag
anddropaVideoViewinstanceontothecenterpointofthelayoutsothattheuserinterface
resemblesthatofFigure53-1:
Figure53-1
DoubleclickontheVideoViewinstanceinthedevicescreenlayoutandchangetheIDof
thecomponenttovideoView1.Finally,eitheredittheXMLorusetheDesignertoolbar
buttonstochangethelayout_widthandlayout_heightpropertiesfortheVideoView
instancetomatch_parent.
53.6ConfiguringtheVideoView
ThenextstepistoconfiguretheVideoViewwiththepathofthevideotobeplayedand
thenstarttheplayback.Thiswillbeperformedwhenthemainactivityhasinitialized,so
loadtheVideoPlayerActivity.javafileintotheeditorandmodifytheOnCreate()methodas
outlinedinthefollowinglisting:
packagecom.ebookfrenzy.videoplayer;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.VideoView;
publicclassVideoPlayerActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_player);
finalVideoViewvideoView=
(VideoView)findViewById(R.id.videoView1);
videoView.setVideoPath(
“http://www.ebookfrenzy.com/android_book/movie.mp4”);
videoView.start();
}
.
.
.
}
AllthatthiscodedoesisobtainareferencetotheVideoViewinstanceinthelayout,set
thevideopathonittopointtoanMPEG-4filehostedonawebsiteandthenstartthe
videoplaying.
53.7AddingInternetPermission
Anattempttoruntheapplicationatthispointwouldresultintheapplicationfailingto
launchwithanerrordialogappearingontheAndroiddevicethatreads“UnabletoPlay
Video.Sorry,thisvideocannotbeplayed”.Thisisnotbecauseofanerrorinthecodeor
anincorrectvideofileformat.Theissuewouldbethattheapplicationisattemptingto
accessafileovertheinternet,buthasfailedtorequestappropriatepermissionstodoso.
Toresolvethis,edittheAndroidManifest.xmlfilefortheprojectandaddalinetorequest
internetaccess:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.videoplayer.videoplayer”>
<uses-permissionandroid:name=“android.permission.INTERNET”/>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
.
.
.
</manifest>
TesttheapplicationbyrunningitonaphysicalAndroiddevice.Aftertheapplication
launchestheremaybeashortdelaywhilevideocontentisbufferedbeforetheplayback
begins(Figure53-2).
Figure53-2
Thisprovidesanindicationofhoweasyitcanbetointegratevideoplaybackintoan
Androidapplication.Everythingsofarinthisexamplehasbeenachievedusinga
VideoViewinstanceandthreelinesofcode.
53.8AddingtheMediaControllertotheVideoView
AstheVideoPlayerapplicationcurrentlystands,thereisnowayfortheusertocontrol
playback.Aspreviouslyoutlined,thiscanbeachievedusingtheMediaControllerclass.To
addacontrollertotheVideoView,modifytheonCreate()methodonceagain:
packagecom.ebookfrenzy.videoplayer;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.VideoView;
importandroid.widget.MediaController;
publicclassVideoPlayerActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_player);
finalVideoViewvideoView=(VideoView)
findViewById(R.id.videoView1);
videoView.setVideoPath(
“http://www.ebookfrenzy.com/android_book/movie.mp4”);
MediaControllermediaController=new
MediaController(this);
mediaController.setAnchorView(videoView);
videoView.setMediaController(mediaController);
videoView.start();
}
.
.
.
}
Whentheapplicationislaunchedwiththesechangesimplemented,tappingtheVideoView
canvaswillcausethemediacontrolswillappearoverthevideoplayback.Thesecontrols
shouldincludeaseekbartogetherwithfastforward,rewindandplay/pausebuttons.After
thecontrolsrecedefromview,theycanberestoredatanytimebytappingonthe
VideoViewcanvasonceagain.Withjustthreemorelinesofcode,ourvideoplayer
applicationnowhasmediacontrolsasshowninFigure53-3:
Figure53-3
53.9SettinguptheonPreparedListener
Asafinalexampleofworkingwithvideobasedmedia,theonCreate()methodwillnow
beextendedfurthertodemonstratethemechanismforconfiguringalistener.Inthiscase,a
listenerwillbeimplementedthatisintendedtooutputthedurationofthevideoasa
messageintheAndroidStudioLogCatpanel:
packagecom.ebookfrenzy.videoplayer;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.VideoView;
importandroid.widget.MediaController;
importandroid.util.Log;
importandroid.media.MediaPlayer;
publicclassVideoPlayerActivityextendsAppCompatActivity{
StringTAG=“VideoPlayer”;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_player);
finalVideoViewvideoView=
(VideoView)findViewById(R.id.videoView1);
videoView.setVideoPath(
“http://www.ebookfrenzy.com/android_book/movie.mp4”);
MediaControllermediaController=new
MediaController(this);
mediaController.setAnchorView(videoView);
videoView.setMediaController(mediaController);
videoView.setOnPreparedListener(new
MediaPlayer.OnPreparedListener(){
@Override
publicvoidonPrepared(MediaPlayermp){
Log.i(TAG,“Duration=”+
videoView.getDuration());
}
});
videoView.start();
}
.
.
.
}
Nowjustbeforethevideoplaybackbegins,amessagewillappearintheAndroidStudio
LogCatpanelthatreadsalongthelinesof:
11-0510:27:52.25612542-12542/com.ebookfrenzy.videoplayer
I/VideoPlayer:Duration=6874
53.10Summary
TabletbasedAndroiddevicesmakeexcellentplatformsforthedeliveryofcontentto
users,particularlyintheformofvideomedia.Asoutlinedinthischapter,theAndroid
SDKprovidestwoclasses,namelyVideoViewandMediaController,whichcombineto
maketheintegrationofvideoplaybackintoAndroidapplicationsquickandeasy,often
involvingjustafewlinesofJavacode.
54.VideoRecordingandImageCaptureon
AndroidusingCameraIntents
ManyAndroiddevicesareequippedwithatleastonecamera.Thereareanumberofways
toallowtheusertorecordvideofromwithinanAndroidapplicationviathesebuilt-in
cameras,butbyfartheeasiestapproachistomakeuseofacameraintentincludedwith
theAndroidoperatingsystem.ThisallowsanapplicationtoinvokethestandardAndroid
videorecordinginterface.Whentheuserhasfinishedrecording,theintentwillreturnto
theapplication,passingthroughareferencetothemediafilecontainingtherecorded
video.
Aswillbedemonstratedinthischapter,thisapproachallowsvideorecordingcapabilities
tobeaddedtoapplicationswithjustafewlinesofcode.
54.1CheckingforCameraSupport
BeforeattemptingtoaccessthecameraonanAndroiddevice,itisessentialthatdefensive
codebeimplementedtoverifythepresenceofcamerahardware.Thisisofparticular
importancesincenotallAndroiddevicesincludeacamera.
Thepresenceorotherwiseofacameracanbeidentifiedviaacalltothe
PackageManager.hasSystemFeature()method.Inordertocheckforthepresenceofa
front-facingcamera,thecodeneedstocheckforthepresenceofthe
PackageManager.FEATURE_CAMERA_FRONTfeature.Thiscanbeencapsulatedinto
thefollowingconveniencemethod:
privatebooleanhasCamera(){
if(getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CAMERA_FRONT)){
returntrue;
}else{
returnfalse;
}
}
Thepresenceofacamerafacingawayfromthedevicescreencanbesimilarlyverified
usingthePackageManager.FEATURE_CAMERAconstant.Atestforwhetheradevicehas
anycameracanbeperformedbyreferencing
PackageManager.FEATURE_CAMERA_ANY.
54.2CallingtheVideoCaptureIntent
Useofthevideocaptureintentinvolves,ataminimum,theimplementationofcodetocall
theintentactivityandamethodtohandlethereturnfromtheactivity.TheAndroidbuiltinvideorecordingintentisrepresentedbyMediaStore.ACTION_VIDEO_CAPTUREand
maybelaunchedasfollows:
privatestaticfinalintVIDEO_CAPTURE=101;
Intentintent=newIntent(MediaStore.ACTION_VIDEO_CAPTURE);
startActivityForResult(intent,VIDEO_CAPTURE);
Wheninvokedinthisway,theintentwillplacetherecordedvideointoafileusinga
defaultlocationandfilename.Aspecificlocationforthemediafilemaybespecified
usingtheputExtra()methodoftheintent,referencingtheMediaStore.EXTRA_OUTPUT
keyconstanttopassthroughthetargetURIvalue.Thefollowingcode,forexample,
specifiesthatthevideoshouldbestoredontheSDcardinafilenamedmyvideo.mp4:
FilemediaFile=
newFile(Environment.getExternalStorageDirectory().getAbsolutePath()
+“/myvideo.mp4”);
Intentintent=newIntent(MediaStore.ACTION_VIDEO_CAPTURE);
UrivideoUri=Uri.fromFile(mediaFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT,videoUri);
startActivityForResult(intent,VIDEO_CAPTURE);
Whentheusereithercompletesorcancelsthevideorecordingsession,the
onActivityResult()methodofthecallingactivitywillbecalled.Thismethodneedsto
checkthattherequestcodepassedthroughasanargumentmatchesthatspecifiedwhen
theintentwaslaunched,verifythattherecordingsessionwassuccessfulandextractthe
pathofthevideomediafile.ThecorrespondingonActivityResult()methodfortheabove
intentlaunchcodemight,therefore,beimplementedasfollows:
protectedvoidonActivityResult(intrequestCode,intresultCode,Intent
data){
if(requestCode==VIDEO_CAPTURE){
if(resultCode==RESULT_OK){
Toast.makeText(this,“Videosavedto:\n”+
videoUri,Toast.LENGTH_LONG).show();
}elseif(resultCode==RESULT_CANCELED){
Toast.makeText(this,“Videorecordingcancelled.”,
Toast.LENGTH_LONG).show();
}else{
Toast.makeText(this,“Failedtorecordvideo”,
Toast.LENGTH_LONG).show();
}
}
}
Theabovecodeexamplesimplydisplaysatoastmessageindicatingthesuccessor
otherwiseoftherecordingintentsession.Intheeventofasuccessfulrecording,thepathto
thestoredvideofileisdisplayed.
Whenexecuted,thevideocaptureintent(Figure54-1)willlaunchandprovidetheuserthe
opportunitytorecordvideo.
Figure54-1
54.3CallingtheImageCaptureIntent
Inadditiontothevideocaptureintent,Androidalsoincludesanintentdesignedfortaking
stillphotosusingthebuilt-incamera.Thisintentislaunchedbyreferencing
MediaStore.ACTION_IMAGE_CAPTURE:
privatestaticfinalintIMAGE_CAPTURE=102;
Intentintent=newIntent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent,IMAGE_CAPTURE);
Aswithvideocapture,theintentmaybepassedthelocationandfilenameintowhichthe
imageistobestored,orlefttousethedefaultlocationandnamingconvention.
54.4CreatinganAndroidStudioVideoRecordingProject
Intheremainderofthischapter,averysimpleapplicationwillbecreatedtodemonstrate
theuseofthevideocaptureintent.Theapplicationwillconsistofasinglebuttonwhich,
whentouchedbytheuser,willlaunchthevideocaptureintent.Oncevideohasbeen
recordedandthevideocaptureintentdismissed,theapplicationwillsimplydisplaythe
pathtothevideofileasaToastmessage.TheVideoPlayerapplicationcreatedinthe
previouschaptermaythenbemodifiedtoplaybacktherecordedvideo.
CreateanewprojectinAndroidStudio,enteringCameraAppintotheApplicationname
fieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedCameraAppActivitywithalayoutfilenamed
activity_camera_app.
54.5DesigningtheUserInterfaceLayout
Navigatetoapp->res->layoutanddoubleclickontheactivity_camera_app.xmllayout
filetoloaditintotheDesignertool.WiththeDesignertoolinDesignmode,deletethe
default“Helloworld!”textviewandreplaceitwithaButtonviewpositionedinthecenter
ofthedisplaycanvas.Changethetextonthebuttontoread“RecordVideo”andassignan
onClickpropertytothebuttonsothatitcallsamethodnamedstartRecordingwhen
selectedbytheuser:
Figure54-2
Finally,doubleclickonthebuttonandchangetheIDtorecordButton.Thecorresponding
XMLintheactivity_camera_app.xmlfileshouldapproximatelyresemblethefollowing
listing:
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=”com.ebookfrenzy.cameraapp.CameraAppActivity”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/record_string”
android:id=”@+id/recordButton”
android:layout_centerVertical=“true”
android:layout_centerHorizontal=“true”
android:onClick=“startRecording”/>
</RelativeLayout>
54.6CheckingfortheCamera
Beforeattemptingtolaunchthevideocaptureintent,theapplicationfirstneedstoverify
thatthedeviceonwhichitisrunningactuallyhasacamera.Forthepurposesofthis
example,wewillsimplymakeuseofthepreviouslyoutlinedhasCamera()method,this
timecheckingforanycameratype.Intheeventthatacameraisnotpresent,theRecord
Videobuttonwillbedisabled.
EdittheCameraAppActivity.javafileandmodifyitasfollows:
packagecom.ebookfrenzy.cameraapp;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.content.pm.PackageManager;
importandroid.widget.Button;
publicclassCameraAppActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera_app);
ButtonrecordButton=
(Button)findViewById(R.id.recordButton);
if(!hasCamera())
recordButton.setEnabled(false);
}
privatebooleanhasCamera(){
if(getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CAMERA_ANY)){
returntrue;
}else{
returnfalse;
}
}
.
.
.
}
54.7LaunchingtheVideoCaptureIntent
TheobjectiveisforthevideocaptureintenttolaunchwhentheuserselectstheRecord
Videobutton.SincethisisnowconfiguredtocallamethodnamedstartRecording(),the
nextlogicalstepistoimplementthismethodwithintheCameraAppActivity.javasource
file:
packagecom.ebookfrenzy.cameraapp;
importjava.io.File;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.content.pm.PackageManager;
importandroid.widget.Button;
importandroid.net.Uri;
importandroid.os.Environment;
importandroid.provider.MediaStore;
importandroid.content.Intent;
importandroid.view.View;
publicclassCameraAppActivityextendsAppCompatActivity{
privatestaticfinalintVIDEO_CAPTURE=101;
privateUrifileUri;
publicvoidstartRecording(Viewview)
{
FilemediaFile=new
File(Environment.getExternalStorageDirectory().getAbsolutePath()
+“/myvideo.mp4”);
Intentintent=newIntent(MediaStore.ACTION_VIDEO_CAPTURE);
fileUri=Uri.fromFile(mediaFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT,fileUri);
startActivityForResult(intent,VIDEO_CAPTURE);
}
.
.
.
}
54.8HandlingtheIntentReturn
Whencontrolreturnsbackfromtheintenttotheapplication’smainactivitythe
onActivityResult()methodwillbecalled.Allthatthismethodneedstodoforthisexample
isverifythesuccessorotherwiseofthevideocaptureanddisplaythepathofthefileinto
whichthevideohasbeenstored:
packagecom.ebookfrenzy.cameraapp;
importjava.io.File;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.content.pm.PackageManager;
importandroid.widget.Button;
importandroid.net.Uri;
importandroid.os.Environment;
importandroid.provider.MediaStore;
importandroid.content.Intent;
importandroid.view.View;
importandroid.widget.Toast;
publicclassCameraAppActivityextendsAppCompatActivity{
.
.
.
protectedvoidonActivityResult(intrequestCode,
intresultCode,Intentdata){
if(requestCode==VIDEO_CAPTURE){
if(resultCode==RESULT_OK){
Toast.makeText(this,“Videohasbeensavedto:\n”+
fileUri,Toast.LENGTH_LONG).show();
}elseif(resultCode==RESULT_CANCELED){
Toast.makeText(this,“Videorecordingcancelled.”,
Toast.LENGTH_LONG).show();
}else{
Toast.makeText(this,“Failedtorecordvideo”,
Toast.LENGTH_LONG).show();
}
}
}
.
.
}
54.9TestingtheApplication
CompileandruntheapplicationonaphysicalAndroiddevice,touchtherecordbuttonand
usethevideocaptureintenttorecordsomevideo.Oncecompleted,stopthevideo
recording.Playbacktherecordingbyselectingtheplaybuttononthescreen.Finally,
touchtheDone(sometimesrepresentedbyacheckmark)buttononthescreentoreturnto
theCameraAppapplication.Onreturning,aToastmessageshouldappearstatingthatthe
videohasbeenstoredinaspecificlocationonthedevice(theexactlocationwilldiffer
fromonedevicetypetoanother).
FromtheAndroidStudiowelcomescreen,locatetheVideoPlayerprojectcreatedinthe
previouschapter,navigatetotheVideoPlayerActivity.javafileandmodifythe
setVideoPath()methodcalltoreferencethenewlyrecordedvideofilepath.
RunningthemodifiedVideoPlayerapplicationontheAndroiddeviceshouldresultinthe
previouslyrecordedvideobeingplayedback.
54.10Summary
MostAndroidtabletandsmartphonedevicesincludeacamerathatcanbeaccessedby
applications.Whilethereareanumberofdifferentapproachestoaddingcamerasupport
toapplications,theAndroidvideoandimagecaptureintentsprovideasimpleandeasy
solutiontocapturingvideoandimages.
55.MakingRuntimePermissionRequestsin
Android6.0
Inanumberoftheexampleprojectscreatedinprecedingchapters,changeshavebeen
madetotheAndroidManifest.xmlfiletorequestpermissionfortheapptoperforma
specifictask.Inacoupleofinstances,forexample,internetaccesspermissionhasbeen
requestedinordertoallowtheapptodownloadanddisplaywebpages.Ineachcaseup
untilthispoint,theadditionoftherequesttothemanifesthasbeenallthatisrequiredin
orderfortheapptoobtainpermissionfromtheusertoperformthedesignatedtask.
Thereare,however,anumberpermissionsforwhichadditionalstepsarerequiredinorder
fortheapptofunctionwhenrunningonAndroid6.0orlater.Thefirstoftheseso-called
“dangerous”permissionswillbeencounteredinthenextchapter.Beforereachingthat
point,however,thischapterwilloutlinethestepsinvolvedinrequestingsuchpermissions
whenrunningonthelatestgenerationsofAndroid.
55.1UnderstandingNormalandDangerousPermissions
Androidenforcessecuritybyrequiringtheusertograntpermissionforanapptoperform
certaintasks.PriortotheintroductionofAndroid6,permissionwasalwayssoughtatthe
pointthattheappwasinstalledonthedevice.Figure55-1,forexample,showsatypical
screenseekingavarietyofpermissionsduringtheinstallationofanappviaGooglePlay.
Figure55-1
FormanytypesofpermissionthisscenariostillappliesforappsonAndroid6.0orlater.
Thesepermissionsarereferredtoasnormalpermissionsandarestillrequiredtobe
acceptedbytheuseratthepointofinstallation.Asecondtypeofpermission,referredtoas
dangerouspermissionsmustalsobedeclaredwithinthemanifestfileinthesamewayasa
normalpermission,butmustalsoberequestedfromtheuserwhentheapplicationisfirst
launched.Whensucharequestismade,itappearsintheformofadialogboxas
illustratedinFigure55-2:
Figure55-2
ThefulllistofpermissionsthatfallintothedangerouscategoryiscontainedinTable55-1:
PermissionGroup Permission
Calendar
READ_CALENDAR
WRITE_CALENDAR
Camera
CAMERA
READ_CONTACTS
Contacts
WRITE_CONTACTS
GET_ACCOUNTS
Location
ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
Microphone
RECORD_AUDIO
READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
Phone
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
Sensors
BODY_SENSORS
SEND_SMS
RECEIVE_SMS
SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
Storage
READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
Table55-1
55.2CreatingthePermissionsExampleProject
CreateanewprojectinAndroidStudio,enteringPermissionDemointotheApplication
namefieldandcom.ebookfrenzyastheCompanyDomainsettingbeforeclickingonthe
Nextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI19:Android4.4(KitKat).Continuetoproceedthroughthescreens,
requestingthecreationofanemptyactivitynamedPermissionDemoActivitywitha
correspondinglayoutnamedactivity_permission_demo.
55.3CheckingforaPermission
TheAndroidSupportLibrarycontainsanumberofmethodsthatcanbeusedtoseekand
managedangerouspermissionswithinthecodeofanAndroidapp.TheseAPIcallscanbe
madesafelyregardlessoftheversionofAndroidonwhichtheappisrunning,butwill
onlyperformmeaningfultaskswhenexecutedonAndroid6.0orlater.
Beforeanappattemptstomakeuseofafeaturethatrequiresapprovalofadangerous
permission,andregardlessofwhetherornotpermissionwaspreviouslygranted,thecode
mustcheckthatthepermissionhasbeengranted.Thiscanbeachievedviaacalltothe
checkSelfPermission()methodoftheContextCompatclass,passingthroughasarguments
areferencetothecurrentactivityandthepermissionbeingrequested.Themethodwill
checkwhetherthepermissionhasbeenpreviouslygrantedandreturnanintegervalue
matchingPackageManager.PERMISSION_GRANTEDor
PackageManager.PERMISSION_DENIED.
WithinthePermissionDemoActivity.javafileoftheexampleproject,modifythecodeto
checkwhetherpermissionhasbeengrantedfortheapptorecordaudio:
packagecom.ebookfrenzy.permissiondemoactivity;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.Manifest;
importandroid.content.pm.PackageManager;
importandroid.support.v4.content.ContextCompat;
importandroid.util.Log;
publicclassPermissionDemoActivityextendsAppCompatActivity{
privatestaticStringTAG=“PermissionDemo”;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission_demo);
intpermission=ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO);
if(permission!=PackageManager.PERMISSION_GRANTED){
Log.i(TAG,“Permissiontorecorddenied”);
}
}
}
RuntheapponadeviceoremulatorrunningaversionofAndroidthatpredatesAndroid
6.0andcheckthelogcatoutputwithinAndroidStudio.Aftertheapphaslaunched,the
outputshouldincludethe“Permissiontorecorddenied”message.
EdittheAndroidManifest.xmlfile(locatedintheProjecttoolwindowunderapp->
manifests)andaddalinetorequestrecordingpermissionasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.permissiondemoactivity”>
<uses-permissionandroid:name=“android.permission.RECORD_AUDIO”/>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:supportsRtl=“true”
android:theme=”@style/AppTheme”>
<activityandroid:name=”.PermissionDemoActivity”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Compileandruntheapponceagainandnotethatthistimethepermissiondenialmessage
doesnotappear.Clearlyeverythingthatneedtobedonetorequestthispermissionon
olderversionsofAndroidhasbeendone.Runtheapponadeviceoremulatorrunning
Android6.0orlater,however,andnotethateventhoughpermissionhasbeenaddedtothe
manifestfile,thecheckstillreportsthatpermissionhasbeendenied.Thisisbecause
Android6requiresthattheappalsorequestdangerouspermissionsatruntime.
55.4RequestingPermissionatRuntime
ApermissionrequestismadeviaacalltotherequestPermissions()methodofthe
ActivityCompatclass.Whenthismethodiscalled,thepermissionrequestishandled
asynchronouslyandamethodnamedonRequestPermissionsResult()calledwhenthetask
iscompleted.
TherequestPermissions()methodtakesasargumentsareferencetothecurrentactivity,
togetherwiththeidentifierofthepermissionbeingrequestedandarequestcode.The
requestcodecanbeanyintegervalueandwillbeusedtoidentifywhichrequesthas
triggeredthecalltotheonRequestPermissionsResult()method.Modifythe
PermissionDemoActivity.javafiletodeclarearequestcodeandrequestrecording
permissionintheeventthatthepermissioncheckfailed:
packagecom.ebookfrenzy.permissiondemoactivity;
importandroid.Manifest;
importandroid.content.pm.PackageManager;
importandroid.support.v4.content.ContextCompat;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.support.v4.app.ActivityCompat;
publicclassPermissionDemoActivityextendsAppCompatActivity{
privatestaticStringTAG=“PermissionDemo”;
privatestaticfinalintRECORD_REQUEST_CODE=101;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission_demo);
intpermission=ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO);
if(permission!=PackageManager.PERMISSION_GRANTED){
Log.i(TAG,“Permissiontorecorddenied”);
makeRequest();
}
}
protectedvoidmakeRequest(){
ActivityCompat.requestPermissions(this,
newString[]{Manifest.permission.RECORD_AUDIO},
RECORD_REQUEST_CODE);
}
}
Next,implementtheonRequestPermissionsResult()methodsothatitreadsasfollows:
@Override
publicvoidonRequestPermissionsResult(intrequestCode,
Stringpermissions[],int[]
grantResults){
switch(requestCode){
caseRECORD_REQUEST_CODE:{
if(grantResults.length==0
||grantResults[0]!=
PackageManager.PERMISSION_GRANTED){
Log.i(TAG,“Permissionhasbeendeniedbyuser”);
}else{
Log.i(TAG,“Permissionhasbeengrantedbyuser”);
}
return;
}
}
}
CompileandruntheapponanAndroid6emulatorordeviceandnotethatadialog
seekingpermissiontorecordaudioappearsasshowninFigure55-3:
Figure55-3
TaptheAllowbuttonandcheckthatthe“Permissionhasbeengrantedbyuser”message
appearsintheLogCatpanel.
Oncetheuserhasgrantedtherequestedpermission,thecheckSelfPermission()method
callwillreturnaPERMISSION_GRANTEDresultonfutureappinvocationsuntilthe
useruninstallsandre-installstheapporchangesthepermissionsfortheappinSettings.
55.5ProvidingaRationaleforthePermissionRequest
AsisevidentfromFigure55-3,theuserhastheoptiontodenytherequestedpermission.
Inthiscase,theappwillcontinuetorequestthepermissioneachtimethatitislaunched
bytheuserunlesstheuserselectedthe“Neveraskagain”optionpriortoclickingonthe
Denybutton.Repeateddenialsbytheusermayindicatethattheuserdoesn’tunderstand
whythepermissionisrequiredbytheapp.Theusermight,therefore,bemorelikelyto
grantpermissionifthereasonfortherequirementsisexplainedwhentherequestismade.
Unfortunately,itisnotpossibletochangethecontentoftherequestdialogtoincludesuch
anexplanation.
Anexplanationisbestincludedinaseparatedialogwhichcanbedisplayedbeforethe
requestdialogispresentedtotheuser.Thisraisesthequestionastowhentodisplaythis
explanationdialog.TheAndroiddocumentationrecommendsthatanexplanationdialog
onlybeshownintheeventthattheuserhaspreviouslydeniedthepermissionandprovides
amethodtoidentifywhenthisisthecase.
AcalltotheshouldShowRequestPermissionRationale()methodoftheActivityCompat
classwillreturnatrueresultiftheuserhaspreviouslydeniedarequestforthespecified
permissionandafalseresultiftherequesthasnotpreviouslybeenmade.Inthecaseofa
trueresult,theappshoulddisplayadialogcontainingarationaleforneedingthe
permissionand,oncethedialoghasbeenreadanddismissedbytheuser,thepermission
requestshouldberepeated.
Toaddthisfunctionalitytotheexampleapp,modifytheonCreate()methodsothatit
readsasfollows:
.
.
importandroid.app.AlertDialog;
importandroid.content.DialogInterface;
.
.
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission_demo);
intpermission=ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO);
if(permission!=PackageManager.PERMISSION_GRANTED){
Log.i(TAG,“Permissiontorecorddenied”);
if(ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.RECORD_AUDIO)){
AlertDialog.Builderbuilder=new
AlertDialog.Builder(this);
builder.setMessage(“Permissiontoaccessthemicrophoneis
requiredforthisapptorecordaudio.”)
.setTitle(“Permissionrequired”);
builder.setPositiveButton(“OK”,new
DialogInterface.OnClickListener(){
publicvoidonClick(DialogInterfacedialog,intid){
Log.i(TAG,“Clicked”);
makeRequest();
}
});
AlertDialogdialog=builder.create();
dialog.show();
}else{
makeRequest();
}
}
}
Themethodstillcheckswhetherornotthepermissionhasbeengranted,butnowalso
identifieswhetherarationaleneedstobedisplayed.Iftheuserhaspreviouslydeniedthe
request,adialogisdisplayedcontaininganexplanationandanOKbuttononwhicha
listenerisconfiguredtocallthemakeRequest()methodwhenthebuttonistapped.Inthe
eventthatthepermissionrequesthasnotpreviouslybeenmade,thecodemovesdirectlyto
seekingpermission.
55.6TestingthePermissionsApp
OntheAndroid6deviceoremulatorsessiononwhichtestingisbeingperformed,launch
theSettingsapp,selecttheAppsoptionandscrolltoandselectthePermissionDemoapp.
Ontheappsettingsscreen,taptheuninstallbuttontoremovetheappfromthedevice.
Runtheapponceagainand,whenthepermissionrequestdialogappears,clickonthe
Denybutton.Runtheappasecondtimeandverifythattherationaledialogappears.Tap
theOKbuttonandwhenthepermissionrequestdialogappearstaptheAllowbutton.
ReturntotheSettingsapp,selecttheAppsoptionandselectthePermissionDemoapp
onceagainfromthelist.Oncethesettingsfortheapparelisted,verifythatthe
PermissionssectionliststheMicrophonepermission:
Figure55-4
55.7Summary
PriortotheintroductionofAndroid6.0theonlystepnecessaryforanapptorequest
permissiontoaccesscertainfunctionalitywastoaddanappropriatelinetothe
application’smanifestfile.Theuserwouldthenbepromptedtoapprovethepermissionat
thepointthattheappwasinstalled.Thisisstillthecaseformostpermissionswiththe
exceptionofasetofpermissionsthatareconsidereddangerous.Permissionsthatare
considereddangeroususuallyhavethepotentialtoallowanapptoviolatetheuser’s
privacysuchasallowingaccesstothemicrophone,contactslistorexternalstorage.
Asoutlinedinthischapter,Android6orlaterappsmustnowrequestdangerous
permissionapprovalfromtheuserwhentheapplaunchesinadditiontoincludingthe
permissionrequestinthemanifestfile.
56.AndroidAudioRecordingandPlayback
usingMediaPlayerandMediaRecorder
ThischapterwillprovideanoverviewoftheMediaRecorderclassandexplainthebasics
ofhowthisclasscanbeusedtorecordaudioorvideo.TheuseoftheMediaPlayerclassto
playbackaudiowillalsobecovered.Havingcoveredthebasics,anexampleapplication
willbecreatedtodemonstratethesetechniquesinaction.Inadditiontolookingataudio
andvideohandling,thischapterwillalsotouchonthesubjectsofsavingfilestotheSD
cardandthestepsinvolvedindetectingwhetherornotadevicehasamicrophoneor
camera.
56.1PlayingAudio
Intermsofaudioplayback,mostimplementationsofAndroidsupportAACLC/LTP,HEAACv1(AAC+),HE-AACv2(enhancedAAC+),AMR-NB,AMR-WB,MP3,MIDI,Ogg
Vorbis,andPCM/WAVEformats.
AudioplaybackcanbeperformedusingeithertheMediaPlayerortheAudioTrackclasses.
AudioTrackisamoreadvancedoptionthatusesstreamingaudiobuffersandprovides
greatercontrolovertheaudio.TheMediaPlayerclass,ontheotherhand,providesan
easierprogramminginterfaceforimplementingaudioplaybackandwillmeettheneedsof
mostaudiorequirements.
TheMediaPlayerclasshasassociatedwithitarangeofmethodsthatcanbecalledbyan
applicationtoperformcertaintasks.Asubsetofsomeofthekeymethodsofthisclassis
asfollows:
· create()–Calledtocreateanewinstanceoftheclass,passingthroughtheUriofthe
audiotobeplayed.
· setDataSource()–Setsthesourcefromwhichtheaudioistoplay.
· prepare()–Instructstheplayertopreparetobeginplayback.
· start()–Startstheplayback.
· pause()–Pausestheplayback.Playbackmayberesumedviaacalltotheresume()
method.
· stop()–Stopsplayback.
· setVolume()–Takestwofloating-pointargumentsspecifyingtheplaybackvolumefor
theleftandrightchannels.
· resume()–Resumesapreviouslypausedplaybacksession.
· reset()–Resetsthestateofthemediaplayerinstance.Essentiallysetstheinstance
backtotheuninitializedstate.Ataminimum,aresetplayerwillneedtohavethedata
sourcesetagainandtheprepare()methodcalled.
· release()–Tobecalledwhentheplayerinstanceisnolongerneeded.Thismethod
ensuresthatanyresourcesheldbytheplayerarereleased.
Inatypicalimplementation,anapplicationwillinstantiateaninstanceoftheMediaPlayer
class,setthesourceoftheaudiotobeplayedandthencallprepare()followedbystart().
Forexample:
MediaPlayermediaPlayer=newMediaPlayer();
mediaPlayer.setDataSource(“http://www.ebookfrenzy.com/myaudio.mp3”);
mediaPlayer.prepare();
mediaPlayer.start();
56.2RecordingAudioandVideousingtheMediaRecorderClass
Aswithaudioplayback,recordingcanbeperformedusinganumberofdifferent
techniques.OneoptionistousetheMediaRecorderclass,which,aswiththeMediaPlayer
class,providesanumberofmethodsthatareusedtorecordaudio:
- setAudioSource()–Specifiesthesourceoftheaudiotoberecorded(typicallythis
willbeMediaRecorder.AudioSource.MICforthedevicemicrophone).
- setVideoSource()–Specifiesthesourceofthevideotoberecorded(forexample
MediaRecorder.VideoSource.CAMERA).
- setOutputFormat()–Specifiestheformatintowhichtherecordedaudioorvideois
tobestored(forexampleMediaRecorder.OutputFormat.AAC_ADTS).
- setAudioEncoder()–Specifiestheaudioencodertobeusedfortherecordedaudio
(forexampleMediaRecorder.AudioEncoder.AAC).
- setOutputFile()–Configuresthepathtothefileintowhichtherecordedaudioor
videoistobestored.
- prepare()–PreparestheMediaRecorderinstancetobeginrecording.
- start()-Beginstherecordingprocess.
- stop()–Stopstherecordingprocess.Oncearecorderhasbeenstopped,itwillneedto
becompletelyreconfiguredandpreparedbeforebeingrestarted.
- reset()–Resetstherecorder.Theinstancewillneedtobecompletelyreconfiguredand
preparedbeforebeingrestarted.
- release()–Shouldbecalledwhentherecorderinstanceisnolongerneeded.This
methodensuresallresourcesheldbytheinstancearereleased.
Atypicalimplementationusingthisclasswillsetthesource,outputandencodingformat
andoutputfile.Callswillthenbemadetotheprepare()andstart()methods.Thestop()
methodwillthenbecalledwhenrecordingistoendfollowedbythereset()method.When
theapplicationnolongerneedstherecorderinstanceacalltotherelease()methodis
recommended:
MediaRecordermediaRecorder=newMediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setOutputFile(audioFilePath);
mediaRecorder.prepare();
mediaRecorder.start();
.
.
.
mediaRecorder.stop()
mediaRecorder.reset()
mediaRecorder.release()
Inordertorecordaudio,themanifestfilefortheapplicationmustincludethe
android.permission.RECORD_AUDIOpermission:
<uses-permissionandroid:name=“android.permission.RECORD_AUDIO”/>
AsoutlinedinthechapterentitledMakingRuntimePermissionRequestsinAndroid6.0,
accesstothemicrophonefallsintothecategoryofdangerouspermissions.Tosupport
Android6,therefore,aspecificrequestformicrophoneaccessmustalsobemadewhen
theapplicationlaunches,thestepsforwhichwillbecoveredlaterinthischapter.
56.3AbouttheExampleProject
Theremainderofthischapterwillworkthroughthecreationofanexampleapplication
intendedtodemonstratetheuseoftheMediaPlayerandMediaRecorderclassesto
implementtherecordingandplaybackofaudioonanAndroiddevice.
Whendevelopingapplicationsthatmakeuseofspecifichardwarefeatures,the
microphonebeingacaseinpoint,itisimportanttochecktheavailabilityofthefeature
beforeattemptingtoaccessitintheapplicationcode.Theapplicationcreatedinthis
chapterwill,therefore,alsodemonstratethestepsinvolvedindetectingthepresenceor
otherwiseofamicrophoneonthedevice.
Oncecompleted,thisapplicationwillprovideaverysimpleinterfaceintendedtoallowthe
usertorecordandplaybackaudio.Therecordedaudiowillneedtobestoredwithinan
audiofileonthedevice.Thatbeingthecase,thistutorialwillalsobrieflyexplorethe
mechanismforusingSDCardstorage.
56.4CreatingtheAudioAppProject
CreateanewprojectinAndroidStudio,enteringAudioAppintotheApplicationname
fieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI8:Android2.2(Froyo).Continuetoproceedthroughthescreens,requesting
thecreationofanemptyactivitynamedAudioAppActivitywithacorrespondinglayout
resourcefilenamedactivity_audio_app.
56.5DesigningtheUserInterface
Oncethenewprojecthasbeencreated,selecttheactivity_audio_app.xmlfilefromthe
Projecttoolwindowand,withtheDesignertoolinDesignmode,selectthe“Helloworld!”
TextViewanddeleteitfromthelayout.DraganddropthreeButtonviewsontothelayout.
Thepositioningofthebuttonsisnotofparamountimportancetothisexample,though
Figure56-1showsasuggestedlayout.
ConfigurethebuttonstodisplaystringresourcesthatreadPlay,RecordandStopandgive
themviewIDsofrecordButton,playButton,andstopButtonrespectively.
Figure56-1
SelectthePlaybuttonand,withinthePropertiespanel,configuretheonClickpropertyto
callamethodnamedplayAudiowhenselectedbytheuser.Repeatthesestepstoconfigure
theremainingbuttonstocallmethodsnamedrecordAudioandstopAudiorespectively.
56.6CheckingforMicrophoneAvailability
AttemptingtorecordaudioonadevicewithoutamicrophonewillcausetheAndroid
systemtothrowanexception.Itisvital,therefore,thatthecodecheckforthepresenceof
amicrophonebeforemakingsuchanattempt.Thereareanumberofwaysofdoingthis
includingcheckingforthephysicalpresenceofthedevice.Aneasierapproach,andone
thatismorelikelytoworkondifferentAndroiddevices,istoasktheAndroidsystemifit
hasapackageinstalledforaparticularfeature.Thisinvolvescreatinganinstanceofthe
AndroidPackageManagerclassandthenmakingacalltotheobject’shasSystemFeature()
method.Inthiscase,thefeatureofinterestis
PackageManager.FEATURE_MICROPHONE.
Forthepurposesofthisexample,wewillcreateamethodnamedhasMicrophone()that
maybecalledupontocheckforthepresenceofamicrophone.WithintheProjecttool
window,locateanddoubleclickontheAudioAppActivity.javafileandmodifyittoadd
thismethod:
packagecom.ebookfrenzy.audioapp;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.content.pm.PackageManager;
publicclassAudioAppActivityextendsAppCompatActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_app);
}
protectedbooleanhasMicrophone(){
PackageManagerpmanager=this.getPackageManager();
returnpmanager.hasSystemFeature(
PackageManager.FEATURE_MICROPHONE);
}
.
.
}
56.7PerformingtheActivityInitialization
ThenextstepistomodifytheonCreate()methodoftheactivitytoperformanumberof
initializationtasks.RemainingwithintheAudioAppActivity.javafile,modifythemethod
asfollows:
packagecom.ebookfrenzy.audioapp;
importjava.io.IOException;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.content.pm.PackageManager;
importandroid.media.MediaRecorder;
importandroid.os.Environment;
importandroid.widget.Button;
importandroid.view.View;
importandroid.media.MediaPlayer;
publicclassAudioAppActivityextendsAppCompatActivity{
privatestaticMediaRecordermediaRecorder;
privatestaticMediaPlayermediaPlayer;
privatestaticStringaudioFilePath;
privatestaticButtonstopButton;
privatestaticButtonplayButton;
privatestaticButtonrecordButton;
privatebooleanisRecording=false;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_app);
recordButton=
(Button)findViewById(R.id.recordButton);
playButton=(Button)findViewById(R.id.playButton);
stopButton=(Button)findViewById(R.id.stopButton);
if(!hasMicrophone())
{
stopButton.setEnabled(false);
playButton.setEnabled(false);
recordButton.setEnabled(false);
}else{
playButton.setEnabled(false);
stopButton.setEnabled(false);
}
audioFilePath=
Environment.getExternalStorageDirectory().getAbsolutePath()
+“/myaudio.3gp”;
}
.
.
}
Theaddedcodebeginsbyobtainingreferencestothethreebuttonviewsintheuser
interface.Next,thepreviouslyimplementedhasMicrophone()methodiscalledto
ascertainwhetherthedeviceincludesamicrophone.Ifitdoesnot,allthebuttonsare
disabled,otherwiseonlytheStopandPlaybuttonsaredisabled.
Thenextlineofcodeneedsalittlemoreexplanation:
audioFilePath=
Environment.getExternalStorageDirectory().getAbsolutePath()
+“/myaudio.3gp”;
ThepurposeofthiscodeistoidentifythelocationoftheSDcardstorageonthedevice
andtousethattocreateapathtoafilenamedmyaudio.accintowhichtheaudiorecording
willbestored.ThepathoftheSDcard(whichisreferredtoasexternalstorageeven
thoughitisinternaltothedeviceonmanyAndroiddevices)isobtainedviaacalltothe
getExternalStorageDirectory()methodoftheAndroidEnvironmentclass.
Whenworkingwithexternalstorageitisimportanttobeawarethatsuchactivitybyan
applicationrequirespermissiontoberequestedintheapplicationmanifestfile.For
example:
<uses-permission
android:name=“android.permission.WRITE_EXTERNAL_STORAGE”/>
56.8ImplementingtherecordAudio()Method
WhentheusertouchestheRecordbutton,therecordAudio()methodwillbecalled.This
methodwillneedtoenableanddisabletheappropriatebuttons,configurethe
MediaRecorderinstancewithinformationaboutthesourceoftheaudio,theoutputformat
andencodingandthelocationofthefileintowhichtheaudioistobestored.Finally,the
prepare()andstart()methodsoftheMediaRecorderobjectwillneedtobecalled.
Combined,theserequirementsresultinthefollowingmethodimplementationinthe
AudioAppActivity.javafile:
publicvoidrecordAudio(Viewview)throwsIOException
{
isRecording=true;
stopButton.setEnabled(true);
playButton.setEnabled(false);
recordButton.setEnabled(false);
try{
mediaRecorder=newMediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mediaRecorder.setOutputFile(audioFilePath);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.prepare();
}catch(Exceptione){
e.printStackTrace();
}
mediaRecorder.start();
}
56.9ImplementingthestopAudio()Method
ThestopAudio()methodisresponsibleforenablingthePlaybutton,disablingtheStop
buttonandthenstoppingandresettingtheMediaRecorderinstance.Thecodetoachieve
thisreadsasoutlinedinthefollowinglistingandshouldbeaddedtothe
AudioAppAcitivy.javafile:
publicvoidstopAudio(Viewview)
{
stopButton.setEnabled(false);
playButton.setEnabled(true);
if(isRecording)
{
recordButton.setEnabled(false);
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder=null;
isRecording=false;
}else{
mediaPlayer.release();
mediaPlayer=null;
recordButton.setEnabled(true);
}
}
56.10ImplementingtheplayAudio()method
TheplayAudio()methodwillsimplycreateanewMediaPlayerinstance,assigntheaudio
filelocatedontheSDcardasthedatasourceandthenprepareandstarttheplayback:
publicvoidplayAudio(Viewview)throwsIOException
{
playButton.setEnabled(false);
recordButton.setEnabled(false);
stopButton.setEnabled(true);
mediaPlayer=newMediaPlayer();
mediaPlayer.setDataSource(audioFilePath);
mediaPlayer.prepare();
mediaPlayer.start();
}
56.11ConfiguringandRequestingPermissions
Beforetestingtheapplication,itisessentialthattheappropriatepermissionsberequested
withinthemanifestfilefortheapplication.Specifically,theapplicationwillrequire
permissiontorecordaudioandtoaccesstheexternalstorage(SDcard).WithintheProject
toolwindow,locateanddoubleclickontheAndroidManifest.xmlfiletoloaditintothe
editorandmodifytheXMLtoaddthetwopermissiontags:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.audioapp”>
<uses-permissionandroid:name=
“android.permission.WRITE_EXTERNAL_STORAGE”/>
<uses-permissionandroid:name=“android.permission.RECORD_AUDIO”/>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activityandroid:name=”.AudioAppActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=
“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Theabovestepswillbeadequatetoensurethattheuserenablesthesepermissionswhen
theappisinstalledondevicesrunningversionsofAndroidpre-datingAndroid6.0.Both
microphoneandexternalstorageaccessarecategorizedinAndroidasbeingdangerous
permissionsbecausetheygivetheappthepotentialtocompromisetheuser’sprivacy.In
orderfortheexampleapptofunctiononAndroid6orlaterdevices,therefore,codeneeds
tobeaddedtospecificallyrequestthesetwopermissionsatappruntime.
EdittheAudioAppActivity.javafileandbeginbyaddingsomeadditionalimportdirectives
andconstantstoactasrequestidentificationcodesforthepermissionsbeingrequested:
packagecom.ebookfrenzy.audioapp;
importjava.io.IOException;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.content.pm.PackageManager;
importandroid.media.MediaRecorder;
importandroid.os.Environment;
importandroid.widget.Button;
importandroid.view.View;
importandroid.media.MediaPlayer;
importandroid.widget.Toast;
importandroid.support.v4.content.ContextCompat;
importandroid.Manifest;
importandroid.support.v4.app.ActivityCompat;
publicclassAudioAppActivityextendsAppCompatActivity{
privatestaticfinalintRECORD_REQUEST_CODE=101;
privatestaticfinalintSTORAGE_REQUEST_CODE=102;
privatestaticMediaRecordermediaRecorder;
privatestaticMediaPlayermediaPlayer;
.
.
.
Next,amethodneedstobeaddedtotheclass,thepurposeofwhichistotakeas
argumentsthepermissiontoberequestedandthecorrespondingrequestidentification
code.RemainingwiththeAudioAppActivity.javaclassfile,implementthismethodas
follows:
protectedvoidrequestPermission(StringpermissionType,int
requestCode){
intpermission=ContextCompat.checkSelfPermission(this,
permissionType);
if(permission!=PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,
newString[]{permissionType},requestCode
);
}
}
UsingthestepsoutlinedintheMakingRuntimePermissionRequestsinAndroid6.0
chapterofthisbook,theabovemethodverifiesthatthespecifiedpermissionhasnot
alreadybeengrantedbeforemakingtherequest,passingthroughtheidentificationcodeas
anargument.
Whentherequesthasbeenhandled,theonRequestPermissionsResult()methodwillbe
calledontheactivity,passingthroughtheidentificationcodeandtheresultsoftherequest.
Thenextstep,therefore,istoimplementthismethodwithintheAudioAppActivity.javafile
asfollows:
@Override
publicvoidonRequestPermissionsResult(intrequestCode,
Stringpermissions[],int[]grantResults){
switch(requestCode){
caseRECORD_REQUEST_CODE:{
if(grantResults.length==0
||grantResults[0]!=
PackageManager.PERMISSION_GRANTED){
recordButton.setEnabled(false);
Toast.makeText(this,
“Recordpermissionrequired”,
Toast.LENGTH_LONG).show();
}
return;
}
caseSTORAGE_REQUEST_CODE:{
if(grantResults.length==0
||grantResults[0]!=
PackageManager.PERMISSION_GRANTED){
recordButton.setEnabled(false);
Toast.makeText(this,
“ExternalStoragepermissionrequired”,
Toast.LENGTH_LONG).show();
}
return;
}
}
}
Theabovecodecheckstherequestidentifiercodetoidentifywhichpermissionrequest
hasreturnedbeforecheckingwhetherornotthecorrespondingpermissionwasgranted.In
theeventthatpermissionwasdenied,amessageisdisplayedtotheuserindicatingtheapp
willnotfunction.Inbothinstances,therecordbuttonisalsodisabled.
AllthatremainspriortotestingtheappistocallthenewlyaddedrequestPermission()
methodforeachoftherequiredpermissionswhentheapplaunches.Remaininginthe
AudioAppActivity.javafile,modifytheonCreate()methodasfollows:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_app);
recordButton=(Button)findViewById(R.id.recordButton);
playButton=(Button)findViewById(R.id.playButton);
stopButton=(Button)findViewById(R.id.stopButton);
if(!hasMicrophone())
{
stopButton.setEnabled(false);
playButton.setEnabled(false);
recordButton.setEnabled(false);
}else{
playButton.setEnabled(false);
stopButton.setEnabled(false);
}
audioFilePath=
Environment.getExternalStorageDirectory().getAbsolutePath()
+“/myaudio.3gp”;
requestPermission(Manifest.permission.RECORD_AUDIO,
RECORD_REQUEST_CODE);
requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
STORAGE_REQUEST_CODE);
}
56.12TestingtheApplication
CompileandruntheapplicationonanAndroiddevicecontainingamicrophoneandtouch
theRecordbutton.Afterrecording,touchStopfollowedbyPlay,atwhichpointthe
recordedaudioshouldplaybackthroughthedevicespeakers.IfrunningonAndroid6.0or
laternotethattheapprequestspermissiontousetheexternalstorageandtorecordaudio
whenfirstlaunched.
56.13Summary
TheAndroidSDKprovidesanumberofmechanismsfortheimplementationofaudio
recordingandplayback.Thischapterhaslookedattwooftheseintheformofthe
MediaPlayerandMediaRecorderclasses.Havingcoveredthetheoryofusingthese
techniques,thischapterworkedthroughthecreationofanexampleapplicationdesigned
torecordandthenplaybackaudio.InthecourseofworkingwithaudioinAndroid,this
chapteralsolookedatthestepsinvolvedinensuringthatthedeviceonwhichthe
applicationisrunninghasamicrophonebeforeattemptingtorecordaudio.Theuseof
externalstorageintheformofanSDcardwasalsocovered.
57.WorkingwiththeGoogleMapsAndroid
APIinAndroidStudio
WhenGoogledecidedtointroduceamapservicemanyyearsago,itishardtosaywhether
ornottheyeveranticipatedhavingaversionavailableforintegrationintomobile
applications.WhenthefirstwebbasedversionofwhatwouldeventuallybecalledGoogle
Mapswasintroducedin2005,theiPhonehadyettoignitethesmartphonerevolutionand
thecompanythatwasdevelopingtheAndroidoperatingsystemwouldnotbeacquiredby
Googleforanothersixmonths.WhateveraspirationsGooglehadforthefutureofGoogle
Maps,itisremarkabletoconsiderthatallofthepowerofGoogleMapscannowbe
accesseddirectlyviaAndroidapplicationsusingtheGoogleMapsAndroidAPI.
ThischapterisintendedtoprovideanoverviewoftheGoogleMapssystemandGoogle
MapsAndroidAPI.Thechapterwillprovideanoverviewofthedifferentelementsthat
makeuptheAPI,detailthestepsnecessarytoconfigureadevelopmentenvironmentto
workwithGoogleMapsandthenworkthroughsomecodeexamplesdemonstratingsome
ofthebasicsofGoogleMapsAndroidintegration.
57.1TheElementsoftheGoogleMapsAndroidAPI
TheGoogleMapsAndroidAPIconsistsofacoresetofclassesthatcombinetoprovide
mappingcapabilitiesinAndroidapplications.Thekeyelementsofamapareasfollows:
· GoogleMap–ThemainclassoftheGoogleMapsAndroidAPI.Thisclassis
responsiblefordownloadinganddisplayingmaptilesandfordisplayingand
respondingtomapcontrols.TheGoogleMapobjectisnotcreateddirectlybythe
applicationbutisinsteadcreatedwhenMapVieworMapFragmentinstancesare
created.AreferencetotheGoogleMapobjectcanbeobtainedwithinapplicationcode
viaacalltothegetMap()methodofaMapView,MapFragmentor
SupportMapFragmentinstance.
· MapView-AsubclassoftheViewclass,thisclassprovidestheviewcanvasonto
whichthemapisdrawnbytheGoogleMapobject,allowingamaptobeplacedinthe
userinterfacelayoutofanactivity.
· SupportMapFragment–AsubclassoftheFragmentclass,thisclassallowsamapto
beplacedwithinaFragmentinanAndroidlayout.
· Marker–ThepurposeoftheMarkerclassistoallowlocationstobemarkedona
map.MarkersareaddedtoamapbyobtainingareferencetotheGoogleMapobject
associatedwithamapandthenmakingacalltotheaddMarker()methodofthatobject
instance.ThepositionofamarkerisdefinedviaLongitudeandLatitude.Markerscan
beconfiguredinanumberofways,includingspecifyingatitle,textandanicon.
Markersmayalsobemadetobe“draggable”,allowingtheusertomovethemarkerto
differentpositionsonamap.
· Shapes–Thedrawingoflinesandshapesonamapisachievedthroughtheuseofthe
Polyline,PolygonandCircleclasses.
· UiSettings–TheUiSettingsclassprovidesalevelofcontrolfromwithinan
applicationofwhichuserinterfacecontrolsappearonamap.Usingthisclass,for
example,theapplicationcancontrolwhetherornotthezoom,currentlocationand
compasscontrolsappearonamap.Thisclasscanalsobeusedtoconfigurewhich
touchscreengesturesarerecognizedbythemap.
· MyLocationLayer–Whenenabled,theMyLocationLayerdisplaysabuttononthe
mapwhich,whenselectedbytheuser,centersthemapontheuser’scurrent
geographicallocation.Iftheuserisstationary,thislocationisrepresentedonthemap
byabluemarker.Iftheuserisinmotionthelocationisrepresentedbyachevron
indicatingtheuser’sdirectionoftravel.
ThebestwaytogainfamiliaritywiththeGoogleMapsAndroidAPIistoworkthroughan
example.TheremainderofthischapterwillcreateasimpleGoogleMapsbased
applicationwhilehighlightingthekeyareasoftheAPI.
57.2CreatingtheGoogleMapsProject
CreateanewprojectinAndroidStudio,enteringMapDemointotheApplicationname
fieldandcom.ebookfrenzyastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI19:Android4.4(KitKat).Continuetoproceedthroughthescreens,
requestingthecreationofaGoogleMapsActivitynamedMapDemoActivitywitha
correspondinglayoutnamedactivity_map_demo.
57.3ObtainingYourDeveloperSignature
BeforeanapplicationcanmakeuseoftheGoogleMapsAndroidAPI,itmustfirstbe
registeredwithintheGoogleAPIsConsole.Beforeanapplicationcanberegistered,
however,thedevelopersignature(alsoreferredtoastheSHA-1fingerprint)associated
withyourdevelopmentenvironmentmustbeidentified.Thisiscontainedinakeystorefile
locatedinthe.androidsubdirectoryofyourhomedirectoryandmaybeobtainedusingthe
keytoolutilityprovidedaspartoftheJavaSDKasoutlinedbelow.Inordertomakethe
processeasier,however,AndroidStudioaddssomeadditionalfilestotheprojectwhenthe
GoogleMapsActivityoptionisselectedduringtheprojectcreationprocess.Oneofthese
filesisnamedgoogle_maps_api.xmlandislocatedintheapp->res->valuesfolderof
theproject.
Containedwithinthegoogle_maps_api.xmlfileisalinktotheGoogleDeveloperconsole.
Copyandpastethislinkintoabrowserwindow.Onceloaded,apagesimilartothe
followingwillappear:
Figure57-1
VerifythatthemenuissettoCreateanewprojectbeforeclickingontheContinuebutton.
OncetheAPIhasbeenenabled,clickontheGotoCredentialsbutton.Afterashortdelay,
thenewprojectwillbecreatedandapanelwillappear(Figure57-2)providingtheoption
tocreateanAndroidkeyfortheapplication.
Figure57-2
CheckthattheSHA-1fingerprintandapplicationpackagenamematchthoselistedinthe
google_maps_api.xmlfileandclickontheCreatebuttontogeneratetheAPIkeyforyour
application.AdialogwindowwillsubsequentlyappearcontainingtheAPIkeyfortheapp.
Copythiskey,returntoAndroidStudioandpastetheAPIkeyintothe
YOUR_KEY_HEREsectionofthefile:
<stringname=“google_maps_key”templateMergeStrategy=“preserve”>
YOUR_KEY_HERE
</string>
57.4TestingtheApplication
PerformatestrunoftheapplicationtoverifythattheAPIkeyiscorrectlyconfigured.
Assumingthattheconfigurationiscorrect,theapplicationwillrunanddisplayamapon
thescreen.
Intheeventthatamapisnotdisplayed,checkthefollowingareas:
· Iftheapplicationisrunningonanemulator,makesurethattheemulatorisrunninga
versionofAndroidthatincludestheGoogleAPIs.Thecurrentoperatingsystemcanbe
changedforanAVDconfigurationbyselectingtheTools->Android->AVDManager
menuoption,clickingonthepenciliconintheActionscolumnoftheAVDfollowedby
theChange…buttonnexttothecurrentAndroidversion.Withinthesystemimage
dialog,selectatargetwhichincludestheGoogleAPIs.
· ChecktheLogCatoutputforanyareasrelatingtoauthenticationproblemswithregard
totheGoogleMapsAPI.ThisusuallymeanstheAPIkeywasenteredincorrectlyor
thattheapplicationpackagenamedoesnotmatchthatspecifiedwhentheAPIkeywas
generated.
· VerifywithintheGoogleAPIConsolethattheGoogleMapsAndroidAPIhasbeen
enabledintheServicespanel.
57.5UnderstandingGeocodingandReverseGeocoding
Itisimpossibletotalkaboutmapsandgeographicallocationswithoutfirstcoveringthe
subjectofGeocoding.Geocodingcanbestbedescribedastheprocessofconvertinga
textualbasedgeographicallocation(suchasastreetaddress)intogeographical
coordinatesexpressedintermsoflongitudeandlatitude.
GeocodingcanbeachievedusingtheAndroidGeocoderclass.Aninstanceofthe
Geocoderclasscan,forexample,bepassedastringrepresentingalocationsuchasacity
name,streetaddressorairportcode.TheGeocoderwillattempttofindamatchforthe
locationandreturnalistofAddressobjectsthatpotentiallymatchthelocationstring,
rankedinorderwiththeclosestmatchatposition0inthelist.Avarietyofinformationcan
thenbeextractedfromtheAddressobjects,includingthelongitudeandlatitudeofthe
potentialmatches.
Thefollowingcode,forexample,requeststhelocationoftheNationalAirandSpace
MuseuminWashington,D.C.:
importjava.io.IOException;
importjava.util.List;
importandroid.location.Address;
importandroid.location.Geocoder;
.
.
.
doublelatitude;
doublelongitude;
List<Address>geocodeMatches=null;
try{
geocodeMatches=
newGeocoder(this).getFromLocationName(
“600IndependenceAveSW,Washington,DC20560”,1);
}catch(IOExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
if(!geocodeMatches.isEmpty())
{
latitude=geocodeMatches.get(0).getLatitude();
longitude=geocodeMatches.get(0).getLongitude();
}
Notethatthevalueof1ispassedthroughasthesecondargumenttothe
getFromLocationName()method.ThissimplytellstheGeocodertoreturnonlyoneresult
inthearray.Giventhespecificnatureoftheaddressprovided,thereshouldonlybeone
potentialmatch.Formorevaguelocationnames,however,itmaybenecessarytorequest
morepotentialmatchesandallowtheusertochoosethecorrectone.
Theabovecodeisanexampleofforward-geocodinginthatcoordinatesarecalculated
basedonatextlocationdescription.Reverse-geocoding,asthenamesuggests,involves
thetranslationofgeographicalcoordinatesintoahumanreadableaddressstring.Consider,
forexample,thefollowingcode:
importjava.io.IOException;
importjava.util.List;
importandroid.location.Address;
importandroid.location.Geocoder;
.
.
.
List<Address>geocodeMatches=null;
StringAddress1;
StringAddress2;
StringState;
StringZipcode;
StringCountry;
try{
geocodeMatches=
newGeocoder(this).getFromLocation(38.8874245,-77.0200729,
1);
}catch(IOExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
if(!geocodeMatches.isEmpty())
{
Address1=geocodeMatches.get(0).getAddressLine(0);
Address2=geocodeMatches.get(0).getAddressLine(1);
State=geocodeMatches.get(0).getAdminArea();
Zipcode=geocodeMatches.get(0).getPostalCode();
Country=geocodeMatches.get(0).getCountryName();
}
InthiscasetheGeocoderobjectisinitializedwithlatitudeandlongitudevaluesviathe
getFromLocation()method.Onceagain,onlyasinglematchingresultisrequested.The
textbasedaddressinformationisthenextractedfromtheresultingAddressobject.
ItshouldbenotedthatthegeocodingisnotactuallyperformedontheAndroiddevice,but
ratheronaservertowhichthedeviceconnectswhenatranslationisrequiredandthe
resultssubsequentlyreturnedwhenthetranslationiscomplete.Assuch,geocodingcan
onlytakeplacewhenthedevicehasanactiveinternetconnection.
57.6AddingaMaptoanApplication
Thesimplestwaytoaddamaptoanapplicationistospecifyitintheuserinterfacelayout
XMLfileforanactivity.Thefollowingexamplelayoutfileshowsthe
SupportMapFragmentinstanceaddedtotheactivity_map_demo.xmlfilecreatedby
AndroidStudio:
<fragmentxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:id=”@+id/map”
tools:context=”.MapDemoActivity”
android:name=“com.google.android.gms.maps.SupportMapFragment”/>
57.7RequestingCurrentLocationPermission
AsoutlinedinthechapterentitledMakingRuntimePermissionRequestsinAndroid6.0,
certainpermissionsarecategorizedasbeingdangerousandrequirespecialhandingfor
Android6.0orlater.Onesuchpermissiongivesapplicationstheabilitytoidentifythe
user’scurrentlocation.Bydefault,AndroidStudiohasplacedalocationpermission
requestwithintheAndroidManifest.xml.Locatethisfilelocatedunderapp->manifestsin
theProjecttoolwindowandlocatethefollowingpermissionline:
<uses-permission
android:name=“android.permission.ACCESS_FINE_LOCATION”/>
Thiswillensurethattheappisgiventheopportunitytoprovidepermissionfortheappto
obtainlocationinformationatthepointthattheappisinstalledonolderversionsof
Android,buttofullysupportAndroid6.0orlater,theappmustalsospecificallyrequest
thispermissionatruntime.Toachievethis,somecodeneedstobeaddedtothe
MapDemoActivity.javafile.
Beginbyaddingsomeimportdirectivesandaconstanttoactasthepermissionrequest
code:
packagecom.ebookfrenzy.mapdemo;
importandroid.content.pm.PackageManager;
importandroid.support.v4.app.FragmentActivity;
importandroid.os.Bundle;
importandroid.support.v4.content.ContextCompat;
importandroid.support.v4.app.ActivityCompat;
importandroid.Manifest;
importandroid.widget.Toast;
importandroid.content.pm.PackageManager;
importcom.google.android.gms.maps.CameraUpdateFactory;
importcom.google.android.gms.maps.GoogleMap;
importcom.google.android.gms.maps.OnMapReadyCallback;
importcom.google.android.gms.maps.SupportMapFragment;
importcom.google.android.gms.maps.model.LatLng;
importcom.google.android.gms.maps.model.MarkerOptions;
publicclassMapDemoActivityextendsFragmentActivityimplements
OnMapReadyCallback{
privatestaticfinalintLOCATION_REQUEST_CODE=101;
privateStringTAG=“MapDemo”;
privateGoogleMapmMap;
.
.
}
Next,amethodneedstobeaddedtotheclass,thepurposeofwhichistorequestthe
locationpermission.RemainingwithintheMapDemoActivity.javaclassfile,implement
thismethodasfollows:
protectedvoidrequestPermission(StringpermissionType,int
requestCode){
intpermission=ContextCompat.checkSelfPermission(this,
permissionType);
if(permission!=PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,
newString[]{permissionType},requestCode
);
}
}
Whentheuserhasrespondedtothepermissionrequest,theonRequestPermissionsResult()
methodwillbecalledontheactivity.RemainingintheMapDemoActivity.javafile,
implementthismethodnowsothatitreadsasfollows:
@Override
publicvoidonRequestPermissionsResult(intrequestCode,
Stringpermissions[],int[]
grantResults){
switch(requestCode){
caseLOCATION_REQUEST_CODE:{
if(grantResults.length==0
||grantResults[0]!=PackageManager.PERMISSION_GRANTED)
{
Toast.makeText(this,“Unabletoshowlocationpermissionrequired”,Toast.LENGTH_LONG).show();
}
return;
}
}
}
Finally,addacalltotherequestPermission()methodwithintheonCreate()method:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map_demo);
requestPermission(Manifest.permission.ACCESS_FINE_LOCATION,
LOCATION_REQUEST_CODE);
SupportMapFragmentmapFragment=
(SupportMapFragment)getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
}
CompileandruntheapponadeviceoremulatorsessionrunningAndroid6.0orlaterand
tapontheAllowoptionwhenthepermissionrequestdialogappears:
Figure57-3
57.8DisplayingtheUser’sCurrentLocation
Theuser’scurrentlocationmaybedisplayedonthemapbyobtainingareferencetothe
GoogleMapobjectassociatedwiththedisplayedmapandcallingthe
setMyLocationEnabled()methodofthatinstance,passingthroughavalueoftrue.
Whenthemapisreadytodisplay,theonMapReady()methodoftheactivityiscalled.By
default,AndroidStudiohasimplementedthismethodandaddedsomecodetoorientthe
mapoverAustraliawithamarkerpositionedoverthecityofSidney.Locateandeditthe
onMapReady()methodintheMapDemoActivty.javafiletoremovethistemplatecodeand
toenabledisplayoftheuser’scurrentlocation:
@Override
publicvoidonMapReady(GoogleMapgoogleMap){
mMap=googleMap;
//AddamarkerinSydneyandmovethecamera
LatLngsydney=newLatLng(-34,151);
mMap.addMarker(newMarkerOptions().position(sydney).title(“Marker
inSydney”));
mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
if(mMap!=null){
mMap.setMyLocationEnabled(true);
}
}
Whentheappisnowrunabluedotwillappearonthemapindicatingthecurrentlocation
ofthedevice.
57.9ChangingtheMapType
Thetypeofmapdisplayedcanbemodifieddynamicallybymakingacalltothe
setMapType()methodofthecorrespondingGoogleMapobject,passingthroughoneofthe
followingvalues:
· GoogleMap.MAP_TYPE_NONE–Anemptygridwithnomappingtilesdisplayed.
· GoogleMap.MAP_TYPE_NORMAL–Thestandardviewconsistingoftheclassic
roadmap.
· GoogleMap.MAP_TYPE_SATELLITE–Displaysthesatelliteimageryofthemap
region.
· GoogleMap.MAP_TYPE_HYBRID–Displayssatelliteimagerywiththeroadmaps
superimposed.
· GoogleMap.MAP_TYPE_TERRAIN–Displaystopographicalinformationsuchas
contourlinesandcolors.
ThefollowingcodechangetotheonCreate()method,forexample,switchesamapto
Satellitemode:
publicvoidonMapReady(GoogleMapgoogleMap){
mMap=googleMap;
if(mMap!=null){
mMap.setMyLocationEnabled(true);
mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE);
}
}
Alternatively,themaptypemaybespecifiedintheXMLlayoutfileinwhichthemapis
embeddedusingthemap:mapTypepropertytogetherwithavalueofnone,normal,hybrid,
satelliteorterrain.Forexample:
<?xmlversion=“1.0”encoding=“utf-8”?>
<fragmentxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:map=“http://schemas.android.com/apk/res-auto”
android:id=”@+id/map”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
map:mapType=“hybrid”
android:name=“com.google.android.gms.maps.SupportMapFragment”/>
57.10DisplayingMapControlstotheUser
TheGoogleMapsAndroidAPIprovidesanumberofcontrolsthatmaybeoptionally
displayedtotheuserconsistingofzoominandoutbuttons,a“mylocation”buttonanda
compass.
Whetherornotthezoomandcompasscontrolsaredisplayedmaybecontrolledeither
programmaticallyorwithinthemapelementinXMLlayoutresources.Inorderto
configurethecontrolsprogrammatically,areferencetotheUiSettingsobjectassociated
withtheGoogleMapobjectmustbeobtained:
importcom.google.android.gms.maps.UiSettings;
.
.
.
UiSettingsmapSettings;
mapSettings=mMap.getUiSettings();
ThezoomcontrolsareenabledanddisabledviacallstothesetZoomControlsEnabled()
methodoftheUiSettingsobject.Forexample:
mapSettings.setZoomControlsEnabled(true);
Alternatively,themap:uiZoomControlspropertymaybesetwithinthemapelementofthe
XMLresourcefile:
map:uiZoomControls=“false”
ThecompassmaybedisplayedeitherviaacalltothesetCompassEnabled()methodofthe
UiSettingsinstance,orthroughXMLresourcesusingthemap:uiCompassproperty.Note
thecompassicononlyappearswhenthemapcameraistiltedorrotatedawayfromthe
defaultorientation.
The“MyLocation”buttonwillonlyappearwhenMyLocationmodeisenabledas
outlinedearlierinthischapter.Thebuttonmaybepreventedfromappearingevenwhenin
thismodeviaacalltothesetMyLocationButtonEnabled()methodoftheUiSettings
instance.
57.11HandlingMapGestureInteraction
TheGoogleMapsAndroidAPIiscapableofrespondingtoanumberofdifferentuser
interactions.Theseinteractionscanbeusedtochangetheareaofthemapdisplayed,the
zoomlevelandeventheangleofview(suchthata3Drepresentationofthemapareais
displayedforcertaincities).
57.11.1MapZoomingGestures
Supportforgesturesrelatingtozoominginandoutofamapmaybeenabledordisabled
usingthesetZoomControlsEnabled()methodoftheUiSettingsobjectassociatedwiththe
GoogleMapinstance.Forexample,thefollowingcodedisableszoomgesturesforour
examplemap:
UiSettingsmapSettings;
mapSettings=map.getUiSettings();
mapSettings.setZoomGesturesEnabled(false);
ThesameresultcanbeachievedwithinanXMLresourcefilebysettingthe
map:uiZoomGesturespropertytotrueorfalse.
Whenenabled,zoomingwilloccurwhentheusermakespinchinggesturesonthescreen.
Similarly,adoubletapwillzoominwhileatwofingertapwillzoomout.Onefinger
zoominggestures,ontheotherhand,areperformedbytappingtwicebutnotreleasingthe
secondtapandthenslidingthefingerupanddownonthescreentozoominandout
respectively.
57.11.2MapScrolling/PanningGestures
Ascrolling,orpanninggestureallowstheusertomovearoundthemapbydraggingthe
maparoundthescreenwithasinglefingermotion.Scrollinggesturesmaybeenabled
withincodeviaacalltothesetScrollGesturesEnabled()methodoftheUiSettingsinstance:
UiSettingsmapSettings;
mapSettings=mMap.getUiSettings();
mapSettings.setScrollGesturesEnabled(true);
Alternatively,scrollingonamapinstancemaybeenabledinanXMLresourcelayoutfile
usingthemap:uiScrollGesturesproperty.
57.11.3MapTiltGestures
Tiltgesturesallowtheusertotilttheangleofprojectionofthemapbyplacingtwofingers
onthescreenandmovingthemupanddowntoadjustthetiltangle.Tiltgesturesmaybe
enabledordisabledviaacalltothesetTiltGesturesEnabled()methodoftheUiSettings
instance,forexample:
UiSettingsmapSettings;
mapSettings=mMap.getUiSettings();
mapSettings.setTiltGesturesEnabled(true);
Tiltgesturesmayalsobeenabledanddisabledusingthemap:uiTiltGesturespropertyinan
XMLlayoutresourcefile.
57.11.4MapRotationGestures
Byplacingtwofingersonthescreenandrotatingtheminacircularmotion,theusermay
rotatetheorientationofamapwhenmaprotationgesturesareenabled.Thisgesture
supportisenabledanddisabledincodeviaacalltothesetRotateGesturesEnabled()
methodoftheUiSettingsinstance,forexample:
UiSettingsmapSettings;
mapSettings=mMap.getUiSettings();
mapSettings.setRotateGesturesEnabled(true);
Rotationgesturesmayalsobeenabledordisabledusingthemap:uiRotateGestures
propertyinanXMLlayoutresourcefile.
57.12CreatingMapMarkers
Markersareusedtonotifytheuseroflocationsonamapandtaketheformofeithera
standardorcustomicon.Markersmayalsoincludeatitleandoptionaltext(referredtoas
asnippet)andmaybeconfiguredsuchthattheycanbedraggedtodifferentlocationson
themapbytheuser.Whentappedbytheuseraninfowindowwillappeardisplaying
additionalinformationaboutthemarkerlocation.
MarkersarerepresentedbyinstancesoftheMarkerclassandareaddedtoamapviaacall
totheaddMarker()methodofthecorrespondingGoogleMapobject.Passedthroughasan
argumenttothismethodisaMarkerOptionsclassinstancecontainingthevariousoptions
requiredforthemarkersuchasthetitleandsnippettext.Thelocationofamarkeris
definedbyspecifyinglatitudeandlongitudevalues,alsoincludedaspartofthe
MarkerOptionsinstance.Forexample,thefollowingcodeaddsamarkerincludingatitle,
snippetandapositiontoaspecificlocationonthemap:
importcom.google.android.gms.maps.model.Marker;
importcom.google.android.gms.maps.model.LatLng;
importcom.google.android.gms.maps.model.MarkerOptions;
.
.
.
LatLngMUSEUM=newLatLng(38.8874245,-77.0200729);
Markermuseum=mMap.addMarker(newMarkerOptions()
.position(MUSEUM)
.title(“Museum”)
.snippet(“NationalAirandSpaceMuseum”));
Whenexecuted,theabovecodewillmarkthelocationspecifiedwhich,whentapped,will
displayaninfowindowcontainingthetitleandsnippetasshowninFigure57-4:
Figure57-4
57.13ControllingtheMapCamera
BecauseAndroiddevicescreensareflatandtheworldisasphere,theGoogleMaps
AndroidAPIusestheMercatorprojectiontorepresenttheearthonaflatsurface.The
defaultviewofthemapispresentedtotheuserasthoughthroughacamerasuspended
abovethemapandpointingdirectlydownatthemap.TheGoogleMapsAndroidAPI
allowsthetarget,zoom,bearingandtiltofthiscameratobechangedinreal-timefrom
withintheapplication:
· Target–Thelocationofthecenterofthemapwithinthedevicedisplayspecifiedin
termsoflongitudeandlatitude.
· Zoom–Thezoomlevelofthecameraspecifiedinlevels.Increasingthezoomlevelby
1.0doublesthewidthoftheamountofthemapdisplayed.
· Tilt–Theviewingangleofthecameraspecifiedasapositiononanarcspanning
directlyoverthecenteroftheviewablemapareameasuredindegreesfromthetopof
thearc(thisbeingthenadirofthearcwherethecamerapointsdirectlydowntothe
map).
· Bearing–Theorientationofthemapindegreesmeasuredinaclockwisedirection
fromNorth.
CamerachangesaremadebycreatinganinstanceoftheCameraUpdateclasswiththe
appropriatesettings.CameraUpdateinstancesarecreatedbymakingmethodcallstothe
CameraUpdateFactoryclass.OnceaCameraUpdateinstancehasbeencreated,itis
appliedtothemapviaacalltothemoveCamera()methodoftheGoogleMapinstance.To
obtainasmoothanimatedeffectasthecamerachanges,theanimateCamera()methodmay
becalledinsteadofmoveCamera().
AsummaryofCameraUpdateFactorymethodsisasfollows:
· CameraUpdateFactory.zoomIn()–ProvidesaCameraUpdateinstancezoomedinby
onelevel.
· CameraUpdateFactory.zoomOut()-ProvidesaCameraUpdateinstancezoomedout
byonelevel.
· CameraUpdateFactory.zoomTo(float)-GeneratesaCameraUpdateinstancethat
changesthezoomleveltothespecifiedvalue.
· CameraUpdateFactory.zoomBy(float)–ProvidesaCameraUpdateinstancewitha
zoomlevelincreasedordecreasedbythespecifiedamount.
· CameraUpdateFactory.zoomBy(float,Point)-CreatesaCameraUpdateinstancethat
increasesordecreasesthezoomlevelbythespecifiedvalue.
· CameraUpdateFactory.newLatLng(LatLng)-CreatesaCameraUpdateinstancethat
changesthecamera’stargetlatitudeandlongitude.
· CameraUpdateFactory.newLatLngZoom(LatLng,float)-Generatesa
CameraUpdateinstancethatchangesthecamera’slatitude,longitudeandzoom.
· CameraUpdateFactory.newCameraPosition(CameraPosition)-Returnsa
CameraUpdateinstancethatmovesthecameratothespecifiedposition.A
CameraPositioninstancecanbeobtainedusingCameraPosition.Builder().
Thefollowingcode,forexample,zoomsinthecamerabyonelevelusinganimation:
mMap.animateCamera(CameraUpdateFactory.zoomIn());
Thefollowingcode,ontheotherhand,movesthecameratoanewlocationandadjuststhe
zoomlevelto10withoutanimation:
privatestaticfinalLatLngMUSEUM=
newLatLng(38.8874245,-77.0200729);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(MUSEUM,10));
Finally,thenextcodeexampleusesCameraPosition.Builder()tocreateaCameraPosition
objectwithchangestothetarget,zoom,bearingandtilt.Thischangeisthenappliedtothe
camerausinganimation:
importcom.google.android.gms.maps.model.CameraPosition;
.
.
CameraPositioncameraPosition=newCameraPosition.Builder()
.target(MUSEUM)
.zoom(50)
.bearing(70)
.tilt(25)
.build();
map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
57.14Summary
Thischapterhasprovidedanoverviewofthekeyclassesandmethodsthatmakeupthe
GoogleMapsAndroidAPIandoutlinedthestepsinvolvedinpreparingboththe
developmentenvironmentandanapplicationprojecttomakeuseoftheAPI.
58.PrintingwiththeAndroidPrinting
Framework
WiththeintroductionoftheAndroid4.4KitKatrelease,itisnowpossibletoprintcontent
fromwithinAndroidapplications.Whilesubsequentchapterswillexploreinmoredetail
theoptionsforaddingprintingsupporttoyourownapplications,thischapterwillfocuson
thevariousprintingoptionsnowavailableinAndroidandthestepsinvolvedinenabling
thoseoptions.Havingcoveredtheseinitialtopics,thechapterwillthenprovidean
overviewofthevariousprintingfeaturesthatareavailabletoAndroiddevelopersinterms
ofbuildingprintingsupportintoapplications.
58.1TheAndroidPrintingArchitecture
PrintinginAndroid4.4andlaterisprovidedbythePrintingframework.Inbasicterms,
thisframeworkconsistsofaPrintManagerandanumberofprintserviceplugins.Itisthe
responsibilityofthePrintManagertohandletheprintrequestsfromapplicationsonthe
deviceandtointeractwiththeprintservicepluginsthatareinstalledonthedevice,
therebyensuringthatprintrequestsarefulfilled.Bydefault,manyAndroiddeviceshave
printservicepluginsinstalledtoenableprintingusingtheGoogleCloudPrintandGoogle
Driveservices.PrintServicesPluginforotherprintertypes,ifnotalreadyinstalled,may
alsobeobtainedfromtheGooglePlaystore.PrintServicePluginsarecurrentlyavailable
forHP,EpsonandCanonprintersandpluginsfromotherprintermanufactureswillmost
likelybereleasedinthenearfuturethoughtheGoogleCloudPrintserviceplugincanalso
beusedtoprintfromanAndroiddevicetojustaboutanyprintertypeandmodel.Forthe
purposesofthisbook,wewillusetheHPPrintServicesPluginasareferenceexample.
58.2ThePrintServicePlugins
ThepurposeofthePrintServicepluginsistoenableapplicationstoprinttocompatible
printersthatarevisibletotheAndroiddeviceviaalocalareawirelessnetworkor
Bluetooth.PrintServicepluginsarecurrentlyavailableforawiderangeofprinterbrands
includingHP,Samsung,Brother,CanonLexmarkandXerox.
ThepresenceofthePrintServicePluginonanAndroiddevicecanbeverifiedbyloading
theGooglePlayappandperformingasearchfor“PrintServicesPlugin”.Oncetheplugin
islistedinthePlayStore,andintheeventthatthepluginisnotalreadyinstalled,itcanbe
installedbyselectingtheInstallbutton.Figure58-1,forexample,showstheHPPrint
ServicepluginwithinGooglePlay:
Figure58-1
ThePrintServicespluginswillautomaticallydetectcompatibleHPprintersonthe
networktowhichtheAndroiddeviceiscurrentlyconnectedandlistthemasoptionswhen
printingfromanapplication.
58.3GoogleCloudPrint
GoogleCloudPrintisaserviceprovidedbyGooglethatenablesyoutoprintcontentonto
yourownprinteroverthewebfromanywherewithinternetconnectivity.GoogleCloud
PrintsupportsawiderangeofdevicesandprintermodelsintheformofbothCloudReady
andClassicprinters.ACloudReadyprinterhastechnologybuilt-inthatenablesprinting
viatheweb.ManufacturersthatprovidecloudreadyprintersincludeBrother,Canon,Dell,
Epson,HP,KodakandSamsung.Toidentifyifyourprinterisbothcloudreadyand
supportedbyGoogleCloudPrint,reviewthelistofprintersatthefollowingURL:
https://www.google.com/cloudprint/learn/printers.html
Inthecaseofclassic,non-CloudReadyprinters,GoogleCloudPrintprovidessupportfor
cloudprintingthroughtheinstallationofsoftwareonthecomputersystemtowhichthe
classicprinterisconnected(eitherdirectlyoroverahomeorofficenetwork).
TosetupGoogleCloudPrint,visitthefollowingwebpageandloginusingthesame
GoogleaccountIDthatyouusewhenloggingintoyourAndroiddevices:
https://www.google.com/cloudprint/learn/index.html
OnceprintershavebeenaddedtoyourGoogleCloudPrintaccount,theywillbelistedas
printerdestinationoptionswhenyouprintfromwithinAndroidapplicationsonyour
devices.
58.4PrintingtoGoogleDrive
Inadditiontosupportingphysicalprinters,itisalsopossibletosaveprintedoutputtoyour
GoogleDriveaccount.Whenprintingfromadevice,selecttheSavetoGoogleDrive
optionintheprintingpanel.ThecontenttobeprintedwillthenbeconvertedtoaPDFfile
andsavedtotheGoogleDrivecloud-basedstorageassociatedwiththecurrentlyactive
GoogleAccountIDonthedevice.
58.5SaveasPDF
ThefinalprintingoptionprovidedbyAndroidallowstheprintedcontenttobesaved
locallyasaPDFfileontheAndroiddevice.Onceselected,thisoptionwillrequestaname
forthePDFfileandalocationonthedeviceintowhichthedocumentistobesaved.
BoththeSaveasPDFandGoogleDriveoptionscanbeinvaluableintermsofsaving
paperwhentestingtheprintingfunctionalityofyourownAndroidapplications.
58.6PrintingfromAndroidDevices
Googlerecommendsthatapplicationswhichprovidetheabilitytoprintcontentdosoby
placingtheprintoptionintheOverflowmenu(atopiccoveredinsomedetailinthe
chapterentitledCreatingandManagingOverflowMenusonAndroid).Anumberof
applicationsbundledwithAndroidnowinclude“Print…”menuoptions.Figure58-2,for
example,showsthePrintoptionintheOverflowmenuoftheChromebrowser
application:
Figure58-2
Oncetheprintoptionhasbeenselectedfromwithinanapplication,thestandardAndroid
printscreenwillappearshowingapreviewofthecontenttobeprintedasillustratedin
Figure58-3:
Figure58-3
Tappingthepanelalongthetopofthescreenwilldisplaythefullrangeofprinting
options:
Figure58-4
TheAndroidprintpanelprovidestheusualprintingoptionssuchaspapersize,color,
orientationandnumberofcopies.Otherprintdestinationoptionsmaybeaccessedby
tappingonthecurrentprinterorPDFoutputselection.
58.7OptionsforBuildingPrintSupportintoAndroidApps
ThePrintingframeworkintroducedintotheAndroid4.4SDKprovidesanumberof
optionsforincorporatingprintsupportintoAndroidapplications.Theseoptionscanbe
categorizedasfollows:
58.7.1ImagePrinting
Asthenamesuggests,thisoptionallowsimageprintingtobeincorporatedintoAndroid
applications.Whenaddingthisfeaturetoanapplication,thefirststepistocreateanew
instanceofthePrintHelperclass:
PrintHelperimagePrinter=newPrintHelper(context);
Next,thescalemodefortheprintedimagemaybespecifiedviaacalltothe
setScaleMode()methodofthePrintHelperinstance.Optionsareasfollows:
· SCALE_MODE_FIT–Theimagewillbescaledtofitwithinthepapersizewithout
anycroppingorchangestoaspectratio.Thiswilltypicallyresultinwhitespace
appearinginonedimension.
· SCALE_MODE_FILL–Theimagewillbescaledtofillthepapersizewithcropping
performedwherenecessarytoavoidtheappearanceofwhitespaceintheprinted
output.
Intheabsenceofascalemodesetting,thesystemwilldefaulttoSCALE_MODE_FILL.
Thefollowingcode,forexample,setsscaletofitmodeonthepreviouslydeclared
PrintHelperinstance:
imagePrinter.setScaleMode(PrintHelper.SCALE_MODE_FIT);
Similarly,thecolormodemayalsobeconfiguredtoindicatewhethertheprintoutputisto
beincolororblackandwhite.Thisisachievedbypassingoneofthefollowingoptions
throughtothesetColorMode()methodofthePrintHelperinstance:
· COLOR_MODE_COLOR–Indicatesthattheimageistobeprintedincolor.
· COLOR_MODE_MONOCHROME–Indicatesthattheimageistobeprintedin
blackandwhite.
Theprintingframeworkwilldefaulttocolorprintingunlessthemonochromeoptionis
specifiedasfollows:
imagePrinter.setColorMode(PrintHelper.COLOR_MODE_MONOCHROME);
Allthatisrequiredtocompletetheprintingoperationisanimagetobeprintedandacall
totheprintBitmap()methodofthePrintHelperinstance,passingthroughastring
representingthenametobeassignedtotheprintjobandareferencetotheimage(inthe
formofeitheraBitmapobjectoraUrireferencetotheimage):
Bitmapbitmap=BitmapFactory.decodeResource(getResources(),
R.drawable.oceanscene);
imagePrinter.printBitmap(“MyTestPrintJob”,bitmap);
Oncetheprintjobhasbeenstarted,thePrintingframeworkwilldisplaytheprintdialog
andhandleboththesubsequentinteractionwiththeuserandtheprintingoftheimageon
theuserselectedprintdestination.
58.7.2CreatingandPrintingHTMLContent
TheAndroidPrintingframeworkalsoprovidesaneasywaytoprintHTMLbasedcontent
fromwithinanapplication.ThiscontentcaneitherbeintheformofHTMLcontent
referencedbytheURLofapagehostedonawebsite,orHTMLcontentthatis
dynamicallycreatedwithintheapplication.
ToenableHTMLprinting,theWebViewclasshasbeenextendedinAndroid4.4toinclude
supportforprintingwithminimalcodingrequirements.
WhendynamicallycreatingHTMLcontent(asopposedtoloadingandprintinganexisting
webpage)theprocessinvolvesthecreationofaWebViewobjectandassociatingwithita
WebViewClientinstance.Thewebviewclientisthenconfiguredtostartaprintjobwhen
theHTMLhasfinishedbeingloadedintotheWebView.Withthewebviewclient
configured,theHTMListhenloadedintotheWebView,atwhichpointtheprintprocessis
triggered.
Consider,forexample,thefollowingcode:
privateWebViewmyWebView;
publicvoidprintContent(Viewview)
{
WebViewwebView=newWebView(this);
webView.setWebViewClient(newWebViewClient(){
publicbooleanshouldOverrideUrlLoading(WebViewview,
Stringurl)
{
returnfalse;
}
@Override
publicvoidonPageFinished(WebViewview,Stringurl){
createWebPrintJob(view);
myWebView=null;
}
});
StringhtmlDocument=
“<html><body><h1>AndroidPrintTest</h1><p>”
+“Thisissomesamplecontent.</p></body></html>”;
webView.loadDataWithBaseURL(null,htmlDocument,
“text/HTML”,“UTF-8”,null);
myWebView=webView;
}
ThecodeinthismethodbeginsbydeclaringavariablenamedmyWebViewinwhichwill
bestoredareferencetotheWebViewinstancecreatedinthemethod.Withinthe
printContent()method,aninstanceoftheWebViewclassiscreatedtowhicha
WebViewClientinstanceisthenassigned.
TheWebViewClientassignedtothewebviewobjectisconfiguredtoindicatethatloading
oftheHTMLcontentistobehandledbytheWebViewinstance(byreturningfalsefrom
theshouldOverrideUrlLoading())method.Moreimportantly,anonPageFinished()
handlermethodisdeclaredandimplementedtocallamethodnamedcreateWebPrintJob().
TheonPageFinished()callbackmethodwillbecalledautomaticallywhenallofthe
HTMLcontenthasbeenloadedintothewebview.Thisensuresthattheprintjobisnot
starteduntilthecontentisready,therebyensuringthatallofthecontentisprinted.
Next,astringiscreatedcontainingsomeHTMLtoserveasthecontent.Thisisthen
loadedintothewebview.OncetheHTMLisloaded,theonPageFinished()methodwill
trigger.Finally,themethodstoresareferencetothewebviewobject.Withoutthisvital
step,thereisasignificantriskthattheJavaruntimesystemwillassumethatthe
applicationnolongerneedsthewebviewobjectandwilldiscardittofreeupmemory(a
conceptreferredtoinJavaterminologyasgarbagecollection)resultingintheprintjob
terminatingpriortocompletion.
AllthatremainsinthisexampleistoimplementthecreateWebPrintJob()methodas
follows:
privatevoidcreateWebPrintJob(WebViewwebView){
PrintManagerprintManager=(PrintManager)this
.getSystemService(Context.PRINT_SERVICE);
PrintDocumentAdapterprintAdapter=
webView.createPrintDocumentAdapter(“MyDocument”);
StringjobName=getString(R.string.app_name)+”Document”;
PrintJobprintJob=printManager.print(jobName,printAdapter,
newPrintAttributes.Builder().build());
}
ThismethodsimplyobtainsareferencetothePrintManagerserviceandinstructstheweb
viewinstancetocreateaprintadapter.Anewstringiscreatedtostorethenameofthe
printjob(inthiscasebasedonthenameoftheapplicationandtheword“Document”).
Finally,theprintjobisstartedbycallingtheprint()methodoftheprintmanager,passing
throughthejobname,printadapterandasetofdefaultprintattributes.Ifrequired,the
printattributescouldbecustomizedtospecifyresolution(dotsperinch),marginandcolor
options.
58.7.3PrintingaWebPage
Thestepsinvolvedinprintingawebpagearesimilartothoseoutlinedabove,withthe
exceptionthatthewebviewispassedtheURLofthewebpagetobeprintedinplaceof
thedynamicallycreatedHTML,forexample:
webView.loadUrl(“http://developer.android.com/google/index.html”);
ItisalsoimportanttonotethattheWebViewClientconfigurationisonlynecessaryifa
webpageistoautomaticallyprintassoonasithasloaded.Iftheprintingistobeinitiated
bytheuserselectingamenuoptionafterthepagehasloaded,onlythecodeinthe
createWebPrintJob()methodoutlinedaboveneedbeincludedintheapplicationcode.The
nextchapter,entitledAnAndroidHTMLandWebContentPrintingExample,will
demonstratejustsuchascenario.
58.7.4PrintingaCustomDocument
WhiletheHTMLandwebprintingfeaturesintroducedbythePrintingframeworkprovide
aneasypathtoprintingcontentfromwithinanAndroidapplication,itisclearthatthese
optionswillbeoverlysimplisticformoreadvancedprintingrequirements.Formore
complexprintingtasks,thePrintingframeworkalsoprovidescustomdocumentprinting
support.Thisallowscontentintheformoftextandgraphicstobedrawnontoacanvas
andthenprinted.
UnlikeHTMLandimageprinting,whichcanbeimplementedwithrelativeease,custom
documentprintingisamorecomplex,multi-stageprocesswhichwillbeoutlinedintheA
GuidetoAndroidCustomDocumentPrintingchapterofthisbook.Thesestepscanbe
summarizedasfollows:
· ConnecttotheAndroidPrintManager
· CreateaCustomPrintAdaptersub-classedfromthePrintDocumentAdapterclass
· CreateaPdfDocumentinstancetorepresentthedocumentpages
· ObtainareferencetothepagesofthePdfDocumentinstance,eachofwhichhas
associatedwithitaCanvasinstance
· Drawthecontentonthepagecanvases
· Notifytheprintframeworkthatthedocumentisreadytoprint
Thecustomprintadapteroutlinedintheabovestepsneedstoimplementanumberof
methodswhichwillbecalleduponbytheAndroidsystemtoperformspecifictasksduring
theprintingprocess.ThemostimportantofthesearetheonLayout()methodwhichis
responsibleforre-arrangingthedocumentlayoutinresponsetotheuserchangingsettings
suchaspapersizeorpageorientation,andtheonWrite()methodwhichisresponsiblefor
renderingthepagestobeprinted.Thistopicwillbecoveredindetailinthechapter
entitledAGuidetoAndroidCustomDocumentPrinting.
58.8Summary
TheAndroid4.4KitKatreleaseintroducedtheabilitytoprintcontentfromAndroid
devices.Printoutputcanbedirectedtosuitablyconfiguredprinters,alocalPDFfileorto
thecloudviaGoogleDrive.FromtheperspectiveoftheAndroidapplicationdeveloper,
thesecapabilitiesareavailableforuseinapplicationsbymakinguseofthePrinting
framework.Byfartheeasiestprintingoptionstoimplementarethoseinvolvingcontentin
theformofimagesandHTML.Moreadvancedprintingmay,however,beimplemented
usingthecustomdocumentprintingfeaturesoftheframework.
59.AnAndroidHTMLandWebContent
PrintingExample
Asoutlinedinthepreviouscxzhapter,entitledPrintingwiththeAndroidPrinting
Framework,theAndroidPrintingframeworkcanbeusedtoprintbothwebpagesand
dynamicallycreatedHTMLcontent.Whilethereismuchsimilarityinthesetwo
approachestoprinting,therearealsosomesubtledifferencesthatneedtobetakeninto
consideration.Thischapterwillworkthroughthecreationoftwoexampleapplicationsin
ordertobringsomeclaritytothesetwoprintingoptions.
59.1CreatingtheHTMLPrintingExampleApplication
BeginthisexamplebylaunchingtheAndroidStudioenvironmentandcreatinganew
project,enteringHTMLPrintintotheApplicationnamefieldandebookfrenzy.comasthe
CompanyDomainsettingbeforeclickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI21:Android5.0(Lollipop).Continuetoproceedthroughthescreens,
requestingthecreationofanemptyactivitynamedHTMLPrintActivitywitha
correspondinglayoutnamedactivity_html_print.
59.2PrintingDynamicHTMLContent
ThefirststageofthistutorialistoaddcodetotheprojecttocreatesomeHTMLcontent
andsendittothePrintingframeworkintheformofaprintjob.
BeginbylocatingtheHTMLPrintActivity.javafile(locatedintheProjecttoolwindow
underapp->java->com.ebookfrenzy.htmlprint)andloadingitintotheeditingpanel.
Onceloaded,modifythecodesothatitreadsasoutlinedinthefollowinglisting:
packagecom.ebookfrenzy.htmlprint;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.webkit.WebView;
importandroid.webkit.WebViewClient;
importandroid.print.PrintAttributes;
importandroid.print.PrintDocumentAdapter;
importandroid.print.PrintManager;
importandroid.content.Context;
publicclassHTMLPrintActivityextendsAppCompatActivity{
privateWebViewmyWebView;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_html_print);
WebViewwebView=newWebView(this);
webView.setWebViewClient(newWebViewClient(){
publicbooleanshouldOverrideUrlLoading(WebViewview,
Stringurl)
{
returnfalse;
}
@Override
publicvoidonPageFinished(WebViewview,Stringurl)
{
createWebPrintJob(view);
myWebView=null;
}
});
StringhtmlDocument=
“<html><body><h1>AndroidPrintTest</h1><p>”
+“Thisissomesamplecontent.</p></body></html>”;
webView.loadDataWithBaseURL(null,htmlDocument,
“text/HTML”,“UTF-8”,null);
myWebView=webView;
}
.
.
.
}
ThecodechangesbeginbydeclaringavariablenamedmyWebViewinwhichwillbe
storedareferencetotheWebViewinstanceusedfortheprintingoperation.Withinthe
onCreate()method,aninstanceoftheWebViewclassiscreatedtowhicha
WebViewClientinstanceisthenassigned.
TheWebViewClientassignedtothewebviewobjectisconfiguredtoindicatethatloading
oftheHTMLcontentistobehandledbytheWebViewinstance(byreturningfalsefrom
theshouldOverrideUrlLoading()method).Moreimportantly,anonPageFinished()
handlermethodisdeclaredandimplementedtocallamethodnamedcreateWebPrintJob().
TheonPageFinished()methodwillbecalledautomaticallywhenalloftheHTMLcontent
hasbeenloadedintothewebview.Asoutlinedinthepreviouschapter,thisstepis
necessarywhenprintingdynamicallycreatedHTMLcontenttoensurethattheprintjobis
notstarteduntilthecontenthasfullyloadedintotheWebView.
Next,aStringobjectiscreatedcontainingsomeHTMLtoserveasthecontentand
subsequentlyloadedintothewebview.OncetheHTMLisloaded,theonPageFinished()
callbackmethodwilltrigger.Finally,themethodstoresareferencetothewebviewobject
inthepreviouslydeclaredmyWebViewvariable.Withoutthisvitalstep,thereisa
significantriskthattheJavaruntimesystemwillassumethattheapplicationnolonger
needsthewebviewobjectandwilldiscardittofreeupmemoryresultingintheprintjob
terminatingbeforecompletion.
AllthatremainsinthisexampleistoimplementthecreateWebPrintJob()methodwhichis
currentlyconfiguredtobecalledbytheonPageFinished()callbackmethod.Remaining
withintheHTMLPrintActivity.javafile,therefore,implementthismethodsothatitreads
asfollows:
privatevoidcreateWebPrintJob(WebViewwebView){
PrintManagerprintManager=(PrintManager)this
.getSystemService(Context.PRINT_SERVICE);
PrintDocumentAdapterprintAdapter=
webView.createPrintDocumentAdapter(“MyDocument”);
StringjobName=getString(R.string.app_name)+”PrintTest”;
printManager.print(jobName,printAdapter,
newPrintAttributes.Builder().build());
}
ThismethodobtainsareferencetothePrintManagerserviceandinstructsthewebview
instancetocreateaprintadapter.Anewstringiscreatedtostorethenameoftheprintjob
(inthiscasebasedonthenameoftheapplicationandtheword“PrintTest”).
Finally,theprintjobisstartedbycallingtheprint()methodoftheprintmanager,passing
throughthejobname,printadapterandasetofdefaultprintattributes.
CompileandruntheapplicationonadevicerunningAndroid5.0orlater.Oncelaunched,
thestandardAndroidprintingpageshouldappearasillustratedinFigure59-1.
Figure59-1
Printtoaphysicalprinterifyouhaveoneconfigured,savetoGoogleDriveor,
alternatively,selecttheoptiontosavetoaPDFfile.Oncetheprintjobhasbeeninitiated,
checkthegeneratedoutputonyourchosendestination.NotethatwhenusingtheSaveto
PDFoption,thesystemwillrequestanameandlocationforthePDFfile.TheDownloads
foldermakesagoodoption,thecontentsofwhichcanbeviewedbyselectingthe
Downloadsiconlocatedamongsttheotherappiconsonthedevice.Figure59-2,for
example,showsthePDFoutputgeneratedbytheSavetoPDFoptionviewedonan
Androiddevice:
Figure59-2
59.3CreatingtheWebPagePrintingExample
Thesecondexampleapplicationtobecreatedinthischapterwillprovidetheuserwithan
OverflowmenuoptiontoprintthewebpagecurrentlydisplayedwithinaWebView
instance.CreateanewprojectinAndroidStudio,enteringWebPrintintotheApplication
namefieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingonthe
Nextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI21:Android5.0(Lollipop).Continuetoproceedthroughthescreens,
requestingthecreationofablankactivity(sincewewillbemakinguseofthecontext
menuprovidedbytheblankactivitytemplate)namedWebPrintActivitywiththeremaining
propertiessettothedefaultvalues.
59.4RemovingtheFloatingActionButton
SelectingtheBlankActivitytemplateprovidedacontextmenuandafloatingaction
button.Sincethefloatingactionbuttonisnotrequiredbytheappitcanberemovedbefore
proceeding.Loadtheactivity_web_print.xmllayoutfileintotheDesigner,selectthe
floatingactionbuttonandtapthekeyboardDeletekeytoremovetheobjectfromthe
layout.EdittheWebPrintActivity.javafileandremovethefloatingactionbuttoncodefrom
theonCreatemethodasfollows:
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_print);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButtonfab=
(FloatingActionButton)findViewById(R.id.fab);
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
Snackbar.make(view,“Replacewithyourownaction”,
Snackbar.LENGTH_LONG)
.setAction(“Action”,null).show();
}
});
}
59.5DesigningtheUserInterfaceLayout
Loadthecontent_web_print.xmllayoutresourcefileintotheDesignertoolifithasnot
alreadybeenloadedand,inDesignmode,selectanddeletethe“HelloWorld!”TextView
object.
SwitchtotheXMLviewbyclickingontheTexttablocatedalongtheloweredgeofthe
DesignerpanelandremovethepaddingpropertiesfromthefilesothattheWebViewwill
extendtotheedgesofthedisplaywhenaddedtothelayout:
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=”.WebPrintActivity”>
</RelativeLayout>
SwitchbacktoDesignmodeand,fromtheWidgetssectionofthepalette,draganddropa
WebViewobjectontothecenterofthedevicescreenlayout.UsingeithertheProperties
panelortheDesignertoolbarbuttons,changethelayout_widthandlayout_height
propertiesoftheWebViewtomatch_parentsothatitfillstheentirelayoutcanvasas
outlinedinFigure59-3:
Figure59-3
Double-clickonthenewlyaddedWebViewinstance,andchangetheIDoftheviewto
myWebView.
Beforeproceedingtothenextstepofthistutorial,anadditionalpermissionneedstobe
addedtotheprojecttoenabletheWebViewobjecttoaccesstheinternetanddownloada
webpageforprinting.AddthispermissionbylocatingtheAndroidManifest.xmlfileinthe
Projecttoolwindowanddoubleclickingonittoloaditintotheeditingpanel.Once
loaded,edittheXMLcontenttoaddtheappropriatepermissionlineasshowninthe
followinglisting:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.webprint”>
<uses-permissionandroid:name=“android.permission.INTERNET”/>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.WebPrintActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=
“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
59.6LoadingtheWebPageintotheWebView
BeforethewebpagecanbeprinteditneedstobeloadedintotheWebViewinstance.For
thepurposesofthistutorial,thiswillbeperformedbyacalltotheloadUrl()methodofthe
WebViewinstancewhichwillbeplacedintheonCreate()methodoftheWebPrintActivity
class.EdittheWebPrintActivity.javafile,therefore,andmodifyitasfollows:
packagecom.ebookfrenzy.webprint;
importandroid.os.Bundle;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.webkit.WebView;
publicclassWebPrintActivityextendsAppCompatActivity{
privateWebViewmyWebView;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_print);
myWebView=(WebView)findViewById(R.id.myWebView);
myWebView.loadUrl(
“http://developer.android.com/google/index.html”);
}
.
.
}
59.7AddingthePrintMenuOption
TheoptiontoprintthewebpagewillnowbeaddedtotheOverflowmenuusingthe
techniquesoutlinedinthechapterentitledCreatingandManagingOverflowMenuson
Android.
Thefirstrequirementisastringresourcewithwhichtolabelthemenuoption.Withinthe
Projecttoolwindow,locatetheapp->res->values->strings.xmlfile,doubleclickonit
toloaditintotheeditorandmodifyittoaddanewstringresource:
<resources>
<stringname=“app_name”>WebPrint</string>
<stringname=“action_settings”>Settings</string>
<stringname=“print_string”>Print</string>
</resources>
Next,locateandedittheapp->res->menu->menu_web_print.xmlfileandreplacethe
Settingsmenuoptionwiththeprintoption:
<menuxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
tools:context=“com.ebookfrenzy.webprint.WebPrintActivity”>
<itemandroid:id=”@+id/action_settings”
android:title=”@string/action_settings”
android:orderInCategory=“100”
app:showAsAction=“never”/>
<item
android:id=”@+id/action_print”
android:orderInCategory=“100”
app:showAsAction=“never”
android:title=”@string/print_string”/>
</menu>
Allthatremainsintermsofconfiguringthemenuoptionistomodifythe
onOptionsItemSelected()handlermethodwithintheWebPrintActivity.javafile:
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
intid=item.getItemId();
if(id==R.id.action_print){
createWebPrintJob(myWebView);
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
WiththeonOptionsItemSelected()methodimplemented,theactivitywillcallamethod
namedcreateWebPrintJob()whentheprintmenuoptionisselectedfromtheoverflow
menu.Theimplementationofthismethodisidenticaltothatusedintheprevious
HTMLPrintprojectandmaynowbeaddedtotheWebPrintActivity.javafilesuchthatit
readsasfollows:
packagecom.ebookfrenzy.webprint;
importandroid.os.Bundle;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.webkit.WebView;
importandroid.print.PrintAttributes;
importandroid.print.PrintDocumentAdapter;
importandroid.print.PrintManager;
importandroid.content.Context;
publicclassWebPrintActivityextendsAppCompatActivity{
privateWebViewmyWebView;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web_print);
myWebView=(WebView)findViewById(R.id.myWebView);
myWebView.loadUrl(
“http://developer.android.com/google/index.html”);
}
privatevoidcreateWebPrintJob(WebViewwebView){
PrintManagerprintManager=(PrintManager)this
.getSystemService(Context.PRINT_SERVICE);
PrintDocumentAdapterprintAdapter=
webView.createPrintDocumentAdapter(“MyDocument”);
StringjobName=getString(R.string.app_name)+
”PrintTest”;
printManager.print(jobName,printAdapter,
newPrintAttributes.Builder().build());
}
.
.
}
Withthecodechangescomplete,runtheapplicationonaphysicalAndroiddevicerunning
Androidversion5.0orlater.Oncesuccessfullylaunched,theWebViewshouldbevisible
withthedesignatedwebpageloaded.Oncethepagehasloaded,selectthePrintoption
fromtheOverflowmenu(Figure59-4)andusetheresultingprintpaneltoprinttheweb
pagetoasuitabledestination.
Figure59-4
59.8Summary
TheAndroidPrintingframeworkincludesextensionstotheWebViewclassthatmakeit
possibletoprintHTMLbasedcontentfromwithinanAndroidapplication.Thiscontent
canbeintheformofHTMLcreateddynamicallywithintheapplicationatruntime,ora
pre-existingwebpageloadedintoaWebViewinstance.Inthecaseofdynamicallycreated
HTML,itisimportanttouseaWebViewClientinstancetoensurethatprintingdoesnot
startuntiltheHTMLhasbeenfullyloadedintotheWebView.
60.AGuidetoAndroidCustomDocument
Printing
Aswehaveseenintheprecedingchapters,theAndroidPrintingframeworkmakesit
relativelyeasytobuildprintingsupportintoapplicationsaslongasthecontentisinthe
formofanimageorHTMLmarkup.Moreadvancedprintingrequirementscanbemetby
makinguseofthecustomdocumentprintingfeatureofthePrintingframework.
60.1AnOverviewofAndroidCustomDocumentPrinting
Insimplisticterms,customdocumentprintingusescanvasestorepresentthepagesofthe
documenttobeprinted.Theapplicationdrawsthecontenttobeprintedontothese
canvasesintheformofshapes,colors,textandimages.Inactualfact,thecanvasesare
representedbyinstancesoftheAndroidCanvasclass,therebyprovidingaccesstoarich
selectionofdrawingoptions.Onceallthepageshavebeendrawnthedocumentisthen
printed.
Whilethissoundssimpleenough,thereareactuallyanumberofstepsthatneedtobe
performedtomakethishappen,eachofwhichcanbesummarizedasfollows:
· Implementacustomprintadaptersub-classedfromthePrintDocumentAdapterclass
· ObtainareferencetothePrintManagerService
· CreateaninstanceofthePdfDocumentclassinwhichtostorethedocumentpages
· AddpagestothePdfDocumentintheformofPdfDocument.Pageinstances
· ObtainreferencestotheCanvasobjectsassociatedwiththedocumentpages
· Drawcontentontothecanvases
· WritethePDFdocumenttoadestinationoutputstreamprovidedbythePrinting
framework
· NotifythePrintingframeworkthatthedocumentisreadytoprint
Inthischapter,anoverviewofthesestepswillbeprovidedfollowedbyadetailedtutorial
designedtodemonstratetheimplementationofcustomdocumentprintingwithinAndroid
applications.
60.1.1CustomPrintAdapters
TheroleoftheprintadapteristoprovidethePrintingframeworkwiththecontenttobe
printed,andtoensurethatitisformattedcorrectlyfortheuser’schosenpreferences
(takingintoconsiderationfactorssuchaspapersizeandpageorientation).
WhenprintingHTMLandimages,muchofthisworkisperformedbytheprintadapters
providedaspartoftheAndroidPrintingframeworkanddesignedforthesespecific
printingtasks.Whenprintingawebpage,forexample,aprintadapteriscreatedforus
whenacallismadetothecreatePrintDocumentAdapter()methodofaninstanceofthe
WebViewclass.
Inthecaseofcustomdocumentprinting,however,itistheresponsibilityofthe
applicationdevelopertodesigntheprintadapterandimplementthecodetodrawand
formatthecontentinpreparationforprinting.
Customprintadaptersarecreatedbysub-classingthePrintDocumentAdapterclassand
overridingasetofcallbackmethodswithinthatclasswhichwillbecalledbythePrinting
frameworkatvariousstagesintheprintprocess.Thesecallbackmethodscanbe
summarizedasfollows:
· onStart()–Thismethodiscalledwhentheprintingprocessbeginsandisprovidedso
thattheapplicationcodehasanopportunitytoperformanynecessarytasksin
preparationforcreatingtheprintjob.Implementationofthismethodwithinthe
PrintDocumentAdaptersub-classisoptional.
· onLayout()–ThiscallbackmethodiscalledafterthecalltotheonStart()methodand
thenagaineachtimetheusermakeschangestotheprintsettings(suchaschangingthe
orientation,papersizeorcolorsettings).Thismethodshouldadaptthecontentand
layoutwherenecessarytoaccommodatethesechanges.Oncethesechangesare
completed,themethodmustreturnacountofthenumberofpagestobeprinted.
ImplementationoftheonLayout()methodwithinthePrintDocumentAdaptersub-class
ismandatory.
· onWrite()–ThismethodiscalledaftereachcalltoonLayout()andisresponsiblefor
renderingthecontentonthecanvasesofthepagestobeprinted.Amongstother
arguments,thismethodispassedafiledescriptortowhichtheresultingPDFdocument
mustbewrittenoncerenderingiscomplete.Acallisthenmadetothe
onWriteFinished()callbackmethodpassingthroughanargumentcontaining
informationaboutthepagerangestobeprinted.ImplementationoftheonWrite()
methodwithinthePrintDocumentAdaptersub-classismandatory.
· onFinish()–Anoptionalmethodwhich,ifimplemented,iscalledoncebythePrinting
frameworkwhentheprintingprocessiscompleted,therebyprovidingtheapplication
theopportunitytoperformanyclean-upoperationsthatmaybenecessary.
60.2PreparingtheCustomDocumentPrintingProject
LaunchtheAndroidStudioenvironmentandcreateanewproject,enteringCustomPrint
intotheApplicationnamefieldandebookfrenzy.comastheCompanyDomainsetting
beforeclickingontheNextbutton.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI21:Android5.0(Lollipop).Continuetoproceedthroughthescreens,
requestingthecreationofanemptyactivitynamedCustomPrintActivitywitha
correspondinglayoutresourcefilenamedactivity_custom_print.
Loadtheactivity_custom_print.xmllayoutfileintotheDesignertooland,inDesignmode,
selectanddeletethe“HelloWorld!”TextViewobject.DraganddropaButtonviewfrom
theFormWidgetssectionofthepaletteandpositionitinthecenterofthelayoutview.
DoubleclickontheButtonview,andchangethetextdisplayedto“PrintDocument”.
Extractthestringtoanewstringresourcenamedprint_string.Oncompletion,theuser
interfacelayoutshouldmatchthatshowninFigure60-1:
Figure60-1
Whenthebuttonisselectedwithintheapplicationitwillberequiredtocallamethodto
initiatethedocumentprintingprocess.SwitchDesignertoTextmodeandaddanonClick
propertytothebuttonconfiguredtocallamethodnamedprintDocument.Assumingthat
thelayouthasbeenconfiguredcorrectly,thecompletedXMLshouldnowreadasfollows:
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=”.CustomPrintActivity”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/print_string”
android:id=”@+id/button”
android:layout_centerVertical=“true”
android:layout_centerHorizontal=“true”
android:onClick=“printDocument”/>
</RelativeLayout>
60.3CreatingtheCustomPrintAdapter
MostoftheworkinvolvedinprintingacustomdocumentfromwithinanAndroid
applicationinvolvestheimplementationofthecustomprintadapter.Thisexamplewill
requireaprintadapterwiththeonLayout()andonWrite()callbackmethodsimplemented.
WithintheCustomPrintActivity.javafile,addthetemplateforthisnewclasssothatit
readsasfollows:
packagecom.ebookfrenzy.customprint;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.os.CancellationSignal;
importandroid.os.ParcelFileDescriptor;
importandroid.print.PageRange;
importandroid.print.PrintAttributes;
importandroid.print.PrintDocumentAdapter;
importandroid.content.Context;
publicclassCustomPrintActivityextendsAppCompatActivity{
publicclassMyPrintDocumentAdapterextendsPrintDocumentAdapter
{
Contextcontext;
publicMyPrintDocumentAdapter(Contextcontext)
{
this.context=context;
}
@Override
publicvoidonLayout(PrintAttributesoldAttributes,
PrintAttributesnewAttributes,
CancellationSignalcancellationSignal,
LayoutResultCallbackcallback,
Bundlemetadata){
}
@Override
publicvoidonWrite(finalPageRange[]pageRanges,
finalParcelFileDescriptordestination,
finalCancellationSignal
cancellationSignal,
finalWriteResultCallbackcallback){
}
}
.
.
}
Asthenewclasscurrentlystands,itcontainsaconstructormethodwhichwillbecalled
whenanewinstanceoftheclassiscreated.Theconstructortakesasanargumentthe
contextofthecallingactivitywhichisthenstoredsothatitcanbereferencedlaterinthe
twocallbackmethods.
Withtheoutlineoftheclassestablished,thenextstepistobeginimplementingthetwo
callbackmethods,beginningwithonLayout().
60.4ImplementingtheonLayout()CallbackMethod
RemainingwithintheCustomPrintActivity.javafile,beginbyaddingsomeimport
directivesthatwillberequiredbythecodeintheonLayout()method:
packagecom.ebookfrenzy.customprint;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.os.CancellationSignal;
importandroid.os.ParcelFileDescriptor;
importandroid.print.PageRange;
importandroid.print.PrintAttributes;
importandroid.print.PrintDocumentAdapter;
importandroid.content.Context;
importandroid.print.PrintDocumentInfo;
importandroid.print.pdf.PrintedPdfDocument;
importandroid.graphics.pdf.PdfDocument;
publicclassCustomPrintActivityextendsAppCompatActivity{
.
.
.
}
Next,modifytheMyPrintDocumentAdapterclasstodeclarevariablestobeusedwithin
theonLayout()method:
publicclassMyPrintDocumentAdapterextendsPrintDocumentAdapter
{
Contextcontext;
privateintpageHeight;
privateintpageWidth;
publicPdfDocumentmyPdfDocument;
publicinttotalpages=4;
.
.
}
Notethatforthepurposesofthisexample,afourpagedocumentisgoingtobeprinted.In
morecomplexsituations,theapplicationwillmostlikelyneedtodynamicallycalculate
thenumberofpagestobeprintedbasedonthequantityandlayoutofthecontentin
relationtotheuser’spapersizeandpageorientationselections.
Withthevariablesdeclared,implementtheonLayout()methodasoutlinedinthe
followingcodelisting:
@Override
publicvoidonLayout(PrintAttributesoldAttributes,
PrintAttributesnewAttributes,
CancellationSignalcancellationSignal,
LayoutResultCallbackcallback,
Bundlemetadata){
myPdfDocument=newPrintedPdfDocument(context,newAttributes);
pageHeight=
newAttributes.getMediaSize().getHeightMils()/1000*72;
pageWidth=
newAttributes.getMediaSize().getWidthMils()/1000*72;
if(cancellationSignal.isCanceled()){
callback.onLayoutCancelled();
return;
}
if(totalpages>0){
PrintDocumentInfo.Builderbuilder=newPrintDocumentInfo
.Builder(“print_output.pdf”)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(totalpages);
PrintDocumentInfoinfo=builder.build();
callback.onLayoutFinished(info,true);
}else{
callback.onLayoutFailed(“Pagecountiszero.”);
}
}
Clearlythismethodisperformingquiteafewtasks,eachofwhichrequiressomedetailed
explanation.
Tobeginwith,anewPDFdocumentiscreatedintheformofaPdfDocumentclass
instance.OneoftheargumentspassedintotheonLayout()methodwhenitiscalledbythe
PrintingframeworkisanobjectoftypePrintAttributescontainingdetailsaboutthepaper
size,resolutionandcolorsettingsselectedbytheuserfortheprintoutput.Thesesettings
areusedwhencreatingthePDFdocument,alongwiththecontextoftheactivity
previouslystoredforusbyourconstructormethod:
myPdfDocument=newPrintedPdfDocument(context,newAttributes);
ThemethodthenusesthePrintAttributesobjecttoextracttheheightandwidthvaluesfor
thedocumentpages.Thesedimensionsarestoredintheobjectintheformofthousandths
ofaninch.Sincethemethodsthatwillusethesevalueslaterinthisexampleworkinunits
of1/72ofaninchthesenumbersareconvertedbeforetheyarestored:
pageHeight=newAttributes.getMediaSize().getHeightMils()/1000*72;
pageWidth=newAttributes.getMediaSize().getWidthMils()/1000*72;
Althoughthisexampledoesnotmakeuseoftheuser’scolorselection,thispropertycan
beobtainedviaacalltothegetColorMode()methodofthePrintAttributesobjectwhich
willreturnavalueofeitherCOLOR_MODE_COLORor
COLOR_MODE_MONOCHROME.
WhentheonLayout()methodiscalled,itispassedanobjectoftype
LayoutResultCallback.Thisobjectprovidesawayforthemethodtocommunicatestatus
informationbacktothePrintingframeworkviaasetofmethods.TheonLayout()method,
forexample,willbecalledintheeventthattheusercancelstheprintprocess.Thefactthat
theprocesshasbeencancelledisindicatedviaasettingwithintheCancellationSignal
argument.Intheeventthatacancellationisdetected,theonLayout()methodmustcallthe
onLayoutCancelled()methodoftheLayoutResultCallbackobjecttonotifythePrint
frameworkthatthecancellationrequestwasreceivedandthatthelayouttaskhasbeen
cancelled:
if(cancellationSignal.isCanceled()){
callback.onLayoutCancelled();
return;
}
Whenthelayoutworkiscomplete,themethodisrequiredtocalltheonLayoutFinished()
methodoftheLayoutResultCallbackobject,passingthroughtwoarguments.Thefirst
argumenttakestheformofaPrintDocumentInfoobjectcontaininginformationaboutthe
documenttobeprinted.ThisinformationconsistsofthenametobeusedforthePDF
document,thetypeofcontent(inthiscaseadocumentratherthananimage)andthepage
count.ThesecondargumentisaBooleanvalueindicatingwhetherornotthelayouthas
changedsincethelastcallmadetotheonLayout()method:
if(totalpages>0){
PrintDocumentInfo.Builderbuilder=newPrintDocumentInfo
.Builder(“print_output.pdf”)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(totalpages);
PrintDocumentInfoinfo=builder.build();
callback.onLayoutFinished(info,true);
}else{
callback.onLayoutFailed(“Pagecountiszero.”);
}
Intheeventthatthepagecountiszero,thecodereportsthisfailuretothePrinting
frameworkviaacalltotheonLayoutFailed()methodoftheLayoutResultCallbackobject.
ThecalltotheonLayoutFinished()methodnotifiesthePrintingframeworkthatthelayout
workiscomplete,therebytriggeringacalltotheonWrite()method.
60.5ImplementingtheonWrite()CallbackMethod
TheonWrite()callbackmethodisresponsibleforrenderingthepagesofthedocumentand
thennotifyingthePrintingframeworkthatthedocumentisreadytobeprinted.When
completed,theonWrite()methodreadsasfollows:
packagecom.ebookfrenzy.customprint;
importjava.io.FileOutputStream;
importjava.io.IOException;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.os.CancellationSignal;
importandroid.os.ParcelFileDescriptor;
importandroid.print.PageRange;
importandroid.print.PrintAttributes;
importandroid.print.PrintDocumentAdapter;
importandroid.content.Context;
importandroid.print.PrintDocumentInfo;
importandroid.print.pdf.PrintedPdfDocument;
importandroid.graphics.pdf.PdfDocument;
importandroid.graphics.pdf.PdfDocument.PageInfo;
.
.
.
.
@Override
publicvoidonWrite(finalPageRange[]pageRanges,
finalParcelFileDescriptordestination,
finalCancellationSignalcancellationSignal,
finalWriteResultCallbackcallback){
for(inti=0;i<totalpages;i++){
if(pageInRange(pageRanges,i))
{
PageInfonewPage=newPageInfo.Builder(pageWidth,
pageHeight,i).create();
PdfDocument.Pagepage=
myPdfDocument.startPage(newPage);
if(cancellationSignal.isCanceled()){
callback.onWriteCancelled();
myPdfDocument.close();
myPdfDocument=null;
return;
}
drawPage(page,i);
myPdfDocument.finishPage(page);
}
}
try{
myPdfDocument.writeTo(newFileOutputStream(
destination.getFileDescriptor()));
}catch(IOExceptione){
callback.onWriteFailed(e.toString());
return;
}finally{
myPdfDocument.close();
myPdfDocument=null;
}
callback.onWriteFinished(pageRanges);
}
TheonWrite()methodstartsbyloopingthrougheachofthepagesinthedocument.Itis
importanttotakeintoconsideration,however,thattheusermaynothaverequestedthatall
ofthepagesthatmakeupthedocumentbeprinted.Inactualfact,thePrintingframework
userinterfacepanelprovidestheoptiontospecifythatspecificpages,orrangesofpages
beprinted.Figure60-2,forexample,showstheprintpanelconfiguredtoprintpages1-4,
pages8and9andpages11-13ofadocument.
Figure60-2
WhenwritingthepagestothePDFdocument,theonWrite()methodmusttakestepsto
ensurethatonlythosepagesspecifiedbytheuserareprinted.Tomakethispossible,the
PrintingframeworkpassesthroughasanargumentanarrayofPageRangeobjects
indicatingtherangesofpagestobeprinted.IntheaboveonWrite()implementation,a
methodnamedpagesInRange()iscalledforeachpagetoverifythatthepageiswithinthe
specifiedranges.ThecodeforthepagesInRange()methodwillbeimplementedlaterin
thischapter.
for(inti=0;i<totalpages;i++){
if(pageInRange(pageRanges,i))
{
Foreachpagethatiswithinanyspecifiedranges,anewPdfDocument.Pageobjectis
created.Whencreatinganewpage,theheightandwidthvaluespreviouslystoredbythe
onLayout()methodarepassedthroughasargumentssothatthepagesizematchestheprint
optionsselectedbytheuser:
PageInfonewPage=newPageInfo.Builder(pageWidth,
pageHeight,i).create();
PdfDocument.Pagepage=myPdfDocument.startPage(newPage);
AswiththeonLayout()method,theonWrite()methodisrequiredtorespondto
cancellationrequests.Inthiscase,thecodenotifiesthePrintingframeworkthatthe
cancellationhasbeenperformedbeforeclosingandde-referencingthemyPdfDocument
variable:
if(cancellationSignal.isCanceled()){
callback.onWriteCancelled();
myPdfDocument.close();
myPdfDocument=null;
return;
}
Aslongastheprintprocesshasnotbeencancelled,themethodthencallsamethodto
drawthecontentonthecurrentpagebeforecallingthefinishedPage()methodonthe
myPdfDocumentobject.
drawPage(page,i);
myPdfDocument.finishPage(page);
ThedrawPage()methodwillberesponsiblefordrawingthecontentontothepageand
willbeimplementedoncetheonWrite()methodiscomplete.
WhentherequirednumberofpageshavebeenaddedtothePDFdocument,thedocument
isthenwrittentothedestinationstreamusingthefiledescriptorwhichwaspassedthrough
asanargumenttotheonWrite()method.If,foranyreason,thewriteoperationfails,the
methodnotifiestheframeworkbycallingtheonWriteFailed()methodofthe
WriteResultCallbackobject(alsopassedasanargumenttotheonWrite()method).
try{
myPdfDocument.writeTo(newFileOutputStream(
destination.getFileDescriptor()));
}catch(IOExceptione){
callback.onWriteFailed(e.toString());
return;
}finally{
myPdfDocument.close();
myPdfDocument=null;
}
Finally,theonWriteFinish()methodoftheWriteResultsCallbackobjectiscalledtonotify
thePrintingframeworkthatthedocumentisreadytobeprinted.
60.6CheckingaPageisinRange
Aspreviouslyoutlined,whentheonWrite()methodiscalleditispassedanarrayof
PageRangeobjectsindicatingtherangesofpageswithinthedocumentthataretobe
printed.ThePageRangeclassisdesignedtostorethestartandendpagesofapagerange
which,inturn,maybeaccessedviathegetStart()andgetEnd()methodsoftheclass.
WhentheonWrite()methodwasimplementedintheprevioussection,acallwasmadetoa
methodnamedpageInRange(),whichtakesasargumentsanarrayofPageRangeobjects
andapagenumber.TheroleofthepageInRange()methodistoidentifywhetherthe
specifiedpagenumberiswithintherangesspecifiedandmaybeimplementedwithinthe
MyPrintDocumentAdapterclassintheCustomPrintActivity.javaclassasfollows:
publicclassMyPrintDocumentAdapterextendsPrintDocumentAdapter
{
.
.
.
privatebooleanpageInRange(PageRange[]pageRanges,intpage)
{
for(inti=0;i<pageRanges.length;i++)
{
if((page>=pageRanges[i].getStart())&&
(page<=pageRanges[i].getEnd()))
returntrue;
}
returnfalse;
}
.
.
}
60.7DrawingtheContentonthePageCanvas
Wehavenowreachedthepointwheresomecodeneedstobewrittentodrawthecontent
onthepagessothattheyarereadyforprinting.Thecontentthatgetsdrawniscompletely
applicationspecificandlimitedonlybywhatcanbeachievedusingtheAndroidCanvas
class.Forthepurposesofthisexample,however,somesimpletextandgraphicswillbe
drawnonthecanvas.
TheonWrite()methodhasbeendesignedtocallamethodnameddrawPage()whichtakes
asargumentsthePdfDocument.Pageobjectrepresentingthecurrentpageandaninteger
representingthepagenumber.WithintheCustomPrintActivity.javafilethismethodshould
nowbeimplementedasfollows:
packagecom.ebookfrenzy.customprint;
importjava.io.FileOutputStream;
importjava.io.IOException;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.os.CancellationSignal;
importandroid.os.ParcelFileDescriptor;
importandroid.print.PageRange;
importandroid.print.PrintAttributes;
importandroid.print.PrintDocumentAdapter;
importandroid.content.Context;
importandroid.print.PrintDocumentInfo;
importandroid.print.pdf.PrintedPdfDocument;
importandroid.graphics.pdf.PdfDocument;
importandroid.graphics.pdf.PdfDocument.PageInfo;
importandroid.graphics.Canvas;
importandroid.graphics.Color;
importandroid.graphics.Paint;
publicclassCustomPrintActivityextendsAppCompatActivity{
.
.
publicclassMyPrintDocumentAdapterextendsPrintDocumentAdapter
{
.
.
privatevoiddrawPage(PdfDocument.Pagepage,
intpagenumber){
Canvascanvas=page.getCanvas();
pagenumber++;//Makesurepagenumbersstartat1
inttitleBaseLine=72;
intleftMargin=54;
Paintpaint=newPaint();
paint.setColor(Color.BLACK);
paint.setTextSize(40);
canvas.drawText(
“TestPrintDocumentPage”+pagenumber,
leftMargin,
titleBaseLine,
paint);
paint.setTextSize(14);
canvas.drawText(“Thisissometestcontenttoverify
thatcustomdocumentprintingworks”,leftMargin,titleBaseLine+35,
paint);
if(pagenumber%2==0)
paint.setColor(Color.RED);
else
paint.setColor(Color.GREEN);
PageInfopageInfo=page.getInfo();
canvas.drawCircle(pageInfo.getPageWidth()/2,
pageInfo.getPageHeight()/2,
150,
paint);
}
.
.
}
Pagenumberingwithinthecodestartsat0.Sincedocumentstraditionallystartatpage1,
themethodbeginsbyincrementingthestoredpagenumber.AreferencetotheCanvas
objectassociatedwiththepageisthenobtainedandsomemarginandbaselinevalues
declared:
Canvascanvas=page.getCanvas();
pagenumber++;
inttitleBaseLine=72;
intleftMargin=54;
Next,thecodecreatesPaintandColorobjectstobeusedfordrawing,setsatextsizeand
drawsthepagetitletext,includingthecurrentpagenumber:
Paintpaint=newPaint();
paint.setColor(Color.BLACK);
paint.setTextSize(40);
canvas.drawText(“TestPrintDocumentPage”+pagenumber,
leftMargin,
titleBaseLine,
paint);
Thetextsizeisthenreducedandsomebodytextdrawnbeneaththetitle:
paint.setTextSize(14);
canvas.drawText(“Thisissometestcontenttoverifythatcustom
documentprintingworks”,leftMargin,titleBaseLine+35,paint);
Thelasttaskperformedbythismethodinvolvesdrawingacircle(redonevennumbered
pagesandgreenonodd).Havingascertainedwhetherthepageisoddoreven,themethod
obtainstheheightandwidthofthepagebeforeusingthisinformationtopositionthecircle
inthecenterofthepage:
if(pagenumber%2==0)
paint.setColor(Color.RED);
else
paint.setColor(Color.GREEN);
PageInfopageInfo=page.getInfo();
canvas.drawCircle(pageInfo.getPageWidth()/2,
pageInfo.getPageHeight()/2,
150,paint);
Havingdrawnonthecanvas,themethodreturnscontroltotheonWrite()method.
WiththecompletionofthedrawPage()method,theMyPrintDocumentAdapterclassis
nowfinished.
60.8StartingthePrintJob
Whenthe“PrintDocument”buttonistouchedbytheuser,theprintDocument()onClick
eventhandlermethodwillbecalled.Allthatnowremainsbeforetestingcancommence,
therefore,istoaddthismethodtotheCustomPrintActivity.javafile,takingparticularcare
toensurethatitisplacedoutsideoftheMyPrintDocumentAdapterclass:
packagecom.ebookfrenzy.customprint;
importjava.io.FileOutputStream;
importjava.io.IOException;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.os.CancellationSignal;
importandroid.os.ParcelFileDescriptor;
importandroid.print.PageRange;
importandroid.print.PrintAttributes;
importandroid.print.PrintDocumentAdapter;
importandroid.content.Context;
importandroid.print.PrintDocumentInfo;
importandroid.print.pdf.PrintedPdfDocument;
importandroid.graphics.pdf.PdfDocument;
importandroid.graphics.pdf.PdfDocument.PageInfo;
importandroid.graphics.Canvas;
importandroid.graphics.Color;
importandroid.graphics.Paint;
importandroid.print.PrintManager;
importandroid.view.View;
publicclassCustomPrintActivityextendsAppCompatActivity{
publicvoidprintDocument(Viewview)
{
PrintManagerprintManager=(PrintManager)this
.getSystemService(Context.PRINT_SERVICE);
StringjobName=this.getString(R.string.app_name)+
”Document”;
printManager.print(jobName,new
MyPrintDocumentAdapter(this),
null);
}
.
.
.
}
ThismethodobtainsareferencetothePrintManagerservicerunningonthedevicebefore
creatinganewStringobjecttoserveasthejobnamefortheprinttask.Finallytheprint()
methodofthePrintManageriscalledtostarttheprintjob,passingthroughthejobname
andaninstanceofourcustomprintdocumentadapterclass.
60.9TestingtheApplication
CompileandruntheapplicationonaphysicalAndroiddevicethatisrunningAndroid4.4
orlater.Whentheapplicationhasloaded,touchthe“PrintDocument”buttontoinitiate
theprintjobandselectasuitabletargetfortheoutput(theSavetoPDFoptionisauseful
optionforavoidingwastingpaperandprinterink).
Checktheprintedoutputwhichshouldconsistof4pagesincludingtextandgraphics.
Figure60-3,forexample,showsthefourpagesofthedocumentviewedasaPDFfile
readytobesavedonthedevice:
Figure60-3
Experimentwithotherprintconfigurationoptionssuchaschangingthepapersize,
orientationandpagessettingswithintheprintpanel.Eachsettingchangeshouldbe
reflectedintheprintedoutput,indicatingthatthecustomprintdocumentadapteris
functioningcorrectly.
60.10Summary
AlthoughmorecomplextoimplementthantheAndroidPrintingframeworkHTMLand
imageprintingoptions,customdocumentprintingprovidesconsiderableflexibilityin
termsofprintingcomplexcontentfromwithinanAndroidapplication.Themajorityofthe
workinvolvedinimplementingcustomdocumentprintinginvolvesthecreationofa
customPrintAdapterclasssuchthatitnotonlydrawsthecontentonthedocumentpages,
butalsorespondscorrectlyaschangesaremadebytheusertoprintsettingssuchasthe
pagesizeandrangeofpagestobeprinted.
61.HandlingDifferentAndroidDevicesand
Displays
BeforebeingmadeavailableforpurchaseontheGooglePlayAppStore,anapplication
mustfirstbesubmittedtotheportalforreviewandapproval.Oneofthemostimportant
stepstotakebeforesubmittinganapplicationistodecidewhichAndroiddevicemodels
theapplicationisintendedtosupportand,moreimportantly,thattheapplicationruns
withoutissueonthosedevices.
Thischapterwillcoversomeoftheareastoconsiderwhenmakingsurethatanapplication
runsonthewidestpossiblerangeofAndroiddevices.
61.1HandlingDifferentDeviceDisplays
Androiddevicescomeinavarietyofdifferentscreensizesandresolutions.Theideal
solutionistodesigntheuserinterfaceofyourapplicationsothatitappearscorrectlyon
thewidestpossiblerangeofdevices.Thebestwaytoachievethisistodesigntheuser
interfaceusinglayoutmanagersthatdonotrelyonabsolutepositioning(inotherwords
specificXandYcoordinates)suchastheRelativeLayoutsothatviewsarepositioned
relativetoboththesizeofthedisplayandeachother.
Similarly,avoidusingspecificwidthandheightpropertieswhereverpossible.Whensuch
propertiesareunavoidable,alwaysusedensity-independent(dp)valuesastheseare
automaticallyscaledtomatchthedevicedisplayatapplicationruntime.
Havingdesignedtheuserinterface,besuretotestitoneachdeviceonwhichitisintended
tobesupported.Intheabsenceofthephysicaldevicehardware,usetheemulator
templateswhereverpossibletotestonthewidestpossiblerangeofdevices.
Intheeventthatitisnotpossibletodesigntheuserinterfacesuchthatasingledesignwill
workonallAndroiddevices,anotheroptionistoprovideadifferentlayoutforeach
display.
61.2CreatingaLayoutforeachDisplaySize
Theidealsolutiontothemultipledisplayproblemistodesignuserinterfacelayoutsthat
adapttothedisplaysizeofthedeviceonwhichtheapplicationisrunning.This,for
example,hastheadvantageofhavingonlyonelayouttomanagewhenmodifyingthe
application.Inevitably,however,therewillbesituationswherethisidealisunachievable
giventhevastdifferenceinscreensizebetweenaphoneandatablet.Anotheroptionisto
providedifferentlayouts,eachtailoredforaspecificdisplaycategory.Thisinvolves
identifyingthesmallestwidthqualifiervalueofeachdisplayandcreatinganXMLlayout
fileforeachone.Thesmallestwidthvalueofadisplayindicatestheminimumwidthof
thatdisplaymeasuredindpunits.
Display-specificlayoutsareimplementedbycreatingadditionalsub-directoriesunderthe
resdirectoryofaproject.Thenamingconventionforthesefoldersis:
layout-<smallest-width>
Forexample,layoutresourcefoldersforarangeofdevicesmightbeconfiguredas
follows:
· res/layout–Thedefaultlayoutfile
· res/layout-sw200dp
· res/layout-sw600dp
· res/layout-sw800dp
Alternatively,moregeneralcategoriescanbecreatedbytargetingsmall,normal,largeand
xlargedisplays:
· res/layout–Thedefaultlayoutfile
· res/layout-small
· res/layout-normal
· res/layout-large
· res/layout-xlarge
Eachfoldermust,inturn,containacopyofthelayoutXMLfileadaptedforthe
correspondingdisplay,allofwhichmusthavematchingfilenames.Onceimplemented,
theAndroidruntimesystemwillautomaticallyselectthecorrectlayoutfiletodisplayto
theusertomatchthedevicedisplay.
61.3ProvidingDifferentImages
Userinterfacelayoutsarenottheonlyareaofconcernwhenadaptinganapplicationfor
differentscreendensities,dimensionsandaspectratios.Anotherareatopayattentiontois
thatofimages.Animagethatappearscorrectlyscaledonalargetabletscreen,for
example,mightnotappearcorrectlyscaledonasmallerphonebaseddevice.Aswith
layouts,however,multiplesetsofimagescanbebundledwiththeapplication,each
tailoredforaspecificdisplay.Thiscanonceagainbeachievedbyreferencingthesmallest
widthvalue.Inthiscase,drawablefoldersneedtobecreatedintheresdirectory.For
example:
· res/drawable–Thedefaultimagefolder
· res/drawable-sw200dp
· res/drawable-sw600dp
· res/drawable-sw800dp
Havingcreatedthefolders,simplyplacethedisplayspecificversionsoftheimagesinto
thecorrespondingfolder,usingthesamenameforeachoftheimages.
Alternatively,theimagesmaybecategorizedintobroaderdisplaydensitiesusingthe
followingdirectoriesbasedonthepixeldensityofthedisplay:
· res/drawable-ldpi-Imagesforlowdensityscreens(approx.120dpi)
· res/drawable-mdpi–Imagesformedium-densityscreens(approx.160dpi)
· res/drawable-hdpi–Imagesforhigh-densityscreens(approx.240dpi)
· res/drawable-xhdpi–Imagesforextrahigh-densityscreens(approx.320dpi)
· res/drawable-tvdpi–Imagesfordisplaysbetweenmediumandhighdensity(approx.
213dpi)
· res/drawable-nodpi–Imagesthatmustnotbescaledbythesystem
61.4CheckingforHardwareSupport
Bynow,itshouldbeapparentthatnotallAndroiddeviceswerecreatedequal.An
applicationthatmakesuseofspecifichardwarefeatures(suchasamicrophoneorcamera)
shouldincludecodetogracefullyhandletheabsenceofthathardware.Thistypically
involvesperformingachecktofindoutifthehardwarefeatureismissing,and
subsequentlyreportingtotheuserthatthecorrespondingapplicationfunctionalitywillnot
beavailable.
Thefollowingmethodcanbeusedtocheckforthepresenceofamicrophone:
protectedbooleanhasMicrophone(){
PackageManagerpmanager=this.getPackageManager();
returnpmanager.hasSystemFeature(
PackageManager.FEATURE_MICROPHONE);
}
Similarly,thefollowingmethodisusefulforcheckingforthepresenceofafrontfacing
camera:
privatebooleanhasCamera(){
if(getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CAMERA_FRONT)){
returntrue;
}else{
returnfalse;
}
}
61.5ProvidingDeviceSpecificApplicationBinaries
Evenwiththebestofintentions,therewillinevitablybesituationswhereitisnotpossible
totargetallAndroiddeviceswithinasingleapplication(thoughGooglecertainly
encouragesdeveloperstotargetasmanydevicesaspossiblewithinasingleapplication
binarypackage).Inthissituation,theapplicationsubmissionprocessallowsmultiple
applicationbinariestobeuploadedforasingleapplication.Eachbinaryisthenconfigured
toindicatetoGooglethedeviceswithwhichthebinaryisconfiguredtowork.Whena
usersubsequentlypurchasestheapplication,Googleensuresthatthecorrectbinaryis
downloadedfortheuser’sdevice.
Itisalsoimportanttobeawarethatitmaynotalwaysmakesensetotrytoprovidesupport
foreveryAndroiddevicemodel.Thereislittlepoint,forexample,inmakingan
applicationthatreliesheavilyonaspecifichardwarefeatureavailableondevicesthatlack
thatspecifichardware.TheserequirementscanbedefinedusingGooglePlayFiltersas
outlinedat:
http://developer.android.com/google/play/filters.html
61.6Summary
ThereismoretocompletinganAndroidapplicationthanmakingsureitworksonasingle
devicemodel.BeforeanapplicationissubmittedtotheGooglePlayDeveloperConsole,it
shouldfirstbetestedonaswidearangeofdisplaysizesaspossible.Thisincludesmaking
surethattheuserinterfacelayoutsandimagesscalecorrectlyforeachdisplayvariation
andtakingstepstoensurethattheapplicationgracefullyhandlestheabsenceofcertain
hardwarefeatures.Itisalsopossibletosubmittothedeveloperconsoleadifferent
applicationbinaryforspecificAndroidmodels,ortostatethataparticularapplication
simplydoesnotsupportcertainAndroiddevices.
62.SigningandPreparinganAndroid
ApplicationforRelease
OncethedevelopmentworkonanAndroidapplicationiscompleteandithasbeentested
onawiderangeofAndroiddevices,thenextstepistopreparetheapplicationfor
submissiontotheGooglePlayAppStore.Beforesubmissioncantakeplace,however,the
applicationmustbepackagedforreleaseandsignedwithaprivatekey.Thischapterwill
workthroughthestepsinvolvedinobtainingaprivatekeyandpreparingtheapplication
packageforrelease.
62.1TheReleasePreparationProcess
Upuntilthispointinthebookwehavebeenbuildingapplicationprojectsinamode
suitablefortestinganddebugging.Buildinganapplicationpackageforreleaseto
customersviatheGooglePlaystore,ontheotherhand,requiresthatsomeadditionalsteps
betaken.Thefirstrequirementisthattheapplicationbecompiledinreleasemodeinstead
ofdebugmode.Secondly,theapplicationmustbesignedwithaprivatekeythatuniquely
identifiesyouastheapplication’sdeveloper.Finally,theapplicationpackagemustbe
aligned.Thisissimplyaprocessbywhichsomedatafilesintheapplicationpackageare
formattedwithacertainbytealignmenttoimproveperformance.
WhileeachofthesetaskscanbeperformedoutsideoftheAndroidStudioenvironment,
theprocedurescanmoreeasilybeperformedusingtheAndroidStudiobuildmechanism
asoutlinedintheremainderofthischapter.
62.2ChangingtheBuildVariant
ThefirststepintheprocessofgeneratingasignedapplicationAPKfileinvolveschanging
thebuildvariantfortheprojectfromdebugtorelease.ThisisachievedusingtheBuild
Variantstoolwindowwhichcanbeaccessedfromthetoolwindowquickaccessmenu
(locatedinthebottomlefthandcorneroftheAndroidStudiomainwindowasshownin
Figure62-1).
Figure62-1
OncetheBuildVariantstoolwindowisdisplayed,changetheBuildVariantsettingsforall
themoduleslistedfromdebugtorelease:
Figure62-2
Theprojectisnowconfiguredtobuildinreleasemode.Thenextstepistoconfigure
signingkeyinformationforusewhengeneratingthesignedapplicationpackage.
62.3EnablingProGuard
Whengeneratinganapplicationpackage,theoptionisavailabletouseProGuardduring
thepackagecreationprocess.ProGuardperformsaseriesofoptimizationandverification
tasksthatresultinsmallerandmoreefficientbytecode.InordertouseProGuard,itis
necessarytoenablethisfeaturewithintheProjectStructuresettingspriortogeneratingthe
APKfile.
ThestepstoenableProGuardinAndroidStudio1.2areasfollows:
1. DisplaytheProjectStructuredialog(File->ProjectStructure).
2. Selectthe“app”moduleinthefarleftpanel.
3. Selectthe“BuildTypes”tabinthemainpanelandthe“release”entryfromthemiddle
panel.
4. Changethe“MinifyEnabled”optionfrom“false”to“true”andclickonOK.
5. FollowthestepstocreateakeystorefileandbuildthereleaseAPKfile.
62.4CreatingaKeystoreFile
Tocreateakeystorefile,selecttheBuild->GenerateSignedAPK…menuoptionto
displaytheGenerateSignedAPKWizarddialogasshowninFigure62-3:
Figure62-3
Intheeventthatyouhaveanexistingreleasekeystorefile,clickontheChooseexisting…
buttonandnavigatetoandselectthefile.Intheeventthatyouhaveyettocreatea
keystorefile,clickontheCreatenew…buttontodisplaytheNewKeyStoredialog(Figure
62-4).ClickonthebuttontotherightoftheKeystorepathfieldandnavigatetoasuitable
locationonyourfilesystem,enteranameforthekeystorefile(forexample
release.keystore.jks)andclickontheOKbutton.
TheNewKeyStoredialogisnowdividedintotwosections.Thetopsectionrelatestothe
keystorefile.Inthissection,enterastrongpasswordwithwhichtoprotectthekeystore
fileintoboththePasswordandConfirmfields.Thelowersectionofthedialogrelatesto
thereleasekeythatwillbestoredinthekeystorefile.
Figure62-4
62.5GeneratingaPrivateKey
Thenextstepistogenerateanewprivatekeywhichwillbeusedtosigntheapplication
package.WithintheKeysectionoftheNewKeyStoredialog,enterthefollowingdetails:
· Analiasbywhichthekeywillbereferenced.Thiscanbeanysequenceofcharacters,
thoughonlythefirst8areusedbythesystem.
· Asuitablystrongpasswordtoprotectthekey.
· Thenumberofyearsforwhichthekeyistobevalid(Googlerecommendsaduration
inexcessof27years).
Inaddition,informationmustbeprovidedforatleastoneoftheremainingfields(for
exampleyourfirstandlastnameororganizationname).
Figure62-5
Oncetheinformationhasbeenentered,clickontheNextbuttontoproceedwiththe
packagecreation.
62.6CreatingtheApplicationAPKFile
ThenexttasktobeperformedistoinstructAndroidStudiotobuildtheapplicationAPK
packagefileinreleasemodeandthensignitwiththenewlycreatedprivatekey.Atthis
pointtheGenerateSignedAPKWizarddialogshouldstillbedisplayedwiththekeystore
path,passwordsandkeyaliasfieldspopulatedwithinformation:
Figure62-6
Assumingthatthesettingsarecorrect,clickontheNextbuttontoproceedtotheAPK
generationscreen(Figure62-7).Withinthisscreen,reviewtheDestinationAPKpath:
settingtoverifythatthelocationintowhichtheAPKfilewillbegeneratedisacceptable.
Intheeventthatanotherlocationispreferred,clickonthebuttontotherightofthetext
fieldandnavigatetothedesiredfilesystemlocationandclickFinish.
Figure62-7
TheGradlesystemwillnowcompiletheapplicationinreleasemode.Oncethebuildis
complete,adialogwillappearprovidingtheoptiontoopenthefoldercontainingtheAPK
fileinanexplorerwindow:
Figure62-8
AtthispointtheapplicationisreadytobesubmittedtotheGooglePlaystore.
Theprivatekeygeneratedaspartofthisprocessshouldbeusedwhensigningand
releasingfutureapplicationsand,assuch,shouldbekeptinasafeplaceandsecurely
backedup.
ThefinalstepintheprocessofbringinganAndroidapplicationtomarketinvolves
submittingittotheGooglePlayDeveloperConsole.Oncesubmitted,theapplicationwill
beavailablefordownloadfromtheGooglePlayAppStore.
62.7RegisterforaGooglePlayDeveloperConsoleAccount
ThefirststepintheapplicationsubmissionprocessistocreateaGooglePlayDeveloper
Consoleaccount.Todoso,navigatetohttps://play.google.com/apps/publish/signup/and
followtheinstructionstocompletetheregistrationprocess.Notethatthereisaone-time
$25feetoregister.Onceanapplicationgoesonsale,Googlewillkeep30%ofall
revenuesassociatedwiththeapplication.
Oncetheaccounthasbeencreated,thenextstepistogathertogetherinformationabout
theapplication.Inordertobringyourapplicationtomarket,thefollowinginformation
willberequired:
· Title–Thetitleoftheapplication.
· ShortDescription-Upto80wordsdescribingtheapplication.
· Description–Upto4000wordsdescribingtheapplication.
· Screenshots–Upto8screenshotsofyourapplicationrunning(aminimumoftwois
required).Googlerecommendssubmittedscreenshotsoftheapplicationrunningona
7”or10”tablet.
· Language–Thelanguageoftheapplication(thedefaultisUSEnglish).
· PromotionalText–Thetextthatwillbeusedwhenyourapplicationappearsin
specialpromotionalfeatureswithintheGooglePlayenvironment.
· ApplicationType–Whetheryourapplicationisconsideredtobeagameoran
application.
· Category–Thecategorythatbestdescribesyourapplication(forexamplefinance,
healthandfitness,education,sportsetc.).
· Locations–Thegeographicallocationsintowhichyouwishyourapplicationtobe
madeavailableforpurchase.
· ContactDetails–Methodsbywhichusersmaycontactyouforsupportrelatingtothe
application.Optionsincludeweb,emailandphone.
· Pricing&Distribution–Informationaboutthepriceoftheapplicationandthe
geographicallocationswhereitistobemarketedandsold.
Havingcollectedtheaboveinformationandpreparedtheapplicationpackagefilefor
release,simplyfollowthestepsintheGooglePlayDeveloperConsoletosubmitthe
applicationfortestingandsale.
62.8UploadingNewAPKVersionstotheGooglePlayDeveloperConsole
ThefirstAPKfileuploadedforyourapplicationwillinvariablyhaveaversioncodeof1.
IfanattemptismadetouploadanotherAPKfilewiththesameversioncodenumber,the
consolewillrejectthefilewiththefollowingerror:
YouneedtouseadifferentversioncodeforyourAPKbecauseyou
alreadyhaveonewithversioncode1.
Toresolvethisproblem,theversioncodeembeddedintotheAPKneedstobeincreased.
Thisisperformedinthemodulelevelbuild.gradlefileoftheproject,shownhighlightedin
Figure62-9.Itisimportanttonotethatthisisnotthetoplevelbuild.gradlefilepositioned
lowerintheprojecthierarchylisting:
Figure62-9
Bydefault,thisfilewilltypicallyreadasfollows:
applyplugin:‘com.android.application’
android{
compileSdkVersion23
buildToolsVersion“23.0.1”
defaultConfig{
applicationId“com.ebookfrenzy.customprint”
minSdkVersion21
targetSdkVersion23
versionCode1
versionName“1.0”
}
buildTypes{
release{
minifyEnabledtrue
proguardFilesgetDefaultProguardFile(‘proguardandroid.txt’),‘proguard-rules.pro’
}
debug{
minifyEnabledtrue
}
}
}
dependencies{
compilefileTree(dir:‘libs’,include:[‘*.jar’])
testCompile‘junit:junit:4.12’
compile‘com.android.support:appcompat-v7:23.1.0’
}
Tochangetheversioncode,simplychangethenumberdeclarednexttoversionCode.To
alsochangetheversionnumberdisplayedtousersofyourapplication,changethe
versionNamestring.Forexample:
versionCode2
versionName“2.0”
Havingmadethesechanges,rebuildtheAPKfileandperformtheuploadagain.
62.9Summary
BeforeanapplicationcanbesubmittedtotheGooglePlaystore,itmustfirstbebuiltin
releasemode,signedwithaprivatecertificateandtheresultingAPKpackagefile
subjectedtoaprocessreferredtoasalignment.Asoutlinedinthischapter,allofthese
stepscanbeperformedwithrelativeeasethroughtheuseoftheAndroidStudiobuild
system.
63.IntegratingGooglePlayIn-appBilling
intoanAndroidApplication
IntheearlydaysofmobileapplicationsforoperatingsystemssuchasAndroidandiOS,
themostcommonmethodforearningrevenuewastochargeanupfrontfeetodownload
andinstalltheapplication.Anotherrevenueopportunitywassoonintroducedintheform
ofembeddingadvertisingwithinapplications.Perhapsthemostcommonandlucrative
optionisnowtochargetheuserforpurchasingitemsfromwithintheapplicationafterit
hasbeeninstalled.Thistypicallytakestheformofaccesstoahigherlevelinagame,
acquiringvirtualgoodsorcurrency,orsubscribingtothedigitaleditionofamagazineor
newspaper.
GoogleprovidessupportfortheintegrationofinapppurchasingthroughtheGooglePlay
In-AppBillingAPI.Thepurposeofthischapteristoworkthroughatutorialthat
demonstratesthestepsinvolvedinimplementingbasicGooglePlaybasedin-appbilling
withinanAndroidapplication.
63.1InstallingtheGooglePlayBillingLibrary
AprerequisitetoimplementingGooglePlayIn-appBillingisthattheGooglePlayBilling
Librarybeinstalledonthedevelopmentsystem.Checkwhetherornotthelibraryis
installedbylaunchingtheAndroidSDKManagerbyselectingConfigure->SDK
ManagerfromtheAndroidStudiowelcomescreen,orviatheTools->Android->SDK
Managermenuofthemainwindow.OncetheSDKsettingshaveloaded,selecttheSDK
ToolstabandchecktheStatuscolumnnexttotheGooglePlayBillingLibraryentryas
showninFigure63-1.
Ifthelibrary’sstatusislistedasNotInstalled,selectthecheckboxnexttothelibraryand
clickontheApplybutton.Oncethedownloadhascompleted,theSDKwillhavebeen
installedintothesdk/extras/google/play_billingsubfolderoftheAndroidStudio
installationdirectory(thelocationofwhichcanbefoundintheSDKPathfieldatthetop
oftheAndroidSDKsettingswindow).
Figure63-1
WithintheaboveSDKsubfolderresidesafilenamedIInAppBillingService.aidlwhichwill
needtobeincludedwithanyprojectsthatrequireGooglePlaybillingsupport.Thefolder
alsoincludesasampleapplication(containedwithinthesamplessub-directory)named
TrivialDrive.Partofthissampleapplicationisapackagecontainingasetofconvenience
classesthatsignificantlyeasetheprocessofintegratingbillingintoanapplication.Laterin
thistutorial,theseclasseswillbeimportedintoourownapplicationprojectandusedto
implementin-appbilling.
63.2CreatingtheExampleIn-appBillingProject
TheobjectiveofthistutorialistocreateasimpleapplicationthatusestheGooglein-app
billingsystemtoallowconsumablepurchasestobemade.Theapplicationwillconsistof
twobuttons,oneofwhichwillbedisabledbydefault.Inordertoenablethebuttonsothat
itcanbeclicked,theusermustpurchasea“buttonclick”itembyclickingonthesecond
buttonandcompletingapurchase.Thefirstbuttonwillthenbeenabledforasingleclick
beforebeingdisabledagainuntiltheusermakesanotherpurchase.
CreateanewprojectinAndroidStudio,enteringInAppBillingintotheApplicationname
fieldandyourownURLastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI19:Android4.4(KitKat).Continuetoproceedthroughthescreens,
requestingthecreationofanemptyactivitynamedInAppBillingActivitywitha
correspondinglayoutresourcefilenamedactivity_in_app_billing.
ClickonFinishtoinitiatetheprojectcreationprocess.
63.3AddingBillingPermissiontotheManifestFile
Beforeanapplicationcansupportin-appbilling,anewpermissionlinemustbeaddedto
theproject’sAndroidManifest.xmlfile.WithintheProjecttoolwindow,therefore,locate
andloadtheAndroidManifest.xmlfileforthenewlycreatedInAppBillingprojectand
modifyittoaddthebillingpermissionasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.ebookfrenzy.inappbilling”>
<uses-permissionandroid:name=“com.android.vending.BILLING”/>
<application
android:allowBackup=“true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.InAppBillingActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=
“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
63.4AddingtheIInAppBillingService.aidlFiletotheProject
TheIInAppBillingService.aidlfileincludedaspartoftheGooglePlayBillingLibrary
shouldnowbeaddedtotheproject.Thisfilemustbeaddedsuchthatitiscontainedina
packagenamedcom.android.vending.billinglocatedintheapp->aidlfolderofthe
InAppBillingprojectmodule.
Tocreatetheaidldirectory,right-clickontheappnodeintheprojecttoolwindow,
selectingtheNew->Folder->AIDLFoldermenuoptionasshowninFigure63-2:
Figure63-2
Intheresultingoptionsdialog,acceptthedefaultsbyclickingontheFinishbutton.
WithintheProjecttoolwindowtheaidlfoldershouldnowbelisted.Thenextstepisto
createthecom.android.vending.billingpackage.Right-clickontheaidlfolderandselect
theNew->Packagemenuitem.Intheresultingdialog,entercom.android.vending.billing
intothetextfieldandclickonOK.
Usingtheexplorerorfindertoolforyouroperatingsystem,navigatetothe<sdk
path>/sdk/extras/google/play_billingwhere<sdkpath>isreplacedbythepathintowhich
youinstalledtheAndroidSDK.Fromthislocation,copytheIInAppBillingService.aidl
file,returntoAndroidStudioandpastethefileontothecom.android.vending.billing
packageintheProjecttoolwindow.IntheCopydialog,acceptthedefaultsettingsand
clickontheOKbutton.AtthispointtherelevantsectionsoftheProjecttoolwindow
shouldbeorganizedtomatchthatofFigure63-3:
Figure63-3
Withthelibraryfileinstalled,thenextstepistoimporttheutilityclassesfromthe
TrivialDrivesampleintotheprojectsothatthesecanbeutilizedwithintheapplication
code.
63.5AddingtheUtilityClassestotheProject
TheTrivialDrivesampleprojectthatwasinstalledintotheSDKaspartoftheGooglePlay
Billinglibraryincludesasetofclassesintendedspecificallytomakethetaskof
implementingin-appbillingeasier.AlthoughbundledaspartoftheTrivialDriveproject,
theseclassesaregeneralpurposeinnatureandareapplicabletomostapplicationbilling
requirements.Forthepurposesofthisexample,wewillcreateanewpackagenamed
com.ebookfrenzy.inappbilling.utilwithinourproject.
Tocreatethispackage,right-clickontheapp->javafolderintheProjecttoolwindow
andselecttheNew->Packagemenuoption.Intheresultingdialog,select
..\app\src\main\javafromtheDirectoryStructurepanelandclickonOK.Inthenext
dialognamethepackage<yourdomain>.inappbilling.util(where<yourdomain>is
replacedbythereverseddomainenteredwhentheprojectwascreated,forexample
com.mycompany)andclickonOK.
ThenextstepistoimporttheTrivialDriveutilityclassfilesintotheAndroidStudio
project.Returningtothefilesystemexplorerwindow(andassumingitisstillpositioned
inthe<sdkpath>/sdk/extras/google/play_billingdirectory),navigatefurtherintothefile
systemhierarchytothefollowingdirectory:
samples/TrivialDrive/src/com/example/android/trivialdrivesample/util
Selectallninefilesinthisfolderandcopythem.ReturntoAndroidStudioandpastethe
filesontothecom.example.inappbilling.inappbilling.utilpackageintheProjecttool
window.VerifythattheprojecthierarchynowmatchesFigure63-4:
Figure63-4
63.6DesigningtheUserInterface
Theuserinterface,aspreviouslyoutlined,isgoingtoconsistoftwobuttons,thefirstof
whichcanonlybeclickedafterapurchasehasbeenmadeviaaclickperformedonthe
secondbutton.Double-clickontheapp->res->layout->activity_in_app_billing.xml
filetoloaditintotheDesignertoolanddesigntheuserinterfacesothatitresemblesthat
ofFigure63-5:
Figure63-5
Extractthebuttontextstringstostringresourcesnamedclick_stringandbuy_stringand,
withtheuserinterfacelayoutdesigned,switchtheDesignertooltoTextmodeandname
thebuttonsclickButtonandbuyButtonrespectively.Also,setonClickpropertiesto
configurethebuttonstocallmethodsnamedbuttonClickedandbuyClicksuchthatthe
completedXMLlayoutfilereadsasfollows:
<?xmlversion=“1.0”encoding=“utf-8”?>
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=”.InAppBillingActivity”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/click_string”
android:id=”@+id/clickButton”
android:layout_alignParentTop=“true”
android:layout_centerHorizontal=“true”
android:layout_marginTop=“113dp”
android:onClick=“buttonClicked”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/buy_string”
android:id=”@+id/buyButton”
android:layout_centerVertical=“true”
android:layout_centerHorizontal=“true”
android:onClick=“buyClick”/>
</RelativeLayout>
Withtheuserinterfacedesigncomplete,itistimetostartwritingsomeJavacodeto
handlethepurchasingandconsumptionofclicks.
63.7Implementingthe“ClickMe”Button
Whentheapplicationisinitiallylaunched,the“ClickMe!”buttonwillbedisabled.To
makesurethatthishappens,loadtheInAppBillingActivity.javafileintotheeditorand
modifytheonCreatemethodtoobtainareferencetobothbuttonsandthendisablethe
clickButton:
packagecom.ebookfrenzy.inappbilling;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.Button;
publicclassInAppBillingActivityextendsAppCompatActivity{
privateButtonclickButton;
privateButtonbuyButton;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_in_app_billing);
buyButton=(Button)findViewById(R.id.buyButton);
clickButton=(Button)findViewById(R.id.clickButton);
clickButton.setEnabled(false);
}
.
.
.
}
ThebuttonClickedmethodthatwillbecalledwhenthebuttonisclickedbytheusernow
alsoneedstobeimplemented.Allthismethodneedstodoistodisablethebuttononce
againsothatthebuttoncannotbeclickeduntilanotherpurchaseismadeandtoenablethe
buybuttonsothatanotherclickcanbepurchased.Remainingwithinthe
InAppBillingActivity.javafile,implementthismethodasfollows:
packagecom.ebookfrenzy.inappbilling;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.Button;
importandroid.view.View;
publicclassInAppBillingActivityextendsAppCompatActivity{
.
.
.
publicvoidbuttonClicked(Viewview)
{
clickButton.setEnabled(false);
buyButton.setEnabled(true);
}
.
.
}
Workonthefunctionalityofthefirstbuttonisnowcomplete.Thenextstepsaretobegin
implementingthein-appbillingfunctionality.
63.8GooglePlayDeveloperConsoleandGoogleWalletAccounts
ApplicationdevelopersmakinguseofGooglePlaybillingmustbeidentifiedbyaunique
publiclicensekey.Theonlywaytoobtainapubliclicensekeyistoregisteranapplication
withintheGooglePlayDeveloperConsole.IfyoudonotalreadyhaveaGooglePlay
DeveloperConsoleaccount,gotohttp://play.google.com/apps/publishandfollowthe
stepstoregisterasoutlinedinthechapterentitledSigningandPreparinganAndroid
ApplicationforRelease.
Onceyouareloggedin,clickontheSettingsoption(locatedonthelefthandedgeofthe
webpage)and,ontheAccountdetailspage,scrolldowntotheMerchantAccountsection.
Inordertousein-appbilling,yourGooglePlayDeveloperConsoleaccountmusthavea
GoogleWalletMerchantaccountassociatedwithit.IfaGoogleWalletmerchantaccount
isnotsetup,createamerchantaccountandregisteritwithyourGoogleDeveloper
Consoleaccountbeforeproceeding.
63.9ObtainingthePublicLicenseKeyfortheApplication
FromthehomepageoftheGooglePlayDeveloperConsole,clickontheAddnew
applicationbutton,specifyingthedefaultlanguageandatitleofInAppBilling.Oncethis
informationhasbeenentered,clickontheUploadAPKbutton:
Figure63-6
ItisnotnecessarytouploadtheAPKfileatthispoint,sooncetheapplicationhasbeen
registered,clickontheServices&APIsoptiontodisplaytheBase64-encodedRSApublic
keyfortheapplicationasshowninFigure63-7:
Figure63-7
KeepthisBrowserwindowopenfornowasthiskeywillneedtobeincludedinthe
applicationcodeinthenextstepofthistutorial.
63.10SettingUpGooglePlayBillingintheApplication
Withthepublickeygenerated,itisnowtimetousethatkeytoinitializebillingwithinthe
applicationcode.FortheInAppBillingexampleprojectthiswillbeperformedinthe
onCreatemethodoftheInAppBillingActivity.javafileandwillmakeuseoftheIabHelper
classfromtheutilitiesclassespreviouslyaddedtotheprojectasfollows.Notethat<your
licensekeyhere>shouldbereplacedbyyourownlicensekeygeneratedintheprevious
sectionand<yourdomain>bythepackagenameusedtoholdtheutilityclassfiles:
packagecom.ebookfrenzy.inappbilling;
import<yourdomain>.inappbilling.util.IabHelper;
import<yourdomain>.inappbilling.util.IabResult;
import<yourdomain>.inappbilling.util.Inventory;
import<yourdomain>.inappbilling.util.Purchase;
importandroid.support.v7.app.AppCompatActivity;
importandroid.os.Bundle;
importandroid.widget.Button;
importandroid.view.View;
importandroid.content.Intent;
importandroid.util.Log;
publicclassInAppBillingActivityextendsAppCompatActivity{
privatestaticfinalStringTAG=
“InAppBilling”;
IabHelpermHelper;
privateButtonclickButton;
privateButtonbuyButton;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_in_app_billing);
buyButton=(Button)findViewById(R.id.buyButton);
clickButton=(Button)findViewById(R.id.clickButton);
clickButton.setEnabled(false);
Stringbase64EncodedPublicKey=
“<yourlicensekeyhere>”;
mHelper=newIabHelper(this,base64EncodedPublicKey);
mHelper.startSetup(new
IabHelper.OnIabSetupFinishedListener(){
publicvoidonIabSetupFinished(IabResultresult)
{
if(!result.isSuccess()){
Log.d(TAG,“In-appBillingsetupfailed:”+
result);
}else{
Log.d(TAG,“In-appBillingissetupOK”);
}
}
});
}
.
.
.
}
Afterimplementingtheabovechanges,compileandruntheapplicationonaphysical
Androiddevice(GooglePlayBillingcannotbetestedwithinanemulatorsession)and
makesurethatthe“In-appBillingissetupOK”messageappearsintheLogCatoutput
panel.
63.11InitiatingaGooglePlayIn-appBillingPurchase
Withaccesstothebillingsysteminitialized,wecannowturnourattentiontoinitiatinga
purchasewhentheusertouchestheBuyClickbuttonintheuserinterface.Thiswas
previouslyconfiguredtotriggeracalltoamethodnamedbuyClickwhichnowneedstobe
implementedintheInAppBillingActivity.javafile.Inadditiontoinitiatingthepurchase
processinthismethod,itwillbenecessarytoimplementanonActivityResultmethodand
alsoalistenermethodtobecalledwhenthepurchasehascompleted.
BeginbyeditingtheInAppBillingActivity.javafileandaddingthecodeforthebuyClick
methodsothatitreadsasfollows:
.
.
.
publicclassInAppBillingActivityextendsAppCompatActivity{
privatestaticfinalStringTAG=“InAppBilling”;
IabHelpermHelper;
staticfinalStringITEM_SKU=“android.test.purchased”;
.
.
.
publicvoidbuyClick(Viewview){
mHelper.launchPurchaseFlow(this,ITEM_SKU,10001,
mPurchaseFinishedListener,“mypurchasetoken”);
}
.
.
.
}
Clearly,allthismethodneedstodoismakeacalltothelaunchPurchaseFlowmethodof
ourmHelperinstance.Theargumentspassedthroughtothemethodareasfollows:
· AreferencetotheenclosingActivityinstancefromwhichthemethodisbeingcalled.
· TheSKUthatidentifiestheproductthatisbeingpurchased.Inthisinstanceweare
goingtouseastandardSKUprovidedbyGooglefortestingpurposes.ThisSKU,
referredtoasastaticresponseSKU,willalwaysresultinasuccessfulpurchase.Other
testingSKUsavailableforusewhentestingpurchasingfunctionalitywithoutmaking
realpurchasesareandroid.test.cancelled,android.test.refundedand
android.test.item_unavailable.
· Therequestcodewhichcanbeanypositiveintegervalue.Whenthepurchasehas
completed,theonActivityResultmethodwillbecalledandpassedthisintegeralong
withthepurchaseresponse.Thisallowsthemethodtoidentifywhichpurchaseprocess
isreturningandcanbeusefulwhenthemethodneedstobeabletohandlepurchasing
fordifferentitems.
· Thelistenermethodtobecalledwhenthepurchaseiscomplete.
· Thedeveloperpayloadtokenstring.Thiscanbeanystringvalueandisusedto
identifythepurchase.Forthepurposesofthisexample,thisissetto
“mypurchasetoken”.
63.12ImplementingtheonActivityResultMethod
Whenthepurchasingprocessreturns,itwillcallamethodonthecallingactivitynamed
onActivityResult,passingthroughasargumentstherequestcodepassedthroughtothe
launchPurchaseFlowmethod,aresultcodeandintentdatacontainingthepurchase
response.
Thismethodneedstoidentifyifitwascalledasaresultofanin-apppurchaserequestor
somerequestunrelatedtoin-appbilling.ItdoesthisbycallingthehandleActivityResult
methodofthemHelperinstanceandpassingthroughtheincomingarguments.Ifthisisa
purchaserequestthemHelperwillhandleitandreturnatruevalue.Ifthisisnottheresult
ofapurchase,thenthemethodneedstopassituptothesuperclasstobehandled.
Bringingthistogetherresultsinthefollowingcode:
@Override
protectedvoidonActivityResult(intrequestCode,intresultCode,
Intentdata)
{
if(!mHelper.handleActivityResult(requestCode,
resultCode,data)){
super.onActivityResult(requestCode,resultCode,data);
}
}
IntheeventthattheonActivityResultmethodwascalledinresponsetoanin-appbilling
purchase,acallwillthenbemadetothelistenermethodreferencedinthecalltothe
launchPurchaseFlowmethod(inthiscaseamethodnamedmPurchaseFinishedListener).
Thenexttask,therefore,istoimplementthismethod.
63.13ImplementingthePurchaseFinishedListener
The“purchasefinished”listenermustperformanumberofdifferenttasks.Inthefirst
instance,itmustchecktoensurethatthepurchasewassuccessful.Itthenneedstocheck
theSKUofthepurchaseditemtomakesureitmatchestheonespecifiedinthepurchase
request.Intheeventofasuccessfulpurchase,themethodwillneedtoconsumethe
purchasesothattheusercanpurchaseitagainwhenanotheroneisneeded.Ifthepurchase
isnotconsumed,futureattemptstopurchasetheitemwillfailstatingthattheitemhas
alreadybeenpurchased.Whilethiswouldbedesiredbehavioriftheuseronlyneededto
purchasetheitemonce,clearlythisisnotthebehaviorrequiredforconsumablepurchases.
Finally,themethodneedstoenablethe“ClickMe!”buttonsothattheusercanperform
thebuttonclickthatwaspurchased.
WithintheInAppBillingActivity.javafile,implementthismethodasfollows:
IabHelper.OnIabPurchaseFinishedListenermPurchaseFinishedListener
=newIabHelper.OnIabPurchaseFinishedListener(){
publicvoidonIabPurchaseFinished(IabResultresult,
Purchasepurchase)
{
if(result.isFailure()){
//Handleerror
return;
}
elseif(purchase.getSku().equals(ITEM_SKU)){
consumeItem();
buyButton.setEnabled(false);
}
}
};
Ascanbeseenfromtheabovecodefragment,intheeventthatthepurchasewas
successful,amethodnamedconsumeItem()willbecalled.Clearly,thenextstepisto
implementthismethod.
63.14ConsumingthePurchasedItem
InthedocumentationforGooglePlayIn-appBilling,Googlerecommendsthat
consumableitemsbeconsumedbeforeprovidingtheuserwithaccesstothepurchased
item.Sofarinthistutorialwehaveperformedthepurchaseoftheitembutnotyet
consumedit.Intheeventofasuccessfulpurchase,themPurchaseFinishedListener
implementationhasbeenconfiguredtocallamethodnamedconsumeItem().Itwillbethe
responsibilityofthismethodtoquerythebillingsystemtomakesurethatthepurchasehas
beenmade.ThisinvolvesmakingacalltothequeryInventoryAsync()methodofthe
mHelperobject.Thistaskisperformedasynchronouslyfromtheapplication’smainthread
andalistenermethodcalledwhenthetaskiscomplete.Iftheitemhasbeenpurchased,the
listenerwillconsumetheitemviaacalltotheconsumeAsync()methodofthemHelper
object.Bringingtheserequirementstogetherresultsinthefollowingadditionstothe
InAppBillingActivity.javafile:
publicvoidconsumeItem(){
mHelper.queryInventoryAsync(mReceivedInventoryListener);
}
IabHelper.QueryInventoryFinishedListenermReceivedInventoryListener
=newIabHelper.QueryInventoryFinishedListener(){
publicvoidonQueryInventoryFinished(IabResultresult,
Inventoryinventory){
if(result.isFailure()){
//Handlefailure
}else{
mHelper.consumeAsync(inventory.getPurchase(ITEM_SKU),
mConsumeFinishedListener);
}
}
};
Aswiththequery,theconsumptiontaskisalsoperformedasynchronouslyand,inthis
case,isconfiguredtocallalistenernamedmConsumeFinishedListenerwhencompleted.
Thislistenernowneedstobeimplementedsuchthatitenablesthe“ClickMe!”button
aftertheitemhasbeenconsumedinthebillingsystem:
IabHelper.OnConsumeFinishedListenermConsumeFinishedListener=
newIabHelper.OnConsumeFinishedListener(){
publicvoidonConsumeFinished(Purchasepurchase,
IabResultresult){
if(result.isSuccess()){
clickButton.setEnabled(true);
}else{
//handleerror
}
}
};
63.15ReleasingtheIabHelperInstance
Throughoutthistutorial,muchoftheworkhasbeenperformedbycallingmethodsonan
instanceoftheIabHelperutilityclassnamedmHelper.Nowthatthecodetohandle
purchasingandsubsequentconsumptionofavirtualitemiscomplete,thelasttaskisto
makesurethisobjectisreleasedwhentheactivityisdestroyed.Remaininginthe
InAppBillingActivity.javafile,overridetheonDestroy()activitylifecyclemethodas
follows:
@Override
publicvoidonDestroy(){
super.onDestroy();
if(mHelper!=null)mHelper.dispose();
mHelper=null;
}
63.16ModifyingtheSecurity.javaFile
WhenanapplicationiscompiledandinstalledonadevicefromwithinAndroidStudio,it
isbuiltandexecutedindebugmode.Whentheapplicationiscompleteitisthenbuiltin
releasemodeanduploadedtotheGooglePlayAppStoreasdescribedinthechapter
entitledSigningandPreparinganAndroidApplicationforRelease.
AstheInAppBillingapplicationiscurrentlyconfigured,purchasesarebeingmadeusing
theandroid.test.purchasedstaticresponseSKUcode.Itisimportanttobeawarethatstatic
responseSKUscanonlybeusedwhenrunninganapplicationindebugmode.Aswillbe
outlinedlater,newin-appproductsmustbecreatedwithintheGooglePlayDeveloper
Consolebeforefulltestingcanbeperformedinreleasemode.
ThecurrentversionoftheutilityclassesprovidedwiththeTrivialDriveexample
applicationincludeanaddedlevelofsecuritythatpreventspurchasesfrombeingmade
withoutavalidsignaturekeybeingreturnedfromtheGooglePlaybillingserver.Aside
effectofthischangeisthatitpreventsthecodefromfunctioningwhenusingthestatic
responseSKUvalues.Beforetestingtheapplicationindebugmode,therefore,afewextra
linesofcodeneedtobeaddedtotheverifyPurchase()methodintheSecurity.javafile.
WithintheAndroidStudioProjecttoolwindow,selecttheSecurity.javafilelocatedinthe
app->java-><packagename>->utilfolderoftheprojecttoloaditintotheeditor.
Onceloaded,locateandmodifytheverifyPurchase()methodsothatitreadsasfollows:
packagecom.ebookfrenzy.inappbilling.util;
importandroid.text.TextUtils;
importandroid.util.Log;
importorg.json.JSONException;
importorg.json.JSONObject;
import<yourdomain>.inappbilling.BuildConfig;
importjava.security.InvalidKeyException;
importjava.security.KeyFactory;
importjava.security.NoSuchAlgorithmException;
importjava.security.PublicKey;
importjava.security.Signature;
importjava.security.SignatureException;
importjava.security.spec.InvalidKeySpecException;
importjava.security.spec.X509EncodedKeySpec;
.
.
.
publicstaticbooleanverifyPurchase(Stringbase64PublicKey,
StringsignedData,Stringsignature){
if(TextUtils.isEmpty(signedData)||
TextUtils.isEmpty(base64PublicKey)||
TextUtils.isEmpty(signature)){
Log.e(TAG,“Purchaseverificationfailed:missingdata.”);
if(BuildConfig.DEBUG){
returntrue;
}
returnfalse;
}
PublicKeykey=Security.generatePublicKey(base64PublicKey);
returnSecurity.verify(key,signedData,signature);
}
Thiswillensurethatwhentheapplicationisrunningindebugmodethemethoddoesnot
reportanerrorifthesignatureismissingwhenastaticresponseSKUpurchaseisverified.
Bycheckingfordebugmodeinthiscode,weensurethatthissecuritycheckwillfunction
asintendedwhentheapplicationisbuiltinreleasemode.
63.17TestingtheIn-appBillingApplication
CompileandruntheapplicationonaphysicalAndroiddevicewithGooglePlaysupport
andclickonthe“BuyaClick”button.ThisshouldcausetheGooglePlaypurchasedialog
toappearlistingthetestitemasillustratedinFigure63-8:
Figure63-8
ClickontheBuybuttontosimulateapurchaseatwhichpointaPaymentSuccessful
message(Figure63-9)shouldappearafterwhichitshouldbepossibletoclickonthe
“ClickMe!”buttononce.
Figure63-9
Havingconsumedtheclick,itwillbenecessarytopurchaseanotherclickinordertoonce
againenablethebutton.
63.18BuildingaReleaseAPK
Upuntilthispointtheexampleapplicationcreatedinthischapterhasusedastatic
responsetestingSKUprovidedbyGoogleforearlystagetestingofin-appbilling.The
nextstepistocreatearealin-appbillingproductSKUcodeforavirtualitemandusethis
whentestingtheapplication.Beforecreatinganin-appbillingproduct,however,the
applicationcodeneedstobechangedslightlysothatitusesarealSKUinsteadofthe
staticresponseSKU.TheproductSKUthatwillbeusedintheremainderofthischapter
willbenamedcom.example.buttonclick,soedittheInAppBillingActivity.javafileand
modifytheSKUreferenceaccordingly:
publicclassInAppBillingActivityextendsActivity{
privatestaticfinalStringTAG=
“InAppBilling”;
IabHelpermHelper;
staticfinalStringITEM_SKU=“android.test.purchased”;
staticfinalStringITEM_SKU=“com.example.buttonclick”;
privateButtonclickButton;
privateButtonbuyButton;
.
.
.
Beforeanyproductscanbecreated,areleaseAPKfilefortheapplicationmustfirstbe
uploadedtothedeveloperconsole.InordertopreparethisreleaseAPKfileforthe
InAppBillingapplication,followthestepsoutlinedinthechapterentitledSigningand
PreparinganAndroidApplicationforRelease.OncetheAPKfilehasbeencreated,select
thepreviouslyregisteredapplicationfromthelistofapplicationsintheGooglePlay
DeveloperConsoleand,fromtheresultingscreen,clickontheAPKlinkinthelefthand
panel.Whenprompted,uploadthereleaseAPKfiletotheconsole.
63.19CreatingaNewIn-appProduct
OncetheAPKfilehasbeenuploaded,selecttheIn-appProductsmenuitemfromtheleft
handpanelofthedeveloperconsoletodisplaythescreenshowninFigure63-10:
Figure63-10
Toaddanewproduct,clickontheAddnewproductbuttonand,intheresultingpanel,set
theproducttypetoManagedproductandenteraProductID(inthiscase
com.example.buttonclick).ClickonContinueandinthesecondscreenenteratitle,
descriptionandpricefortheitem.ChangethemenuatthetopofthepagetoActivate.
OnreturningtotheIn-appProductshomescreen,thenewproductshouldnowbelisted:
Figure63-11
63.20PublishingtheApplicationtotheAlphaDistributionChannel
TheapplicationAPKfileiscurrentlystoredwithintheGooglePlayDeveloperConsolein
Draftmode.Beforeitcanbeusedforfurthertestingusingrealin-appproducts,the
applicationmustbepublishedtoeithertheAlphaorBetatestingdistributionchannels.
Thesechannelsmaketheapplicationavailablefortestingbydesignatedgroupsofusers.
BeforeanapplicationAPKcanbesubmittedtoeitherofthetestingchannels,thestore
listing,pricinganddistributioninformationfortheapplicationmustbecompleted.To
meetthisrequirement,selecttheapplicationfromtheGooglePlayDeveloperConsoleand
clickontheStoreListinglinkinthelefthandnavigationpanel.Fromwithinthisscreen,
filloutthemandatoryinformation(thoseareasmarkedwithanasterisk)anduploadthe
necessaryimages.Oncetheformiscomplete,clickontheSavebuttonlocatedatthetop
ofthepage.Toconfigurethepricinganddistributioninformation,selectthePricing&
Distributionoptionfromthesidepanelandcompletethenecessaryfields.
Whentherequiredinformationhasbeenprovided,theapplicationiseligibletobe
publishedinthetestingdistributionchannels.Oncealltheinformationhasbeensaved,the
buttoninthetoprighthandcornerofthescreenshouldhavechangedfromDrafttoReady
toPublish.IfthebuttonstillreadsDraft,clickonitandselect“Whycan’tIpublish?”
fromthemenu.Thisoptionwilllistanyissuesthatneedtoberesolvedbeforethe
applicationcanbepublished.
Onceanyissueshavebeenaddressed,clickonthePublishappbutton.Notethatitcan
takeseveralhoursbeforetheapplicationisactuallypublishedtothechannelandavailable
torespondtoin-apppurchaserequestmadebytesters.
63.21AddingIn-appBillingTestAccounts
Unfortunately,Googlewillnotallowdeveloperstomaketestpurchasesusingrealproduct
SKUsfromtheirownGoogleaccounts.Inordertotestin-appbillingfromthispointon,it
willbenecessarytosetupotherGoogleaccountsastestingaccounts.Theusersofthese
accountsmustloadyourapplicationontotheirdevicesandmaketestpurchases.Toadd
individualtestuseraccounts,clickontheSettingsoptionlocatedonthelefthandsideof
yourGooglePlayDeveloperConsolehomescreenandontheaccountdetailsscreenscroll
downtotheLicenseTestingsection.Inthecorrespondingtextbox,entertheGmail
accountsfortheuserswhowillbeperformingthein-apptestingonyourbehalfbefore
savingthechanges.
Intheabsenceofrealuserswillingtotestyourapplication,itisalsopossibletosetupa
newGoogleaccountfortestingpurposes.SimplycreateanewGmailaccountthatisnot
connectedinanywaywithyourexistingGoogleaccountidentity.Oncetheaccounthas
beencreatedandaddedasatestaccountintheGooglePlayDeveloperConsole,openthe
SettingsapplicationonthephysicalAndroiddevice,selectUsersfromthelistofoptions
and,ontheUsersscreen,clickontheAdduseroption.EnterthenewGmailaccountand
passwordinformationtocreatethenewuseronthedevice.Returntothedevicelock
screenandlogintothedeviceasthetestuserbyselectingtheappropriateiconatthe
bottomofthescreen.Notethatduringthetestpurchase,itwillbenecessarytoentercredit
cardinformationforthenewuserinordertobeabletofullytestthein-appbilling
implementation.
Oncetheuserhasbeenaddedasatestaccount,thenextstepistoloadthepreviously
generatedreleaseAPKfileontothedevice.BeginbyenablingthedeviceforUSB
debuggingbyfollowingthestepsinthechapterentitledTestingAndroidStudioAppsona
PhysicalAndroidDevice.Onceenabled,attachthedevicetothedevelopmentsystemand
removeanypreviousversionsoftheapplicationfromthedevicebyrunningthefollowing
inaterminalorcommandpromptwindow,where<packagename>isthefullpackage
nameoftheapplication(forexample<yourdomain>.inappbilling):
adbuninstall<packagename>
Next,uploadthereleaseAPKcreatedearlierinthischapterbyrunningthefollowing
command:
adb–dinstall/path/to/release/apkfile.apk
Oncetheapplicationisinstalled,locateitamongtheapplicationsonthedeviceandlaunch
it.AslongastheapplicationremainsintestingstatuswithintheGooglePlayDeveloper
consolenochargeswillbeincurredbytheuserwhiletestingthein-appbilling
functionalityoftheapplication.NotethattheGooglePlaydialog(Figure63-12)nowlists
theproducttitleandpriceasdeclaredfortheproductintheGooglePlayDeveloper
Console:
Figure63-12
Anytestpurchasesmadeinthiswaywillbelistedwithinthedeveloper’sGoogleWallet
accountbutwillnotbebilledtotheuserperformingthetesting.Anytransactionsnot
cancelledmanuallywithinGoogleWalletwillbecancelledautomaticallyafter14days.
63.22ConfiguringGroupTesting
Testingcanbeexpandedbeyondthelimitsofasingletestaccount,therebyallowinglarger
groupstobeinvolvedinthetestingofanewapplication.Thesecanbespecifiedaslistsof
usersorasgroupsofmembersofaGoogleGrouporGoogle+Community.Toconfigure
suchagroup,accesstheapplicationsettingswithintheGooglePlayDeveloperConsole
andselecttheAPKiteminthelefthandnavigationpanel.Selecteitherthebetaoralpha
testingtab(dependingonwhichtestingdistributionchannelyouarecurrentlyusing)and
selectoneandconfigureoneofthethreetestingoptions(closed,openorgroup)testing
optionsashighlightedinFigure63-13:
Figure63-13
63.23ResolvingProblemswithIn-AppPurchasing
ImplementationofGooglePlayin-apppurchasingisamulti-stepprocesswhereeventhe
simplestofmistakescanleadtounsuccessfulandconfusingresults.Thereare,however,a
numberofstepsthatcanbetakentoidentifyandresolveissues.Itisimportanttonotethat
Googleoccasionallychangesthemechanismforimplementingandtestingin-app
purchasing.Beforespendingtoomuchdebuggingtimeitisalwaysworthcheckingthe
AnnouncementssectionintheGooglePlayDeveloperConsoletoseeifanythinghas
changedsincethisbookwaswritten.
Ifin-apppurchasingisstillnotworking,thenextstepistoensurethatthelicensekeyin
theGooglePlaydeveloperconsolematchesthatcontainedintheapplicationcode.Ifthe
keyisnotanexactmatch,purchaseattemptswillfail.Alsokeepinmindthatitcantakea
fewhoursafteranapplicationhasbeenpublishedtoatestingdistributionchannelbeforeit
willbeavailablefortestingpurposes.
Whentesting,itisalsoimportanttokeepinmindthatstaticresponseSKUcodesonly
workwhentheapplicationisrunningindebugmode.Similarly,realSKUproductcodes
createdinthedeveloperconsolecanonlybepurchasedfromwithinareleaseversionof
theapplicationrunningunderanaccountthathasbeenauthorizedfromwithinthe
developerconsole.Thisaccountmustnotbethesameasthatoftheapplicationdeveloper
registeredwiththeGooglePlaydeveloperconsole.
Ifproblemspersist,checktheoutputontheAndroidStudioLogCatpanelwhilethe
applicationisrunningindebugmode.Thein-apppurchaseutilityclassesprovideuseful
feedbackinmostfailuresituations.Thelevelofdiagnosticdetailcanbeincreasedby
addingthefollowinglineofcodetothein-appbillinginitializationsequence:
mHelper.enableDebugLogging(true,TAG);
Forexample:
mHelper.startSetup(new
IabHelper.OnIabSetupFinishedListener(){
publicvoidonIabSetupFinished(IabResultresult)
{
if(!result.isSuccess()){
Log.d(TAG,“In-appBillingsetupfailed:”+
result);
}else{
Log.d(TAG,“In-appBillingissetupOK”);
mHelper.enableDebugLogging(true,TAG);
}
}
});
}
Finally,itisnotunusualforthedevelopertofindthatcodethatworkedindebugmode
usingstaticresponsesfailswheninreleasemodewithrealSKUvalues.Inthissituation,
theLogCatoutputfromtherunningreleasemodeapplicationcanbeviewedinreal-time
byconnectingthedevicetothedevelopmentcomputerandrunningthefollowingadb
commandinaterminalorcommandpromptwindow:
adblogcat
Generally,thediagnosticmessageswillprovideagoodstartingpointtoidentifypotential
causesofmostfailures.
63.24Summary
TheGooglePlayIn-appBillingAPIprovidesamechanismbywhichuserscanbecharged
forvirtualgoodsorservicesfromwithinapplications.In-appproductscanbeconfigured
tobesubscriptionbased,one-timepurchaseonly,orconsumable(inthattheitemneedsto
re-purchasedafteritisusedwithintheapplication).
ThischapterworkedthroughthestepsinvolvedinpreparingforandimplementingGoogle
Playin-appbillingwithinanAndroidapplication
64.AnOverviewofGradleinAndroidStudio
Upuntilthispointithas,forthemostpart,beentakenforgrantedthatAndroidStudiowill
takethenecessarystepstocompileandruntheapplicationprojectsthathavebeencreated.
AndroidStudiohasbeenachievingthisinthebackgroundusingasystemknownas
Gradle.
ItisnowtimetolookathowGradleisusedtocompileandpackagetogetherthevarious
elementsofanapplicationprojectandtobeginexploringhowtoconfigurethissystem
whenmoreadvancedrequirementsareneededintermsofbuildingprojectsinAndroid
Studio.
64.1AnOverviewofGradle
Gradleisanautomatedbuildtoolkitthatallowsthewayinwhichprojectsarebuilttobe
configuredandmanagedthroughasetofbuildconfigurationfiles.Thisincludesdefining
howaprojectistobebuilt,whatdependenciesneedtobefulfilledfortheprojecttobuild
successfullyandwhattheendresult(orresults)ofthebuildprocessshouldbe.
ThestrengthofGradleliesintheflexibilitythatitprovidestothedeveloper.TheGradle
systemisaself-contained,command-linebasedenvironmentthatcanbeintegratedinto
otherenvironmentsthroughtheuseofplug-ins.InthecaseofAndroidStudio,Gradle
integrationisprovidedthroughtheappropriatelynamedAndroidStudioPlug-in.
AlthoughtheAndroidStudioPlug-inallowsGradletaskstobeinitiatedandmanaged
fromwithinAndroidStudio,theGradlecommand-linewrappercanstillbeusedtobuild
AndroidStudiobasedprojects,includingonsystemsonwhichAndroidStudioisnot
installed.
TheconfigurationrulestobuildaprojectaredeclaredinGradlebuildfilesandscripts
basedontheGroovyprogramminglanguage.
64.2GradleandAndroidStudio
GradlebringsanumberofpowerfulfeaturestobuildingAndroidapplicationprojects.
Someofthekeyfeaturesareasfollows:
64.2.1SensibleDefaults
Gradleimplementsaconceptreferredtoasconventionoverconfiguration.Thissimply
meansthatGradlehasapre-definedsetofsensibledefaultconfigurationsettingsthatwill
beusedunlesstheyareoverriddenbysettingsinthebuildfiles.Thismeansthatbuildscan
beperformedwiththeminimumofconfigurationrequiredbythedeveloper.Changesto
thebuildfilesareonlyneededwhenthedefaultconfigurationdoesnotmeetyourbuild
needs.
64.2.2Dependencies
AnotherkeyareaofGradlefunctionalityisthatofdependencies.Consider,forexample,a
modulewithinanAndroidStudioprojectwhichtriggersanintenttoloadanothermodule
intheproject.Thefirstmodulehas,ineffect,adependencyonthesecondmodulesince
theapplicationwillfailtobuildifthesecondmodulecannotbelocatedandlaunchedat
runtime.ThisdependencycanbedeclaredintheGradlebuildfileforthefirstmoduleso
thatthesecondmoduleisincludedintheapplicationbuild,oranerrorflaggedintheevent
thesecondmodulecannotbefoundorbuilt.Otherexamplesofdependenciesarelibraries
andJARfilesonwhichtheprojectdependsinordertocompileandrun.
Gradledependenciescanbecategorizedaslocalorremote.Alocaldependencyreferences
anitemthatispresentonthelocalfilesystemofthecomputersystemonwhichthebuild
isbeingperformed.Aremotedependencyreferstoanitemthatispresentonaremote
server(typicallyreferredtoasarepository).
RemotedependenciesarehandledforAndroidStudioprojectsusinganotherproject
managementtoolnamedMaven.IfaremotedependencyisdeclaredinaGradlebuildfile
usingMavensyntaxthenthedependencywillbedownloadedautomaticallyfromthe
designatedrepositoryandincludedinthebuildprocess.Thefollowingdependency
declaration,forexample,causestheGooglePlayServiceslibrarytobeaddedtothe
projectfromtheGooglerepository:
compile‘com.google.android.gms:play-services:+’
64.2.3BuildVariants
Inadditiontodependencies,GradlealsoprovidesbuildvariantsupportforAndroidStudio
projects.Thisallowsmultiplevariationsofanapplicationtobebuiltfromasingleproject.
Androidrunsonmanydifferentdevicesencompassingarangeofprocessortypesand
screensizes.Inordertotargetaswidearangeofdevicetypesandsizesaspossibleitwill
oftenbenecessarytobuildanumberofdifferentvariantsofanapplication(forexample,
onewithauserinterfaceforphonesandanotherfortabletsizedscreens).Throughtheuse
ofGradle,thisisnowpossibleinAndroidStudio.
64.2.4ManifestEntries
EachAndroidStudioprojecthasassociatedwithitanAndroidManifest.xmlfile
containingconfigurationdetailsabouttheapplication.Anumberofmanifestentriescanbe
specifiedinGradlebuildfileswhicharethenauto-generatedintothemanifestfilewhen
theprojectisbuilt.Thiscapabilityiscomplementarytothebuildvariantsfeature,allowing
elementssuchastheapplicationversionnumber,applicationidandSDKversion
informationtobeconfigureddifferentlyforeachbuildvariant.
64.2.5APKSigning
ThechapterentitledSigningandPreparinganAndroidApplicationforReleasecovered
thecreationofasignedreleaseAPKfileusingtheAndroidStudioenvironment.Itisalso
possibletoincludethesigninginformationenteredthroughtheAndroidStudiouser
interfacewithinaGradlebuildfilesothatsignedAPKfilescanbegeneratedfromthe
command-line.
64.2.6ProGuardSupport
ProGuardisatoolincludedwithAndroidStudiothatoptimizes,shrinksandobfuscates
Javabytecodetomakeitmoreefficientandhardertoreverseengineer(themethodby
whichthelogicofanapplicationcanbeidentifiedbyothersthroughanalysisofthe
compiledJavabytecode).TheGradlebuildfilesprovidetheabilitytocontrolwhetheror
notProGuardisrunonyourapplicationwhenitisbuilt.
64.3TheTop-levelGradleBuildFile
AcompletedAndroidStudioprojectcontainseverythingneededtobuildanAndroid
applicationandconsistsofmodules,libraries,manifestfilesandGradlebuildfiles.
Eachprojectcontainsonetop-levelGradlebuildfile.Thisfileislistedasbuild.gradle
(Project:<projectname>)andcanbefoundintheprojecttoolwindowashighlightedin
Figure64-1:
Figure64-1
Bydefault,thecontentsofthetoplevelGradlebuildfilereadasfollows:
//Top-levelbuildfilewhereyoucanaddconfigurationoptionscommon
toallsub-projects/modules.
uildscript{
repositories{
jcenter()
}
dependencies{
classpath‘com.android.tools.build:gradle:1.3.0’
//NOTE:Donotplaceyourapplicationdependencieshere;they
belong
//intheindividualmodulebuild.gradlefiles
}
}
allprojects{
repositories{
jcenter()
}
}
taskclean(type:Delete){
deleterootProject.buildDir
}
Asitstandsallthefiledoesisdeclarethatremotelibrariesaretobeobtainedusingthe
jcenterrepositoryandthatbuildsaredependentontheAndroidpluginforGradle.Inmost
situationsitisnotnecessarytomakeanychangestothisbuildfile.
64.4ModuleLevelGradleBuildFiles
AnAndroidStudioapplicationprojectismadeupofoneormoremodules.Take,for
example,ahypotheticalapplicationprojectnamedGradleDemowhichcontainstwo
modulesnamedModule1andModule2respectively.Inthisscenario,eachofthemodules
willrequireitsownGradlebuildfile.Intermsoftheprojectstructure,thesewouldbe
locatedasfollows:
· Module1/build.gradle
· Module2/build.gradle
Bydefault,theModule1build.gradlefilewouldresemblethatofthefollowinglisting:
applyplugin:‘com.android.application’
android{
compileSdkVersion23
buildToolsVersion“23.0.1”
defaultConfig{
applicationId“com.ebookfrenzy.module1”
minSdkVersion8
targetSdkVersion23
versionCode1
versionName“1.0”
}
buildTypes{
release{
minifyEnabledfalse
proguardFilesgetDefaultProguardFile(‘proguardandroid.txt’),‘proguard-rules.pro’
}
}
}
dependencies{
compilefileTree(dir:‘libs’,include:[‘*.jar’])
testCompile‘junit:junit:4.12’
compile‘com.android.support:appcompat-v7:23.1.0’
}
Asisevidentfromthefilecontent,thebuildfilebeginsbydeclaringtheuseoftheGradle
Androidplug-in:
applyplugin:‘com.android.application’
TheandroidsectionofthefilethenstatestheversionofboththeSDKandtheAndroid
BuildToolsthataretobeusedwhenbuildingModule1.
android{
compileSdkVersion23
buildToolsVersion“23.0.1”
TheitemsdeclaredinthedefaultConfigsectiondefineelementsthataretobegenerated
intothemodule’sAndroidManifest.xmlfileduringthebuild.Thesesettings,whichmaybe
modifiedinthebuildfile,aretakenfromthesettingsenteredwithinAndroidStudiowhen
themodulewasfirstcreated:
defaultConfig{
applicationId“com.ebookfrenzy.gradledemo.module1”
minSdkVersion8
targetSdkVersion23
versionCode1
versionName“1.0”
}
ThebuildTypessectioncontainsinstructionsonwhetherandhowtorunProGuardonthe
APKfilewhenareleaseversionoftheapplicationisbuilt:
buildTypes{
release{
runProguardfalse
proguardFilesgetDefaultProguardFile(‘proguard-android.txt’),
‘proguard-rules.pro’
}
}
Ascurrentlyconfigured,ProGuardwillnotberunwhenModule1isbuilt.Toenable
ProGuard,therunProguardentryneedstobechangedfromfalsetotrue.Theproguardrules.profilecanbefoundinthemoduledirectoryoftheproject.Changesmadetothis
fileoverridethedefaultsettingsintheproguard-android.txtfilewhichislocatedonthe
AndroidSDKinstallationdirectoryundersdk/tools/proguard.
SincenodebugbuildTypeisdeclaredinthisfile,thedefaultswillbeused(builtwithout
ProGuard,signedwithadebugkeyandwithdebugsymbolsenabled).
Anadditionalsection,entitledproductFlavorsmayalsobeincludedinthemodulebuild
filetoenablemultiplebuildvariantstobecreated.Thistopicwillbecoveredinthenext
chapterentitledAnAndroidStudioGradleBuildVariantsExample.
Finally,thedependenciessectionlistsanylocalandremotedependenciesonwhichthe
moduleisdependent.Thefirstdependencyreadsasfollows:
compilefileTree(dir:‘libs’,include:[‘*.jar’])
ThisisastandardlinethattellstheGradlesystemthatanyJARfilelocatedinthe
module’slibsub-directoryistobeincludedintheprojectbuild.If,forexample,aJARfile
namedmyclasses.jarwaspresentintheGradleDemo/Module1/libfolderoftheproject,
thatJARfilewouldbetreatedasamoduledependencyandincludedinthebuildprocess.
Adependencyonothermoduleswithinthesameapplicationprojectmayalsobedeclared
withinthebuildfile.If,forexample,Module1hasadependencyonModule2,the
followinglinewouldneedtobeaddedtothedependenciessectionoftheModule1
build.gradlefile:
compileproject(“:Module2”)
ThelastdependencylineintheaboveexamplefileusesMavensyntaxtodesignatethat
theAndroidSupportlibraryneedstobeincludedfromtheAndroidRepository:
compile‘com.android.support:appcompat-v7:23.1.0’
AnothercommonrepositoryrequirementistheGooglePlayServiceslibrary,a
dependencywhichcanbedeclaredasfollows:
compile‘com.google.android.gms:play-services:+’
Notethatthedependencydeclarationcanincludeanoptionalversionnumbertoindicate
whichversionofthelibraryshouldbeincluded.Version23.1.0ismandatedforthe
supportlibrarywhereasnoparticularversionisspecifiedfortheplayservices(resultingin
themostrecentversionbeingobtainedfromtherepository).
64.5ConfiguringSigningSettingsintheBuildFile
TheSigningandPreparinganAndroidApplicationforReleasechapterofthisbook
coveredthestepsinvolvedinsettingupkeysandgeneratingasignedreleaseAPKfile
usingtheAndroidStudiouserinterface.Thesesettingsmayalsobedeclaredwithina
signingSettingssectionofthebuild.gradlefile.Forexample:
applyplugin:‘android’
android{
compileSdkVersion23
buildToolsVersion“23.0.1”
defaultConfig{
applicationId“com.ebookfrenzy.gradledemo.module1”
minSdkVersion8
targetSdkVersion23
versionCode1
versionName“1.0”
}
signingConfigs{
release{
storeFilefile(“keystore.release”)
storePassword“yourkeystorepasswordhere”
keyAlias“yourkeyaliashere”
keyPassword“yourkeypasswordhere”
}
}
buildTypes{
.
.
.
}
Theaboveexampleembedsthekeypasswordinformationdirectlyintothebuildfile.
Alternativestothisapproacharetoextractthesevaluesfromsystemenvironment
variables:
signingConfigs{
release{
storeFilefile(“keystore.release”)
storePasswordSystem.getenv(“KEYSTOREPASSWD”)
keyAlias“yourkeyaliashere”
keyPasswordSystem.getenv(“KEYPASSWD”)
}
}
YetanotherapproachistoconfigurethebuildfilesothatGradlepromptsforthe
passwordstobeenteredduringthebuildprocess:
signingConfigs{
release{
storeFilefile(“keystore.release”)
storePasswordSystem.console().readLine
(“\nEnterKeystorepassword:“)
keyAlias“yourkeyaliashere”
keyPasswordSystem.console().readLIne(“\nEnterKeypassword:“)
}
}
64.6RunningGradleTasksfromtheCommand-line
EachAndroidStudioprojectcontainsaGradlewrappertoolforthepurposeofallowing
Gradletaskstobeinvokedfromthecommandline.Thistoolislocatedintheroot
directoryofeachprojectfolder.WhilethiswrapperisexecutableonWindowssystems,it
needstohaveexecutepermissionenabledonLinuxandMacOSXbeforeitcanbeused.
Toenableexecutepermission,openaterminalwindow,changedirectorytotheproject
folderforwhichthewrapperisneededandexecutethefollowingcommand:
chmod+xgradlew
Oncethefilehasexecutepermissions,thelocationofthefilewilleitherneedtobeadded
toyour$PATHenvironmentvariable,orthenameprefixedby./inordertorun.For
example:
./gradlewtasks
Gradleviewsprojectbuildingintermsofnumberofdifferenttasks.Afulllistingoftasks
thatareavailableforthecurrentprojectcanbeobtainedbyrunningthefollowing
commandfromwithintheprojectdirectory(rememberingtoprefixthecommandwitha./
ifrunninginMacOSXorLinux):
gradlewtasks
Tobuildadebugreleaseoftheprojectsuitablefordeviceoremulatortesting,usethe
assembleDebugoption:
gradlewassembleDebug
Alternatively,tobuildareleaseversionoftheapplication:
gradlewassembleRelease
64.7Summary
Forthemostpart,AndroidStudioperformsapplicationbuildsinthebackgroundwithout
anyinterventionfromthedeveloper.ThisbuildprocessishandledusingtheGradle
system,anautomatedbuildtoolkitdesignedtoallowthewaysinwhichprojectsarebuilt
tobeconfiguredandmanagedthroughasetofbuildconfigurationfiles.Whilethedefault
behaviorofGradleisadequateformanybasicprojectbuildrequirements,theneedto
configurethebuildprocessisinevitablewithmorecomplexprojects.Thischapterhas
providedanoverviewoftheGradlebuildsystemandconfigurationfileswithinthecontext
ofanAndroidStudioproject.Thenextchapter,entitledAnAndroidStudioGradleBuild
VariantsExamplewilltakethisastepfurtherintheformofusingGradletobuilddifferent
versionsofthesameapplicationproject.
65.AnAndroidStudioGradleBuildVariants
Example
ThegoalofthischapteristousethebuildvariantsfeatureofAndroidStudiotocreatea
projectwhichcanbebuiltintwoflavorsdesignedtotargetphoneandtabletdevices
respectively.Thebuildenvironmentwillbeconfiguredsuchthateachflavorcanbebuilt
usingeitherareleaseordebugbuildtype.Theendresult,therefore,willbefourbuild
variantoptionsavailableforselectionwithinAndroidStudio:
· phoneDebug
· phoneRelease
· tabletDebug
· tabletRelease
Thisraisesthequestionastothedifferencebetweenabuildtypeandabuildflavor.In
general,abuildtypedefineshowamoduleisbuilt(forexamplewhetherornotProGuard
isrun,howtheresultingapplicationpackageissignedandwhetherdebugsymbolsareto
beincluded).
Thebuildflavor,ontheotherhand,typicallydefineswhatisbuilt(suchaswhichresource
andsourcecodefilesaretobeincludedinthebuild)foreachvariantofthemodule.
Initiallythetwoflavorswillbeconfiguredsuchthattheydifferonlyvisuallyintermsof
theresourcesthatareusedforeachtargetsuchaslayoutsandstringvalues.Theproject
willthenbefurtherextendedtoprovideanexampleofhoweachflavormightmakeuseof
differentsourcecodebasesinordertoprovidedifferingapplicationbehavior.
65.1CreatingtheBuildVariantExampleProject
CreateanewprojectinAndroidStudio,enteringBuildExampleintotheApplicationname
fieldandebookfrenzy.comastheCompanyDomainsettingbeforeclickingontheNext
button.
Ontheformfactorsscreen,enablethePhoneandTabletoptionandsettheminimumSDK
settingtoAPI19:Android4.4(KitKat).Continuetoproceedthroughthescreens,
requestingthecreationofanemptyactivitynamedBuildExampleActivitywiththe
remainingfieldssettothedefaultvalues.
65.2ExtractingtheHelloWorldStringResource
Selecttheactivity_build_example.xmllayoutfileandloaditintotheDesignertool.Switch
toTextmodeandclickonthe“HelloWorld”stringwithintheXMLatwhichpointthe
lightbulbiconwillappear.Clickonthelightbulbicon,selecttheExtractStringResource
optionand,intheresultingdialog,extra