MasteringXamarinUIDevelopment
TableofContents
MasteringXamarinUIDevelopmentCreditsAbouttheAuthorAcknowledgmentsAbouttheReviewerswww.PacktPub.com
Whysubscribe?CustomerFeedbackDedicationPreface
WhatthisbookcoversWhatyouneedforthisbookWhothisbookisforConventionsReaderfeedbackCustomersupport
DownloadingtheexamplecodeErrataPiracyQuestions
1.CreatingtheTrackMyWalksNativeAppCreatingtheTrackMyWalkssolution
UpdatingtheTrackMyWalkssolutionpackagesCreatingtheTrackMyWalksmodelCreatingthewalksmainpageCreatingthenewwalkentrycontentpageCreatingthewalktrailcontentpage
AddingtheXamarin.Forms.MapsNuGetpackageCreatingtheDistanceTravelledPagecontentpageCreatingtheSplashscreencontentpage
UpdatingtheXamarin.FormsAppclassDifferencesbetweenXamarinStudioandVisualStudioRunningtheTrackMyWalksappusingthesimulatorSummary
2.MVVMandDataBindingUnderstandingtheMVVMpatternarchitectureImplementingtheMVVMViewModelswithinyourappCreatingtheWalkBaseViewModelfortheTrackMyWalksappImplementingtheWalksPageViewModel
UpdatingthewalksmainpagetousetheMVVMmodelImplementingthewalksentrypageViewModel
UpdatingtheWalksEntryPagetousetheMVVMmodelImplementingthewalktrailpageViewModel
UpdatingtheWalksTrailPagetousetheMVVMmodelImplementingtheDistanceTravelledViewModel
UpdatingtheDistanceTravelledPagetousetheMVVMmodelSummary
3.NavigatingwithintheMVVMModel-TheXamarin.FormsWayUnderstandingtheXamarin.FormsNavigationAPIDifferencesbetweenthenavigationandViewModelapproachesImplementingthenavigationservicewithinyourapp
CreatingthenavigationserviceinterfacefortheTrackMyWalksappCreatinganavigationservicetonavigatewithinourViewModelsUpdatingtheWalkBaseViewModeltouseournavigationserviceUpdatingthewalksmainpageViewModelandnavigationserviceUpdatingthewalksmainpagetousetheupdatedViewModelUpdatingthewalksentrypageViewModelandnavigationserviceUpdatingtheWalksEntryPagetousetheupdatedViewModelUpdatingthewalkstrailpageViewModelandnavigationserviceUpdatingtheWalksTrailPagetousetheupdatedViewModelUpdatingthedistancetravelledViewModelandnavigationserviceUpdatingtheDistanceTravelledPagetousetheupdatedViewModelUpdatingtheXamarin.Forms.Appclasstousethenavigationservice
Summary4.AddingLocation-BasedFeatureswithinYourApp
Creatingandusingplatform-specificservicesCreatingtheLocationServiceInterfacefortheTrackMyWalksappCreatingtheLocationServiceclassfortheAndroidplatformCreatingtheLocationServiceclassfortheiOSplatformEnablingbackgroundupdatesandgettingtheuser'scurrentlocationUpdatingtheWalkEntryViewModeltousethelocationserviceUpdatingtheDistanceTravelledViewModeltousethelocationserviceUpdatingtheSplashPagetoregisterourViewModelsUpdatingtheMainActivityclasstouseXamarin.Forms.MapsUpdatingtheXamarin.FormsAppclasstouseplatformspecifics
Summary5.CustomizingtheUserInterface
CreatingtheDataTemplateclassfortheTrackMyWalksappUpdatingthewalksmainpagetousethedatatemplate
CreatingaTableViewEntryCellcustompickerfortheiOSplatformCreatingthecustompickerrendererclassfortheiOSplatform
UpdatingtheWalksEntryPagetousethecustompickerrendererCreatingPlatformEffectsusingtheEffectsAPIfortheiOSplatformCreatingPlatformEffectsusingtheEffectsAPIfortheAndroidplatformImplementingvalueconverterswithintheTrackMyWalksapp
UpdatingtheWalkBaseViewModeltouseourBooleanconverterUpdatingtheWalksPageViewModeltouseourBooleanconverterUpdatingthewalksmainpagetousetheupdatedViewModelUpdatingtheWalksTrailPagetousetheupdatedViewModelUpdatingtheDistanceTravelledPagetousetheupdatedViewModelUpdatingtheWalkCellDataTemplateclasstousePlatformEffects
Summary6.WorkingwithRazorTemplates
UnderstandingtheRazortemplateengineCreatingandimplementingRazortemplateswithinXamarinStudio
AddingtheSQLite.NetpackagetotheBookLibrarysolutionCreatingandimplementingthebooklibrarydatabasemodelCreatingandimplementingthebookdatabasewrapperCreatingandimplementingtheBookLibrarydatabasewrapperCreatingandimplementingthebooklistingmainpageCreatingandimplementingtheBookLibraryAddRazortemplateCreatingandimplementingtheBookLibraryEditRazortemplateCreatingandimplementingtheWebViewControllerclass
UpdatingthebooklibraryCascadingStyleSheet(CSS)Summary
7.IncorporatingAPIDataAccessUsingMicrosoftAzureAppServicesSettingupourTrackMyWalksappusingMicrosoftAzure
AddingtheJson.NetNuGetpackagetotheTrackMyWalksappAddingtheHttpClientNuGetpackagetotheTrackMyWalksappUpdatingtheWalkEntriesmodeltousetheJson.Netframework
CreatingtheHTTPwebserviceclassfortheTrackMyWalksappCreatingtheDataServiceAPIfortheTrackMyWalksappCreatingtheDataServiceAPIclassfortheTrackMyWalksapp
UpdatingtheWalkBaseViewModeltouseourDataServiceAPIUpdatingtheWalkEntryViewModeltouseourDataServiceAPIUpdatingtheWalksPageViewModeltouseourDataServiceAPIUpdatingtheWalksPagetousetheupdatedViewModelUpdatingthecustompickerrendererclassfortheiOSplatformUpdatingtheWalksEntryPagetousetheupdatedcustompicker
Summary8.MakingOurAppSocial-UsingtheFacebookAPI
SettingupandregisteringtheTrackMyWalksappwithFacebookAddingtheXamarin.AuthNuGetpackagetotheTrackMyWalksappAddingtheFaceBookSDKlibrarytotheTrackMyWalksapp
CreatingaFacebookusermodelfortheTrackMyWalksappCreatingaFacebookCredentialsclassfortheTrackMyWalksappCreatingtheFacebookSignIntousewithinourTrackMyWalksappCreatingtheFacebookSignInClassforTrackMyWalks(iOS)app
UpdatingtheNavigationServiceInterfacefortheTrackMyWalksapp
UpdatingtheNavigationServiceclassfortheTrackMyWalksappUpdatingtheWalksPagetoproperlyhandleFacebookSignIn
UpdatingtheWalksPageViewModeltouseourFaceBookApiUserUpdatingtheDistanceTravelledPagefortheTrackMyWalksappUpdatingtheXamarin.FormsAppclasstohandleFacebookSignInEnablingFacebookfunctionalitywithintheTrackMyWalksapp
Summary9.UnitTestingYourXamarin.FormsAppsUsingtheNUnitandUITestFrameworks
CreatingaunittestsolutionfolderusingXamarinStudioCreatingaunittestprojectusingXamarinStudio
AddingtheMoqNuGetpackagetotheunittestprojectAddingtheTrackMyWalksprojecttoTrackMyWalks.UnitTestsCreatingandimplementingtheWalksTrailViewModelNUnittestclassCreatingandimplementingtheWalkEntryViewModelNUnittestclassRunningtheTrackMyWalks.UnitTestsusingXamarinStudio
CreatingaUItestprojectusingXamarinStudioUnderstandingthecommonlyusedUITestmethods
SettingupandinitializingourTrackMyWalksappforUITestImplementingtheCreateNewWalkEntryusingtheUITest.Framework
AddingtheXamarinTestCloudAgenttotheiOSprojectUpdatingtheTrackMyWalksAppDelegateclasstohandleXamarinTestCloudAgent
RunningtheTrackMyWalksUITestsusingXamarinStudioSummary
10.PackagingandDeployingYourXamarin.FormsApplicationsCreatingandsettingupyouriOSdevelopmentteamCreatingtheTrackMyWalksiOSdevelopmentcertificate
ObtainingtheiOSdevelopmentcertificatefromAppleCreatingtheAppIDfortheTrackMyWalks(iOS)application
CreatingtheTrackMyWalksdevelopmentprovisioningprofilePreparingtheTrackMyWalks(iOS)appforsubmission
SubmittingtheTrackMyWalks(iOS)apptoiTunesConnectusingXamarinStudioSummary
MasteringXamarinUIDevelopment
MasteringXamarinUIDevelopmentCopyright©2017PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:January2017
Productionreference:1130117
PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
Birmingham
B32PB,UK.
ISBN978-1-78646-200-8
www.packtpub.com
Credits
Author
StevenF.Daniel
CopyEditor
SafisEditing
Reviewers
LanceMcCarthy
EnginPolat
ProjectCoordinator
IzzatContractor
CommissioningEditor
AmarabhaBanerjee
Proofreader
SafisEditing
AcquisitionEditor
ShwetaPant
Indexer
TejalDaruwaleSoni
ContentDevelopmentEditor
PriyankaMehta
Graphics
AbhinashSahu
TechnicalEditor
AbhishekSharma
ProductionCoordinator
DeepikaNaik
AbouttheAuthorStevenF.DanielistheCEOandfounderofGENIESOFTSTUDIOS,asoftwaredevelopmentcompanybasedinMelbourne,Victoria,thatfocusesprimarilyondevelopinggamesandbusinessapplicationsfortheiOS,Android,andMacOSXplatforms.Heisanexperiencedsoftwaredeveloperwithmorethan17yearsofexperienceindevelopingdesktopandweb-basedapplicationsforseveralcompaniesandstartups.
StevenisextremelypassionateaboutmakingpeopleemployablebyhelpingthembridgethegapbetweenusingtheirexistingskillsiniOS,Android,andXamarintogetthejobdone.Toachievethis,hewritesbookstohelpnoviceandadvancedprogrammerssucceedwithintheindustry.Stevenisextremelypassionateabout,andlovesbeingattheforefrontof,technology.HeisamemberoftheSQLServerSpecialInterestGroup(SQLSIG),MelbourneCocoaHeads,andtheJavaCommunity.HewasthecofounderandChiefTechnologyOfficer(CTO)atSoftMpirePtyLtd.,acompanythatisfocusedprimarilyondevelopingbusinessapplicationsfortheiOSandAndroidplatforms.
Stevenistheauthorofvariousbooktitles,someofwhichareasfollows:
AppleWatchAppDevelopmentAndroidWearableProgrammingXcode4CookbookiPadEnterpriseApplicationDevelopmentBlueprintsiOS5EssentialsXcode4iOSDevelopmentBeginner’sGuide
Checkouthisblogathttp://www.geniesoftstudios.com/blog/,orfollowhimontwitterathttp://twitter.com/GenieSoftStudio.
AcknowledgmentsNobookistheproductofjusttheauthor;hejusthappenstobetheonewithhisnameonthecover.Severalpeoplecontributedtothesuccessofthisbook,anditwouldtakemorespacethanthankingeachoneindividually.
Iwouldpersonallyliketothankthreespecialpeoplewhohavebeenaninspirationandwhohaveprovidedmewithsomuchsupportduringthewritingofthisbook,ReshmaRaman,mySeniorAcquisitionEditor,whoisthereasonthatthisbookexists;ShwetaPant,myAcquisitionEditor;andPriyankaMehtaforherunderstandingandsupport,aswellasherbrilliantsuggestiveapproachesduringthechapterrewrites.Iwouldliketothankeachofyouforeverything,andmakingthewritingprocessenjoyable.
Lastly,tomyreviewers,thankyousomuchforyourvaluedsuggestionsandimprovementstomakethisbookwhatitis;Iamtrulygratefultoeachoneofyou.
ThankyoualsototheentirePACKTPublishingteamforworkingsodiligentlytohelpbringoutahigh-qualityproduct.Finally,abigshoutouttotheengineersatXamarin,Inc.forcreatingXamarinStudioandtheMonoPlatformtoprovidedeveloperswiththetoolstocreatefunandsophisticatedapplicationswiththepowerofXamarin.Forms.
Finally,Iwouldliketothankallmyfriendsfortheirsupport,understanding,andencouragementduringthebookwritingprocess.Iamextremelygratefultohaveyouasmyfriends,anditisaprivilegetoknoweachoneofyou.
AbouttheReviewersLanceMcCarthyisanexceptionalcommunityleaderwithanacuteexpertiseforallthings.NETandC#,especiallyontheXAMLstack,includingWPF,Silverlight,WindowsPhone,andWindowsstoreapps.Heisveryhelpfulonline,guidingandansweringquestionsfromMicrosoftdevelopersonTwitteras@lancewmccarthy;heblogsonhisowntimeaswell,withastrongfocusonWindowsUniversalapps,atWinPlatform.wordpress.com.HeorganizesandhostseventsintheBostonarea,suchasusergroupnights,mini-codecamps,andfullhackathons.
Duringtheday,LanceisaseniortechnicalsupportengineeratTelerik,wherehesupportsdeveloperswiththeirClassicWindows,UniversalWindows,webandmobile(Xamarin,AndroidandiOSnative)applicationdevelopment.
Ontheside,Lancewritesblogpostsforblogs.windows.com/buildingapps/,createsresourcesfordevelopers(tutorials,samplesourcecode,tipsoftheweek,andsoon),andhelpsthedevelopercommunityinanywaypossible.
Previously,LanceworkedforNokiaandMicrosoftasaDeveloperAmbassador,wherehesoughtoutandengageddevelopersthroughoutreachprogramsandprovidesthemwithtechnicalsupportandresourcestomakethemsuccessfulontheWindowsplatforms.
LancewasalsoanassistantprofessoratHarvardUniversity,helpingstudentsbuild,market,andpublishsuccessfulWindowsPhoneapps.Hehasalsoappearedonpodcasts,suchastheWindowsDeveloperShow,hasbeenatechnicaleditorforpublicationsandbooks,haswonseveralappbuildingcontestsandhackathons(includingfirstplaceintheMicrosoftBuild2013hackathon),andisapublisheddeveloperwithoveramilliondownloadsintheWindowsStore.
Someofthebooksthathehasreviewedareasfollows:
NetdunioHomeAutomationProjectsbyMattCavanaughMasteringCross-PlatformDevelopmentwithXamarinbyCanBilginBegintoCodewithC#byRobMiles
EnginPolathasbeeninvolvedinmanylargeandmedium-scaleprojectson.NETtechnologiesasadeveloper,architect,andconsulting,andhehaswonmanyawardssince1999.Since2008,hehasbeentrainingmanylargeenterprisesinTurkeyonWindowsdevelopment,webdevelopment,distributedapplicationdevelopment,softwarearchitecture,mobiledevelopment,clouddevelopment,andsoon.Apartfromthis,heorganizesseminarsandeventsinmanyuniversitiesinTurkeyabout.NETtechnologies,Windowsplatformdevelopment,clouddevelopment,webdevelopment,gamedevelopment,andsoon.Heshareshisexperiencesonhispersonalblog(http://www.enginpolat.com).HehasMCP,MCAD,MCSD,MCDBA,andMCTcertifications.Since2012,heisrecognizedasaWindowsDevelopmentMVPbyMicrosoft;since2017,heisrecognizedasaVisualStudioandDevelopmentTechnologiesMVPtoo.Between2013and2015,
hewasrecognizedasaNokiaDeveloperChampion-veryfewpeopleintheworldaregiventhisaward.Since2015,hehasbeenrecognizedasaRegionalDirectorbyMicrosoft.
HehasreviewedafewbooksforPackt,someofwhichareasfollows:
MasteringCross-PlatformDevelopmentwithXamarinXamarinBlueprintsXamarin4byExample
I'dliketothankmydearwife,Yeliz,andmybeautifuldaughter,MelisAda,forallthesupporttheygavemewhileIwasworkingonthisbookproject.
Ialsowanttoextendawarmwelcometothenewestmemberofmyfamily,mydearson,UtkuEge.
www.PacktPub.comForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.
DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatservice@packtpub.comformoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
https://www.packtpub.com/mapt
Getthemostin-demandsoftwareskillswithMapt.MaptgivesyoufullaccesstoallPacktbooksandvideocourses,aswellasindustry-leadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.
Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser
CustomerFeedbackThankyouforpurchasingthisPacktbook.Wetakeourcommitmenttoimprovingourcontentandproductstomeetyourneedsseriously-that'swhyyourfeedbackissovaluable.Whateveryourfeelingsaboutyourpurchase,pleaseconsiderleavingareviewonthisbook'sAmazonpage.Notonlywillthishelpus,moreimportantlyitwillalsohelpothersinthecommunitytomakeaninformeddecisionabouttheresourcesthattheyinvestintolearn.Youcanalsoreviewforusonaregularbasisbyjoiningourreviewers'club.Ifyou'reinterestedinjoining,orwouldliketolearnmoreaboutthebenefitsweoffer,pleasecontactus:[email protected].
DedicationTomyfavoriteuncle,BenjaminJacobDaniel,thankyouforalwaysmakingmesmileandforinspiringmetoworkhardandachievemydreams;youareatrueinspirationandIcouldn’thavedonethiswithoutyourlove,support,andguidance.Thankyou.
Asalways,toChanBanGuan,forthecontinuedpatience,encouragement,andsupport,andmostofallforbelievinginmeduringthewritingofthisbook.Iwouldliketothankmyfamilyfortheircontinuedloveandsupport,andforalwaysbelievinginmethroughoutthewritingofthisbook.
ThisbookwouldnothavebeenpossiblewithoutyourloveandunderstandingandIwouldliketothankyoufromthebottomofmyheart.
PrefaceXamarinisthemostpowerfulcross-platformmobiledevelopmentframework.IfyouareinterestedincreatingstunninguserinterfacesfortheiOSandAndroidmobileplatformsusingthepowerofXamarinandXamarin.Forms,thenthisisyourticket.
Thisbookwillprovideyouwiththepracticalskillsrequiredtodevelopreal-worldXamarinapplications.Youwilllearnhowtoimplementuserinterfacestructuresandlayouts,createcustomizedelements,andwriteC#scriptstocustomizelayouts.You’lllearnhowtocreateUserInterfacelayoutsfromscratchandcustomizetheselayoutstosuityourneedsbyusingDataTemplatesandCustomRenderers.
You’llbeintroducedtothearchitecturebehindtheModel-View-ViewModel(MVVM)pattern,andhowtoimplementthiswithinyourapplicationsothatyoucannavigatebetweeneachofyourViewModelsandContentPages.
Wewillthenmoveontodiscussmoreadvancedtopics,suchashowtoincorporateplatform-specificfeatureswithinyourappsthataredependentonthemobileplatformbeingrun,andyouwilllearnhowtoproperlyperformlocationupdates,whethertheapplication'sstateisintheforegroundorbackground,byregisteringtheappasabackground-necessaryapplication.
Wediscussmoreadvancedtopics,suchasworkingwithMicrosoftAzureAppservicestocreateyourveryfirstcloud-basedbackendHTTPwebservicetohandlecommunicationbetweenthecloudandtheapp,bycreatingaDataServiceAPIthatwillallowourapptoconsumetheAPIsothatitcanretrieve,store,anddeletewalktrailinformationfromthecloud.
WewillalsocoverhowyoucanworkwiththeFacebookSDKtoincorporatesocialnetworkingfeaturestoobtaininformationaboutaFacebookuser,aswellaspostinformationtotheirFacebookwallandusetheOpenGraphAPItoretrievecertaininformationabouttheuser.
Movingon,youwilllearnhowtousethird-partylibraries,suchastheRazortemplateengine,whichallowsyoutocreateyourownHTML5templates,withintheXamarinStudioenvironmenttobuildabooklibraryHybridsolutionthatusestheSQLite.Netlibrarytostore,update,retrieve,anddeleteinformationwithinaSQLitelocaldatabase.You’llalsoimplementkeydatabindingtechniquesthatwillmakeyouruserinterfacesdynamicandcreatepersonalizedanimationsandvisualeffectswithinyouruserinterfacesusingcustomrenderersandthePlatformEffectsAPItocustomizeandchangetheappearanceofcontrolelements.
Attheendofthisbook,youwilllearnhowtocreateandrununittestsusingtheNUnitandUITesttestingframeworksrightwithintheXamarinStudioIDE.You'lllearnhowtowriteunittestsforyourViewModelsthatwillessentiallytestthebusinesslogictovalidatethateverythingisworkingcorrectly,beforemovingontotesttheuserinterfaceportionusingautomatedUItesting.
Inthisbook,Ihavetriedmybesttokeepthecodesimpleandeasytounderstandbyprovidinga
step-by-stepapproach,withlotsofscreenshotsateachsteptomakeiteasiertofollow.YouwillsoonmasterthedifferentaspectsofXamarin.Forms,andthetechnologyandskillsneededtocreateyourownapplicationsfortheXamarin.Formsplatform.
[email protected],orjustdropmeane-mailtosayafriendly"Hello".
WhatthisbookcoversChapter1,CreatingtheTrackMyWalksNativeApp,focusesonhowtosetupabasiccross-platformnativeappstructureusingXamarin.Formsbeforeproceedingwithaddingnew,andupdatingexisting,packageswithinyoursolution.
You’lllearnhowtocreateC#classesthatwillactasthemodelforourapp,andwillcreatecontentpagesthatwillformtheuserinterface.WewillalsocoverthedifferencesbetweendevelopingappsusingXamarinStudioandMicrosoftVisualStudio.
Chapter2,MVVMandDataBinding,introducesyoutothearchitecturebehindtheMVVMpattern,andhowyoucanimplementthiswithinyourapplicationbyaddingnewViewsandtheassociatedModels.
You’lllearnhowtocreatetheunderlyingC#classfilesthatwillactastheViewModelsforyourapp,andupdateexistingcontentpagestodata-bindwiththeViewModelstorepresenttheinformationthatwillbedisplayedwithintheuserinterfaceforourapplication.
Chapter3,NavigatingwithintheMVVMModel-TheXamarin.FormsWay,buildsuponyourworkingknowledgeoftheMVVMdesignpatternarchitecturetoshowyouhowyoucannavigatethroughtheViewModelsbycreatingaC#classthatactsasthenavigationserviceforourapp,andupdatesourexistingWalkBaseViewModelclasstoincludeadditionalabstractclassmethodseachofourViewModelswillinherit;inturn,you'llupdatecontentpagestobindwiththeViewModelstoallownavigationbetweentheseViewstohappen.
Chapter4,AddingLocation-BasedFeatureswithinYourApp,focusesonhowyoucanincorporateplatform-specificfeatureswithintheTrackMyWalksapp,whichisdependentonthemobileplatform,bycreatingalocationserviceC#classthatwillincludeseveralclassmethodsforboththeiOSandAndroidplatforms.
You’lllearnhowtoproperlyperformlocationupdateswhethertheapplication'sstateisintheforegroundorbackgroundbyregisteringtheappasabackground-necessaryapplication.
Chapter5,CustomizingtheUserInterface,showsyouhowyoucanworkwithDataTemplatestolayoutyourviewsneatlywithinyourapplicationsuserinterfacebycreatingaC#class.You’llgetaccustomedtoworkingwithplatform-specificAPIstoextendthedefaultbehaviorofXamarin.Formscontrolsusingcustomrendererstocreateacustompicker,beforemovingontolearnhowtousetheXamarin.FormsEffectsAPItocustomizetheappearanceandstylingofnativecontrolelementsforeachplatformbyimplementingaCustomRendererclass.
Finally,youwilllearnhowtomanipulatethevisualappearanceofdataboundusingvalueandimageconverters.
Chapter6,WorkingwithRazorTemplates,introducesyoutotheRazorHTMLtemplatingengine,
andhowyoucanuseittocreateahybridmobilesolution.You'lllearnhowtobuildabooklibrarymobilesolutionusingthepowerofRazortemplates,howtocreateandusemodelswithinyourapplication,andhowtoconnectthisuptoaSQLitedatabasetostore,retrieve,update,anddeletebookdetails.
Chapter7,IncorporatingAPIDataAccessusingMicrosoftAzureAppServices,showsyouhowyoucanuseMicrosoftAzureAppservicestocreateyourveryfirstlive,cloud-basedbackendHTTPwebservicetohandleallthecommunicationbetweenthecloudandtheapp.You’lllearnhowtocreateaDataServiceAPIthatwillallowtheapptoconsumetheAPIsothatitcanretrieve,store,anddeletewalktrailinformationfromthecloud,allfromwithintheTrackMyWalksapp.
Chapter8,MakingourAppSocial–UsingtheFacebookAPI,showsyouhowyoucanusebothXamarin.AuthandtheFacebookSDKtoincorporatesocialnetworkingfeatureswithintheTrackMyWalksapptoobtaininformationaboutaFacebookuser,aswellaspostinformationtotheirFacebookwall.
You’lllearnhowtocreateasign-inpagethatallowsuserstologintoyourappusingtheirFacebookcredentials,andhowtocreateaFacebookApiUserclassthatwillbeusedtostoreinformationaboutthelogged-inuser,andusetheOpenGraphAPItoretrievecertaininformationabouttheuser.
Finally,youwillseehowyoucanleveragetheFacebooklibrarytopostwalkdatatoyourFacebookprofilepage,soyoucanshowoffyourWalkTrailprogresstoyourfriendsand/orworkcolleagues.
Chapter9,UnitTestingyourXamarin.FormsAppusingtheNUnitandUITestFrameworks,focusesonshowingyouhowtocreateandrununittestsusingtheNUnitandUITesttestingframeworksrightwithintheXamarinStudioIDE.You'lllearnhowtowriteunittestsforourViewModelsthatwillessentiallytestthebusinesslogictovalidatethateverythingisworkingcorrectlybeforetestingtheuserinterface'sportionusingautomatedUItesting.
Chapter10,PackagingandDeployingyourXamarin.FormsApplications,focusesonhowtosubmityourTrackMyWalksiOSapptotheAppleAppStore,andshareyourcreationswiththerestofthecommunity.You'lllearnthestepsrequiredtosetupyouriOSdevelopmentteam,aswellascertificatesforbothdevelopmentanddistribution,andyouwilllearnhowtocreatethenecessaryprovisioningprofilesforbothyourdevelopmentanddistributionbuildsandcreatethenecessaryappIDsforyourapplication.
Finally,youwilllearnhowtoregisteryouriOSdevicessothatyouruserscandownloadandtestyourappsontheiriOSdevicesandlearnhowtoprepareyourTrackMyWalksiOSappforsubmissiontoiTunesConnectusingtheXamarinStudioIDE.
WhatyouneedforthisbookTheminimumrequirementforthisbookisanIntel-basedMacintoshcomputerrunningOSXElCapitan10.11.WewillbeusingXamarinStudio6.1.2,whichistheIntegratedDevelopmentEnvironment(IDE)usedforcreatingXamarin.FormsapplicationsusingC#,aswellasXcode8.2.1tocompileouriOSappandrunthiswithinthesimulator.
AlmostalltheprojectsthatyoucreatewiththehelpofthisbookwillworkandrunontheiOSsimulator.However,someprojectswillrequireaniOSorAndroiddevicetoworkcorrectly.YoucandownloadthelatestversionsofXamarinStudioandXcodeat:
XamarinStudio:http://xamarin.com/download
Xcode:https://itunes.apple.com/au/app/xcode/id497799835?mt=12
WhothisbookisforThisbookisintendedfordeveloperswhohaveaworkingexperienceofapplicationdevelopmentprinciples,aswellasabasicknowledgeofXamarinandC#codingandwishtoexpandtheirknowledgeanddevelopapplicationsusingXamarin.Forms.ItisassumedthatyouarefamiliarwithObject-OrientedProgramming(OOP),andhavesomeexperiencedevelopingC#applicationsusingXamarinStudio.
ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.
Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:"OnethingyouwillnoticeisthatoursolutioncontainsafilecalledTrackMyWalks.cswhichispartoftheTrackMyWalksPortableClassLibrary."
Ablockofcodeissetasfollows:
//
//WalkEntryViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
usingXamarin.Forms;
namespaceTrackMyWalks
{
publicclassWalkEntryViewModel:WalkBaseViewModel
{
Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:
//
//WalksPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.Collections.Generic;
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
namespaceTrackMyWalks
{
publicclassWalksPage:ContentPage
{
publicWalksPage()
{
Anycommand-lineinputoroutputiswrittenasfollows:
Lastlogin:SunNov610:48:41onconsole
GENIESOFT-MAC-Mini:~stevendaniel$curl
https://trackmywalks.azurewebsites.net/tables/walkentries
--header"ZUMO-API-VERSION:2.0.0"
Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:"ifyouclickontheProceedWith...button,itwillnavigatetothewalksTrailDetailspagewhereyoucanbeginyourtrail,byclickingontheBeginthisTrailbutton."
Note
Warningsorimportantnotesappearinaboxlikethis.
Tip
Tipsandtricksappearlikethis.
ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook-whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.Tosendusgeneralfeedback,[email protected],andmentionthebook'stitleinthesubjectofyourmessage.Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.
CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.
DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforthisbookfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
Youcandownloadthecodefilesbyfollowingthesesteps:
1. Loginorregistertoourwebsiteusingyoure-mailaddressandpassword.2. HoverthemousepointerontheSUPPORTtabatthetop.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchbox.5. Selectthebookforwhichyou'relookingtodownloadthecodefiles.6. Choosefromthedrop-downmenuwhereyoupurchasedthisbookfrom.7. ClickonCodeDownload.
Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:
WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux
ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/PacktPublishing/Mastering-Xamarin-UI-Development.Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideosavailableathttps://github.com/PacktPublishing/.Checkthemout!
ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks-maybeamistakeinthetextorthecode-wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.
Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.
PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.
Pleasecontactusatcopyright@packtpub.comwithalinktothesuspectedpiratedmaterial.
Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.
QuestionsIfyouhaveaproblemwithanyaspectofthisbook,[email protected],andwewilldoourbesttoaddresstheproblem.
Chapter1.CreatingtheTrackMyWalksNativeAppSinceXamarinmadeitsappearanceseveralyearsago,developershavebeendelightedwithbeingabletocreatenativemobileapplicationsthattargetnon-Microsoftplatforms,andwithhavingtheoptionofdevelopingappsusingeitherC#orF#programminglanguages,whichenablesdeveloperstodistributetheirappideasoniOSandAndroidplatforms.
Asyouprogressthroughthisbook,youwilllearnhowtoapplybestpracticeprincipleswhendevelopingcross-platformmobileapplicationsanddesignpatternsusingtheXamarin.Formsplatform,whichallowsdeveloperstobuildcross-platformuserinterfacelayoutsthatcanbesharedacrossAndroid,iOS,andWindowsPhonemobileplatforms.
Sinceeachoftheseappscanbewrittenusingasingleprogramminglanguage,itmakessensetowriteasinglecodebasethatwouldcompileandbuildseparateappsforeachofthesedifferentplatforms.
ThischapterwillbeginbysettingupabasicstructureofanappbuiltusingXamarin.Forms,whichwillbethefoundationforthesubsequentchapters,wherewewillcontinuallybuilduponthis,applyingnewconcepts.Inthischapter,youwillseehowtocreateaninitialcross-platformnativeappusingXamarin.Formsandhowtogoaboutaddingnew,andupdatingexisting,packageswithinyoursolution.
You'lllearnhowtocreateC#classesthatwillactasthemodelforourapp,aswellascreatingcontentpagesthatwillformtheuserinterface.Toendthechapter,youwilllearnaboutthedifferencesbetweendevelopingappsusingXamarinStudioand/orMicrosoftVisualStudio.
Thischapterwillcoverthefollowingpoints:
CreatingtheXamarin.FormsTrackMyWalksmobileappsolutionUpdatingtheTrackMyWalkssolutionpackagesusingtheNuGetpackagemanagerCreatingtheTrackMyWalksdatamodelCreatingtheContentPagesfortheTrackMyWalkssolutionUnderstandingthedifferencesbetweenXamarinStudioandVisualStudio
CreatingtheTrackMyWalkssolutionInthissection,wewilltakealookathowwecangoaboutcreatinganewXamarin.Formssolutionforthefirsttime.Wewillbeginbydevelopingthebasicstructureforourapplication,aswellasbyaddingthenecessaryentitymodelsanddesigningtheuserinterfacefiles.
Beforewecanproceed,weneedtocreateourTrackMyWalksproject.ItisverysimpletocreatethisusingXamarinStudio.Simplyfollowthestepslistedbelow:
1. LaunchtheXamarinStudioapplication.Youwillbepresentedwiththefollowingscreen:
2. Next,clickontheNewSolution...button,oralternativelychoosetheFile|New|Solution...orsimplypressShift+Command+N.
3. Next,choosetheFormsAppoptionwhichislocatedundertheMultiplatform|Appsection.EnsureyouhaveselectedC#astheprogramminglanguagetouse:
4. Next,enterTrackMyWalkstouseasthenameforyourappintheAppNamefield.5. Then,specifyanamefortheOrganizationIdentifierfield.6. Next,ensurethatboththeAndroidandiOScheckboxeshavebeenselectedintheTarget
Platformsfields.7. Then,ensurethattheUsePortableClassLibraryoptionhasbeenselectedintheShared
Codesection,asshowninthefollowingscreenshot.
Tip
ThedifferencebetweenusingaPortableClassLibraryversusaSharedLibraryisessentiallythataPortableClassLibraryenablesdeveloperstowritecodeonce,sothatitcanbeusedwithindifferentplatformprojectssuchasWebsites,Android,andiOS.ASharedLibraryenablesdeveloperstocopyallthefileswithinthelibraryprojecttoalltheprojectsthatarecontainedwithinthesolutionduringcompilationforthevariousplatformsthatwilluseit.
Note
TheOrganizationIdentifieroptionforyourappneedstobeunique.XamarinStudiorecommendsthatyouusethereversedomainstyle(forexample,com.domainName.appName).
8. Next,ensurethattheUseXAMLforuserinterfacefilesoptionhasnotbeenselected.9. Then,clickontheNextbuttontoproceedtothenextstepinthewizard.
10. Next,ensurethattheCreateaprojectdirectorywithinthesolutiondirectory.checkboxhasbeenselected.
11. Then,clickontheCreatebuttontosaveyourprojecttothespecifiedlocation.
Onceyourprojecthasbeencreated,youwillbepresentedwiththeXamarindevelopmentenvironmentalongwithseveralprojectfilesthatthetemplatehascreatedforyou,asshowninthefollowingscreenshot:
Asyoucanseefromtheprecedingscreenshot,theTrackMyWalkssolutionhasbeendividedintothreemainareas.Thefollowingtableprovidesabriefdescriptionofwhateachareaisusedfor:
Platformspecificproject Description
TrackMyWalks
ThisisthePortableClassLibrary(PCL)projectthatwillberesponsibleforactingasthemainarchitecturallayerfortheTrackMyWalkssolution.
Thisprojectcontainsallofthebusinesslogic,dataobjects,Xamarin.FormsPages,views,andothernon-platformspecificcode.
Anycodethatyoucreatewithinthisprojectcanbesharedacrossmultipleplatform-specificprojects.
TrackMyWalks.Droid
ThisprojectisanAndroidspecificprojectthatcontainsallofthecodeandassetsrequiredtobuildanddeploytheAndroidappcontainedwithinthesolution.
Bydefault,thisprojectcontainsareferencetotheTrackMyWalksPortableClassLibrary.
ThisprojectisaniOSspecificprojectthatcontainsallofthecodeandassetsrequiredtobuildanddeploytheiOSappcontainedwithinthesolution.
TrackMyWalks.iOS Bydefault,thisprojectcontainsareferencetotheTrackMyWalksPortableClassLibrary.
OnethingyouwillnoticeisthatoursolutioncontainsafilecalledTrackMyWalks.cswhichispartoftheTrackMyWalksPortableClassLibrary.TheTrackMyWalks.csfilecontainsaclassnamedAppthatinheritsfromtheXamarin.Forms.Applicationclasshierarchy,ascanbeseeninthefollowingcodesnippet:
//
//TrackMyWalks.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
namespaceTrackMyWalks
{
publicclassApp:Application
{
publicApp()
{
//Therootpageofyourapplication
varcontent=newContentPage
{
Title="TrackMyWalks",
Content=newStackLayout{
VerticalOptions=LayoutOptions.Center,
Children={newLabel{
HorizontalTextAlignment=
TextAlignment.Center,
Text="WelcometoXamarinForms!"
}
}
}
};
MainPage=newNavigationPage(content);
}
protectedoverridevoidOnStart()
{
//Handlewhenyourappstarts
}
protectedoverridevoidOnSleep()
{
//Handlewhenyourappsleeps
}
protectedoverridevoidOnResume()
{
//Handlewhenyourappresumes
}
}
}
TheAppconstructormethodsetsuptheMainPagepropertytoanewinstanceoftheContentPagethatwillsimplydisplaysomedefaulttextascreatedbytheprojectwizard.Throughoutthischapter,wewillbebuildingtheinitialuserinterfacepageviewsandthenmodifyingtheMainPagepropertyforourAppclass,containedwithintheTrackMyWalks.csfile.
UpdatingtheTrackMyWalkssolutionpackagesInthissection,wewilltakealookathowtoupdatetheXamarin.FormspackagescontainedwithinourTrackMyWalkssolution.Basically,youwillnoticethateachprojectcontainedwithinoursolutioncontainsaPackagesfolder.
TheXamarin.FormspackageisessentiallyaNuGetpackagethatgetsautomaticallyincludedinoursolutionwheneverwespecifythatwewanttocreateaXamarin.FormsAppprojecttemplate.
Fromtimetotime,youwillnoticethatXamarinwillnotifyyouwheneverapackageisoutofdateandneedstobeupdatedtoensurethatyouarerunningthelatestversion.
Note
ANuGetpackage,isessentiallythepackagemanagerfortheMicrosoftDevelopmentPlatformthatcontainstheclienttoolsthatprovidethecapabilityforproducingandconsuming.NETpackages.
Let'stakealookathowtogoaboutupdatingtheNuGetpackageswithinourTrackMyWalkssolutiontoensurethatwearerunningthelatestXamarin.Formspackages.Performthefollowingsteps:
1. Right-clickontheTrackMyWalkssolutionandchoosetheUpdateNuGetPackagesmenuoption,asshowninthefollowingscreenshot:
Onceyouhaveselectedthisoption,XamarinStudiowillproceedtoupdateeachpackagethatiscontainedwithintheTrackMyWalkssolutionforeachoftheplatform-specificprojects,andwilldisplayaprogressindicatorsimilartotheoneshowninthefollowingscreenshot:
Duringthepackageupdateprocess,somepackagesthatarecontainedaspartofeachplatform-specificprojectrequireyoutoaccepttheirlicensetermspriortoinstalling,whichisshowninthefollowingscreenshot:
2. ClickontheAcceptbuttontoacceptthelicensetermsandconditionsforthepackagesdisplayedwithinthedialogboxandtoinstallthepackages,asshownintheprecedingscreenshot.
NowthatyouhavesuccessfullyupdatedtheXamarin.Formspackageswithinyoursolution,wecannowproceedwithbuildingtheuserinterfacefilesfortheTrackMyWalkssolution.
CreatingtheTrackMyWalksmodelInthissection,wewillproceedtocreateourTrackMyWalksmodelthatwillrepresentourwalkentries.Asweprogressthroughoutthischapter,wewillseehowwecanusethismodeltosetupandinitializesomewalkentriesforourmainWalksPageusingaListViewcontrolsothatwecandisplaywalkentryforeachrowwithintheListView.
Let'stakealookathowwecanachievethis,byfollowingthestepsbelow:
1. CreateanewfolderwithintheTrackMyWalksPortableClassLibraryprojectsolution,calledModelsasshowninthefollowingscreenshot:
2. Next,createanewclassfilewithintheModelsfolder,asshowninthefollowingscreenshot:
3. Then,choosetheEmptyClassoptionundertheGeneralsectionandenterinWalkEntriesforthenameofthenewclassfiletobecreatedasshowninthefollowingscreenshot:
4. Next,clickontheNewbuttontoallowthewizardtoproceedandcreatethenewfile,asshownintheprecedingscreenshot.
Congratulations,youhavecreatedyourfirstfolderandC#classfileforoursolution.Wecannowproceedwithaddingthepropertydescriptorsthatwillbeusedtodefineourmodel.
5. EnsurethattheWalkEntries.csfileisdisplayed,thenlocatetheWalkEntriesclassconstructorandenterthefollowinghighlightedcodesections:
//
//WalkEntries.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
namespaceTrackMyWalks.Models
{
publicclassWalkEntries
{
publicstringTitle{get;set;}
publicstringNotes{get;set;}
publicdoubleLongitude{get;set;}
publicdoubleLatitude{get;set;}
publicdoubleKilometers{get;set;}
publicstringDifficulty{get;set;}
publicdoubleDistance{get;set;}
publicstringImageUrl{get;set;}
}
}
Intheprecedingcodesnippet,wehavesuccessfullydefinedthemodelthatwillbeusedtorepresentourwalkentries.Inthenextsection,wewillusethismodeltosetupandinitializesomewalkentriesforourmainWalksPageusingaListViewcontrol,thenuseaDataTemplatetodescribehowthemodeldatashouldbedisplayedforeachrowwithintheListView.
CreatingthewalksmainpageAsmentionedintheprevioussection,theWalksPagewillessentiallyserveasthemainentrypointforourapplication.WewilluseourWalkEntriesmodeltopopulatesomestaticwalksinformationdata,thendisplaythisinformationwithinaListViewcontrolusingaDataTemplate.Solet'sgetstartedbyfollowingthesesteps:
1. Firstly,createanewfolderwithintheTrackMyWalksPortableClassLibraryprojectsolutioncalledPages,asyoudidintheprevioussection.
2. Next,fromtheNewFilescreen,selecttheFormssectionwithintheleftsectionpane.3. Then,selecttheFormsContentPageoptionintherightpane.4. Next,enterWalksPageforthenameofthenewclasstobecreated.5. Finally,clickontheNewbutton,asshowninthefollowingscreenshot:
6. Next,ensurethattheWalksPage.csfileisdisplayedwithinthecodeeditorandenterinthefollowinghighlightedcodesections.
//
//WalksPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.Collections.Generic;
usingXamarin.Forms;
usingTrackMyWalks.Models;
namespaceTrackMyWalks
{
publicclassWalksPage:ContentPage
{
publicWalksPage()
{
varnewWalkItem=newToolbarItem
{
Text="AddWalk"
};
newWalkItem.Clicked+=(sender,e)=>
{
Navigation.PushAsync(newWalkEntryPage());
};
ToolbarItems.Add(newWalkItem);
varwalkItems=newList<WalkEntries>
{
newWalkEntries{
Title="10MileBrookTrail,MargaretRiver",
Notes="The10MileBrookTrailstartsintheRotaryPark
nearOldKate,apreservedsteam"+
"engineatthenorthernedgeofMargaretRiver.",
Latitude=-33.9727604,
Longitude=115.0861599,
Kilometers=7.5,
Distance=0,
Difficulty="Medium",
ImageUrl="http://trailswa.com.au/media/cache/media/
images/trails/_mid/FullSizeRender1_600_480_c1.jpg"
},
newWalkEntries{
Title="AncientEmpireWalk,ValleyoftheGiants",
Notes="TheAncientEmpireisa450metrewalktrail
thattakesyouaroundandthroughsomeof"+
"thegianttingletreesincludingthemostpopular
ofthegnarledveterans,knownasGrandmaTingle.",
Latitude=-34.9749188,
Longitude=117.3560796,
Kilometers=450,
Distance=0,
Difficulty="Hard",
ImageUrl="http://trailswa.com.au/media/cache/media/
images/trails/_mid/Ancient_Empire_534_480_c1.jpg"
},
};
varitemTemplate=newDataTemplate(typeof(ImageCell));
itemTemplate.SetBinding(TextCell.TextProperty,"Title");
itemTemplate.SetBinding(TextCell.DetailProperty,"Notes");
itemTemplate.SetBinding(ImageCell.ImageSourceProperty,
"ImageUrl");
varwalksList=newListView{
HasUnevenRows=true,
ItemTemplate=itemTemplate,
ItemsSource=walkItems,
SeparatorColor=Color.FromHex("#ddd"),
};
//Setupoureventhandler
walksList.ItemTapped+=(objectsender,
ItemTappedEventArgse)=>
{
varitem=(WalkEntries)e.Item;
if(item==null)return;
Navigation.PushAsync(newWalkTrailPage(item));
item=null;
};
Content=walksList;
}
}
}
Intheprecedingcodesnippet,webeganbydeclaringournewWalkItemvariablethatinstantiatesfromtheToolbarItemclasswhichwillbeusedtoattachanewAddWalkbuttontothemaintoolbarofthebaseContentPage.ToolbarItemscollectiontoprovideawayforuserstoaddnewwalktrailinformationwithintheapp.
Next,wecreateaneventforournewWalkItemusingtheClickedeventoftheToolbarItemclass,whichwillbeusedtonavigatetothenewWalksEntryPage.
Inournextstep,wedeclareanewvariablewalkItemsthatisacollectionoflistitemstostoreeachofourwalkentrieswithinourmodelandthenusetheDataTemplateclasstodescribehowwewantourmodeldatatobedisplayedwithineachoftherowsdeclaredwithintheListView.
Finally,wesetupaneventhandlerforourListViewthatwillbeusedtomovetotheWalksTrailPagetodisplayinformationabouttheitemselected.
CreatingthenewwalkentrycontentpageInthissection,wewillbeginbuildingtheuserinterfaceforournewWalkEntryPage.ThispageiscalledwhentheuserclicksontheAddWalkbuttonfromthemainpageandwillbeusedtoallowtheuserameansofaddingnewwalkinformationtobeusedwithintheapplication.
Thereareanumberofwaysyoucangoaboutpresentingthisinformationtocollectdata.Forthepurposeofthisapp,wewillbeusingaTableView,butyoucouldquiteeasilyuseaStackLayoutandpresentthisinformationasaseriesofLabelsandEntryCells.
Let'sbeginbycreatingthenewWalkEntryPagebyperformingthefollowingsteps:
1. CreateanewContentPagecalledWalkEntryPage,asyoudidinthesectionentitledCreatingthewalksmainpage,locatedwithinthischapter.
2. Next,ensurethattheWalkEntryPage.csfileisdisplayedwithinthecodeeditorandenterinthefollowinghighlightedcodesections:
//
//WalkEntryPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingSystem.Collections.Generic;
namespaceTrackMyWalks
{
publicclassWalkEntryPage:ContentPage
{
publicWalkEntryPage()
{
//SettheContentPageTitle
Title="NewWalkEntry";
//DefineourNewWalkEntryfields
varwalkTitle=newEntryCell
{
Label="Title:",
Placeholder="TrailTitle"
};
varwalkNotes=newEntryCell
{
Label="Notes:",
Placeholder="Description"
};
varwalkLatitude=newEntryCell
{
Label="Latitude:",
Placeholder="Latitude",
Keyboard=Keyboard.Numeric
};
varwalkLongitude=newEntryCell
{
Label="Longitude:",
Placeholder="Longitude",
Keyboard=Keyboard.Numeric
};
varwalkKilometers=newEntryCell
{
Label="Kilometers:",
Placeholder="Kilometers",
Keyboard=Keyboard.Numeric
};
varwalkDifficulty=newEntryCell
{
Label="DifficultyLevel:",
Placeholder="WalkDifficulty"
};
varwalkImageUrl=newEntryCell
{
Label="ImageUrl:",
Placeholder="ImageURL"
};
//DefineourTableView
Content=newTableView
{
Intent=TableIntent.Form,
Root=newTableRoot
{
newTableSection()
{
walkTitle,
walkNotes,
walkLatitude,
walkLongitude,
walkKilometers,
walkDifficulty,
walkImageUrl
}
}
};
varsaveWalkItem=newToolbarItem{
Text="Save"
};
saveWalkItem.Clicked+=(sender,e)=>{
Navigation.PopToRootAsync(true);
};
ToolbarItems.Add(saveWalkItem);
}
}
}
Intheprecedingcodesnippet,webeganbydeclaringanumberofEntryCelllabelsforouruserinterfacetocaptureinformationenteredbytheuserfor-Title,Notes,Latitude,Longitude,Kilometers,DifficultyandImageURL.Asyouprogressthroughthisbook,youwilllearnhowtocustomizethelookandfeeloftheEntryCellsbycreatingacustomizedplatform-specificpickerfortheWalkDifficultyandKilometers.
Next,wedefineourTableViewandaddeachofourEntryCellfieldstotheTableSectionpropertyoftheTableViewcontrol.EachTableSectionthatisdefinedwithinaTableViewconsistsofaheadingandoneormoreViewCells,which,inourcase,aretheEntryCellfields.
Finally,wedeclareandaddaToolbarItemcalledsaveWalkItemtoourContentPageToolbarItemscollection,thencreateaneventthat,whenclicked,willsavethewalkinformationenteredtothemainwalkspage.Obviously,wewillberefactoringthenewWalkEntryPagethroughoutthisbook,which,whentheSavebuttonispressed,willactuallysendthisinformationtotheserverusingaRESTfulAPIandrefreshthemainTrackMyWalkspage.
CreatingthewalktrailcontentpageInthissection,wewillbeginbuildingtheuserinterfaceforourWalksTrailPage.ThispageiscalledwhentheuserclicksonanentrywithintheListViewonourTrackMyWalksmaincontentpageandwillbeusedtodisplayinformationassociatedwiththechosentrail:
1. CreateanewContentPagecalledWalkTrailPageasyoudidinthesectionentitledCreatingthewalksmainpage,locatedwithinthischapter.
2. Next,ensurethattheWalkTrailPage.csfileisdisplayedwithinthecodeeditorandenterthefollowinghighlightedcodesections:
//
//WalkTrailPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Models;
namespaceTrackMyWalks
{
publicclassWalkTrailPage:ContentPage
{
publicWalkTrailPage(WalkEntrieswalkItem)
{
Title="WalksTrail";
varbeginTrailWalk=newButton
{
BackgroundColor=Color.FromHex("#008080"),
TextColor=Color.White,
Text="BeginthisTrail"
};
//Setupoureventhandler
beginTrailWalk.Clicked+=(sender,e)=>
{
if(walkItem==null)return;
Navigation.PushAsync(newDistanceTravelledPage(walkItem));
Navigation.RemovePage(this);
walkItem=null;
};
varwalkTrailImage=newImage()
{
Aspect=Aspect.AspectFill,
Source=walkItem.ImageUrl
};
vartrailNameLabel=newLabel()
{
FontSize=28,
FontAttributes=FontAttributes.Bold,
TextColor=Color.Black,
Text=walkItem.Title
};
vartrailKilometersLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=12,
TextColor=Color.Black,
Text=$"Length:{walkItem.Kilometers}km"
};
vartrailDifficultyLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=12,
TextColor=Color.Black,
Text=$"Difficulty:{walkItem.Difficulty}"
};
vartrailFullDescription=newLabel()
{
FontSize=11,
TextColor=Color.Black,
Text=$"{walkItem.Notes}",
HorizontalOptions=LayoutOptions.FillAndExpand
};
this.Content=newScrollView
{
Padding=10,
Content=newStackLayout
{
Orientation=StackOrientation.Vertical,
HorizontalOptions=LayoutOptions.FillAndExpand,
Children=
{
walkTrailImage,
trailNameLabel,
trailKilometersLabel,
trailDifficultyLabel,
trailFullDescription,
beginTrailWalk
}
}
};
}
}
}
Intheprecedingcodesnippet,webeganbyimportingourTrackMyWalks.ModelsclassaswewillbeusingthistoextracttheinformationpassedinfromourWalksPage.
Next,wedeclareourbeginTrailWalkvariablethatinheritsfromtheButtonclass;thenwesetuptheClickedeventoftheButtonclass,whichwillbeusedtonavigatetotheDistanceTravelledPagecontentpagewhenclickedtodisplayinformationaboutourtrailafterremovingourwalkstrailcontentpagefromtheNavigationPagehierarchy.
Inthenextstep,wedeclareanimagevariablewalkTrailImageandsettheSourcepropertyoftheimagetobetheimageoftheselectedwalkItemfromtheListView.WethendeclareandinitializeanumberoflabelobjectsthatwillcontainthewalkIteminformationthathasbeenpassedfromtheWalksPagecontentpageListViewcontrolanddisplayed.
Next,wedefineaScrollViewcontrolthatispartoftheXamarin.Forms.Corebaseclass,thenaddeachofourformImageandLabelfieldstotheStackLayoutcontrol.TheScrollViewcontrolisafantasticcontrolthatallowsourContentPagetoscrollitscontentsshouldtheinformationbetoobigtofitwithintheactualdevice'sscreenrealestate.
AddingtheXamarin.Forms.MapsNuGetpackageInthissection,wewillneedtoaddtheXamarin.Forms.MapsNuGetpackagetoourcoreproject,aswellasforeachoftheplatform-specificprojectsforbothiOSandAndroidplatforms.ThispackageisrequiredinordertousetheXamarin.FormsMapcontrolintheDistanceTravelledcontentpagethatwewillbebuildinginthenextsection.
1. Right-clickontheTrackMyWalkssolutionandchoosetheAddPackages...menuoption,asshowninthefollowingscreenshot:
2. ThiswilldisplaytheAddPackagesdialog.EnterinmapswithintheSearchdialogandthenselectandclicktheXamarin.Forms.Mapsoptionwithinthelist,asshowninthefollowingscreenshot:
3. Finally,clickontheAddPackagebuttontoaddtheXamarin.Forms.MapsNuGetpackagetotheTrackMyWalkscoresolution.
4. RepeatthesameprocesstoaddtheXamarin.Forms.MapsNuGetpackageforboththeiOSandAndroidprojectsthatarecontainedwithintheTrackMyWalkssolution.
NowthatyouhaveaddedtheNuGetPackagefortheXamarin.FormsMap,wecanbegintoutilizethiscontrolwithintheDistanceTravelledcontentpagethatwewillbecoveringinthenextsection.
CreatingtheDistanceTravelledPagecontentpageInthissection,wewillbeginbuildingtheuserinterfaceforourDistanceTravelledPagecontentpage.ThispageiscalledwhentheuserclicksontheBeginthisTrailbuttonfromtheWalksTrailPagecontentpage,whichwillbeusedtodisplayinformationaboutthechosentrail,aswellasplacingapinplaceholderwithintheXamarin.Forms.Mapscontrolandcalculatingthedistancetravelled,andtimetaken:
1. CreateanewContentPagecalledDistanceTravelledPageasyoudidintheprevioussection.
2. Next,ensurethattheDistanceTravelledPage.csfileisdisplayedwithinthecodeeditorandenterthefollowinghighlightedcodesections:
//
//DistanceTravelledPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingXamarin.Forms.Maps;
usingTrackMyWalks.Models;
namespaceTrackMyWalks
{
publicclassDistanceTravelledPage:ContentPage
{
3. Then,updatetheDistanceTravelledPagemethodconstructortoincludethewalkItemparameterforthechosenwalk,asshownbythefollowinghighlightedcodesections:
publicDistanceTravelledPage(WalkEntrieswalkItem)
{
Title="DistanceTravelled";
4. Next,wedeclareatrailMapvariablethatwillpointtoaninstanceoftheXamarin.Forms.Mapscontroltocreateaplaceholderpinmarkerwithinthemapcontrol.Usingthelatitudeandlongitudecoordinates,enterthefollowinghighlightedcodesections:
//Instantiateourmapobject
vartrailMap=newMap();
//Placeapinonthemapforthechosenwalktype
trailMap.Pins.Add(newPin
{
Type=PinType.Place,
Label=walkItem.Title,
Position=newPosition(walkItem.Latitude,walkItem.Longitude)
});
//Centerthemaparoundthelistofwalksentry'slocation
trailMap.MoveToRegion(MapSpan.FromCenterAndRadius(new
Position(walkItem.Latitude,walkItem.Longitude),
Distance.FromKilometers(1.0)));
5. Then,wedeclareanumberofLabelobjectsthatcontainourwalkIteminformation,whichhasbeenpassedfromtheWalkTrailPagecontentpagesothatwecantrailrelatedinformation.Enterinthefollowinghighlightedcodesections:
vartrailNameLabel=newLabel()
{
FontSize=18,
FontAttributes=FontAttributes.Bold,
TextColor=Color.Black,
Text=walkItem.Title
};
vartrailDistanceTravelledLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=20,
TextColor=Color.Black,
Text="DistanceTravelled",
HorizontalTextAlignment=TextAlignment.Center
};
vartotalDistanceTaken=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=20,
TextColor=Color.Black,
Text=$"{walkItem.Distance}km",
HorizontalTextAlignment=TextAlignment.Center
};
vartotalTimeTakenLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=20,
TextColor=Color.Black,
Text="TimeTaken:",
HorizontalTextAlignment=TextAlignment.Center
};
vartotalTimeTaken=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=20,
TextColor=Color.Black,
Text="0h0m0s",
HorizontalTextAlignment=TextAlignment.Center
};
6. Next,wedeclareourwalksHomeButtonvariablethatinheritsfromtheButtonclassandproceedtosetupourclickhandler,whichwillbeusedtonavigateourapptothemainWalksPagecontentpagewhenclicked.Enterthefollowinghighlightedcodesections:
varwalksHomeButton=newButton
{
BackgroundColor=Color.FromHex("#008080"),
TextColor=Color.White,
Text="EndthisTrail"
};
//Setupoureventhandler
walksHomeButton.Clicked+=(sender,e)=>
{
if(walkItem==null)return;
Navigation.PopToRootAsync(true);
walkItem=null;
};
7. Then,wedefineaScrollViewcontrolthatispartoftheXamarin.Forms.CorebaseclassandproceedtoaddeachofourformButtonandLabelfieldstotheStackLayoutcontrol.Enterthefollowinghighlightedcodesections:
this.Content=newScrollView
{
Padding=10,
Content=newStackLayout
{
Orientation=StackOrientation.Vertical,
HorizontalOptions=LayoutOptions.FillAndExpand,
Children={
trailMap,
trailNameLabel,
trailDistanceTravelledLabel,
totalDistanceTaken,
totalTimeTakenLabel,
totalTimeTaken,
walksHomeButton
}
}
};
}
}
}
Intheprecedingcodesnippet,webeganbyimportingourTrackMyWalks.ModelsclassaswewillbeusingthistoextracttheinformationpassedinfromourWalksPage.TheXamarin.Forms.MapsNuGetpackageisacross-platformlibrarythatallowsdeveloperstodisplayandannotateinformationwithinthemap.Wewillbeusingthiscontroltocreateapinplaceholderwithinthemapcontrol,alongwithadditionaldetailsassociatedwiththetrail.
Next,wedeclareourtrailMapvariablethatpointstoaninstanceoftheXamarin.Forms.Mapscontrolandcreateaplaceholderpinmarkerwithinthemapcontainingthedetailsforthechosentrail,thencenterinonthemaptoshowtheareaforourwalkslocation,derivedbythelatitudeandlongitudecoordinates.WethendeclareandinitializeanumberofLabelobjectsthatcontainthewalkIteminformationthathasbeenpassedfromtheWalkTrailPagecontentpageand
declareourwalksHomeButtonvariablethatinheritsfromtheButtonclass,thensetuptheClickedeventfortheButtonclass,whichwillbeusedtonavigatebacktotheTrackMyWalkswhenclicked.
Finally,wedefineaScrollViewcontrolthatispartoftheXamarin.Forms.Corebaseclass,thenaddeachofourformButtonandLabelfieldstotheStackLayoutcontrol.
Inournextsection,wewillneedtoinitializetheXamarin.Forms.MapsNuGetpackagelibrarywithineachofourplatform-specificstartupclasses(forexample,AppDelegate.csforiOSandMainActivity.csforAndroid).Let'stakealookathowwecanachievethisbyfollowingthestepsbelow.
1. EnsurethattheAppDelegate.csfileisdisplayedwithinthecodeeditorandenterthefollowinghighlightedcodesections:
//
//AppDelegate.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingFoundation;
usingUIKit;
namespaceTrackMyWalks.iOS
{
[Register("AppDelegate")]
publicpartialclassAppDelegate:
global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
publicoverrideboolFinishedLaunching(UIApplicationapp,
NSDictionaryoptions)
{
global::Xamarin.Forms.Forms.Init();
Xamarin.FormsMaps.Init();
LoadApplication(newApp());
returnbase.FinishedLaunching(app,options);
}
}
}
Intheprecedingcodesnippet,webeganbyinitializingourAppDelegateclasstousetheXamarin.Forms.Mapslibrary,byaddingtheXamarin.FormsMaps.InitwhichinitializestheXamarin.Formsplatform,sothatourTrackMyWalkssolutioncanusethemaps.Ifthisisomittedfromthisclass,theDistanceTravelledPagecontentpagewillnotdisplaythemapandwillnotworkasexpected.
Note
FormoreinformationonXamarin.Forms.Mapslibrary,aswellasthevarioustypesofdifferent
classesavailable,pleaserefertotheXamarindeveloperdocumentationathttps://developer.xamarin.com/api/namespace/Xamarin.Forms.Maps/.
CreatingtheSplashscreencontentpageInthissection,wewillbeginbuildingtheuserinterfaceforourSplashpagewhichwillonlybeusedfortheAndroidplatform,sinceiOSalreadycontainsamethodofachievingthis.ThesplashpagewillsimplydisplaythedefaultXamarinicon,butasweprogressthroughoutthisbook,wewillberefactoringthispagetoincludeamoresuitableimageforourapp:
1. CreateanewContentPagecalledSplashPageasyoudidintheprevioussection.2. Next,ensurethattheSplashPage.csfileisdisplayedwithinthecodeeditorandenterthe
followinghighlightedcodesections:
//
//SplashPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Threading.Tasks;
usingXamarin.Forms;
namespaceTrackMyWalks
{
publicclassSplashPage:ContentPage
{
3. Then,locatetheSplashPageconstructormethodandenterthefollowinghighlightedcodesections:
publicSplashPage()
{
AbsoluteLayoutsplashLayout=newAbsoluteLayout
{
HeightRequest=600
};
varimage=newImage()
{
Source=ImageSource.FromFile("icon.png"),
Aspect=Aspect.AspectFill,
};
AbsoluteLayout.SetLayoutFlags(image,AbsoluteLayoutFlags.All);
AbsoluteLayout.SetLayoutBounds(image,newRectangle(0f,0f,
1f,1f));
splashLayout.Children.Add(image);
Content=newStackLayout()
{
Children={splashLayout}
};
}
4. Next,locatetheOnAppearingmethodandenterthefollowinghighlightedcodesections:
protectedoverrideasyncvoidOnAppearing()
{
base.OnAppearing();
//Delayforafewsecondsonthesplashscreen
awaitTask.Delay(3000);
//InstantiateaNavigationPagewiththeMainPage
varnavPage=newNavigationPage(newWalksPage()
{
Title="TrackMyWalks"
});
Application.Current.MainPage=navPage;
}
}
}
Intheprecedingcodesnippet,webeganbyimportingourSystem.Threading.Tasksclass.Thisclasswillbeusedtoperformaslightdelaytoallowourusertoseethesplashscreen,asdefinedwithintheOnAppearingclassmethod.
WethencreateasplashLayoutvariableoftypeAbsoluteLayoutthatwillbeusedtopositionandsizeeachofthechildelementsproportionallywithinourview,andthensettheHeightRequestproperty.
Next,wedeclareanimagevariablethatinheritsfromtheImageclass;thenassigntheSourcepropertytotheimagethatwewouldliketouseandsettheimagesAspectpropertysothattheimagefillstheview.
Inourfinalsteps,wedefinevaluesforbothoftheLayoutFlagsandLayoutBoundspropertiesontheAbsoluteLayoutclasssothattheimageresizeswithintheview.ThenweaddoursplashLayouttothecontentpageusingtheStackLayoutcontrol.Finally,weoverridetheOnAppearingclassmethodoftheXamarin.Forms.Pagepagelifecycle,whichgetscalledimmediatelypriortothepagebecomingvisibleonthescreen,andthenspecifyadelayofthreesecondspriortoinstantiatinganewinstanceoftheNavigationPage,whichwillcallourWalksPagetobethemaincontentrootpage.
UpdatingtheXamarin.FormsAppclassInthissection,weneedtoinitializetheAppclassofXamarin.FormslibrarytocallourSplashPageandsettherootpageforourapplicationifthedetectedOSdeviceisAndroid.Let'stakealookathowwecanachievethis:
1. OpentheTrackMyWalks.cslocatedwithintheTrackMyWalksgroupandensurethatitisdisplayedwithinthecodeeditor.
2. Next,locatetheAppmethodandenterthefollowinghighlightedcodesections,asshowninthefollowingcodesnippet:
//
//TrackMyWalks.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
namespaceTrackMyWalks
{
publicclassApp:Application
{
publicApp()
{
//ChecktheTargetOSPlatform
if(Device.OS==TargetPlatform.Android)
{
MainPage=newSplashPage();
}
else
{
//Therootpageofyourapplication
varnavPage=newNavigationPage(newTrackMyWalks.
WalksPage()
{
Title="TrackMyWalks"
});
MainPage=navPage;
}
}
}
}
Intheprecedingcodesnippet,webegancheckingtheDevice.OSclasstodeterminewhatOSXamarin.Formsisrunningon,thenusedtheTargetPlatformclasstodeterminewhetherourappisbeingrunonGoogle'sAndroidOSplatform.IfourappisrunningonAndroid,wesettheAppconstructormethodsMainPagetoanewinstanceoftheContentPagethatwillsimplyusetheSplashPageastherootpage.Alternatively,wecreateanewinstanceoftheNavigationPageclassandsetthistoourWalksPagetobethemaincontentrootpageforourContentPage.
DifferencesbetweenXamarinStudioandVisualStudioWhendevelopingcross-platformmobileapps,youcurrentlyhaveachoiceofusingeitherXamarinStudioorMicrosoft'sVisualStudiodevelopmentenvironments.Itisworthnotingthat,althoughthescreenshotsandexamplecodeusedthroughoutthisbookhavebeendevelopedusingXamarinStudiorunningonanAppleMacintoshcomputer,thecodeshouldcompilefineonaWindowsmachinerunningMicrosoftVisualStudio2015.
However,therearesomedifferencesthatyouneedtobeawareofbeforeembarkingonthejourneyofbuildingyourmobiledevelopmentsolutions.IfyouarerunningXamarinStudioonaWindowsmachine,youwillgetanAndroidprojectsolutionwheneveryoucreateanewXamarin.Formssolution.
IfyouwanttointegrateanddevelopappsforWindowsPhone,youwillneedtoensurethatyouarerunningMicrosoftVisualStudioonaWindowsmachine.WhendevelopingappsforiOSapplications,youwillneedtoprepareyourMactobetheXamarinbuildhostbyfirstlyenablingRemoteLoginonyourMacwithintheSystemPreferencessection,andthenselectingtheMactobethebuildhostfromwithintheMicrosoftVisualStudioenvironmentonyourWindowsmachine.
Note
FormoreinformationonhowtoprepareyourMactobetheXamarinbuildhost,refertotheXamarindeveloperdocumentationathttps://developer.xamarin.com/guides/ios/getting_started/installation/windows/connecting-to-mac/.
NowthatyouhaveanunderstandingofthedifferencesbetweenXamarinStudioandMicrosoftVisualStudio,ournextstepistocompile,buildandruntheTrackMyWalksapplicationwithintheiOSsimulator.
RunningtheTrackMyWalksappusingthesimulatorInthissection,wewilltakealookathowtocompileandruntheTrackMyWalksapplication.Youhavetheoptionofchoosingtorunyourapplicationusinganactualdevice,orchoosingfromalistofsimulatorsavailableforaniOSdevice.
TheversionnumberofthesimulatorisdependentontheversionoftheiOSSDKthatisinstalledonyourcomputer.Performthefollowingsteps:
1. Toruntheapp,chooseyourpreferreddevicefromthelistofavailableiOSsimulatorsandselecttheRunmenuoption,asshowninthefollowingscreenshot:
2. Next,choosetheRunWithsub-menuitem,andthenchoosetheCustomConfiguration...thenclickontheRunbuttonfromtheCustomParametersdialog,asshowninthefollowingscreenshot:
3. Alternatively,youcanalsobuildandruntheTrackMyWalksapplicationbypressingCommand+Returnkeycombinations.
Whenthecompilationiscomplete,theiOSsimulatorwillappearautomaticallyandtheTrackMyWalksapplicationwillbedisplayed,asshowninthefollowingscreenshot:
Asyoucanseefromtheprecedingscreenshot,thiscurrentlydisplaysalistofstaticwalktrailentries,thataredisplayedwithinourListView.WhentheuserclicksontheAddWalkbuttonlink,thiswilldisplaythenewWalkEntrycontentpage,sotheycanbeginenteringnewWalktrailinformation.
Currently,thispagedoesn'tsaveenteredinformation,butasweprogressthroughoutthisbook,wewillberefactoringthesepagestoallowforthistohappen.UponclickingtheSavebutton,theuserwillberedirectedbacktothemainTrackMyWalkspage.
Theprecedingscreenshotshowsyouthenavigationflowbetweeneachofthepageswhenatrailhasbeenselectedfromthelist,withthefinalscreenshowingthedistancetravelledpagealongwiththeplaceholderpinmarkershowingthetraillocationwithinthemapview.
Congratulations,youhavesuccessfullybuiltthefoundationfortheTrackMyWalksapplication,aswellastheuserinterfaceforeachofthecontentpagesthatwillbeusedbyourapp.Asweprogressthroughthisbook,wewillbeenhancingourapptoincludebetterarchitecturaldesignpatterns,nicerlookinguserinterfaceelements,aswellasreal-timedatabeingsynchronizedthroughtheuseofRESTfulwebserviceAPIs.
SummaryInthischapterweexploredhowtogoaboutcreatingaXamarin.Formscross-platformapplicationforbothiOSandAndroidplatforms.Wethenmovedontobuildingaseriesofcontentpageswithstaticdata.
Next,welookedathowtousethedefaultXamarin.FormsnavigationAPIstohelpmovebetweeneachofthecontentpages,whichwewillrefactorquiteabitwhenwecoverthisinChapter3,NavigatingwithintheMVVMModel-TheXamarin.FormsWaytouseamoreflexible,customizednavigationservice.Finally,wetalkedaboutsomeofthedifferencesbetweenusingXamarinStudioandMicrosoftVisualStudiofordevelopment,beforerunningourTrackMyWalksappwithinthesimulator.
Inthenextchapter,youwilllearnabouttheconceptsbehindtheModel-View-View-Model(MVVM)patternarchitecture,howtoimplementtheMVVMmodelwithinyourapplication,andtheprocessofhowtoaddnewViewModelstoyourXamarinsolution.
Chapter2.MVVMandDataBindingInthepreviouschapter,weexploredhowtogoaboutcreatinganativeXamarin.Formscross-platformapplicationforboththeiOSandAndroidplatforms,andlearnedhowtoaddnewpackagestoyoursolutionusingtheNuGetpackagemanager.WealsolookedathowtogoaboutaddingandcreatingseveralContentPagestoyoursolution,aswellashowtorunandtestyourappwithinthesimulator.
TheModel-View-ViewModel(MVVM)architecturalpatternwasinventedwiththeExtensibleApplicationMarkupLanguage(XAML)inmindthatwascreatedbyMicrosoftbackin2008andisparticularlywellsuitedforusewiththeMVVMapplicationarchitecturalpattern,becauseitenforcesaseparationoftheXAMLuserinterfacefromtheunderlyingdatamodelthroughaclassthatwillactasaconnectionbetweenboththeViewandtheModel.TheViewandtheViewModelcanthenbeconnectedthroughdatabindingsthathavebeendefinedwithintheXAMLfile.
XAMLhasalsobeenintegratedintotheXamarin.Formsplatformthatallowsforthecreationofcross-platform,natively-basedprogramminginterfacesforiOS,Android,andWindowsPhonemobiledevices.ThisallowsdeveloperstodefineuserinterfacesusingalltheXamarin.Formsviews,layouts,andpages,aswellascustomclasses.
XAMLenforcestheseparationbetweentheapplication'suserinterfacefromtheunderlyingdata,throughaclassthatwillactasthecommunicationlayerbetweentheViewandtheViewModel,whichareconnectedthroughdata-bindingsthataredefinedineithertheXAMLorunderlyingcodefile,alongwiththebindingcontextfortheView,thatpointstoaninstanceoftheViewModel.
ThischapterwillbeginbyintroducingyoutothearchitecturebehindtheMVVMpatternarchitecture,andhowyoucanimplementthesewithinyourapplication,byaddingnewViewsandtheassociatedModels.
You'lllearnhowtocreatetheunderlyingC#classfilesthatwillactastheViewModelsforourapp,aswellasupdatingtheexistingcontentpagestodata-bindwiththeViewModelstorepresenttheinformationthatwillbedisplayedwithintheuser-interfaceforourapplication.
Thischapterwillcoverthefollowingpoints:
UnderstandingtheMVVMpatternarchitectureanddata-bindingImplementingtheMVVMbasemodelwithintheTrackMyWalkssolutionImplementingtheMVVMViewModelswithintheappImplementingtheMVVMdata-bindingstoouruserinterfacepages
UnderstandingtheMVVMpatternarchitectureInthissectionwewillbetakingalookattheMVVMpatternarchitectureandthecommunicationbetweenthecomponentsthatmakeupthearchitecture.
TheMVVMdesignpatternisdesignedtocontroltheseparationbetweentheuserinterfaces(views),theViewModelsthatcontaintheactualbindingtotheModel,andthemodelsthatcontaintheactualstructureoftheentitiesrepresentinginformationstoredonadatabaseorfromawebservice.
ThefollowingscreenshotshowsthecommunicationbetweeneachofthecomponentscontainedwithintheMVVMdesignpatternarchitecture:
TheMVVMdesignpatternisdividedintothreemainareas,asyoucanseefromtheprecedingscreenshot,andtheseareexplainedinthefollowingtable:
MVVMtype Description
ModelTheModelisbasicallyarepresentationofbusinessrelatedentitiesusedbyanapplication,andisresponsibleforfetchingdatafromeitheradatabase,orwebservice,andthende-serializedtotheentitiescontainedwithintheModel.
View
TheViewcomponentoftheMVVMmodelbasicallyrepresentstheactualscreensthatmakeuptheapplication,alongwithanycustomcontrolcomponents,andcontrolelements,suchasbuttons,labels,andtextfields.
TheviewscontainedwithintheMVVMpatternareplatform-specificandaredependentontheplatformAPIsthatareusedtorendertheinformationthatis
containedwithintheapplication'suserinterface.
ViewModel
TheViewModelessentiallycontrols,andmanipulatestheviewsbyactingastheirmaindatacontext.TheViewModelcontainsaseriesofpropertiesthatareboundtotheinformationcontainedwithineachModel.Thosepropertiesarethenboundtoeachoftheviewstorepresentthisinformationwithintheuserinterface.
ViewModelscanalsocontaincommandobjectsthatprovideaction-basedeventsthatcantriggertheexecutionofeventmethodsthatoccurwithintheView.Forexample,whentheusertapsonatoolbaritem,orabutton.
ViewModelsgenerallyimplementtheINotifyPropertyChangedandINotifyCollectionChangedinterfaces.SuchaclassfiresaPropertyChangedandINotifyCollectionChangedeventwheneveracollectionhaschanged(suchas,addinganitem,removinganitem,orwhenachangeoccurstooneoftheitems,properties)changes.ThedatabindingmechanisminXamarin.FormsattachesahandlertothisPropertyChangedandCollectionChangedeventssoitcanbenotifiedwhenapropertychangesandkeepthetargetupdatedwiththenewvalue.
NowthatyouhaveagoodunderstandingofthecomponentsthatarecontainedwithinMVVMdesignpatternarchitecture,wecanbegintocreateourentitymodelsandupdateouruserinterfacefiles.
Note
InXamarin.Forms,thetermViewisusedtodescribeformcontrols,suchasbuttonsandlabels,andusesthetermPagetodescribetheuserinterfaceorscreen.Whereas,inMVVM,Viewsareusedtodescribetheuserinterface,orscreen.
ImplementingtheMVVMViewModelswithinyourappInthissection,wewillbeginbysettingupthebasicstructureforourTrackMyWalkssolutiontoincludethefolderthatwillbeusedtorepresentourViewModels.Let'stakealookatthefollowingstepstoachievethis:
1. LaunchtheXamarinStudioapplicationandensurethattheTrackMyWalkssolutionisloadedwithintheXamarinStudioIDE.
2. Next,createanewfolderwithintheTrackMyWalksPCLproject,calledViewModelsasshowninthefollowingscreenshot:
CreatingtheWalkBaseViewModelfortheTrackMyWalksappInthissection,wewillbeginbycreatingabaseMVVMViewModelthatwillbeusedbyeachofourViewModelswhenwecreatethese,theViews(pages)willthenimplementthoseViewModelsandusethemastheirBindingContext.
Tip
WewillstartbycreatingabaseViewModelclassthatwillessentiallybeanabstractclass,containingbasicfunctionalitythateachofourViewModelswillinheritfrom,andwillimplementtheINotifyPropertyChangedInterface.
Let'stakealookathowwecanachievethis,byfollowingthesesteps:
1. CreateanemptyclasswithintheViewModelsfolder,showninthefollowingscreenshot:
2. Next,choosetheEmptyClassoptionlocatedwithintheGeneralsection,andenterinWalkBaseViewModelforthenameofthenewclassfiletobecreated,asshowninthefollowingscreenshot:
3. Next,clickontheNewbuttontoallowthewizardtoproceedandcreatethenewemptyclassfile,asshownintheprecedingscreenshot.
Upuntilthispoint,allwehavedoneiscreateourWalkBaseViewModelclassfile.ThisabstractclasswillactasthebaseViewModelclassthatwillcontainthebasicfunctionalitythateachofourViewModelswillinheritfrom.
Aswestarttobuildthebaseclass,youwillseethatitcontainsacoupleofmembersanditwillimplementtheINotifyPropertyChangedInterface.Asweprogressthroughthisbook,wewillbuildtothisclass,whichwillbeusedbytheTrackMyWalksapplication.ToproceedwithcreatingthebaseViewModelclass.
EnsurethattheWalkBaseViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
//
//WalkBaseViewModel.cs
//TrackMyWalksBaseViewModel
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.ComponentModel;
usingSystem.Runtime.CompilerServices;
namespaceTrackMyWalks.ViewModels
{
publicabstractclassWalkBaseViewModel:
INotifyPropertyChanged
{
protectedWalkBaseViewModel()
{
}
publiceventPropertyChangedEventHandlerPropertyChanged;
protectedvirtualvoidOnPropertyChanged
([CallerMemberName]stringpropertyName=null)
{
varhandler=PropertyChanged;
if(handler!=null)
{
handler(this,newPropertyChangedEventArgs(propertyName));
}
}
}
}
Intheprecedingcodesnippet,webeginbycreatinganewabstractclassforourWalkBaseViewModelthatimplementsfromtheINotifyPropertyChangedinterfaceclass,whichallowstheVieworpagetobenotifiedwheneverpropertiescontainedwithintheViewModelhavechanged.Next,wedeclareavariablePropertyChangedthatinheritsfromthePropertyChangedEventHandlerthatwillbeusedtoindicatewheneverpropertiesontheobjecthavechanged.Finally,withintheOnPropertyChangedmethod,thiswillbecalledwhenithasdeterminedthatachangehasoccurredonapropertywithintheViewModelfromachildclass.
Note
TheINotifyPropertyChangedinterfaceisusedtonotifyclients,typicallybindingclients,whenthevalueofapropertyhaschanged.
ImplementingtheWalksPageViewModelIntheprevioussection,webuiltourbaseclassViewModelforourTrackMyWalksapplication.ThiswillactasthemainclassthatwillallowourVieworpagestobenotifiedwheneverchangestopropertieswithintheViewModelhavebeenmade.
Inthissection,wewillneedtobeginbuildingtheViewModelforourWalksPage.ThismodelwillbeusedtostoretheWalkEntries,whichwilllaterbeusedanddisplayedwithintheListViewontheWalksPagecontentpage.
Let'stakealookathowwecanachievethisbyfollowingthesesteps:
1. First,createanewclassfilewithintheViewModelsfoldercalledWalksPageViewModel,asyoudidintheprevioussection,entitledCreatingtheWalkBaseViewModellocatedwithinthischapter.
2. Next,ensurethattheWalksPageViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
//
//WalksPageViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.Collections.ObjectModel;
usingTrackMyWalks.Models;
namespaceTrackMyWalks.ViewModels
{
publicclassWalksPageViewModel:WalkBaseViewModel
{
ObservableCollection<WalkEntries>_walkEntries;
publicObservableCollection<WalkEntries>walkEntries
{
get{return_walkEntries;}
set{_walkEntries=value;
OnPropertyChanged();
}
}
Intheprecedingcodesnippet,webeginbyensuringthatourViewModelinheritsfromtheWalkBaseViewModelclass.Next,wecreateanObservableCollectionvariable_walkEntrieswhichisveryusefulwhenyouwanttoknowwhenthecollectionhaschanged,andaneventistriggeredthatwilltelltheuserwhatentrieshavebeenaddedorremovedfromtheWalkEntriesmodel.
Inournextstep,wecreatetheObservableCollectionconstructorWalkEntries,thatisdefinedwithintheSystem.Collections.ObjectModelclass,andacceptsaList
parametercontainingourWalkEntriesmodel.TheWalkEntriespropertywillbeusedtobindtotheItemSourcepropertyoftheListViewwithintheWalksMainPage.Finally,wedefinethegetter(get)andsetter(set)methodsthatwillreturnandsetthecontentofour_walkEntrieswhenithasbeendeterminedwhetherapropertyhasbeenmodifiedornot.
3. Next,locatetheWalksPageViewModelclassconstructor,andenterthefollowinghighlightedcodesections:
publicWalksPageViewModel()
{
walkEntries=newObservableCollection<WalkEntries>(){
newWalkEntries{
Title="10MileBrookTrail,MargaretRiver",
Notes="The10MileBrookTrailstartsinthe
RotaryParknearOldKate,apreservedsteam"+
"engineatthenorthernedgeofMargaretRiver.",
Latitude=-33.9727604,
Longitude=115.0861599,
Kilometers=7.5,
Distance=0,
Difficulty="Medium",
ImageUrl="http://trailswa.com.au/media/
cache/media/images/trails/_mid/"+
"FullSizeRender1_600_480_c1.jpg"
},
newWalkEntries{
Title="AncientEmpireWalk,ValleyoftheGiants",
Notes="TheAncientEmpireisa450metrewalktrail
thattakesyouaroundandthroughsomeof"+
"thegianttingletreesincludingthemostpopularof
thegnarledveterans,knownas"+"GrandmaTingle.",
Latitude=-34.9749188,
Longitude=117.3560796,
Kilometers=450,
Distance=0,
Difficulty="Hard",
ImageUrl="http://trailswa.com.au/media/cache/media/
images/trails/_mid/"+"Ancient_Empire_534_480_c1.jpg"
},
};
}
}
}
Intheprecedingcodesnippet,webeganbycreatinganewObservableCollectionforourwalkEntriesmethodandthenaddedeachofthewalklistitemsthatwewouldliketostorewithinourModel.Aseachitemisadded,theObservableCollection,constructoriscalled,andthesetter(set)methodisinvokedtoaddtheitem,thentheINotifyPropertyChangedeventwillbetriggeredtonotifythatachangehasoccurred.
UpdatingthewalksmainpagetousetheMVVMmodelNowthatwehavecreatedtheMVVMViewModelthatwillbeusedforourmainWalksPage,weneedtomodifytheWalksmainpage.Inthissection,wewillbetakingalookathowtobindtheWalksPageBindingContexttotheWalksPageViewModelsothatthewalkentrydetailscanbedisplayed.
Let'stakealookathowwecanachievethis,byfollowingthesesteps:
1. EnsurethattheWalksPage.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//WalksPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.Collections.Generic;
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
namespaceTrackMyWalks
{
publicclassWalksPage:ContentPage
{
publicWalksPage()
{
varnewWalkItem=newToolbarItem
{
Text="AddWalk"
};
//Setupourclickeventhandler
newWalkItem.Clicked+=(sender,e)=>
{
Navigation.PushAsync(newWalkEntryPage());
};
//AddtheToolBaritemtoourToolBar
ToolbarItems.Add(newWalkItem);
2. Next,weneedtodeclareandcreateanewBindingContextinstancefortheWalksPage,andsetthistoanewinstanceoftheWalksPageViewModelsothatitknowswheretogettheWalkEntriessothatwecanpopulatethesewithintheListView.Proceedandenterinthefollowinghighlightedcodesections:
//DeclareandinitializeourModelBindingContext
BindingContext=newWalksPageViewModel();
//DefineourItemTemplate
varitemTemplate=newDataTemplate(typeof(ImageCell));
itemTemplate.SetBinding(TextCell.TextProperty,"Title");
itemTemplate.SetBinding(TextCell.DetailProperty,"Notes");
itemTemplate.SetBinding(ImageCell.ImageSourceProperty,
"ImageUrl");
3. Then,changethewayinwhichourListViewgetstheWalkEntriesvariableitems,byupdatingtheListViewclassandsettingthebindingpropertyforthewalkentries,byinitializingtheItemsView<Cell>.ItemsSourcePropertypropertyandusingtheSetBindingmethodtobindthecontents.Proceedandenterinthefollowinghighlightedcodesections:
varwalksList=newListView
{
HasUnevenRows=true,
ItemTemplate=itemTemplate,
SeparatorColor=Color.FromHex("#ddd"),
};
//SettheBindingpropertyforourwalksEntries
walksList.SetBinding(ItemsView<Cell>.ItemsSourceProperty,
"walkEntries");
//InitializeoureventHandlertousewhentheitemistapped
walksList.ItemTapped+=(objectsender,ItemTappedEventArgse)=>
{
varitem=(WalkEntries)e.Item;
if(item==null)return;
Navigation.PushAsync(newWalkTrailPage(item));
item=null;
};
Content=walksList;
}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingourWalksPagesothatitcantakeadvantageofourWalksPageViewModel.WelookedathowtosetourcontentpagetoaninstanceofourWalksPageViewModelsothatitknowswheretogetthelistofwalkentriestobeusedanddisplayedwithintheListViewcontrol.TheSetBindingpropertycreatesandappliesabindingtoaspecificproperty.Asyoucansee,byusingViewModelswithinyourapplication,youcanseethattheresultisbothcleanandelegant,andmakesyourcodealotmorereadablewhensupportingcodemodifications.
ImplementingthewalksentrypageViewModelWehavecreatedtheViewModelthatwillbeusedbyourWalksPagesothatthewalkentriescanbedisplayedwithintheListViewcontrol.ThenextstepistobuildtheViewModelthatwillbeusedtocreatenewwalkentriesandhavethisinformationsavedbacktoourWalkBaseViewModel,whichwewillbecoveringinalaterchapterasweprogressthroughthisbook.
Inthissection,wewillbetakingalookatthestepsrequiredtocreatetheViewModelforourWalksEntryViewModelsothatwecaninitialize,andcaptureinformationenteredwithinthisscreen.
Let'stakealookathowwecanachievethis,byfollowingthesesteps:
1. CreateanewclassfilewithintheViewModelsfoldercalledWalksEntryViewModel,asyoudidintheprevioussection,entitledCreatingtheWalkBaseViewModellocatedwithinthischapter.
2. Next,ensurethattheWalksEntryViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet.
//
//WalkEntryViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
usingXamarin.Forms;
namespaceTrackMyWalks.ViewModels
{
publicclassWalkEntryViewModel:WalkBaseViewModel
{
3. Then,createthefollowingTitlepropertyanditsassociatedgettersandsetterqualifiers.TheOnPropertyChangedmethod,aswementionedpreviously,willbecalledwhenourpropertydeterminesthatthecontentshavebeenchanged.AcallismadetotheSaveCommand.ChangeCanExecutemethodthatwillvalidatetheformfields,andthendeterminewhethertheSaveToolBarItemshouldbeenabledornottoallowtheusertosavethewalkentrydetails.Proceedandenterinthefollowingcodesnippet:
string_title;
publicstringTitle
{
get{return_title;}
set
{
_title=value;
OnPropertyChanged();
SaveCommand.ChangeCanExecute();
}
}
4. Next,createtheremainingViewModelpropertiesandtheassociatedgettersandsetterqualifiersthatwillbeusedtobindthevaluesenteredontheWalkEntryPage,asshowninthefollowingcodesnippets:
string_notes;
publicstringNotes
{
get{return_notes;}
set
{
_notes=value;
OnPropertyChanged();
}
}
double_latitude;
publicdoubleLatitude
{
get{return_latitude;}
set
{
_latitude=value;
OnPropertyChanged();
}
}
double_longitude;
publicdoubleLongitude
{
get{return_longitude;}
set
{
_longitude=value;
OnPropertyChanged();
}
}
double_kilometers;
publicdoubleKilometers
{
get{return_kilometers;}
set
{
_kilometers=value;
OnPropertyChanged();
}
}
string_difficulty;
publicstringDifficulty
{
get{return_difficulty;}
set
{
_difficulty=value;
OnPropertyChanged();
}
}
double_distance;
publicdoubleDistance
{
get{return_distance;}
set
{
_distance=value;
OnPropertyChanged();
}
}
string_imageUrl;
publicstringImageUrl
{
get{return_imageUrl;}
set
{
_imageUrl=value;
OnPropertyChanged();
}
}
5. Next,weneedtoinitializeourViewModelclassconstructorwithdefaultvaluesforourTitle,DifficultyandDistanceproperties.LocatetheWalkEntryViewModelclassconstructor,andenterthefollowinghighlightedcodesections:
publicWalkEntryViewModel()
{
Title="NewWalk";
Difficulty="Easy";
Distance=1.0;
}
6. Then,weneedtocreateaCommandpropertyforourclass.ThiswillbeusedwithinourWalkEntryPageandwillbeusedtobindtotheSaveToolBarItem.TheCommandpropertywillrunanactionuponbeingpressed,thenexecuteaclassinstancemethodtodeterminewhetherthecommandcanbeexecuted.Proceedandenterthefollowinghighlightedcodesections:
Command_saveCommand;
publicCommandSaveCommand
{
get
{
return_saveCommand??(_saveCommand=new
Command(ExecuteSaveCommand,ValidateFormDetails));
}
}
7. Next,weneedtocreatetheExecuteSaveCommandinstancemethod.Thiswillbeusedtostorethewalkinformationthatweenter,whichwillbewrittentoeachofthepropertiesasdefinedwithintheWalkEntriesmodel.Thesavingportionwillnotbecoveredinthischapter,asthiswillbecoveredinalaterchapter.Fornow,proceedandenterinthefollowinghighlightedcodesections:
voidExecuteSaveCommand()
{
varnewWalkItem=newWalkEntries
{
Title=this.Title,
Notes=this.Notes,
Latitude=this.Latitude,
Longitude=this.Longitude,
Kilometers=this.Kilometers,
Difficulty=this.Difficulty,
Distance=this.Distance,
ImageUrl=this.ImageUrl
};
//Here,wewillsavethedetailsenteredinalaterchapter.
}
8. Finally,createtheValidateFormDetailsinstancemethod.Thiswillbeusedtodeterminewhetherornotwecansaveournewwalkinformation.Thismethodisprettybasic,butyoucouldaddadditionalchecksdependingonyourneeds.Forthisexample,we'llusetheIsNullOrWhiteSpacemethodonthestringclass,andpassintheTitleproperty,whichcheckstoseeiftheTitlepropertycontainsanyblankspaces,orisanemptyfield.Toproceed,enterinthefollowinghighlightedcodesection:
//methodtocheckforanyformerrors
boolValidateFormDetails()
{
return!string.IsNullOrWhiteSpace(Title);
}
}
}
Inthissection,webeganbyensuringthatourViewModelinheritsfromtheWalkBaseViewModelclassandthenmovedontocreateaTitlepropertyanditsassociatedgettersandsetterqualifiers.WealsocreatedtheOnPropertyChangedmethod,aswedefinedpreviouslysothatitwillbecalledwhenthepropertydeterminesthatthecontentshavebeenchanged.
Next,weaddedareferencetothemethodSaveCommand.ChangeCanExecutethatwillvalidatetheformfieldstodetermineiftheSaveToolBarItemshouldbeenabledtoallowtheusertosavethewalkentrydetails.WethencreatedtheremainingpropertiesforourViewModelwiththeirassociatedgettersandsetters,whichwillbeusedtobindthevaluesenteredontheWalkEntryPage.
Inthenextsteps,weinitializedtheclassconstructorwithdefaultvaluesfortheTitle,DifficultyandDistancepropertiesandthencreatedaCommandpropertytoourclasssothatitcanbeusedwithintheWalkEntryPageandwillbeusedtobindtotheSaveToolBarItem.
Finally,weneededtocreatetheExecuteSaveCommandinstancemethodsothatitcanstoreourwalkinformationtoeachofthepropertiesasdefinedwithintheWalkEntriesmodel.
UpdatingtheWalksEntryPagetousetheMVVMmodelInthissection,weneedtobindourmodelbindingcontextBindingContexttotheWalkEntryViewModelsothatthenewwalkinformationthatwillbeenteredwithinthispagecanbestoredwithintheWalkEntriesmodel.Let'stakealookathowwecanachievethis,byfollowingthesesteps:
1. EnsurethattheWalkEntryPage.csfileisdisplayedwithinthecodeeditor.
//
//WalkEntryPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingSystem.Collections.Generic;
namespaceTrackMyWalks
{
publicclassWalkEntryPage:ContentPage
{
publicWalkEntryPage()
{
//SettheContentPageTitle
Title="NewWalkEntry";
2. Next,weneedtodeclareandcreateanewBindingContextinstancefortheWalksEntryPage,andsetthistoanewinstanceoftheWalkEntryViewModelsothatitknowswheretogettheWalkEntriessothatwecanbindittotheassociatedpropertiescontainedwithinourmodel.Proceedandenterinthefollowinghighlightedcodesections:
//DeclareandinitializeourModelBindingContext
BindingContext=newWalkEntryViewModel();
3. Next,createtheremainingEntryCellobjects,aswellastheSetBindingpropertiestotheirmatchedpropertynameascontainedwithintheViewModel,asshowninthefollowingcodesnippets:
//DefineourNewWalkEntryfields
varwalkTitle=newEntryCell
{
Label="Title:",
Placeholder="TrailTitle"
};
walkTitle.SetBinding(EntryCell.TextProperty,"Title",
BindingMode.TwoWay);
varwalkNotes=newEntryCell
{
Label="Notes:",
Placeholder="Description"
};
walkNotes.SetBinding(EntryCell.TextProperty,"Notes",
BindingMode.TwoWay);
varwalkLatitude=newEntryCell
{
Label="Latitude:",
Placeholder="Latitude",
Keyboard=Keyboard.Numeric
};
walkLatitude.SetBinding(EntryCell.TextProperty,
"Latitude",BindingMode.TwoWay);
varwalkLongitude=newEntryCell
{
Label="Longitude:",
Placeholder="Longitude",
Keyboard=Keyboard.Numeric
};
walkLongitude.SetBinding(EntryCell.TextProperty,
"Longitude",BindingMode.TwoWay);
varwalkKilometers=newEntryCell
{
Label="Kilometers:",
Placeholder="Kilometers",
Keyboard=Keyboard.Numeric
};
walkKilometers.SetBinding(EntryCell.TextProperty,
"Kilometers",BindingMode.TwoWay);
varwalkDifficulty=newEntryCell
{
Label="DifficultyLevel:",
Placeholder="WalkDifficulty"
};
walkDifficulty.SetBinding(EntryCell.TextProperty,
"Difficulty",BindingMode.TwoWay);
varwalkImageUrl=newEntryCell
Label="ImageUrl:",
Placeholder="ImageURL"
};
walkImageUrl.SetBinding(EntryCell.TextProperty,
"ImageUrl",BindingMode.TwoWay);
//DefineourTableView
Content=newTableView
{
Intent=TableIntent.Form,
Root=newTableRoot
{
newTableSection()
{
walkTitle,
walkNotes,
walkLatitude,
walkLongitude,
walkKilometers,
walkDifficulty,
walkImageUrl
}
}
};
varsaveWalkItem=newToolbarItem
{
Text="Save"
};
saveWalkItem.SetBinding(MenuItem.CommandProperty,
"SaveCommand");
ToolbarItems.Add(saveWalkItem);
saveWalkItem.Clicked+=(sender,e)=>
{
Navigation.PopToRootAsync(true);
};
}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingourWalksEntryPagesothatitcantakeadvantageofourWalksEntryViewModel.WelookedathowtosetthecontentpagetoaninstanceoftheWalksEntryViewModelsothatitpointstotheWalkEntriesmodel.WethenusedtheSetBindingmethodoneachofourEntryCellssothatitcanbindtotheappropriateViewModelproperty.Finally,weupdatedtheSaveToolbarItemontheWalkEntryPagetobindtotheWalksEntryViewModelSaveCommandproperty.
Thefollowingtableprovidesabriefdescriptionofthedifferentbindingtypes,andwhentheseshouldbeusedwithinyourapplications:
Bindingmode Description
OneWayThistypeofbindingindicatesthatthebindingshouldonlypropagatechangesfromsource(usuallytheViewModel)totarget(theBindableObject).ThisisthedefaultmodeformostBindablePropertyvalues.
OneWayToSourceThistypeofbindingindicatesthatthebindingonlypropagateschangesfromthetargetBindableObjecttotheViewModelandismainlyusedforread-onlyBindablePropertyvalues.
TwoWayThistypeofbindingindicatesthatthebindingshouldpropagatethechangesfromtheViewModeltothetargetBindableObjectinbothdirections.
Onethingtonoticeisthat,ifyoudon'tspecifyavaluefortheBindingModeproperty,itwillusethedefaultBindingMode.OneWaywhichisdefinedintheBindableProperty.DefaultBindingModeproperty.
ImplementingthewalktrailpageViewModelInthissection,wewillbetakingalookatthestepsrequiredtocreatetheViewModelforourWalksTrailViewModelsothatwecanobtainthewalkentryinformationassociatedwiththechosenwalkthathasbeenselectedfromthemainWalksPage.
Let'stakealookathowwecanachievethis,byfollowingthesesteps:
1. CreateanewclassfilewithintheViewModelsfoldercalledWalksTrailViewModel,asyoudidintheprevioussectionentitledCreatingtheWalkBaseViewModel,locatedwithinthischapter.
2. Next,ensurethattheWalksTrailViewModel.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//WalksTrailViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingTrackMyWalks.Models;
namespaceTrackMyWalks.ViewModels
{
publicclassWalksTrailViewModel:WalkBaseViewModel
{
WalkEntries_walkEntry;
publicWalkEntriesWalkEntry
{
get{return_walkEntry;}
set
{
_walkEntry=value;
OnPropertyChanged();
}
}
publicWalksTrailViewModel(WalkEntrieswalkEntry)
{
WalkEntry=walkEntry;
}
}
}
Inthissection,webeganbyensuringthatourViewModelinheritsfromtheWalkBaseViewModelclassandthenwecreatedanWalkEntriesvariable_walkEntriesthatwillbeusedtostoreourWalkEntries.Next,wecreatedaWalkEntrypropertyanditsassociatedgettersandsetter
qualifiersandtheOnPropertyChangedmethod,sothatitwillbecalledwhenourpropertydeterminesthatthecontentshavebeenchanged.Inthefinalstep,wecreatedtheWalksTrailViewModelconstructor,thatacceptsaListparametercontainingourWalkEntriesmodel.
UpdatingtheWalksTrailPagetousetheMVVMmodelInthissectionweneedtobindourmodelbindingcontextBindingContexttotheWalksTrailViewModelsothatthewalkinformationdetailswillbedisplayedfromtheWalkEntriesmodelwhenawalkhasbeenclickedonwithinthemainWalksPage.Let'stakealookathowwecanachievethis,byfollowingthesesteps:
1. EnsurethattheWalkTrailPage.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//WalkTrailPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
namespaceTrackMyWalks
{
publicclassWalkTrailPage:ContentPage
{
publicWalkTrailPage(WalkEntrieswalkItem)
{
Title="WalksTrail";
2. Next,weneedtodeclareandcreateanewBindingContextinstancefortheWalksTrailPage,andsetthistoanewinstanceoftheWalksTrailViewModelsothatitknowswheretogettheWalkEntriessothatwecanbindittotheassociatedpropertiescontainedwithinourModelanddisplaythiswithintheView.Proceedandenterthefollowinghighlightedcodesections:
//DeclareandinitializeourModelBindingContext
BindingContext=newWalksTrailViewModel(walkItem);
varbeginTrailWalk=newButton
{
BackgroundColor=Color.FromHex("#008080"),
TextColor=Color.White,
Text="BeginthisTrail"
};
//DeclareandinitializeourEventHandler
beginTrailWalk.Clicked+=(sender,e)=>
{
if(walkItem==null)return;
Navigation.PushAsync(newDistanceTravelled(walkItem));
Navigation.RemovePage(this);
walkItem=null;
};
3. Next,createtheremainingImageandLabelcontrolobjects,aswellastheSetBindingpropertiestotheirmatchedpropertynameascontainedwithintheViewModel,asshowninthefollowingcodesnippets:
varwalkTrailImage=newImage()
{
Aspect=Aspect.AspectFill
};
walkTrailImage.SetBinding(Image.SourceProperty,
"WalkEntry.ImageUrl");
vartrailNameLabel=newLabel()
{
FontSize=28,
FontAttributes=FontAttributes.Bold,
TextColor=Color.Black
};
trailNameLabel.SetBinding(Label.TextProperty,
"WalkEntry.Title");
vartrailKilometersLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=12,
TextColor=Color.Black,
};
trailKilometersLabel.SetBinding(Label.TextProperty,
"WalkEntry.Kilometers",stringFormat:"Length:{0}km");
vartrailDifficultyLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=12,
TextColor=Color.Black
};
trailDifficultyLabel.SetBinding(Label.TextProperty,
"WalkEntry.Difficulty",stringFormat:"Difficulty:{0}");
vartrailFullDescription=newLabel()
{
FontSize=11,
TextColor=Color.Black,
HorizontalOptions=LayoutOptions.FillAndExpand
};
trailFullDescription.SetBinding(Label.TextProperty,
"WalkEntry.Notes");
this.Content=newScrollView
{
Padding=10,
Content=newStackLayout
{
Orientation=StackOrientation.Vertical,
HorizontalOptions=LayoutOptions.FillAndExpand,
Children=
{
walkTrailImage,
trailNameLabel,
trailKilometersLabel,
trailDifficultyLabel,
trailFullDescription,
beginTrailWalk
}
}
};
}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingourWalksTrailPagesothatitcantakeadvantageoftheWalksTrailViewModel.WelookedathowtosetthecontentpagetoaninstanceofourWalksTrailViewModelsothatitknowswheretogetthelistofwalkentriestobeusedanddisplayedwithinourStackLayoutcontrol.WeusedtheSetBindingpropertytocreateandbindeachofourmodelvaluestoaspecificproperty.
Finally,wedefinedaScrollViewcontrol,thenaddedeachofourformImageandLabelfieldstotheStackLayoutcontrol.
ImplementingtheDistanceTravelledViewModelInthissection,wewillbetakingalookatthestepsrequiredtocreatetheViewModelforourDistTravelledViewModelsothatwecancalculatehowlongthechosenwalkfromthemainWalksPagetooktocomplete.
Let'stakealookathowwecanachievethis,byfollowingthesesteps:
1. CreateanewclassfilewithintheViewModelsfoldercalledDistTravelledViewModel,asyoudidintheprevioussectionentitledCreatingtheWalkBaseViewModel,locatedwithinthischapter.
2. Next,ensurethattheDistTravelledViewModel.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//DistTravelledViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
namespaceTrackMyWalks.ViewModels
{
publicclassDistTravelledViewModel:WalkBaseViewModel
{
WalkEntries_walkEntry;
3. Then,createthefollowingWalkEntrypropertyanditsassociatedgettersandsetterqualifiers.TheOnPropertyChangedmethod,aswementionedpreviously,willbecalledwhenourpropertydeterminesthatthecontentshavebeenchanged.Proceedandenterinthefollowingcodesnippet:
publicWalkEntriesWalkEntry
{
get{return_walkEntry;}
set
{
_walkEntry=value;
OnPropertyChanged();
}
}
4. Next,createtheremainingViewModelpropertiesandtheassociatedgettersandsetterqualifiersthatwillbeusedtobindthevaluesenteredontheDistanceTravelledPage,asshowninthefollowingcodesnippets:
double_travelled;
publicdoubleTravelled
{
get{return_travelled;}
set
{
_travelled=value;
OnPropertyChanged();
}
}
double_hours;
publicdoubleHours
{
get{return_hours;}
set
{
_hours=value;
OnPropertyChanged();
}
}
double_minutes;
publicdoubleMinutes
{
get{return_minutes;}
set
{
_minutes=value;
OnPropertyChanged();
}
}
double_seconds;
publicdoubleSeconds
{
get{return_seconds;}
set
{
_seconds=value;
OnPropertyChanged();
}
}
publicstringTimeTaken
{
get
{
returnstring.Format("{0:00}:{1:00}:{2:00}",this.Hours,
this.Minutes,this.Seconds);
}
}
publicDistTravelledViewModel(WalkEntrieswalkEntry)
{
this.Hours=0;
this.Minutes=0;
this.Seconds=0;
this.Travelled=100;
WalkEntry=walkEntry;
}
}
}
Inthissection,webeganbyensuringthatourViewModelinheritsfromtheWalkBaseViewModelclassandthenwecreatedanWalkEntriesvariable_walkEntriesthatwillbeusedtostoreourWalkEntries.
Next,wecreatedaWalkEntrypropertyanditsassociatedgettersandsetterqualifiersandtheOnPropertyChangedmethod,sothatitwillbecalledwhenourpropertydeterminesthatthecontentshavebeenchanged.
Inourfinalstep,wecreatedtheDistTravelledViewModelconstructor,thatacceptsaListparametercontainingourWalkEntriesmodel,priortoinitializingourclassconstructorwithdefaultvaluesfortheHours,Minutes,Seconds,andTravelledproperties.
UpdatingtheDistanceTravelledPagetousetheMVVMmodelInthissectionweneedtobindourmodelbindingcontextBindingContexttotheDistTravelledViewModelsothatthewalkinformationdetailsandthecalculationsofdistancetravelledwillbedisplayedfromtheWalkEntriesmodel.
Let'stakealookathowwecanachievethis,byfollowingthesesteps:
1. EnsurethattheDistanceTravelledPage.csfileisdisplayedwithinthecodeeditor.
//
//DistanceTravelledPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingXamarin.Forms.Maps;
usingTrackMyWalks.Models;
namespaceTrackMyWalks
{
publicclassDistanceTravelledPage:ContentPage
{
2. Next,weneedtoupdatethemaptogetitsvaluesfromtheDistTravelledViewModelsothatitcorrectlyplotsthesewithinthemapview.WeneedtodothisbecausetheXamarin.Forms.Mapcontroldoesn'tprovidesupportforbindingdirectlytothemap,sowehavetosetthesedirectlywithinourViewModelinstead.Therefore,weneedtocreateaprivate_viewModelvariablewithinthecontentpagetoreturnthevaluefromthepage'sBindingContext.
3. Proceedandenterthefollowinghighlightedcodesections:
DistTravelledViewModel_viewModel
{
get{returnBindingContextasDistTravelledViewModel;}
}
publicDistanceTravelledPage(WalkEntrieswalkItem)
{
Title="DistanceTravelled";
4. Next,createanewBindingContextinstancefortheDistanceTravelledPage,andsetthistoanewinstanceoftheDistTravelledViewModelsothatitknowswheretogettheWalkEntriestobindittotheassociatedpropertiescontainedwithinourmodelanddisplaythiswithintheView.Proceedandenterinthefollowinghighlightedcodesections:
//DeclareandinitializeourModelBindingContext
BindingContext=newDistTravelledViewModel(walkItem);
//Instantiateourmapobject
vartrailMap=newMap();
5. Next,updatethemaptograbthenameofthechosenwalkandtheLatitudeandLongitudevaluesfromtheDistTravelledViewModel.Proceedandenterthefollowinghighlightedcodesections:
//Placeapinonthemapforthechosenwalktype
trailMap.Pins.Add(newPin
{
Type=PinType.Place,
Label=_viewModel.WalkEntry.Title,
Position=newPosition(_viewModel.WalkEntry.Latitude,
_viewModel.WalkEntry.Longitude)
});
//Centerthemaparoundthelistofwalksentry'slocation
trailMap.MoveToRegion(MapSpan.FromCenterAndRadius(new
Position(_viewModel.WalkEntry.Latitude,
_viewModel.WalkEntry.Longitude),
Distance.FromKilometers(1.0)));
6. Next,createtheremainingLabelcontrolobjects,aswellastheSetBindingpropertiestotheirmatchedpropertynameascontainedwithintheViewModel,asshowninthefollowingcodesnippets:
vartrailNameLabel=newLabel()
{
FontSize=18,
FontAttributes=FontAttributes.Bold,
TextColor=Color.Black,
HorizontalTextAlignment=TextAlignment.Center
};
trailNameLabel.SetBinding(Label.TextProperty,
"WalkEntry.Title");
vartrailDistanceTravelledLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=20,
TextColor=Color.Black,
HorizontalTextAlignment=TextAlignment.Center
};
trailDistanceTravelledLabel.SetBinding(Label.TextProperty,
"Travelled",stringFormat:"DistanceTravelled:{0}km");
vartotalTimeTakenLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=20,
TextColor=Color.Black,
HorizontalTextAlignment=TextAlignment.Center
};
totalTimeTakenLabel.SetBinding(Label.TextProperty,"TimeTaken",
stringFormat:"TimeTaken:{0}");
varwalksHomeButton=newButton
{
BackgroundColor=Color.FromHex("#008080"),
TextColor=Color.White,
Text="EndthisTrail"
};
//Setupoureventhandler
walksHomeButton.Clicked+=(sender,e)=>
{
if(walkItem==null)return;
Navigation.PopToRootAsync(true);
walkItem=null;
};
this.Content=newScrollView
{
Padding=10,
Content=newStackLayout
{
Orientation=StackOrientation.Vertical,
HorizontalOptions=LayoutOptions.FillAndExpand,
Children={
trailMap,
trailNameLabel,
trailDistanceTravelledLabel,
totalTimeTakenLabel,
walksHomeButton
}
}
};
}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingtheDistanceTraveledPagesothatitcantakeadvantageoftheDistTravelledViewModel.WelookedathowtosetthecontentpagetoaninstanceofourDistTravelledViewModel.WeusedtheSetBindingpropertytocreateandbindeachofourmodelvaluestoaspecificproperty.Finally,wedefinedaScrollViewcontrol,thenaddedeachoftheformImageandLabelfieldstotheStackLayoutcontrol.
NowthatyouhavecreatedalloftheMVVMViewModelsandhaveupdatedtheassociatedcontentpages,ournextstepistofinallybuildandruntheTrackMyWalksapplicationwithintheiOSsimulator.
Whencompilationcompletes,theiOSSimulatorwillappearautomaticallyandtheTrackMyWalksapplicationwillbedisplayed,asshowninthefollowingscreenshot:
Asyoucanseefromtheprecedingscreenshot,thiscurrentlydisplaysalistofstatictrailentries,thataredisplayedwithintheListView.WhentheuserclicksontheAddWalkbuttonlink,thiswilldisplaytheNewWalkEntrycontentpage.
AsyoucanseefromtheNewWalkEntryscreen,wehavesuccessfullybindedeachoftheEntryCellpropertiestoourWalkEntryViewModelandhavedisplayedthisinformationbesideeachofthecontentpageproperties.IfyouclearouttheinformationassociatedwiththeTitleproperty,youwillnoticethattheSavebuttonwilldim.ThisisbecausetheValidateFormDetailsinstancemethodperformsacheck,thentheSaveCommandcommandeventistriggered,andhandlesitaccordingly.
Theprecedingscreenshotshowsyouthenavigationflowbetweeneachofthepageswhenatrailhasbeenselectedfromthelist,withthefinalscreenshowingtheDistanceTravelledpagealongwiththeplaceholderpinmarkershowingthetraillocationwithinthemapview.
SummaryInthischapter,weupdatedourTrackMyWalksapplicationandcreatedanumberofViewModels.Wethenremovedandmigratedourdataandlogicfromeachofourpages,thenaddedbindingtothosepagessothattheypointdirectlytotheViewModelsassociatedwiththepage.
Inthenextchapter,youwilllearnaboutthedifferentnavigationtechniquesthatcanbeusedwithintheMVVMmodelarchitecture,tonavigatebetweenViewModelsbycreatingandimplementinganavigationservicebetweeneachoftheViews,sothatyoucaneasilynavigatebetweenthem.
Chapter3.NavigatingwithintheMVVMModel-TheXamarin.FormsWayUptothispoint,youhaveseenhowtoincorporatetheMVVMarchitecturalpatternintoyourapplications,sothatitenforcestheseparationbetweentheapplication'suserinterface,orpresentationlayer,fromtheunderlyingdata.ThisisdonebyusingaclassthatactsasthecommunicationlayerbetweenboththeViewandtheViewModel,andisconnectedthroughdatabindingsalongwiththebindingcontextfortheView,pointingtoaninstanceoftheViewModel.
Inthischapter,youwillseehowyoucanleveragewhatyoualreadyknowabouttheMVVMdesignpattern,andwewilllearnhowtomovenavigationintotheViewModels.You'lllearnhowtocreateaC#classthatwillactasthenavigationserviceforourapp,aswellashowtoupdateourexistingWalkBaseViewModelclassfile.ThiswillincludeanumberofabstractclassmethodsthateachofourViewModelswillinherit,andinturnupdatethecontentpagestobindwiththeViewModelstoallownavigationbetweentheseViewstohappen.
Thischapterwillcoverthefollowingtopics:
UnderstandingtheXamarin.FormsNavigationAPIpatternarchitectureCreatinganavigationserviceusingtheXamarin.Forms.NavigationclassUpdatingtheTrackMyWalksapplicationtousethenavigationserviceUpdatingtheMVVMViewModelstousethenavigationserviceinterfaceUpdatingtheuserinterfacecontentpagestousetheupdatedMVVMViewModels
UnderstandingtheXamarin.FormsNavigationAPIInthissection,wewilltakealookattheXamarin.FormsNavigationAPIpatternarchitectureandgainanunderstandingintothedifferenttypesofnavigationpatternsthatareavailable.
TheXamarin.FormsNavigationAPIisexposedthroughtheXamarin.Forms.INavigationinterface,andisimplementedviatheNavigationproperty.TheNavigationpropertycanbecalledfromanyXamarin.Formsobject,typicallytheXamarin.Forms.PagethatinheritsfromtheContentPageclassthatispartoftheXamarin.Forms.Coreassembly.
TheXamarin.FormsNavigationAPIsupportstwodifferenttypesofnavigation-hierarchicalandmodal,andtheseareexplainedinthefollowingtable:
Navigationpage Description
Hierarchical
Thehierarchicalnavigationtypeisessentiallyastack-basednavigationpatternthatenablesuserstomoveiterativelythrougheachofthescreenswithinthehierarchy,andthennavigatebackoutagain,onescreenatatime,removingthemfromthenavigationstack.
ModalThemodalnavigationtypeisasinglepop-uporscreenthatinterruptsthehierarchicalnavigationbyrequiringtheusertorespondtoanaction,priortothescreenorpopupfrombeingdismissed.
Thehierarchicalnavigationpatternprovidesameansofnavigatingthroughthenavigationalstructureandistypicallythemostused.Thisinvolvestheusertappingtheirwayforwardthroughaseriesofpages,andthennavigatingbackwardsthroughthestackusingthenavigationmethodsonAndroidoriOSdevices.
Thefollowingscreenshotshowstheprocesswhenmovingfromonepagetoanotherwithinthehierarchicalnavigationmodel,andpoppingpagesfromtheNavigationStack.Wheneveranewpageispushedontothenavigationstack,thiswillbecometheactivepage.
Alternatively,whenyouwanttoreturnbacktothepreviouspage,theapplicationwillstartbypoppingthecurrentpagefromthenavigationstack,andthenewtopmostpagewillthenbecometheactivepage.Themodalnavigationpatterndisplaysapageontopofthecurrentpagethatpreventstheuserfromanyinteractionfromthepageunderneathit,andprovidestheuserwithchoicesforwhattheywanttodobeforethemodalpagecanbeclosed.
TheINavigationinterface,whichispartoftheXamarin.Forms.NavigationPage,implementsandexposestwoseparateread-onlyproperties-NavigationStackandModalStack.Thiswillallowyoutoviewboththehierarchicalandmodalnavigationstacks.
TheXamarin.FormsINavigationinterfaceprovidesyouwithseveralmethodsthatwillallowyoutoasynchronouslypush(add)andpop(remove)pagesontothenavigationandmodalstacks,andtheseareexplainedinthetablebelow:
Navigationmethods Description
PushAsync(Page
page)
ThismethodaddsanewpageatthetopoftheNavigationStackthatenablesuserstomovedeeperwithinthescreenhierarchy.
PopAsync()
ThismethodallowsyoutonavigatebackthroughtheNavigationStacktothepreviouspage,ifonehasbeenpreviouslyaddedtotheNavigationStack.
Thismethodallowsyoutodisplayapagemodallywhenyouneedtoeitherdisplaysomeinformationalinformationorrequestinformation
PushModalAsync(Page
page)fromtheuser.Agoodexampleofamodalpagewouldbeasign-onpage,whereyouneedtogetusercredentials.
PopModalAsync()Thismethodwilldismissthecurrentlydisplayedmodalpageandreturnyoutothepagedisplayedunderneath.
Aswellastheabovementionednavigationmethods,theXamarin.Forms.INavigationinterfaceprovidesyouwithanumberofadditionalmethodsthatwillhelpyoumanipulatetheNavigationStack,andtheseareexplainedinthefollowingtable:
Navigationmethods Description
InsertPageBefore(Page
page,Pagebefore)
ThismethodallowsyoutoinsertapagebeforeaspecificpagethathasalreadybeenaddedtotheNavigationStack.
RemovePage(Pagepage)ThismethodallowsyoutoremoveaspecificpagewithintheNavigationStack.
PopToRootAsync()
ThismethodnavigatesyoubacktothefirstpagethatiscontainedwithintheNavigationStackwhilstremovingalloftheotherpagesthatarecontainedwithintheNavigationStack.
NowthatyouhaveagoodunderstandingofthecomponentsthatarecontainedwithintheNavigationAPIpatternarchitecture,wecanbegintotakealookatsomeofthedifferentapproachestonavigatingbetweenpagesandViewModels.
DifferencesbetweenthenavigationandViewModelapproachesInthissection,wewilltakealookattheapproacheswhenperformingnavigationwithinViewModelscontainedwithinanXamarin.Formssolution.WhenperformingnavigationwithinyourViewModels,thereareacoupleofapproachesthatyoushouldconsiderbeforegoingdownthispath.Anapproachwouldbetousethepagenavigationapproach,whichinvolvesnavigatingtoanotherpageusingadirectreferencetothatpage.
ThepagenavigationapproachcanbeaccomplishedinXamarin.FormsbyessentiallypassingthecurrentINavigationinstanceintoaViewModel'sobjectconstructor,whichwillforcetheViewModeltousetheXamarin.Formsdefaultnavigationmechanismtonavigatetootherpages.
IfyouwantedtousetheViewModelapproachtonavigatetoapageusingtheassociatedpagesViewModel,youwouldneedtoformsomesortofmappingbetweeneachofthepages,aswellastheirassociatedViewModels.Thiswouldbedonebycreatingadictionaryorkey-valuetypepropertyinthenavigationservicethatwillmaintainaone-to-onemappingforeachofthepagesandtheirtype.
InMVVM,actionstakenbytheuseronaparticularpageareboundtocommandsthatarepartofthepages,ViewModel,andsothisprocessneedstobethoughtthroughdifferentlywhennavigatingtoanotherpage,oreventhepreviouspage,whenperformingtaskssuchassavingdataorupdatingamapslocation.Assuch,weneedtorethinkhowtoachievenavigationthatleveragestheMVVMdesignpatternwithinourapp,sothatitcanbecontrolledbytheViewModelsandnotbytheunderlyingpages.
WhenusingtheViewModelstohandlethenavigation,thisalleviatestheneedforaViewModeltohaveanydependenciesonthespecificimplementationofapage,andbecausetheViewModeldoesn'tnavigatedirectlytoapageviatheContentPage'sViewModel,thismeansthatwhenyouimplementthisapproach,thereisaneedforarelationshipormappingtobedonebetweeneachofthepagesandtheirassociatedViewModels.
Inthenextsection,wewillbetakingalookathowtonavigatethroughourTrackMyWalksappbycreatinganavigationservicethatwillincludeViewModelandpagetypemappings.
ImplementingthenavigationservicewithinyourappInthissection,wewillbeginbysettingupthebasicstructureforourTrackMyWalkssolutiontoincludethefolderthatwillbeusedtorepresentourServices.Let'stakealookathowwecanachievethis,byperformingfollowingthesteps:
1. LaunchtheXamarinStudioapplicationandensurethattheTrackMyWalkssolutionisloadedwithintheXamarinStudioIDE.
2. Next,createanewfolderwithintheTrackMyWalksPortableClassLibraryproject,calledServices,asshowninthefollowingscreenshot:
Nowthatwehavecreatedthefolderstructurethatwillbeusedtostoreournavigationservices,wecanbegintostartbuildingtheNavigationServiceInterfaceclassthatwillbeusedbyourNavigationServiceclassandinturnusedbyourViewModels.
CreatingthenavigationserviceinterfacefortheTrackMyWalksappInthissection,wewillbeginbycreatinganavigationserviceinterfaceclassthatwillextendfromtheXamarin.Formsnavigationabstractionlayer.ThisissothatwecanperformViewModeltoViewModelnavigationwithinourMVVMdesignpattern,andinturnbindwithourcontentpagestoallownavigationbetweentheseViewstohappen.
Wewillfirstneedtodefinetheinterfaceforournavigationservice,asthiswillcontainanddefineitsmethods,andwillmakeitaloteasierifweeverwantedtoaddnewmethodimplementationsforourservice,withouttheneedtochangeeachofourViewModels.
Let'stakealookathowwecanachievethis,byperformingthefollowingsteps:
1. CreateanemptyclasswithintheServicesfolder,asshowninthefollowingscreenshot:
2. Next,choosetheEmptyInterfaceoptionlocatedwithintheGeneralsection,andenterinIWalkNavServiceforthenameofthenewinterfacefiletocreate,asshowninthefollowingscreenshot:
3. Next,clickontheNewbuttontoallowthewizardtoproceedandcreatethenewemptyclassfile,asshownintheprecedingscreenshot.
Upuntilthispoint,allwehavedoneiscreateourIWalkNavServiceclassfile.ThisInterfaceclasswillbeusedandwillactasthebaseNavigationServiceclassthateachofourViewModelswillinheritfrom.AswestarttobuildtheNavigationServiceInterfaceclass,youwillseethatitcontainsacoupleofclassmembersthatwillbeusedbyourcontentpagesandViewModels,aswewillbeusingthisasourbaseclasswithinourViewModelsusedbytheTrackMyWalksapplication.
ToproceedwithcreatingthebaseIWalkNavServiceinterface,ensurethattheIWalkNavService.csfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
//
//IWalkNavService.cs
//TrackMyWalksNavigationServiceInterface
//
//CreatedbyStevenF.Danielon03/09/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.Threading.Tasks;
usingTrackMyWalks.ViewModels;
namespaceTrackMyWalks.Services
{
publicinterfaceIWalkNavService
{
//NavigatebacktothePreviouspagein
theNavigationStack
TaskPreviousPage();
//Navigatetothefirstpagewithin
theNavigationStack
TaskBackToMainPage();
//NavigatetoaparticularViewModelwithin
ourMVVMModel,
//andpassaparameter
TaskNavigateToViewModel<ViewModel,
TParameter>(TParameterparameter)
whereViewModel:WalkBaseViewModel;
}
}
Intheprecedingcodesnippet,westartedbycreatinganewInterfaceclassforourIWalkNavServicethatallowstheabilitytonavigatetoeachofourViewModels,aswellasnavigatingbacktothePreviousPagemethod,andbacktothefirstpagewithinourhierarchicalmodel,asdeterminedbytheBackToMainPagemethod.
Note
Aninterfacecontainsonlythemethods,properties,andeventsignaturedefinitions.Anyclassthatimplementstheinterfacemustimplementallmembersoftheinterfacethatarespecifiedintheinterfacedefinition.
TheNavigateToViewModelmethoddeclaresagenerictypewhichisusedtorestricttheViewModeltoitsusetoobjectsoftheWalkBaseViewModelbaseclass,andastrongly-typedTParameterparametertobepassedalongwiththenavigation.
Note
Thetermstrongly-typedmeansthat,ifavariablehasbeendeclaredofaspecifictype(string,integer,ordefinedasauser-definedtype),itcannotbeassignedavalueofadifferenttypelateron,asthiswillresultinthecompilernotifyingyouofanerror.Anexamplewouldbe:inti=10;i="Ten"
TheTaskclassisessentiallyusedtohandleasynchronousoperations.Thisisdonebyensuringthattheasynchronousmethodyouinitiatedwilleventuallyfinish,thuscompletingthetask.The
TaskobjectisusedtoreturnbackinformationonceithasfinishedbyreturningbackaTaskobjectalmostinstantaneously,althoughtheunderlyingworkwithinthemethodwouldlikelyfinishlater.
Tohandlethis,youcanusetheawaitkeywordtowaitforthetasktocompletewhichwillblockthecurrentthreadandwaituntiltheasynchronousmethodhascompleted.
CreatinganavigationservicetonavigatewithinourViewModelsIntheprevioussection,wecreatedourbaseinterfaceclassforournavigationserviceanddefinedanumberofdifferentmethods,whichwillbeusedtonavigatewithinourMVVMViewModel.
ThesewillbeusedbyeachofourViewModels,andtheViews(pages)willimplementtheseViewModelsandusethemastheirBindingContext.
Let'stakealookathowwecanachievethis,byperformingthefollowingthesteps:
1. CreateanemptyclasswithintheServicesfolder,asshowninthenextscreenshot.2. Next,choosetheEmptyClassoptionlocatedwithintheGeneralsection,andenterin
WalkNavServiceforthenameofthenewclassfiletocreate,asshowninthefollowingscreenshot:
3. Next,clickontheNewbuttontoallowthewizardtoproceedandcreatethenewemptyclassfile,asshownintheprecedingscreenshot.
4. Upuntilthispoint,allwehavedoneiscreateourWalkNavServiceclassfile.ThisclasswillbeusedandwillactasthebaseNavigationServiceclassthatwillcontainthefunctionalityrequiredthateachofourViewModelswillinheritfrom,inordertonavigatebetweeneachoftheViewModelswithinourMVVMmodel.
5. AswestarttobuildourNavigationclass,youwillseethatitcontainsanumberofmethodmembersthatwillbeusedtoenablenavigationbetweeneachofourViewModelsanditwillimplementtheIWalkNavServiceInterface.ToproceedwithcreatingthebaseWalkNavServiceclass,performthefollowingsteps:
6. EnsurethattheWalkNavService.csfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
//
//WalkNavService.cs
//TrackMyWalksNavigationServiceClass
//
//CreatedbyStevenF.Danielon03/09/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingXamarin.Forms;
usingSystem.Collections.Generic;
usingSystem.Threading.Tasks;
usingSystem.Reflection;
usingSystem.Linq;
usingTrackMyWalks.ViewModels;
usingTrackMyWalks.Services;
First,weneedtoinitializeournavigationclasstobemarkedasadependency,byaddingtheDependencymetadataattributesothatitcanberesolvedbytheXamarin.FormsDependencyServiceclass.Thiswillenablesothatittofindanduseourmethodimplementationasdefinedbyourinterface.
7. Next,wealsoneedtoneedtoensurethatourWalkNavServiceclassinheritsfromtheIWalkNavServicenavigationinterfaceclass,sothatitcanaccessthemethodgettersandsetters.Proceedandenterinthefollowingcodesnippetasshownhere:
[assembly:Dependency(typeof(WalkNavService))]
namespaceTrackMyWalks.Services
{
publicclassWalkNavService:IWalkNavService
{
8. Next,weneedtocreateapublicINavigationpropertynamednavigation.ThenavigationpropertywillprovideourclasswithareferencetothecurrentXamarin.Forms.INavigationinstance,andthiswillneedtobesetwhenthenavigationserviceisfirstinitialized.WewillseehowthisisdoneasweprogressthroughupdatingourTrackMyWalksapp.Proceedandenterinthefollowingcodesnippet:
publicINavigationnavigation{get;set;}
9. ThenweneedtoregisterthenavigationservicetohandleContentPagetoViewModelmappingsbydeclaringadictionary_viewMappingpropertyvariablethatinheritsfromthe
IDictionaryinterface.Proceedandenterinthefollowingcodesnippet:
readonlyIDictionary<Type,Type>_viewMapping=
newDictionary<Type,Type>();
10. Next,weneedtodeclareanewmethodcalledRegisterViewMappingwhichwillbeusedtopopulateourViewModelandContentPage(View)withinthe_viewMappingdictionarypropertyobject.Proceedandenterinthefollowingcodesections:
//RegisterourViewModelandViewwithinourDictionary
publicvoidRegisterViewMapping(TypeviewModel,Typeview)
{
_viewMapping.Add(viewModel,view);
}
11. Then,weneedtocreatethePreviousPageinstancemethodforourWalkNavServiceclass.ThiswillbeusedtonavigatebacktothepreviouspagecontainedwithinourNavigationStack,byfirstcheckingtheNavigationStackpropertyofthenavigationpropertyINavigationinterfacetoensurethatitisnotnullandthatwehavemorethanoneViewModelcontainedwithinourNavigationStacktonavigatebackto.Ifwedon'tperformthischeck,itcouldresultinourapplicationcrashing.Finally,weusethePopAsyncmethodtoremovethelastviewaddedtoourNavigationStack,thusreturningbacktothepreviousViewModel.Proceedandenterinthefollowingcodesections:
//Instancemethodthatallowsustomovebacktothe
//previouspage.
publicasyncTaskPreviousPage()
{
//Checktoseeifwecanmovebacktothepreviouspage
if(navigation.NavigationStack!=null&&
navigation.NavigationStack.Count>0)
{
awaitnavigation.PopAsync(true);
}
}
12. Next,weneedtocreatetheBackToMainPageinstancemethodforourWalkNavServiceclass.ThiswillbeusedtotakeusbacktothefirstContentPagecontainedwithinourNavigationStack.WeusethePopToRootAsyncmethodofournavigationpropertyandusetheawaitoperatortowaituntilthetaskcompletes,beforeremovingallViewModelscontainedwithinourNavigationStackandreturningbackaTaskobject.Proceedandenterthefollowingcode:
//Instancemethodthattakesusbacktothemain
//RootWalksPage
publicasyncTaskBackToMainPage()
{
awaitnavigation.PopToRootAsync(true);
}
13. Then,weneedtocreatetheNavigateToViewModelinstancemethodforourWalkNavServiceclass.ThiswillbeusedtonavigatetoaspecificViewModelthatiscontainedwithinour_viewMappingdictionaryobject.Next,weusetheTryGetValue
methodofthe_viewMappingdictionaryobjecttochecktoseeifourViewModeldoesindeedexistwithinourdictionary,andreturnthenameoftheViewModel.
14. ThenameofthereturnedviewwillbestoredwithintheviewTypeobject,andwethenusethePushAsyncmethodtonavigatetothatview.Finally,wesettheBindingContextforthelastpushedviewthatiscontainedwithinourNavigationStack,andthennavigatetotheview,passinginanyparametersrequired.Proceedandenterinthefollowingcodesections:
//InstancemethodthatnavigatestoaspecificViewModel
//withinourdictionaryviewMapping
publicasyncTaskNavigateToViewModel<ViewModel,
WalkParam>(WalkParamparameter)
whereViewModel:WalkBaseViewModel
{
TypeviewType;
if(_viewMapping.TryGetValue(typeof(ViewModel),out
viewType))
{
varconstructor=viewType.GetTypeInfo()
.DeclaredConstructors
.FirstOrDefault(dc=>dc.GetParameters()
.Count()<=0);
varview=constructor.Invoke(null)asPage;
awaitnavigation.PushAsync(view,true);
}
if(navigation.NavigationStack.Last().BindingContextis
WalkBaseViewModel<WalkParam>)
await((WalkBaseViewModel<WalkParam>)(
navigation.NavigationStack.Last().BindingContext)).
Init(parameter);
}
}
}
Intheprecedingcodesnippet,webeganbyensuringthatourWalkNavServiceclassinheritsfromourIWalkNavServiceclass,andthenmovedontocreateanavigationpropertythatinheritsfromourINavigationclass.Wethencreateditsassociatedgetterandsetterqualifiers.
Finally,wecreatedtheinstancemethodsrequiredforourWalkNavServiceclass.
UpdatingtheWalkBaseViewModeltouseournavigationserviceInthissectionwewillproceedtoupdateourWalkBaseViewModelclasstoincludereferencestoourIWalkNavService.SinceourWalkBaseViewModelinheritsandisusedbyeachofourViewModels,itmakessensetoplaceitwithinthisclass.Thatway,ifweneedtoaddadditionalmethods,wecanjustaddthemwithinthisclass.Toproceed,performthefollowingsteps:
1. EnsurethattheWalkBaseViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
//
//WalkBaseViewModel.cs
//TrackMyWalksBaseViewModel
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.ComponentModel;
usingSystem.Runtime.CompilerServices;
usingSystem.Threading.Tasks;usingTrackMyWalks.Services;
namespaceTrackMyWalks.ViewModels
{
publicabstractclassWalkBaseViewModel:
INotifyPropertyChanged
{
2. Next,weneedtocreateaprotectedIWalkNavServicepropertynamedNavService.TheNavServicepropertywillprovideourclasswithareferencetothecurrentnavigationinstancethatiscontainedwithinourIWalkNavServiceinterfaceclass.Proceedandenterinthefollowingcodesection:
protectedIWalkNavServiceNavService{get;privateset;}
3. Then,weneedtomodifytheclassconstructoranddeclareanavServiceparameterthatinheritsfromourIWalkNavServiceinterfaceclass.Next,wesettheNavServicepropertyforourWalkBaseViewModelbaseclass,toaninstanceofthenavServiceparameter.Proceedandenterinthefollowinghighlightedcodesections:
protectedWalkBaseViewModel(IWalkNavServicenavService)
{
NavService=navService;
}
4. Next,weneedtocreatetheInitabstractmethodforourWalkBaseViewModelclassthatreturnsbackanasynchronousTaskobject.ThiswillbeusedtoinitializeourWalkBaseViewModel.Proceedandenterinthefollowinghighlightedcodesections:
publicabstractTaskInit();
publiceventPropertyChangedEventHandlerPropertyChanged;
protectedvirtualvoidOnPropertyChanged(
[CallerMemberName]stringpropertyName=null)
{
varhandler=PropertyChanged;
if(handler!=null)
{
handler(this,newPropertyChangedEventArgs(propertyName));
}
}
}
5. Then,weneedtocreateasecondaryabstractclassforourWalkBaseViewModelthatinheritsfromtheWalkBaseViewModelanddefinesageneric-typedTParameterobject.WethenproceedtooverloadtheWalkBaseViewModelclassconstructor,andsetthisclasstoinheritfromournavServicebaseclass.Proceedandenterinthefollowinghighlightedcodesections:
publicabstractclassWalkBaseViewModel<WalkParam>:
WalkBaseViewModel
{
protectedWalkBaseViewModel(IWalkNavServicenavService):
base(navService)
{
}
6. Next,weneedtooverridetheInitmethodforourWalkBaseViewModel<WalkParam>thatacceptsadefaultWalkParamvalueforourwalkDetailsmodel.Proceedandenterinthefollowinghighlightedcodesections:
publicoverrideasyncTaskInit()
{
awaitInit(default(WalkParam));
}
publicabstractTaskInit(WalkParamwalkDetails);
}
}
Intheprecedingcodesnippet,webeganbycreatingaNavServicepropertythatinheritsfromourIWalkNavServiceclass,andthencreateditsassociatedgetterandsetterqualifiers.
Next,weupdatetheWalkBaseViewModelclassconstructortosettheNavServicepropertytoaninstanceofournavService,beforecreatingourInitabstractionmethodthatwillbeusedtoinitializeourclass.
Inthenextstep,wecreateanewabstractclassforourWalkBaseViewModelthatimplementsfromtheWalkBaseViewModelclass,andthenoverloadsourclassconstructorsothatitinheritsfromournavServiceclass.
Next,we'lloverridetheInitmethodforourWalkBaseViewModel<WalkParam>thatacceptsadefaultWalkParamvalueforourwalkDetailsmodel.
UpdatingthewalksmainpageViewModelandnavigationserviceWehavecreatedourIWalkNavServiceInterfaceclassandupdatedtheNavServiceclasstoincludeallofthenecessaryclassinstancemethods.WealsomadesomechangestoourWalkBaseViewModelclasstoinheritfromourIWalkNavServicenavigationservice.WehavealsoincludedanadditionalabstractionclassthatwillbeusedtoinitializeourWalkBaseViewModelwhennavigatingbetweenViewModelswithinourMVVMmodel.
Ournextstepistomodifythewalksmainpage.Inthissection,wewillbetakingalookathowtoupdateourWalksPageViewModelsothatitcantakeadvantageofournavigationservice.
Let'stakealookathowwecanachievethis,byperformingthefollowingsteps:
1. EnsurethattheWalksPageViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//WalksPageViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Collections.ObjectModel;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Models;
usingTrackMyWalks.Services;
usingXamarin.Forms;
namespaceTrackMyWalks.ViewModels
{
publicclassWalksPageViewModel:WalkBaseViewModel
{
ObservableCollection<WalkEntries>_walkEntries;
publicObservableCollection<WalkEntries>walkEntries
{
get{return_walkEntries;}
set
{
_walkEntries=value;
OnPropertyChanged();
}
}
2. NowweneedtomodifytheWalksPageViewModelclassconstructor,whichwillneedtoincludeaparameternavServicethatisincludedwithinourIWalkNavServiceinterfaceclass.ThenweneedtosettheViewModel'sclassconstructortoaccessallinstanceclassmemberscontainedwithinthenavServicewithintheWalksPageViewModelbyusingthe
basekeyword.WethensetupanObservableCollectioncalledwalkEntries.ThisacceptsalistparametercontainingourWalkEntriesmodel,whichwillbeusedtodeterminewheneverthecollectionhaschangedwithintheWalkEntriesmodel.
3. Next,wecreatetheInitmethodwithinourWalksPageViewmodel,andamethodcalledLoadWalkDetailstopopulatetheWalkDetails.Proceedandenterinthefollowinghighlightedcodesections:
publicWalksPageViewModel(IWalkNavServicenavService):
base(navService)
{
walkEntries=newObservableCollection<WalkEntries>();
}
publicoverrideasyncTaskInit()
{
awaitLoadWalkDetails();
}
4. Next,weneedtocreateanewasyncmethodcalledLoadWalkDetailsthatwillbeusedtoaddeachofthewalkentrieswithinourmodel.WeusetheTask.Factory.StartNewtostartandexecuteourtask,andthenproceedtopopulateeachofourlistsofWalkEntriesasynchronously.WethenuseandspecifytheawaitkeywordtowaituntilourTaskcompletes.Proceedandenterinthefollowinghighlightedcodesections:
publicasyncTaskLoadWalkDetails()
{
awaitTask.Factory.StartNew(()=>
{
walkEntries=newObservableCollection<WalkEntries>(){
newWalkEntries{
Title="10MileBrookTrail,MargaretRiver",
Notes="The10MileBrookTrailstartsinthe
RotaryParknearOldKate,apreservedsteam"+
"engineatthenorthernedgeofMargaretRiver.",
Latitude=-33.9727604,
Longitude=115.0861599,
Kilometers=7.5,
Distance=0,
Difficulty="Medium",
ImageUrl=
"http://trailswa.com.au/media/cache/media/images/
trails/_mid/"+
"FullSizeRender1_600_480_c1.jpg"
},
newWalkEntries
{
Title="AncientEmpireWalk,ValleyoftheGiants",
Notes="TheAncientEmpireisa450metrewalktrail
thattakesyouaroundandthroughsomeof"+
"thegianttingletreesincludingthemostpopular
ofthegnarledveterans,knownas"+
"GrandmaTingle.",
Latitude=-34.9749188,
Longitude=117.3560796,
Kilometers=450,
Distance=0,
Difficulty="Hard",
ImageUrl="http://trailswa.com.au/media/cache
/media/images/trails/_mid/"+
"Ancient_Empire_534_480_c1.jpg"
},
};
});
}
5. Next,weneedtocreateaCommandpropertyforourclass.ThiswillbeusedwithinourWalksPageandwillbeusedtobindtotheAddWalkToolBarItem.TheCommandpropertywillrunanactionuponbeingpressed,andthenexecuteaclassinstancemethod,todeterminewhetherthecommandcanbeexecuted.Proceedandenterinthefollowinghighlightedcodesections:
Command_createNewWalk;
publicCommandCreateNewWalk
{
get
{
return_createNewWalk
??(_createNewWalk=
newCommand(async()=>
awaitNavService.NavigateToViewModel<WalkEntryViewMod
el,WalkEntries>(null)));
}
}
6. Then,createaCommandpropertytoourclass.ThiswillbeusedwithintheWalksPageandwillbeusedtohandleclicksonawalkitemwithintheListView.TheCommandpropertywillrunanactionuponbeingpressed,andthenexecuteaclassinstancemethodtodeterminewhetherthecommandcanbeexecutedornot,priortonavigatingtotheWalksTrailViewModelandpassinginthetrailDetailsforthechosenwalkwithintheListView.Proceedandenterinthefollowinghighlightedcodesections:
Command<WalkEntries>_trailDetails;
publicCommand<WalkEntries>WalkTrailDetails
{
get
{
return_trailDetails
??(_trailDetails=
newCommand<WalkEntries>(async(trailDetails)=>
awaitNavService.NavigateToViewModel
<WalksTrailVi
ewModel,WalkEntries>(trailDetails)));
}
}
}
}
NowthatwehavemodifiedourWalksPageViewModeltoincludethenavigationserviceclass,whichwillbeusedbyourmainWalksPage,ournextstepistomodifyourwalksmainpagesothatitpointstoareferenceofourWalksPageViewModel,andensuresthatallofthenecessaryCommandbindingsandBindingContextshavebeensetupcorrectly.
UpdatingthewalksmainpagetousetheupdatedViewModelNowthatwehavemodifiedourMVVMViewModeltotakeadvantageofthenavigationservice,weneedtomodifyourwalksmainpagetobindtheWalksPageBindingContexttotheWalksPageViewModelsothatthewalkentrydetailscanbedisplayedandallofthenavigationalaspectsareworkingasexpected.
Let'stakealookathowwecanachievethis,byperformingthefollowingsteps:
1. EnsurethattheWalksPage.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//WalksPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.Collections.Generic;
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
usingTrackMyWalks.Services;
namespaceTrackMyWalks
{
publicclassWalksPage:ContentPage
{
2. Next,weneedtocreateanewprivatepropertynamed_viewModelwithinourWalksPageclass.ThisisoftheWalksPageViewModeltype,andwillessentiallyprovideuswithaccesstotheContentPage'sBindingContextobject.Proceedandenterinthefollowinghighlightedcodesections:
WalksPageViewModel_viewModel
{
get{returnBindingContextasWalksPageViewModel;
}
}
publicWalksPage()
{
varnewWalkItem=newToolbarItem
{
Text="AddWalk"
};
3. Then,weneedtosetupaBindingtotheCommandpropertythatwedefinedwithinourWalksPageViewModelclass.ThiswillbecalledwhentheuserchoosestheAddWalk
button.Proceedandenterinthefollowinghighlightedcodesections:
//SetupourBindingclickeventhandler
newWalkItem.SetBinding(ToolbarItem.CommandProperty,
"CreateNewWalk");
//AddtheToolBaritemtoourToolBar
ToolbarItems.Add(newWalkItem);
4. Next,weneedtodeclareandinitializeourWalksPageViewModelBindingContexttoincludeourIWalkNavServiceconstructor,whichisusedbytheWalkBaseViewModelclass,andisretrievedfromtheXamarin.FormsDependencyServiceclass.Proceedandenterinthefollowinghighlightedcodesections:
//DeclareandinitializeourModelBindingContext
BindingContext=newWalksPageViewModel(DependencyService.Get
<IWalkNavS
ervice>());
//DefineourItemTemplate
varitemTemplate=newDataTemplate(typeof(ImageCell));
itemTemplate.SetBinding(TextCell.TextProperty,"Title");
itemTemplate.SetBinding(TextCell.DetailProperty,"Notes");
itemTemplate.SetBinding(ImageCell.ImageSourceProperty,
"ImageUrl");
varwalksList=newListView
{
HasUnevenRows=true,
ItemTemplate=itemTemplate,
SeparatorColor=Color.FromHex("#ddd"),
};
//SettheBindingpropertyforourwalksEntries
walksList.SetBinding(ItemsView<Cell>.ItemsSourceProperty,
"walkEntries");
5. Then,weneedtochangethewayinwhichanitemgetsselectedfromtheListView.WeneedtomakeacalltotheWalksTrailDetailscommandthatisincludedwithinourWalksPageViewModelclass,sothatitcannavigatetotheWalksTrailViewModel,whilstpassinginthechosenitemfromwithintheListView.Proceedandenterthefollowinghighlightedcodesections:
//InitializeoureventHandlertousewhen
theitemistapped
walksList.ItemTapped+=(objectsender,
ItemTappedEventArgse)=>
{
varitem=(WalkEntries)e.Item;
if(item==null)return;
_viewModel.WalkTrailDetails.Execute(item);
item=null;
};
Content=walksList;
}
6. Finally,weneedtocreateanOnAppearinginstancemethodofthenavigationhierarchythatwillbeusedtodisplayourWalksEntriespriortotheViewModelappearingonscreen.
7. WeneedtoensurethatourViewModelhasbeenproperlyinitializedbycheckingtoseethatitisn'tnull,priortocallingtheInitmethodofourWalksPageViewModel.Proceedandenterthefollowinghighlightedcodesections:
protectedoverrideasyncvoidOnAppearing()
{
base.OnAppearing();
//InitializeourWalksPageViewModel
if(_viewModel!=null)
await_viewModel.Init();
}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingtheWalksPagesothatitcantakeadvantageofourupdatedWalksPageViewModel.WelookedathowtosetthecontentpagetoaninstanceoftheWalksPageViewModelsothatitknowswheretogetthelistofwalkentries.ThelistwillbeusedanddisplayedwithintheListViewcontrol,andwillthenupdatetheBindingContextpropertyfortheWalksPagetopointtoaninstanceoftheIWalkNavServiceinterface.Asyoucansee,byusinganavigationservicewithinyourViewModels,itmakesnavigatingbetweeneachoftheViewModelsquiteeasy.
UpdatingthewalksentrypageViewModelandnavigationserviceNowthatwehavemodifiedtheMVVMViewModelthatwillbeusedforthemainWalksPage,ournextstepistobeginmodifyingtheWalkEntryViewModeltotakeadvantageofthenavigationservice,whichwillbeusedtocreatenewwalkentries,andsavethisinformationbacktotheWalkBaseViewModel.Thiswillbecoveredinalaterchapterasweprogressthroughoutthisbook.
Let'stakealookathowwecanachievethis,byperformingthefollowingsteps:
1. EnsurethattheWalkEntryViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//WalkEntryViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Diagnostics.Contracts;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Models;
usingTrackMyWalks.Services;
usingTrackMyWalks.ViewModels;
usingXamarin.Forms;
namespaceTrackMyWalks.ViewModels
{
publicclassWalkEntryViewModel:WalkBaseViewModel
{
string_title;
publicstringTitle
{
get{return_title;}
set
{
_title=value;
OnPropertyChanged();
SaveCommand.ChangeCanExecute();
}
}
string_notes;
publicstringNotes
{
get{return_notes;}
set
{
_notes=value;
OnPropertyChanged();
}
}
double_latitude;
publicdoubleLatitude
{
get{return_latitude;}
set
{
_latitude=value;
OnPropertyChanged();
}
}
double_longitude;
publicdoubleLongitude
{
get{return_longitude;}
set
{
_longitude=value;
OnPropertyChanged();
}
}
double_kilometers;
publicdoubleKilometers
{
get{return_kilometers;}
set
{
_kilometers=value;
OnPropertyChanged();
}
}
string_difficulty;
publicstringDifficulty
{
get{return_difficulty;}
set
{
_difficulty=value;
OnPropertyChanged();
}
}
double_distance;
publicdoubleDistance
{
get{return_distance;}
set
{
_distance=value;
OnPropertyChanged();
}
}
string_imageUrl;
publicstringImageUrl
{
get{return_imageUrl;}
set
{
_imageUrl=value;
OnPropertyChanged();
}
}
2. Inthenextstep,weneedtomodifytheWalksEntryViewModelclassconstructorwhichwillnowneedtoincludeaparameternavServicethatisincludedwithintheIWalkNavServiceinterfaceclass.Thenwe'llsettheViewModel'sclassconstructortoaccessallinstanceclassmemberscontainedwithinthenavServicewithintheWalksEntryViewModel,byusingthebasekeyword.Next,we'llinitializetheconstructorwithdefaultvaluesforourTitle,DifficultyandDistanceproperties.
3. LocatetheWalkEntryViewModelclassconstructor,andenterthefollowinghighlightedcodesections:
publicWalkEntryViewModel(IWalkNavServicenavService):
base(navService)
{
Title="NewWalk";
Difficulty="Easy";
Distance=1.0;
}
4. Next,weneedtomodifytheSaveCommandcommandpropertytoincludetheasyncandawaitkeywords.ThiscommandpropertywillbeusedtobindtotheSaveToolBarItemandwillrunanactionuponbeingpressed.Itwillthenexecuteaclassinstancemethodtodeterminewhetherthecommandcanbeexecuted.Proceedandenterinthefollowinghighlightedcodesections:
Command_saveCommand;
publicCommandSaveCommand
{
get
{
return_saveCommand??(_saveCommand=
newCommand(async()=>awaitExecuteSaveCommand(),
ValidateFormDetails));
}
}
5. Next,welocateandmodifytheExecuteSaveCommandinstancemethodtoincludetheasyncTaskkeywordstothemethoddefinition,andthenincludeareferencetoourPreviousPagemethodthatisdefinedwithinourIWalkNavServiceinterfacetoallowourWalkEntryPage
tobedismissedupontheuserclickingontheSavebutton.Proceedandenterinthefollowinghighlightedcodesections:
asyncTaskExecuteSaveCommand()
{
varnewWalkItem=newWalkEntries
{
Title=this.Title,
Notes=this.Notes,
Latitude=this.Latitude,
Longitude=this.Longitude,
Kilometers=this.Kilometers,
Difficulty=this.Difficulty,
Distance=this.Distance,
ImageUrl=this.ImageUrl
};
//Here,wewillsavethedetailsenteredinalaterchapter.
awaitNavService.PreviousPage();
}
//methodtocheckforanyformerrors
boolValidateFormDetails()
{
return!string.IsNullOrWhiteSpace(Title);
}
6. Finally,createtheInitmethodwithintheWalkEntryViewModel.ThiswillbeusedtoinitializetheWalkEntryPagewhenitiscalled.WeusetheTask.Factory.StartNewmethodtogivetheViewModelenoughtimetodisplaythepageonscreen,priortoinitializingtheContentPagecontents.Proceedandenterinthefollowinghighlightedcodesections:
publicoverrideasyncTaskInit()
{
awaitTask.Factory.StartNew(()=>
{
Title="NewWalk";
Difficulty="Easy";
Distance=1.0;
});
}
}
}
Inthissection,webeganbyensuringthatourViewModelinheritsfromtheWalkBaseViewModelclassandthenmodifiestheWalksEntryViewModelclassconstructortoincludetheparameternavServicewhichisincludedwithintheIWalkNavServiceinterfaceclass.Inournextstep,we'llinitializetheclassconstructorwithdefaultvaluesfortheTitle,Difficulty,andDistancepropertiesandthenmodifytheSaveCommandcommandmethodtoincludeareference
totheNavService.PreviousPagemethod.ThisisdeclaredwithintheIWalkNavServiceinterfaceclasstoallowourWalkEntryPagetonavigatebacktothepreviouscallingpagewhentheSavebuttonisclicked.
UpdatingtheWalksEntryPagetousetheupdatedViewModelInthissection,weneedtobindourmodelbindingcontext,BindingContext,totheWalkEntryViewModelsothatthenewwalkinformation,whichwillbeenteredwithinthispage,canbestoredwithintheWalkEntriesmodel.Let'stakealookathowwecanachievethis,byperformingthefollowingsteps:
1. EnsurethattheWalkEntryPage.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//WalkEntryPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingSystem.Collections.Generic;
namespaceTrackMyWalks
{
publicclassWalkEntryPage:ContentPage
{
2. Next,weneedtocreateanewprivatepropertynamed_viewModelwithintheWalkEntryPageclassthatisoftheWalksEntryViewModeltype,andwhichwillessentiallyprovideuswithaccesstotheContentPage'sBindingContextobject.Proceedandenterinthefollowinghighlightedcodesections:
WalkEntryViewModel_viewModel
{
get
{returnBindingContextasWalkEntryViewModel;
}
}
publicWalkEntryPage()
{
//SettheContentPageTitle
Title="NewWalkEntry";
3. Next,weneedtodeclareandinitializeourWalkEntryViewModelBindingContexttoincludetheIWalkNavServiceconstructor,whichisusedbytheWalkBaseViewModelclass,andisretrievedfromtheXamarin.FormsDependencyServiceclass.Proceedandenterinthefollowinghighlightedcodesections:
//DeclareandinitializeourModelBindingContext
BindingContext=newWalkEntryViewModel(
DependencyService.
Get<IWalkNavService>());
//DefineourNewWalkEntryfields
varwalkTitle=newEntryCell
{
Label="Title:",
Placeholder="TrailTitle"
};
walkTitle.SetBinding(EntryCell.TextProperty,
"Title",BindingMode.TwoWay);
varwalkNotes=newEntryCell
{
Label="Notes:",
Placeholder="Description"
};
walkNotes.SetBinding(EntryCell.TextProperty,
"Notes",BindingMode.TwoWay);
varwalkLatitude=newEntryCell
{
Label="Latitude:",
Placeholder="Latitude",
Keyboard=Keyboard.Numeric
};
walkLatitude.SetBinding(EntryCell.TextProperty,
"Latitude",BindingMode.TwoWay);
varwalkLongitude=newEntryCell
{
Label="Longitude:",
Placeholder="Longitude",
Keyboard=Keyboard.Numeric
};
walkLongitude.SetBinding(EntryCell.TextProperty,
"Longitude",BindingMode.TwoWay);
varwalkKilometers=newEntryCell
{
Label="Kilometers:",
Placeholder="Kilometers",
Keyboard=Keyboard.Numeric
};
walkKilometers.SetBinding(EntryCell.TextProperty,
"Kilometers",BindingMode.TwoWay);
varwalkDifficulty=newEntryCell
{
Label="DifficultyLevel:",
Placeholder="WalkDifficulty"
};
walkDifficulty.SetBinding(EntryCell.TextProperty,
"Difficulty",BindingMode.TwoWay);
varwalkImageUrl=newEntryCell
{
Label="ImageUrl:",
Placeholder="ImageURL"
};
walkImageUrl.SetBinding(EntryCell.TextProperty,
"ImageUrl",BindingMode.TwoWay);
//DefineourTableView
Content=newTableView
{
Intent=TableIntent.Form,
Root=newTableRoot
{
newTableSection()
{
walkTitle,
walkNotes,
walkLatitude,
walkLongitude,
walkKilometers,
walkDifficulty,
walkImageUrl
}
}
};
varsaveWalkItem=newToolbarItem
{
Text="Save"
};
saveWalkItem.SetBinding(MenuItem.CommandProperty,
"SaveCommand");
ToolbarItems.Add(saveWalkItem);}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingtheWalkEntryPagesothatitcantakeadvantageofourupdatedWalkEntryViewModel.WelookedathowtosetthecontentpagetoaninstanceoftheWalkEntryViewModelsothattheBindingContextpropertyfortheWalkEntryPagewillnowpointtoaninstanceoftheIWalkNavServiceinterface.
UpdatingthewalkstrailpageViewModelandnavigationserviceNowthatwehavemodifiedtheMVVMViewModelthatwillbeusedforourWalkEntrypage,ournextstepistobeginmodifyingtheWalksTrailViewModeltotakeadvantageofthenavigationservice,sothatitwillbeusedtodisplaythewalkentryinformationthathasbeenassociatedwiththechosenwalk.
Let'stakealookathowwecanachievethis,byperformingthefollowingthesteps:
1. EnsurethattheWalksTrailViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//WalksTrailViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.Threading.Tasks;
usingTrackMyWalks.Models;
usingTrackMyWalks.Services;
usingXamarin.Forms;
namespaceTrackMyWalks.ViewModels
{
publicclassWalksTrailViewModel:
WalkBaseViewModel<WalkEntries>
{
WalkEntries_walkEntry;
publicWalkEntriesWalkEntry
{
get{return_walkEntry;}
set
{
_walkEntry=value;
OnPropertyChanged();
}
}
2. Next,weneedtocreateaCommandpropertyforourclass.ThiswillbeusedwithinourWalkTrailPageandwillbeusedtohandlewhentheuserclicksontheBeginThisTrialbutton.TheCommandpropertywillrunanactionuponbeingpressed,andthenexecuteaclassinstancemethodtodeterminewhetherthecommandcanbeexecutedornot,priortonavigatingtotheDistTravelledViewModel,andpassinginthetrailDetailsforthechosenwalkfromtheWalksPage.Proceedandenterinthefollowinghighlightedcodesections:
Command<WalkEntries>_command;
publicCommand<WalkEntries>DistanceTravelled
{
get
{
return_command
??(_command=
newCommand<WalkEntries>(async(trailDetails)=>
awaitNavService.NavigateToViewModel
<DistTravelledViewModel,WalkEntries>(trailDetails)));
}
}
3. Next,weneedtodeclareandinitializeourWalksTrailViewModelBindingContexttoincludetheIWalkNavServiceconstructor,whichisusedbytheWalkBaseViewModelclass,andisretrievedfromtheXamarin.FormsDependencyServiceclass.Proceedandenterinthefollowinghighlightedcodesections:
publicWalksTrailViewModel(IWalkNavServicenavService):
base(navService)
{
}
4. Finally,createtheInitmethodwithintheWalksTrailViewModel.ThiswillbeusedtoinitializetheWalkTrailPagewhenitiscalled.WeusetheTask.Factory.StartNewmethodtogivetheViewModelenoughtimetodisplaythepageonscreen,priortoinitializingtheContentPagecontents,usingthepassedinwalkDetailsforourmodel.Proceedandenterinthefollowinghighlightedcodesections:
publicoverrideasyncTaskInit(WalkEntrieswalkDetails)
{
awaitTask.Factory.StartNew(()=>
{
WalkEntry=walkDetails;
});
}
}
}
Inthissection,webeginbyensuringthatourViewModelinheritsfromtheWalkBaseViewModelclass,andthatitacceptstheWalkEntriesdictionaryasitsparameter.Inournextstep,we'llcreateaDistanceTravelledCommandmethodthatwillnavigatetotheDistanceTravelledPagecontentpagewithinourNavigationStackthatpassestheWalkEntrydictionarytotheDistTravelledViewModelViewModelandpassaparametercontainingthetrailDetailsofthechosenwalk.
UpdatingtheWalksTrailPagetousetheupdatedViewModelInthissection,weneedtobindourmodelbindingcontext,BindingContext,totheWalksTrailViewModelsothatthewalkinformationdetailswillbedisplayedfromtheWalkEntriesmodelwhenawalkhasbeenclickedonwithinthemainWalksPage.Let'stakealookathowwecanachievethis,byperformingthefollowingsteps:
1. EnsurethattheWalkTrailPage.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//WalkTrailPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
usingTrackMyWalks.Services;
namespaceTrackMyWalks
{
publicclassWalkTrailPage:ContentPage
{
publicWalkTrailPage(WalkEntrieswalkItem)
{
Title="WalksTrail";
2. Next,weneedtodeclareandinitializeourWalkEntryViewModelBindingContexttoincludetheIWalkNavServiceconstructor,whichisusedbytheWalkBaseViewModelclass,andisretrievedfromtheXamarin.FormsDependencyServiceclass.Proceedandenterinthefollowinghighlightedcodesections:
//DeclareandinitializeourModelBindingContext
BindingContext=newWalksTrailViewModel(DependencyService.
Get<IWalkNavService>());
varbeginTrailWalk=newButton
{
BackgroundColor=Color.FromHex("#008080"),
TextColor=Color.White,
Text="BeginthisTrail"
};
3. Next,weneedtomodifythebeginTrailWalk.Clickedhandlerforourbutton,sothatuponbeingclicked,itwillnavigatetotheDistTravelledViewModelandpassintheWalkEntrydictionaryforthechosenwalkfromtheWalksPage.Proceedandenterinthefollowinghighlightedcodesections:
//DeclareandinitializeourEventHandler
beginTrailWalk.Clicked+=(sender,e)=>
{
if(_viewModel.WalkEntry==null)return;
_viewModel.DistanceTravelled.Execute(_viewModel.WalkEntry);
};
varwalkTrailImage=newImage()
{
Aspect=Aspect.AspectFill
};
walkTrailImage.SetBinding(Image.SourceProperty,
"WalkEntry.ImageUrl");
vartrailNameLabel=newLabel()
{
FontSize=28,
FontAttributes=FontAttributes.Bold,
TextColor=Color.Black
};
trailNameLabel.SetBinding(Label.TextProperty,
"WalkEntry.Title");
vartrailKilometersLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=12,
TextColor=Color.Black,
};
trailKilometersLabel.SetBinding(Label.TextProperty,
"WalkEntry.Kilometers",
stringFormat:"Length:{0}km");
vartrailDifficultyLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=12,
TextColor=Color.Black
};
trailDifficultyLabel.SetBinding(Label.TextProperty,
"WalkEntry.Difficulty",stringFormat:"Difficulty:{0}");
vartrailFullDescription=newLabel()
{
FontSize=11,
TextColor=Color.Black,
HorizontalOptions=LayoutOptions.FillAndExpand
};
trailFullDescription.SetBinding(Label.TextProperty,
"WalkEntry.Notes");
this.Content=newScrollView
{
Padding=10,
Content=newStackLayout
{
Orientation=StackOrientation.Vertical,
HorizontalOptions=LayoutOptions.FillAndExpand,
Children=
{
walkTrailImage,
trailNameLabel,
trailKilometersLabel,
trailDifficultyLabel,
trailFullDescription,
beginTrailWalk
}
}
};
}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingtheWalksTrailPagesothatitcantakeadvantageoftheWalksTrailViewModel.WelookedathowtosetthecontentpagetoaninstanceoftheWalksTrailViewModelsothattheBindingContextpropertyfortheWalkTrailPagewillnowpointtoaninstanceoftheIWalkNavServiceinterface.
WealsoslightlymodifiedourClickedhandlerforthebeginTrailWalkbuttonsothatitwillnownavigatetotheDistanceTravelledPagecontentpagewithintheNavigationStack,andpassintheWalkEntrydictionaryobjecttotheDistTravelledViewModelViewModel.
UpdatingthedistancetravelledViewModelandnavigationserviceNowthatwehavemodifiedtheMVVMViewModelthatwillbeusedforourWalkTrailPage,ournextstepistoupdatetheDistTravelledViewModeltotakeadvantageofthenavigationservice,sothatitcandisplaythewalkentryinformationthathasbeenassociatedwiththechosenwalk.
Let'stakealookathowwecanachievethis,byperformingthefollowingsteps:
1. EnsurethattheDistTravelledViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//DistTravelledViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Models;
usingTrackMyWalks.Services;
usingTrackMyWalks.ViewModels;
usingXamarin.Forms;
namespaceTrackMyWalks.ViewModels
{
publicclassDistTravelledViewModel:
WalkBaseViewModel<WalkEntries>
{
WalkEntries_walkEntry;
publicWalkEntriesWalkEntry
{
get{return_walkEntry;}
set
{
_walkEntry=value;
OnPropertyChanged();
}
}
double_travelled;
publicdoubleTravelled
{
get{return_travelled;}
set
{
_travelled=value;
OnPropertyChanged();
}
}
double_hours;
publicdoubleHours
{
get{return_hours;}
set
{
_hours=value;
OnPropertyChanged();
}
}
double_minutes;
publicdoubleMinutes
{
get{return_minutes;}
set
{
_minutes=value;
OnPropertyChanged();
}
}
double_seconds;
publicdoubleSeconds
{
get{return_seconds;}
set
{
_seconds=value;
OnPropertyChanged();
}
}
publicstringTimeTaken
{
get
{
returnstring.Format("{0:00}:{1:00}:{2:00}",
this.Hours,this.Minutes,this.Seconds);
}
}
2. Next,weneedtomodifytheDistTravelledViewModelclassconstructor,whichwillnowneedtoincludeanavServiceparameterthatisincludedwithintheIWalkNavServiceinterfaceclass.WethensettheViewModel'sclassconstructortoaccessallinstanceclassmemberscontainedwithinthenavServicebyusingthebasekeywordandinitializetheconstructorwithdefaultvaluesfortheHours,Minutes,Seconds,andTravelledproperties.
3. LocatetheDistTravelledViewModelclassconstructor,andenterthefollowinghighlightedcode:
publicDistTravelledViewModel(IWalkNavServicenavService):
base(navService)
{
this.Hours=0;
this.Minutes=0;
this.Seconds=0;
this.Travelled=100;
}
4. Then,createtheInitmethodwithintheDistTravelledViewModel,whichwillbeusedtoinitializetheDistanceTravelledPagecontentpagewhenitiscalled.WeneedtospecifyandusetheTask.Factory.StartNewmethodtogivetheViewModelenoughtimetodisplaythepageonscreen,priortoinitializingtheContentPagecontents,usingthepassedinwalkDetailsforourmodel.Proceedandenterinthefollowinghighlightedcodesections:
publicoverrideasyncTaskInit(WalkEntrieswalkDetails)
{
awaitTask.Factory.StartNew(()=>
{
WalkEntry=walkDetails;
});
}
5. Next,weneedtocreatetheBackToMainPagecommandpropertythatwillbeusedtobindtotheEndThisTrailbuttonthatwillrunanactionuponbeingpressed.Thisactionwillexecuteaclassinstancemethod,todeterminewhethertheCommandcanbeexecuted.
6. IftheCommandcanbeexecuted,acallwillbemadetotheBackToMainPagemethodontheNavServicenavigationserviceclasstotaketheuserbacktotheTrackMyWalksmainpage,byremovingallexistingViewModelswithintheNavigationStack,exceptthefirstpage.Proceedandenterinthefollowinghighlightedcodesections:
Command_mainPage;
publicCommandBackToMainPage
{
get
{
return_mainPage
??(_mainPage=new
Command(async()=>await
NavService.BackToMainPage()));
}
}
}
}
Inthissection,weupdatedtheDistanceTravelledViewModeltoinheritfromour
WalkBaseViewModelInterfaceclassandthenmodifytheDistTravelledViewModelclassconstructortopointtoaninstanceoftheIWalkNavServiceinterfaceclass.
WethencreatedtheInitmethodthatwillinitializetheDistanceTravelledViewModelwhenitiscalledandusetheTask.Factory.StartNewmethodtogivetheViewModelenoughtimetodisplaytheDistanceTravelledPagecontentpageonscreen,priortoinitializingtheContentPagecontents,usingthepassedinwalkDetailsforourmodel.
WealsocreatedtheBackToMainPagecommandpropertythatwillbeusedtobindtotheEndThisTrailbuttonthatwillrunanactiontoexecuteaclassinstancemethod,todeterminewhethertheCommandcanbeexecuted,andthenacallwillbemadetoBackToMainPagemethodontheNavServicenavigationserviceclasstotaketheuserbacktothefirstpagewithintheNavigationStack.
UpdatingtheDistanceTravelledPagetousetheupdatedViewModelNowthatwehavemodifiedtheMVVMViewModelthatwillbeusedbyourDistanceTravelledPagecontentpage,ournextstepistobeginmodifyingtheDistanceTravelledPagepagetotakeadvantageofournavigationservice,anddisplaywalkinformationdetails.ThecalculationsanddistancetravelledwillbedisplayedfromtheWalkEntriesmodel.
Let'stakealookathowwecanachievethis,byperformingthefollowingsteps:
1. EnsurethattheDistanceTravelledPage.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//DistanceTravelledPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingXamarin.Forms.Maps;
usingTrackMyWalks.Models;
usingTrackMyWalks.Services;
namespaceTrackMyWalks
{
publicclassDistanceTravelledPage:ContentPage
{
2. Next,weneedtocreateanewprivatepropertynamed_viewModelwithintheDistanceTravelledPageclass,whichisofourDistTravelledViewModeltype,andwillessentiallyprovideuswithaccesstotheContentPage'sBindingContextobject.Proceedandenterinthefollowinghighlightedcodesections:
DistTravelledViewModel_viewModel
{
get{returnBindingContextasDistTravelledViewModel;}
}
publicDistanceTravelledPage()
{
Title="DistanceTravelled";
3. Next,weneedtodeclareandinitializetheDistTravelledViewModelBindingContexttoincludeourIWalkNavServiceconstructor,whichisusedbytheWalkBaseViewModelclass,andisretrievedfromtheXamarin.FormsDependencyServiceclass.Proceedandenterin
thefollowinghighlightedcodesections:
//DeclareandinitializeourModelBindingContext
BindingContext=newDistTravelledViewModel
(DependencyService.
Get<IWalkNavService>());
4. Then,weneedtocreateanewmethodcalledLoadDetailswhichwillbeusedtograbthenameofthechosenwalkandtheLatitudeandLongitudevaluesfromtheDistTravelledViewModelaswellaszoomintotheuserentrylocation,usingtheMoveToRegionmethod.Proceedandenterinthefollowinghighlightedcodesections:
publicvoidLoadDetails()
{
//Instantiateourmapobject
vartrailMap=newMap();
//Placeapinonthemapforthechosen
//walktype
trailMap.Pins.Add(newPin
{
Type=PinType.Place,
Label=_viewModel.WalkEntry.Title,
Position=newPosition(_viewModel.WalkEntry.Latitude,
_viewModel.WalkEntry.Longitude)
});
//Centerthemaparoundthelistof
//walksentry'slocation
trailMap.MoveToRegion(MapSpan.FromCenterAndRadius(
newPosition(_viewModel.WalkEntry.Latitude,
_viewModel.WalkEntry.Longitude),
Distance.FromKilometers(1.0)));
vartrailNameLabel=newLabel()
{
FontSize=18,
FontAttributes=FontAttributes.Bold,
TextColor=Color.Black,
HorizontalTextAlignment=TextAlignment.Center
};
trailNameLabel.SetBinding(Label.TextProperty,
"WalkEntry.Title");
vartrailDistanceTravelledLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=20,
TextColor=Color.Black,
HorizontalTextAlignment=TextAlignment.Center
};
trailDistanceTravelledLabel.SetBinding(Label.TextProperty,
"Travelled",stringFormat:"DistanceTravelled:{0}km");
vartotalTimeTakenLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=20,
TextColor=Color.Black,
HorizontalTextAlignment=TextAlignment.Center
};
totalTimeTakenLabel.SetBinding(Label.TextProperty,
"TimeTaken",stringFormat:"TimeTaken:{0}");
varwalksHomeButton=newButton
{
BackgroundColor=Color.FromHex("#008080"),
TextColor=Color.White,
Text="EndthisTrail"
};
5. Next,weneedtomodifythewalksHomeButton.Clickedhandlerforourbuttonsothat,uponbeingclicked,itwillallowtheDistanceTravelledPagetonavigatebacktothefirstpagewithintheNavigationStack.Proceedandenterinthefollowinghighlightedcodesections:
//Setupoureventhandler
walksHomeButton.Clicked+=(sender,e)=>
{
if(_viewModel.WalkEntry==null)return;
_viewModel.BackToMainPage.Execute(0);
};
this.Content=newScrollView
{
Padding=10,
Content=newStackLayout
{
Orientation=StackOrientation.Vertical,
HorizontalOptions=LayoutOptions.FillAndExpand,
Children={
trailMap,
trailNameLabel,
trailDistanceTravelledLabel,
totalTimeTakenLabel,
walksHomeButton
}
}
};
}
6. Finally,weneedtocreateanOnAppearinginstancemethodofthenavigationhierarchythatwillbeusedtocorrectlyplotthewalk'sLongitudeandLatitudecoordinateswithinthemap,alongwiththewalkinformation,priortotheViewModelappearingonscreen.WeneedtoensurethattheViewModelhasproperlybeeninitializedbycheckingtoseethatitisn'tnull,priortocallingtheInitmethodoftheDistTravelledViewModel.Proceedandenterinthefollowinghighlightedcodesections:
protectedoverrideasyncvoidOnAppearing()
{
base.OnAppearing();
//InitializeourDistanceTravelledViewModel
if(_viewModel!=null)
{
await_viewModel.Init();
LoadDetails();
}
}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingtheDistanceTraveledPagesothatitcantakeadvantageoftheDistTravelledViewModel.WelookedathowtosetthecontentpagetoaninstanceoftheDistTravelledViewModelsothattheBindingContextpropertyfortheDistanceTravelledPagewillnowpointtoaninstanceoftheIWalkNavServiceinterface.
WealsoslightlymodifiedourClickedhandlerfortheWalksHomeButtonbutton,sothatitwillnownavigatetotheNavService.BackToMainPagemethod,whichisdeclaredwithintheIWalkNavServiceinterfaceclasstoallowtheDistanceTravelledPagetonavigatebacktothefirstpagewithintheNavigationStack.
UpdatingtheXamarin.Forms.AppclasstousethenavigationserviceInthissection,weneedtoupdateourXamarin.Forms.Appclass,bymodifyingtheconstructorinthemainAppclasstocreateanewinstanceofthenavigationserviceandregistertheapplication'sContentPagetoViewModelmappings.
Let'stakealookathowwecanachievethis,byperformingthefollowingsteps:
1. OpentheTrackMyWalks.csfileandensurethatitisdisplayedwithinthecodeeditor.2. Next,locatetheAppmethodandenterinthefollowinghighlightedcodesections:
//
//TrackMyWalks.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingTrackMyWalks.Services;
usingTrackMyWalks.ViewModels;
usingXamarin.Forms;
namespaceTrackMyWalks
{
publicclassApp:Application
{
publicApp()
{
//ChecktheTargetOSPlatform
if(Device.OS==TargetPlatform.Android)
{
MainPage=newSplashPage();
}
else
{
//Therootpageofyourapplication
varwalksPage=newNavigationPage(new
WalksPage()
{
Title="TrackMyWalks"
});
varnavService=DependencyService.
Get<IWalkNavService>()asWalkNavService;
navService.navigation=walksPage.Navigation;
navService.RegisterViewMapping(typeof
(WalksPageViewModel
),typeof(WalksPage));
navService.RegisterViewMapping(
typeof(WalkEntryViewModel
),
typeof(WalkEntryPage));
navService.RegisterViewMapping(
typeof(WalksTrailViewModel
),
typeof(WalkTrailPage));
navService.RegisterViewMapping(
typeof(DistTravelledViewMo
del),
typeof(DistanceTravelledPage));
MainPage=walksPage;
}
}
protectedoverridevoidOnStart()
{
//Handlewhenyourappstarts
}
protectedoverridevoidOnSleep()
{
//Handlewhenyourappsleeps
}
protectedoverridevoidOnResume()
{
//Handlewhenyourappresumes
}
}
}
Intheprecedingcodesnippet,webeginbydeclaringanavServicevariablethatpointstoaninstanceofthenavigationserviceasdefinedbyourassemblyattributefortheXamarin.FormsDependencyService,asdeclaredintheWalkNavServiceclass.
Inournextstep,wesetthenavService.navigationpropertytopointtoaninstanceoftheNavigationPageclassthatthewalksPage.navigationpropertycurrentlypointsto,andwillbeusedasthemainrootpage.
Finally,wecalltheRegisterViewMappinginstancemethodforeachoftheViewModelsandspecifytheassociatedContentPageforeach.
SummaryInthischapter,weupdatedourTrackMyWalksapplicationandcreatedanavigationserviceclassthatextendsthedefaultXamarin.Forms.NavigationAPI,whichprovidesuswithabettermethodofperformingViewModelnavigation.ThisseparatesthepresentationaspectsandbusinesslogicthatarecontainedwithintheViewModels.
Inthenextchapter,you'lllearnhowtocreatealocationservicesclassthatwillallowourTrackMyWalksapptoretrievelocation-basedinformation,anddeterminetheuser'scurrentlocation.You'llalsolearnhowtosetupourapptohandlebackgroundlocationupdates.Youwillalsolearnhowtoincorporateplatform-specificfeatureswithinyourapp,dependingontheplatformthatisbeingrun.
Chapter4.AddingLocation-BasedFeatureswithinYourAppInourpreviouschapter,welookedathowwecanapplywhatwealreadyknowabouttheMVVMdesignpattern,andhowwecannavigatebetweenourViewModels,bycreatinganavigationserviceC#classthatactsasthenavigationserviceforourapp,usingtheXamarin.FormsDependencyServiceclass.
Inthischapter,you'lllearnhowtogoaboutincorporatingplatform-specificfeatureswithintheTrackMyWalksapp,dependingonthemobileplatform.You'lllearnhowtocreateaC#class,whichwillactastheLocationServiceforourapp,aswellascreatingaIWalkLocationServiceinterfaceclassfile,whichwillincludeanumberofclassmethodsthatbothouriOSandAndroidplatformswillinherit,and,inturn,updatethecontentpagestobindwiththeViewModelstoallowlocation-basedinformationbetweentheseViewstohappen.
Wewillalsobecoveringhowtoproperlyperformlocationupdateswhiletheapplicationiseitherintheforegroundorbackground,andwewillalsobetouchingonsomekeybackgroundconcepts,whichincluderegisteringanappasabackground-necessaryapplication.
Thischapterwillcoverthefollowingtopics:
Creatingalocation-basedclassthatutilizesthenativeplatformcapabilitiesthatcomeaspartoftheiOSandAndroidplatformsEnablingbackgroundlocationupdatesaswellasgettingtheuser'scurrentlocationUpdatingtheTrackMyWalksapplicationtousetheLocationServiceUpdatingtheWalkEntryViewModeltousetheLocationServiceInterfaceUpdatingtheDistanceTravelledViewModeltousetheLocationServiceInterface
Creatingandusingplatform-specificservicesAsmentionedintheintroductiontothischapter,wecreatedacustomizednavigationservice,whichprovidedanIWalkNavServiceInterfaceclassforwhichourWalkBaseViewModelcontainedapropertyofthatinterfacetype,sothatanyimplementationsoftheIWalkNavServicecanbeprovidedtoeachoftheViewModels,asrequired.
ThebenefitofusinganInterfacetodefineplatform-specificservicesisthatitcanbeusedwithintheViewModelsandtheimplementationsoftheservicecanbeprovidedviadependencyinjection,usingtheDependencyService,withthoseimplementationsbeingactualservices,orevenmocked-upservicesforunittestingtheViewModels,whichwewillbecoveringinChapter9,UnitTestingYourXamarin.FormsAppsUsingtheNUnitandUITestFrameworks.
Inadditiontothenavigationservice,wecanuseacoupleofotherplatform-specificfeatureserviceswithinourTrackMyWalksapptoenrichitsdataanduserexperience.Inthissection,wewillbetakingalookathowtocreateaLocationServiceclassthatallowsustogetthespecificgeolocationcoordinatesfromtheactualdeviceforbothouriOSandAndroidplatforms.
CreatingtheLocationServiceInterfacefortheTrackMyWalksappBeforewecanbeginallowingourTrackMyWalksapptotakeadvantageofthedevice'sgeolocationcapabilitiesforbothouriOSandAndroidplatforms,wewillneedtocreateanInterfacewithintheTrackMyWalksPortableClassLibrary,whichcanthenbeusedbytheViewModelsforeachplatform.
Wewillneedtodefinetheinterfaceforourlocationservice,asthiswillcontainmethodimplementations,aswellasadatastructurethatwillbeusedtorepresentourlatitudeandlongitudecoordinates.
Let'stakealookathowwecanachievethisthroughthefollowingsteps:
1. LaunchtheXamarinStudioapplication,andensurethattheTrackMyWalkssolutionisloadedwithintheXamarinStudioIDE.
2. Next,createanewemptyinterfacewithintheTrackMyWalksPCLprojectsolution,undertheServicesfolder.
3. Then,choosetheEmptyInterfaceoptionlocatedwithintheGeneralsectionandenterIWalkLocationServiceforthenameofthenewinterfacefiletobecreated,asshowninthefollowingscreenshot:
4. Next,clickontheNewbuttontoallowthewizardtoproceedandcreatethenewEmptyInterfaceclassfile,asshownintheprecedingscreenshot.
5. OurwizardhascreatedourIWalkLocationServiceclassfile,whichwillbeusedbyourViewModelsandcontentpageViewstodisplaygeolocationcoordinates.AswestarttobuildtheLocationServiceInterfaceclass,youwillseethatitcontainsacoupleofclassmembersthatwillallowustogettheuser'slocationaswellasdeterminingthedistancethattheuserhastravelledfrompointAtopointB.
6. ItalsocontainsadatastructureIWalkLocationCoordsthatwillbeusedtoholdourlatitudeandlongitudegeolocationcoordinates.ToproceedwithcreatingthebaseIWalkLocationServiceInterface,performthefollowingsteps:
7. EnsurethattheIWalkLocationService.csfileisdisplayedwithinthecodeeditorandenterthefollowingcodesnippet:
//
//IWalkLocationService.cs
//TrackMyWalksLocationServiceInterface
//
//CreatedbyStevenF.Danielon16/09/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
namespaceTrackMyWalks.Services
{
//DefineourWalkLocationServiceInterface
publicinterfaceIWalkLocationService
{
//DefineourLocationServiceInstanceMethods
voidGetMyLocation();
doubleGetDistanceTravelled(doublelat,doublelon);
eventEventHandler<IWalkLocationCoords>MyLocation;
}
//WalkLocationCoordinatesObtained
publicinterfaceIWalkLocationCoords
{
doublelatitude{get;set;}
doublelongitude{get;set;}
}
}
Intheprecedingcodesnippet,westartbydefiningtheimplementationforourIWalkLocationService,whichwillprovideourTrackMyWalksappwiththeabilitytogettheuser'scurrentlocation,calculatingthedistancetravelledfromtheuser'scurrentlocationtothetrailgoal.WealsodefineanEventHandlermyLocation,whichwillbecalledwhenevertheplatformobtainsanewlocation.
TheIWalkLocationCoordsinterfacedefinesaclassthatcontainstwopropertiesthatwillbeusedbyourEventHandlertoreturnthelatitudeandlongitudevalues.
Note
AnInterfacecontainsonlythemethods,properties,andeventssignaturedefinitions.Anyclassthatimplementstheinterfacemustimplementallmembersoftheinterfacethatarespecifiedintheinterfacedefinition.
NowthatwehavedefinedthepropertyandmethodimplementationsthatwillbeusedbyourIWalkLocationService,ournextstepwillbetocreatetherequiredLocationServiceclassimplementationsforeachofourplatforms,astheyaredefinedquitedifferently.
CreatingtheLocationServiceclassfortheAndroidplatformInthissection,wewillbeginbysettingupthebasicstructureforourTrackMyWalks.DroidsolutiontoincludethefolderthatwillbeusedtorepresentourServices.Let'stakealookathowwecanachievethisthroughthefollowingsteps:
1. LaunchtheXamarinStudioapplication,andensurethattheTrackMyWalkssolutionisloadedwithintheXamarinStudioIDE.
2. Next,createanewfolderwithintheTrackMyWalks.Droidproject,calledServices,asshowninthefollowingscreenshot:
3. Next,createanemptyclasswithintheServicesfolder.Ifyoucan'trememberhowtodothis,youcanrefertothesectionentitledCreatingtheNavigationServiceInterfacefortheTrackMyWalksapp,withinChapter3,NavigatingwithintheMVVMModel-TheXamarin.FormsWay.
4. Then,choosetheEmptyClassoptionlocatedwithintheGeneralsectionandenterWalkLocationServiceforthenameofthenewclassfiletobecreated,asshowninthefollowingscreenshot:
5. Next,clickontheNewbuttontoallowthewizardtoproceedandcreatethenewemptyclassfile,asshownintheprecedingscreenshot.
Upuntilthispoint,allwehavedoneiscreateourWalkLocationServiceclassfile.ThisclasswillbeusedandwillactasthebaseLocationServiceclassthatwillcontainthefunctionalityrequiredbyourViewModels.
AswestarttobuildourLocationClass,youwillseethatitcontainsanumberofmethodmembersthatwillbeusedtohelpusgettheuser'scurrentgeolocationcoordinatesfromtheirdevice,sothatwecandisplaythiswithineachofourViewModels,anditwillimplementtheIWalkLocationServiceInterface.
ToproceedwithcreatingandimplementingthebaseWalkLocationServiceclass,performthefollowingsteps:
1. EnsurethattheWalkLocationService.csfileisdisplayedwithinthecodeeditor,andenterthehighlightedcodesectionsshowninthefollowingcodesnippet:
//
//WalkLocationService.cs
//TrackMyWalksLocationServiceClass(Android)
//
//CreatedbyStevenF.Danielon16/09/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingAndroid.Content;
usingAndroid.Locations;
usingTrackMyWalks.Droid;
usingTrackMyWalks.Services;
usingXamarin.Forms;
2. First,weinitializeourWalkLocationServiceclass,whichistobemarkedasadependency,byaddingtheDependencymetadataattributejustaswedidforournavigationservice.ThisissothatitcanberesolvedbytheXamarin.FormsDependencyServicetoallowittofindanduseourmethodimplementationsasdefinedwithinourInterface.WealsoneedtoimplementtheIWalkCoordinatesinterfaceusingtheLocationEventArgsclassthatcontainsourlatitudeandlongitudeproperties,whichwillbepopulatedwheneveranewlocationisobtained:
[assembly:Xamarin.Forms.Dependency(
typeof(WalkLocationService))]
namespaceTrackMyWalks.Droid
{
//Eventargumentscontaininglatitudeandlongitude
publicclassCoordinates:EventArgs,IWalkCoordinates
{
publicdoublelatitude{get;set;}
publicdoublelongitude{get;set;}
}
3. Next,weneedtomodifyourWalkLocationServiceclassconstructorsignature,sothatitinheritsfromtheIWalkLocationServiceInterfaceclass,aswellasimplementinganILocationListenerinterfaceclass,whichwillbeusedtoindicatewhenevertheuser'slocationchanges,byimplementingfourmethods-OnLocationChanged,OnProviderDisabled,OnProviderEnabled,andOnStatusChanged:
publicclassWalkLocationService:Java.Lang.Object,
IWalkLocationService,ILocationListener{
LocationManagerlocationManager;
LocationnewLocation;
//CreatethefourmethodsforourLocationListener
//interface.
publicvoidOnProviderDisabled(stringprovider){}
publicvoidOnProviderEnabled(stringprovider){}
publicvoidOnStatusChanged(stringprovider,
Availabilitystatus,Android.OS.Bundleextras){}
Note
WeneedtoensurethatourWalkLocationServiceclassinheritsfromtheAndroid-specificJava.Lang.Objectclass,sothatwecanprovideaccesstothesystemlocationservices,inordertoobtainperiodicupdatesonthedevice'sgeographicallocation.
WheneveryourclassesinheritfromtheILocationListenerAPI,theILocationListenerInterfacesupportsseveraldifferentmethodtypes,whichareexplainedinthefollowingtable:
Methodname Description
OnProviderDisabledThismethodisfiredupwheneverthelocationserviceproviderhasbeendisabledbytheuser.
OnProviderEnabledThismethodisfiredupwheneverthelocationserviceproviderhasbeenenabledbytheuser.
OnStatusChanged
Thismethodisfiredupwheneverthelocationserviceproviderstatushasbeenchanged,thatis,thelocationserviceshavebeendisabledbytheuser.
OnLocationChangedThismethodisfiredupwheneverachangeinlocationhasbeendetected.
4. Then,weneedtosetupanEventHandlerdelegateobjectthatwillbecalledwheneverthelocationhasbeenobtainedorchanged:
//SetupourEventHandlerdelegatethatiscalled
//wheneveralocationhasbeenobtained
publiceventEventHandler<IWalkCoordinates>MyLocation;
5. Next,wecreatetheOnLocationChangedmethodthatwillbefiredupwhenevertheuser'slocationhasbeenchangedsincethelasttime.Thismethodacceptstheuser'scurrentlocation,andweneedtoaddachecktoensurethatourlocationisnotemptypriortocreatinganinstanceofourCoordinatesclassdatastructure,andthenassigningthenewlocationdetailsforourlatitudeandlongitude,beforefinallypassingacopyoftheCoordinatestotheMyLocationEventHandler:
//Firedwheneverthereisachangeinlocation
publicvoidOnLocationChanged(Locationlocation)
{
if(location!=null)
{
//CreateaninstanceofourCoordinates
varcoords=newCoordinates();
//Assignouruser'sLatitudeandLongitude
//values
coords.latitude=location.Latitude;
coords.longitude=location.Longitude;
//Updateournewlocationtostorethe
//newdetails.
newLocation=newLocation("PointA");
newLocation.Latitude=coords.latitude;
newLocation.Longitude=coords.longitude;
//Passthenewlocationdetailstoour
//LocationServiceEventHandler.
MyLocation(this,coords);
};
}
6. Then,wecreatetheGetMyLocationmethodthatwillbeusedtostartgettingtheuser'slocation.WethensetupourlocationManagertorequestlocationupdates.Thisisbecause,whendealingwithAndroid,theseservicesrequireaContextobjectinorderforthemtowork.Xamarin.FormscomeswiththeForms.Contextobject,andweusetheNetworkProvidermethodtoobtainthelocationusingthecellularnetworkandWi-Fi.Considerthefollowingcode:
//Methodtocalltostartgettinglocation
publicvoidGetMyLocation()
{
locationManager=(LocationManager)
longminTime=0;//Timeinmilliseconds
floatminDistance=0;//Distanceinmetres
Forms.Context.GetSystemService(Context.LocationService);
locationManager.RequestLocationUpdates(
LocationManager.NetworkProvider,
minTime,
minDistance,
this);
}
7. Next,createtheGetDistanceTravelledmethod,whichacceptstwoparameterscontainingourlatitudeandlongitudevalues.Wecreateanewlocation,andsettheLatitudeandLongitudevaluesthatcontaintheendingcoordinatesforourtrail.Wethendeclareavariabledistance,whichcallstheDistanceTomethodonournewLocationobject,todetermineourcurrentdistancefromtheendgoal.Wedividethedistanceby1000toconvertthedistancetravelledtometers:
//Calculatesthedistancebetweentwopoints
publicdoubleGetDistanceTravelled(doublelat,doublelon)
{
LocationlocationB=newLocation("TrailFinish");
locationB.Latitude=lat;
locationB.Longitude=lon;
floatdistance=newLocation.DistanceTo(locationB)/1000;
returndistance;
}
8. Finally,createtheWalkLocationServiceclassfinalizer;thiswillbeusedtostopallupdatelistenereventswhenourclasshasbeensettonull.
//Stopthelocationupdatewhentheobjectissettonull
~WalkLocationService()
{
locationManager.RemoveUpdates(this);
}
}
}
NowthatwehavecreatedtheWalkLocationServiceclassfortheAndroidportionofourTrackMyWalksapp,ournextstepistocreatethesameclassfortheiOSportion,whichwillbecoveredinthenextsection.
CreatingtheLocationServiceclassfortheiOSplatformIntheprevioussection,wecreatedtheclassforourWalkLocationService.Wealsodefinedanumberofdifferentmethodsthatwillbeusedtoprovidelocation-basedinformationwithinourMVVMViewModel.
Inthissection,wewillbuildtheiOSportionforourWalkLocationService,justlikewedidforourAndroidportion.Youwillnoticethattheimplementationsforbothoftheseclassesarequitesimilar;however,theseimplementdifferentmethods,asyouwillseeoncewestartimplementingthem.
Let'stakealookathowwecanachievethisthroughthefollowingsteps:
1. CreateanemptyclasswithintheServicesfolderforourTrackMyWalks.iOSproject,andenterWalkLocationServiceforthenameofthenewclassfiletocreate.
2. OnceyouhavecreatedtheWalkLocationServiceclassfile,ensurethattheWalkLocationService.csfileisdisplayedwithinthecodeeditorandenterthehighlightedcodesectionsshowninthefollowingcodesnippet:
//
//WalkLocationService.cs
//TrackMyWalksLocationServiceClass(iOS)
//
//CreatedbyStevenF.Danielon16/09/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingCoreLocation;
usingTrackMyWalks.iOS;
usingTrackMyWalks.Services;
usingUIKit;
3. Next,weinitializeourWalkLocationServiceclass,whichistobemarkedasadependency,byaddingtheDependencymetadataattributejustaswedidforournavigationservice.ThisissothatitcanberesolvedbytheXamarin.FormsDependencyServicetoallowittofindanduseourmethodimplementationsasdefinedwithinourInterface.WealsoneedtoimplementtheIWalkCoordinatesinterfaceusingtheCoordinatesclass,whichcontainsthelatitudeandlongitudepropertiesthatwillbepopulatedwheneveranewlocationisobtained:
[assembly:Xamarin.Forms.Dependency(typeof(WalkLocationService))]
namespaceTrackMyWalks.iOS
{
//Eventargumentscontaininglatitudeandlongitude
publicclassCoordinates:EventArgs,IWalkCoordinates
{
publicdoublelatitude{get;set;}
publicdoublelongitude{get;set;}
}
4. Then,weneedtomodifyourWalkLocationServiceclassconstructorsignature,sothatitinheritsfromtheIWalkLocationServiceInterfaceclass.WealsoneedtodeclareourlocationManagerobject,whichwillbeusedtoobtaintheuser'slocation.WealsocreateanewLocationobjectoftypeCLLocation,whichwillbeusedtoconvertthelatitudeandlongitudecoordinatesfromthelocationManagerobjectintoaCLLocationobject:
//WalkLocationServiceclassthatinheritsfromour
//IWalkLocationServiceinterface
publicclassWalkLocationService:IWalkLocationService
{
//DeclareourLocationManager
CLLocationManagerlocationManager;
CLLocationnewLocation;
5. Next,weneedtosetupanEventHandlerdelegateobjectthatwillbecalledwheneverthelocationhasbeenobtainedorchanged:
//SetupourEventHandlerdelegatethatiscalled
//wheneveralocationhasbeenobtained
publiceventEventHandler<IWalkCoordinates>MyLocation;
6. Then,wecreatetheGetMyLocationmethodthatwillbeusedtostartgettingtheuser'slocation.Next,wesetupourlocationManagerusingtheiOSCLLocationManagerclasstoallowourclasstorequestlocationupdates.Wethenperformacheck,usingtheLocationServicesEnabledpropertyoftheCLLocationManagerclass,toensurethatlocationserviceshavebeenenabledontheuser'sdevice.
7. Thisisagoodchecktoenforcepriortorequestingthegettingoftheuser'slocation,andifourCLLocationManagerclassdeterminesthatlocationserviceshavebeendisabled,wedisplayamessagetotheuser,usingtheUIAlertViewclass:
//Methodtocalltostartgettinglocation
publicvoidGetMyLocation()
{
locationManager=newCLLocationManager();
//Checktoseeifwehavelocationservices
//enabled
if(CLLocationManager.LocationServicesEnabled)
{
//Setthedesiredaccuracy,inmeters
locationManager.DesiredAccuracy=1;
//CLLocationManagerDelegateMethods
8. Next,wesetupaneventhandler,LocationsUpdated,thatwillstartfiringupwheneverthereisachangeintheuser'scurrentlocation,andwecallthelocationUpdatedinstancemethod,passinginthelocationgeo-coordinates:
//Firedwheneverthereisachangein
//location
locationManager.LocationsUpdated+=(objectsender,
CLLocationsUpdatedEventArgse)=>
{
locationUpdated(e);
};
9. Then,wesetupaneventhandler,AuthorizationChanged,whichwillbecalledwheneveritdetectsachangemadetotheauthorizationoflocation-basedservices.Forexample,thiswillbecalledif,forsomereason,theuserdecidestoturnofflocation-basedservices:
//Thiseventgetsfiredwheneverit
//detectsachange,i.e.,iftheuser
//hasturnedoffordisabledlocation
//basedservices.
locationManager.AuthorizationChanged+=(object
sender,CLAuthorizationChangedEventArgse)=>
{
didAuthorizationChange(e);
//Performlocationchangeswithinthe
//foreground.
locationManager.RequestWhenInUseAuthorization();
};
}
}
10. Next,wecreatethelocationUpdatedmethodthatwillbefiredupwhenevertheuser'slocationhasbeenchangedsincethelasttime.Thismethodacceptstheuser'scurrentlocation,whichisdefinedbytheCLLocationsUpdatedEventArgsinavariablecallede.Next,wecreateaninstanceofourCoordinatesclassdatastructure,andthenassignthenewlocationdetailsforthelatitudeandlongitude,beforefinallypassingacopyoftheCoordinatestotheMyLocationEventHandler:
//Methodiscalledwheneverthereisachangein
//location
publicvoidlocationUpdated(CLLocationsUpdatedEventArgse)
{
//CreateourLocationCoordinates
varcoords=newCoordinates();
//Getalistofourlocationsfound
varlocations=e.Locations;
//ExtractourLatitudeandLongitudevalues
//fromourlocationsarray.
coords.latitude=locations[locations.Length-1].
Coordinate.Latitude;
coords.longitude=locations[locations.Length-1].
Coordinate.Longitude;
//Then,convertbothourLatitudeandLongitude
//valuestoaCLLocationobject.
newLocation=newCLLocation(coords.latitude,
coords.longitude);
MyLocation(this,coords);
}
11. Then,wecreatethedidAuthorizationChangemethod,whichwillbecalledwhenevertheCLLocationManagerdelegatedetectsachangeintheauthorizationstatus;youwillbenotifiedaboutthosechanges.Tohandleanychangesintheauthorizationstatuswhileyourappisrunning,andtopreventyourapplicationfromcrashingunexpectedly,youwillneedtoensurethattheproperauthorizationishandledaccordingly.
12. Ifwedetectthattheuserhasrestrictedordeniedaccesstolocationservicesonthedevice,wewillneedtoalerttheusertothis,anddisplayanalertdialogpopup:
publicvoiddidAuthorizationChange(
CLAuthorizationChangedEventArgsauthStatus)
{
switch(authStatus.Status){
caseCLAuthorizationStatus.AuthorizedAlways:
locationManager.RequestAlwaysAuthorization();
break;
caseCLAuthorizationStatus.AuthorizedWhenInUse:
locationManager.StartUpdatingLocation();
break;
caseCLAuthorizationStatus.Denied:
UIAlertViewalert=newUIAlertView();
alert.Title="LocationServicesDisabled";
alert.AddButton("OK");
alert.AddButton("Cancel");
alert.Message="Enablelocationsforthisapp
via\ntheSettingsapponyouriPhone";
alert.AlertViewStyle=UIAlertViewStyle.Default;
alert.Show();
alert.Clicked+=(objects,
UIButtonEventArgsev)=>
{
varButton=ev.ButtonIndex;
};
break;
default:
break;
}
}
13. ThedidAuthorizationChangemethodcontainsanumberofauthorizationstatuscodes,andtheseareexplained,alongwiththeirdescriptions,inthefollowingtable:
Authorizationstatus Description
.AuthorizedAlwaysor
.AuthorizedWhenInUse
Eitherofthesecasescanoccurwhenevertheuserhasgrantedaccessforyourapptouselocationservices.Thesestatusesarebothmutuallyexclusive,asyoucanonlyreceiveonetypeofauthorizationatatime.
.NotDetermined
Thisgenerallyhappenswhenevertheuserhasn'tmadeachoiceregardingwhetheryouriOSappcanbeginacceptinglocationupdates,andcanbecausediftheuserhasinstalledyourappforthefirsttimeandhasnotrunityet.
.Restrictedor
.Denied
Youwillgenerallyreceivethistypeofauthorizationstatusstatewhenevertheuserhasexplicitlydeniedaccesstoyourappfortheuseoflocationservices,orwhenlocationservicesarecurrentlyunavailable.
Note
IfyouareinterestedinfindingoutmoreinformationontheCLLocationManagerclass,pleaserefertotheXamarindeveloperdocumentationlocatedathttps://developer.xamarin.com/api/type/CoreLocation.CLLocationManager/.
14. Next,createtheGetDistanceTravelledmethod,whichacceptstwoparameterscontainingourlatandlonvalues,anddeclaresavariabledistance,whichcallstheDistanceFrommethodonournewLocationobject,todetermineourcurrentdistancefromtheendgoal.Wedividethedistanceby1000toconvertthedistancetravelledtometers:
//Calculatesthedistancebetweentwopoints
publicdoubleGetDistanceTravelled(doublelat,doublelon)
{
//Getthedistancetravelledfrom
//currentlocationtothepreviouslocation.
vardistance=newLocation.DistanceFrom(new
CLLocation(lat,lon))/1000;
returndistance;
}
15. Finally,createtheWalkLocationServiceclassfinalizer,whichwillbeusedtostopallupdatelistenereventsandfreethememoryusedwhenourclasshasbeensettonull:
//Stopsperforminglocationupdateswhenthe
//objecthasbeensettonull.
~WalkLocationService()
{
locationManager.StopUpdatingLocation();
}
}
}
NowthatwehavecreatedtheWalkLocationServiceclassfortheiOSportionofourTrackMyWalksapp,ournextstepistolearnhowtoprovideouriOSappwiththefunctionalitytoperformcontinuouslocationupdatesinthebackground.
Enablingbackgroundupdatesandgettingtheuser'scurrentlocationThisrelatestoworkingwithbackgroundlocationupdatestocontinuouslymonitorchangestotheuserlocationinthebackground.
Let'stakealookathowwecanachievethisthroughthefollowingsteps:
1. Double-clickontheInfo.plistfile,whichiscontainedwithintheTrackMyWalks.iOSproject,andensurethattheApplicationtabisshowing.
2. Next,scrolldowntothebottomofthepageandselectEnableBackgroundModesfromundertheBackgroundModessectiontoenablebackgroundupdates.
3. Then,ensurethattheLocationUpdatesoptionhasbeenselected,sothatXcodecanprovisionyourapptomonitorlocation-basedupdatesinthebackground:
4. NowthatwehavemodifiedourTrackMyWalks.iOSprojecttomonitorlocationupdatesinthebackground,weneedtodoonemorethingandtellXcodetohandleLocationupdates.Solet'sdothatnow.
5. EnsurethattheInfo.plistfileisdisplayedwithintheXamarinIDE,andthattheSourcetabisshowing.
6. Next,createthekeysNSLocationAlwaysUsageDescriptionandNSLocationWhenInUseUsageDescriptionbyclickingwithintheAddnewentrysection
oftheInfo.plist.
7. Then,addTrackMyWalkswouldliketoobtainyourlocationasthestringdescriptionfortheValuefield,asshownintheprecedingscreenshot.
Next,weneedtoprovideourappwiththeabilitytomonitorlocationupdatesinthebackgroundforourTrackMyWalks.iOSproject.Let'stakealookathowwecanachievethisthroughthefollowingsteps:
1. EnsurethattheWalkLocationService.csfileisdisplayedwithinthecodeeditor.2. Next,locatetheGetMyLocationmethodandenterthefollowingcodesnippet:
//Methodtocalltostartgettinglocation
publicvoidGetMyLocation()
{
locationManager=newCLLocationManager();
//Checktoseeifwehavelocationservices
//enabled
if(CLLocationManager.LocationServicesEnabled)
{
//Setthedesiredaccuracy,inmeters
locationManager.DesiredAccuracy=1;
//iOS8hasadditionalpermission
//requirements
if(UIDevice.CurrentDevice.CheckSystemVersion(8,0))
{
//Performlocationchangeswithinthe
//background
locationManager.RequestAlwaysAuthorization();
}
//iOS9,comeswithanewmethodthat
//allowsustoreceivelocationupdates
//withintheback,whentheapphas
//suspended.
if(UIDevice.CurrentDevice.CheckSystemVersion(9,0))
{
locationManager.AllowsBackgroundLocationUpdates=
true;
}
//CLLocationManagerDelegateMethods
//Firedwheneverthereisachangein
//location
locationManager.LocationsUpdated+=(objectsender,
CLLocationsUpdatedEventArgse)=>
{
locationUpdated(e);
};
//Thiseventgetsfiredwheneverit
//detectsachange,i.e.,iftheuserhas
//turnedoffordisabledLocationBased
//Services.
locationManager.AuthorizationChanged+=(object
sender,CLAuthorizationChangedEventArgse)=>
{
didAuthorizationChange(e);
//Performlocationchangeswithin
//theforeground.
locationManager.RequestWhenInUseAuthorization();
};
}
}
Intheprecedingcodesnippet,wechecktheiOSversioncurrentlyrunningontheuser'sdevice,andusetheRequestAlwaysAuthorizationmethodcallonthelocationManagerclasstorequesttheuser'spermissiontoobtaintheircurrentlocation.IniOS9,AppledecidedtoaddanewmethodcalledAllowsBackgroundLocationUpdates,whichallowsthehandlingofbackgroundlocationupdates.Next,wealsoneedtoconfigureourAndroidportionofourTrackMyWalks.DroidprojectbymodifyingtheAndroidManifest.xmlfile.
Let'stakealookathowwecanachievethisthroughthefollowingsteps:
1. Double-clickontheAndroidManifest.xmlfile,whichiscontainedwithinthe
TrackMyWalks.Droidproject,andensurethattheSourcetabisselected,asshowninthefollowingscreenshot:
2. EnsurethattheAndroidManifest.xmlfileisdisplayedwithinthecodeeditor,andenterthefollowinghighlightedcodesections:
<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android="http://schemas.
android.com/apk/res/android"
android:versionCode="1"android:versionName="1.0"
package="com.geniesoftstudios.trackmywalks">
<uses-sdkandroid:minSdkVersion="15"/>
<uses-permissionandroid:name="android.
permission.ACCESS_FINE_LOCATION"/>
<uses-permissionandroid:name="android.
permission.ACCESS_COARSE_LOCATION"/>
<uses-permissionandroid:name="android.
permission.INTERNET"/>
<applicationandroid:label="TrackMyWalks">
</application>
</manifest>
Intheprecedingcodesnippet,webeginbyaddingpermissionsthatwillallowour
TrackMyWalksAndroidapptoaccesslocationinformationforlocationupdates,aswellastheInternet.Googleisprettystrictaboutwhichpermissionsareallowed,andthesemustbeapprovedpriortoyourappbeingacceptedintotheGooglePlayStore.
UpdatingtheWalkEntryViewModeltousethelocationserviceNowthatwehavecreatedourWalkLocationServiceforbothourAndroidandiOSimplementations,weneedtobeginmodifyingourViewModel,whichwillbeusedbyourWalkEntrypage,totakeadvantageofourLocationService.
Let'stakealookathowwecanachievethisthroughthefollowingsteps:
1. EnsurethattheWalkEntryViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//WalkEntryViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Diagnostics.Contracts;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Models;
usingTrackMyWalks.Services;
usingTrackMyWalks.ViewModels;
usingXamarin.Forms;
namespaceTrackMyWalks.ViewModels
{
publicclassWalkEntryViewModel:WalkBaseViewModel
{
2. Next,wedeclarealocationServicevariablethatwillbeusedtoprovideareferencetoourIWalkLocationServiceandprovideourclasswithareferencetotheEventHandler,whichiscontainedwithinourIWalkLocationServiceinterfaceclass;thiswillcontainourlocationcoordinateinformationwheneverthelocationchanges.Toproceed,enterthefollowinghighlightedcodesections:
IWalkLocationServicelocationService;
string_title;
publicstringTitle
{
get{return_title;}
set
{
_title=value;
OnPropertyChanged();
SaveCommand.ChangeCanExecute();
}
}
string_notes;
publicstringNotes
{
get{return_notes;}
set
{
_notes=value;
OnPropertyChanged();
}
}
double_latitude;
publicdoubleLatitude
{
get{return_latitude;}
set
{
_latitude=value;
OnPropertyChanged();
}
}
double_longitude;
publicdoubleLongitude
{
get{return_longitude;}
set
{
_longitude=value;
OnPropertyChanged();
}
}
double_kilometers;
publicdoubleKilometers
{
get{return_kilometers;}
set
{
_kilometers=value;
OnPropertyChanged();
}
}
string_difficulty;
publicstringDifficulty
{
get{return_difficulty;}
set
{
_difficulty=value;
OnPropertyChanged();
}
}
double_distance;
publicdoubleDistance
{
get{return_distance;}
set
{
_distance=value;
OnPropertyChanged();
}
}
string_imageUrl;
publicstringImageUrl
{
get{return_imageUrl;}
set
{
_imageUrl=value;
OnPropertyChanged();
}
}
3. Inournextstep,wewillneedtomodifythecontentsofourWalksEntryViewModelclassconstructortodeclareandinitializeourlocationServicevariable,whichwillincludeourIWalkLocationServiceconstructorthatisretrievedfromtheXamarin.FormsDependencyServiceclass.WethenproceedtocalltheMyLocationmethodonourEventHandler,whichisdefinedwithintheIWalkLocationServiceinterface;thiswillreturnthegeographicallocationcoordinatesdefinedbytheirLatitudeandLongitudevalues.
4. LocatetheWalksEntryViewModelclassconstructorandenterthefollowinghighlightedcodesections:
publicWalkEntryViewModel(IWalkNavServicenavService):
base(navService)
{
Title="NewWalk";
Difficulty="Easy";
Distance=1.0;
//GetourLocationService
locationService
=DependencyService.Get<IWalkLocationService>();
//Checktoensurethatwehaveavalue
//forourobject
if(locationService!=null)
{
locationService.MyLocation+=(objectsender,
IWalkCoordinatese)=>
{
//ObtainourLatitudeandLongitude
//coordinates
Latitude=e.latitude;
Longitude=e.longitude;
};
}
//CallourServicetogetourGPSlocation
locationService.GetMyLocation();
}
Command_saveCommand;
publicCommandSaveCommand
{
get
{
return_saveCommand??(
_saveCommand=newCommand(async()=>
awaitExecuteSaveCommand(),ValidateFormDetails));
}
}
asyncTaskExecuteSaveCommand()
{
varnewWalkItem=newWalkEntries
{
Title=this.Title,
Notes=this.Notes,
Latitude=this.Latitude,
Longitude=this.Longitude,
Kilometers=this.Kilometers,
Difficulty=this.Difficulty,
Distance=this.Distance,
ImageUrl=this.ImageUrl
};
5. Then,welocateandmodifytheExecuteSaveCommandinstancemethodtofreethememoryusedbyourlocationServicevariablewhentheSavebuttonispressed.Thisisachievedbysettingthistonull,whichinturnwillcallthe~GetMyLocation()withintheiOSandAndroidclassde-constructor.Proceedtoenterthefollowinghighlightedcodesections:
//UponexitingourNewWalkEntryPage,
//weneedtostopcheckingforlocation
//updates
locationService=null;
//Here,wewillsavethedetailsentered
//inalaterchapter.
awaitNavService.PreviousPage();
}
//methodtocheckforanyformerrors
boolValidateFormDetails()
{
return!string.IsNullOrWhiteSpace(Title);
}
publicoverrideasyncTaskInit()
{
awaitTask.Factory.StartNew(()=>
{
Title="NewWalk";
Difficulty="Easy";
Distance=1.0;
});
}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingourWalkEntryViewModelsothatitcantakeadvantageofourWalkLocationService.
WethendeclaredalocationServicevariablethatwillbeusedtoprovideareferencetoourIWalkLocationServiceandprovideourclasswithareferencetotheEventHandler,whichiscontainedwithinourIWalkLocationServiceinterfaceclass;thiswillcontainourlocationcoordinateinformationwheneverthelocationchanges.
WealsomodifiedourWalkEntryViewModelclassconstructortoinitializeourlocationServicevariable,topointtotheIWalkLocationServiceconstructorthatisretrievedfromtheXamarin.FormsDependencyServiceclass,whichneedstobedonepriortocallingtheMyLocationmethodonourEventHandler,sothatitcanreturnthegeographicallocationcoordinates,definedbytheirLatitudeandLongitudevalues.
Finally,wesetourlocationServiceobjecttonulltostopcheckingforlocationupdates.
UpdatingtheDistanceTravelledViewModeltousethelocationserviceNowthatwehavemodifiedourMVVMViewModelforourWalkEntryViewModel,ournextstepistobeginmodifyingourDistTravelledViewModeltotakeadvantageofourWalkLocationServiceclass,thatwillbeusedtocalculatethedistancetravelled,andsavethisinformationbacktoourDistTravelledViewModel.
Let'stakealookathowwecanachievethisthroughthefollowingsteps:
1. EnsurethattheDistTravelledViewModel.csfileisdisplayedwithinthecodeeditor.
//
//DistTravelledViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Models;
usingTrackMyWalks.Services;
usingTrackMyWalks.ViewModels;
usingXamarin.Forms;
namespaceTrackMyWalks.ViewModels
{
publicclassDistTravelledViewModel:
WalkBaseViewModel<WalkEntries>
{
WalkEntries_walkEntry;
2. Next,wedeclaredalocationServicevariablethatwillbeusedtoprovideareferencetoourIWalkLocationServiceandprovideourclasswithareferencetotheEventHandler,whichiscontainedwithinourIWalkLocationServiceinterfaceclass;thiswillcontainourlocationcoordinateinformationwheneverthelocationchanges.Toproceed,enterthefollowinghighlightedcodesections:
IWalkLocationServicelocationService;
publicWalkEntriesWalkEntry
{
get{return_walkEntry;}
set
{
_walkEntry=value;
OnPropertyChanged();
}
}
double_travelled;
publicdoubleTravelled
{
get{return_travelled;}
set
{
_travelled=value;
OnPropertyChanged();
}
}
double_hours;
publicdoubleHours
{
get{return_hours;}
set
{
_hours=value;
OnPropertyChanged();
}
}
double_minutes;
publicdoubleMinutes
{
get{return_minutes;}
set
{
_minutes=value;
OnPropertyChanged();
}
}
double_seconds;
publicdoubleSeconds
{
get{return_seconds;}
set
{
_seconds=value;
OnPropertyChanged();
}
}
publicstringTimeTaken
{
get
{
returnstring.Format("{0:00}:{1:00}:{2:00}",
this.Hours,this.Minutes,this.Seconds);
}
}
3. Next,weneedtomodifytheDistTravelledViewModelclassconstructortodeclareandinitializeourlocationServicevariable;thiswillincludeourIWalkLocationServiceconstructor,whichisretrievedfromtheXamarin.FormsDependencyServiceclass.WethenproceedtocalltheMyLocationmethodonourEventHandler,whichisdefinedwithin
theIWalkLocationServiceinterface;thiswillreturnthegeographicallocationcoordinates,definedbytheirLatitudeandLongitudevalues.
4. LocatetheDistTravelledViewModelclassconstructorandenterthefollowinghighlightedcode:
publicDistTravelledViewModel(IWalkNavServicenavService):
base(navService)
{
this.Hours=0;
this.Minutes=0;
this.Seconds=0;
this.Travelled=100;
locationService=DependencyService.Get
<IWalkLocationService>();
locationService.MyLocation+=(objectsender,
IWalkCoordinatese)=>
{
//DetermineDistanceTravelled
if(_walkEntry!=null)
{
vardistance=locationService.GetDistanceTravelled(
_walkEntry.Latitude,_walkEntry.Longitude);
this.Travelled=distance;
}
};
locationService.GetMyLocation();
}
5. Then,wecreatetheInitmethodwithinourDistTravelledViewModel,whichwillbeusedtoinitializetheDistanceTravelledwhenitiscalled.WeneedtospecifyandusetheTask.Factory.StartNewmethodtogivetheViewModelenoughtimetodisplaythepageonscreen,priortoinitializingtheContentPagecontentsandusingthepassed-inwalkDetailsforourmodel:
publicoverrideasyncTaskInit(WalkEntrieswalkDetails)
{
awaitTask.Factory.StartNew(()=>
{
WalkEntry=walkDetails;
});
}
6. Next,weneedtocreatetheBackToMainPagecommandpropertythatwillbeusedtobindtotheEndThisTrailbutton,whichwillrunanactionuponbeingpressed.Thisactionwillexecuteaclassinstancemethodtodeterminewhetherthecommandcanbeexecuted.
7. Ifthecommandcanbeexecuted,acallwillbemadetotheBackToMainPagemethodontheNavServicenavigationserviceclasstotaketheuserbacktotheTrackMyWalksmainpage;thisisdonebyremovingallexistingViewModelswithintheNavigationStack,exceptthefirstpage:
Command_mainPage;
publicCommandBackToMainPage
{
get
{
return_mainPage??(_mainPage=newCommand(
async()=>await
NavService.BackToMainPage()));
}
}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingourDistanceTravelledViewModelsothatitcantakeadvantageofourWalkLocationService.WethendeclaredalocationServicevariablethatwillbeusedtoprovideareferencetoourIWalkLocationServiceandprovideourclasswithareferencetotheEventHandler,whichiscontainedwithinourIWalkLocationServiceinterfaceclass;thiswillcontainourlocationcoordinateinformationwheneverthelocationchanges.
WealsomodifiedourDistTravelledViewModelclassconstructortoinitializeourlocationServicevariabletopointtotheIWalkLocationServiceconstructorthatisretrievedfromtheXamarin.FormsDependencyServiceclass;thisneedstobedonepriortocallingtheMyLocationmethodonourEventHandler,sothatitcanreturnthegeographicallocationcoordinates,definedbytheirLatitudeandLongitudevalues.
UpdatingtheSplashPagetoregisterourViewModelsInthissection,weneedtoupdateourSplashPagetoregisterourViewModelsforourAndroidplatform;thiswillinvolvecreatinganewinstanceofthenavigationservice,andregisteringtheapplicationContentPageandViewModelmappings:
1. EnsurethattheSplashPage.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//SplashPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Services;
usingTrackMyWalks.ViewModels;
usingXamarin.Forms;
namespaceTrackMyWalks
{
publicclassSplashPage:ContentPage
{
publicSplashPage()
{
AbsoluteLayoutsplashLayout=newAbsoluteLayout
{
HeightRequest=600
};
varimage=newImage()
{
Source=ImageSource.FromFile("icon.png"),
Aspect=Aspect.AspectFill,
};
AbsoluteLayout.SetLayoutFlags(image,
AbsoluteLayoutFlags.All);
AbsoluteLayout.SetLayoutBounds(image,
newRectangle(0f,0f,1f,1f));
splashLayout.Children.Add(image);
Content=newStackLayout()
{
Children={splashLayout}
};
}
2. Next,locatetheOnAppearingmethodandenterthefollowinghighlightedcodesections:
protectedoverrideasyncvoidOnAppearing()
{
base.OnAppearing();
//Delayforafewsecondsonthesplashscreen
awaitTask.Delay(3000);
//InstantiateaNavigationPagewiththe
//MainPage
varnavPage=newNavigationPage(newWalksPage()
{
Title="TrackMyWalks-Android"
});
navPage.BarBackgroundColor=Color.FromHex("#4C5678");
navPage.BarTextColor=Color.White;
//DeclareourDependencyServiceInterface
varnavService=DependencyService.Get<IWalkNavService>()
asWalkNavService;
navService.navigation=navPage.Navigation;
//RegisterourViewModelMappingsbetween
//ourViewModelsandViews(Pages).
navService.
RegisterViewMapping(typeof(WalksPageViewModel),
typeof(WalksPage));
navService.RegisterViewMapping(
typeof(WalkEntryViewModel),
typeof(WalkEntryPage));
navService.RegisterViewMapping(
typeof(WalksTrailViewModel),
typeof(WalkTrailPage));
navService.RegisterViewMapping(
typeof(DistTravelledViewModel)
,
typeof(DistanceTravelledPage));
//SettheMainPagetobeourWalksNavigationPage
Application.Current.MainPage=navPage;
}
}
}
Intheprecedingcodesnippet,webeginbycustomizingourNavigationBar,bysettingtheBackgroundandTextColorattributes,andthendeclaringavariablenavServicethatpointsto
aninstanceofournavigationserviceasdefinedbyourassemblyattributeforourXamarin.FormsDependencyService,whichisdeclaredinourWalkNavServiceclass.
Inournextstep,wesetthenavService.navigationpropertytopointtoaninstanceoftheNavigationPageclassthatourwalksPage.navigationpropertycurrentlypointsto,andthiswillbeusedasthemainrootpage.
Finally,wecalltheRegisterViewMappinginstancemethodforeachofourViewModelsandspecifytheassociatedContentPageforeach.
UpdatingtheMainActivityclasstouseXamarin.Forms.MapsInthissection,weneedtoupdateourMainActivityClasstointegratewiththeXamarin.Forms.MapspackageforourAndroidplatform,sothatourViewModelscanusethistodisplaymappingcapabilities:
1. OpentheMainActivity.csfileandensurethatitisdisplayedwithinthecodeeditor.2. Next,locatetheOnCreatemethodandenterthefollowinghighlightedcodesections:
//
//MainActivity.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingAndroid.App;
usingAndroid.Content.PM;
usingAndroid.OS;
namespaceTrackMyWalks.Droid
{
[Activity(Label="TrackMyWalks.Droid",
Icon="@drawable/icon",Theme="@style/MyTheme",
MainLauncher=true,ConfigurationChanges=
ConfigChanges.ScreenSize|
ConfigChanges.Orientation)]
publicclassMainActivity:
global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protectedoverridevoidOnCreate(Bundle
savedInstanceState)
{
TabLayoutResource=Resource.Layout.Tabbar;
ToolbarResource=Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
global::Xamarin.Forms.Forms.Init(this,
savedInstanceState);
//IntegrateXamarinFormsMaps
Xamarin.FormsMaps.Init(this,savedInstanceState);
LoadApplication(newApp());
}
}
}
Intheprecedingcodesnippet,webeginbyinitializingourMainActivityclasstousetheXamarin.Forms.Mapslibrary,sothatourTrackMyWalkssolutioncanusethemaps.Ifthisisomittedfromtheclass,theDistanceTravelledPagecontentpagewillnotdisplaythemap,and
thereforewillnotworkasexpected.
UpdatingtheXamarin.FormsAppclasstouseplatformspecificsInthissection,weneedtoupdateourXamarin.Forms.AppclassbymodifyingtheconstructorinthemainAppclasstosettheMainPageinstance,dependingontheTargetPlatformthatourdeviceisrunning.ThisisextremelyeasywhenusingXamarin.Forms.
Let'stakealookathowwecanachievethisbyfollowingthesesteps:
1. OpentheTrackMyWalks.csfileandensurethatitisdisplayedwithinthecodeeditor.2. Next,locatetheAppmethodandenterthefollowinghighlightedcodesections:
//
//TrackMyWalks.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingTrackMyWalks.Services;
usingTrackMyWalks.ViewModels;
usingXamarin.Forms;
namespaceTrackMyWalks
{
publicclassApp:Application
{
publicApp()
{
//ChecktheDeviceTargetOSPlatform
if(Device.OS==TargetPlatform.Android)
{
//Therootpageofyourapplication
MainPage=newSplashPage();
}
elseif(Device.OS==TargetPlatform.iOS)
{
//Therootpageofyourapplication
varwalksPage=newNavigationPage(new
WalksPage()
{
Title="TrackMyWalks-iOS"
});
//SettheNavigationBarTextColorand
//BackgroundColor
walksPage.BarBackgroundColor=
Color.FromHex("#440099");
walksPage.BarTextColor=Color.White;
//DeclareourDependencyServiceInterface
varnavService=DependencyService.
Get<IWalkNavService>()asWalkNavService;
navService.navigation=walksPage.Navigation;
//RegisterourViewModelMappings
//betweenourViewModelsandViews(Pages)
navService.RegisterViewMapping(
typeof(WalksPageViewModel),typeof(WalksPage));
navService.RegisterViewMapping(
typeof(WalkEntryViewModel),
typeof(WalkEntryPage));
navService.RegisterViewMapping(
typeof(WalksTrailViewModel),
typeof(WalkTrailPage));
navService.RegisterViewMapping(typeof(
DistTravelledViewModel),
typeof(DistanceTravelledPage));
//SettheMainPagetobeour
//WalksNavigationPage
MainPage=walksPage;
}
}
protectedoverridevoidOnStart()
{
//Handlewhenyourappstarts
}
protectedoverridevoidOnSleep()
{
//Handlewhenyourappsleeps
}
protectedoverridevoidOnResume()
{
//Handlewhenyourappresumes
}
}
}
Intheprecedingcodesnippet,weusetheTargetPlatformclassthatcomesaspartoftheXamarin.Forms.Corelibrary,andwecheckthisagainsttheDevice.OSclassandhandleitaccordingly.
TheTargetPlatformmethodcontainsanumberplatformcodes,whichareexplainedalongwiththeirdescriptionsinthefollowingtable:
Platformname Description
Android ThisindicatesthattheXamarin.FormsplatformisrunningonadevicethatisrunningtheAndroidoperatingsystem.
iOS ThisindicatesthattheXamarin.FormsplatformisrunningonadevicethatisrunningtheAppleiOSoperatingsystem.
Windows ThisindicatesthattheXamarin.FormsplatformisrunningonadevicethatisrunningtheWindowsplatform.
WinPhone ThisindicatesthattheXamarin.FormsplatformisrunningonadevicethatisrunningtheMicrosoftWinPhoneOS.
Note
FormoreinformationontheDeviceclass,refertotheXamarindocumentationathttps://developer.xamarin.com/guides/xamarin-forms/platform-features/device/.
NowthatwehaveupdatedthenecessaryMVVMViewModelstotakeadvantageofourWalkLocationService,ournextstepistofinallybuildandruntheTrackMyWalksapplicationwithintheiOSsimulator.Whencompilationcompletes,theiOSsimulatorwillappearautomaticallyandtheTrackMyWalksapplicationwillbedisplayed,asshowninthefollowingscreenshot:
Asyoucanseefromtheprecedingscreenshot,thisdisplaysourcurrentlistofwalktrailentries,whicharedisplayedwithinourListView.WhentheuserclicksontheAddWalkbuttonlink,thiswilldisplaytheNewWalkEntrycontentpage,andwilldisplaythecurrentuser'sgeolocationcoordinatesfortheLatitudeandLongitudeEntryCellpropertiescontainedwithinourWalkEntryViewModel.Theprecedingscreenshot,thisshowsthedistancetravelledpagealongwiththeplaceholderpinmarkershowingthetraillocationwithinthemapView.YouwillnoticethattheDistanceTravelledsectionhasbeenupdatedandshowsthedistancetravelledbytheuserthatiscalculatedbytheGetDistanceTravelledmethodcontainedwithinourIWalkLocationServiceinterface.
SummaryInthischapter,weupdatedourTrackMyWalksapplication,andcreatedaLocationServiceclassthatextendedthedefaultnativecoreLocationServicesclassesforiOSandAndroid,whichprovidesuswithabettermethodofcapturinggeolocationcoordinateswithintheViewModel.
Inthenextchapter,you'lllearnaboutcustomrenderersandhowyoucanusethemtochangetheappearanceofthecontrolelementswithintheuserinterfacethattargetaspecificplatform.
YouwilllearnhowtoworkwithDataTemplatesbycreatingaC#classtolayoutyourviewsbeautifullythroughoutyourapplication,andworkwiththeplatform-specificAPIstoextendthedefaultbehaviorofXamarin.Formscontrolsthroughtheuseofcustomrenderers,bycreatingacustompickercontrolforiOS.
WewillalsobecoveringhowyoucanusetheXamarin.FormsEffectsAPItocustomizetheappearanceandstylingofnativecontrolelementsforeachplatform,byimplementingacustomrendererclass,andmanipulatethevisualappearanceofdatathatisbound,throughtheuseofValueandImageConverters.
Chapter5.CustomizingtheUserInterfaceInourpreviouschapter,welookedathowwecanincorporateplatform-specificfeatureswithintheTrackMyWalksapp,whichisdependentonthemobileplatform.YoulearnedhowtocreateaC#class,whichactedasalocationservicethatincludedanumberofclassmethodsforbothiOSandAndroidplatforms.
Wealsocoveredhowtoproperlyperformlocationupdateswhethertheapplication'sstateisintheforegroundorbackgroundbyregisteringtheappasabackground-necessaryapplication.
Inthischapter,you'lllearnhowtoworkwiththeDataTemplateCustomRendererbycreatingaC#classtolayoutyourviewsbeautifullywithinyourapplications,andyouwillalsogetaccustomedtoworkingwithplatform-specificAPIstoextendthedefaultbehaviorofXamarin.Forms'controlsthroughtheuseofcustomrenderers,bycreatingacustompicker.
WewillalsobecoveringhowtousetheXamarin.FormsEffectsAPItocustomizetheappearanceandstylingofnativecontrolelementsforeachplatform,byimplementingaCustomRendererclass.We'lllookathowtomanipulatethevisualappearanceofdatathatisbound,throughtheuseofvalueandimageconverters.
Thischapterwillcoverthefollowingpoints:
CreatingacustomDataTemplateclasswhichutilizesnativeplatformcapabilities,thatcomeaspartoftheiOSandAndroidplatformsWorkingwithcustomrendererstochangetheappearanceofcontrolelementsUsingtheplatformEffectsAPItochangetheappearanceofcontrolelementsWorkingwithBooleanandstringtoimagevalueconvertersUpdatingthewalkscontentpageapplicationtousethedatatemplateUpdatingtheWalkEntrycontentpagetousetheCustomRendererUpdatingtheDistanceTravelledcontentpagetousetheEffectsAPI
CreatingtheDataTemplateclassfortheTrackMyWalksappOneofthefeaturesoftheXamarin.Formstoolkitistheabilitytomanipulatetheuserinterfacebyleveragingthevariousplatform-specificAPIsthatareavailable,whetheritbemanipulatingtheappearanceofcontrolsandtheirelementsusingcustomrenderers,orchangingtheappearanceandstylingofnativecontrolelements.
Inthissection,wewillbeworkingwiththeXamarin.Formsdatatemplates,whichwillprovidetheabilitytodefinethepresentationofdata.Let'sbeginbycreatinganewfoldercalledDataTemplates,withinourTrackMyWalkssolution,whichwillbeusedtorepresentourDataTemplates,byfollowingthesesteps:
1. LaunchtheXamarinStudioapplication,andensurethattheTrackMyWalkssolutionisloadedwithintheXamarinStudioIDE.
2. Next,createanewfolder,withintheTrackMyWalksPortableClassLibraryproject,calledDataTemplatesasshowninthefollowingscreenshot:
3. Next,createanemptyclasswithintheDataTemplatesfolder.Ifyoucan'trememberhowtodothis,youcanrefertothesectionentitledCreatingtheNavigationServiceInterface
fortheTrackMyWalksapp,withinChapter3,NavigatingwithintheMVVMmodel-TheXamarin.FormsWay.
4. Then,choosetheEmptyClassoptionlocatedwithintheGeneralsection,andenterWalkCellDataTemplateasthenameofthenewclassfile,asshowninthefollowingscreenshot:
5. Next,clickontheNewbuttontoallowthewizardtocreatethenewemptyclassfile,asshownintheprecedingscreenshot.
6. OurnextstepistobegincreatingandimplementingthecodeforourWalkCellDataTemplateclass;performthefollowingsteps.
7. EnsurethattheWalkCellDataTemplate.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//WalkCellDataTemplate.cs
//TrackMyWalksDataTemplateforCells
//
//CreatedbyStevenF.Danielon01/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingTrackMyWalks.Converters;
usingXamarin.Forms;
namespaceTrackMyWalks.Controls
{
publicclassWalkCellDataTemplate:ViewCell
{
publicWalkCellDataTemplate()
{
varwalkTrailImage=newImage
{
WidthRequest=140,
HeightRequest=140,
HorizontalOptions=LayoutOptions.FillAndExpand,
VerticalOptions=LayoutOptions.FillAndExpand,
Aspect=Aspect.Fill
};
walkTrailImage.SetBinding(Image.SourceProperty,
"ImageUrl");
varTrailNameLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=16,
TextColor=Color.Black
};
TrailNameLabel.SetBinding(Label.TextProperty,
"Title");
vartotalKilometersLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=12,
TextColor=Color.FromHex("#666")
};
totalKilometersLabel.SetBinding(Label.TextProperty,
"Kilometers",stringFormat:"Kilometers:{0}");
vartrailDifficultyLabel=newLabel()
{
FontAttributes=FontAttributes.Bold,
FontSize=12,
TextColor=Color.Black
};
trailDifficultyLabel.SetBinding(Label.TextProperty,
"Difficulty",stringFormat:"Difficulty:{0}");
vartrailDifficultyImage=newImage
{
HeightRequest=50,
WidthRequest=50,
Aspect=Aspect.AspectFill,
HorizontalOptions=LayoutOptions.Start
};
trailDifficultyImage.SetBinding(Image.SourceProperty,
"Difficulty",converter:new
TrailImageConverter());
varnotesLabel=newLabel()
{
FontSize=12,
TextColor=Color.Black
};
notesLabel.SetBinding(Label.TextProperty,"Notes");
varnotesStack=newStackLayout()
{
Spacing=3,
Orientation=StackOrientation.Vertical,
VerticalOptions=LayoutOptions.FillAndExpand,
Children={notesLabel}
};
varstatusLayout=newStackLayout
{
Orientation=StackOrientation.Vertical,
Children={totalKilometersLabel,
trailDifficultyLabel,
trailDifficultyImage
}
};
varDetailsLayout=newStackLayout
{
Padding=newThickness(10,0,0,0),
Spacing=0,
HorizontalOptions=LayoutOptions.FillAndExpand,
Children={TrailNameLabel,statusLayout,
notesStack
}
};
varcellLayout=newStackLayout
{
Spacing=0,
Padding=newThickness(10,5,10,5),
Orientation=StackOrientation.Horizontal,
HorizontalOptions=LayoutOptions.FillAndExpand,
Children={walkTrailImage,DetailsLayout}
};
this.View=cellLayout;
}
}
}
Intheprecedingcodesnippet,webeganbyensuringthatourclassinheritsfromtheXamarin.FormsViewCellclassrenderer,andisessentiallyacellthatcanbeaddedtoanyListVieworTableViewcontrolthatcontainsadefinedview.WhenworkingwithXamarin.Forms,andtheViewCellclass,everycellhasanaccompanyingrendererthatisassociatedwitheachplatformthatcreatesaninstanceofanativecontrol.WheneveraViewCellclassisrenderedundertheiOSplatform,theViewCellRendererclasswillinstantiatethenativeUITableViewCellcontrol.Alternatively,undertheAndroidplatform,theViewCellRendererclassinstantiatesanativeViewcontrol.
Finally,ontheWindowsPhoneplatform,theViewCellRendererclassinstantiatesanativeDataTemplatecontrol.Next,wecreatethecelllayoutinformationusingtheStackLayoutcontrol,andthenusetheSetBindingpropertytocreateandbindeachofourmodelvaluestoaspecificproperty.Finally,wedefineacellLayoutvariablethatusestheStackLayoutcontrol,toaddeachofourchildelementsandthenassigntheresultingcellLayouttotheclassView.
Note
IfyouareinterestedinfindingoutmoreinformationaboutDataTemplates,pleaserefertotheXamarindeveloperdocumentationlocatedathttps://developer.xamarin.com/guides/xamarin-forms/templates/data-templates/.
NowthatwehavecreatedourWalkCellDataTemplate,thenextstepistomodifythewalksmainpagesothatitcanmakeuseofthisclass.
UpdatingthewalksmainpagetousethedatatemplateIntheprevioussection,wecreatedtheclassforourWalkCellDataTemplate,aswellasdefiningthelayoutinformationforeachofthecontrolelementsthatwewouldliketohavedisplayedwithinourView.
Inthissection,wewilltakealookathowtoimplementthenecessarycodechangessothattheWalksPageContentPagecantakeadvantageofourWalkCellDataTemplateclass.
Let'stakealookathowwecanachievethis,byfollowingthesteps:
1. EnsurethattheWalksPage.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesectionsasshowninthefollowingcodesnippet:
//
//WalksPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
usingTrackMyWalks.Services;
usingTrackMyWalks.DataTemplates;
namespaceTrackMyWalks
{
publicclassWalksPage:ContentPage
{
WalksPageViewModel_viewModel
{
get{returnBindingContextasWalksPageViewModel;}
}
publicWalksPage()
{
varnewWalkItem=newToolbarItem
{
Text="AddWalk"
};
...
...
...
//DefineourDataTemplateClass
varwalksList=newListView
{
HasUnevenRows=true,
ItemTemplate=newDataTemplate(typeof(
WalkCellDataTemplate)),
SeparatorColor=(Device.OS
==TargetPlatform.iOS)?
Color.Default:Color.Black
};
...
...
...
}
}
Intheprecedingcodesnippet,webeganbyincludingareferencetoourDataTemplatesclass,viatheusingstatement.ThenwepassedintheWalkCellDataTemplatedatatemplatetotheDataTemplateclassobject,whichwillbeassignedtotheItemTemplatepropertyoftheListViewclass.Next,dependingontheoperatingsystemwearerunningon,we'llsettheseparatorcolorforourTableView.
Asyoucanseefromtheprecedingscreenshot,thiswillshowyoualistofourcurrentwalkstrailentries,whicharenicelyrenderedusingtheDataTemplate,anddisplayedwithintheListViewcontrol.
Inournextsection,youwillseehowwecangoaboutcreatingacustompickerforourWalkEntrycontentpage,sothatwecandisplayalistofdifficultychoicesfortheusertochoosefrom.
CreatingaTableViewEntryCellcustompickerfortheiOSplatformOurTrackMyWalksappusesaTableViewwithEntryCellstopresentaformtotheusertoaddnewwalkentrieswithintheWalkEntryPage.Currently,thedifficultyfieldwithintheformisusingtheregularEntryCellcontrol,whichpresentstheuserwithaneditabletextfieldusingthedefaultkeyboard.
Asyoucanimagine,thisisnottheidealuserexperiencethatweareafter,asthiscancauseissueswhenitcomestovalidatingtheinformationentered.Ourgoalistopresenttheuserwithastandard,customplatform-specificpickerthatcontainsanumberofchoicestheusercanchoosefrom.
Inthissection,wewillbecreatingacustomrendererthatwillextendtheEntryCellRenderertodisplayanEntryCellthatwillbehavemuchlikethestandardpickercontrol.Sincewedon'twantourpickertorenderalloftheEntryCellswithintheWalkEntryPage,wewillneedtocreateacustomEntryCellcontrolthatthecustomrendererwillassociatedwith.
Let'stakealookathowwecanachievethis,byfollowingthesteps:
1. CreateanewfolderwithintheTrackMyWalksPortableClassLibraryproject,calledControlsandthencreateanemptyclasswithintheControlsfolder.
2. Next,choosetheEmptyClassoptionlocatedwithintheGeneralsection,andenterDifficultyPickerEntryCellasthenameofthenewclassfiletocreate.
3. Next,onceyouhavecreatedtheDifficultyPickerEntryCellclassfile,ensurethattheDifficultyPickerEntryCell.csfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
//
//DifficultyPickerEntryCell.cs
//TrackMyWalksCustomRendererforDifficultyEntryCells
//
//CreatedbyStevenF.Danielon01/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingXamarin.Forms;
namespaceTrackMyWalks.Controls
{
4. Then,weneedtomodifytheDifficultyPickerEntryCellclassconstructorsignature,sothatitinheritsfromtheXamarin.Forms.EntryEntryCellclass,sincetheWalkEntryPagecontainsanumberofEntryCellcontrols:
publicclassDifficultyPickerEntryCell:EntryCell{
5. Next,weneedtocreateastringBindablePropertysothatthecustomcontrolcanbedata-
boundjustlikeourothercontrols:
publicstaticreadonlyBindablePropertyDifficultyProperty=
BindableProperty.Create<DifficultyPickerEntryCell,
String>(p=>p.Difficulty,"Easy",propertyChanged:
newBindableProperty.BindingPropertyChangedDelegate<String>
(DifficultyPropertyChanged));
6. Then,wecreateaDifficultypropertysothat,whenthevalueschangeswithinthecustomcontrol,itcanreturnthevaluebacktoourEntryCell:
publicStringDifficulty
{
get{return(String)GetValue(DifficultyProperty);}
set{SetValue(DifficultyProperty,value);}
}
7. Next,wecreateaCompletedEventHandlerthatwillbeusedinrelationtotheDifficultyPropertyChangedevent,sowecanrespondtotheCompletedeventsonourDifficultyPickerEntryCell:
publicneweventEventHandlerCompleted;
staticvoidDifficultyPropertyChanged(BindableObjectbindable,
StringoldValue,StringnewValue)
{
var@this=(DifficultyPickerEntryCell)bindable;
if(@this.Completed!=null)
@this.Completed(bindable,newEventArgs());
}
}
}
NowthatwehavecreatedtheDifficultyPickerEntryCellclassfortheiOSportionofourTrackMyWalksapp,ournextstepistocreatethecustompickerrendererfortheiOSplatform,whichwewillbecoveringinthenextsection.
CreatingthecustompickerrendererclassfortheiOSplatformIntheprevioussection,wecreatedaclassfortheDifficultyPickerEntryCell,aswellasdefininganumberofdifferentdata-bindablepropertymethodsthatwillbeusedtohandletheuserchoosinganitemwithinourcustompicker.
Inthissection,wewillbuildthecustompickerrenderermodelthatwillbeusedbytheiOSportionoftheDifficultyPickerEntryCell.Let'stakealookathowwecanachievethis,byfollowingthesesteps:
1. CreateanewfolderwithintheTrackMyWalks.iOSproject,calledRenderers.2. Next,createanemptyclasswithintheRenderersfolderforourTrackMyWalks.iOS
project,andenterDifficultyPickerModelasthenameofthenewclassfiletocreate.3. Then,ensurethattheDifficultyPickerModel.csfileisdisplayedwithinthecodeeditor,
andenterinthefollowingcodesnippet:
//
//DifficultyPickerModel.cs
//TrackMyWalksLevelModelforUIPickerViewModel(iOS)
//
//CreatedbyStevenF.Danielon01/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingUIKit;
namespaceTrackMyWalks.iOS.Renderers
{
4. Next,weneedtomodifytheDifficultyPickerModelclassconstructorsignature,sothatitinheritsfromtheUIPickerViewModelinterfaceclass,aswellasdeclaringourdifficultyStringobjectwhichcontainsavalidlistofchoicesfortheusertochoosefrom:
//DeclareourDifficultyPickerModelClass
publicclassDifficultyPickerModel:UIPickerViewModel
{
//Defineourlistofdifficultylevels
staticpublicstring[]difficulty=newstring[]
{
"Easy",
"Moderate",
"Challenging",
"Difficult",
"VeryDifficult",
"Extreme"
};
5. Then,weneedtocreatetheGetComponentCountmethod,whichacceptsapickerView
objectthattellstheUIPickerViewhowmanycomponentsweareexpectingourcustompickertocontain:
publicoverridenintGetComponentCount(UIPickerViewpickerView)
{
return1;
}
6. Next,weneedtocreatetheGetRowsInComponentmethodthatacceptsapickerViewobjectandacomponentvalue.ThismethodworksouthowmanyrowstodisplaywithinourUIPickerViewcustomcontrol,whichisderivedfromthedifficultystringarray.Thecomponentparameterdetermineswhichsectiontodisplaythosevaluesin:
publicoverridenintGetRowsInComponent(UIPickerViewpickerView,
nintcomponent)
{
returndifficulty.Length;
}
7. Finally,weneedtocreatetheGetTitlemethod,whichacceptsapickerViewobject,arowparameter,andacomponentvalue.Thismethodisusedtodisplaythetitleinformationforeachrowcontainedwithinourdifficultyarray:
publicoverridestringGetTitle(UIPickerViewpickerView,
nintrow,nintcomponent)
{
returndifficulty[row];
}
}
}
Upuntilthispoint,allwehavedoneiscreateourmodelforourDifficultyPicker,whichactsasthebasemodelneededbyourDifficultyPickerCellRendererclass.OurnextstepistocreatetheDifficultyPickerCellRendererthatwillusethemodelfortheiOSplatformtodisplayacustomlistofentriesfortheusertochoosefrom.
1. CreateanemptyclasswithintheRenderersfolderfortheTrackMyWalks.iOSproject,andenterinDifficultyPickerCellRendererasthenameofthenewclassfiletocreate.
2. Next,ensurethattheDifficultyPickerCellRenderer.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//DifficultyPickerCellRenderer.cs
//TrackMyWalksCustomRendererforUIPickerViewEntryCells
(iOS)
//
//CreatedbyStevenF.Danielon01/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms.Platform.iOS;
usingUIKit;
usingTrackMyWalks.Controls;
usingXamarin.Forms;
usingTrackMyWalks.iOS.Renderers;
3. Then,weneedtoinitializeourDifficultyCellRendererclasstobemarkedasanExportRendererbyincludingtheExportRendererassemblyattributeatthetopofourclassdefinition.ThisletsourclassknowthatitinheritsfromtheViewRendererclass:
[assembly:ExportRenderer(typeof(DifficultyPickerEntryCell),
typeof(DifficultyPickerCellRenderer))]
namespaceTrackMyWalks.iOS.Renderers
{
4. Next,weneedtomodifytheDifficultyCellRendererclassconstructorsignature,sothatitcaninheritfromtheEntryCellRendererclass:
publicclassDifficultyPickerCellRenderer:EntryCellRenderer
{
5. Then,weneedtooverridetheEntryCellRendererGetCellmethodsothatitcanoverridethedefaultbehavioroftheEntryCellforiOSbysettingtheInputViewoftheUITextFieldtoaUIPickerViewclassinstance:
publicoverrideUITableViewCellGetCell(Cellitem,
UITableViewCellreusableCell,UITableViewtv)
{
varcell=base.GetCell(item,reusableCell,tv);
varentryPickerCell=(EntryCell)item;
UITextFieldtextField=null;
if(cell!=null)
textField=(UITextField)cell.ContentView.Subviews[0];
6. Next,wecreateaninstancetoouriOSUIPickerViewnativecontrol,thatpointstotheDifficultyPickerModel;andthenwecreateatoolbarthatwillcontainaDonebuttonandwillprovideuswithamechanismtoupdatetheEntryCellUITextFieldwiththechosenvaluefromthedifficultyPickerobject.Thendismissthecustompickercontrol:
//CreateouriOSUIPickerViewNativeControl
vardifficultyPicker=newUIPickerView
{
AutoresizingMask=UIViewAutoresizing.FlexibleWidth,
ShowSelectionIndicator=true,
Model=newDifficultyPickerModel(),
BackgroundColor=UIColor.White,
};
//Createatoolbarwithadonebuttonthatwill
//settheselectedvaluewhenclosed.
vardone=newUIBarButtonItem("Done",
UIBarButtonItemStyle.Done,(s,e)=>
{
//UpdatethevalueoftheUITextFieldwithin
//theCell.
if(textField!=null)
{
textField.Text=DifficultyPickerModel.difficulty
[difficultyPicker.SelectedRowInComponent(0)];
textField.ResignFirstResponder();
}
});
vartoolbar=newUIToolbar
{
BarStyle=UIBarStyle.BlackTranslucent,
Translucent=true
};
toolbar.SizeToFit();
toolbar.SetItems(new[]{done},true);
7. Then,wesettheinputviewandtoolbarandaninitialdefaultvaluefortheEntryCell'sTextFieldifnothinghasbeenchosen.ThisisdonebysettingtheInputViewoftheUITextFieldtoaUIPickerViewclassinstance:
//Settheinputview,toolbarandinitialvalue
//fortheCell'sUITextField.
if(textField!=null)
{
textField.InputView=difficultyPicker;
textField.InputAccessoryView=toolbar;
textField.Font=UIFont.FromName("Courier",16);
textField.BorderStyle=UITextBorderStyle.Bezel;
textField.TextColor=UIColor.Red;
8. Finally,ifwehaveselectedadifficultyvaluefromourUIPickerViewcontrol,wefirstneedtoensurethatwehavechosenavalue,andthenassignthisvaluetotheDifficultyEntryCellfieldwithintheentryform:
if(entryPickerCell!=null)
{
textField.Text=DifficultyPickerModel.difficulty
[difficultyPicker.SelectedRowInComponent(0)];
}
}
returncell;
}
}
}
Note
IfyouareinterestedinfindingoutmoreinformationabouttheUIPickerViewclass,pleaserefertotheXamarindeveloperdocumentationathttps://developer.xamarin.com/api/type/MonoTouch.UIKit.UIPickerView/.
NowthatwehavecreatedtheDifficultyPickerCellRendererclassfortheiOSportionofourTrackMyWalksapp,ournextstepistoimplementthiswithintheWalkEntrycontentpage.
UpdatingtheWalksEntryPagetousethecustompickerrendererIntheprevioussection,wecreatedtheclassforourDifficultyPickerCellRenderer,aswellasdefiningthevariousmethodsthatwillhandlethedisplayoftheUIPickerViewcontrolwhenanEntryCellwithintheViewModelhasbeentapped.
Inthissection,wewilltakealookathowtoimplementthecodechangesrequiredsothattheWalkEntryPagecontentpagecantakeadvantageoftheDifficultyPickerCellRendererclass.
Let'stakealookathowwecanachievethis,byfollowingthesesteps:
EnsurethattheWalkEntryPage.csfileisdisplayedwithinthecodeeditor,andenterthefollowinghighlightedcodesectionsasshowninthefollowingcodesnippet:
//
//WalkEntryPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Services;
usingTrackMyWalks.Controls;
namespaceTrackMyWalks
{
publicclassWalkEntryPage:ContentPage
{
WalkEntryViewModel_viewModel
{
get{returnBindingContextasWalkEntryViewModel;}
}
publicWalkEntryPage()
{
//SettheContentPageTitle
Title="NewWalkEntry";
//DeclareandinitializeourModelBindingContext
BindingContext=newWalkEntryViewModel
(DependencyService
.Get<IWalkNavService>());
...
...
...
varwalkDifficulty=newDifficultyPickerEntryCell
{
Label="DifficultyLevel:",
Placeholder="WalkDifficulty"
};
walkDifficulty.SetBinding(
DifficultyPickerEntryCell.DifficultyProperty,
"Difficulty",BindingMode.TwoWay);
...
...
...
}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingourWalkEntryPagetotakeadvantageoftheDifficultyPickerEntryCellclasscustomrenderer.WelookedatupdatingthewalkDifficultyobjectvariable,toreferencetheDifficultyPickerEntryCellclass,andupdatedthesetBindingtoreturnthevaluefromtheDifficultyPropertythatisimplementedwithintheDifficultyPickerEntryCellclass.
Asyoucanseefromtheprecedingscreenshot,thisshowsourcustomUIPickerViewcontrol,populatedwiththeentriesfromtheDifficultyPickerModel,aswellastheDonebuttondisplayedastheheader.ScrollingthroughthelistofchoicesoperatesinthesamewayasyouwouldexpectunderiOS;clickingontheDonebuttonwillpopulatetheDifficultyLevelUITextFieldwiththehighlightedchoicewithintheUIPickerViewcontrol.
Inournextsection,wewillfocusonhowwecanusetheXamarin.FormsEffectsAPItocustomizetheappearanceandstylingofnativecontrolelementsforboththeiOSandAndroidplatformsbyimplementingacustomrendererclass.Youwillnoticethattheimplementationsforbothoftheseclassesarequitesimilar.However,theseimplementdifferentmethods,asyouwillseeoncewestartimplementingthem.
CreatingPlatformEffectsusingtheEffectsAPIfortheiOSplatformInthissection,wewillbuildtheiOSportionofourPlatformEffects,whichwillallowustocustomizetheappearanceoftheXamarin.Formscontrolelements.Wewillbecreatingtwocompletelydifferentplatformeffects-LabelShadowandButtonShadow,forboththeiOSandAndroidplatforms.
Let'stakealookathowwecanachievethis,byfollowingthesesteps:
1. CreateanewfolderwithintheTrackMyWalks.iOSproject,calledPlatformEffects.2. Next,createanemptyclasswithinthePlatformEffectsfolderforourTrackMyWalks.iOS
project.3. Then,enterButtonShadowEffectasthenameofthenewclassfiletocreate,ensurethatthe
ButtonShadowEffect.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//ButtonShadowEffect.cs
//TrackMyWalksButtonShadowEffect(iOS)
//
//CreatedbyStevenF.Danielon02/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingTrackMyWalks.iOS.PlatformEffects;
usingUIKit;
usingXamarin.Forms;
usingXamarin.Forms.Platform.iOS;
4. Next,weinitializetheButtonShadowEffectclassassemblytobemarkedwithtwoimportantattributesforourclasssothatitcanbeusedasaneffectwithintheTrackMyWalksapplication:
[assembly:ResolutionGroupName("com.geniesoftstudios")]
[assembly:ExportEffect(typeof(ButtonShadowEffect),
"ButtonShadowEffect")]
namespaceTrackMyWalks.iOS.PlatformEffects
{
5. Then,weneedtomodifyourButtonShadowEffectclassconstructorsothatitcaninheritfromthePlatformEffectclassandaccesseachoftheplatform-specificimplementationsofthePlatformEffectclass:
publicclassButtonShadowEffect:PlatformEffect
{
6. Next,wecreatetheOnAttachedmethodthatwillbecalledwheneveranaffectisattachedtoaXamarin.FormscontrolandthenusetheContainerpropertytoreferencetheplatform-specificcontrolthatisusedtoimplementthelayout:
protectedoverridevoidOnAttached()
{
Container.Layer.ShadowOpacity=0.5f;
Container.Layer.ShadowColor=UIColor.Black.CGColor;
Container.Layer.ShadowRadius=2;
}
7. Then,wecreatetheOnDetachedmethod,whichwillbecalledwheneveraneffectisdetachedfromaXamarin.Formscontroltoperformanyeffectclean-up.Here,inthismethod,wesettheShadowOpacityoftheContainerpropertytozero:
protectedoverridevoidOnDetached()
{
Container.Layer.ShadowOpacity=0;
}
}
}
Eachplatform-specificPlatformEffectclassexposesanumberofpropertiesandtheseareexplainedinthefollowingtable:
Platformeffect Description
ContainerThisparticulartypereferencestheplatform-specificcontrolthatisbeingusedtoimplementthelayout.
ControlThistypereferencestheplatform-specificcontrolthatisbeingusedtoimplementtheXamarin.Formscontrol.
Element ThistypereferencestheXamarin.Formscontrolthatiscurrentlybeingrendered.
WheneveryoucreateyourownPlatformEffects,theseinheritfromthePlatformEffectclass,whichisdependentontheplatformthatisbeingrun.However,theAPIforaneffectisprettymuchidenticalacrosseachoftheplatformsastheyderivefromthePlatformEffect<T,T>andcontaindifferentgenericparameters.TherearealsotwoveryimportantattributesthatyouneedtosetforeachclassthatsubclassesfromthePlatformEffectclass,andtheseareexplainedinthefollowingtable:
Attributetype Description
ResolutionGroupName
Thisattributesetsacompany-widenamespaceforeffects,preventingcollisionswithothereffectswiththesamename.Itisworthmentioning
that,ifyoucreatemultiplePlatformEffects,youcanonlyapplythisattributeonceperproject.
ExportEffect
ThisattributeregisterstheeffectusingauniqueIDandisusedbytheXamarin.Formsplatformalongwiththegroupname.Theattributetakestwoparameters:thetypenameoftheeffect,andauniquestringthatwillbeusedtolocatetheeffectpriortoapplyingittoacontrol.
Note
IfyouareinterestedinfindingoutmoreinformationaboutthePlatFormEffectclass,pleaserefertotheXamarindeveloperdocumentationathttps://developer.xamarin.com/guides/xamarin-forms/effects/.
NowthatwehavecreatedtheButtonShadowEffectclass,ournextstepistocreatetheLabelShadowEffectfortheiOSportionofourTrackMyWalksapp:
1. Next,createanemptyclasswithinthePlatformEffectsfolderfortheTrackMyWalks.iOSprojectandenterLabelShadowEffectasthenameofthenewclassfiletocreate.
2. Then,ensurethattheLabelShadowEffect.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//LabelShadowEffect.cs
//TrackMyWalksLabelShadowEffect(iOS)
//
//CreatedbyStevenF.Danielon02/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingCoreGraphics;
usingTrackMyWalks.iOS.PlatformEffects;
usingXamarin.Forms;
usingXamarin.Forms.Platform.iOS;
3. Next,weinitializetheLabelShadowEffectclassassemblytobemarkedwiththeExportEffectattributesothatitcanbeusedasaneffectwithintheTrackMyWalksapplication:
[assembly:ExportEffect(typeof(LabelShadowEffect),
"LabelShadowEffect")]
namespaceTrackMyWalks.iOS.PlatformEffects
{
4. Then,weneedtomodifytheLabelShadowEffectclassconstructorsothatitcaninheritfromthePlatformEffectclass,andaccesseachoftheplatform-specificimplementationsofthePlatformEffectclass:
publicclassLabelShadowEffect:PlatformEffect{
5. Next,wecreatetheOnAttachedmethodthatwillbecalledwheneveranaffectisattachedtoaXamarin.FormscontrolandthenusetheControlpropertytoreferencetheplatform-specificcontrolthatwillbeusedtochangetheappearanceofthecontrol.
protectedoverridevoidOnAttached()
{
try
{
Control.Layer.CornerRadius=5;
Control.Layer.ShadowColor=Device.OnPlatform(
Color.Black,Color.White,Color.Black).ToCGColor();
Control.Layer.ShadowOffset=newCGSize(4,4);
Control.Layer.ShadowOpacity=0.5f;
}
catch(Exceptionex)
{
Console.WriteLine("Cannotsetpropertyonattached
control.Error:",ex.Message);
}
}
6. Then,wecreatetheOnDetachedmethod,whichwillbecalledwheneveraneffectisdetachedfromaXamarin.Formscontroltoperformanyeffectcleanup.Here,inthismethod,wedon'tneedtodoanything,butwestillneedtoimplementthistoconformwiththePlatformEffectclassprotocolimplementations:
protectedoverridevoidOnDetached()
{
}
}
}
NowthatwehavecreatedthePlatformEffectsfortheiOSplatform,weneedtoimplementthesamePlatformEffectsfortheAndroidplatform,whichwewillbecoveringinthenextsection.
CreatingPlatformEffectsusingtheEffectsAPIfortheAndroidplatformInthissection,wewillbuildtheAndroidportionofourPlatformEffectsthatwillallowustocustomizetheappearanceofXamarin.FormscontrolelementsjustlikewedidfortheiOSportion,andwewillbeimplementingthesamePlatformEffects--LabelShadowandButtonShadowtoshowyouhowtheseimplementationsdifferoneachplatform,eventhoughtheresultingrenderingisthesame.
Let'stakealookathowwecanachievethis,byfollowingthesteps:
1. CreateanewfolderwithintheTrackMyWalks.Droidproject,calledPlatformEffects.2. Next,createanemptyclasswithinthePlatformEffectsfolderforour
TrackMyWalks.Droidproject.3. Then,enterButtonShadowEffectasthenameofthenewclassfiletocreate,ensurethatthe
ButtonShadowEffect.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//ButtonShadowEffect.cs
//TrackMyWalksButtonShadowEffect(Droid)
//
//CreatedbyStevenF.Danielon02/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingTrackMyWalks.Droid.PlatformEffects;
usingXamarin.Forms;
usingXamarin.Forms.Platform.Android;
usingSystem;
4. Next,weinitializetheButtonShadowEffectclassassemblytobemarkedwiththesametwoattributesforourclass,justlikewedidfortheiOSportion,sothatitcanbeusedasaneffectwithintheTrackMyWalksapplication:
[assembly:ResolutionGroupName("com.geniesoftstudios")]
[assembly:ExportEffect(typeof(ButtonShadowEffect),
"ButtonShadowEffect")]
namespaceTrackMyWalks.Droid.PlatformEffects
{
5. Then,weneedtomodifytheButtonShadowEffectclassconstructorsothatitcaninheritfromthePlatformEffectclass,andaccesseachoftheplatform-specificimplementationsofthePlatformEffectclass:
publicclassButtonShadowEffect:PlatformEffect
{
6. Next,wecreatetheOnAttachedmethodthatwillbecalledwheneveranaffectisattachedtoaXamarin.FormscontrolandthenusetheControlpropertytoreferencetheplatform-specificcontrolthatwillbeusedtochangetheappearanceofthecontrol.UnderAndroidwe
needtocreateacontrolobjectandconverttheButtonintoaControlobject,andthenapplythecustomizationstotheControl.Wewrapthiswithinatry...catch()block,tocatchanyerrorsthatmayoccurif,forsomereason,wecan'tapplythecolororshadowLayerforourcontrol:
protectedoverridevoidOnAttached()
{
try
{
varcontrol=ControlasAndroid.Widget.Button;
Android.Graphics.Colorcolor=
Android.Graphics.Color.Red;
control.SetShadowLayer(12,4,4,color);
}
catch(Exceptionex)
{
Console.WriteLine("Cannotsetpropertyonattached
control.Error:",ex.Message);
}
}
7. Then,wecreatetheOnDetachedmethod,whichwillbecalledwheneveraneffectisdetachedfromaXamarin.Formscontroltoperformanyeffectcleanup.Hereinthismethod,wedon'tneedtodoanything,butwestillneedtoimplementthistoconformwiththePlatformEffectclassprotocolimplementations:
protectedoverridevoidOnDetached()
{
thrownewNotImplementedException();
}
}
}
NowthatwehavecreatedtheButtonShadowEffectclass,ournextstepistocreatetheLabelShadowEffectfortheAndroidportionofourTrackMyWalksapp:
1. Next,createanemptyclasswithinthePlatformEffectsfolderfortheTrackMyWalks.DroidprojectandenterLabelShadowEffectasthenameofthenewclassfiletocreate.
2. Then,ensurethattheLabelShadowEffect.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//LabelShadowEffect.cs
//TrackMyWalksLabelShadowEffect(Droid)
//
//CreatedbyStevenF.Danielon02/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingTrackMyWalks.Droid.PlatformEffects;
usingXamarin.Forms;
usingXamarin.Forms.Platform.Android;
3. Next,weinitializeourLabelShadowEffectclassassemblytobemarkedwiththeExportEffectattributesothatitcanbeusedasaneffectwithintheTrackMyWalksapplication:
[assembly:ExportEffect(typeof(LabelShadowEffect),
"LabelShadowEffect")]
namespaceTrackMyWalks.Droid.PlatformEffects
{
4. Then,weneedtomodifytheLabelShadowEffectclassconstructorsothatitcaninheritfromthePlatformEffectclass,andaccesseachoftheplatform-specificimplementationsofthePlatformEffectclass:
publicclassLabelShadowEffect:PlatformEffect
{
5. Next,wecreatetheOnAttachedmethodthatwillbecalledwheneveranaffectisattachedtoaXamarin.FormscontrolandthenusetheControlpropertytoreferencetheplatform-specificcontrolthatwillbeusedtochangetheappearanceofthecontrol.InAndroidweneedtocreateacontrolobject,converttheTextViewintoaControlobject,andthenapplythecustomizationstotheControl.Wewrapthiswithinatry...catch()block,tocatchanyerrorsthatmayoccurif,forsomereason,wecan'tapplythecolororshadowLayerforourcontrol:
protectedoverridevoidOnAttached()
{
try
{
varcontrol=ControlasAndroid.Widget.TextView;
floatradius=5;
floatdistanceX=4;
floatdistanceY=4;
Android.Graphics.Colorcolor=Device.OnPlatform(
Color.Black,Color.White,Color.Black).ToAndroid();
control.SetShadowLayer(
radius,distanceX,distanceY,color);
}
catch(Exceptionex)
{
Console.WriteLine("Cannotsetpropertyonattached
control.
Error:",ex.Message);
}
}
6. Then,wecreatetheOnDetachedmethodthatwillbecalledwheneveraneffectisdetachedfromaXamarin.Formscontroltoperformanyeffectcleanupinthismethod.AswhatwedidintheiOSimplementation,wedon'tneedtodoanything,butwestillneedtoimplementthistoconformwiththePlatformEffectclassprotocolimplementations:
protectedoverridevoidOnDetached()
{
}
}
}
NowthatwehavecreatedPlatformEffectsforboththeiOSandAndroidimplementations,ournextstepistobegincreatingtwovalueconvertersthatwillbeusedbyourapplication,beforewecanstartmodifyingthecontentpages,andthiswillbecoveredinthenextsection.
ImplementingvalueconverterswithintheTrackMyWalksappAsmentionedintheprevioussection,valueconvertersformanimportantconceptindatabindingastheyallowyoutocustomizetheappearanceofadatapropertyatthetimeitisbound.ThisprocessisquitesimilartoWPF(WindowsPresentationFoundation)ontheWindowsapplicationdevelopmentplatform.Xamarin.FormsprovidesyouwithanumberofvalueconverterinterfacesaspartofitsAPI.
ValueconvertersareextremelyhelpfulwhenworkingwiththeXamarin.Formsplatform,astheyallowyoutotogglethevisibilityofelements,basedonaBooleanproperty.
Inthissection,wewillcreateaBooleanConverterthatwewillusetohidecontrolsuntiltheViewModelhascompletelyfinishedloading.WewillalsocreateaconverterthatconvertsastringvalueintoaURLpropertythatwillbeusedtodisplayanimageforourdifficultyrating.
Let'stakealookathowwecanachievethis,byfollowingthesteps:
1. Createanewfolder,withintheTrackMyWalksPortableClassLibraryproject,calledValueConvertersandthencreateanemptyclasswithintheValueConvertersfolder.
2. Next,choosetheEmptyClassoptionlocatedwithintheGeneralsection,andenterBooleanConverterasthenameofthenewclassfiletocreate.
3. Next,ensurethattheBooleanConverter.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//BooleanConverter.cs
//TrackMyWalksValueConverterforconvertingBooleanvalues
//
//CreatedbyStevenF.Danielon02/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingXamarin.Forms;
namespaceTrackMyWalks.ValueConverters
{
4. Then,weneedtomodifytheBooleanConverterclassconstructorsothatitcaninheritfromtheIValueConverterclass:
publicclassBooleanConverter:IValueConverter
{
5. Next,createtheConvertandConvertBackmethodsoftheIValueConverterclass,sothattheconverterwillreturntheoppositeofagivenBooleanvalue:
publicobjectConvert(objectvalue,TypetargetType,
objectparameter,System.Globalization.CultureInfoculture)
{
if(!(valueisBoolean))
returnvalue;
return!((Boolean)value);
}
publicobjectConvertBack(objectvalue,TypetargetType,
objectparameter,System.Globalization.CultureInfoculture)
{
if(!(valueisBoolean))
returnvalue;
return!((Boolean)value);
}
}
}
Intheprecedingcodesnippet,webeganbymodifyingtheBooleanConverterclassconstructorsothatitcaninheritfromtheIValueConverterclass.ThenweproceededtocreatetheConvertandConvertBackmethodsoftheIValueConverterclass.ThisissothattheconverterwillreturntheoppositeofagivenBooleanvalue;forexample,ifthevalueisTrue,itwillreturnFalse,andconvertitfromFalsebacktoTrue.
NowthatwehavecreatedtheBooleanConverterclass,ournextstepistobegincreatingtheTrailImageConverterthatwillbeusedbytheTrackMyWalksapp.ThisclasswillbeusedtoconvertastringvalueintoaURLpropertythatwillbeusedtodisplayanimageforourdifficultyrating:
1. CreateanemptyclasswithintheValueConvertersfolder,whichislocatedwithintheTrackMyWalksPortableClassLibraryproject.
2. Next,choosetheEmptyClassoptionlocatedwithintheGeneralsection,andenterTrailImageConverterasthenameofthenewclassfiletocreate.
3. Next,ensurethattheTrailImageConverter.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//TrailImageConverter.cs
//TrackMyWalksValueConverterforconvertingdifficultyvalue
//toanimage.
//
//CreatedbyStevenF.Danielon02/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingXamarin.Forms;
namespaceTrackMyWalks.ValueConverters
{
4. Then,weneedtomodifyourTrailImageConverterclassconstructorsothatitcaninheritfromtheIValueConverterclass:
publicclassTrailImageConverter:IValueConverter
{
5. Next,createtheConvertandConvertBackmethodsoftheIValueConverterclass,sothattheconverterwillreturnbackanimageURLbasedonthedifficultylevel:
publicobjectConvert(objectvalue,TypetargetType,
objectparameter,System.Globalization.CultureInfoculture)
{
//Returnbacktherelevantimagebasedonthe
//difficultylevel.
switch((string)value)
{
case"Easy":
return"http://www.yourdomain.com/g1.jpeg";
case"Moderate":
return"http://www.yourdomain.com/g2.jpeg";
case"Challenging":
case"Difficult":
return"http://www.yourdomain.com/g3.jpeg";
case"VeryDifficult":
case"Extreme":
return"http://www.yourdomain.com/g5.jpeg";
default:
return"http://www.yourdomain.com/g1.jpeg";
}
}
publicobjectConvertBack(objectvalue,TypetargetType,
objectparameter,System.Globalization.CultureInfoculture)
{
thrownewNotImplementedException();
}
}
}
Intheprecedingcodesnippet,webeganbymodifyingourTrailImageConverterclassconstructorsothatitcaninheritfromtheIValueConverterclass.Inournextstep,we'llproceedtocreatetheConvertandConvertBackprotocolmethodsoftheIValueConverterclass.ThisissothattheconverterwillreturnbackanimageURLbasedonthedifficultylevelandwillbedisplayedwithinourTrackMyWalksapp.
UpdatingtheWalkBaseViewModeltouseourBooleanconverterInthissection,wewillproceedtoupdateourWalkBaseViewModelclasstoincludereferencestoourBooleanvalueconverter.SincetheWalkBaseViewModelalreadyinheritsandisusedbyeachoftheViewModels,itmakessensetoplaceitwithinthisclass.Thatway,ifweneedtoaddadditionalmethods,wecanjustaddthemwithinthisclass.Toproceed,performthefollowingsteps,asshownhere.
EnsurethattheWalkBaseViewModel.csfileisdisplayedwithinthecodeeditor,andenterthefollowinghighlightedcodesections,asshowninthefollowingcodesnippet:
//
//WalkBaseViewModel.cs
//TrackMyWalksBaseViewModel
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.ComponentModel;
usingSystem.Runtime.CompilerServices;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Services;
namespaceTrackMyWalks.ViewModels
{
publicabstractclassWalkBaseViewModel:
INotifyPropertyChanged
{
protectedIWalkNavServiceNavService
{get;privateset;}
bool_isProcessBusy;
publicboolIsProcessBusy
{
get{return_isProcessBusy;}
set
{
_isProcessBusy=value;
OnPropertyChanged();
OnIsBusyChanged();
}
}
...
...
...
protectedvirtualvoidOnIsBusyChanged()
{
//WeareprocessingourWalksTrailInformation
}
}
publicabstractclassWalkBaseViewModel<WalkParam>:
WalkBaseViewModel
{
protectedWalkBaseViewModel(
IWalkNavServicenavService):
base(navService)
{
}
publicoverrideasyncTaskInit()
{
awaitInit(default(WalkParam));
}
publicabstractTaskInit(WalkParamwalkDetails);
}
}
Intheprecedingcodesnippet,webeganbycreatingaBooleanproperty,calledisProcessBusy,whichwewillonlysettoTruewhileweareintheprocessofactuallyloadingdatawithinourListView,ordoingsomeotherprocessthattakesquitealongtime.TheIsProcessBusypropertycontainsboththegetter(get)andsetter(set)implementations.WhenwesettheIsProcessBusyproperty,weassignthisvaluetoour_isProcessBusyvariable,andthencalltheOnPropertyChangedandOnIsBusyChangedinstancemethodstotelltheViewModelsthatachangehasbeenmade.
UpdatingtheWalksPageViewModeltouseourBooleanconverterInthissection,wewillproceedtoupdatetheWalksPageViewModelViewModeltoreferenceourBooleanvalueconverter.SincetheWalksPageViewModelisusedtodisplayinformationfromtheWalkEntriesmodel,wewillneedtoupdatetheLoadWalksinstancemethodtotogglebetweentheIsProcessBusyvaluewhileitisloadingdatawithintheListView.Toproceed,performthefollowingsteps,asshownhere:
EnsurethattheWalksPageViewModel.csfileisdisplayedwithinthecodeeditor,andenterthefollowinghighlightedcodesections,asshowninthefollowingcodesnippet:
//
//WalksPageViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.Collections.ObjectModel;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Models;
usingTrackMyWalks.Services;
usingXamarin.Forms;
namespaceTrackMyWalks.ViewModels
{
publicclassWalksPageViewModel:WalkBaseViewModel
{
ObservableCollection<WalkEntries>_walkEntries;
publicObservableCollection<WalkEntries>walkEntries
{
get{return_walkEntries;}
set
{
_walkEntries=value;
OnPropertyChanged();
}
}
publicWalksPageViewModel(IWalkNavServicenavService):
base(navService)
{
walkEntries=newObservableCollection<WalkEntries>();
}
publicoverrideasyncTaskInit()
{
awaitLoadWalkDetails();
}
publicasyncTaskLoadWalkDetails()
{
//Checktoseeifwearealreadyprocessingour
//WalkTrailItems
if(IsProcessBusy){
return;
}
//Ifwearen'tcurrentlyprocessing,weneedto
//initialiseourvariabletotrue.
IsProcessBusy=true;
//Addatemporarytimer,sothatwecansee
//ourprogressindicatorworking
awaitTask.Delay(1000);
...
...
...
//Re-initialiseourprocessbusyvaluebacktofalse
IsProcessBusy=false;
}
...
...
...
}
}
Intheprecedingcodesnippet,webeganbyupdatingtheLoadWalksmethodtotogglebetweentheBooleanpropertycalledIsProcessBusy.WesetthistoTruewhileweareintheprocessofactuallyloadingdatawithintheListView.Wethenaddedatemporarytimersowecanseethisprocessinaction,butwewillberemovingthisinChapter7,IncorporatingAPIDataAccess,whenweloadtheTrailinformationfromanAPI.Finally,were-initializedourIsProcessBusystatevaluebysettingthistoFalsetotelltheWalkBaseViewModelthatwehavecompletedprocessingofthewalktrailitems.
NowthattheViewModelisawareofwhenitisbusilyprocessingitemswithintheListView,ournextstepistomodifytheuserinterfaceforthewalkspagetoincludeanactivityindicatorthatinheritsfromtheXamarin.Forms.Coreplatform.WewillalsobemodifyingourDistanceTravelledandWalksTrailPagetoincludethePlatFormEffectsclasses.
UpdatingthewalksmainpagetousetheupdatedViewModelInthissection,wewillproceedtoupdatetheWalksPagecontentpage,whichwillincludeanactivityprocessindicatorthatwilldisplayatextstringtotheuser,lettingthemknowthatthetrailwalksarebeingpopulatedwithintheListViewcontrol.WewillalsobemakinguseofourPlatformEffectsclassesfortheLabelShadow.Toproceed,performthefollowingsteps:
1. EnsurethattheWalksPage.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections,asshowninthefollowingcodesnippet:
//
//WalksPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
usingTrackMyWalks.Services;
usingTrackMyWalks.DataTemplates;
usingTrackMyWalks.ValueConverters;
namespaceTrackMyWalks
{
publicclassWalksPage:ContentPage{
WalksPageViewModel_viewModel
{
get{
returnBindingContextasWalksPageViewModel;}
}
publicWalksPage()
{
varnewWalkItem=newToolbarItem
{
Text="AddWalk"
};
...
...
...
//DeclareandinitializeourModelBindingContext
BindingContext=newWalksPageViewModel(DependencyService
.Get<IWalkNavService>());
//DefineourItemTemplate
varwalksList=newListView
{
HasUnevenRows=true,
ItemTemplate=newDataTemplate(typeof(
WalkCellDataTemplate)),
SeparatorColor=(Device.OS==TargetPlatform.iOS)?
Color.Default:Color.Black
};
//SettheBindingpropertyforourwalksEntries
walksList.SetBinding(ItemsView<Cell>.ItemsSourceProperty,
"walkEntries");
2. Next,weneedtosetuptheBindingtotheIsVisiblePropertythatwedefinedwithintheWalkBaseViewModelclass.ThiswillbecalledwhenthispropertyhasbeensetTruetodisplayourWalkEntrieswithintheListViewandishandledbytheIsProcessBusyproperty.Proceedandenterthefollowinghighlightedcodesections,asshowninthecodesnippet:
walksList.SetBinding(ItemsView<Cell>.IsVisibleProperty,
"IsProcessBusy",converter:newBooleanConverter());
//InitializeoureventHandlertousewhentheitem
//istapped
walksList.ItemTapped+=(objectsender,
ItemTappedEventArgse)=>
{
varitem=(WalkEntries)e.Item;
if(item==null)return;
_viewModel.WalkTrailDetails.Execute(item);
item=null;
};
3. Then,wedeclareandinitializeanewprogressLabelcontrol,whichwillbeusedtodisplayinstructiveinformationtotheuserwhentheListViewisbeingpopulated.Proceedandenterinthefollowinghighlightedcodesections,asshowninthecodesnippet:
//DeclareourProgressLabel
varprogressLabel=newLabel()
{
FontSize=14,
FontAttributes=FontAttributes.Bold,
TextColor=Color.Black,
HorizontalTextAlignment=TextAlignment.Center,
Text="LoadingTrailWalks..."
};
4. Next,weapplythePlatformEffectLabelShadowEffectclasstoourprogressLabelcontrol,instantiateandinitializetheActivityIndicatorclass,andsettheIsRunningpropertytoTrue,beforecreatingaStackLayoutvariableprogressIndicatorandaddingboththeactivityIndicatorandprogressLabelitems.Finally,wesetuptheBindingforourProgressIndicatorusingtheisVisiblePropertyoftheStackLayoutcontrol.ThiswillusetheIsProcessBusypropertytodeterminewhetherornottoshowtheActivityIndicator.Proceedandenterinthefollowinghighlightedcodesections,asshowninthecodesnippet:
//ApplyPlatformEffectstoourProgressLabel
progressLabel.Effects.Add(Effect.Resolve("com.geniesoftst
udios.LabelShadowEffect"));
//InstantiateandinitialiseourActivityIndicator.
varactivityIndicator=newActivityIndicator()
{
IsRunning=true
};
varprogressIndicator=newStackLayout
{
Orientation=StackOrientation.Vertical,
HorizontalOptions=LayoutOptions.CenterAndExpand,
VerticalOptions=LayoutOptions.CenterAndExpand,
Children={
activityIndicator,
progressLabel
}
};
progressIndicator.SetBinding(StackLayout.IsVisibleProperty,
"IsProcessBusy");
varmainLayout=newStackLayout
{
Children=
{
walksList,
progressIndicator
}
};
Content=mainLayout;
}
protectedoverrideasyncvoidOnAppearing()
{
base.OnAppearing();
//InitializeourWalksPageViewModel
if(_viewModel!=null)
await_viewModel.Init();
}
}
}
Intheprecedingcodesnippet,webeganbysettinguptheBindingtoourIsVisibleProperty,whichwedefinedwithintheWalkBaseViewModelclass;thiswillbecalledwhenthepropertyhasbeensettoTruetodisplaytheWalkEntrieswithintheListView,andishandledbytheIsProcessBusyproperty.Inournextstep,wedeclaredandinitializedanewprogressLabelcontrol,whichwillbeusedtodisplayinstructiveinformationtotheuserwhentheListViewisbeingpopulated.
ThenweappliedtheLabelShadowEffectclasstotheprogressLabelcontrol,instantiatedandinitializedtheActivityIndicatorclass,andsettheIsRunningpropertytoTrue,beforecreatingaStackLayoutvariableprogressIndicatorandaddingboththeactivityIndicatorandprogressLabelitems.
Next,wesetuptheBindingfortheProgressIndicatorusingtheisVisiblePropertyofourStackLayoutcontrol,whichwillusetheIsProcessBusypropertytodeterminewhetherornottoshowtheActivityIndicator.Finally,weaddedtheProgressIndicatortoourmainLayoutaspartoftheChildrenproperty,sothatthiscanbedisplayedaspartofthemaincontentpage.
Tip
Ifyoudon'texportanEffectforaparticularplatform,theEffect.Resolvewillreturnanon-nullvaluethateffectivelydoesn'tdoanything,andyouwon'tseeanyrenderinghappenonyourcontrols.Youwillneedtoensurethat,withinyourPlatformEffectclasses,youincludetheExportEffectassemblyattributeatthetopofyourclassimplementations.
UpdatingtheWalksTrailPagetousetheupdatedViewModelInthissection,wewillproceedtoupdateourWalksTrailPagecontentpagewillmakeuseofthePlatformEffectsclassesfortheButtonShadow.Toproceed,performthefollowingsteps:
EnsurethattheWalksTrailPage.csfileisdisplayedwithinthecodeeditor,andenterthefollowinghighlightedcodesections,asshowninthecodesnippet:
//
//WalkTrailPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.ViewModels;
usingTrackMyWalks.Services;
usingTrackMyWalks.ValueConverters;
namespaceTrackMyWalks
{
publicclassWalkTrailPage:ContentPage
{
WalksTrailViewModel_viewModel
{
get{returnBindingContext
asWalksTrailViewModel;}
}
publicWalkTrailPage()
{
Title="WalksTrail";
...
...
...
beginTrailWalk.Effects.Add(Effect.Resolve
("com.geniesofts
tudios.ButtonShadowEffect"));
...
...
...
trailDifficultyLabel.SetBinding(Label.TextProperty,
"WalkEntry.Difficulty",stringFormat:"Difficulty:{0}");
vartrailDifficultyImage=newImage
{
HeightRequest=50,
WidthRequest=50,
Aspect=Aspect.AspectFill,
HorizontalOptions=LayoutOptions.Start
};
trailDifficultyImage.SetBinding(Image.SourceProperty,
"WalkEntry.Difficulty",
converter:newTrailImageConverter());
...
...
...
}
}
}
Intheprecedingcodesnippet,webeganbyapplyingtheButtonShadowEffectclasstoourbeginTrailWalkbutton,andthenupdatedthebindingofthetrailDifficultyImagetousetheTrailImageConvertervalueconverter.Thiswillconvertthestringrepresentationforthechosendifficulty,andreturnbackanimageURLthatwillbedisplayedwithinthecontentpage.
UpdatingtheDistanceTravelledPagetousetheupdatedViewModelInthissection,wewillproceedtoupdateanotherDistanceTravelledPagecontentpagethatwillmakeuseofthePlatformEffectsclassesforourButtonShadow.Toproceed,performthefollowingsteps:
EnsurethattheDistanceTravelledPage.csfileisdisplayedwithinthecodeeditor,andenterthefollowinghighlightedcodesections,asshowninthecodesnippet:
//
//DistanceTravelledPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingXamarin.Forms.Maps;
usingTrackMyWalks.Services;
namespaceTrackMyWalks
{
publicclassDistanceTravelledPage:ContentPage
{
DistTravelledViewModel_viewModel
{
get{returnBindingContextas
DistTravelledViewModel;}
}
publicDistanceTravelledPage()
{
Title="DistanceTravelled";
...
...
...
varwalksHomeButton=newButton
{
BackgroundColor=Color.FromHex("#008080"),
TextColor=Color.White,
Text="EndthisTrail"
};
walksHomeButton.Effects.Add(Effect.Resolve
("com.geniesoft
studios.ButtonShadowEffect"));
...
...
...
}
}
Intheprecedingcodesnippet,webeginbyapplyingtheButtonShadowEffectclasstoourwalksHomeButtoncontrolsothatitcantakeadvantageoftheniceplatform-specificrenderingeffectstovisualcontrolelements.
UpdatingtheWalkCellDataTemplateclasstousePlatformEffectsInthissection,wewillproceedtoupdatetheWalkCellDataTemplateclass,whichwillmakeuseofthePlatformEffectsclassesforourLabelShadow,sothattheDataTemplatewillinheritsomeofthenicevisualrepresentationsthatyouruserswilllove.Toproceed,performthefollowingsteps,asshownhere:
EnsurethattheWalkCellDataTemplate.csfileisdisplayedwithinthecodeeditor,andenterthefollowinghighlightedcodesections,asshowninthecodesnippet:
//
//WalkCellDataTemplate.cs
//TrackMyWalksDataTemplateforCells
//
//CreatedbyStevenF.Danielon01/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingTrackMyWalks.ValueConverters;
usingXamarin.Forms;
namespaceTrackMyWalks.DataTemplates
{
publicclassWalkCellDataTemplate:ViewCell
{
publicWalkCellDataTemplate()
{
...
...
...
//ApplyPlatformEffectstoourTrailNameLabelControl
TrailNameLabel.Effects.Add(Effect.Resolve
("com.geniesofts
tudios.LabelShadowEffect"));
TrailNameLabel.SetBinding(Label.TextProperty,"Title");
...
...
...
trailDifficultyImage.SetBinding(Image.SourceProperty,
"Difficulty",converter:newTrailImageConverter());
...
...
...
this.View=cellLayout;
}
}
}
Intheprecedingcodesnippet,webeganbyapplyingtheLabelShadowEffectclasstoourTrailNameLabelcontrol,andthenupdatedthebindingofthetrailDifficultyImagetousethe
TrailImageConvertervalueconverter.Thiswillconvertthestringrepresentationforourchosendifficulty,andreturnbackanimageURLthatwillbedisplayedwithinthecontentpage.
NowthatyouhaveupdatedthenecessaryViewModelsandContentPagestotakeadvantageofourPlatFormEffectsandValueConverters,ournextstepistofinallybuildandruntheTrackMyWalksapplicationwithintheiOSsimulator.
Whenthecompilationiscomplete,theiOSsimulatorwillappearautomatically,andtheTrackMyWalksapplicationwillbedisplayed,asshowninthefollowingscreenshot:
Asyoucanseefromtheprecedingscreenshot,thiscurrentlydisplaysourActivityIndicatorspinnercontrol,withtheassociatedLoadingTrailWalks...text.AfterthistheListViewcontainingthelistoftrailwalksfromtheDataTemplatecontrolwilldisplay.Youwillnoticethatitdisplaysaniceimageassociatedwiththedifficultyforthetrailwalk,whichispulleddirectlyfromtheTrailImageValueConverterclass.
WhentheAddWalkbuttonispressedfromthemainTrackMyWalkscontentpage,thisdisplaystheNewWalkEntryscreen,withtheDifficultyLevelcustompickershowingthelistofchoicesasdefinedwithinourDifficultyPickerModel:
TheprecedingscreenshotshowstheupdatedViewModelandContentPagesthatmakeuseofthePlatformEffectsclassesfortheButtonShadoweffects,aswellastheValueConvertersfortheTrailImageValueConverter.Asyoucansee,byusingthepowerofXamarin.FormsPlatformEffectsandValueConverterswithinyourownapplications,youcanreallycreatesomestunninguserinterfacesthatwillshowoffyourappsandthatyouruserswilllove.
SummaryInthischapter,weupdatedtheTrackMyWalksapplicationtouseCustomRendererstochangetheappearanceofcontrolelementsthataredisplayedwithintheuserinterfaceforeachspecificplatform.Next,youlearnedhowtoworkwithDataTemplates,bycreatingacustomclasstorepresenttheinformationthatispresentedwithintheListViewclass,aswellascreatingtwoValueConverterclassesBooleanValueConverterthatareusedtodeterminewheninformationiscurrentlybeingdisplayedwithintheuserinterface.YoualsocreatedaTrailImageValueConverterthatreturnsanimageURLbasedonthestringpassedintoit.Finally,youlearnedhowtoworkwiththePlatformEffectsclass,tocreateaLabelShadowandButtonShadow,andupdatedtheViewModelandContentPagestoapplythoseeffectstocontrolelements.
Inthenextchapter,you'lllearnabouttheRazorTemplatingEngineandhowyoucanuseittocreateahybridmobilesolution.You'lllearnhowtocreate,andusemodelswithinyourapplication,aswellascallingJavaScriptcodeusingC#toexecutemethodcalls.
Chapter6.WorkingwithRazorTemplatesInourpreviouschapter,welookedathowtoworkwiththeDataTemplateCustomRendererbycreatingaC#classtolayoutyourviewsbeautifullywithinyourapplications,andhowyouwillalsogetaccustomedtoworkingwiththeplatform-specificAPIstoextendthedefaultbehaviorofXamarin.Formscontrolsusingcustomrenderers,bycreatingacustompicker.
YoualsolearnedhowtousetheXamarin.FormsEffectsAPItocustomizetheappearanceandstylingofnativecontrolelementsforeachplatform,byimplementingacustomrendererclass,andlookedathowtomanipulatethevisualappearanceofdatathatisbound,usingvalueandimageconverters.
Inthischapter,you'lllearnabouttheRazorHTMLtemplateengineandhowyoucanuseittocreateahybridmobilesolution.You'lllearnhowtobuildabooklibrarymobilesolutionusingthepowerofRazortemplates,andlearnhowtocreate,andusemodelswithinyourapplicationandconnectthisuptoanSQLitedatabasetostore,retrieve,update,anddeletebookdetails.
Thischapterwillcoverthefollowingtopics:
IntroductiontotheRazorHTMLtemplateengineHowtobuildahybridmobilesolutionusingXamarinStudioIncorporatingSQLite.NetandcreatingaSQLitedatabasewrapperCreatingthebookdatabasemodelCreatingthebooklistingmainpageCreatingthebooklistingaddpageCreatingthebooklistingeditpage
UnderstandingtheRazortemplateengineTheRazortemplatingenginewasfirstintroducedaspartoftheASP.NetMVCarchitecture,andwasoriginallydesignedtorunonawebservertogenerateHTMLfilestobeservedtowebbrowsers.
SinceRazormadeitsfirstappearanceonthedevelopmentscene,theRazortemplatingenginehascomealongwayandnowextendsthestandardHTMLsyntax,sothatyoucanuseC#toexpressthelayoutofyourHTMLfiles,andincorporateCSSstylesheetsandJavaScripteasily.
EachRazortemplatehastheabilitytoreferenceaModelclasswhichcanbeofanycustomtype,andpropertiescanbeaccesseddirectlyfromthetemplate,byhavingtheabilitytomixHTMLandC#syntaxeasily.
Asyouworkthroughthischapter,youwillseehow,byworkingwithXamarinStudio,youcanutilizetheRazorHTMLtemplatingengineandbeequippedwiththeflexibilityofbuildingcross-platformtemplatedHTMLviewsthatusebothJavaScriptandCSS,aswellashavingaccesstotheunderlyingplatformAPIsusingthepowerofC#.
Note
FormoreinformationonusingRazorsyntax(C#)withASP.NETwebprogrammingrefertothefollowingURL:https://www.asp.net/web-pages/overview/getting-started/introducing-razor-syntax-c.
CreatingandimplementingRazortemplateswithinXamarinStudioInthissection,wewilllookathowtogoaboutcreatinganewRazortemplatesolutionusingXamarinStudio.Wewillbeginbydevelopingthebasicstructureforourapplication,aswellasaddingallthenecessarydatabasemodelsanduserinterfacefiles.
Beforewecanproceed,weneedtocreatetheBookLibraryproject.ItisverysimpletocreatethisusingXamarinStudio.Simplyfollowthegivensteps:
1. LaunchtheXamarinStudioapplication,andchoosetheNewSolution...option,oralternativelychooseFile|New|Solution...orsimplypressShift+Command+N.
2. Next,choosetheWebViewAppoptionwhichislocatedundertheiOS|Appsection,ensurethatyouhaveselectedC#astheprogramminglanguagetouse,andclickNext:
3. Then,enterinBookLibraryasthenameforyourappintheAppNamefieldaswellasspecifyinganamefortheOrganizationIdentifierfield.
4. Next,ensurethatboththeiPadandiPhonecheckboxeshavebeenselectedfortheDevicesfield,aswellasensuringthatyouhavechoseniOS10.0fortheTargetfieldtosupporttheminimumiOSversionthatwewantourapptosupport:
5. ClickontheNextbuttontoproceedtothenextstepinthewizard:
6. Next,ensurethattheCreateaprojectdirectorywithinthesolutiondirectory.checkboxhasbeenselectedandclickontheCreatebuttontosaveyourprojectatthespecifiedlocation.
Onceyourprojecthasbeencreated,youwillbepresentedwiththeXamarinStudiodevelopmentenvironment,alongwithseveralprojectfilesthatthetemplatecreatedforyourRazortemplateSolution,asshowninthefollowingscreenshot:
Asyoucanseefromtheprecedingscreenshot,theBookLibrarysolutionhasbeendividedintothreeseparatefolders.Thefollowingtableprovidesabriefdescriptionofwhateachareaisusedfor:
Foldertype Description
Models
Thissectionisresponsibleforrepresentingthemodelthatourviewswilluse,andcontainsastructureoffieldsthatwillbedisplayedand/orwrittentobyourRazortemplates.
ResourcesThissectioncontainsaplaceforyoutoaddtheimagesandCSS,orJavaScriptfilesthatyourapplicationwilluse.
Views
ThissectionisresponsibleforcontainingalloftheHTML5Razortemplatesthatyourapplicationwillbereferencing,andtheyneedtocontainandbeprefixedwiththe.cshtmlextension.
Onethingyouwillnoticeisthatoursolutioncontainsafilecalledstyle.css.Thisisbecauseourapplicationisessentiallyahybridmobilesolution,andcontainsthefilesyoumightexpectfromawebsolution,ascanbeseeninthefollowingcodesnippet:
/*Thisisaminimalstylesheetintendedtodemonstrate
howtoincludestaticcontentinyourhybridapp.
Otherstaticcontent,suchasjavascriptfilesandimages,
canbeincludedinthissamefolder(ResourcesoniOSorAssets
onAndroid),withthesameBuildAction(BundleResourceoniOS
orAndroidAssetonAndroid),tobeaccessiblefromapathstarting
attherootofyourhybridapplication.*/
#page{
margin-top:10px;
}
Asyoucansee,thisfiledoesn'tcontainmuchinformation,butasweworkourwaythroughthischapter,wewillbeaddingtothisfile,andbuildingtheRazortemplateuser-interfacefiles.
AddingtheSQLite.NetpackagetotheBookLibrarysolutionNowthatwehavecreatedourBookLibrarysolution,thenextstepistoaddtheSQLite.NetNuGetpackagetooursolution,sincethisdoesn'tgetaddedautomaticallyforus.TheSQLite.NetpackagewillessentiallyprovideuswiththeabilitytohaveourapplicationwritetoanSQLitedatabase,tostoreourbookdetails.
Note
ANuGetpackageisessentiallythepackagemanagerfortheMicrosoftdevelopmentplatformthatcontainstheclienttoolsthatprovideoursolutionwiththeabilitytoproduceandconsume.NETpackages.
Let'slookathowtoaddtheSQLite.NETNuGetpackagewithinourBookLibrarysolution,byperformingthefollowingsteps:
1. Right-clickonthePackagesfolderthatiscontainedwithintheBookLibrarysolution,andchoosetheAddPackages...menuoption,asshowninthefollowingscreenshot.
2. ThiswilldisplaytheAddPackages...dialog.EnterinSQLite.Netwithinthesearchdialog,andthenclickonthesqlite-netoptionwithinthelist,asshowninthefollowingscreenshot:
3. Finally,clickontheAddPackagebuttontoaddthesqlite-netNuGetpackagetothePackagesfolder,containedwithintheBookLibrarysolution.
WhenyouclickontheAddPackagebutton,youwillnoticethatthepackagemanagerwillcreatetwonewfilesforuswithinoursolution.ThesearecalledSQLite.csandSQLiteAsync.cs,andarebasicallywrapperconvenienceclassesthatallow.NETandMonoapplicationstostoredatawithinaSQLite3database.
NowthatyouhaveaddedtheNuGetpackageforthesqlite-netlibrary,wecanbegintoutilizethislibrarywithintheBookLibrarysolutionthatwewillbecoveringinthenextsection.
CreatingandimplementingthebooklibrarydatabasemodelInthissection,wewillbeginbybuildingthedatabasemodelthatwillbeusedbyourBookLibraryprojectsolutionthatwillbeusedbyourRazortemplateswhenwecreatethese,andthentheWebViewController.csfilewillcommunicateandinteractwitheachoftheRazortemplateviewsandhandletheactionswithinthem.
Let'slookathowwecanachievethis,byperformingfollowingsteps:
1. CreateanemptyclasswithintheModelsfolder,bychoosingAdd|NewFile...,asshowninthefollowingscreenshot:
2. Next,choosetheEmptyClassoption,locatedwithintheGeneralsection,andenterinBookItem,asshowninthefollowingscreenshot:
3. Next,clickontheNewbuttontoallowthewizardtoproceedandcreatethenewemptyclassfile.
Upuntilnow,allwehavedoneiscreateourBookItemclassfile.ThisclasswillbeusedbyeachofourRazortemplateviews,aswellasourWebViewController.csfilethatwilleventuallyallowcommunicationtohappenbetweeneachoftheRazortemplateviews,aswellasbeingabletohandleactionswithinthem,whenevertheuserinteractswiththem.
Let'snowstarttoimplementthecoderequiredforourBookItemclassmodel,byperformingthefollowingsteps:
1. EnsurethattheBookItem.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//BookItem.cs
//BookLibraryDatabaseModel
//
//CreatedbyStevenF.Danielon20/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSQLite;
namespaceBookLibrary{
publicclassBookItem
{
publicBookItem()
{
}
[PrimaryKey,AutoIncrement]
publicintId{get;set;}
publicstringTitle{get;set;}
publicstringAuthor{get;set;}
publicstringIsbn{get;set;}
publicstringSynopsis{get;set;}
}
}
Intheprecedingcodesnippet,wehavesuccessfullydefinedourdatabasemodelthatwillbeusedtorepresentourbookitems.YouwillnoticesomethingthatisdifferentwithinourmodeltowhatyouwouldhaveseenwithinourTrackMyWalksapp:herewehavedefineda[PrimaryKey,AutoIncrement]itemforouridfield,thiswilltellourBookLibrarydatabasetosettheidpropertytoautomaticallyincrementwheneverweaddanewitemtoourdatabase.
Ifyouhaveusedrelationaldatabasesinthepast,suchasMicrosoftSQLServer,Oracle,orMicrosoftAccess,thisshouldbequitefamiliartoyou.Inthenextsection,wewillusethismodeltosetupandinitializeourdatabase,bycreatingadatabaselibrarywrappertohandleconnectionstoourdatabase,aswellasCreation,Retrieval,Updating,andDeletion(CRUD),ofeachofourbookentries.
Note
TheAndroidversionoftheBookItemclassmodelisavailableinthecompanionsourcecodeforthisbook.
CreatingandimplementingthebookdatabasewrapperIntheprevioussection,wesuccessfullycreatedandimplementedourBookItemdatabasemodelthatwillbeusedbyourBookLibraryapplication.OurnextstepistobeginimplementingthecoderequiredforourBookItemDatabaseclassmodel,byperformingthefollowingsteps:
1. CreateanewfolderwithintheBookLibraryprojectsolution,calledDatabaseasshowninthefollowingscreenshot:
2. Next,createanemptyclasswithintheDatabasefolder,bychoosingAdd|NewFile...,asyoudidwhencreatingthemodelintheprevioussectionentitledCreatingandimplementing
thebooklibrarydatabasemodel,locatedwithinthischapter.3. Then,enterinBookDatabaseforthenameofthenewclassthatyouwanttocreate,andclick
ontheNewbuttontoallowthewizardtoproceedandcreatethenewfile.4. Next,ensurethattheBookDatabase.csfileisdisplayedwithinthecodeeditorwindow,and
thenenterinthefollowingcodesnippet:
//
//BookDatabase.cs
//BookLibraryDatabasetohandleperformingdatabase
//Creation,Retrieval,UpdatingandDeletionofBookItems.
//
//CreatedbyStevenF.Danielon20/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSQLite;
namespaceBookLibrary
{
publicclassBookDatabase
{
5. Next,weneedtocreatealockervariablethatwillbeusedtocreateamutually-exclusivelockonthedatabasewhileweareeithercreating,updating,retrieving,ordeletingbookitems.Wedothissothatnootheroperationcaninterferewhileweareprocessing,andthiswillpreventissuesfromarising.Wealsoneedtodeclareadatabasevariable,thatpointstoaninstanceofourSQLiteConnectionlocatedwithinourSQLitelibrary,sothatwecanperformdatabaseoperations:
staticobjectlocker=newobject();
SQLiteConnectiondatabase;
6. Then,weneedtocreatetheBookDatabase(SQLiteConnectionconn)methodthatacceptsaconnobject,whichisaninstanceofourSQLiteConnectionclass,andthisinstancemethodwillbeusedtocreatethenecessarydatabasetablestructure,basedonourBookItemmodel:
///<summary>
///InitializesanewinstanceoftheBookLibrary
///Databaseclass.
///</summary>
///<paramname="conn">Conn.</param>
publicBookDatabase(SQLiteConnectionconn)
{
database=conn;
//CreatethetableswithinourBookLibraryDatabase
database.CreateTable<BookItem>();
}
7. Next,weneedtocreatetheGetItems()methodthatwillbeusedtoextractalloftheexistingbookentriesthathavebeensavedtothedatabase.WeusetheLINQlanguagesyntax
toiterateandretrieveallitemsfromourBookItemtable,andconvertthiscollectiontoalistinstance,asdeterminedbythe.ToList()method:
///<summary>
///Getsallofthebooklibraryitemsfromourdatabase.
///</summary>
///<returns>Theitems.</returns>
publicIEnumerable<BookItem>GetItems()
{
//Setamutual-exclusivelockonourdatabase,while
//retrievingitems.
lock(locker)
{
return(fromiindatabase.Table<BookItem>()
selecti).ToList();
}
}
8. Then,weneedtocreatetheGetItem()methodthatwillextracttheselectedbookentryfromtheBookItemdatabasetable,usingtheiridasthekey.Again,weusetheLINQlanguagesyntaxtoretrievethefirstitemfromtheBookItemtablethatmatchestheidofthebookitem:
///<summary>
///Getsaspecificbookitemfromthedatabase.
///</summary>
///<returns>Theitem.</returns>
///<paramname="id">Identifier.</param>
publicBookItemGetItem(intid)
{
//Setamutual-exclusivelockonourdatabase,while
//retrievingthebookitem.
lock(locker)
{
returndatabase.Table<BookItem>().FirstOrDefault(x=>
x.Id==id);
}
}
9. Next,weneedtocreatetheSaveItem()methodthatwillsavethebookitemtotheBookItemdatabasetable.Inthisinstancemethod,wearehandlingtwocasescenarios:oneiftheitemwearesavingisanexistingitem,wechecktheidofthebookitem,andifitisanon-zerovalue,weproceedtoupdatethebookitemusingtheUpdatemethodonthedatabaseobject,andreturnthebookitemidback.
10. However,iftheitemisanewbookrecord,thatisallnewbooksthatgetcreatedwillhaveanidof0,thiswillbedirectlyinsertedintotheBookItemtable,usingtheInsertmethodonthedatabaseobject:
///<summary>
///Savesthebookitemcurrentlybeingedited.
///</summary>
///<returns>Theitem.</returns>
///<paramname="item">Item.</param>
publicintSaveItem(BookItemitem)
{
//Setamutual-exclusivelockonourdatabase,while
//saving/updatingourbookitem.
lock(locker)
{
if(item.Id!=0)
{
database.Update(item);
returnitem.Id;
}
else{
returndatabase.Insert(item);
}
}
}
11. Finally,wecreatetheDeleteItem()methodthatwill,asyoumighthaveguessed,deleteabookitemfromtheBookItemdatabasetableusingthebook'sitemidandthencallingtheDeletemethodonthedatabaseobject:
///<summary>
///Deletesaspecificbookitemfromthedatabase.
///</summary>
///<returns>Theitem.</returns>
///<paramname="id">Identifier.</param>
publicintDeleteItem(intid)
{
//Setamutual-exclusivelockonourdatabase,while
//deletingourbookitem.
lock(locker)
{
returndatabase.Delete<BookItem>(id);
}
}
}
}
NowthatwehavecreatedourBookDatabaseclass,wecanproceedandcreateourBookLibraryDBdatabasewrapperthatwilluseourBookDatabaseclasstohandlealltheoperationsforcreating,retrieving,updating,anddeletionofbookitemsfromourdatabase.ThatwillbeusedbyourWebViewController.csclasstointeractwithourRazortemplateViews.
Note
TheAndroidversionoftheBookDatabaseclassisavailableinthecompanionsourcecodeforthisbook.
CreatingandimplementingtheBookLibrarydatabasewrapperIntheprevioussection,wesuccessfullycreatedandimplementedourBookDatabasedatabaseclassthatwillbeusedbyourBookLibraryapplication.OurnextstepistobeginimplementingthecoderequiredforourBookLibraryDBclassthatwillactasawrapperforourBookDatabaseclass,toperformalldatabaseactionsforourBookLibraryapplication.
Let'sbeginbyperformingthefollowingsteps:
1. CreateanemptyclasswithintheBookLibrarysolution,bychoosingAdd|NewFile...,asyoudidwhencreatingthemodelintheprevioussectionentitledCreatingandimplementingthebooklibrarydatabasemodel,locatedwithinthischapter.
2. Then,enterinBookLibraryDBforthenameofthenewclassthatyouwanttocreate,andclickontheNewbuttontoallowthewizardtoproceedandcreatethenewfile.
3. Next,ensurethattheBookLibraryDB.csfileisdisplayedwithinthecodeeditorwindow,andthenenterinthefollowingcodesnippet:
//
//BookLibraryDB.cs
//BookLibraryDatabaseLayer
//
//CreatedbyStevenF.Danielon20/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSQLite;
namespaceBookLibrary
{
publicclassBookLibraryDB
{
4. Next,weneedtocreateaconnvariablethatwillbeusedtosetaconnectiontoourBookLibrarydatabase,andwealsoneedtodeclareadatabasevariable,thatpointstoaninstanceofourBookDatabasethatcontainseachoftheoperationsforhandlingandperformingtheinsertion,updating,anddeletionofbookentries:
staticSQLiteConnectionconn;
staticBookDatabasedatabase;
5. Then,weneedtocreatetheSetDatabaseConnection()methodthatwillsetaconnectiontoourBookDatabasedatabaseclass,sothatitcanaccesseachofourinstancemethodsforhandlingthecreation,updating,retrieval,anddeletionofourbookentries,withinourBookItemdatabasetable.Withinthisinstancemethod,wesetupandinitializetheconnvariablethatcontainsthevalueofourconnectionparameter,andtheninstantiatesaconnectiontoourBookDatabaseclass,passingintheconnconnectionobjectsothattheclasscanreturnourdatabaseobject:
///<summary>
///SetsaconnectiontoourBookDatabasedatabaseclass.
///</summary>
///<paramname="connection">Connection.</param>
publicstaticvoidSetDatabaseConnection(SQLiteConnection
connection)
{
conn=connection;
database=newBookDatabase(conn);
}
6. Finally,wecreatetheDatabasemethodthatgetsareferencetoourBookLibrarydatabasetouse:
///<summary>
///GetsareferencetoourBookLibrarydatabase.
///</summary>
///<value>Thedatabase.</value>
publicstaticBookDatabaseDatabase
{
get{returndatabase;}
}
}
}
NowthatwehavecreatedourBookLibraryDBclass,wecancreateourRazortemplateuserinterfacefilesthatwillconnecttoourBookLibrarydatabase,andallowtheusertointeractwiththedatabasemodeltocreate,retrieve,update,anddeletebookitemsfromthedatabase.
Note
TheAndroidversionoftheBookLibraryDBclassisavailableinthecompanionsourcecodeforthisbook.
CreatingandimplementingthebooklistingmainpageIntheprevioussection,wecreatedandimplementedourBookLibraryDBdatabasewrapperclassthatwillbeusedbyourBookLibraryapplication.ThenextstepistobegincreatingeachoftheRazortemplatesthatwillconnecttoourdatabasemodelallowingtheusertointeractwiththedatabasevisuallybyenteringinbookdetails,andhavingthisinformationpresentedbacktothem,sotheycanmakechanges,ordeletethebookitemaltogether.
Let'sbeginbyperformingthefollowingsteps:
1. CreateanemptyclasswithintheViewsfolder,bychoosingAdd|NewFile...,asyoudidwhencreatingthemodelintheprevioussectionentitledCreatingandimplementingthebooklibrarydatabasemodel,locatedwithinthischapter.
2. Next,choosethePreprocessedRazorTemplateoption,locatedwithintheTextTemplatingsection,andenterinBookLibraryListing,asshowninthefollowingscreenshot:
3. Next,clickontheNewbuttontoallowthewizardtoproceedandcreatethenewemptyRazortemplatefile.
Congratulations,youhavecreatedyourfirstRazortemplate!ThisfilewillbeusedtolistallthebookentriesthatarestoredwithinourBookItemdatabasetable,containedwithinourBookLibrarydatabase.ThenextstepistostarttoimplementthecoderequiredforourBookLibraryListingRazortemplate,byperformingthefollowingstep:
1. EnsurethattheBookLibraryListing.cshtmlfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
@usingBookLibrary
@modelList<BookItem>
<html>
<head><linkrel="stylesheet"href="style.css"/></head>
<body>
<p></p>
<h1>BookLibraryDatabaseListing</h1>
<tableborder="1"cellpadding="8">
@foreach(varbookin@Model){
<tr>
<td>@book.Id</td>
<td>@book.Title</td>
<td>@book.Author</td>
<td>@book.Isbn</td>
<td>
<ahref="hybrid:[email protected]">
Edit</a>
</td>
</tr>
}
<tr>
<tdcolspan="8">
<ahref="hybrid:CreateNewBook?">
<inputtype="submit"name="Button"
value="AddNewBook"/></a>
</td>
</tr>
</table>
</body>
</html>
Intheprecedingcodesnippet,wedefinetheHTMLlayoutinformationthatwillbeusedbyourBookLibraryListingRazortemplate.WefirstlyspecifythatweareusingtheBookLibrary.iOSnamespace,sothatwecanhaveaccesstoourdatabasemodelasspecifiedbythe@modeldirective,andthismustbetheveryfirstlineprecedingthe<html>tagwithinaRazortemplatefile.Youwillnoticethatthe@modeldirectivehasthetypeofList.ThisisbecauseweareiteratingthrougheachofourbookitemswithinourModel,anddisplayingtheid,title,
author,andisbndetailsforeachbook.
Next,wesetupa<ahreftag,thatpointstoourWebViewController.csclass,andin-turnwillcalltheBookLibraryEdit.cshtmlRazortemplatetoretrieveanddisplaythebookentrydetailsfortheassociatedidentifier.Thehybridtag,usedtoidentifyiftheURLisnotourowncustomscheme,andwilljustlettheWebViewloadtheURLasusual.Finally,wesetupanother<ahreftag,thatpointstoourWebViewController.csclass,andin-turnwillcalltheBookLibraryAdd.cshtmlRazortemplatetoallowtheusertocreateanotherbook.Youwillnoticethatwedon'tneedtopassinanidforthebook,asthiswillbeautomaticallyassignedoncethebookhassuccessfullybeenwrittentothedatabase.
CreatingandimplementingtheBookLibraryAddRazortemplateIntheprevioussection,wecreatedandimplementedtheBookLibraryListingRazortemplatethatwillbeusedtodisplayalistofallbooksthathavebeenpreviouslyaddedtotheBookLibraryapplication.OurnextstepistobegincreatingtheRazortemplatethatwillallowtheusertocreateanewbookandsavethistoourdatabasemodel.
Let'sbeginbyperformingthefollowingsteps:
1. CreateanemptyclasswithintheViewsfolder,bychoosingAdd|NewFile...,asyoudidwhencreatingtheBookLibraryListingintheprevioussectionentitledCreatingandimplementingthebooklistingmainpage,locatedwithinthischapter.
2. Next,choosethePreprocessedRazorTemplateoption,locatedwithintheTextTemplatingsection,andenterinBookLibraryAdd.
3. Next,clickontheNewbuttontoallowthewizardtoproceedandcreatethenewemptyRazortemplatefile.
OurnextstepistostarttoimplementthecoderequiredforourBookLibraryAddRazortemplate,byperformingthefollowingsteps:
EnsurethattheBookLibraryAdd.cshtmlfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
@usingBookLibrary
@modelBookItem
<html>
<head>
<linkrel="stylesheet"href="style.css"/>
</head>
<body>
<h1>AddNewBookDetails</h1>
<tableborder="1"cellpadding="8">
<formaction="hybrid:SaveBookDetails"method="GET">
<inputname="id"type="hidden"value="@Model.Id"/>
<tr>
<td>Title:
<inputname="Title"value="@Model.Title"/></td>
<tr>
<td>Author:
<inputname="Author"value="@Model.Author"/></td>
<tr>
<td>BookISBN:
<inputname="ISBN"value="@Model.Isbn"/></td>
<tr>
<td>Synopsis:
<textareaname="Synopsis"rows="5"cols="40">
@Model.Synopsis
</textarea>
</td>
<tr>
<tdcolspan="8">
<inputtype="submit"name="Button"value="Save"/>
<inputtype="submit"name="Button"value="Cancel"/>
</td>
</tr>
</form>
</table>
</body>
</html>
Intheprecedingcodesnippet,wedefinedtheHTMLlayoutinformationthatwillbeusedbyourBookLibraryAddRazortemplate.WefirstlyspecifiedthatweareusingtheBookLibrary.iOSnamespace,sothatwecanhaveaccesstoourdatabasemodelasspecifiedbythe@modeldirective.We'vealreadyexplainedthatthismustbetheveryfirstlineprecedingthe<html>tagwithinaRazortemplatefile.
Next,wesetupa<formactiontagsothatwhentheformgetssubmitted,theSaveorCancelbuttonsarepressed,ourWebViewController.csclasswillbecalled,andtheappropriateactionwilltakeplace.WespecifythehybridtaghereagaintoidentifyiftheURLisnotourowncustomscheme,andwilljustlettheWebViewloadtheURLasusual.
CreatingandimplementingtheBookLibraryEditRazortemplateIntheprevioussection,wecreatedandimplementedtheBookLibraryAddRazortemplatethatwillbeusedtoallowtheusertoaddnewbookentriestotheBookLibraryapplication.OurnextstepistobegincreatingtheRazortemplatethatwillallowtheusertoeditanexistingbookandsavethisbacktoourdatabasemodel.
Let'sbeginbyperformingthefollowingsteps:
1. CreateanemptyclasswithintheViewsfolder,bychoosingAdd|NewFile...,asyoudidwhencreatingtheBookLibraryListingintheprevioussectionentitledCreatingandimplementingthebooklistingmainpage,locatedwithinthischapter.
2. Next,choosethePreprocessedRazorTemplateoption,locatedwithintheTextTemplatingsection,andenterinBookLibraryEdit.
3. Next,clickontheNewbuttontoallowthewizardtoproceedandcreatethenewemptyRazortemplatefile.
OurnextstepistostarttoimplementthecoderequiredforourBookLibraryEditRazortemplate,byperformingthefollowingsteps:
EnsurethattheBookLibraryEdit.cshtmlfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
@usingBookLibrary
@modelBookItem
<html>
<head>
<linkrel="stylesheet"href="style.css"/>
</head>
<body>
<h1>EditBookDetails</h1>
<tableborder="1"cellpadding="8">
<formaction="hybrid:SaveBookDetails"method="GET">
<inputname="id"type="hidden"value="@Model.Id"/>
<tr>
<td>Title:
<inputname="Title"value="@Model.Title"/></td>
<tr>
<td>Author:
<inputname="Author"value="@Model.Author"/></td>
<tr>
<td>BookISBN:
<inputname="ISBN"value="@Model.Isbn"/></td>
<tr>
<td>Synopsis:
<textareaname="Synopsis"rows="5"cols="40">
@Model.Synopsis
</textarea></td>
<tr>
<tdcolspan="8">
<inputtype="submit"name="Button"value="Save"/>
<inputtype="submit"name="Button"value="Cancel"/>
@if(Model.Id>0){
<inputtype="submit"name="Button"value="Delete"/>
}
</td>
</tr>
</form>
</table>
</body>
</html>
Intheprecedingcodesnippet,wedefinetheHTMLlayoutinformationthatwillbeusedbyourBookLibraryEditRazortemplate.WefirstlyspecifythatweareusingtheBookLibrary.iOSnamespace,sothatwecanhaveaccesstoourdatabasemodelasspecifiedbythe@modeldirective,whichandwehavesaidthatthismustbetheveryfirstlineprecedingthe<html>tagwithinaRazortemplatefile.
Next,wesetupa<formactiontag,sothatwhentheformgetssubmittedtheSaveorCancelbuttonsarepressed,ourWebViewController.csclasswillbecalled,andtheappropriateactionwilltakeplace.WespecifythehybridtaghereagaintoidentifyiftheURLisnotourowncustomscheme,andwilljustlettheWebViewloadtheURLasusual.YouwillnoticethatthisimplementationisquitesimilartothatofourBookLibraryAdd.TheonlythingthatthisRazortemplatedoesdifferently,isifwehaveavalueforourbookid,wedisplaytheDeletebuttonsothattheusercanchoosetodeletethebookentry.
Note
TheAndroidversionoftheRazortemplatesareavailableinthecompanionsourcecodeforthisbook.
CreatingandimplementingtheWebViewControllerclassIntheprevioussection,wesuccessfullycreatedandimplementedeachofourRazortemplatesthatwillbeusedbyourBookLibraryapplication.Ournextstepistobeginimplementingthecodeforourapplication,thatwillberesponsibleforinteractingwithourRazortemplates,andhandlingtheactionsassociatedwitheachmodel.ItwilluseourBookDatabaseclass,tohandletheperformanceofallthedatabaseactionsforourBookLibraryapplication.
Let'sbeginbyperformingthefollowingsteps:
1. EnsurethattheWebViewController.csfileisdisplayedwithinthecodeeditorwindow,andthenenterinthefollowinghighlightedcodesections,asshowninthefollowingcodesnippet:
//
//WebViewController.cs
//WebContainerforrepresentingRazorTemplates
withinaWebView
//
//CreatedbyStevenF.Danielon20/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Linq;
usingFoundation;
usingUIKit;
usingSystem.Collections.Specialized;
namespaceBookLibrary
{
publicpartialclassWebViewController:
UIViewController
{
staticboolUserInterfaceIdiomIsPhone
{
get{return
UIDevice.CurrentDevice.UserInterfaceIdiom
==UIUserInterfaceIdiom.Phone;}
}
2. Next,wedeclareawebViewobjectthatwillpointtoourUIWebViewinstance,andthendeclareourWebViewController()classconstructormethodthatwillbeinstantiatedfromourAppDelegateclass,asyouwillseeoncewebeginimplementingthenecessarychanges.
3. Enterinthefollowinghighlightedcodesections,asshowninthefollowingcodesnippet:
UIWebViewwebView;
publicWebViewController()
{
}
protectedWebViewController(IntPtrhandle):base(handle)
{
//Note:thisconstructorshouldnotcontainany
//initializationlogic.
}
publicoverridevoidViewDidLoad()
{
base.ViewDidLoad();
4. Then,wedefineandspecifythescreendimensionsthatwewouldlikeourwebViewtocontain.Inthiscase,wesetthistotakeupthewholeregionofourscreenandthenaddthistoourexistingviewcontainer.Inthenextstep,wecalltheGetItemsinstancemethodonourBookLibraryDB.Databasetoreturnallexistingbookentrieswithinthedatabase,andassignthistoourmodel.
5. Inthenextstep,wespecifytheBookLibraryListingRazortemplate,andpassinthemodelthatwillbeusedtopopulatetheViewModel.Next,wecalltheGenerateString()methodonourtemplate,toexecutethetemplatewithinthemainapplicationbundleandreturntheoutputasastring,andthenloadthiswithinourwebView,usingtheLoadHtmlStringmethod:
webView=newUIWebView(UIScreen.MainScreen.Bounds);
View.Add(webView);
//InterceptURLloadingtohandlenativecallsfrom
//browser
webView.ShouldStartLoad+=HandleShouldStartLoad;
//RendertheviewtouseourBookList.cshtmlfile
varmodel=BookLibraryDB.Database.GetItems().ToList();
vartemplate=newBookLibraryListing()
{Model=model};
varpage=template.GenerateString();
//LoadtherenderedHTMLintotheviewwithabaseURL
//thatpointstotherootofthebundledResources
//folder
webView.LoadHtmlString(page,NSBundle.MainBundle.BundleUrl);
//Performanyadditionalsetupafterloadingtheview,
//typicallyfromanib.
}
publicoverridevoidDidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
//Releaseanycacheddata,images,etcthataren't
//inuse.
}
boolHandleShouldStartLoad(UIWebViewwebView,NSUrlRequest
request,UIWebViewNavigationTypenavigationType)
{
//IftheURLisnotourowncustomscheme,just
//letthewebViewloadtheURLasusual
conststringscheme="hybrid:";
if(request.Url.Scheme!=scheme.Replace(":",""))
returntrue;
//Thishandlerwilltreateverythingbetweenthe
//protocoland"?"asthemethodname.Thequerystring
//hasalloftheparameters.
varresources=request.Url.ResourceSpecifier.Split('?');
varmethod=resources[0];
varparameters=System.Web.HttpUtility.ParseQueryString(
resources[1]);
6. Next,wecreateaswitchstatementtohandlethetypeofmethodoperationthatweobtainedfromtheRazortemplatedirectlyafterthehybrid:tag,andhandleaccordingly:
switch(method)
{
case"CreateNewBook":
CreateNewBook(webView);
break;
case"EditBookDetails":
EditBookDetails(webView,parameters);
break;
case"SaveBookDetails":
SaveBookDetails(webView,parameters);
break;
default:
//Casesnotcoveredarehandledhere.
break;
}
returnfalse;
}
7. Then,weneedtocreatetheCreateNewBook()instancemethodthatwillberesponsibleforhandlingthecreationofournewbookentry.ThismethodacceptsthenameofthewebViewto
displayitscontentandwespecifytheBookLibraryAddRazortemplate,andpassinthemodelthatwillbeusedtopopulatetheViewModel.Next,wecalltheGenerateString()methodonourtemplate,toexecutethetemplatewithinthemainapplicationbundleandreturntheoutputasastring,andthenloadthiswithinourwebView,usingtheLoadHtmlStringmethod:
///<summary>
///Handlesthecreationofournewbookentry.
///</summary>
///<paramname="webView">Webview.</param>
voidCreateNewBook(UIWebViewwebView,
NameValueCollectionparameters)
{
vartemplate=newBookLibraryAdd()
{Model=newBookItem()};
varpage=template.GenerateString();
webView.LoadHtmlString(page,
NSBundle.MainBundle.BundleUrl);
}
8. Next,weneedtocreatetheEditBookDetails()instancemethodthatwillberesponsibleforhandlingtheeditingofourbookentry.ThismethodacceptsthenameofthewebViewtodisplayitscontentaswellasanyparameters.Next,wespecifytheBookLibraryEditRazortemplate,andpassinthemodelaswellastheidvaluefortheselectedbookentrytobeusedtopopulatetheViewModel.Next,wecalltheGenerateString()methodonourtemplate,toexecutethetemplatewithinthemainapplicationbundleandreturntheoutputasastring,andthenloadthiswithinourwebView,usingtheLoadHtmlStringmethod:
///<summary>
///Handlestheeditingofourbookdetails.
///</summary>
///<paramname="webView">Webview.</param>
///<paramname="parameters">Parameters.</param>
voidEditBookDetails(UIWebViewwebView,
System.Collections.Specialized.NameValueCollectionparameters)
{
varmodel=BookLibraryDB.Database.GetItem(
Convert.ToInt32(parameters["Id"]));
vartemplate=newBookLibraryEdit(){Model=model};
varpage=template.GenerateString();
webView.LoadHtmlString(page,
NSBundle.MainBundle.BundleUrl);
}
9. Then,weneedtocreatetheSaveBookDetails()instancemethodthatwillberesponsibleforhandlingthesavingofourbookentry.ThismethodacceptsthenameofthewebViewtodisplayitscontentaswellasanyparameters.Next,wegrabthenameofthebuttonthatwepressedfromtherelevantRazortemplateanduseaswitchstatementtohandlethetypeofbuttonoperationandhandleaccordingly.Oncetheoperationhascompletedsuccessfully,wereturn,andloadtheBookLibraryListingpagewithinthewebViewcontainer:
///<summary>
///SavesthebookdetailstotheSQLiteBookDetailsDatabase.
///</summary>
///<paramname="webView">Webview.</param>
///<paramname="parameters">Parameters.</param>
voidSaveBookDetails(UIWebViewwebView,
System.Collections.Specialized.NameValueCollectionparameters)
{
//PointstoourEditBookDetailsHTMLpage.
varbutton=parameters["Button"];
switch(button)
{
case"Save":
SaveDetailsToDatabase(parameters);
break;
case"Delete":
DeleteBookDetails(parameters);
break;
case"Cancel":
break;
default:
//Casesnotcoveredarehandledhere.
break;
}
varmodel=BookLibraryDB.Database.GetItems().ToList();
vartemplate=newBookLibraryListing(){Model=model};
webView.LoadHtmlString(template.GenerateString(),
NSBundle.MainBundle.BundleUrl);
}
10. Next,weneedtocreatetheSaveDetailsToDatabase()instancemethodthatwillberesponsibleforhandlingthesavingofourbookentrytotheSQLitedatabase.ThismethodacceptsalistofparametersthathavebeenenteredwithintheBookLibraryAddorBookLibraryEditRazortemplatescreens,andcreatesaBookItemdatabasemodel.ThisthengetspassedtotheSaveItemmethodthatourBookLibraryDBwrapperclasscallstheBookDatabaseclass:
///<summary>
///SavesthebookdetailstoourSQLitedatabase.
///</summary>
///<returns>Thedetailstodatabase.</returns>
///<paramname="parameters">Parameters.</param>
voidSaveDetailsToDatabase(System.Collections.Specialized.NameV
alueCollectionparameters)
{
varbook=newBookItem
{
id=Convert.ToInt32(parameters["Id"]),
title=parameters["Title"],
author=parameters["Author"],
isbn=parameters["Isbn"],
synopsis=parameters["Synopsis"]
};
BookLibraryDB.Database.SaveItem(book);
}
11. Then,weneedtocreatetheDeleteBookDetails()instancemethodthatwillbe
responsibleforhandlingthesavingofourbookentrytotheSQLitedatabase.ThismethodacceptsalistofparametersthathavebeenenteredwithintheBookLibraryEditRazortemplatescreen,andpassestheidofthebookentrytotheDeleteItemmethodthatourBookLibraryDBwrapperclasscallstheBookDatabase:
///<summary>
///HandlewhentheDeletebuttonhasbeenpressed
///</summary>
///<returns>Thebookdetails.</returns>
///<paramname="parameters">Parameters.</param>
voidDeleteBookDetails(System.Collections.Specialized.NameValue
Collectionparameters)
{
BookLibraryDB.Database.DeleteItem(Convert.ToInt32(
parameters["Id"]));
}
}
NowwehavesuccessfullyaddedthenecessarycodetoourWebViewController.csclasstoallowittocommunicatewithourRazortemplateswhenbookentrieshavebeencreated,updated,retrieved,deleted,andsaved.
Note
TheAndroidversionoftheWebViewController.csclassisavailableinthecompanionsourcecodeforthisbook.
12. Next,weneedtoinitializeourbookslibrarySQLitelibrarydatabasewithineachofourplatform-specificstart-upclasses(forexample,AppDelegate.csforiOSandMainActivity.csforAndroid).
13. EnsurethattheAppDelegate.csfileisdisplayedwithinthecodeeditorwindow,andthenenterinthefollowinghighlightedcodesections,asshowninthefollowingcodesnippet:
usingSystem;
usingSystem.IO;
usingFoundation;
usingUIKit;
usingSQLite;
namespaceBookLibrary
{
//TheUIApplicationDelegatefortheapplication.
Thisclass
//isresponsibleforlaunchingtheUserInterfaceofthe
//application,aswellaslistening
(andoptionallyresponding)
//toapplicationeventsfromiOS.
[Register("AppDelegate")]
publicclassAppDelegate:UIApplicationDelegate
{
//class-leveldeclarations
publicoverrideUIWindowWindow
{
get;
set;
}
publicoverrideboolFinishedLaunching(UIApplication
application,NSDictionarylaunchOptions)
{
//Overridepointforcustomizationafterapplication
//launch.Ifnotrequiredforyourapplicationyoucan
//safelydeletethismethod
//createanewwindowinstancebasedonthescreensize
Window=newUIWindow(UIScreen.MainScreen.Bounds);
//Declarethenametouseforourdatabasename
varsqliteFilename="BookLibrary.db";
stringdocumentsPath=Environment.GetFolderPath(
Environment.SpecialFolder.Personal);
stringlibraryPath=Path.Combine(documentsPath,"..",
"Library");
vardatabasePath=Path.Combine(libraryPath,sqliteFilename);
//Setaconnectiontoourdatabase
vardatabaseConn=newSQLiteConnection(databasePath);
BookLibraryDB.SetDatabaseConnection(databaseConn);
//SetourRootViewControllertobetheinstanceof
//ourBookViewControllerclassandmakeitvisible.
Window.RootViewController=newWebViewController();
Window.MakeKeyAndVisible();
returntrue;
}
...
...
...
}
Intheprecedingcodesnippet,webeganbyimportingboththeSQLiteandSystem.IOclasses,andthenmodifiedtheFinishedLaunchingmethodtocreateanewWindowinstance
basedonthescreensizeofthedevice.SincewehavespecifiedthatwewantourprojecttoworkonbothiPadandiPhonedevices,thiswilltakeupthefullscreendimensions.
Next,wedeclaredthenametouseforourdatabasenameandspecifiedthelocationofwheretosavetheBookLibrary.dbdatabaseto,asdeterminedbythedatabasePathstring.Inournextstep,wesetupaconnectiontoourdatabase,andthensettheRootViewControllertobetheinstanceofourBookViewControllerclass,beforefinallymakingthisbecomevisibleonscreen.
Note
TheAndroidversionoftheMainActivity.csclassisavailableinthecompanionsourcecodeforthisbook.
UpdatingthebooklibraryCascadingStyleSheet(CSS)Inthissection,weneedtomakesomefinalchangestothestyles.cssfile.ThisfileisessentiallyaCSS,andanyRazortemplatesthatutilizethisstylesheetwillinheriteverythingthatitcontains.WewillbasicallybemakingsomeminorchangestothebodyorourHTMLpages,applyingpaddingtomargins,settingfontsizes,andcolorsofweblinkswhentheyarepressed.
Let'sbeginbyperformingthefollowingstep:
1. EnsurethattheStyle.cssfileisdisplayedwithinthecodeeditorwindow,andthenenterinthefollowinghighlightedcodesections,asshowninthefollowingcodesnippet:
/*Thisisaminimalstylesheetintendedtodemonstrate
howtoincludestaticcontentinyourhybridapp.
Otherstaticcontent,suchasjavascriptfilesandimages,
canbeincludedinthissamefolder(ResourcesoniOS
orAssetsonAndroid),withthesameBuildAction
(BundleResourceoniOSorAndroidAssetonAndroid),
tobeaccessiblefromapathstartingattheroot
ofyourhybridapplication.*/
#page{
margin-top:10px;
}
html,
body{
margin:7px;
padding:0px;
border:0px;
color:#000;
background:#ffffe0;
}
html,body,p,th,td,li,dd,dt
{
font:1emArial,Helvetica,sans-serif;
}
h1{
font-family:Arial,Helvetica,sans-serif;
font-size:18;
}
a:link{
color:#00f;
}
a:visited{
color:#009;
}
a:hover{
color:#06f;
}
a:active{
color:#0cf;
}
NowthatwehavefinishedbuildingallthenecessarycomponentsforourBookLibraryapplication,ournextstepistofinallybuildandruntheBookLibraryapplicationwithintheiOSsimulator.Whencompilationcompletes,theiOSSimulatorwillappearautomaticallyandtheBookLibraryapplicationwillbedisplayed,asshowninthefollowingscreenshot:
Asyoucanseefromtheprecedingscreenshot,thiscurrentlydisplaysourBookLibraryDatabaseListingpage.Sincewedon'thaveanydetailsaddedyet,thiswillshowupasblank,andyoumustpresstheAddNewBookbuttontodisplaytheAddNewBookDetailsscreen:
TheprecedingscreenshotshowstheupdatedBookLibraryDatabaseListingRazortemplatepageViewModelwiththeinformationsavedfromthepreviousscreen.ClickingontheEditlinkwilldisplaytheEditBookDetailsscreenwithallthepreviouslyenteredbookdetailspopulated.
SummaryInthischapter,youlearnedabouttheRazortemplatingengineandhowyoucanuseittocreateahybridmobilesolution.Youalsolearnedhowtocreateandusemodelswithinyourapplication,andhavetheinformationsavedtoanSQLitedatabase,andretrievedlater.Finally,youlearnedhowyoucanuseJavaScriptcodeusingC#toexecutemethodcalls.
Inthenextchapter,you'lllearnhowtocreateandconsumeaRESTfulwebserviceAPI,sothatitcanbeusedwithintheTrackMyWalksapplicationtosaveandretrieveinformationenteredwithintheViewModels.
Chapter7.IncorporatingAPIDataAccessUsingMicrosoftAzureAppServicesInthepreviouschapter,welearnedabouttheRazortemplatingengineandhowyoucanuseittocreateahybridmobilesolution.Youlearnedhowtocreateandusemodelswithinyourapplication,andhowtosavetheinformationtoaSQLitedatabaseandretrieveitlater.Towardstheendofthechapter,youlearnedhowyoucanuseJavaScriptcodeusingC#toexecutemethodcalls.
Upuntilthispoint,youhavebeenbuildingtheTrackMyWalksappwithstaticwalktrailinformationthathasbeenhard-codedwithintheTrackMyWalksapp.However,intherealworld,itisveryrarethatyourappwilldependpurelyonlocalstaticdata,andyouwillneedtosourceyourinformationfromaremotedatasource,typicallyusingaRESTfulAPI.Insomecases,yourappmayevencommunicatewithathird-partyAPI,forexampleFacebook.
Inthischapter,you'lllearnhowyoucanuseMicrosoftAzureAppservicestocreateyourveryfirstlive,cloud-basedbackendHTTPwebservicetohandleallthecommunicationbetweenthecloudandtheapp.YouwillalsolearnhowtocreateaDataServiceAPIthatwillallowtheapptoconsumetheAPIsothatwecanretrieve,store,anddeletewalktrailinformationfromthecloudallwithintheTrackMyWalksapp.
Thischapterwillcoverthefollowingpoints:
GainanunderstandingofwhatMicrosoftAzureAppservicesareSettinguptheTrackMyWalksappwithintheMicrosoftAzureportalAddingtheHttpClientandJSON.NetNuGetpackagestothesolutionCreatingtheTrackMyWalksbaseHTTPserviceCreatingtheTrackMyWalksAPIdataserviceUpdatingtheTrackMyWalksViewModelstousetheAPIdataserviceRunningtheTrackMyWalksappwithinthesimulator
SettingupourTrackMyWalksappusingMicrosoftAzureInthissection,wewilllookatthestepsrequiredtosetuptheTrackMyWalksapplicationwithinMicrosoftAzure.NearlyallmobileapplicationsthatyouwilldevelopwillrequiretheabilitytocommunicatewithanAPItostore,retrieve,update,anddeleteinformation.ThisAPIcanbeanexistingonethatsomeonewithinyourorganizationhasalreadycreated,butsometimesyouwillneedtocreateyourownAPIforyourapplication.
MicrosoftAzure,or("Azure"asit'sbestknownfor),isessentiallyacloud-basedplatformthatwascreatedbyMicrosoftbackinFebruary2010.Azurewasdesignedforbuilding,deploying,andmanagingseveralapplicationsandtheirassociatedservices,suchasSaaS,PaaS,andIaaS.
EachoftheMicrosoftAzurespecificassociatedservicesareexplainedinthefollowingtable:
Azureservice Description
SaaS SoftwareasaServiceprovidessoftwarelicensinganddeliverymodelswheresoftwareislicensedonasubscriptionbasisandiscentrallyhosted.
PaaSPlatformasaServiceprovidescustomerswithaplatformtodevelop,run,andmanageapplicationswithoutthecomplexitiesofmaintainingtheinfrastructurewhendevelopingandlaunchinganapp.
IaaS InfrastructureasaServiceprovidesvirtualizedcomputingresourcesovertheInternet.
OneofthemainbenefitsofusingMicrosoftAzureMobileAppsisthattheyprovideyouwithaveryquickandeasywaytogetafullyfunctionalbackendserviceupandrunningwithinamatterofminutes.BeforewecanproceedwithsettingupandcreatingourTrackMyWalksdatabasewithinthecloud,youwillneedtohaveaMicrosoftAzureaccount.Ifyoudon'talreadyhaveone,youcancreateoneforfreeathttps://azure.microsoft.com/en-us/pricing/free-trial/.
OnceyouhavecreatedyourMicrosoftAzureaccount,youwillneedtologintotheMicrosoftAzureportalusingyourwebbrowser.Let'slookathowtodothis,byperformingthefollowingsteps:
1. LaunchyourwebbrowserwiththefollowingURLhttps://portal.azure.com/andlogintotheMicrosoftAzureportalusingyourcredentials.
2. Next,fromthemainMicrosoftAzureportaldashboard,clickthe+buttoninthetop-lefthandcornerfromtheNewsection,andselecttheWeb+Mobileoption,andthenchoosethe
MobileAppoptionasshowninthefollowingscreenshot:
3. Then,enterinTrackMyWalkstouseasthenameforourappfortheAppnamefield.4. Next,eitherchooseyourSubscriptiontype,orleavethedefaultofFreeTrial.5. Then,provideanameforyourResourceGroup,eitherbycreatinganewone,orchoosing
fromanexistingone.6. Next,ensurethatthePintodashboardoptionhasbeenselected,sothatyoucanhaveyour
MobileAppdisplayedontheMicrosoftAzureDashboard.Thisisparticularlyuseful,andprovideseasyaccess.
NowthatwehavesuccessfullycreatedourMobileAppwithinMicrosoftAzure,ournextstepistobeginsettingupthedatabasethatwillallowourapptostorewalkentryinformation.Let'slookathowwecanachievethiswiththefollowingsteps:
1. FromtheDashboard,clickontheTrackMyWalksservice,asshowninthefollowingscreenshot:
2. Next,choosetheDataconnectionsoptionfromtheMOBILEsectionundertheTrackMyWalksAppServicesection,asshowninthefollowingscreenshot:
3. Then,withintheTrackMyWalks-Dataconnectionsscreen,clickonthe+Addbutton,todisplaytheAdddataconnectionscreen,asshowninthefollowingscreenshot:
4. Next,ensurethatyouhaveselectedSQLDatabasefromtheTypedropdown,andproceedtoconfigureyourSQLDatabase.
5. Then,clickontheOKbuttontosaveyourchangesandcreatethenewdataconnectionforourTrackMyWalksSQLserverdatabase.
OnceyouhavecreatedyourTrackMyWalksmobileappandSQLdatabasewithinMicrosoftAzure,bydefault,yourdatabasewon'tcontainanydatabasetablesordata.BeforewecanstartcommunicatingwithandconsumingtheAPIwithinourTrackMyWalksapp,weneedtocreateanewtablethatwillstoreourwalktrailentries.
Let'slookatthefollowingstepstoachievethis:
1. FromtheDashboard,clickontheTrackMyWalksservice,thenchoosetheEasytablesoptionfromtheMOBILEsectionundertheTrackMyWalksAppServicesection.
2. Next,withintheTrackMyWalks-Easytablesscreen,clickonthe+Addbutton,todisplaytheAddatablescreen,asshowninthefollowingscreenshot:
3. Then,enterinWalkEntriestouseasthenameforourtablefortheNamefield.4. Next,leavethedefaultpermissionsthathavebeensetforourInsertpermission,Update
permission,Deletepermission,Readpermission,andUndeletepermissiondropdownentries:
5. Then,clickontheOKbuttontosaveyourchanges,andyournewWalkEntriestablewillbeaddedtothelistofEasyTablesentries.
Note
WheneveryouchoosetheAllowanonymousaccesspermissionduringthecreationofyourtable,youareessentiallymakingtheAPIavailablewithoutprovidinganyspecificauthenticationheadersaspartoftheHTTPrequest.
BeforewecanstartmakingcallstoourAPIandconsumingthiswithinourTrackMyWalksapp,we'llrunaquickchecktoseeifourAPIendpointisworkingcorrectly.ThisisachievedbyissuingaGETHTTPrequestusingthecommandline,orifyou'dprefer,youcanuseaRESTconsoleclient.
6. Openyourterminalwindow,andtypeinthefollowingstatementfromthecommandlineasfollows:
Lastlogin:SunNov610:48:41onconsole
GENIESOFT-MAC-Mini:~stevendaniel$curl
https://trackmywalks.azurewebsites.net/tables/
walkentries--header"ZUMO-API-VERSION:2.0.0"
IfyouhaveseteverythingupcorrectlywithintheMicrosoftAzureportal,youshouldreceivebacka200(Success)statuscode,alongwithanemptycollectionintheresponsebodyasfollows:
Lastlogin:SunNov610:48:41onconsole
GENIESOFT-MAC-Mini:~stevendaniel$curl
https://trackmywalks.azurewebsites.net/tables/
walkentries--header"ZUMO-API-VERSION:2.0.0"
[]GENIESOFT-MAC-Mini:~stevendaniel$
Note
ThereareseveralRESTconsoleclientsthatexistforyoutochoosefrom,ifyoudon'talreadyhaveoneinstalled.ItendtousePostmanforhandlingRESTAPIs,whichyoucandownloadfromhttp://www.getpostman.com/.
NowthatwehavesuccessfullycreatedourTrackMyWalksAPIandWalkEntriesdatatablewithintheservice,wecanbeginmakingcallstoourAPIandreceivingthoseresponsemessagesdirectlybackfromtheAPI.Inthenextsection,wewillbegintoaddtheJson.NetandHttpClient.NETFrameworklibrariesthatwillberesponsibleforhandlingtheRESTAPIrequeststosaveandretrieveourwalkentrydetails.
AddingtheJson.NetNuGetpackagetotheTrackMyWalksappNowthatyouhavesetupandcreatedtheTrackMyWalksdatabasewithintheMicrosoftAzureplatform,ournextstepistoaddtheJson.NetNuGetpackagetoourTrackMyWalksPortableClassLibrarysolution.TheJson.Netpackageisahigh-performanceJSONframeworkforthe.NETplatformthatallowsyoutoserializeanddeserializeanytypeof.NETobjectwithhelpoftheJSONserializer.
WhenwestarttoincorporatethisframeworkwithinourTrackMyWalkssolution,wewillhavetheabilityofperformingLINQtoJSONcapabilitiesthatwillenableustocreate,parse,query,andmodifytheJSONstructurethatwereceivebackfromourMicrosoftAzureTrackMyWalksdatabasetable.
Let'slookathowtoaddtheJson.NetNuGetpackagetoourTrackMyWalksPortableClassLibrary,byperformingthefollowingsteps:
1. Right-clickonthePackagesfolderthatiscontainedwithintheTrackMyWalksPortableClassLibrarysolution,andchooseAddPackages...menuoption,asshowninthefollowingscreenshot:
2. ThiswilldisplaytheAddPackagesdialog,enterinJson.Netwithinthesearchdialog,andselecttheJson.Netoptionwithinthelist,asshowninthefollowingscreenshot:
3. Finally,clickontheAddPackagebuttontoaddtheNuGetpackagetothePackagesfolder,containedwithintheTrackMyWalksPortableClassLibrarysolution.
NowthatyouhaveaddedtheJson.NetNuGetpackage,ournextstepistoaddtheHttpClientframeworktoourTrackMyWalksPortableClassLibrary,whichwewillbecoveringinthenextsection.
AddingtheHttpClientNuGetpackagetotheTrackMyWalksappIntheprevioussection,weaddedtheJson.NetNuGetpackagetoourTrackMyWalkssolution.OurnextstepistoaddtheHTTPlibrarytoourTrackMyWalkssolutiontoenableittocommunicatewithanAPIoverHTTP.
Sinceweareusingboth.NETandC#tobuildourXamarin.Formsapplication,wecanleveragealibrarywithinthe.NETFrameworkcalledSystem.Net.Http.HttpClient.ThisHttpClientframeworkprovidesuswithamechanismofsendingandreceivingdatausingstandardHTTPmethods,suchasGETandPOST.
Let'slookathowtoaddtheHttpClientNuGetpackagetoourTrackMyWalksPortableClassLibrary,byperformingthefollowingsteps:
1. Right-clickonthePackagesfolderthatiscontainedwithintheTrackMyWalksPortableClassLibrarysolution,andchoosetheAddPackages...menuoption.Ifyoucan'trememberhowtodothis,youcanrefertothesectionentitledAddingtheJson.NetNuGetpackagetotheTrackMyWalksapplocatedwithinthischapter.
2. ThiswilldisplaytheAddPackagesdialog.Here,enterinHttpwithinthesearchdialog,andselecttheSystem.Net.Httpoption,asshowninthefollowingscreenshot:
3. Finally,clickontheAddPackagebuttontoaddtheNuGetpackagetothePackagesfolder,containedwithintheTrackMyWalksPortableClassLibrarysolution.
NowthatyouhaveaddedboththeJson.NetandSystem.Net.HttpNuGetpackagestooursolution,wecanbeginutilizingtheseframeworklibrariesasweprogressthroughoutthischapter.
UpdatingtheWalkEntriesmodeltousetheJson.NetframeworkInthissection,wewillbeginbyupdatingtheWalkEntriesdatamodeltotakeadvantageofourbackendservicecalls,whenwecreatethese,andthentheWalkDataService.csandWalkWebService.csfileswillcommunicateandinteractwithourMicrosoftAzureTrackMyWalksdatabasetostore,delete,andretrievewalkentryinformation.
Let'snowstarttomodifyandimplementthecoderequiredforourWalkEntriesclassmodel,byperformingthefollowingsteps:
EnsurethattheWalkEntries.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections:
//
//WalkEntries.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingNewtonsoft.Json;
namespaceTrackMyWalks.Models
{
publicclassWalkEntries
{
[JsonProperty("id")]
publicstringId{get;set;}
publicstringTitle{get;set;}
publicstringNotes{get;set;}
publicdoubleLatitude{get;set;}
publicdoubleLongitude{get;set;}
publicdoubleKilometers{get;set;}
publicstringDifficulty{get;set;}
publicdoubleDistance{get;set;}
publicUriImageUrl{get;set;}
}
}
Intheprecedingcodesnippet,wehavesuccessfullymodifiedthedatabasemodelthatwillbeusedtostorewalkentryinformationwithinourMicrosoftAzuredatabase.Youwillnoticethatwehavedefineda[JsonProperty("id")]item,aswellasastringpropertynamedIdthatwillserveasauniqueprimarykeyforeachrecordthatwestorewithinthedatabase.WehavealsoupdatedourImageUrlpropertytoincludetheUritypethatwillbeusedtoconverttheURLenteredwithinthewalkentrypage,sothatitisstoredcorrectlywithinthedatabase.
Note
IfyouareinterestedinfindingoutmoreinformationabouttheJsonPropertyandtheNewtonsoft.Jsonclasses,pleaserefertotheJson.NETdocumentationlocatedat
http://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_JsonProperty.htm.
CreatingtheHTTPwebserviceclassfortheTrackMyWalksappIntheprevioussection,wesuccessfullymodifiedtheWalkEntriesdatabasemodelthatwillbeusedbyourTrackMyWalksapplication.ThiswillallowustohavealivebackendservicethatwillenableourapplicationtocommunicateoverHTTPsothatitcansendrequeststotheAPItoretrieve,add,anddeletetrailwalkentries.Inthissection,wewillneedameansforourapptocommunicatewithourAPIoverHTTP,andthereforeitwillrequireanHTTPlibrary.
Sinceweareusing.NETandC#,wecanusealibrarywithinthe.NETFramework,calledSystem.Net.Http.HttpClient.ThisFrameworkprovidesamechanismforallowingourapptosendandreceivedatausingstandardHTTPmethodssuchasGETandPOST.WewillbeginbycreatingabaseserviceclasswithinourTrackMyWalksPortableClassLibrarythatwillberesponsibleforhandlingalltheHTTPcommunicationsforus.
Let'snowstarttoimplementthecoderequiredforourWalkWebServicebase-classmodel,byperformingthefollowingsteps:
1. CreateanemptyclasswithintheServicesfolder,bychoosingAdd|NewFile...,asyoudidinthesectionentitled,CreatingthenavigationserviceinterfacefortheTrackMyWalksappwithinChapter3,NavigatingwithintheMVVMModel-TheXamarin.FormsWay.
2. Then,enterinWalkWebServiceforthenameofthenewclassthatyouwanttocreate,andclickontheNewbuttontoallowthewizardtoproceedandcreatethenewfile.
3. Next,ensurethattheWalkWebService.csfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
//
//WalkWebService.cs
//TrackMyWalksHttpWebServiceClass
//
//CreatedbyStevenF.Danielon30/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Net.Http;
usingSystem.Text;
usingSystem.Threading.Tasks;
usingNewtonsoft.Json;
namespaceTrackMyWalks.Services
{
publicabstractclassWalkWebService
{
4. Then,weneedtocreateaprotectedasyncmethodcalledSendRequestAsync<T>thatacceptsaUrinamedurlaswellaHttpMethodnamedhttpMethod,andfinallyaDictionary<string,string>objectnamedheaders,aswellasanobjectnamed
requestData,thatwillbeusedtoconstructtheHTTPrequest.Proceedandenterinthefollowingcodesnippet:
protectedasyncTask<T>SendRequestAsync<T>(
Uriurl,HttpMethodhttpMethod=null,
IDictionary<string,string>headers=null,
objectrequestData=null)
{
5. Next,we'llsetuptheresulttothedefault(T)typethatwillreturnadefaultvaluetoaparameterizedtypesincewedon'tknowwhatourresultwillcontainatthispoint.We'llthendeclareourmethodvariabletocontaintheGETHttpMethod.ThiswillbeusedtoreturnthecontentandthencreatearequestvariablethatwillsetupaninstanceofourHttpRequestMessageclassandthenserializeourrequesteddatatoourrequestobjectandreturntheinformationbackinJsonformatasdefinedbytheapplication/jsontype.Proceedandenterinthefollowingcodesnippet:
varresult=default(T);
varmethod=httpMethod??HttpMethod.Get;
varrequest=newHttpRequestMessage(method,url);
//Serializeourrequestdata
vardata=requestData==null?null:
JsonConvert.SerializeObject(requestData);
if(data!=null)
{
//Addtheserializedrequestdatatoourrequest
//object.
request.Content=newStringContent(data,
Encoding.UTF8,"application/json");
}
6. Then,we'llbeginiteratingthroughourheaderscollectiontoaddeachofourspecificheaderstotherequestobjectthatwillbesentalongwiththeHttpRequestMessageclass.Proceedandenterinthefollowingcodesnippet:
//Addeachofthespecifiedheaderstoourrequest
if(headers!=null)
{
foreach(varhinheaders)
{
request.Headers.Add(h.Key,h.Value);
}
}
7. Next,we'llsetupanddeclareahandlervariablethatinstantiatesaninstanceoftheHttpClientHandlerclasswhichisessentiallyaHttpMessageHandlerthatcontainsacommonsetofpropertiesthatworkacrosstheHttpWebRequestAPI.Inthenextstep,we'lldeclareaclientvariablethatinstantiatesourHttpClientclassthatacceptsourhandlervariabletobeginsendingourrequestoverHTTP.
8. Next,we'lldeclareourresponseobjectthatperformsaSendAsyncandacceptsourrequestobject,alongwithourHttpCompletionOption.ResponseContentReadthat
completesafterreadingtheentireresponsecontent.9. Finally,we'llperformacomparisonchecktoseeifwehavesuccessfullyreadourcontent
andhavearesponsecodeof200(Success)returned,beforedeserializingourcontentintoJsonformat,usingtheJsonConvertmethod.Proceedandenterinthefollowingcodesnippet:
//GetaresponsefromourWebService
varhandler=newHttpClientHandler();
varclient=newHttpClient(handler;
varresponse=awaitclient.SendAsync(request,
HttpCompletionOption.ResponseContentRead);
if(response.IsSuccessStatusCode&&
response.Content!=null)
{
varcontent=awaitresponse.Content.
ReadAsStringAsync();
result=JsonConvert.DeserializeObject<T>(content);
}
returnresult;
}
}
}
NowthatwehavesuccessfullycreatedourbaseHTTPserviceclass,wecanbegintousethiswithinourViewModelsaswellasourWalkEntriesdatabasemodel,bycreatingabasesub-classwithinourDataServiceAPIwhichwewillbecoveringinthenextsection.
Note
IfyouareinterestedinfindingoutmoreinformationabouttheHttpClientHandlerandHttpClientclasses,pleaserefertotheMicrosoftdeveloperdocumentationlocatedathttps://msdn.microsoft.com/en-us/library/system.net.http.httpclient(v=vs.118).aspx.
CreatingtheDataServiceAPIfortheTrackMyWalksappIntheprevioussection,wecreatedaWalkWebServiceclassthatprovidesuswithameansofsendingHTTPrequeststoourTrackMyWalksMicrosoftAzuredatabase.Inthissection,wewillbeginbycreatingadataserviceclassthatwillallowustosendandreceiveresponsesbackfromourAPI,inJsonformatwhichwillupdatetheWalkEntriesdatabasemodelsoourapplicationcanusethis.
WewillbeginbycreatingtheinterfaceforourdataservicethatcanbeusedtocommunicatewitheachoftheViewModelsthatourTrackMyWalksapplicationutilizes.Let'snowstarttoimplementthecoderequiredforourIWalkDataServicebase-classmodel,byperformingthefollowingsteps:
1. CreateanemptyinterfacewithintheServicesfolder.Ifyoucan'trememberhowtodothis,youcanrefertothesectionentitledCreatingthenavigationserviceinterfacefortheTrackMyWalksappwithinChapter3,NavigatingwithintheMVVMModel-TheXamarin.FormsWay.
2. Next,enterinIWalkDataServiceforthenameofthenewinterfacethatyouwanttocreate,andclickontheNewbuttontoallowthewizardtoproceedandcreatethefile.
3. Then,ensurethattheIWalkDataService.csfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
//
//IWalkDataService.cs
//TrackMyWalksDataServiceInterface
//
//CreatedbyStevenF.Danielon30/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Threading.Tasks;
usingSystem.Collections.Generic;
usingTrackMyWalks.Models;
namespaceTrackMyWalks.Services
{
publicinterfaceIWalkDataService
{
Task<IList<WalkEntries>>GetWalkEntriesAsync();
TaskAddWalkEntryAsync(WalkEntriesentry);
TaskDeleteWalkEntryAsync(WalkEntriesentry);
}
}
Intheprecedingcodesnippet,webeginbyimplementingthemethodsthatwillberequiredtoretrieve,update,anddeleteourWalkEntriesinformation.TheGetWalkEntriesAsyncinstance
methodusesagenerictypewhichisusedtorestricttheWalkEntriestouseobjectsoftheIListclass.
TheAddWalkEntryAsyncinstancemethodacceptsanentryparameterthatcontainsthewalkentrydetailstobeaddedoftypeWalkEntries,andourDeleteWalkEntryAsyncinstancemethodacceptsanentryparameterthatneedstobedeletedfromourdatabase.WeusetheTaskclasstoessentiallyhandleallasynchronousoperations,byensuringthattheasynchronousmethodsthatweinitiatewilleventuallyfinish,thuscompletingthetaskinhand.
CreatingtheDataServiceAPIclassfortheTrackMyWalksappIntheprevioussection,wecreatedourdataservicebaseinterfaceclassforourdataservice,andwedefinedseveraldifferentinstancemethodsthatourclasswillbeutilizing.ThiswillessentiallybeusedbyeachofourViewModelsalongwiththeViews(pages).Let'snowstarttoimplementthecoderequiredforourWalkDataServiceclass,byperformingthefollowingsteps:
1. CreateanemptyclasswithintheServicesfolder,bychoosingAdd|NewFile...,asyoudidwhencreatingtheDataServiceinterfaceintheprevioussectionentitledCreatingtheDataServiceAPIfortheTrackMyWalksapplocatedwithinthischapter.
2. Then,enterinWalkDataServiceforthenameofthenewclassthatyouwanttocreate,andclickontheNewbuttontoallowthewizardtoproceedandcreatethenewfile.
3. Next,ensurethattheWalkDataService.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//WalkDataService.cs
//TrackMyWalksAPIDataServiceClass
//
//CreatedbyStevenF.Danielon30/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Threading.Tasks;
usingSystem.Net.Http;
usingTrackMyWalks.Models;
usingNewtonsoft.Json;
namespaceTrackMyWalks.Services
{
4. Next,weneedtomodifyourWalkDataServiceclassconstructor,sothatitcaninheritfrombothourWalkWebServicebase-classaswellastheIWalkDataServiceclass:
publicclassWalkDataService:WalkWebService,IWalkDataService
{
5. Next,we'lldeclaretwoprivateclassproperties,_baseUriand_headers.The_baseUripropertywillbeusedtostorethebaseURLandthe_headerspropertyoftheIDictionary<string,string>typethatwillbeusedtostoretheheaderinformationthatwewanttopasstoourWalkWebServiceclass.
6. Thezumo-api-versionisbasicallyaspecialheadervaluethatisusedbytheHTTPclientwhencommunicatingwithMicrosoftAzuredatabases.Proceedandenterinthefollowingcodesnippet.
readonlyUri_baseUri;
readonlyIDictionary<string,string>_headers;
//OurClassConstructorthatacceptstheAzureDatabase
//Uripath
publicWalkDataService(UribaseUri)
{
_baseUri=baseUri;
_headers=newDictionary<string,string>();
_headers.Add("zumo-api-version","2.0.0");
}
7. Next,we'llimplementtheGetWalkEntriesAsyncinstancemethodthatwillretrieveallWalkEntriesthatarecontainedwithinourdatabase.We'lldefineaurlvariablethatconstructsanewUriobjectusingour_baseUriURLandcombiningthiswithourwalkEntriesdatabasetablewithinthetablessectionofourTrackMyWalksAzuredatabaseandcalltheSendRequestAsyncWalkWebServicebase-classinstancemethod.
8. We'llpassintheHttpMethod.GetmethodtypetotellourbaseclassthatwearereadytoretrieveourWalkEntries.Proceedandenterinthefollowingcodesnippet:
//APItoretrieveourWalkEntriesfromourdatabase
publicasyncTask<IList<WalkEntries>>GetWalkEntriesAsync()
{
varurl=newUri(_baseUri,"/tables/walkentries");
returnawaitSendRequestAsync<WalkEntries[]>
(url,HttpMethod.Get,_headers);
}
9. Next,we'llimplementtheAddWalkEntryAsyncinstancemethodthatwilladdwalkentryinformationtothewalkEntriestablecontainedwithinourdatabase.WedefineaurlvariablethatconstructsanewUriobjectusingour_baseUriURLandcombiningthiswiththewalkEntriesdatabasetablewithinthetablessectionofourTrackMyWalksAzuredatabaseandcalltheSendRequestAsyncWalkWebServicebase-classinstancemethod.
10. We'llpassintheHttpMethod.PostmethodtypethatwilltellourbaseclassthatwearereadytosubmitinformationtoourwalkEntriesdatabasetable.Proceedandenterinthefollowingcodesnippet:
//APItoaddourWalkEntryinformationtothedatabase
publicasyncTask<WalkEntries>AddWalkEntryAsync(
WalkEntriesentry)
{
varurl=newUri(_baseUri,"/tables/walkentries");
awaitSendRequestAsync<WalkEntries>(url,HttpMethod.Post,
_headers,entry);
}
11. Next,we'llimplementtheDeleteWalkEntryAsyncinstancemethodthatwillpermanentlydeleteassociatedwalkentryinformationfromourwalkEntriestablecontainedwithinourdatabase.We'lldefineaurlvariablethatconstructsanewUriobjectusingour_baseUriURLandcombiningthiswithourwalkEntriesdatabasetable,andpassintheIdvalueofourselectedwalkentrywithintheWalksPageListView.
12. We'llthencalltheSendRequestAsyncWalkWebServicebaseclassinstancemethod.We'llpassintheHttpMethod.DeletemethodtypethatwilltellourbaseclassthatwearereadytopermanentlydeletethewalkentrywithinourwalkEntriesdatabasetable.Proceedand
enterinthefollowingcodesnippet:
//APItodeleteourWalkEntryfromthedatabase
publicasyncTaskDeleteWalkEntryAsync(WalkEntriesentry)
{
varurl=newUri(_baseUri,string.Format("/tables/walken
tries/{0}",entry.Id));
awaitSendRequestAsync<WalkEntries>(url,HttpMethod.Delete
,_headers);
}
}
}
Intheprecedingcodesnippet,webeganbyimplementingeachoftheinstancemethodsthatwedefinedwithinourIWalkDataServiceinterfaceclass.WeusedtheSendRequestAsyncmethodonourbaseclass,andpassedintheAPIdetails,alongwiththeHttpMethodtype,andthezumo-api-versionheaderinformation.YouwillhavenoticedthatwepassedintheWalkEntriesdatamodelobject.ThisissothattheobjectcanbeserializedandaddedtotheHTTPrequestmessagecontent.
Note
IfyouareinterestedinlearningmoreHTTP,pleaserefertotheHypertextTransferProtocolguidelocatedathttps://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol.
TheHTTPclassexposesseveraldifferenttypesofHTTPmethodsthatareusedbytheHttpMethodclass,whichareexplainedinthefollowingtable.
HTTPmethods Description
GET
ThistypetellstheHttpMethodclassprotocolthatwearereadytorequestmessagecontentoverHTTPtoretrieveinformationfromourAPI,andreturnthisinformationback,basedontherepresentationformatspecifiedwithintheAPI.
POSTThistypetellstheHttpMethodclassprotocolthatwewanttocreateanewentrywithinourtable,asspecifiedwithinourAPI.
DELETEThistypetellstheHttpMethodclassprotocolthatwewanttodeleteanexistingentrywithinourtable,asspecifiedwithinourAPI.
Note
IfyouareinterestedinlearningmoreaboutclientandserverversioninginMobileAppsand
MobileServices,pleaserefertotheMicrosoftAzuredocumentationlocatedathttps://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-client-and-server-versioning/.
Inthenextsection,wewillupdateourWalkBaseViewModelsothatitcanuseourDataServiceAPIclass,andinitializeourMicrosoftAzureTrackMyWalksdatabase.
UpdatingtheWalkBaseViewModeltouseourDataServiceAPIIntheprevioussections,wecreatedtheinterfacesclassesthatwillbeusedbyourWalkWebServiceandWalkDataServiceclasstoenableourTrackMyWalksapplicationtocommunicatewiththedatabasethatisstoredwithintheMicrosoftAzureplatform.
OurnextstepistobeginimplementingthecodethatwillberequiredtomakeaconnectiontoourTrackMyWalksMicrosoftAzuredatabasesothatourappuseslivedatainsteadofthelocal,hard-codeddatathatwecurrentlyhaveinplace.
Let'slookathowwecanachievethis,byfollowingthesteps:
1. EnsurethattheWalkBaseViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesectionsasshowninthecodesnippet:
//
//WalkBaseViewModel.cs
//TrackMyWalksBaseViewModel
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.ComponentModel;
usingSystem.Runtime.CompilerServices;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Services;
namespaceTrackMyWalks.ViewModels
{
publicabstractclassWalkBaseViewModel:
INotifyPropertyChanged
{
protectedIWalkNavServiceNavService{
get;privateset;}
bool_isProcessBusy;
publicboolIsProcessBusy
{
get{return_isProcessBusy;}
set
{
_isProcessBusy=value;
OnPropertyChanged();
OnIsBusyChanged();
}
}
2. Next,we'llcreateaIWalkDataServiceinterfacepropertycalled_azureDatabaseService.TiswillstoreandretrievethevalueofourTrackMyWalksAzuredatabaseURL.TheAzureDatabaseServicepropertycontainsboththegetter(get)andsetter(set)implementationsthat.WhenwesettheAzureDatabaseServiceproperty,weassignthisvaluetoour_azureDatabaseServicevariable,andthencallthe
OnPropertyChangedinstancemethodstotelltheViewModelsthatachangehasbeenmade:
IWalkDataService_azureDatabaseService;
publicIWalkDataServiceAzureDatabaseService
{
get{return_azureDatabaseService;}
set
{
_azureDatabaseService=value;
OnPropertyChanged();
}
}
3. Then,we'llneedtomodifytheWalkBaseViewModelclassconstructorandcreateaninstancetoourWalkDataServiceclass,thatinheritsfromtheIWalkDataServiceinterfaceclass,andthenassignthistoourAzureDatabaseServicepropertysothatitcanbeusedthroughouteachofourViewModels:
protectedWalkBaseViewModel(IWalkNavServicenavService)
{
//DeclareourNavigationServiceandAzureDatabaseURL
varWALKS_URL="https://trackmywalks.azurewebsites.net";
NavService=navService;
AzureDatabaseService=newWalkDataService(new
Uri(WALKS_URL,UriKind.RelativeOrAbsolute));
}
publicabstractTaskInit();
publiceventPropertyChangedEventHandlerPropertyChanged;
protectedvirtualvoidOnPropertyChanged([CallerMemberName]
stringpropertyName=null)
{
varhandler=PropertyChanged;
if(handler!=null)
{
handler(this,newPropertyChangedEventArgs(
propertyName));
}
}
protectedvirtualvoidOnIsBusyChanged()
{
//WeareprocessingourWalksTrailInformation
}
}
publicabstractclassWalkBaseViewModel<WalkParam>:
WalkBaseViewModel{
protectedWalkBaseViewModel(IWalkNavServicenavService):
base(navService)
{
}
publicoverrideasyncTaskInit()
{
awaitInit(default(WalkParam));
}
publicabstractTaskInit(WalkParamwalkDetails);
}
}
Intheprecedingcodesnippet,webeginbycreatingaAzureDatabaseServicepropertythatinheritsfromourIWalkDataServiceclass,andthencreateditsassociatedgetterandsetterqualifiers.Next,weupdatedtheWalkBaseViewModelclassconstructortosettheAzureDatabaseServicepropertytoaninstanceofWalkDataServiceclasssothatitcanbeusedthroughouteachofourViewModels.
UpdatingtheWalkEntryViewModeltouseourDataServiceAPINowthatwehavemodifiedourWalkBaseViewModelclasssothatitwillbeusedbyanyViewModelthatinheritsfromthisbaseclass,ournextstepistobeginmodifyingtheWalkEntryViewModelthatwillutilizeourWalkDataServiceclass,sothatanynewwalkinformationthatisenteredwillbesavedbacktoourAzuredatabase.Oncethat'sdonewecanbeginstoringwalkentryinformationwhentheSavebuttonispressed.
Let'slookathowwecanachievethiswiththefollowingsteps:
1. EnsurethattheWalkEntryViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesectionsasshowninthecodesnippet:
//
//WalkEntryViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.Threading.Tasks;
usingTrackMyWalks.Models;
usingTrackMyWalks.Services;
usingTrackMyWalks.ViewModels;
usingXamarin.Forms;
usingSystem;
namespaceTrackMyWalks
{
publicclassWalkEntryViewModel:WalkBaseViewModel
{
IWalkLocationServicemyLocation;
string_title;
publicstringTitle
{
get{return_title;}
set
{
_title=value;
OnPropertyChanged();
SaveCommand.ChangeCanExecute();
}
}
...
...
2. Next,locateandmodifytheExecuteSaveCommandinstancemethodtoincludeachecktoseeifourImageUrlfieldcontainsavalue,otherwisestoreanemptyplaceholderimagefortheImageUrlpropertywhentheSaveToolBarItemhasbeenpressed.Proceedandenterinthefollowinghighlightedcodesections:
asyncTaskExecuteSaveCommand()
{
//Checktoseeifweareinthemiddleofprocessing
//arequest.
if(IsProcessBusy)
return;
//InitialiseourWalkEntryModeltostatethatweare
//inthemiddleofupdatingdetailstothedatabase.
IsProcessBusy=true;
//SetupourNewWalkitemmodel
varnewWalkItem=newWalkEntries
{
Title=this.Title,
Notes=this.Notes,
Latitude=this.Latitude,
Longitude=this.Longitude,
Kilometers=this.Kilometers,
Difficulty=this.Difficulty,
Distance=this.Distance,
ImageUrl=(this.ImageUrl==null?
newUri("https://heuft.com/upload/image/4
00x267/no_image_placeholder.png"):
newUri(this.ImageUrl))
};
//UponexitingourNewWalkEntryPage,weneedto
//stopcheckingforlocationupdates
myLocation=null;
3. Then,we'llmakeacalltoourAddWalkEntryAsyncinstancemethod,containedwithintheAzureDatabaseServiceclasstostorethenewlyenteredinformation.We'llincludeareferencetoourNavService.PreviousPagemethod,whichisdeclaredwithintheIWalkNavServiceinterfaceclasstoallowourWalkEntryPagetonavigatebacktothepreviouscallingpage,beforefinallyinitializingourIsProcessBusyindicatortofalsetoinformourViewModelthatwearenolongerprocessing.Proceedandenterinthefollowinghighlightedcodesections:
try
{
//SavethedetailsenteredtoourAzureDatabase
awaitAzureDatabaseService.AddWalkEntryAsync(newWalkItem);
awaitNavService.PreviousPage();
}
finally
{
//Re-InitialiseourProcessBusyIndicator
IsProcessBusy=false;
}
}
//methodtocheckforanyformerrors
boolValidateFormDetails()
{
return!string.IsNullOrWhiteSpace(Title);
}
publicoverrideasyncTaskInit()
{
awaitTask.Factory.StartNew(()=>
{
Title="NewWalk";
Difficulty="Easy";
Distance=1.0;
});
}
}
}
Intheprecedingcodesnippet,wemodifiedtheExecuteSaveCommandinstancemethodtoincludeachecktoseeifourImageUrlfieldcontainsavaluepriortostoringtheinformationwithintheImageUrlproperty.Ifwehavedeterminedthatthispropertyisempty,weproceedtoassignanemptyplaceholderimagefortheImageUrlpropertytoavoidourapplicationperformingunexpectedresults.
Inthenextstep,weattempttomakeacalltoourAddWalkEntryAsyncinstancemethod,containedwithinourAzureDatabaseServiceclasstostorethenewlyenteredinformation.
Next,we'llincludeareferencetoourNavService.PreviousPagemethod,whichisdeclaredwithintheIWalkNavServiceinterfaceclasstoallowourWalkEntryPagetonavigatebacktothepreviouscallingpagewhentheSavebuttonhasbeenpressed.Finally,we'llinitializeourIsProcessBusyindicatortofalsetoinformourViewModelthatwearenolongerprocessing.
UpdatingtheWalksPageViewModeltouseourDataServiceAPIInthissection,wewillproceedtoupdateourWalksPageViewModelViewModeltoreferenceourWalkDataServiceclass.SinceourWalksPageViewModelisusedtodisplayinformationfromourWalkEntriesmodel,wewillneedtoupdatethissothatitretrievesthisinformationfromtheTrackMyWalksdatabase,locatedwithinourMicrosoftAzureplatform,anddisplaythiswithintheListViewcontrol.
Let'slookathowwecanachievethiswiththefollowingsteps:
1. EnsurethattheWalksPageViewModel.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesectionsasshowninthefollowingcodesnippet:
//
//WalksPageViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Collections.ObjectModel;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Models;
usingTrackMyWalks.Services;
usingXamarin.Forms;
namespaceTrackMyWalks.ViewModels
{
publicclassWalksPageViewModel:WalkBaseViewModel
{
ObservableCollection<WalkEntries>_walkEntries;
publicObservableCollection<WalkEntries>walkEntries
{
get{return_walkEntries;}
set
{
_walkEntries=value;
OnPropertyChanged();
}
}
...
...
2. Next,locateandmodifytheLoadWalkDetailsinstancemethodtochecktoseeifwearealreadyinthemiddleofprocessingwalktrailitemswithintheListView.Nextwe'llproceedtopopulateourWalkEntriesarraywithitemsretrievedfromourGetWalkEntriesAsyncAzurewebserviceinstancemethodcalltopopulateourWalkEntriesasynchronouslyandusetheawaitkeywordtowaituntiltheTaskcompleted.Finally,we'llinitializeourIsProcessBusyindicatortofalsetoinformourViewModelthatwearenolongerprocessing.Proceedandenterinthefollowinghighlightedcode
sections:
publicasyncTaskLoadWalkDetails()
{
//Checktoseeifwearealreadyprocessingour
//WalkTrailItems
if(IsProcessBusy)
{return;}
//Ifwearen'tcurrentlyprocessing,weneedto
//initialiseourvariabletotrue
IsProcessBusy=true;
try
{
//PopulateourWalkEntriesarraywithitems
//fromourAzureWebService
walkEntries=newObservableCollection<WalkEntries>
(awaitAzureDatabaseService.GetWalkEntriesAsync());
}
finally
{
//Re-initialiseourprocessbusyvaluebacktofalse
IsProcessBusy=false;
}
}
...
...
3. Then,createtheDeleteWalkItemcommandpropertywithinourclass,thatwillbeusedwithinourWalksPagetohandlewheneverweclickonawalkitemwithinourListView.TheDeleteWalkItempropertywillthenrunanaction,whenevertheDeleteoptionhasbeenchosenfromtheActionSheet,todeletethechosenitemasdeterminedbyourtrailDetailstopermanentlyremovetherecordfromourTrackMyWalksdatabaselocatedwithinourMicrosoftAzureplatform.Proceedandenterinthefollowinghighlightedcodesections:
Command_deleteWalkItem;
publicCommandDeleteWalkItem
{
get
{
return_deleteWalkItem
??(_deleteWalkItem=
newCommand(async
(trailDetails)=>
awaitAzureDatabaseService.
DeleteWalkEntryAsync((WalkEntries)trailDetails)));
}
}
}
}
Intheprecedingcodesnippet,wemodifiedourLoadWalkDetailsinstancemethodtochecktoseeifwearealreadyinthemiddleofprocessingwalktrailitemswithintheListView.Then,weproceededtopopulateourWalkEntriesarraywithitemsretrievedfromourGetWalkEntriesAsyncAzurewebserviceinstancemethodcalltopopulateourWalkEntriesasynchronouslyandusetheawaitkeywordtowaituntiltheTaskcompleted.
Inthenextstep,weinitializedtheIsProcessBusyindicatortofalse,toinformourViewModelthatwearenolongerprocessing.
Finally,wecreatedaCommandpropertywithinourclassthatwillbeusedtopermanentlyhandlethedeletionofthechosenwalkentrywithinourListViewandourTrackMyWalksMicrosoftAzuredatabase.
UpdatingtheWalksPagetousetheupdatedViewModelInthissection,weneedtoupdateourWalksPageContentPagesothatitcanreferencetheupdatedchangeswithinourWalksPageViewModel.WewillneedtoapplyadditionallogictohandledeletionsofwalkentryinformationfromourWalkEntriesmodelsothatitcanretrievenewlyupdatedinformationfromourTrackMyWalksdatabaselocatedwithinourMicrosoftAzureplatform,anddisplaythiswithintheListViewcontrol.
Let'slookathowwecanachievethiswithfollowingsteps:
1. EnsurethattheWalksPage.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesectionsasshowninthecodesnippet:
//
//WalksPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
usingTrackMyWalks.Services;
usingTrackMyWalks.DataTemplates;
usingTrackMyWalks.ValueConverters;
usingSystem.Diagnostics;
namespaceTrackMyWalks
{
publicclassWalksPage:ContentPage
{
WalksPageViewModel_viewModel
{
get{returnBindingContextas
WalksPageViewModel;}
}
2. Next,modifythenewWalkItemvariable,whichiswithintheWalksPageclassconstructorandupdatetheTextpropertyforourToolbarItem.Proceedandenterinthefollowinghighlightedcodesections:
publicWalksPage()
{
varnewWalkItem=newToolbarItem
{
Text="Add"
};
//SetupourBindingclickeventhandler
newWalkItem.SetBinding(ToolbarItem.CommandProperty,
"CreateNewWalk");
//AddtheToolBaritemtoourToolBar
ToolbarItems.Add(newWalkItem);
//DeclareandinitialiseourModelBindingContext
BindingContext=newWalksPageViewModel(DependencyService
.Get<IWalkNavService>());
...
...
3. Then,weneedtomodifyourwalksList.ItemTappedmethodwheneveranitemwithintheListViewhasbeenselected.Here,weneedtodisplayaselectionofchoicesfortheusertochoosefrom,usingtheDisplayActionSheetmethod.WhentheuserchoosestheProceedWithoption,theuserwillbenavigatedtotheWalksTrailpagewithinourViewModel,andpassintheitemthathasbeenselected.Alternatively,iftheuserchoosestheDeletebutton,acallismadetoourDeleteWalkItemcommandthatisincludedwithinourWalksPageViewModelclass,sothatitcanthenpermanentlydeletetheWalkEntryfromourTrackMyWalksAzuredatabase.Oncethewalkentryhasbeendeletedfromthedatabase,theuserwillreceiveapop-upnotificationtellingthemthattheitemhasbeendeleted.Proceedandenterinthefollowinghighlightedcodesections:
//InitializeoureventHandlertousewhenthe
//itemistapped
walksList.ItemTapped+=async(objectsender,
ItemTappedEventArgse)=>
{
//Gettheselecteditembytheuser
varitem=(WalkEntries)e.Item;
//Checktoseeifwehaveavalueforouritem
if(item==null)return;
//Displayanactionsheetwithchoices
varaction=awaitDisplayActionSheet("TrackMyWalks
-TrailDetails","Cancel","Delete",
"ProceedWith"+item.Title+"Trail");
if(action.Contains("Proceed"))
{
_viewModel.WalkTrailDetails.Execute(item);
}
//IfwehavechosenDelete,deletetheitemfrom
//ourdatabaseandrefreshtheListView
elseif(action.Contains("Delete"))
{
_viewModel.DeleteWalkItem.Execute(item);
awaitDisplayAlert("TrackMyWalks-TrailDetails",
item.Title+
"hasbeendeletedfromthedatabase.","OK");
await_viewModel.Init();
}
//Initialiseouritemvariabletonull
item=null;
};
...
...
}
}
Intheprecedingcodesnippet,wemodifiedournewWalkItemvariable,whichiswithintheWalksPageclassconstructorandupdatedtheTextpropertyforourToolbarItem.
Next,wemodifiedourwalksList.ItemTappedmethodtohandlesituationswhenanitemhasbeenselectedfromtheListView,whichwilldisplayaselectionofchoicesfortheusertochoosefrom.WeaccomplishedthisbyusingtheDisplayActionSheetmethod.WhentheuserchoosestheDeletebutton,acallismadetoourDeleteWalkItemcommandthatisincludedwithinourWalksPageViewModelclass,sothatitcanthenpermanentlydeletethewalkentryfromourTrackMyWalksAzuredatabase,andusetheawaitkeywordtowaituntiltheTaskhascompletedbeforedisplayingapop-upnotificationtellingthemthattheitemhasbeendeleted.
UpdatingthecustompickerrendererclassfortheiOSplatformNowthatwehavemodifiedourWalkEntriesdatabasemodel,wewillneedtoupdatetheDifficultyPickerCellRendererclasswhichwillbeusedbyouriOSportionoftheDifficultyPickerEntryCellclass.
ThiscustompickerwillbeusedtoobtaintheitemchosenfromthecustomlistofentriesdefinedwithintheDifficultyPickerclassandstorethiswithintheDifficultypropertythatwillthenbewrittentoourTrackMyWalksMicrosoftAzuredatabase.
Let'slookathowwecanachievethiswiththefollowingsteps:
1. OpentheTrackMyWalks.iOSprojectlocatedwithinourTrackMyWalkssolution,andexpandtheRenderersfolder.
2. Next,selecttheDifficultyPickerCellRenderer.csfileandensurethatitisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesectionsinthecodesnippet:
//
//DifficultyPickerCellRenderer.cs
//TrackMyWalksCustomRendererforUIPickerViewEntryCells
(iOS)
//
//CreatedbyStevenF.Danielon01/10/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms.Platform.iOS;
usingUIKit;
usingTrackMyWalks.Controls;
usingXamarin.Forms;
usingTrackMyWalks.iOS.Renderers;
[assembly:ExportRenderer(typeof(
DifficultyPickerEntryCell),
typeof(DifficultyPickerCellRenderer))]
namespaceTrackMyWalks.iOS.Renderers
{
publicclassDifficultyPickerCellRenderer:
EntryCellRenderer
{
publicoverrideUITableViewCellGetCell(
Cellitem,UITableViewCellreusableCell,
UITableViewtv)
{
varcell=base.GetCell(item,reusableCell,tv);
varentryPickerCell=(EntryCell)item;
UITextFieldtextField=null;
if(cell!=null)
textField=
(UITextField)cell.ContentView.Subviews[0];
//CreateouriOSUIPickerViewNativeControl
vardifficultyPicker=newUIPickerView
{
AutoresizingMask=
UIViewAutoresizing.FlexibleWidth,
ShowSelectionIndicator=true,
Model=newDifficultyPickerModel(),
BackgroundColor=UIColor.White,
};
3. Next,weneedtomodifytheEntryCellRendererGetCellmethodsothatitcanupdatetheDifficultypropertyfortheEntryCellwearecurrentlyonwhentheDonebuttonhasbeenpressed.ItwillupdateitwiththevaluefromthedifficultyPickerobjectandthendismissthecustompickercontrol.Proceedandenterinthefollowinghighlightedcodesections:
//Createatoolbarwithadonebuttonthatwill
//settheselectedvaluewhenclosed.
vardone=newUIBarButtonItem("Done",
UIBarButtonItemStyle.Done,(s,e)=>
{
//UpdatetheDifficultypropertyontheCell
if(entryPickerCell!=null)
entryPickerCell.Text=DifficultyPickerModel.
difficulty[difficultyPicker.
SelectedRowInComponent(0)];
//UpdatethevalueoftheUITextFieldwithinthe
//Cell
if(textField!=null)
{
textField.Text=DifficultyPickerModel.difficulty
[difficultyPicker.SelectedRowInComponent(0)];
textField.ResignFirstResponder();
}
});
...
...
}
}
}
NowthatwehaveappliedthecodechangesrequiredtotheDifficultyPickerCellRendererclassforouriOSportionofourTrackMyWalksapp,thenextstepistomakechangestoourWalkEntryContentPagesothatitwillretrievethecorrectdifficultyvaluethatisreturnedfromourcustompicker,andtheDifficultypropertyvalue.
UpdatingtheWalksEntryPagetousetheupdatedcustompickerIntheprevioussection,wemodifiedourDifficultyPickerCellRendererclass,aswellasdefiningthevariousmethodsthatwillhandlethedisplayoftheUIPickerViewcontrolwhenanEntryCellwithintheViewModelhasbeentapped.
Inthissection,we'lllookatmakingthenecessarycodechangesrequiredsothatourWalkEntryPageContentPagecorrectlyretrievesthelevelofdifficultychosenfromourcustomUIPickerViewDifficultyPickerCellRendererclass.
Let'slookathowwecanachievethiswiththefollowingsteps:
1. EnsurethattheWalkEntryPage.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesectionsasshowninthecodesnippet:
//
//WalkEntryPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Services;
usingTrackMyWalks.Controls;
namespaceTrackMyWalks
{
publicclassWalkEntryPage:ContentPage
{
WalkEntryViewModel_viewModel
{
get{returnBindingContextasWalkEntryViewModel;}
}
publicWalkEntryPage()
{
//SettheContentPageTitle
Title="NewWalkEntry";
//DeclareandinitialiseourModelBindingContext
BindingContext=newWalkEntryViewModel(
DependencyService.Get<IWalkNavService>());
//DefineourNewWalkEntryfields
varwalkTitle=newEntryCell
{
Label="Title:",
Placeholder="TrailTitle"
};
walkTitle.SetBinding(EntryCell.TextProperty,"Title",
BindingMode.TwoWay);
...
...
2. Next,locateandmodifythewalkDifficultyEntryCellpropertysothatitcancorrectlyreturnthevalueoftheDifficultypropertyfromourWalkEntriesViewModel,thatisupdatedbyourDifficultyPickerEntryCellclass.Proceedandenterinthefollowinghighlightedcodesections:
varwalkDifficulty=newDifficultyPickerEntryCell
{
Label="DifficultyLevel:",
Placeholder="WalkDifficulty"
};
walkDifficulty.SetBinding(EntryCell.TextProperty,
"Difficulty",BindingMode.TwoWay);
...
...
}
}
}
Inthissection,welookedatthestepsinvolvedinmodifyingourWalkEntryPagesothatitcorrectlyreturnsthelevelofdifficultythathasbeenchosenfromourDifficultyPickerEntryCellclasscustomrenderer.WelookedatupdatingourwalkDifficultyobjectvariabletoreferencetheDifficultyPickerEntryCellclass,andupdatedthesetBindingtoreturnthevalueoftheDifficultypropertythatisimplementedwithintheWalkEntriesViewModelclass.
NowthatwehavefinishedbuildingallthenecessarycomponentsforourTrackMyWalksapplication,thenextstepistofinallybuildandruntheTrackMyWalksapplicationwithintheiOSsimulator.Whencompilationcompletes,theiOSSimulatorwillappearautomaticallyandtheTrackMyWalksapplicationwillbedisplayed,asshowninthefollowingscreenshot:
Asyoucanseefromtheprecedingscreenshot,thiscurrentlydisplaysourActivityIndicatorspinnercontrol,withtheassociatedLoadingTrailWalks...text,afterwhichthiswilldisplaytheListViewthatwillcontainourlistoftrailwalksfromourDataTemplatecontrol.Sincewedon'thaveanywalkentriescontainedwithinourMicrosoftAzureTrackMyWalksdatabase,theNewWalkEntryscreendisplaysentriesbeingentered:
Theprecedingscreenshot,showstheupdatedListViewcontroldisplayinginformationfromourTrackMyWalksMicrosoftAzuredatabase.Youwillnotice,thatuponselectingaWalkentryitemfromtheListViewcontrol,itwillpopupwithseveralchoicesforyoutochoosefrom.IfyouclickontheDeletebutton,itwillcalltheDeleteWalkEntryAsyncAPIandpassintheIdentifierfortheselecteditemthatistobepermanentlydeletedfromthedatabase.
Uponsuccessfuldeletion,youwillbepresentedwithadialogboxtellingyouthatthewalkentryhasbeendeleted.WhenclickingontheOKbutton,theListViewcontrolwillberefreshedanddisplayallentries,exceptfortheonethatyouhadjustdeleted.Alternatively,ifyouclickontheProceedWith...button,itwillnavigatetothewalksTrailDetailspagewhereyoucanbeginyourtrail,byclickingontheBeginthisTrailbutton.
SummaryInthischapter,youlearnedaboutMicrosoftAzureAppservicesandhowyoucanusethisplatformtogetinformationfromaremotedatasourcebycreatingyourveryfirstAPIwithinthecloudtoconnectto,andstoreandretrieveinformationfrom,allwithintheTrackMyWalksapp.Youlearnedhowtocreatealive,cloud-basedbackendserviceandAPIusingtheMicrosoft'sAzureAppservicesplatformtostoreandretrieveWalkEntryinformation,aswellascreatingaDataServiceclassthatwillbeusedtohandleallthecommunicationbetweenthecloudandtheTrackMyWalksapp.
Inthenextchapter,you'lllearnhowtocreateasign-inpagethatwillallowtheusertosignintotheTrackMyWalksappusingtheirFacebookcredentials.You'lllearnhowyoucantakeadvantageoftheFacebookSDKandessentiallypostwalkdatatoyourFacebookprofilepagesoyoucanshowoffyourprogresstoyourfriendsand/orworkcolleagues.
Chapter8.MakingOurAppSocial-UsingtheFacebookAPIInthepreviouschapter,youlearnedhowyoucanuseMicrosoftAzureAppservicestocreateyourveryfirstlive,cloud-based,backendHTTPwebservicetohandleallthecommunicationbetweenthecloudandourapp.Now,youwillalsolearnhowtocreateaDataServiceAPIthatwillallowourapptoconsumetheAPI,sothatwecanhavetheabilitytoretrieve,store,anddeletewalktrailinformationfromthecloud,allwithintheTrackMyWalksapp.
OnMay24th,2007,MarkZuckerbergannouncedtheFacebookplatform,adevelopmentplatformforprogrammerstocreatesocialapplicationswithinFacebook.WhenFacebooklaunchedthedevelopmentplatform,numerousapplicationshadbeenbuiltandalreadyhadmillionsofusersplayingthem.ThesocialnetworkingapplicationutilizestheFacebookcollectionofAPIsthatenablesdeveloperstoconnecttotheFacebookplatformandsendapplicationrequests.
Inthischapter,you'lllearnhowyoucanusebothXamarin.AuthandFacebookSDK,whichwillallowyoutoincorporatesocialnetworkingfeatureswithintheTrackMyWalksapptoobtaininformationaboutaFacebookuser,aswellaspostinformationtotheirFacebookwall.
You'lllearnhowtocreateasign-inpagethatwillallowtheusertosignintotheTrackMyWalksappusingtheirFacebookcredentials.YouwillalsocreateaFacebookApiUserclassthatwillbeusedtostoreinformationaboutthelogged-inuser,aswellasusingtheOpenGraphAPItoretrievecertaininformationabouttheuseranddisplaythiswithintheTrackMyWalksapp.Toendthischapter,youwillseehowyoucanleveragetheFacebooklibrary,essentiallytopostwalkdatatoyourFacebookprofilepage,soyoucanshowoffyourprogresstoyourfriendsand/orworkcolleagues.
Thischapterwillcoverthefollowingpoints:
SettingupourTrackMyWalksappwithintheFacebookDeveloperportalAddingtheXamarin.AuthandFacebookSDKpackagestothesolutionCreatingtheFacebookApiUserandFacebookCredentialsclassCreatingtheFBSignInPageandFBSignInPageRendererclassesUpdatingtheTrackMyWalksViewModelstousetheFacebookAPIUsingtheOpenGraphAPItoreadJSONdataRuntheTrackMyWalksappwithinthesimulator
SettingupandregisteringtheTrackMyWalksappwithFacebookInthissection,wewillbeginbysettingupourTrackMyWalksappandregisteringitwiththeFacebookplatform,sothatwecanbegincommunicatingandinteractingwithFacebook,andhavetheabilitytoretrieveuserinformation,aswellasallowingtheusertopostwalkinformationtotheirFacebookwall.
Let'snowstarttoimplementthecoderequiredforourFacebookApiUserclassmodel,byperformingthefollowingsteps:
1. Launchyourwebbrowserandtypeinhttps://developers.facebook.com/apps.2. Next,eithersignupforFacebookifyouarenotaregistereduser,orenteryourFacebook
accountcredentials.3. Then,clickontheCreateaNewAppbutton,whichwilldisplaytheCreateaNewAppID
screen,asshowninthefollowingscreenshot:
4. Next,enterTrackMyWalksfortheDisplayNamefieldandprovideyourContactEmailaddresssothatFacebookcancontactyouiftheyneedto.
5. Then,withintheCategorysection,selectacategoryfromtheChooseaCategorydropdown,andclickontheCreateAppIDbuttontocreateourTrackMyWalksapp,asshownintheprecedingscreenshot.
6. Next,youwillbepromptedtoentertheSecurityCheckanswerbeforeyoucanproceedtothenextstep:
7. Then,enterthetextdisplayedonyourscreenandclickontheSubmitbuttontocontinue.
Note
Ifyouenterthetextincorrectly,youmayendupwithyouraccountbeingblocked.Ifthisisthecase,youwillneedtocontactFacebookdirectlytohavethisunblocked.
NowthatwehavecreatedourTrackMyWalksFacebookAppID,ournextstepistobeginsettinguptheClientOAuthSettings,whichwillbeusedbyourOAuth2AuthenticatorclasswithinourTrackMyWalksapp:
8. Next,fromtheValidOAuthredirectURIssectionlocatedwithintheClientOAuthSettingscreen,enterhttps://www.facebook.com/connect/login_success.htmlastheURLtousewheneverwedetectthatwehavesuccessfullysignedintoFacebookfromwithinourapp.
9. Then,clickontheSaveChangesbuttontosaveourchangeswithinthisscreen.10. Next,choosetheAppReviewmenuitemlocatedundertheDashboard,ascanbeseeninthe
followingscreenshot:
11. Then,ensurethatyouhavechosenYesfortheMakeTrackMyWalkspublic?question.
Note
Essentially,theAPPIDisanimportantfieldthatwewillusewithinouriOSandAndroidapplicationtocommunicatewithFacebook.
WheneveryouenabletheMakeTrackMyWalkspublic?option,thiswillmakeyourapplivetothepubliconFacebook,sothatyourfriendsandfamilycanseeyourwalkinformationpostedonyourFacebookwall.Youwillnoticethat,whenyouenablethisoption,thelistofApprovedItemswillbeenabledbydefault,aswellastheirloginpermissions.
Note
Ifyouareinterestedinlearningmoreaboutthevarioustypesofloginpermissions,pleaserefertothePermissionsReference-FacebookLoginwhichcanbeaccessedathttps://developers.facebook.com/docs/facebook-login/permissions.
NowthatwehavesuccessfullysetupourTrackMyWalksappnamewithintheFacebookplatform,wecanbeginmakingourappcommunicatewiththeFacebookAPIstoobtainuserinformation,aswellaspostingmessagestothecurrentlylogged-inuser'sFacebookwall.Inournextsection,we
willbegintoaddtheXamarin.Auth.NETFrameworklibrary,aswellastheFacebookSDK,toconnectourTrackMyWalksapptoFacebookandauthenticateuserswithFacebook,sothatwecanpoststatusmessagesdirectlyfromwithinourappandmore.
AddingtheXamarin.AuthNuGetpackagetotheTrackMyWalksappNowthatwehavesetupourFacebookAppID,ournextstepistoaddtheXamarin.AuthNuGetpackagetoourTrackMyWalksPortableClassLibraryproject.TheXamarin.AuthpackagewillallowourapptoauthenticateauserwhorequiresaccesstousetheFacebookplatformbyusingOAuth2.0authentication.
TheseAuthenticatorsareresponsibleformanagingtheuserinterfaceandcommunicatingwithauthenticationservices.Authenticatorstakeavarietyofparameters;inthiscase,theapplication'sID,itsauthorizationscope,aswellasFacebook'svariousservicelocationsarerequired.
Let'slookathowtoaddtheXamarin.AuthNuGetpackagetoourTrackMyWalksPortableClassLibrarybyperformingthefollowingsteps:
1. Right-clickonthePackagesfolder,whichiscontainedwithintheTrackMyWalksPortableClassLibraryproject,andchoosetheAddPackages...menuoption,asshowninthefollowingscreenshot:
2. ThiswilldisplaytheAddPackagesdialog.EnterXamarin.AuthwithinthesearchdialogandselecttheXamarin.Authoptionwithinthelist,asshowninthefollowingscreenshot:
3. Finally,clickontheAddPackagebuttontoaddtheNuGetpackagetothePackagesfolder,whichiscontainedwithintheTrackMyWalksPortableClassLibraryproject.
NowthatwehaveaddedtheXamarin.AuthNuGetPackage,ournextstepistoaddtheFacebookFrameworktoourTrackMyWalksPortableClassLibrary,whichwewillbecoveringinthenextsection.
AddingtheFaceBookSDKlibrarytotheTrackMyWalksappIntheprevioussection,weaddedourXamarin.AuthNuGetpackagetoourTrackMyWalkssolution;thismeansthatournextstepistoaddtheFacebooklibrarytoourTrackMyWalkssolution.
Sinceweareusingboth.NETandC#tobuildourXamarin.Formsapplication,wecanleveragealibrarydevelopedbyacompanycalledTheOutercurveFoundation.ThislibraryisessentiallyaFacebookSDKthathelps.NETdevelopersbuildapplicationsthatintegratewithFacebook.
TheFacebookSDKframeworkcontainsallthemethodobjectsandAPIsthatarerequiredtoenableyoutointeractwithFacebookandsendnotificationrequests,orsimplypostmessagestothecurrentuser'swallpageusingthesinglesign-onfeatureofFacebookSDK.ThissimplyletsyouruserssignintoyourappusingtheirFacebookidentity,andwilldisplayaninlinedialogboxcomprisingaWebViewcontainerinwhichtheauthorizationUIwillbeshowntotheuser,whichrequiresthemtoentertheircredentialstogainaccesstoyourapp.
Let'slookathowwecanaddtheFacebookSDKlibrarytoourTrackMyWalksPortableClassLibrarybyperformingthefollowingsteps:
1. Launchyourwebbrowser,andtypethefollowingURL,https://components.xamarin.com/view/facebook-sdk,andlogintotheXamarinportalusingyourXamarincredentials.
2. Next,fromtheFacebookSDKsection,ensurethatyouhaveselectedversion6.2.2asthelatestversiontodownloadfromundertheVersionssection.
3. Then,proceedtoclickontheDownloadbuttontodownloadtheFacebookSDKlibrary,asshowninthefollowingscreenshot:
Note
OnceyouhavedownloadedtheFacebookSDK,extracttheZiparchivepackagecontents.Thedefaultdownloadlocationis~/Downloads/facebook-sdk-6.2.2.zip.
4. Next,right-clickontheReferencesfolder,whichiscontainedwithintheTrackMyWalksPortableClassLibraryproject,andchoosetheEditReferences...menuoption,asshowninthefollowingscreenshot:
5. Then,ensurethatthe.NetAssemblytabhasbeenselected,andclickontheBrowsebuttontochoosetheFacebook.dllforeithertheAndroidoriOSplatform:
6. Finally,ensurethatyouhaveselectedtheFacebook.dllassemblywithinthe.NetAssemblytab,clickontheOKbuttonaddthenewassemblytothereferencessectionofyourTrackMyWalksPortableClassLibrary,andclosetheEditReferencesdialog.
IncorporatingandusingtheFacebookSDKwithinyourapplicationsallowsyoutodowhatisdescribedinthefollowingtable:
FACEBOOKSDKTypes Description
Authenticationandauthorization
ThispromptsyouruserstosignintoFacebookandgrantpermissionstoyourapplication.
MakeAPIcalls
Thisallowsyoutofetchuser-profiledata,aswellasanyinformationthatrelatestotheuser'sfriends,usingtheJSONAPIcalls.
Displaydialog
ThisallowsyoutointeractwiththeuserviaaWebViewcontainerobject,whichisextremelyusefulforenablinginteractionswithFacebook,withouttheneedforrequiringupfrontpermissions.
NowthatwehaveaddedboththeXamarin.AuthNuGetpackageandFacebookDynamic-LinkLibrary(DLL)packagestooursolution,wecannowbeginutilizingtheseframeworklibrariesasweprogressthroughoutthischapter.
CreatingaFacebookusermodelfortheTrackMyWalksappIntheprevioussection,weaddedbothofourXamarin.AuthandFacebookSDK.NETAssemblypackagestoourTrackMyWalksPortableClassLibrary.ThiswillessentiallybeusedbyeachofourViewModelsalongwiththeViews(pages).
Inthissection,wewillbeginbycreatingourFacebookApiUserdatamodel,whichwillbeusedtostoreourFacebooklogininformationfromwhenwecreateourbackendservicecalls,andthentheFacebookCredentials.csandFBSignInRenderer.csfileswillcommunicateandinteractwithourFacebookTrackMyWalksAppIDtoretrieveFacebookrelatedinformation,aswellasallowingtheusertopostwalkinformationtotheirFacebookwall.
Let'snowstarttoimplementthecoderequiredforourFacebookApiUserclassmodelbyperformingthefollowingsteps:
1. CreateanemptyclasswithintheModelsfolderbychoosingAdd|NewFile....Ifyoucan'trememberhowtodothis,youcanrefertothesectionentitledCreatingtheTrackMyWalksModelwithinChapter1,CreatingtheTrackMyWalksNativeApp.
2. Then,enterFacebookApiUserforthenameofthenewclassthatyouwanttocreate,andclickontheNewbuttontoallowthewizardtoproceedandcreatethenewfile.
3. Next,ensurethattheFacebookApiUser.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//FacebookApiUser.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon07/11/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingXamarin.Auth;
usingNewtonsoft.Json.Linq;
namespaceTrackMyWalks.Facebook
{
4. Next,weneedtoimplementtheFacebookApiUserclassthatwillcontainthevariouspropertymethodsusedtostorethecurrentlylogged-inuser,theirFacebookId,andthepropertiesthatwillbeusedtostoreandretrievetheuser'sFacebookdetails.Toproceed,enterthefollowingcodesnippet:
publicclassFacebookApiUser
{
//Storethecurrentlyloggedinuser
publicstaticboolIsLoggedIn
{
get{return!string.IsNullOrWhiteSpace
(FaceBookApiAuthToken.GetAuthToken);
}
}
//DefineourFacebookIdproperty
publicstaticstringFacebookId
{
get{return"<YOUR_FACEBOOK_ID>";}
}
//Retrieveouruserdetails
staticJObject_userDetails;
publicstaticJObjectGetUserDetails
{
get{return_userDetails;}
}
//Storeouruserdetails
publicstaticvoidSaveUserDetails(JObjectuserDetails)
{
_userDetails=userDetails;
}
}
5. Next,weneedtoimplementtheFacebookApiAuthTokenclassthatwillcontainthevariouspropertymethodsthatwillbeusedtostoreandretrieveourFacebookauthenticationTokenonsuccessfullyloggingintoourTrackMyWalksapp.ThesepropertieswillbeusedthroughoutourapplicationtoretrieveourFacebookuserdetails,andwhenwepostwalkinformationtoourFacebookwall.Toproceed,enterthefollowingcodesnippet:
//FacebookAPIauthenticationToken
publicclassFacebookApiAuthToken
{
//PropertytopointtotheApiuser
publicFacebookApiUserUser{get;set;}
//GetourFacebookauthenticationToken
staticstring_authToken;
publicstaticstringGetAuthToken
{
get{return_authToken;}
}
//StoreourauthenticationToken
publicstaticvoidStoreAuthToken(stringauthToken)
{
_authToken=authToken;
}
//GetourFacebookauthenticationAccountDetails
staticAuthenticatorCompletedEventArgs_authAccount;
publicstaticEventArgsGetAuthAccount
{
get{return_authAccount;}
}
//StoreourFacebookauthenticationAccountDetails
publicstaticvoidStoreAuthAccount
(AuthenticatorCompletedEventArgsauthAccount)
{
_authAccount=authAccount;
}
}
}
Intheprecedingcodesnippet,webeginbyimplementingthevariouspropertymethodsthatwillberequiredtohandlecommunicationbetweenourTrackMyWalksappandFacebook,toallowourapptosuccessfullylogin.TheFacebookApiUserclassmethodisresponsibleforhandlingtheinformationrelatingtotheFacebookuserwhowillbeloggingin.ItcontainsapropertycalledIsLoggedInthatwillbeusedthroughoutourapptodetermineiftheuserhasloggedin;thisisdeterminedbycheckingtoseeifwehavereceivedavalidauthenticationtokenbackfromFacebook.
TheFacebookIdpropertyisessentiallytheuser'sFacebookId.YouwillneedtoreplacethiswithyourownFacebookIDsothatyoucanpostwalktrailinformationtoyourFacebookwall.TheGetUserDetailsandSaveUserDetailspropertiesareusedtostoretheuserinformationthatwillbedisplayedwithintheDistanceTravelledpage.TheFaceBookApiAuthTokenmethodcontainsthevariouspropertiesthatwillbeusedtohandlethestoringoftheusertokendetails,whenourappdeterminesthatwehavesuccessfullybeenauthenticatedwithFacebook.
CreatingaFacebookCredentialsclassfortheTrackMyWalksappInourprevioussection,wecreatedourFacebookApiUserclassmodel,whichwillprovideuswithamechanismforstoringourFacebookcredentials,thatwecanusethroughoutourTrackMyWalksapp.
Inthissection,wewillbeginbycreatingaFacebookCredentialsclassthatwillallowustomakeAPIcalls,sothatwecanretrieveuserprofileinformationbackfromourAPI,inJSONformat,andstorethisinformationtobeusedlater.OurFacebookCredentialsclasscontainsamethodthatallowsourapptopostwalktrailinformationtotheuser'sFacebookpagewall.
Let'snowstarttoimplementthecoderequiredforourFacebookCredentialsclassbyperformingthefollowingsteps:
1. CreateanemptyclasswithintheServicesfolder.Ifyoucan'trememberhowtodothis,youcanrefertothesectionentitledCreatingtheNavigationServiceInterfacefortheTrackMyWalksappwithinChapter3,NavigatingwithintheMVVMModel-TheXamarin.FormsWay.
2. Next,entertheFacebookCredentialsforthenameofthenewclassthatyouwanttocreate,andclickontheNewbuttontoallowthewizardtoproceedandcreatethefile.
3. Then,ensurethattheFacebookCredentials.csfileisdisplayedwithinthecodeeditor,andenterthefollowingcodesnippet:
//
//FacebookCredentials.cs
//StoresthecredentialstobeusedforFacebook
//
//CreatedbyStevenF.Danielon09/11/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingTrackMyWalks.Models;
usingFacebook;
usingXamarin.Auth;
usingSystem;
usingSystem.Threading.Tasks;
usingNewtonsoft.Json.Linq;
usingSystem.Collections.Generic;
usingTrackMyWalks.Facebook;
namespaceTrackMyWalks
{
publicclassFacebookCredentials
{
4. Next,implementthePostWalkInformationinstancemethodthatwillbeusedtopostinformationrelatingtothecurrentlyactivewalktrailtotheuser'sFacebookpagewall.WedefineanfbvariablethatinstantiatesanewFacebookClientobjectusingtheauthorizationtokenGetAuthTokenfromourFacebookAuthTokenclass,whichweobtainedupon
achievingasuccessfulloginfromFacebook.5. WeusethePostTaskAsyncmethodandpassintheGraphAPIme/feedsyntaxvalueasthe
firstparameter,followedbyamessageparameter,alongwiththemessagedetailsthatwewanttoposttotheuser'sFacebookwall.Toproceed,enterthefollowingcodesnippet:
//Postinformationtotheuser'sFacebookWall
publicstaticvoidPostWalkInformation(stringTitle,
doubleKilometers,stringDifficulty,
stringNotes,stringtrailPictureUrl)
{
FacebookClientfb=newFacebookClient(FacebookApiAuthToken.
GetAuthToken);
//Themessagetopostasakey/valuepair
stringpostMessage="TrackMyWalksApp-TrailCompleted-
Results.";
postMessage+="\n\nTitle:"+Title;
postMessage+="\nKilometers:"+Kilometers;
postMessage+="\nDifficulty:"+Difficulty;
postMessage+="\nNotes:"+Notes;
postMessage+="\nTrailImage:"+trailPictureUrl;
fb.PostTaskAsync("me/feed",new{message=postMessage}).
ContinueWith(t=>
{
if(t.IsFaulted)
{
//Catchanyerrorsthatoccurhere.
}
});
}
Note
TheGraphAPIistheprimarywayinwhichwecangetdatainandoutofFacebook'ssocialgraph,andisessentiallyalow-levelHTTP-basedAPIthatisusedtoquerydata,postnewstories,anduploadphotos.IfyouareinterestedinfindingourmoreinformationabouttheFacebookGraphAPIframeworkclasses,pleaserefertotheFacebookDeveloperdocumentationlocatedathttps://developers.facebook.com/docs/graph-api/using-graph-api/.
6. Then,implementtheGetProfileInformationinstancemethodthatwillbeusedtoretrieveinformationrelatingtothecurrentlyactiveFacebookuser.WedefinearequestobjectthatinitializesanewinstanceoftheOAuth2Requestclass,andacceptstheHTTPmethodtype,alongwiththeURL,andalistofparameters.ThefinalparameterisourobtainedFacebookaccountdetailsthatwillbeusedtoauthenticateourrequest.
7. WethenusetheGetResponseAsyncmethodtomakeanasynchronouswebrequestcalltoretrievetheinformation,asspecifiedbyourrequestobject,andthenusetheGetReponseTextmethodtoreturnaJSONobject,containingtheFacebookuserdetailsasspecifiedinourURLstring,andthenparsethisusingtheJObject.ParsemethodtoconvertthedetailstoaJSONobjectandassignthistoourobjvariable.
8. Next,wechecktoensurethatwehavetheinformationreturnedbyourwebrequest,andthen
passtheJSONobjectdetails,asdefinedbyourobjvariable,toourSaveUserDetailsproperty,whichiscontainedwithinourFacebookApiUserclass.Toproceed,enterthefollowingcodesnippet:
//RetrieveFacebookinformationpertainingtotheuser.
publicstaticasyncTaskGetProfileInformation(AuthenticatorCompl
etedEventArgseventArgs)
{
//Makearequesttoretrieveouritemsbasedonthelistof
//parametersbelow.
varrequest=newOAuth2Request("GET",
newUri("https://graph.facebook.com/me?fields=id,name,
first_name,last_name,gender,picture,email,devices,education"),
null,eventArgs.Account);
varresponse=awaitrequest.GetResponseAsync();
varobj=JObject.Parse(response.GetResponseText());
//Checktoseeifwehavereturnedanyinformation
if(obj!=null)
{
try
{
//Storeouruserprofileinformationintoour
property.
FacebookApiUser.SaveUserDetails(obj);
}
catch(Exceptione)
{
//Handleanyerrorsthatfallinhere.
}
}
}
}
}
Intheprecedingcodesnippet,webeginbyimplementingthemethodsthatarerequiredtopostandretrieveourFacebookinformationusingtheFacebookClientclassthatisusedtomakesynchronousrequeststotheFacebookserver.ThePostWalkInformationinstancemethodisusedtopostinformationrelatingtothecurrentlyactiveusertotheuser'sFacebookpagewall.
TheGetProfileInformationinstancemethodisusedtoretrieveinformationassociatedwiththecurrentlylogged-inFacebookuser,usingtheOAuth2RequestclassandtheFacebookGraphAPI,whichacceptsaURL,containingalistofparametersthatwewouldlikeourmethodtoreturnandthatwillbestoredwithinourSaveUserDetailsproperty,whichisdefinedwithinourFacebookApiUserclass.
Note
IfyouareinterestedinfindingoutmoreinformationabouttheOAuth2RequestandtheXamarin.Authclasses,pleaserefertotheXamarindeveloperdocumentationlocatedat
https://components.xamarin.com/gettingstarted/xamarin.auth.
CreatingtheFacebookSignIntousewithinourTrackMyWalksappInourprevioussection,wecreatedandimplementedourFacebookCredentialswrapperclassthatwillbeusedbyourTrackMyWalksapplicationtohandletheretrievingofourFacebookuserdetails,aswellasprovidingtheabilitytopostwalktrailinformationdirectlytoourFacebookwallsothatourfriendsandfamilycantrackourprogress.
OurnextstepistobegincreatingaFacebookSignInpagethatwillbehookeduptoacustomrendererpagethatwillbeusedtodisplaytheFacebookloginpagewithinawebcontainer.
Let'snowstarttoimplementthecoderequiredforourFBSignInPageContentPagebyperformingthefollowingsteps:
1. CreateanemptyFormsContentPagewithinthePagesfolder.Ifyoucan'trememberhowtodothis,youcanrefertothesectionentitledCreatingthewalksmainpagewithinChapter1,CreatingtheTrackMyWalksNativeApp.
2. Next,enterFBSignInPageforthenameofthenewContentPagethatyouwanttocreate,andclickontheNewbuttontoallowthewizardtoproceedandcreatethefile.
3. Then,ensurethattheFBSignInPage.csfileisdisplayedwithinthecodeeditorandenterthefollowingcodesnippet:
//
//FBSignInPage.cs
//TrackMyWalksFacebookSignInPage
//
//CreatedbyStevenF.Danielon09/11/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
namespaceTrackMyWalks
{
publicclassFBSignInPage:ContentPage
{
}
}
Intheprecedingcodesnippet,ourContentPagecontainsthebare-bonesimplementation.Thisisintentional,as,inournextsection,wewillbecreatingacustomclassrendererthatwilluseourFBSignInPageContentPagetoinstantiateaninstanceoftheFacebookloginwebpage.
CreatingtheFacebookSignInClassforTrackMyWalks(iOS)appInourprevioussection,wecreatedourFBSignInPagecontentpagethatwillbeusedasaplaceholderforourFacebookSignInclasscustomrenderer.Inthissection,wewillbuildthecustomFacebooksign-inpagerenderer,whichwillbeusedbytheiOSandAndroidportionsofourapptohandlethesigningintoFacebookviatheFacebookApiAuthTokenmodel,tostorethereceivedFacebooktokenthatwillbeusedthroughouttheTrackMyWalksapplication.
Let'slookathowwecanachievethisbyperformingthefollowingsteps:
1. CreateanemptyclasswithintheRenderersfolderforourTrackMyWalks.iOSproject.Ifyoucan'trememberhowtodothis,youcanrefertothesectionentitledCreatingthecustompickerrendererclassfortheiOSplatformwithinChapter5,CustomizingtheUserInterface.
2. Next,enterFBSignInPageRendererforthenameofthenewclassthatyouwanttocreate,andclickontheNewbuttontoallowthewizardtoproceedandcreatethefile.
3. Then,ensurethattheFBSignInPageRenderer.csfileisdisplayedwithinthecodeeditorandenterthefollowingcodesnippet:
//
//FBSignInPageRenderer.cs
//TrackMyWalksFacebookSignInPage(iOS)
//
//CreatedbyStevenF.Danielon09/11/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingXamarin.Forms;
usingTrackMyWalks;
usingXamarin.Forms.Platform.iOS;
usingXamarin.Auth;
usingTrackMyWalks.Facebook;
4. Next,weneedtoinitializeourFBSignInPageRendererclassbeingmarkedasanExportRendererbyincludingtheExportRendererassemblyattributetothetopofourclassdefinition.ThisletsourclassknowthatitinheritsfromtheViewRendererclass.
[assembly:ExportRenderer(typeof(FBSignInPage),
typeof(TrackMyWalks.iOS.FBSignInPageRenderer))]
namespaceTrackMyWalks.iOS
{
publicclassFBSignInPageRenderer:PageRenderer
{
Then,wedeclareanIsVerifiedBooleanvariablethatwillbeusedtodetermineifasuccessfultoFacebookhashappened.Next,weimplementtheViewDidAppearmethodthatwillbelaunchedupontheContentPagebecomingvisible,andthencalltheFacebookSignIninstancemethod:
boolIsVerified=false;
publicoverridevoidViewDidAppear(boolanimated)
{
base.ViewDidAppear(animated);
if(!IsVerified)
{
FacebookSignIn();
}
}
5. Next,weneedtocreatetheFacebookSignIninstancemethodthatwillbecalledwhenevertheuserhasn'tsignedintoFacebook.WeusetheOAuth2Authenticatormethod,whichwillberesponsibleformanagingtheuserinterfaceandhandlingthecommunicationwiththeFacebookauthenticationservices.
6. TheOAuth2Authenticatorclassacceptstheuser'sFacebookID,whichisstoredwithintheFacebookIdpropertythatisdeclaredwithintheFacebookApiUserclass.TheOAuth2AuthenticatorclassalsoacceptstheauthorizationscopeandtheFacebookservicelocationstoauthenticateanddeterminewhattodowhenasuccessfulloginhappens:
voidFacebookSignIn()
{
stringAccessToken=String.Empty;
varauth=new
OAuth2Authenticator(FaceBookApiUser.FacebookId,
"publish_actions",
newUri("https://m.facebook.com/dialog/oauth/"),
new
Uri("https://www.facebook.com/connect/login_success.html")
);
//Preventourformfrombeingdismissedbytheuser.
auth.AllowCancel=false;
7. Then,beforewepresenttheFacebookUItotheuser,weneedtostartlisteningtotheCompletedeventoftheOAuth2Authenticatorinstance,whichfiresupwhenevertheusersuccessfullyauthenticatesorcancels,andthenchecktheIsAuthenticatedpropertyoftheeventArgspropertytoproperlydetermineiftheauthenticationhassucceeded.
8. Ifwehavedeterminedthatasuccessfulloginhashappened,wemakeacalltotheDismissViewControllermethodtodismissthecurrentlypresentedFacebookUIandthencalltheRemoveFBSignInPageinstancemethodfromourTrackMyWalksappclass,withinPortableClassLibrary,toremoveourFBSignInPagefrommemory:
auth.Completed+=async(sender,eventArgs)=>
{
if(eventArgs.IsAuthenticated)
{
//DismissourFacebookAuthenticationUIDialog
DismissViewController(true,null);
//RemoveourFacebookSignInPageViewfrommemory.
App.RemoveFBSignInPage();
9. Next,weproceedtoretrievetheaccesstokenfromtheFacebooksessionofthesuccessfullylogged-inuser,andproceedtostorethevalueswithintheStoreAuthTokenandStoreAuthAccountdetailswithinourFacebookApiAuthTokenclass.Finally,wecallourNavigateToWalksPageActionwithinourTrackMyWalksAppclass:
//RetrieveouraccesstokenforourFacebooksession.
eventArgs.Account.Properties.TryGetValue("access_token",
outAccessToken);
FacebookApiAuthToken.StoreAuthToken(AccessToken);
FacebookApiAuthToken.StoreAuthAccount(eventArgs);
//NavigateToWalksListmethodfromourmainclass.
awaitApp.NavigateToWalksPage();
}
else
{
//TheusercancelledtheFacebookLoginUI
Console.WriteLine("Youarenotauthorisedtouse
theTrackMyWalksapp");
IsVerified=false;
return;
}
};
10. Then,wepresenttheFacebookloginUIbyusingthePresentViewControllermethod,andcalltheGetUImethodreturnsUINavigationControllersoniOSandintentsonAndroid.OnAndroid,wewouldwritethefollowingcodetopresenttheUIfromtheOnCreatemethod:
IsVerified=true;
PresentViewController(auth.GetUI(),true,null);
}
}
}
Intheprecedingcodesnippet,webeginbyinitializingourFBSignInPageRendererclass,beingmarkedasanExportRenderer,toletourclassknowthatitinheritsfromtheViewRendererclass,andthendeclareanIsVerifiedBooleanvariablethatwillbeusedtodetermineifasuccessfullogintoFacebookhashappened.WethenproceedandimplementtheViewDidAppearmethod,whichwillbelaunchedwhentheContentPagebecomesvisible,andthencalltheFacebookSignIninstancemethod,whichwillbecalledwhenevertheuserhasn'tsignedintoFacebook,andusetheOAuth2Authenticatormethod,whichwillberesponsibleformanagingtheuserinterfaceandhandlingthecommunicationwiththeFacebookauthenticationservices.
Finally,wepresenttheFacebookloginUI,retrievetheaccesstokenfromtheFacebooksessionofthesuccessfullylogged-inuser,andproceedtostorethevalueswithintheStoreAuthTokenandStoreAuthAccountdetailswithinourFacebookApiAuthTokenclass:
Note
TheAndroidversionoftheFBSignInPageRendererclassisavailableinthecompanionsourcecodeforthisbook,whichcanbelocatedwithintheTrackMyWalks.Droidproject.
UpdatingtheNavigationServiceInterfacefortheTrackMyWalksappInthissection,wewillproceedtoupdateourIWalkNavServiceInterfacetocontainanewmethodofimplementationthatwillallowourapptoclearallpreviouslycreatedviewsfromtheNavigationStack.SincethisabstractInterfaceclasswillactasthebaseNavigationServiceclassthateachofourViewModelswillinheritfrom,theywillbeabletoaccesseachoftheclassmemberscontainedwithinthisInterface.
Let'slookathowwecanachievethisbyperformingthefollowingsteps:
EnsurethattheIWalkNavService.csfileisdisplayedwithinthecodeeditorandenterthehighlightedcodesectionsshowninthefollowingcodesnippet:
//
//IWalkNavService.cs
//TrackMyWalksNavigationServiceInterface
//
//CreatedbyStevenF.Danielon03/09/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem.Threading.Tasks;
usingTrackMyWalks.ViewModels;
namespaceTrackMyWalks.Services
{
publicinterfaceIWalkNavService
{
//NavigatebacktothePreviouspage
intheNavigationStack
TaskPreviousPage();
//Navigatetothefirstpagewithin
theNavigationStack
TaskBackToMainPage();
//NavigatetoaparticularViewModel
withinourMVVMModel,
//andpassaparameter
TaskNavigateToViewModel<ViewModel,
WalkParam>(WalkParamparameter)
whereViewModel:WalkBaseViewModel;
//Clearallpreviouslycreatedviews
fromtheNavigationStack
voidClearAllViewsFromStack();
}
}
Intheprecedingcodesnippet,weimplementanewclassmemberClearAllViewsFromStack
methodthatwillbeusedtoclearallpreviouslycreatedviewsfromtheNavigationStack.Thisisbecause,uponsuccessfullyloggingintoourTrackMyWalksappaftertheFacebookUIloginhasbeendismissed,weneedtohavetheabilitytoremovetheFBSignInPagefromtheNavigationStack.
UpdatingtheNavigationServiceclassfortheTrackMyWalksappIntheprevioussection,weupdatedthebaseInterfaceclassforourNavigationService,aswellasdefininganewclassmemberthatwillbeusedtohandletheremovingofallpreviouslycreatedviewsfromourNavigationStackwithinourMVVMViewModel.
ThesewillbeusedbyeachofourViewModels,andtheViews(pages)willimplementthoseViewModelsandusethemastheirBindingContext.
Let'slookathowwecanachievethisbyperformingthefollowingsteps:
1. EnsurethattheWalkNavService.csfileisdisplayedwithinthecodeeditor,andenterthehighlightedcodesectionsshowninthefollowingcodesnippet:
//
//WalkNavService.cs
//TrackMyWalksNavigationServiceClass
//
//CreatedbyStevenF.Danielon03/09/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingXamarin.Forms;
usingSystem.Collections.Generic;
usingSystem.Threading.Tasks;
usingSystem.Reflection;
usingSystem.Linq;
usingTrackMyWalks.ViewModels;
usingTrackMyWalks.Services;
[assembly:Dependency(typeof(WalkNavService))]
namespaceTrackMyWalks.Services
{
publicclassWalkNavService:IWalkNavService
{
publicINavigationnavigation{get;set;}
readonlyIDictionary<Type,Type>
_viewMapping=new
Dictionary<Type,Type>();
//RegisterourViewModelandViewwithinourDictionary
publicvoidRegisterViewMapping(
TypeviewModel,Typeview)
{
_viewMapping.Add(viewModel,view);
}
//Instancemethodthatallowsustomovebackto
thepreviouspage
publicasyncTaskPreviousPage()
{
//Checktoseeifwecanmovebacktothepreviouspage
if(navigation.NavigationStack!=null&&
navigation.NavigationStack.Count>0)
{
awaitnavigation.PopAsync(true);
}
}
//Instancemethodthattakesusbacktothe
mainRootWalksPage
publicasyncTaskBackToMainPage()
{
awaitnavigation.PopToRootAsync(true);
}
//InstancemethodthatnavigatestoaspecificViewModel
//withinourdictionaryviewMapping.
publicasyncTaskNavigateToViewModel
<ViewModel,WalkParam>(WalkParamparameter)
whereViewModel:WalkBaseViewModel
{
TypeviewType;
if(_viewMapping.TryGetValue(typeof(ViewModel),
outviewType))
{
varconstructor=viewType.GetTypeInfo()
.DeclaredConstructors
.FirstOrDefault(dc=>dc.GetParameters()
.Count()<=0);
varview=constructor.Invoke(null)asPage;
awaitnavigation.PushAsync(view,true);
}
if(navigation.NavigationStack.Last().BindingContextis
WalkBaseViewModel<WalkParam>)
await((WalkBaseViewModel<WalkParam>)
(navigation.Navigation
Stack.Last().BindingContext)).Init(parameter);
}
2. Next,weneedtocreatetheClearAllViewsFromStackinstancemethodforourWalkNavServiceclass,whichwillbeusedtoremoveallpreviouslycreatedviewsfromtheNavigationStackbyfirstcheckingtheNavigationStackpropertyforthenavigationpropertyINavigationinterfacetoseeifanyitemsalreadyhavebeenpushedontotheNavigationStack.Thisistoensurethatacrashdoesn'thappenwithinourapp.
3. Inournextstep,weproceedtoiteratethrougheachitemthatiscontainedinourNavigationStackandcalltheRemovePage,methodtoremoveeachpage:
//Instancemethodtoremoveallpreviouslycreatedviewsfrom
//theNavigationStack.
publicvoidClearAllViewsFromStack()
{
//Checktoseeifanyitemshavealreadybeenpushed
//ontotheNavigationStack.
if(navigation.NavigationStack.Count<=1)
return;
for(vari=0;i<navigation.NavigationStack.Count-1;
i++)
navigation.RemovePage(navigation.NavigationStack[i]);
}
}
}
Intheprecedingcodesnippet,weimplementanewclassmemberClearAllViewsFromStackinstancemethodwithinourWalkNavServiceclass,tohandletheclearingofallpreviouslycreatedViews(pages)fromtheNavigationStack.
UpdatingtheWalksPagetoproperlyhandleFacebookSignInInthissection,weneedtoupdateourWalksPageContentPagesothatitcanreferencetheupdateswithinourWalksPageViewModel.WewillneedtoapplyadditionallogictoupdatetheNavigationBarTitleoncewehavedismissedourFacebookSignIndialog.
Let'slookathowwecanachievethisbyperformingthefollowingsteps:
EnsurethattheWalksPage.csfileisdisplayedwithinthecodeeditor,andenterthehighlightedcodesectionsshowninthefollowingcodesnippet:
//
//WalksPage.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingXamarin.Forms;
usingTrackMyWalks.Models;
usingTrackMyWalks.ViewModels;
usingTrackMyWalks.Services;
usingTrackMyWalks.DataTemplates;
usingTrackMyWalks.ValueConverters;
namespaceTrackMyWalks
{
publicclassWalksPage:ContentPage
{
WalksPageViewModel_viewModel
{
get{returnBindingContext
asWalksPageViewModel;}
}
publicWalksPage()
{
varnewWalkItem=newToolbarItem
{
Text="Add"
};
if(Device.OS==TargetPlatform.iOS)
{
Title="TrackMyWalks-iOS";
}
elseif(Device.OS==TargetPlatform.Android)
{
Title="TrackMyWalks-Android";
}
...
...
...
...
Intheprecedingcodesnippet,webeginbycheckingtheDevice.OSclass,todeterminewhatOSXamarin.Formsisrunningon,andthenusetheTargetPlatformclasstodetermineifourappisrunningontheAndroidoriOSplatform.IfwehavedeterminedthatourappisrunningonAndroid,wesettheTitlepropertyforourContentPage;alternatively,ifwearerunningoniOS,wesettheTitlepropertyaswell.
UpdatingtheWalksPageViewModeltouseourFaceBookApiUserInthissection,wewillproceedtoupdateourWalksPageViewModelViewModel,sothatithastheabilitytodisplayourFacebookSignInpageifitdeterminesthatwehaven'talreadyloggedin.
Let'slookathowwecanachievethisbyperformingthefollowingsteps:
EnsurethattheWalksPageViewModel.csfileisdisplayedwithinthecodeeditor,andenterthehighlightedcodesectionsshowninthefollowingcodesnippet:
//
//WalksPageViewModel.cs
//TrackMyWalksViewModels
//
//CreatedbyStevenF.Danielon22/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Collections.ObjectModel;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Models;
usingTrackMyWalks.Services;
usingXamarin.Forms;
usingTrackMyWalks.Facebook;
namespaceTrackMyWalks.ViewModels
{
publicclassWalksPageViewModel:
WalkBaseViewModel
{
ObservableCollection<WalkEntries>
_walkEntries;
publicObservableCollection<WalkEntries>
walkEntries
{
get{return_walkEntries;}
set
{
_walkEntries=value;
OnPropertyChanged();
}
}
publicWalksPageViewModel
(IWalkNavServicenavService):
base(navService)
{
walkEntries=newObservableCollection
<WalkEntries>();
}
publicoverrideasyncTaskInit()
{
//Checkifwehaveloggedinandthatwearerunningour
//deviceoniOS
if(!FacebookApiUser.IsLoggedIn&&
Device.OS==TargetPlatform.iOS)
{
awaitApp.Current.MainPage.Navigation
.PushModalAsync(new
FBSignInPage());
}
else{
awaitLoadWalkDetails();
awaitNavService.ClearAllViewsFromStack();
}
}
...
...
...
...
Intheprecedingcodesnippet,webeginbymodifyingtheInitmethodtoincludeachecktoseeifwehavealreadyloggedintoFacebook,bycheckingtheIsLoggedInpropertyoftheFacebookApiUserclass,andwhetherwearerunningonadevicerunningiOS.IfwedeterminethattheIsLoggedInpropertydoesn'tcontainavalue,wecallthePushModalAsyncmethodontheNavigationpropertyfromtheMainPagetodisplayourFBSignInPageContentPage.Alternatively,iftheuserhasloggedin,weproceedtoloadourwalkentrydetailsusingtheLoadWalkDetailsinstancemethod,andcalltheClearAllViewsFromStackinstancemethodthatislocatedwithinourWalkNavServiceclass.
UpdatingtheDistanceTravelledPagefortheTrackMyWalksappInthissection,weneedtoupdateourDistanceTravelledPageContentPage,sothatitcanmakeuseofourFacebookAPIandretrievethecurrentlylogged-inFacebookuser,aswellasprovidingtheabilitytopostWalkTrailinformationtotheuser'sFacebookwall.
Let'slookathowwecanachievethisbyperformingthefollowingsteps:
1. EnsurethattheDistanceTravelledPage.csfileisdisplayedwithinthecodeeditor,andenterthehighlightedcodesectionsshowninthefollowingcodesnippet:
varpostToFacebook=newButton
{
BackgroundColor=Color.FromHex("#455c9f"),
TextColor=Color.White,
Text="PosttoFacebook"
};
postToFacebook.Effects.Add(Effect.Resolve(
"com.geniesoftstudi
os.ButtonShadowEffect"));
2. Next,wecreateaClickedhandlermethodforourpostToFacebookbutton,sothatwheneverthePosttoFacebookispressedwedisplayaselectionofchoicesfortheusertochoosefrom,usingtheDisplayActionSheetmethod:
//Setupoureventhandler
postToFacebook.Clicked+=async(sender,e)=>
{
if(_viewModel.WalkEntry==null)return;
//Displayourlistofchoicestochoosefrom
varaction=awaitDisplayActionSheet(
"TrackMyWalks-TrailDetails",
"Cancel",
"DisplayUserDetails",
"PosttoFacebookWall");
3. Then,weusetheContainspropertyoftheactionvariabletodetermineifthePostoptionhasbeenselected,and,ifso,wecallthePostWalkInformationinstancemethodofourFacebookCredentialsclass;passintheTitle,Kilometers,Difficulty,Notes,andImageUrlasparameterstotheclass;andthendisplayanalertdialogboxtellingtheuserthattheirwalkinformationhasbeenpostedtotheirFacebookwall:
if(action.Contains("Post"))
{
//DeclareaninstancetoourFacebookCredentialsClass
FacebookCredentials.PostWalkInformation(
_viewModel.WalkEntry.Title,
_viewModel.WalkEntry.Kilometers,
_viewModel.WalkEntry.Difficulty,
_viewModel.WalkEntry.Notes,
_viewModel.WalkEntry.ImageUrl.AbsoluteUri);
//Displayanalertdialoglettingtheuserknowthat
//theirinformationhasbeenpostedtotheirFacebook
//Wall.
awaitDisplayAlert("PosttoFacebook","Trail
informationhasbeenpostedtoyourwall!","OK");
}
4. Next,weusetheContainspropertyoftheactionvariabletodetermineiftheUserDetailsoptionhasbeenselected,and,ifso,wecalltheGetProfileInformationinstancemethodofourFacebookCredentialsclass,andpassintheGetAuthAccountpropertyfromourFacebookApiAuthTokenclasstoretrieveourcurrentlylogged-inFacebookuser'sdetailsandassignthevaluetoourobjUserDetailsvariable.WethenproceedtoconstructouruserDetailsstringwiththeinformationextractedfromtheobjUserDetailsdictionary,anddisplaytheinformationwithinanalertdialogbox:
elseif(action.Contains("UserDetails"))
{
//DeclareaninstancetoourFacebookCredentialsClass
awaitFacebookCredentials.GetProfileInformation((Xamarin
.Auth.AuthenticatorCompletedEventArgs)
FacebookApiAuthToken.GetAuthAccount);
//ConstructourFacebookUserdetailsbasedon
//informationstoredwithineachoftheproperties
varobjUserDetails=FacebookApiUser.GetUserDetails;
varuserDetails=objUserDetails.GetValue("id").ToString();
userDetails
+="\n"+objUserDetails.GetValue("name").ToString();
userDetails+="\n"+objUserDetails.GetValue("first_name")
.
ToString();
userDetails+="\n"+objUserDetails.GetValue("last_name")
.ToString();
userDetails+="\n"+objUserDetails.GetValue("gender")
.ToString();
userDetails+="\n"+objUserDetails.GetValue("devices")
.ToString();
//DisplayanAlertDialogthatwilldisplay
//informationfromouruserproperties
awaitDisplayAlert("FacebookUserDetails",
userDetails,"OK");
}
};
Intheprecedingcodesnippet,wecreateourpostToFacebookbuttonandbeginapplyingtheButtonShadowEffectclasstoourcontrol,sothatitcantakeadvantageoftheniceplatform-specificrenderingeffectsforvisualcontrolelements.
Next,wemodifytheClickedmethodtohandleeachpressofthebutton,sothatwecandisplayaselectionofoptionsfortheusertochoosefrom.WeaccomplishthisbyusingtheDisplayActionSheetmethod.WhentheuserchoosesthePostbutton,acallismadetoourFacebookCredentialsclassandthePostWalkInformationmethodtosubmitthecurrentwalkinformationtotheuser'sFacebookpage.Alternatively,iftheuserchoosestheUserDetailsoption,wemakeacalltoourFacebookCredentialsclass,butthistimewecalltheGetProfileInformationmethodtoreturnaJSONdictionarythatcontainsthecurrentlylogged-inFacebookuser'sdetails,whichweassigntoanobjectvariablecalledobjUserDetails.
Finally,wecreateauserDetailsvariablethatweconstructbyextractingeachofthevaluesforname,last_name,first_name,etc.anddisplaythisinformationwithinanalertdialogboxusingtheDisplayAlertmethod.
UpdatingtheXamarin.FormsAppclasstohandleFacebookSignInInthissection,weneedtoupdateourXamarin.Forms.AppclassbymodifyingtheconstructorinthemainAppclasstoincludeadditionalpropertyandactionmethodsthatwillhelpwithnavigatingtoourWalksPageContentPageafterourTrackMyWalksapphassuccessfullysignedintoFacebook.
Let'slookathowwecanachievethisbyperformingthefollowingsteps:
1. OpentheTrackMyWalks.csfile,locatedwithintheTrackMyWalksPortableClassLibraryprojectsolution.
2. Next,ensurethattheTrackMyWalks.csfileisdisplayedwithinthecodeeditor,locatetheAppmethod,andenterthehighlightedcodesectionsshowninthefollowingcodesnippet:
//
//TrackMyWalks.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingSystem;
usingSystem.Threading.Tasks;
usingTrackMyWalks.Services;
usingTrackMyWalks.ViewModels;
usingXamarin.Forms;
namespaceTrackMyWalks
{
publicclassApp:Application
{
publicApp()
{
//ChecktheDeviceTargetOSPlatform
if(Device.OS==TargetPlatform.Android)
{
//Settherootpageofyourapplication
MainPage=newSplashPage();
}
elseif(Device.OS==TargetPlatform.iOS)
{
//SetourWalksPagetobetherootpageofour
//application.
varmainPage=newNavigationPage(newWalksPage()
{
Title="TrackMyWalks-iOS",
});
...
...
...
...
}
}
3. Next,createtheRemoveFBSignInPageactionpropertymethod,whichwillbeusedtohandletheremovaloftheFBSignInPageoncewehavesuccessfullysignedintoFacebook,asdeterminedbytheFBSignInPageRendererclass.WethencallthePopModalAsyncpropertyoftheNavigationpropertyontheMainPagetopopthelastpageofftheNavigationStack:
//PropertymethodinstancetoremoveourFBSignInPage
publicstaticActionRemoveFBSignInPage
{
get
{
returnnewAction(()=>App.Current.
MainPage.Navigation.
PopModalAsync());
}
}
4. Then,createtheNavigateToWalksPageinstancemethod;thiswillbeusedtohandlenavigatingtotheWalksPageViewModelwithintheNavigationStackbycallingthePushAsyncpropertyontheNavigationpropertyontheMainPage:
//HandlenavigatingtoourWalksPagewhen
wehavesuccessfully
//signedintoFacebook.
publicasyncstaticTaskNavigateToWalksPage()
{
awaitApp.Current.MainPage.Navigation.PushAsync
(newWalksPage());
}
}
Intheprecedingcodesnippet,webeginbyimplementingthemethodsthatwillberequiredtoremoveaninstanceofFBSignInPage,asdeterminedbytheRemoveFBSignInPagepropertyActionmethodthatwillbeusedtohandleremovaloftheFBSignInPageoncewehavesuccessfullysignedintoFacebook,asdeterminedbytheFBSignInPageRendererclass.WethencallthePopModalAsyncpropertyoftheNavigationpropertyontheMainPagetopopthelastpageofftheNavigationStack.
TheNavigateToWalksPageinstancemethodwillbeusedtohandlethenavigationtoourWalksPageViewModeluponsuccessfullybeingloggedintoFacebookviatheTrackMyWalksapp;thisisdonebyusingthePushAsyncpropertyontheNavigationproperty.
EnablingFacebookfunctionalitywithintheTrackMyWalksappWhenworkingwiththeFacebookSDKandtheXamarin.Authframework,weneedtomakesomeadditionalchangestoouriOSprojectsolutionpropertylisttoenableSSO(SingleSign-On)supportwhentheapplicationruns.
Let'slookathowwecanachievethisbyperformingthefollowingsteps:
1. Double-clickontheInfo.plistfilethatiscontainedwithintheTrackMyWalks.iOSproject,andensurethattheAdvancedtabisshowing.
2. Next,scrolldowntothebottomofthepage,andexpandtheURLTypessection.3. Then,withintheIdentifierfield,provideyourFacebookIdwhilstprefixingitwithfbasthe
firsttwocharactersthatisfb1234567890:
Note
TheURLtypessectionisasinglearraysub-itemthatneedsyourFacebookAppIDtobeprefixedwithfb.Thisisusedtoensuretheapplicationwillreceivethecall-backmethodsoftheURLandtheweb-basedOAuthflow.
NowthatwehavemodifiedourTrackMyWalks.iOSprojecttoallowourapptoreceivethecall-backmethodsoftheURLandtheweb-basedOAuthflow,weneedtodoonemorething,andset
upourFacebookAppIDandapplicationnamethatweconfiguredwithintheappdashboard:
1. EnsurethattheInfo.plistfileisdisplayedwithintheXamarinIDE,andthattheSourcetabisshowing.
2. Next,createtheFacebookAppIDandFacebookDisplayNamekeysbyclickingwithintheAddnewentrysectionoftheInfo.plist.
3. Then,enteryourFacebookAppIDasthestringdescriptionfortheValuefield,asshowninthefollowingscreenshot.Youwillnoticeherethatwedon'tneedtoprovidethefbprefixaswedidforourURLtypessection.
4. Next,enterTrackMyWalksasthestringdescriptionfortheValuefield,asshowninthefollowingscreenshot:
5. Then,createtheLSApplicationQueriesScenesarraykeys,andaddthestringdescriptionfieldsandtheirvalues,usingfbapi,fbauth2,andfbshareextensionastheValuefields,asshownintheprecedingscreenshot.
AppleintroducedtheAppTransportSecurityprotocolwithiOS9toenforcesecureconnectionsbetweenInternetconnections,aswellaswithanyappthatcommunicatesusingtheHTTPSprotocol.ItrequiresthatinformationisencryptedusingtheTLSversion1.2.WeneedtodisableandoptoutofATSentirelybyconfiguringourlocalInfo.plistfilewithintheTrackMyWalks.iOSprojectsolution,sothatitcancommunicateoverHTTPSwithoutanyissues.
6. Next,withtheInfo.plistfilestillopenwithintheXamarinIDEenvironment,createtheNSAppTransportSecuritydictionaryarray.
7. Next,addtheNSAllowArbitraryLoadsBooleanvalue,settingittoYes,asshownintheprecedingscreenshot.
Note
IfyouareinterestedinfindingoutmoreinformationontheAppTransportSecurityandNSAppTransportSecurityclass,refertotheXamarindeveloperdocumentationlocatedathttps://developer.xamarin.com/guides/ios/platform_features/introduction_to_ios9/ats/.
NowthatwehavefinishedbuildingallthecomponentsforourTrackMyWalksapplicationnecessarytoenableintegrationwithFacebook,wecanbuildandruntheTrackMyWalksapplicationwithintheiOSsimulator.Whencompilationcompletes,theiOSSimulatorwillappearautomaticallyandtheTrackMyWalksapplicationwillbedisplayed,asshowninthefollowingscreenshot:
Asyoucanseefromtheprecedingscreenshot,thiscurrentlydisplaysourFacebookSignInpage,whichtellstheusertoLogintoyourFacebookaccounttoconnecttoTrackMyWalksapplication.Toproceed,provideyourEmailaddressorphonenumberandyourFacebookpassword,andclickontheLogInbutton.
UponsuccessfullydeterminingthatyourdetailshavebeenvalidatedbyFacebook,youwillbepresentedwithaPosttoFacebookauthenticationscreen,askingyouwhoyouwouldliketoshareyourpostswith;youhavetheoptiontochooseeitherFriends,Public,orOnlyMe.Onceyouhavemadeyourchoice,clickontheOKbuttontodismissthePosttoFacebookdialoganddisplaytheListViewthatwillcontainourlistofwalktrailsfromourDataTemplatecontrol:
TheprecedingscreenshotshowstheDistanceTravelledpagethatincludesourPosttoFacebookbutton,andyouwillnoticethat,uponclickingonthisbutton,severalchoiceswillpopupforyoutochoosefrom.IfyouproceedandclickontheDisplayUserDetailsbutton,thiswillmakeacalltotheGetProfileInformationmethodthatislocatedwithinourFacebookCredentialsclass,andwillpasstheGetAuthAccountaccountinformationtoobtaintheuserdetails,whicharelocatedwithinourFacebookApiAuthToken,usingtheOpenGraphAPIplatform.Uponsuccessfullyobtainingtheuser'sFacebookdetails,youwillbepresentedwithadialogboxcontainingeachofouruser'sfielddetails:
TheprecedingscreenshotshowstheDistanceTravelledpagethatincludesourPosttoFacebookbutton,andyouwillnoticethat,uponclickingonthisbutton,severalchoiceswillpopupforyoutochoosefrom.IfyouclickonthePosttoFacebookWallbutton,thiswillmakeacalltothePostWalkInformationmethodthatislocatedwithinourFacebookCredentialsclass,andwillpassthecurrentlychosenwalktrailinformation,asdeterminedbyourViewModel.Uponsuccessfullypostingtotheuser'sFacebookwall,youwillbepresentedwithadialogboxtellingyouthatthewalkentryhasbeenpostedtotheuser'sFacebookwall.
SummaryInthischapter,weupdatedourTrackMyWalksapplicationtoallowustouseFacebooktosignintoourapp.YoulearnedhowyoucanuseboththeXamarin.AuthandtheFacebookSDKtoauthenticatewhethertheuserisavalidFacebookuser.Next,youlearnedhowtocreateacustomFacebookApiUsermodelandaFacebookCredentialsclassthatareusedtostoretheuser'scredentials,sothatthesecanbeusedthroughoutourapptoobtaininformationabouttheuser.
Asweprogressedthroughoutthechapter,youcreatedaFacebookSignIncontentpageandacustompagerendererclassthatwillallowtheusertosignintotheTrackMyWalksappusingtheirFacebookcredentials,andupdatedtheViewModelandcontentpagessothattheycanutilizetheFacebookfunctionalityappropriately.YoulearnedhowtotakeadvantageoftheFacebookSDKandpostwalkdatatoyourFacebookprofilepage,soyoucanshowoffyourprogresstoyourfriendsand/orworkcolleagues.
Inthenextchapter,you'lllearnhowtocreateandrununittestswithintheXamarinStudioIDE,usingtheUITestframework,beforemovingontolearnhowtoprofileourapplicationusingtheXamarinProfiler,andhowtousetheXamarinInspectortoinspectanddebugouruserinterfacesvisually,andfixUI-relatedproblems.
Chapter9.UnitTestingYourXamarin.FormsAppsUsingtheNUnitandUITestFrameworksInourpreviouschapter,weupdatedourTrackMyWalksapplicationtoallowustouseFacebooktosignintoourapp.YoulearnedhowyoucanuseboththeXamarin.AuthandFacebookSDKtoauthenticateiftheuserisavalidFacebookuser.Next,youlearnedhowtocreateacustomFacebookApiUsermodelandFacebookCredentialsclassthatwillbeusedtostoretheuser'scredentials,sothatthesecanbeusedthroughoutourapptoobtaininformationabouttheuser,aswellaspostinformationtotheirFacebookwall.
DuringthedevelopmentofourTrackMyWalksapp,wehavedesignedandimplementedvariousdesignpatternsandbestpractices,withtheintentionofmakingiteasiertomaintainandtestourappbyseparatingtheuserinterfaceandbusinesslogic.
Inthischapter,you'lllearnhowtocreateandrununittestsusingtheNUnitandUITesttestingframeworksrightwithintheXamarinStudioIDE.You'lllearnhowtowriteunittestsforourViewModelsthatwillessentiallytestthebusinesslogictovalidatethateverythingisworkingcorrectly,beforemovingontotestingtheuserinterfacesportionusingautomatedUItesting.
Thischapterwillcoverthefollowingtopics:
CreatingaunittestingsolutionusingthepopularNUnittestingframeworkAddingtheMoqNuGetpackagetotheunittestingsolutionAddingtheXamarinTestCloudAgentNuGetpackagetotheUITestsolutionSuccessfullylearninghowtotestyourViewModelsRunningunittestsandUITestsusingtheXamarinStudioIDEUnderstandingthecommontypesofUITesttestingmethodsCreatingaunittestingsolutionusingtheUITestframeworkSuccessfullylearninghowtotestyourContentPages(Views)
CreatingaunittestsolutionfolderusingXamarinStudioDuringthedevelopmentofourTrackMyWalksapplication,wehavedesignedtheuserinterfaces,ViewModels,andContentPages.Asdevelopers,theremaybetimeswhenwewouldliketoobtainfeedbacktoletusknowwhenourapplicationlogicisworkingasexpected.WecanusetheNUnittestingframeworktoprovideuswiththatconfirmation.
Inthissection,you'llbeginbyaddinganewsolutionfoldertoourexistingTrackMyWalksPortableClassLibrarysolution.ThisnewsolutionfolderwillbeusedtoseparateeachofourNUnitandUITestsfromourmainsolution.
ThegoodnewsisthatXamarinStudiohasbuiltinsupportfortheNUnitframeworkthatwecanrunourunittestsfrom,andthenhaveourresultsdisplayed,rightwithintheXamarinStudioIDE.
Let'slookathowtoaddanewSolutionfoldertoourTrackMyWalksPortableClassLibrary,byperformingthefollowingsteps:
1. Right-clickontheTrackMyWalkssolutionprojectandchoosetheAdd|AddSolutionFoldermenuoption,asshowninthefollowingscreenshot:
2. Next,enterinTrackMyWalks.Testsforthenameofthesolutionfolder.
NowthatyouhavecreatedtheTrackMyWalks.TestssolutionwithinthemainTrackMyWalkssolutionproject,ournextstepistocreateanewunittestprojectsolution,thatwillberesponsiblefortestingthebusinesslogicwithinourTrackMyWalksViewModels.
CreatingaunittestprojectusingXamarinStudioIntheprevioussection,wecreatedtheunittestingSolutionfolderwithinourTrackMyWalksmainprojectsolutionthatwillbeusedtoseparatetheunittestsfromourmainiOSandAndroidprojectsolutions.Thisissothatwecanruntheseindependentlyfromthemainsolution.
OneofthegreatbenefitsofusingXamarinStudiotohandleyourtestsisthatitleveragesthepopularNUnittestingframeworkforperformingunittests.WewillbeginbycreatingtheNUnittestprojectwithintheTrackMyWalks.Testssolutionthatwe'vepreviouslycreated.
Let'sstartbycreatinganewNUnitprojectwithinourTrackMyWalks.Testsprojectsolution,byperformingthefollowingsteps:
1. Right-clickontheTrackMyWalks.TestssolutionprojectandchoosetheAdd|AddNewProject...menuoption,asshowninthefollowingscreenshot:
2. Next,choosetheNUnitLibraryProjectoptionlocatedwithintheGeneralsectionundertheOther|.NETsection;ensureyouhaveselectedC#astheprogramminglanguagetouse,asshowninthefollowingscreenshot:
3. Then,clickontheNextbuttontoproceedtothenextstepinthewizard.4. Next,enterTrackMyWalks.UnitTeststouseasthenameforyournewprojectinthe
ProjectNamefield.5. Then,ensurethattheCreateaprojectdirectorywithinthesolutiondirectory.hasbeen
selected,asshowninthefollowingscreenshot:
6. Finally,clickontheCreatebuttontosaveyourprojectatthespecifiedlocation.
Onceyourprojecthasbeencreated,youwillbepresentedwiththeXamarinStudiodevelopmentenvironment,withyournewprojectedcreatedwithintheTrackMyWalks.Testssolutionfolder.
Inthenextsection,wewillbegintoaddtheMoq(pronouncedasmock)frameworklibrarythatwillberesponsibleforallowingustotestourViewModelswithintheTrackMyWalkssolution.
AddingtheMoqNuGetpackagetotheunittestprojectNowthatyouhavesetupandcreatedanewunittestproject,ournextstepistoaddtheMoq(pronouncedasMock)NuGetpackagetotheTrackMyWalks.UnitTestssolution.Thislibraryisessentiallyoneofthemostpopularandfriendlymockingframeworklibrariesforthe.NETplatform,andwewillusethistotestsomeofourViewModelswithinourTrackMyWalksapp.
Let'slookathowtoaddtheMoqNuGetpackagetoourTrackMyWalks.UnitTestsprojectsolution,byperformingthefollowingsteps:
1. Right-clickonthePackagesfolderthatiscontainedwithintheTrackMyWalks.UnitTestssolution,andchoosetheAddPackages...menuoption,asshowninthefollowingscreenshot:
2. ThiswilldisplaytheAddPackagesdialog,enterinmoqwithinthesearchdialog,andselecttheMoq:anenjoyablemockinglibraryoptionwithinthelist,asshowninthefollowingscreenshot:
3. Finally,clickontheAddPackagebuttontoaddtheNuGetpackagetothePackagesfoldercontainedwithintheTrackMyWalks.UnitTestssolution.
NowthatyouhaveaddedtheMoqNuGetpackage,ournextstepistobeginwritingthetestcasescenariosforourViewModels,whichwewillbecoveringinthenextsection.
AddingtheTrackMyWalksprojecttoTrackMyWalks.UnitTestsIntheprevioussection,weaddedtheMoqNuGetpackagetoourTrackMyWalkssolution.ThenextstepistoaddareferencetotheTrackMyWalkscorelibrarytoourTrackMyWalks.UnitTestssolution.
SincewewillbetestingourViewModels,youwillneedtoensurethatyouhaveappliedallofthecumulativecodechangestotheTrackMyWalkssolutionprojectthroughoutthisbooktoavoidanyissues,aswewillessentiallyneedtobreakeachofthetestsintoindividualclassesrepresentingeachViewModelandtheaccompanyingunittestclassthatwewanttotestthebusinesslogicon.TosuccessfullytestourViewModels,wewillfirstneedtoincludeareferencetotheTrackMyWalksprojectwithinourTrackMyWalks.UnitTestssolutionproject.
Let'slookathowwecanachievethis,byperformingthefollowingsteps:
1. Right-clickontheReferencesfolderthatiscontainedwithintheTrackMyWalks.UnitTestsprojectsolution,andchoosetheEditReferences...menuoption,asshowninthefollowingscreenshot:
2. Then,ensurethattheProjectstabhasbeenselectedandchoosetheTrackMyWalksprojecttoincludeourAndroidandiOSplatformsolutionprojectswithinourTrackMyWalks.UnitTestsprojectsolution:
3. Next,ensurethatyouhaveselectedtheTrackMyWalksprojectwithintheProjectstab,clickonOKtoaddtheprojectreferencetoyourReferencessectionofyourTrackMyWalks.UnitTestsprojectsolution,andclosetheEditReferencesdialog.
Inthenextsection,wewillbeginbycreatingourfirstunittestwhichwillberesponsibleforvalidatingtheWalkEntrymodeltoensurethataftertheViewModelhasbeeninitialized,itwillcontainwalkinformation.
CreatingandimplementingtheWalksTrailViewModelNUnittestclassNowthatyouhaveincorporatedtheTrackMyWalksprojectintotheTrackMyWalks.UnitTestssolution,ournextstepistocreatetheunittestforourWalksTrailViewModel.ThesetestswillbeusedtohelpuschecktoseewhenourViewModelpassesorfailsunderthesetestconditions.
Let'snowstarttoimplementthecoderequiredforourWalksTrailViewModelTestclass,byperformingthefollowingsteps:
1. CreateanemptyclasswithintheTrackMyWalks.UnitTestsprojectsolutionfolder,bychoosingAdd|NewFile....Ifyoucan'trememberhowtodothis,youcanrefertothesectionentitledCreatingtheTrackMyWalksmodel,withinChapter1,CreatingtheTrackMyWalksNativeApp.
2. Then,enterWalksTrailViewModelTestforthenameofthenewclassthatyouwanttocreate,andclickontheNewbuttontoallowthewizardtoproceedandcreatethenewfile.
3. Next,ensurethattheWalksTrailViewModelTest.csfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
//
//WalksTrailViewModelTest.cs
//WalksTrailViewModelTestingFramework
//
//CreatedbyStevenF.Danielon23/09/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingNUnit.Framework;
usingTrackMyWalks.ViewModels;
usingTrackMyWalks.Services;
usingMoq;
usingSystem.Threading.Tasks;
namespaceTrackMyWalks.Tests
{
4. Next,weneedtomodifytheWalksTrailViewModelTestclassconstructorbyaddingthe[TestFixture]attributewhichsetsupourclasstobeaninstanceoftheTestFixturetestingclass.Proceedandenterinthefollowingcodesnippet:
[TestFixture]
publicclassWalksTrailViewModelTest
{
WalksTrailViewModel_vm;
5. Then,createtheSetupinstancemethodthatwillberesponsibleforcreatinganewinstanceofourViewModelforeachoftheteststhataredeclaredwithintheclass.ThisistoensurethateachtestisrunusingacleaninstanceoftheViewModel.WethenproceedtodeclareanavMockvariableinstanceoftheMockclassfromourMoqlibrarytocreateanewinstanceoftheIWalkNavServiceandinstantiatetheWalksTrailViewModel,usingthenavMockinstance.Proceedandenterinthefollowingcodesnippet:
[SetUp]
publicvoidSetup()
{
varnavMock=newMock<IWalkNavService>().Object;
_vm=newWalksTrailViewModel(navMock);
}
6. Next,weneedtoimplementtheCheckIfWalkEntryIsNotNullinstancemethodthatwillchecktoseeifourWalksTrailViewModelhasbeenproperlyinitializedwhentheInitmethodiscalled.Wedeclarethe[Test]attributewhichisessentiallyanabstractclassthatrepresentsatestwithintheNUnit.Testframework.WeproceedtoinitializeourWalkEntrymodeltonull,andthencalltheInitmethodtochecktoseeiftheWalkEntrymodelhasbeenproperlysettothevalueprovidedintheInitmethod'sparameterandthenusetheIsNotNullmethodontheAssertclasstodisplayamessageshouldthetestfail.Thisissothatyoucantroubleshootthecodeatalaterpoint.Proceedandenterinthefollowingcodesnippet:
[Test]
publicasyncTaskCheckIfWalkEntryIsNotNull()
{
//Arrange
_vm.WalkEntry=null;
//Act
await_vm.Init();
//Assert
Assert.IsNotNull(_vm.WalkEntry,"WalkEntryisnull
afterbeinginitializedwithavalidWalkEntriesobject.");
}
}
}
Intheprecedingcodesnippet,webeganbyimplementingthevariousinstancemethodsthatwillberequiredtoperformeachtestforourWalksTrailViewModel.Weaddedthe[TestFixture]attributeatthebeginningofourclassconstructorsothatitwillbeaninstanceoftheTestFixturetestingclass.WethenproceededtocreatetheSetupinstancemethodsothatitwillberesponsibleforcreatinganewinstanceofourViewModelforeachoftheteststhataredeclaredwithintheclass,usingthe[Test]attribute.ThisisessentiallyanabstractclassthatrepresentsatestwithintheNUnit.Testframework,andensuresthateachtestisrunusingacleaninstanceoftheViewModel.
Next,weusedtheMockclassfromourMoqlibrarytocreateanewinstanceoftheIWalkNavServicewheninstantiatingtheWalksTrailViewModel.
Inthenextstep,weimplementedtheCheckIfWalkEntryIsNotNullinstancemethodthatwillperformachecktoseeifourWalksTrailViewModelhasbeenproperlyinitializedwhenevertheInitmethodhasbeencalled.Again,wedeclaredthe[Test]attributepriortoinitializingourWalkEntrymodeltonull,andpriortocallingtheInitmethodtochecktoseeiftheWalkEntry
modelhasbeenproperlysettothevalueprovidedintheInitmethod'sparameter.Afterthat,weusedtheIsNotNullmethodontheAssertclasstodisplayamessageshouldthetestfail.Thisissothatyoucantroubleshootthecodeatalaterpoint.
Inthenextsection,wewillbeginbycreatingthesecondunittestwhichwillberesponsibleforvalidatinginformationcontainedwithinourWalkEntryViewModeltoensurethatafterourViewModelhasbeeninitialized,wereceivetheexpectedresultsreturned.
CreatingandimplementingtheWalkEntryViewModelNUnittestclassIntheprevioussection,wecreatedtheNUnittestforourWalksTrailViewModelwhichcheckedtoensurethattheWalksEntrymodelwasproperlyinitializedaftertheInitmethodwascalled.Inthissection,wewillcreateanotherNUnittestthatwillchecktoseeifcertainpropertieswithinourWalksEntryViewModelhavebeensetupandinitialized.
Let'snowstarttoimplementthecoderequiredforourWalkEntryViewModelTestclassbyperformingthefollowingsteps:
1. CreateanemptyclasswithintheTrackMyWalks.UnitTestsprojectsolutionfolder,bychoosingAdd|NewFile....Ifyoucan'trememberhowtodothis,youcanrefertothesectionentitledCreatingandimplementingtheWalksTrailViewModelNUnittestclass,withinthischapter.
2. Then,enterinWalkEntryViewModelTestforthenameofthenewclassthatyouwanttocreate,andclickontheNewbuttontoallowthewizardtoproceedandcreatethenewfile.
3. Next,ensurethattheWalkEntryViewModelTest.csfileisdisplayedwithinthecodeeditor,andenterinthefollowingcodesnippet:
//
//WalkEntryViewModelTest.cs
//WalkEntryViewModelTestingFramework
//
//CreatedbyStevenF.Danielon23/09/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingNUnit.Framework;
usingTrackMyWalks.ViewModels;
usingTrackMyWalks.Services;
usingMoq;
usingSystem.Threading.Tasks;
namespaceTrackMyWalks.UnitTests
{
4. Next,weneedtomodifytheWalkEntryViewModelTestclassconstructorbyaddingthe[TestFixture]attributejustaswedidintheprevioussection.ThissetsupourclasstobeaninstanceoftheTestFixturetestingclass.Proceedandenterinthefollowingcodesnippet:
[TestFixture]
publicclassWalkEntryViewModelTest
{
WalkEntryViewModel_vm;
5. Then,createtheSetupinstancemethodthatwillberesponsibleforcreatinganewinstanceofourViewModelforeachoftheteststhataredeclaredwithintheclass.ThisistoensurethateachtestisrunusingacleaninstanceoftheViewModel.WethenusetheMockclassfromourMoqlibrarytocreateanewinstanceoftheIWalkNavServicewheninstantiating
theWalkEntryViewModel.Proceedandenterinthefollowingcodesnippet:
[SetUp]
publicvoidSetup()
{
varnavMock=newMock<IWalkNavService>().Object;
_vm=newWalkEntryViewModel(navMock);
}
6. Next,weneedtoimplementtheCheckIfEntryTitleIsEqualinstancemethodthatwillchecktoseeifourTitlepropertyhasbeenproperlyinitializedwhentheInitmethodhasbeencalled.We'lldeclarethe[Test]attributejustaswedidintheprevioustest,andthenwe'llproceedtoinitializetheTitlepropertyandcalltheInitmethodtocheckwhethertheTitlepropertyhasbeeninitializedcorrectlytothevalueprovidedintheInitmethod'sparameter.
7. Next,weusetheAreEqualmethodontheAssertclasstochecktoseeiftheTitlepropertyhasbeeninitializedcorrectly,andthendisplayamessagecontainingthevalueoftheTitlepropertyfromtheViewModel,shouldthetestfail.Proceedandenterinthefollowingcodesnippet:
[Test]
publicasyncTaskCheckIfEntryTitleIsEqual()
{
//Arrange
_vm.Title="NewWalk";
//Act
await_vm.Init();
//Assert
Assert.AreEqual("NewWalk",_vm.Title);
}
8. Then,weneedtoimplementtheCheckIfDifficultyIsEqualinstancemethodanddeclarethe[Test]attribute,priortoinitializingourDifficultypropertytoastringvalue,andthencallingtheInitmethod.Inthenextstep,weusetheAreEqualmethodontheAssertclasstocheckwhethertheDifficultypropertyhasbeeninitializedcorrectly,anddisplayamessagecontainingthevalueoftheDifficultypropertyfromtheViewModel,shouldthetestfail.Proceedandenterinthefollowingcodesnippet:
[Test]
publicasyncTaskCheckIfDifficultyIsEqual()
{
//Arrange
_vm.Difficulty="Easy";
//Act
await_vm.Init();
//Assert
Assert.AreEqual("Easy",_vm.Difficulty);
}
9. Next,weneedtoimplementtheCheckIfKilometersIsNotEqualinstancemethodthatdeclaresthe[Test]attributejustaswedidintheprevioustest.WetheninitializeourKilometerspropertytoaDoublevalue,andcalltheInitmethod.Inthenextstep,weusetheAreEqualmethodontheAssertclasstochecktoseeiftheKilometerspropertyhasbeeninitializedcorrectly,anddisplayamessagecontainingthevalueoftheKilometerspropertyfromtheViewModel,shouldthetestfail.Proceedandenterinthefollowingcodesnippet:
[Test]
publicasyncTaskCheckIfKilometersIsNotEqual()
{
//Arrange
_vm.Kilometers=40.0;
//Act
await_vm.Init();
//Assert
Assert.AreNotEqual(40.0,_vm.Kilometers);
}
}
}
Intheprecedingcodesnippet,webeganbyimplementingthevariousinstancemethodsthatwillberequiredtoperformeachtestforourWalkEntryViewModel.Weaddedthe[TestFixture]attributeatthebeginningoftheclassconstructorsothatitwillbeaninstanceoftheTestFixturetestingclass;andthenproceededtocreatetheSetupinstancemethodsothatitwillberesponsibleforcreatinganewinstanceofourViewModelforeachoftheteststhatisdeclaredwithintheclass,usingthe[Test]attributewhichisessentiallyanabstractclassthatrepresentsatestwithintheNUnit.Testframework,andensuresthateachtestisrunusingacleaninstanceoftheViewModel.
Next,weusedtheMockclassfromourMoqlibrarytocreateanewinstanceoftheIWalkNavServicewheninstantiatingtheWalkEntryViewModel.Inthenextstep,weimplementedtheCheckIfEntryTitleIsEqualinstancemethodthatwillperformachecktoseeiftheTitlepropertyhasbeenproperlyinitializedwhenevertheInitmethodhasbeencalled.Again,wedeclarethe[Test]attributepriortoinitializingtheTitlepropertyoftheWalksEntrymodel,andpriortocallingtheAreEqualmethodontheAssertclasstochecktoseeiftheTitlepropertyhasbeeninitializedcorrectly.WethendisplayedamessagecontainingthevalueoftheTitlepropertyfromtheViewModel,shouldthetestfail.
Next,weimplementedtheCheckIfDifficultyIsEqualinstancemethodthatwillinitializetheDifficultypropertytoastringvalue,andthencalltheInitmethodoftheWalkEntryViewModel.WecalledtheAreEqualmethodontheAssertclasstoconfirmthat,afterwecalltheInitmethod,thevalueoftheDifficultypropertyfromtheViewModelisthevaluethatweexpecttocomebackfromtheprovidedMockinstance.Ifthevalueisnotwhatweexpect,
thetestwillfailandwilldisplayamessagecontainingthevalueoftheDifficultypropertyfromtheViewModel.
Inourfinalstep,weimplementedtheCheckIfKilometersIsNotEqualinstancemethodthatinitializesourKilometerspropertytoaDoublevalue,andthencallstheInitmethod.JustaswedidinourCheckIfDifficultyIsEqualinstancemethod,weusedtheAreNotEqualmethodontheAssertclasstoconfirmthat,afterwecalltheInitmethod,thevalueoftheKilometerspropertyfromtheViewModelisthevaluethatweexpecttocomebackfromtheprovidedmockinstance.Ifthevalueisnotwhatweexpect,thetestwillfailandwilldisplayamessagecontainingthevalueoftheKilometerspropertyfromtheViewModel.
ForeachtestmethodthatyoucreatethatwillberepresentedbytheNUnit[Test]attribute,theArrange-Act-Assertpatternwillfollow.Thisisdescribedinthefollowingtable:
Testpattern Description
Arrange Thiswillessentiallyperformallthesettingupandinitializationconditionsforyourtest.
Act Thisensuresthatyourtestwillsuccessfullyinteractwiththeapplication.
Assert ThiswillexaminetheresultsoftheactionsthatwereinitiallyperformedwithintheActsteptoverifytheresults.
Youcannowsee,thatbyincorporatingtheNUnit.Frameworkwithinyourapplications,aswellasadoptingtheArrange-Act-Assertpattern,youcanessentiallyperformtestsonyourViewModelstoensurethattheresultsyouareexpectingarereturned.
Note
IfyouareinterestedinlearningmoreabouttheNUnit.Framework.Testclass,anditsassociatedmethods,pleaserefertotheinformationcontainedathttps://developer.xamarin.com/api/type/NUnit.Framework.Internal.Test/.
TolearnmoreabouttheNUnit.Framework.Assertclassandothermethodsthatyoucanusetohandlethedifferenttypesofassertions,pleaserefertotheinformationlocatedathttps://developer.xamarin.com/api/type/NUnit.Framework.Assert/.
Nowthatyouhavecreatedyourunittests,ournextstepistobeginrunningourtestsrightwithintheXamarinStudioIDE,whichwewillbecoveringinthenextsection.
RunningtheTrackMyWalks.UnitTestsusingXamarinStudioInourprevioussection,wecreatedandimplementedunittestsforboththeWalksTrailViewModelandtheWalkEntryViewModel.Thesecontainedsetsofvarioustestconditionsthatwecheckedagainst.
OurnextstepistobeginrunningtheseunittestsdirectlyfromwithintheXamarinStudiodevelopmentenvironment.
Let'slookathowwecanachievethiswiththefollowingsteps:
1. Torunaunittest,right-clickontheTrackMyWalks.UnitTestsprojectwithintheSolutionpane,andchoosetheRunItemoption,asshowninthefollowingscreenshot:
2. Alternatively,youcanalsoruntheunittestbyselectingtheTrackMyWalks.UnitTestssolutionprojectandthennavigatingtotheRunmenuoptionandchoosingtheRunUnitTestssub-menuitem.
Whenthecompilationoftheunittestshascompleted,youwillbepresentedwithalistshowingeachofyourtestresultsthathavepassed,failed,orwereignored.ThesearedisplayedwithintheTestResultspane,asshowninthefollowingscreenshot:
Shouldanyofyourtestsfail,thesewillbedisplayedwithintheTestResultspane,alongwiththeirassociatedStackTrace.YouwillalsonoticethatthemessagethatweprovidedwithintheAssert.AreEqualmethodwillalsobedisplayedaspartofthefailureresult:
Fromthisscreen,youhavetheoptionoffilteringyourtestresultsorre-runningyourunittestconditionsagain.Theseareexplainedinmoredetailinthefollowingtable:
Testresultoption Description
SuccessfulTests
Thiswilldisplayallthesuccessfullyexecutedtestswhichpassedtheconditionsasspecifiedwithinthetestcase.
InconclusiveTests
Thiswilldisplayanytestresultsthatwerefoundtobeinconclusive,meaningthatafirmresultcouldnotbedetermined.
FailedTests Thisoptiondisplaysalistofanyteststhatdidnotmeettheconditionsasspecifiedwithinthetestcasescenario.
IgnoredTests
Thisoptiondisplaysalistofanyteststhatwereignoredasspecifiedbythe[Ignore]attribute.
OutputThisoptiondisplaysaconsoleoutputforeachoftheteststhatareexecutedandwillcontainanyteststhathavesuccessfullypassed,failed,beenignored,orwerefoundtobeinconclusive.
RerunTests Thisoptionenablesyoutore-runyourtestsagain,withouttheneedforrecompilingyourtestcases.
NowthatyouhaveagoodunderstandingofhowtocreateyourownunittestsusingtheNUnittestingframework,wecannowlookathowtocreateanotherformofunittesting,whichiscalledautomatedUItesting.ThistimewewillbeleveragingtheUITestframeworkwhichwillenableustoperformtestsontheuserinterfaceportionofourTrackMyWalksappwhichwewillbecoveringoverthenextsections.
CreatingaUItestprojectusingXamarinStudioIntheprevioussection,wesawhoweasyitistocreateasetofunitteststhatenableustotestourViewModelswithintheTrackMyWalksproject.Whilstunittestingensuresthatasignificantamountofcodeistested,itisprimarilyfocusedontestingtheactualbusinesslogicwithintheapp.Thisleavestheuserinterfaceportionsoftheappstilluntested,butthebeautyofusingUItestingallowsustoautomatespecificactionswithinourapp'suserinterfacetoensurethatitisworkingasexpected.
Fortunately,XamarinStudioprovidesyouwitharichsetoftoolsforperformingautomatedUItests,andthesecanbebothwritteninC#andmakeuseoftheUITestframework.Let'sstartbycreatinganewUITestprojectwithinourTrackMyWalks.Testsprojectsolution,byperformingthefollowingsteps:
1. Right-clickontheTrackMyWalks.TestssolutionprojectandchoosetheAdd|AddNewProject...menuoption.Ifyoucan'trememberhowtodothis,youcanrefertothesectionentitledCreatingaunittestprojectusingXamarinStudio,locatedwithinthischapter.
2. Next,choosetheUITestAppoptionlocatedwithintheXamarinTestCloudsection,undertheMultiplatform|Testssection.EnsurethatyouhaveselectedC#astheprogramminglanguagetouse,asshowninthefollowingscreenshot:
3. Then,clickontheNextbuttontoproceedtothenextstepinthewizard.4. Next,enterTrackMyWalks.UITeststouseasthenameforyournewprojectastheProject
Namefield.5. Then,ensurethattheCreateaprojectdirectorywithinthesolutiondirectory.hasbeen
selected,asshowninthefollowingscreenshot:
6. Finally,clickontheCreatebuttontosaveyourprojectatthespecifiedlocation.
Onceyourprojecthasbeencreated,youwillbepresentedwiththeXamarinStudiodevelopmentenvironment,withyournewprojectcreatedwithintheTrackMyWalks.Testssolutionfolder.
YouwillnoticethatbydefaultourprojecthascreatedafilenamedTest.csthatwecanusetowriteourUITests,aswellasaclassnamedAppInitializer.csthatisessentiallyusedbytheTest.csclasstocreateanIAppinstanceandstarttheappforeachtestcondition.SincewewillonlybecreatingoneUITestforthischapter,wecanessentiallyjustusetheTest.csfilefornow.
Inanidealworld,youwouldbecreatingvarioustests,oneforeachtestcondition,soitwouldmakesensetobreakeachofyourUITestsintoindividualfiles.Inthenextsection,wewilllearnaboutsomeofthecommonlyusedUITestmethodsthatwecanusewhileperformingUITestsforourapplication'suserinterface.
UnderstandingthecommonlyusedUITestmethodsAsmentionedpreviously,inthissection,wewilllearnaboutsomeofthecommonlyusedmethodsthatwecanusewiththeUITestframework.TheUITestframeworkprovidesyouwithawayofautomatingtheinteractionsbetweenyouriOS,orAndroidappsusingC#andtheNUnittestingplatform.
WewillbeusinganinstanceoftheIAppandConfigureAppclassesthatwillbeusedtocreateouriOSandAndroidIAppinstancestohandlealltheinteractionswithintheUI.
Asweprogressthroughoutthenextcoupleofsections,wewillbetakingacloserlookathowtocreateIAppinstancesusingtheConfigureAppclass.TheUITestframeworkprovidesyouwithseveralAPIsthatyoucanusetointeractwithanapp'suserinterface.
ThefollowingtabledescribessomeofthemorecommonlyusedmethodsandtheonesthatwewillbeusingtotesttheTrackMyWalksapp:
UITestmethods Description
Screenshot() Thiswillessentiallytakeascreenshotofthecurrentstateoftheapp.
Tap()Thisisusedtosendatapinteractiontoaspecificelementontheapp'scurrentscreen.
EnterText()andClearText()
ThesemethodsareusedtoaddandremovetextfrominputelementssuchastheentryviewsusedwithinXamarin.Forms.
Query()Thismethodisessentiallyusedtolocateorfindelementsthatarecurrentlydisplayedwithintheapp'sscreen.
Repl()Thiscommandiscommonlyusedtointeractinreal-timewiththeappthroughtheterminalusingtheUITestAPI.
WaitForElement()Thismethodisusedtopausethetestuntilaspecificelementappearsontheapp'scurrentscreenwithinaspecifictimeoutperiod.
MethodssuchastheQueryandWaitForElementreturnanAppResult[]objectthatyoucan
essentiallyusetodeterminetheresultsofthecall.AnexamplewouldbethatifyouusedtheQuerymethodcallthatreturnsanemptyresultset,wecanbesurethattheelementdoesnotexistwithintheapp'scurrentscreen.
Note
ItisworthmentioningthatcurrentlytheUITestframeworkonlyprovidessupportforboththeiOSandAndroidplatformsanddoesn'tyetprovidesupportfortheWindowsPhoneplatform.
Asyouwillseefromthemethodsdisplayedinthefollowingtable,theseareessentiallyallthememberspertainingtotheAppQueryclassthatareusedbytheQueryandWaitForElementmethodmembersoftheIAppmethods:
AppQueryclassmethods Description
Class() Findselementsontheapp'scurrentscreen,basedontheirclasstype.
Marked()Findselementswithintheapp'scurrentscreen,basedontheirtextoridentifier.
Css()PerformsCSSselectoroperationsonthecontentsofaWebViewontheapp'scurrentscreen.
Note
IfyouareinterestedinlearningmoreaboutthevarioustypesofUITestmethods,pleaserefertotheIntroductiontoXamarin.UITestathttps://developer.xamarin.com/guides/testcloud/uitest/intro-to-uitest/.
NowthatyouunderstandsomeofthemostcommonlyusedUITestmethods,wecanstarttoimplementsometestswhichwewillbecoveringoverthenextcoupleofsectionswithinthischapter.
SettingupandinitializingourTrackMyWalksappforUITestPriortostartinganappandinteractingwithitusingtheUITestframework,weneedtodosomepreliminaryinitializationstepsforwhichwe'llmakesomemodificationswithintheAppInitializerclass.TheAppInitializerclasscontainsastaticmethodcalledStartApp.
Thisstaticmethodiscalledeachtimethetest'sSetupmethodiscalledtogetanIAppinstance.ItcurrentlysupportsboththeiOSAppandAndroidAppasdefinedbytheConfigureAppclass.
OnethingthatyouwillnoticewithintheAppInitializerclassisthattheApkFileandtheAppBundlehavebothbeencommentedout.YouwillneedtouncommenttheseifyouwouldliketorunthetestslocallywithintheunittestpaneusingXamarinStudio.
usingSystem;
usingSystem.IO;
usingSystem.Linq;
usingXamarin.UITest;
usingXamarin.UITest.Queries;
namespaceTrackMyWalks.UITests
{
publicclassAppInitializer
{
publicstaticIAppStartApp(Platformplatform)
{
...
...
...
if(platform==Platform.Android)
{
returnConfigureApp.Android
//TODO:Updatethispathtopoint
toyourAndroid
//appanduncommentthecodeifthe
appisnot
//includedinthesolution.
//.ApkFile("../../../Droid/bin/Debug
/TrackMyWalks.apk").StartApp();
}
returnConfigureApp.iOS
//TODO:Updatethispathtopointto
youriOSappand
//uncommentthecodeiftheappis
notincludedinthe
//solution.
//.AppBundle("../../../iOS/bin/
iPhoneSimulator/Debug/TrackM
//yWalks.iOS.app").StartApp();
}
}
}
Asyoucanseefromtheprecedingcodesnippet,theAppInitializerclasscontainsseveraldifferentmethodsthatarepartoftheConfigureAppmethod.Thefollowingtableprovidesabriefdescriptionofwhateachoneisusedfor:
ConfigureAppmethods Description
AppBundle()Thismethodisusedforspecifyingthepathtotheappbundletouseduringtesting.
StartApp() Thismethodessentiallylaunchestheappwithinthesimulator.
Debug()
Thismethodisessentiallyusedtoenabledebuggingandloggingofmessagesandisparticularlyusefulifyouneedtotroubleshootproblemswhenrunningtheapplicationusingthesimulator.
DeviceIdentifier()
Thismethodconfiguresthedevicetousewiththedeviceidentifier.ThiscanbeusedtodetectiOSsimulatorsusingthefollowingcommandlinestatement:
xcruninstruments-sdevices
EnableLocalScreenshots
Thismethodisusedtoenablescreenshotswhenyou'rerunningtestslocally.Bydefault,screenshotsarealwaysenabledwhenevertestsarebeingrunusingXamarinTestCloud.
Repl()ThismethodwillessentiallypausethetestexecutionandinvoketheREPLinaterminalprompt.
Asyoucansee,theAppInitializerfiledoesn'tcontainmuchinformation,butasweworkourwaythroughthischapter,wewillbeaddingtheXamarin.TestCloud.AgenttotheiOSportionoftheTrackMyWalksappsothatwewillbeabletorunourUITests.
ImplementingtheCreateNewWalkEntryusingtheUITest.FrameworkIntheprevioussection,welookedatsomeofthedifferenttypesofmethodsthatwecanusetocustomizetheAppInitializerclasssothatwecanspecifydifferentappbundlestouseduringtesting,aswellastoenablescreenshots,andprovidetheabilitytodebugourunittestswithintheXamarinStudioenvironment.
Inthissection,wewillbeginbyimplementingaUITestthatwecanusetohandlesigningintoFacebookandcreatinganewwalkentryusingtheUITestframework.
Let'snowstarttoimplementthecoderequiredforourclassbyperformingthefollowingsteps:
1. OpentheTest.csfilewhichcanbelocatedwithintheTrackMyWalks.UITestsprojectaspartoftheTrackMyWalks.Testssolutionfolder.
2. Next,ensurethattheTest.csfileisdisplayedwithinthecodeeditor,andenterinthefollowinghighlightedcodesections,asshowninthecodesnippet:
usingSystem;
usingSystem.IO;
usingSystem.Linq;
usingNUnit.Framework;
usingXamarin.UITest;
usingXamarin.UITest.Queries;
namespaceTrackMyWalks.UITests
{
[TestFixture(Platform.Android)]
[TestFixture(Platform.iOS)]
publicclassTests
{
IAppapp;
Platformplatform;
3. Then,modifytheTestsinstancemethodthatwillberesponsibleforcreatinganewinstanceofourIAppinstance.WeupdateourentryCellPlatformClassNamestringvariabletoreturnthetypeofTextField,dependentontheplatformthatwearetestingon.UnderiOS,weusetheUITextField,whereasunderAndroiditwillusethedefaultEntryCellEditTextclass.Proceedandenterinthehighlightedcodesectionswithinthefollowingcodesnippet:
stringentryCellPlatformClassName;
publicTests(Platformplatform)
{
this.platform=platform;
entryCellPlatformClassName=platform
==Platform.iOS
?"UITextField"
:"EntryCellEditText";
}
[SetUp]
publicvoidBeforeEachTest()
{
app=AppInitializer.StartApp(platform);
}
[Test]
publicvoidAppLaunches()
{
app.Screenshot("Firstscreen.");
}
4. Next,createtheSignInToFacebookinstancemethodthatwillberesponsibleforhandlingthetest'sstepsspecificallytotheFacebooksign-inprocess.Thisusestheuser'slogincredentialstoautomatetheloginprocesspriortocarryingoutotherstepswithintheUI.Proceedandenterinthehighlightedcodesectionswithinthefollowingcodesnippet:
//PerformsigningintoFacebook
publicvoidSignInToFacebook()
{
//SetupourFacebookcredentials
varFaceBookEmail="<Your-Facebook-Email-Address>";
varFaceBookPassword="<Your-Facebook-Password>";
//WaitforLoginbuttonwithinFacebookoAuthwebview
//toappear.
app.WaitForElement(x=>x.WebView().Css("[name=login]"));
//Entertextwithinthewebviewwithname="email"
app.EnterText(x=>x.WebView().Css("[name=email]"),
FaceBookEmail);
//Entertextwithinthewebviewwithname="email"
app.EnterText(x=>x.WebView().Css("[name=pass]"),
FaceBookPassword);
app.ScrollDownTo(x=>x.WebView().Css("[name=login]"));
//Tapthebuttoninthewebviewwithname="login"
app.Tap(x=>x.WebView().Css("[name=login]"));
}
5. Then,createthePopulateEntryCellFieldsinstancemethodthatwillberesponsibleforhandlingtheteststepsspecificallyforthecreationofanewwalkentry.Inthismethod,wemakeuseofboththeClearTextandEnterTextmethodsoftheUITestframeworkthatwilllocateeachentryfieldwithintheNewWalkEntryformandpopulateitwiththenecessaryinformation.TheDismissKeyboardmethodwill,asthenamesuggests,dismissthekeyboardfromtheviewandcontinuetothenextstep.Proceedandenterinthehighlightedcodesectionswithinthefollowingcodesnippet:
//PopulateourEntryCellFields
voidPopulateEntryCellFields()
{
//ClearthedefaulttextentryforourTitleEntryCell
app.ClearText(x=>x.Class
(entryCellPlatformClassName).Index(0));
app.DismissKeyboard();
//EnterinsomedefaulttextforourTitleEntryCell
app.EnterText(x=>x.Class
(entryCellPlatformClassName).Index(0),
"ThisisanewwalkEntry");
app.DismissKeyboard();
//EnterinsomedefaulttextforourNotesEntryCell
app.EnterText(x=>x.Class(entryCellPlatformClassName).Index(1),
"NewNoteEntryForWalkEntry");
app.DismissKeyboard();
//ClearthedefaulttextforourImageUrlEntryCell
app.ClearText(x=>x.Class(entryCellPlatformClassName).Index(6));
app.DismissKeyboard();
//EnterinsomedefaulttextImageUrlEntryCell
app.EnterText(x=>x.Class(entryCellPlatformClassName).Index(6),"
https://heuft.com/upload/image/
400x267/no_image_placeholder.png");
app.DismissKeyboard();
}
6. Next,createtheChooseDifficultyPickerinstancemethodthatwillberesponsiblefordisplayingthedifficultypickerthatcontainsvariouschoicesofdifficultyfortheusertochoosefrom.Allwearedoinghereisdisplayingthepickerwhentheusertapsintothecellentry,anddismissingthepickerfromtheviewwhenthenusertapstheDoneorOKbuttons.Proceedandenterthehighlightedcodesections,asshownwithinthefollowingcodesnippet:
//AutomaticallytapintotheDifficultyCelltodisplaythe
//DifficultyPicker,anddismissitbypressingtheDoneor
//OKbutton.
publicvoidChooseDifficultyPicker()
{
//TapintoDifficultyEntryCell
app.Tap(x=>x.Class(entryCellPlatformClassName).Index(5));
//TapDonelocatedwithintheDifficultyPickerCell
if(platform==Platform.iOS)
app.Tap(x=>x.Marked("Done"));
else
app.Tap(x=>x.Marked("OK"));
}
}
7. Then,createtheCreateNewWalkEntryUITestmethodthatincludesthe[Test]attribute.ThisisanabstractclassthatrepresentsatestwithintheUITestandNUnit.Testframework.Thismethodwillessentiallybethemaintestdriverandwillcalleachoftheotherinstancemethodsthatwe'vepreviouslydeclared.Withinthismethod,wecallourSignInToFacebookmethodtoperformthesign-intoFacebook,usingtheuser'sFacebookcredentials.
8. Uponsuccessfullogin,weusetheWaitForElementmethodtowaituntilthemainTrackMyWalksscreenhasbeendisplayed,priortousingtheAssert.IsTruemethodtochecktoseeiftheTrackMyWalksscreenisdisplayed.Ifit'snotdisplayed,ourtestwillfailanddisplaytheassignederrormessagewithintheTestResultsscreen.Proceedandenterthefollowingcodesectionsshownwithinthefollowingcodesnippet:
[Test]
publicvoidCreateNewWalkEntry()
{
//SignintoFacebook
SignInToFacebook();
//Waitformainscreentoappearandcheckforour
//navigationtitle.
varnavigationBarTitle=(platform==Platform.iOS?
"TrackMyWalks-iOS":
"TrackMyWalks-Android");
varmainScreen=app.WaitForElement(x=>x.Marked
(navigationBarTitle).Class("UINavigationBar"));
//ChecktoseeiftheTrackMyWalks-iOSmainscreenis
//displayed.
Assert.IsTrue(mainScreen.Any(),navigationBarTitle+"screen
wasn'tshownaftersigningin.");
9. Next,weusetheTapmethodoftheappclassinstanceandtheMarkedmethodtofindtheAddelementwithintheapp'scurrentscreen,andwaituntiltheNewWalkEntrypageisdisplayedwithinthescreen.WethenproceedtousetheAssert.IsTruemethodtochecktoseeiftheNewWalkEntryscreenhasbeensuccessfullydisplayed.Alternatively,ourtestwillfailanddisplaytheassignederrormessagewithintheTestResultsscreen.Proceedandenterinthehighlightedcodesectionsshownwithinthefollowingcodesnippet:
//ClickontheAddbuttonfromourmainscreenandwaitfor
//theNewWalkEntryscreentoappear.
app.Tap(x=>x.Marked("Add"));
varnewWalkEntryBarTitle="NewWalkEntry";
varnewWalkEntryScreen=app.WaitForElement(x=>x.Marked(newWalk
EntryBarTitle));
//ChecktoensurethatourNewWalkEntryscreenwasdisplayed.
Assert.IsTrue(newWalkEntryScreen.Any(),newWalkEntryBarTitle+"
screenwasnotshownaftertappingtheAddbutton.");
10. Then,wecallthePopulateEntryCellFieldsandChooseDifficultyPickerinstancemethodstopopulateourentrycellfieldsandhandlethedisplayofthedifficultypickerselector.Inourfinalsteps,weusetheTapmethodoftheappclassinstance,andweusetheMarkedmethodtofindtheSaveelementwithintheapp'scurrentscreen.We'llwaituntiltheTrackMyWalkspageisdisplayed,beforeusingtheAssert.IsTruemethodtochecktoseeiftheTrackMyWalksscreenhasbeensuccessfullydisplayed.Alternatively,ourtestwillfailanddisplaytheassignederrormessagewithintheTestResultsscreen.Proceedandenterthefollowinghighlightedcodesections,asshownwithinthefollowingcodesnippet:
//PopulateourEntryCellFields
PopulateEntryCellFields();
//DisplayourDifficultyPickerselector.
ChooseDifficultyPicker();
//ThentapontheSavebuttontosavethedetailsandexit
//thescreen.
app.Tap(x=>x.Marked("Save"));
//Next,waitformainscreentoappear
mainScreen=app.WaitForElement(x=>x.Marked
(navigationBarTitle).
Class("UINavigationBar"));
//ChecktoseeiftheTrackMyWalks-iOSmainscreenis
//displayed.
Assert.IsTrue(mainScreen.Any(),navigationBarTitle
+"screenwasn'tshownaftersigningin.");
}
}
}
Intheprecedingcodesnippet,webeganbyimplementingthevariousinstancemethodsthatwillberequiredtoperformeachtestforourCreateNewWalkEntry.Weaddedthe[Test]attributetoourCreateNewWalkEntryinstancemethod.Thismethodwillessentiallybethemaintestdriverandcalleachoftheotherinstancemethodsthatwe'vepreviouslydeclared.Withinthismethod,wecallourSignInToFacebookmethodtoperformthesign-intoFacebook,usingtheuser'sFacebookcredentials.Uponsuccessfullogin,weusetheWaitForElementmethodtowaituntilthemainTrackMyWalksscreenhasbeendisplayed.
Inthenextstep,weusedtheTapmethodoftheAppclassinstance,andusetheMarkedmethodtofindtheAddelementwithintheApp'scurrentscreen,andwaituntiltheNewWalkEntrypageisdisplayedwithinthescreen.WethenproceedtousetheAssert.IsTruemethodtochecktoseeiftheNewWalkEntryscreenwassuccessfullydisplayed.Alternatively,ourtestwillfailanddisplaytheassignederrormessagewithintheTestResultsscreen.
AddingtheXamarinTestCloudAgenttotheiOSprojectNowthatyouhavecreatedyourUITest,thenextstepistoaddtheXamarinTestCloudAgentNuGetpackagetoourTrackMyWalks.iOSproject.ThislibraryallowsyoutoexecuteyourXamarin.UITest,usingC#andtheNUnitframeworktovalidatethefunctionalityofiOSandAndroidappswithintheXamarinStudiodevelopmentenvironment.
Let'slookathowtoaddtheXamarinTestCloudNuGetpackagetoourTrackMyWalks.iOSproject,byperformingthefollowingsteps:
1. Right-clickonthePackagesfolderthatiscontainedwithintheTrackMyWalks.iOSproject,andchoosetheAddPackages...menuoption,asyoudidinthesectionentitled,AddingtheMoqNuGetpackagetotheunittestproject,locatedwithinthischapter.
2. ThiswilldisplaytheAddPackagesdialog.EnterinCloudAgentwithinthesearchdialog,andselecttheXamarinTestCloudAgentoptionwithinthelist,asshowninthefollowingscreenshot:
3. Finally,clickontheAddPackagebuttontoaddtheNuGetpackagetothePackagesfolder
containedwithintheTrackMyWalks.iOSproject.
NowthatyouhaveaddedtheXamarinTestCloudAgentNuGetpackage,ournextstepistobeginbymodifyingtheAppDelegateclasswithintheTrackMyWalks.iOSportionofourproject.Wewillbecoveringthisinthenextsection.
UpdatingtheTrackMyWalksAppDelegateclasstohandleXamarinTestCloudAgentPriortorunningUITestswithinXamarinStudio,wewillneedtoaddtheXamarinTestCloudAgentNuGetpackagetotheiOSportionofourTrackMyWalksapp.UnderAndroid,thisisnotrequiredastheXamarinTestCloudAgentisprovidedbytheUITestframework.
Inthissection,weneedtoupdatetheAppDelegateclass,bymodifyingtheFinishLaunchingmethodlocatedwithinourTrackMyWalks.iOSproject.ThiswillincludeacompilerdirectivethatwillstarttheXamarinTestCloudAgent.
Let'slookathowwecanachievethiswiththefollowingsteps:
1. OpentheAppDelegate.csfilelocatedwithintheTrackMyWalks.iOSproject.2. Next,ensurethattheAppDelegate.csfileisdisplayedwithinthecodeeditor,andlocate
theFinishLaunchingmethodandenterinthefollowinghighlightedcodesections:
//
//AppDelegate.cs
//TrackMyWalks
//
//CreatedbyStevenF.Danielon04/08/2016.
//Copyright©2016GENIESOFTSTUDIOS.Allrightsreserved.
//
usingFoundation;
usingUIKit;
namespaceTrackMyWalks.iOS
{
[Register("AppDelegate")]
publicpartialclassAppDelegate:global::Xamarin.Forms.
Platform.iOS.FormsApplicationDelegate
{
publicoverrideboolFinishedLaunching
(UIApplicationapp,NSDictionaryoptions)
{
global::Xamarin.Forms.Forms.Init();
//IntegrateXamarinFormsMaps
Xamarin.FormsMaps.Init();
#ifUSE_TEST_CLOUD
Xamarin.Calabash.Start();
#endif
LoadApplication(newApp());
returnbase.FinishedLaunching(app,options);
}
}
}
Note
CalabashisbasicallyanAutomatedUIAcceptanceTestingframeworkthatallowsyoutowriteandexecuteteststhatvalidatethefunctionalityofyouriOSandAndroidapps.
Intheprecedingcodesnippet,youwillnoticethatwehavedefinedtheUSE_TEST_CLOUDcompilervariablethatiswrappedwithinthe#ifand#endifdirectiveandincludesacalltotheXamarin.Calabash.Start()methodthatwillonlybestartedwhenithasbeendefinedunderspecificconfigurationsasdefinedwithinthecompilerconfigurationsettingsfortheproject.
Note
IfyouareinterestedinlearningmoreabouttheCalabashframework,pleaserefertothesectiononanIntroductiontoCalabashwhichislocatedathttps://developer.xamarin.com/guides/testcloud/calabash/introduction-to-calabash/.
WehavejustaddedinthecodethatwillessentiallystartourXamarinTestCloudfunctionality.However,forthistowork,wewillneedtoperformoneadditionalstep,whichistomodifythecompilerconfigurationsforourTrackMyWalks.iOSproject.Performthefollowingtoachievethis:
1. Right-clickontheTrackMyWalks.iOSproject,andchoosetheOptionsmenuoption.2. Next,withintheProjectOptions-TrackMyWalks.iOSdialog,choosetheCompiler
optionlocatedundertheBuildsection.3. Then,ensurethatyouhavechosendebugfromtheConfigurationdropdownandthatyou
havechoseniPhoneSimulatorfromthePlatformdropdown.4. Next,addtheUSE_TEST_CLOUD;tothelistofexistingDefineSymbols,asshowninthe
followingscreenshot:
5. Then,clickonOKtosaveyourchangesandclosetheProjectOptions-TrackMyWalks.iOSdialog.
NowthatyouhavemodifiedthecompilerconfigurationsforouriOSportionoftheTrackMyWalksapp,wecanfinallybuildandrunourUITestsusingXamarinStudio,similarlytowhatwedidwhenexecutingourNUnittests.However,thisneedstobehandledverydifferently,andwewillbecoveringthisinthenextsection.
RunningtheTrackMyWalksUITestsusingXamarinStudioPriortorunningyourUITestswithinXamarinStudio,youwillneedtoaddyouriOSorAndroidappstotheTestAppsnodeoftheUnitTestspane,oralternativelyspecifyingapathtoyourappwithintheAppInitializerclass.Ifyoudon'tdothis,yourtestswillcontinuetofailuntilyouaddtheseprojectstoyoursolution.
Inthissection,wewilllookathowtogoaboutaddingyourappstotheTestAppsnodewithintheUnitTestspane.Let'slookathowwecanachievethiswiththefollowingsteps:
1. ToaddyouriOSandAndroidappstoyourTrackMyWalks.UITestsproject,selecttheViewmenuoption,thenchoosethePadssub-menuitem,andthentheUnitTestsoption,asshowninthefollowingscreenshot:
2. Next,right-clickontheTestAppsitemwithintheUnitTestspaneandclickontheAddAppProject.ThiswilldisplaytheSelectaprojectorsolutiondialogthatallowsyoutoselecteachofyourprojectsforthevariousplatforms,asshowninthefollowingscreenshot:
3. OnceyouhaveselectedtheprojectsthatyouwouldliketoaddtoyourTrackMyWalks.UITestssolution,clickOKanddismissthedialog.
Note
Ifforsomereason,youdon'tseeyouriOSappprojectlisted,youmayhaveforgottentoaddtheXamarinTestCloudAgentNuGetpackagetoyouriOSproject.
OnceyouhavesuccessfullyaddedyourprojectstotheTrackMyWalks.UITestssolutionproject,ournextstepistorunourappandseetheresults:
4. TorunyourUITests,selecttheRunmenuoption,thenchoosetheRunUnitTestsmenuitem,asshowninthefollowingscreenshot.
Whenyourappstartstorun,theUITestframeworkwillautomaticallydeployyourapptotheiOSorAndroidsimulator,andthenruntheappandprocessthrougheachofthestepsthatyouhavespecifiedwithinyourtestmethods.TheresultsfromeachofthetestswillappearwithintheTestResultspanewithinXamarinStudio.
SummaryInthischapter,weupdatedtheTrackMyWalksapplicationbyaddinganewprojectsolution,TrackMyWalks.Tests,sothatwecanseparateourtestsfromthemainPortableClassLibrary.Thisgivesustheabilitytowritetestcases.WeaddedtheMockframeworksothatitwillprovideuswiththeabilitytosuccessfullytestourViewModelsaswellastoprovidethebusinesslogicbehindthem.
WethenmovedontoconsideringhowwecanleveragetheUITestframeworktowrite,test,andexecuteUItestslocallybyusingtheXamarinTestCloudAgentandtheCalabashframework,byaddingtheiOSandAndroidprojectstotheUITestsolutionproject.
Inthefinalchapter,you'lllearnhowtoprepareyouriOSappforsubmissiontoiTunesConnect,andlearnhowtosetupinternalandexternaluserswithinTestFlightsothatyouruserscandownloadandtestyourappsontheiriOSdevices.Toendthechapter,youwilllearnhowtocode-signyourAndroidappsbeforepublishing,andreleasingyourAndroidAPKfiletotheGooglePlayStore.
Chapter10.PackagingandDeployingYourXamarin.FormsApplicationsInourpreviouschapter,weupdatedourTrackMyWalksapplicationtoallowustocreateandrununittestsusingtheNUnitandUITesttestingframeworksrightwithintheXamarinStudioIDE.YoulearnedhowtowriteunittestsforourViewModelstotestthebusinesslogictovalidatethateverythingisworkingcorrectly,beforemovingontotestingtheuserinterfacesportionusingautomatedUItesting.
Inthischapter,you'lllookatwhatisrequiredtosubmityourTrackMyWalksiOSapptotheAppleAppStore,andshareyourcreationswiththerestofthecommunity.
You'lllearnthestepsrequiredtosetupyouriOSdevelopmentteam,aswellasthecertificatesforbothdevelopmentanddistribution,andlearnhowtocreatethenecessaryprovisioningprofilesforbothyourdevelopmentanddistributionbuilds,andcreatethenecessaryappIDsforyourapplication.
Attheendofthechapter,youwilllearnhowtoregisteryouriOSdevicessothatyouruserscandownloadandtestyourappsontheiriOSdevicesandlearnhowtoprepareyourTrackMyWalksiOSappforsubmissiontoiTunesConnect,usingtheXamarinStudioIDE.
Thischapterwillcoverthefollowingtopics:
SettingupyouriOSdevelopmentteamCreatingtheTrackMyWalksiOSdevelopmentcertificateObtainingthedevelopmentcertificatefromAppleRegisteringyouriOSdevicesfortestingCreatingyourTrackMyWalksiOSAppIDCreatingthedevelopmentprovisioningprofilesPreparingyourTrackMyWalksiOSappforsubmissionUsingtheprovisioningprofilestoinstalltheappontheiOSdeviceBuildingandarchivingyourappforpublishingusingXamarinStudioUsingXamarinStudiotosubmityourTrackMyWalksiOSapptoiTunesConnect
CreatingandsettingupyouriOSdevelopmentteamYouhavefinallycompletedbuildingyourTrackMyWalksappandarereadytoreleaseittotherestoftheworld;allyouneedtodoisdecidehowtodeployandmarketit.BeforeyoucanbeginsubmittingyouriOSapplicationstotheAppleAppStoreforapproval,youwillneedtofirstsetupyouriOSdevelopmentteam,whichcanbeachievedbyfollowingthesesteps:
1. LogintotheiOSdeveloperportalwebsiteathttp://developer.apple.com/.2. ClickontheMemberCenterlinkthatislocatedrightatthetopofthescreen.3. SignintoyouraccountusingyourAppleIDandpassword.Thiswillthendisplaythe
developerprogramresourcespage,asshowninthefollowingscreenshot:
4. Next,clickontheiTunesConnectbutton,ashighlightedintheprecedingscreenshot.ThisiswhereyoucancheckonvariousthingssuchasSalesandTrends,PaymentsandFinancialReports,andAppAnalytics.Takealookatthefollowingimage:
5. Next,clickontheUsersandRolesbutton,ashighlightedintheprecedingscreenshot.ThiswillbringuptheUsersandRolesoptionpanefromwhereyoucanaddanewuser,asshowninthefollowingscreenshot:
Note
TheUsersandRolesscreenallowsyoutoaddyourselforthepeoplewithinyourorganizationwhowillbeabletologintotheiOSdeveloperprogramportal,testappsoniOSdevices,andaddadditionaliOSdevicestotheaccount.
6. EnsurethatyouarewithintheiTunesConnectUserssection,ashighlightedintheprecedingscreenshot.Then,clickonthe+buttontobringuptheAddNewUserscreenthatisshowninthefollowingscreenshot.
7. Next,fillintheUserInformationsectionforthepersonthatyouwillbeaddingtoyourdevelopmentteam.Onceyouhavefinished,clickontheNextbutton,asshowninthe
followingscreenshot:
8. Next,undertheRolesection,fromthelistofrolesavailable,choosewhatrolestheusercanperformandthenclickontheNextbutton,asshowninthefollowingscreenshot:
9. Next,fromundertheNotificationsandSettingssections,thisiswhereyouwillbeassigningthewaysinwhichyouwanttheusertobenotified.Fromthisscreen,youalsohavetheabilityofspecifyingwhatinformationrelatingtoalistofterritoriesyouwanttheusertobenotifiedabout,asshowninthefollowingscreenshot:
10. Onceyouhavefinishedspecifyingeachofthedifferenttypesofnotificationmethods,clickontheSavebutton,asshownintheprecedingscreenshot.Thenewuseraccountwillthenbecreated,alongwithaconfirmatione-mailthatwillbesenttotheusers,accounts,requestingthemtoactivatetheiraccount:
Nowthatwehavecoveredthenecessarystepsrequiredtocreateandassignrolestonewusers,
aswellassettingupwhichuserrolescanlogintotheiOSdeveloperportaltomanagenewandexistingusers,viewSalesandTrendsreports,aswellasPaymentsandFinancialstatements,ournextstepistolookatthestepsinvolvedtogenerateaniOSdevelopmentcertificate.
Thiscertificateisencryptedandservesthepurposeasyourdigitalidentificationsignature,andyoumustsignyourappsusingthiscertificatebeforeyoucansubmityourappstotheAppleAppStore.
CreatingtheTrackMyWalksiOSdevelopmentcertificateInthissection,youwilllearnhowtocreatetheiOSdevelopmentcertificatethatwillenableustorunandtestourTrackMyWalksappontheiOSdevice.WewillbeginbygeneratingtheiOSdevelopmentcertificate,whichwillbeencryptedandwillservethepurposeofidentifyingyoudigitally.
YouwillthenneedtosignyourappsusingthiscertificatebeforeyoucanrunandtestanyapplicationthatyoudeveloponyouriOSdevice.Tobegin,performthefollowingsimplesteps:
1. LaunchtheKeychainAccessapplication,whichcanbefoundinthe/Applications/Utilitiesfolder.
2. Next,choosetheRequestaCertificateFromaCertificateAuthority...menuoptionfromtheKeychainAccess|CertificateAssistant,asshowninthefollowingscreenshot:
3. Then,weneedtoprovidesomeinformationbeforethecertificatecanbegeneratedundertheCertificateInformationsection.
4. Next,enterintherequiredinformation,asshowninthefollowingscreenshot,whilstensuringthatyouhaveselectedtheSavedtodiskandtheLetmespecifykeypairinformationoptions:
5. Oncealltheinformationhasbeenfilledout,clickontheContinuebutton.Youwillthenbeaskedtospecifyanameforthecertificate.Acceptthedefaultsuggestedname,andclickontheSavebutton.
Note
Atthispointthecertificateisbeingcreatedatthelocationspecified.YouwillthenbeaskedtospecifytheKeySizeandAlgorithmtouse.
6. Next,acceptthedefaultof2048bitsandRSAalgorithm.WeneedtoprovidesomeinformationbeforethecertificatecanbegeneratedundertheCertificateInformationsection,asshowninthefollowingscreenshot:
7. ClickontheContinuebuttonandthenclickontheDonebuttonwhenthefinalscreenappears.
Upuntilnow,youlearnedhowtogenerateacertificaterequestforiOSdevelopment,usingtheCertificateSigningRequest(CSR)usingthepre-installedMacOSXKeychainAccessapplication,sothatwehavetheabilityofcode-signingourapplications,whichwillenableustodeployourapplicationstotheiOSdeviceforbothdevelopmentandtesting.
Inournextstep,wewilllearnhowtorequestadevelopmentcertificatefromApplethatwillprovideuswiththeabilityofcode-signingourapplicationsusingourgeneratedcertificateinformationfilethatwecreatedinthissection.
ObtainingtheiOSdevelopmentcertificatefromAppleInthissection,wewilllearnhowtoobtainthedevelopmentcertificatefromApple,toenableustobegindevelopingapps.
BeforeyoucanbeginsubmittingyourapplicationtotheAppleAppStore,youwillneedtoobtainyourowncopyoftheiOSdevelopmentcertificate.Thiscertificateisbasicallyyouruniqueidentityforeachofyourappsthatyousubmitforapproval,solet'sgetstarted:
1. LogintotheiOSdeveloperportalwebsiteathttp://developer.apple.com/.2. ClickontheMemberCenterlinkthatislocatedrightatthetopofthescreen.3. SignintoyouraccountusingyourAppleIDandpassword.Thiswillthendisplaythe
developerprogramresourcespage,asshowninthefollowingscreenshot:
4. Next,clickontheCertificates,Identifiers&Profilesbutton,ashighlightedintheprecedingscreenshot.
5. Then,clickonthe+button,ashighlightedintheprecedingscreenshot.
6. Next,choosetheiOSAppDevelopmentoptionundertheDevelopmentsection,ashighlightedintheprecedingscreenshot,andclickontheContinuebuttontoproceedtothenextstep,asdisplayedfurtherdownthepage:
7. Then,clickontheContinuebutton,ashighlightedintheprecedingscreenshot,toproceedtothenextstep.
8. Next,clickontheChooseFile...button,ashighlightedintheprecedingscreenshot.9. Then,selecttheCertificateSigningRequest.certSigningRequestfilethatyoucreated
intheprevioussectionsandclickontheContinuebuttontoproceedtothenextstepinthewizard.
10. Afterafewseconds,thepagewillrefreshandthecertificatewillbereadyandyouwillbeabletodownloadit.
Inthissection,weconsideredthestepsinvolvedinrequestingacertificatefromApplethatwillbeusedtoprovideuswiththeabilityofcode-signingourapplicationsrequiredtodeployontotheiOSdeviceandtheAppleAppStore.
Wethenmovedontolearnhowtousethegeneratedcertificaterequestfilethatwecreatedinourprevioussection,CreatingtheTrackMyWalksiOSdevelopmentcertificate,togeneratethedevelopmentcertificate.
CreatingtheAppIDfortheTrackMyWalks(iOS)applicationInprevioussections,wehavelearnedhowtorequestacertificatefromAppletoprovideuswiththeabilityofcode-signingourapplications,aswellaslearninghowtousethegeneratedcertificaterequestfiletogenerateourdeploymentcertificate.
Inthissection,wewillbelookingathowtocreatetheapplicationAppIDssothatwecanusethesetodeployourapplicationstotestonaniOSdevice:
1. LogintotheiOSdeveloperportalwebsiteathttp://developer.apple.com/.2. ClickontheMemberCenterlinkthatislocatedrightatthetopofthescreen.3. SignintoyouraccountusingyourAppleIDandpassword.Thiswillthendisplaythe
developerprogramresourcespage,asshowninthefollowingscreenshot:
4. Next,clickontheCertificates,Identifiers&Profilesbutton,ashighlightedintheprecedingscreenshot.
5. Then,clickontheAppIDsitemlocatedunderneaththeIdentifiersgroupattheleft-handsideofthepageandclickonthe+buttontodisplaytheRegisteriOSAppIDssection,ashighlightedinthefollowingscreenshot:
6. Next,provideadescriptionfortheAppIDDescriptionfieldthatwillbeusedtoidentifyyourapp,asshownintheprecedingscreenshot.
7. Then,provideanamefortheBundleIDfield.Thisneedstobethesameasyourapplication'sbundleidentifier.
Note
TheBundleIDforyourappneedstobeunique.Applerecommendsthatyouusethereversedomainstyle(forexample,com.domainName.appName).
Considerthefollowingscreenshot:
8. Next,choosefromthelistofAppServicesthatyouwouldliketoenableforyourapp,andthenclickontheContinuebutton,asshowninthefollowingscreenshot:
9. Then,fromtheConfirmyourAppIDscreen,clickontheRegisterbutton.
Inthissection,wecoveredthenecessarystepsrequiredtocreatetheAppIDforourapplication.CreationofAppIDsarerequiredforeachapplicationthatyoucreateandmustcontainauniqueapplicationIDthatidentifiesitself.TheAppIDispartoftheprovisioningprofileandidentifiesanApporasuiteofrelatedapplications.
TheseareusedwhenyourapplicationscommunicatewiththeiOShardwareaccessories,theApplePushNotificationService(APNS),andwhensharingofdatahappensbetweeneachofyourapplications.
CreatingtheTrackMyWalksdevelopmentprovisioningprofileInthissection,wewilllearnhowtocreatethedevelopmentprovisioningprofilessothatyourapplicationscanbeinstalledontheiOSdevicesothatyoucandeployandtestyourapplicationspriortodeployingyourapptotheAppleAppStore:
1. LogbackintotheiOSdeveloperportalathttp://developer.apple.com/.2. ClickontheMemberCenterlinkthatislocatedrightatthetopofthescreen.3. SignintoyouraccountusingyourAppleIDandpassword.Thiswillthendisplaythe
developerprogramresourcespage,asshowninthefollowingscreenshot:
4. Next,clickontheCertificates,Identifiers&Profilesbutton,asdonepreviously.5. Then,clickontheAllitemlocatedundertheProvisioningProfilessectionlocatedatthe
left-handsideofthepage.6. Next,clickonthe+buttontodisplaytheAddiOSProvisioningProfilessection,as
highlightedinthefollowingscreenshot:
7. Then,choosetheiOSAppDevelopmentoptionfromtheDevelopmentsection,andthenclickontheContinuebuttontoproceedtothenextstep,asshownintheprecedingscreenshot.
8. Next,selectyourAppIDfromthedrop-downlistavailable,asshownintheprecedingscreenshot,andclickontheContinuebuttontoproceedtothenextstepinthewizard:
9. Then,chooseyourcertificatefromthelistofavailablecertificatesthatyouwouldliketoincludetobepartoftheProvisioningProfiles,andclickontheContinuebuttontoproceedtothenextstep,asshownintheprecedingscreenshot.
10. Next,choosefromthelistofdevicesthatyouwouldliketoincludeaspartoftheProvisioningProfilesthatyouareabouttocreate,andclickontheContinuebuttontoproceedtothenextstep,asshownintheprecedingscreenshot.
Note
FormoreinformationabouthowtoregisteriOSdevicesusingtheMemberCenter,pleaserefertotheAppledistributionguidedocumentationusingthefollowinglink:https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/MaintainingProfiles/MaintainingProfiles.html#//apple_ref/doc/uid/TP40012582-CH30-SW10.
11. Then,specifyanamefortheProfileNamefieldtobeusedtoidentifytheprovisioningprofilewithintheiOSdeveloperportal,andclickontheContinuebuttontoproceedtothenextstep,asshownintheprecedingscreenshot.
12. Finally,yourprovisioningprofilehasbeencreatedandisreadytobeused.YoucanchoosetoDownloadyourprovisioningprofilefromhere,oryoucanletXcodehandlethisforyou,whichwewillbecoveringinthenextsections.
13. Toclosethisscreen,andtakeyoubacktothelistofProvisioningProfiles,clickontheDonebutton,asshownintheprecedingscreenshot.
Inthissection,welearnedhowtocreateaprovisioningprofilethatwillallowyourapplicationstobeinstalledontoarealiOSdevice.Thiswillgiveyoutheabilitytoassignteammemberswhoareauthorizedtoinstallandtestanapplicationontoeachoftheirdevices.
Note
WheneveryoudeployanapplicationontoaniOSdevice,thiswillcontaintheiOSdevelopmentcertificateforeachteammember,aswellastheUniqueDeviceIdentifier(UDID),whichisa
sequenceof40lettersandnumbersthatarespecifictoyourdevice,andtheAppid.
PreparingtheTrackMyWalks(iOS)appforsubmissionNowthatyouhavetestedyourapplicationtoensurethateverythingworksfineandisfreefromerrors,youwillwanttostartpreparingyourapplicationsothatitisreadyforsubmissiontotheAppleAppStore.
Inthissection,wewillneedtouseXcodeandsigninwithourAppleIDsothatwecandownloadourprovisioningprofilesforbothdevelopmentanddistribution.ThisismainlysinceXamarinStudiousesXcodetoperformitscompilation,andifwedon'tsetthisup,wewon'tbeabletosubmitourTrackMyWalksiOSapptotheAppStoreandiTunesConnect.
TobeginpreparingyourapplicationusingXcode,followthesesimplesteps:
1. EnsurethatyouhavelaunchedtheXcodedevelopmentIDEanditisdisplayed.2. Next,choosethePreferences...menuoptionfromtheXcode|Preferences...menu,or
alternativelypresscommand+,asshowninthefollowingscreenshot:
3. Next,ensurethattheAccountsbuttonhasbeenselected,thenclickonthe+button,andchoosetheAddAppleID...menuoption,asshownintheprecedingscreenshot.
4. Then,enterinyourAppleDevelopercredentialsbyspecifyingboththeAppleIDandPassword,ascanbeseenintheprecedingscreenshot.
Note
OnceXcodehasvalidatedyourApplecredentials,youwillbepresentedwithascreenliketheoneshownintheprecedingscreenshot.Thisscreenshowsyoutheteamthatyoubelongto,aswellasyourrolewithintheteam.YoucanalsoaddmultipleAppleIDstothisscreen.
NowthatwehavesetupourXcodedevelopmentIDEtouseouriOSdevelopmentanddistributionprovisioningprofiles,ournextstepistocreateanentryforourapplicationwithiniTunesConnect.
ThisissothatwhenwebegintosubmitourappusingXamarinStudioandtheApplicationLoaderapplicationtotheAppleAppStoreusingiTunesConnect,wewon'trunintoanyissues:
1. LogbackintotheiOSdeveloperportalathttp://developer.apple.com/.2. ClickontheMemberCenterlinkthatislocatedrightatthetopofthescreen.3. SignintoyouraccountusingyourAppleIDandpassword.Thiswillthendisplaythe
developerprogramresourcespage.4. Next,clickontheMyAppsbutton,asshowninthefollowingscreenshot:
5. Then,clickonthe+buttonandthenchoosetheNewAppmenuoption,asshowninthefollowingscreenshot:
6. Next,proceedtoenterintheapplicationdetailsfortheapplicationthatweareuploading.TheSKUnumberfieldisauniqueidentifierthatyoucreateforyourapp:
Note
TheBundleIDsuffixthatyouprovidemustmatchthesameonethatyouusedwithinyourTrackMyWalks.iOSapp'sinfo.plist;otherwise,youwillrunintoissueswhensubmittingyourappstotheAppStoreandiTunesConnect.
7. Then,clickontheCreatebuttontocreateyourappandproceedtothenextstep.8. Next,choosethePricingandAvailabilitymenuoption,locatedunderneaththeAPPSTORE
INFORMATIONsection,ontheleft-handsidepanel.9. Then,fromthePricingandAvailabilitysection,specifythevaluesforourPriceSchedule
aswellastheStartDateandEndDateforourapplication.Thiswilldeterminewhenourapplicationwillbemadeavailablefordownload,asshowninthefollowingscreenshot:
10. Next,clickontheSavebuttontosaveanychangesmadewithinthisscreen.
Therearemorethan100pricingtierstochoosefrom,includinganoptionforsellingyourapplicationforfree.
Inthissection,welearnedthestepsinvolvedinpreparingourapplicationforsubmissiontotheAppleAppStoreusingiTunesConnect.Wealsolearnedthatbeforesubmittingourappsforapproval,youmustensurethateverythingworksproperly,andisfreefromproblems,andtheiOSsimulatorisagoodplacetostart.
Although,noteverythingcanbetestedwithintheiOSsimulator,itprovesagoodstartingpoint.ApplesuggeststhatyoushouldalwaysdeployyourappstoarealiOSdevicerunningthelatestiOSrelease,sothatyoucantestyourappforafewdaystoensurethatallissuesareironedout,priortosubmittingyourapptotheAppleAppStore.Next,welookedathowtocreateanewapplicationIDfortheapplicationthatwillbeuploadedtotheAppleAppStore,aswellasprovidingdetailedinformationabouttheapplication,andspecifyingadatewhentheapplicationwillbecomeavailable.
Note
FormoreinformationonhowtogoaboutsubmittingandmanagingyourappsusingiTunesConnect,youcanrefertothefollowinglinkatthislocation:https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistributionGuide/UsingiTunesConnect/UsingiTunesConnect.html#//apple_ref/doc/uid/TP40012582-CH22-SW3.
SubmittingtheTrackMyWalks(iOS)apptoiTunesConnectusingXamarinStudioIntheprevioussection,webeganbycreatingtheTrackMyWalksAppwithiniTunesConnectandlearnedthatbeforesubmittingappsforapproval,youmustensurethateverythingisworkingproperly,andisfreefromproblems.
Inthissection,wewillbegingettingourTrackMyWalks.iOSappreadyforsubmissiontotheAppleAppStore,usingXamarinStudioIDE.
Tobeginsubmittingyourapplication,followthesesimplesteps:
1. EnsurethattheTrackMyWalks.slnprojectisalreadyopenwithinXamarinStudioIDE.2. Next,right-clickontheTrackMyWalks.iOSproject,choosetheOptionsmenuoption,and
choosetheiOSBundleSigninglocatedundertheBuildsectionwithintheleftpane.3. Then,withintheiOSBundleSigningsection,chooseReleasefortheConfigurationand
iPhoneforthePlatformandensurethatyouhavechosentheDistribution(Automatic)optionwithintheSigningIdentitydropdownthatwillbeusedtosignourTrackMyWalksappwith.
4. Next,ensurethatyouhavechosentheAutomaticoptiontouseforourProvisioningProfile,andclickontheOKbuttontosavethesettingsanddismisstheProjectOptions-TrackMyWalks.iOSdialog.
Note
YouriOSprovisioningcertificatewillbeshowninbold,withyourprovisioningprofileingray.Ifyoudon'timportavalidprovisioningcertificate,youwon'tbeabletodeployoruploadyourTrackMyWalksiOSapplicationtotheAppleAppStore.
5. Then,ensurethatyouhavechosenRelease|iPhonetouseastheiOSdevicepriortochoosingtheArchiveforPublishingoptionwithintheBuildmenu,asshowninthefollowingscreenshot:
6. Next,provideacommentforyourapplication,byclickingwithintheCommentfield,andthenclickontheSignandDistribute...buttontohaveXamarinsignandprepareyourappforsubmission,asshowninthefollowingscreenshot:
Note
OnceyouclickontheSignandDistribute...button,youwillbepresentedwiththeSelect
iOSDistributionChanneldialog,whereyoucanchooseyourdistributionchanneltocreateapackageforyourapp.
7. Then,sincewewanttopublishourTrackMyWalksapptotheAppStore,choosetheAppStoreoptionwithinthelistandclickontheNextbuttontoproceedtothenextstepwithinthewizard,asshowninthefollowingscreenshot:
8. Next,youwillbepresentedwiththeProvisioningprofilescreenwhereyoucanselectyoursigningidentityandprovisioningprofile,orre-signusingadifferentidentity.ClickontheNextbuttontoproceedtothenextstepwithinthewizard,asshowninthefollowingscreenshot.
9. UponclickingontheNextbutton,XamarinStudiowillproceedtocollectallthenecessaryfiles,andcreateaTrackMyWalks.ipafilewhich,bydefault,willbesavedwithinyourTrackMyWalksfolder.
10. YouwillbepresentedwiththePublishtoAppStoredialog,whereyouwillbepresentedwiththeabilityofpublishingyourapptotheappstore,asshowninthefollowingscreenshot:
11. Then,clickonthePublishbuttontoproceedtothenextstepwithinthewizardwhereyoucanthenuploadyourbinaryarchive.
12. OnceyouhaveclickedonthePublishbutton,andeverythingpasses,youwillbepresentedwiththePublishingSucceededdialogwhereyoucanbeginuploadingyourbinaryarchivebyclickingontheOpenApplicationLoaderbutton,ascanbeseenintheprecedingscreenshot.
Note
FormoreinformationonhowtogoaboutdeployingyourXamarin.FormsAndroidapp,pleaserefertothesectiononPreparinganApplicationforReleaseatthefollowinglink:https://developer.xamarin.com/guides/android/deployment,_testing,_and_metrics/publishing_an_application/part_1_-_preparing_an_application_for_release/.
ThiswillthenlaunchtheApplicationLoaderapplication,asshowninthefollowingscreenshot,whereyouwillneedtosign-intoiTunesConnectusingyouriTunesConnectcredentials.
13. Next,choosetheDeliverYourAppoptionandclickontheChoosebutton,asshowninthefollowingscreenshot:
14. OnceyouhaveclickedontheChoosebutton,youwillbepresentedwiththeDeliverYourAppdialogwhereyouwillneedtochoosetheTrackMyWalks.ipafilethatwasgeneratedduringtheSignandDistributeprocesswithintheSubmittingtheTrackMyWalks(iOS)apptoiTunesConnectusingXamarinStudiosectionlocatedwithinthischapter.
15. Then,selectandchoosetheTrackMyWalks.ipafile,andclickontheOpenbutton,asshownintheprecedingscreenshot.
TheApplicationLoaderwillreadtheinformationcontainedwithintheTrackMyWalks.ipabinaryfile,populatedfromiTunesConnect,anddisplaytheApplicationname,VersionNumber,SKUNumber,PrimaryLanguage,Type,andtheuser'sAppleID,asshownintheprecedingscreenshot.
16. Next,clickontheNextbuttontoproceedtothenextstepwithinthewizard,asshownintheprecedingscreenshot.
Intheprecedingscreenshot,theApplicationLoaderapplicationwillbeginbyauthenticatingyourappwiththeAppleAppStoreandiTunesConnect,andthenvalidatingtoensurethateverythingpasses,atwhichpointyourbinaryarchivewillbeginuploading.
Note
ForinformationonhowtousetheApplicationLoadertopublishyourXamarin.iOSapps,youcanrefertoPublishingtotheAppStoreGuidefromtheXamarindeveloperdocumentation,whichcanbeaccessedbyusingthefollowinglink:https://developer.xamarin.com/guides/ios/deployment,_testing,_and_metrics/app_distribution/app-store-distribution/publishing_to_the_app_store/.
SummaryInthischapter,youlearnedhowtocreateandsetupyouriOSdevelopmentteamandtheassociatediOSdevelopmentcertificatethatwillenableyoutorunandtestyourappsonaniOSdevice.WethenmovedontodescribehowtocreateanAppIDforourTrackMyWalksapp.
ThesespecialAppIDsareusedbothwithinXamarinandXcodetoassociateyourappwiththeoneassignedaspartofyouriOSprovisioningprofiles.Oncewecreatedallthenecessarydevelopmentcertificatesandprovisioningprofiles,youlearnedhowtopackage,sign,anddistributeyourappusingXamarinStudioIDE,anddeployittoiTunesConnectusingtheApplicationLoaderapplication,whereyoucanthendownloadandtestyourapponarealiOSdevice.
Thiswasthefinalchapter,andIsincerelyhopethatyouhadlotsoffundevelopingappsthroughoutourjourneyworkingthroughthisbook.YounowhaveenoughknowledgeandexpertisetounderstandwhatittakestobuildrichandengagingappsfortheXamarin.Formsplatform,byusingahostofexcitingconceptsandtechniquesthatareuniquetotheXamarin.Formsplatform.
YouhaveenoughknowledgetogetyourXamarin.Formsprojectsofftoagreatstart,andIcan'twaittoseewhatyoubuild.ThankyousomuchforpurchasingthisbookandIwishyoutheverybestofluckwithyourXamarin.Formsadventures.