Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
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