125 106 21MB
English Pages [1049] Year 2016
Xamarin: Cross-Platform Mobile Application Development Master the skills required to develop cross-platform applications from drawing board to app store(s) using Xamarin
A course in three modules
BIRMINGHAM - MUMBAI
Xamarin: Cross-Platform Mobile Application Development Copyright © 2016 Packt Publishing
All rights reserved. No part of this course may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews. Every effort has been made in the preparation of this course to ensure the accuracy of the information presented. However, the information contained in this course is sold without warranty, either express or implied. Neither the authors, nor Packt Publishing, and its dealers and distributors will be held liable for any damages caused or alleged to be caused directly or indirectly by this course. Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this course by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Published on: August 2016
Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK. ISBN 978-1-78712-012-9 www.packtpub.com
Credits Authors Jonathan Peppers
Content Development Editor Amedh Pohad
George Taskos Can Bilgin Reviewers Betim S. Drenica Ole Falkerslev Kristensen Frédéric Mauroy Patrick D. Sullivan Stan Okunevic Engin Polat Lance McCarthy Toni Petrina
Graphics Kirk D'Penha Production Coordinator Shantanu N. Zagade
Preface Xamarin has built three core products for developing iOS and Android applications in C#: Xamarin Studio, Xamarin.iOS, and Xamarin.Android. Xamarin gives you direct access to the native APIs on each platform and the flexibility to share C#code between platforms. Using Xamarin and C#, you get better productivity when compared to Java or Objective-C, and still retain great performance compared to an HTML or a JavaScript solution.
What this learning path covers
Module 1, Xamarin Cross-Platform Application Development, this module is a step-by-step guide to building real-world applications for iOS and Android. The module walks you through building a chat application, complete with a backend web service and native features such as GPS location, camera, and push notifications. Additionally, you'll learn how to use external libraries with Xamarin and Xamarin. Forms to create shared user interfaces and make app-store-ready applications. This Module has been updated with new screenshots and detailed steps to provide you with a holistic overview of the new features incorporated in Xamarin 3. By the end of the module, you will have gained expertise to build on the concepts learned and effectively develop a market-ready cross-platform application. Module 2, Xamarin Cross-Platform Development Cookbook, this module provides recipes on how to create an architecture that will be maintainable, extendable, use Xamarin Forms plugins to boost productivity, customize your views per platforms, and use platform-specific implementations at runtime. We start with a simple creation of a Xamarin Forms solution with the three major platforms. We will then jump to XAML recipes and you will learn how to create a tabbed application page, and customize the style and behavior of views for each platform.
[i]
Preface
Moving on, you will acquire more advanced knowledge and techniques while implementing views and pages for each platform and also calling native UI screens such as the native camera page. Further on, we demonstrate the power of architecting a cross-platform solution and how to share code between platforms, create abstractions, and inject platform-specific implementations. Next, you will utilize and access hardware features that vary from platform to platform with crossplatform techniques. Well then show you the power of databinding offered by Xamarin Forms and how you can create bindable models and use them in XAML. You will learn how to handle user interactions with the device and take actions in particular events. With all the work done and your application ready, you will master the steps of getting the app ready and publishing it in the app store. Module 3, Mastering Cross-Platform Development with Xamarin, this module starts with general topics such as memory management, asynchronous programming, local storage, and networking, and later moves onto platform-specific features. During this transition, you will learn about key tools to leverage the patterns described, as well as advanced implementation strategies and features. The module also presents User Interface design and implementation concepts on Android and iOS platforms from a Xamarin and cross-platform perspective, with the goal to create a consistent but native UI experience. Finally, we show you the toolset for application lifecycle management to help you prepare the development pipeline to manage and see crossplatform projects through to public or private release.
What you need for this learning path Module 1:
For this module, you will need a Mac computer running at least OS X 10.7 Lion. Apple requires iOS applications to be compiled on a Mac, so Xamarin does as well. You will also need a license of the business edition of Xamarin.Android and Xamarin.iOS. A free 30-day trial is also available. You can also try the free starter edition of Xamarin, but some of the more advanced examples will not work with this module. You can visit http://xamarin.com/download to download the appropriate software.
Module 2: On Mac:
• Xamarin Studio 5.10.1 (build 6) • The latest iOS SDK (Currently in version 9.2) [ ii ]
Preface
• Xcode 7.1 • OS X 10.10.5+ (Yosemite) or 10.11 (El Capitan) On Windows: • Any non-Express edition of Visual Studio 2012 • Visual Studio 2013 • Visual Studio 2015 (Community, Professional, and Enterprise) • Visual Studio Extensions for iOS and Android All examples will work with the Free Trial evaluation or Xamarin Starter licenses. Xamarin Starter is installed by default with Visual Studio 2015 and works with VS 2012, 2013, and 2015 (including Community editions).
Module 3:
In order to build the sample project and make use of the code samples in this module, you will need a Xamarin.iOS and/or Xamarin.Android subscription, depending on the platform you want to target. Most of the diagnostic tools used are distributed as part of the development SDKs for the target platforms. As a development IDE, you will need Visual Studio 2013 (or higher) or Xamarin Studio if you are using or configuring a Windows based development environment, but only Xamarin Studio otherwise. For testing and diagnostics, real mobile devices or SDK-provided emulators can be used.
Who this learning path is for
This learning path is for developers who are already familiar with C# and want to start doing mobile development with Xamarin. If you have worked in ASP.NET, WPF, WinRT, or Windows Phone, then you will be right at home using this learning path to develop native iOS and Android applications. No previous experience with Xamarin is required. To develop cross-platform apps effectively and efficiently, this learning path is the right choice for you. The path is ideal for those who want to take their novice or intermediate-level Xamarin mobile development skills to the next level to become the go-to person within their organization. To fully understand the patterns and concepts described, you should possess a reasonable level of knowledge and an understanding of the core elements of cross-platform application development with Xamarin.
[ iii ]
Preface
Reader feedback
Feedback from our readers is always welcome. Let us know what you think about this course—what you liked or disliked. Reader feedback is important for us as it helps us develop titles that you will really get the most out of. To send us general feedback, simply e-mail [email protected], and mention the course's title in the subject of your message. If there is a topic that you have expertise in and you are interested in either writing or contributing to a course, see our author guide at www.packtpub.com/authors.
Customer support
Now that you are the proud owner of a Packt course, we have a number of things to help you to get the most from your purchase.
Downloading the example code
You can download the example code files for this course from your account at http://www.packtpub.com. If you purchased this course elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you. You can download the code files by following these steps: 1. Log in or register to our website using your e-mail address and password. 2. Hover the mouse pointer on the SUPPORT tab at the top. 3. Click on Code Downloads & Errata. 4. Enter the name of the course in the Search box. 5. Select the course for which you're looking to download the code files. 6. Choose from the drop-down menu where you purchased this course from. 7. Click on Code Download. You can also download the code files by clicking on the Code Files button on the course's webpage at the Packt Publishing website. This page can be accessed by entering the course's name in the Search box. Please note that you need to be logged in to your Packt account.
[ iv ]
Preface
Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of: • WinRAR / 7-Zip for Windows • Zipeg / iZip / UnRarX for Mac • 7-Zip / PeaZip for Linux The code bundle for the course is also hosted on GitHub at https://github.com/
PacktPublishing/Xamarin-Cross-Platform-Mobile-Application-Development.
We also have other code bundles from our rich catalog of books, courses and videos available at https://github.com/PacktPublishing/. Check them out!
Errata
Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you find a mistake in one of our courses—maybe a mistake in the text or the code—we would be grateful if you could report this to us. By doing so, you can save other readers from frustration and help us improve subsequent versions of this course. If you find any errata, please report them by visiting http://www. packtpub.com/submit-errata, selecting your course, clicking on the Errata Submission Form link, and entering the details of your errata. Once your errata are verified, your submission will be accepted and the errata will be uploaded to our website or added to any list of existing errata under the Errata section of that title. To view the previously submitted errata, go to https://www.packtpub.com/books/ content/support and enter the name of the course in the search field. The required information will appear under the Errata section.
Piracy
Piracy of copyrighted material on the Internet is an ongoing problem across all media. At Packt, we take the protection of our copyright and licenses very seriously. If you come across any illegal copies of our works in any form on the Internet, please provide us with the location address or website name immediately so that we can pursue a remedy. Please contact us at [email protected] with a link to the suspected pirated material. We appreciate your help in protecting our authors and our ability to bring you valuable content.
[v]
Preface
Questions
If you have a problem with any aspect of this course, you can contact us at [email protected], and we will do our best to address the problem.
[ vi ]
Module 1: Xamarin Cross-Platform Application Development Chapter 1: Setting Up Xamarin
3
The Xamarin tools 4 Installing Xcode 6 Installing Xamarin 7 Choosing a Xamarin license 8 Setting up the Android emulator 9 Enrolling in the iOS Developer Program 11 Registering as a Google Play developer 13 Summary 16
Chapter 2: Hello, Platforms!
19
Building your first iOS application 20 Understanding Apple's MVC pattern 23 Using the iOS designer 24 Building your first Android application 32 Android activities 35 Xamarin's Android designer 38 Summary 42
[i]
Table of Contents
Chapter 3: Code Sharing between iOS and Android
43
Chapter 4: XamChat – a Cross-platform App
63
Chapter 5: XamChat for iOS
87
Learning the MVVM design pattern Comparing project organization strategies Working with Portable Class Libraries Using preprocessor statements Simplifying dependency injection Implementing Inversion of Control Summary
43 47 51 52 55 58 60
Starting our sample application concept 63 Developing our Model layer 64 Writing a mock web service 65 Writing the ViewModel layer 71 Writing unit tests 79 Summary 85 Understanding the basics of an iOS app 88 Using UINavigationController 93 Implementing the login screen 95 Using segues and UITableView 100 Adding a friends list screen 105 Adding a list of messages 109 Composing messages 113 Summary 118
Chapter 6: XamChat for Android
119
Chapter 7: Deploying and Testing on Devices
147
Introducing Android Manifest 120 Adding a login screen 125 Using ListView and BaseAdapter 129 Implementing the friends list 134 Composing messages 139 Summary 145 iOS provisioning 147 Android device settings 153 Understanding the linker 155 Understanding AOT compilation 158 Avoiding common memory pitfalls 160 Summary 164
[ ii ]
Table of Contents
Chapter 8: Web Services with Push Notifications
165
Chapter 9: Third-party Libraries
197
Chapter 10: Contacts, Camera, and Location
215
Learning Windows Azure 166 Setting up your Azure account 167 Exploring Azure Mobile Services 169 Creating tables and scripts 171 Adding a backend to XamChat 174 Using the Apple Push Notification service 181 Implementing Google Cloud Messaging 188 Summary 195 The Xamarin Component Store 197 Porting existing C# libraries 199 Objective-C bindings 203 Java bindings 207 Summary 213 Introducing Xamarin.Mobile 215 Accessing contacts 217 Looking up GPS location 224 Accessing the photo library and camera 230 Summary 236
Chapter 11: Xamarin.Forms 237
Creating Hello World in Xamarin.Forms 237 Understanding the architecture behind Xamarin.Forms 241 Using XAML in Xamarin.Forms 242 Using data binding and MVVM 246 Summary 252
Chapter 12: App Store Submission
Following the iOS App Store Review Guidelines Submitting an app to the iOS App Store Signing your Android applications Submitting the app to Google Play Tips to make a successful mobile app Summary
[ iii ]
253
253 258 263 265 268 270
Table of Contents
Module 2: Xamarin Cross-Platform Development Cookbook Chapter 1: One Ring to Rule Them All
273
Chapter 2: Declare Once, Visualize Everywhere
313
Chapter 3: Native Platform-Specific Views and Behavior
353
Chapter 4: Different Cars, Same Engine
389
Chapter 5: Dude, Where's my Data?
439
Chapter 6: One for All and All for One
481
Introduction 273 Creating a cross-platform solution 274 Creating a cross-platform login screen 285 Using common platform features 293 Authenticating with Facebook and Google providers 303 Introduction 313 Creating a tabbed-page cross-platform application 314 Adding UI behaviors and triggers 324 Configuring XAML with platform-specific values 335 Using custom renderers to change the look and feel of views 344 Introduction 353 Showing native pages with renderers 354 Attaching platform-specific gestures 366 Taking an in-app photo with the native camera page 371 Introduction 389 Sharing code between different platforms 390 Using the dependency locator 400 Adding a third-party Dependency Injection Container 406 Architecture design with Model-View-ViewModel (MVVM) pattern 413 Using the event messenger 426 Adding localization 428 Introduction 439 Creating a shared SQLite data access 440 Performing CRUD operations in SQLite 449 Consuming REST web services 459 Leveraging native REST libraries and making efficient network calls 469 Introduction 481 Creating cross-platform plugins 482 Taking or choosing photos 491 Getting the GPS location 497 Show and schedule local notifications 504 [ iv ]
Table of Contents
Chapter 7: Bind to the Data
511
Chapter 8: A List to View
535
Chapter 9: Gestures and Animations
565
Chapter 10: Test Your Applications, You Must
585
Chapter 11: Three, Two, One – Launch and Monitor
627
Introduction 511 Binding data in code 512 Binding data in XAML 514 Configuring two-way data binding 517 Using value converters 525 Introduction 535 Displaying a collection and selecting a row 536 Adding, removing, and refreshing items 541 Customizing the row template 550 Adding grouping and a jump index list 558 Introduction 565 Adding gesture recognizers in XAML 566 Handling gestures with native platform renderers 568 Adding cross-platform animations 578 Introduction 585 Creating unit tests 586 Creating acceptance tests with Xamarin.UITest 592 Using the Xamarin.UITest REPL runtime shell to test the UI 602 Uploading and running tests in Xamarin Test Cloud 614 Introduction 627 Using Xamarin Insights 628 Publishing iOS applications 640 Publishing Android applications 648 Publishing Windows Phone applications 659
Module 3: Mastering Cross-Platform Development with Xamarin Chapter 1: Developing with Xamarin
Cross-platform projects with Xamarin Target platforms Setting up the development environment Emulator options [v]
667
667 669 672 679
Table of Contents
A typical Xamarin solution structure Quality in cross-development Summary
681 689 690
Chapter 2: Memory Management
693
Chapter 3: Asynchronous Programming
719
Chapter 4: Local Data Management
753
Application Component lifecycle 693 Garbage collection 697 Platform-specific concepts 699 Troubleshooting and diagnosis 700 Patterns and best practices 705 Summary 718 Multithreading on Xamarin 719 Asynchronous methods 722 Parallel execution 728 Patterns and best practices 730 Background tasks 748 Summary 752 Data in mobile applications 753 Application data 755 Local filesystem 765 SQLite 767 Patterns and best practices 770 Backup/Roaming 776 Summary 786
Chapter 5: Networking 787
Connected apps 787 Web services 788 SignalR 802 Patterns and best practices 804 Platform-specific concepts 814 Cloud integration 823 Summary 833
Chapter 6: Platform Extras
835
Content sharing 835 Peripherals 842 Location data 845 Native libraries 859 Summary 864 [ vi ]
Table of Contents
Chapter 7: View Elements
865
Chapter 8: Xamarin.Forms
907
Chapter 9: Reusable UI Patterns
949
Chapter 10: ALM – Developers and QA
983
Chapter 11: ALM – Project and Release Management
997
Design philosophy Design elements User interaction Summary
865 871 900 905
Under the hood 907 Components 913 Extending forms 929 Patterns and best practices 939 Summary 947 Visual assets 949 Localization 965 Architectural patterns 972 Summary 982 Development pipeline 983 Troubleshooting and diagnostics 985 Unit testing 987 UI testing 991 Summary 996 Source control Continuous integration Automated testing Beta deployment Live telemetry Summary
997 1003 1006 1008 1011 1014
Chapter 12: ALM – App Stores and Publishing
1015
Bibliography
1027
Release packages Distribution options Line of Business apps Summary
[ vii ]
1015 1023 1025 1026
Module 1
Xamarin Cross-Platform Application Development Develop production-ready applications for iOS and Android using Xamarin
Setting Up Xamarin Xamarin's development tools have given us the power to develop native iOS, Android, and Mac applications in C#, which is one of the most popular programming languages. There are many advantages of choosing Xamarin to develop mobile applications instead of Java and Objective-C. You can share code between both the platforms and can be more productive by taking advantage of the advanced language features of C# and the .NET base class libraries. Alternatively, you would have to write the app twice for Android and iOS and lose the benefits of garbage collection when using Objective-C. In comparison to other techniques of developing cross-platform applications with JavaScript and HTML, Xamarin also has some distinct advantages. C# is generally more performant than JavaScript, and Xamarin gives developers direct access to the native APIs on each platform. This allows Xamarin applications to have a native look and perform in a manner similar to their Java or Objective-C counterparts. Xamarin's tooling works by compiling your C# into a native ARM executable that can be packaged as an iOS or Android application. It bundles a stripped-down version of the Mono runtime with your application that only includes the features of the base class libraries your app uses.
Setting Up Xamarin
In this chapter, we'll set up everything you need to get started on developing with Xamarin. By the end of this chapter, we'll have all the proper SDKs and tools installed and all the developer accounts needed for app store submission. In this chapter, we will cover: • An introduction to Xamarin tools and technology • Installing Xcode, Apple's IDE • Setting up all Xamarin tools and software • Setting up the Android emulator • Enrolling in the iOS Developer Program • Registering for Google Play
The Xamarin tools
Xamarin has developed three core products for developing cross-platform applications: Xamarin Studio (formerly MonoDevelop), Xamarin.iOS (formerly MonoTouch), and Xamarin.Android (formerly Mono for Android). These tools allow developers to leverage the native libraries on iOS and Android and are built on the Mono runtime. Mono, an open source implementation of C# and the .NET framework, was originally developed by Novell to be used on Linux operating systems. Since iOS and Android are similarly based on Linux, Novell was able to develop MonoTouch and Mono for Android as products to target the new mobile platforms. Shortly after their release, another company acquired Novell, and the Mono team left to form a new company. Very shortly after, Xamarin was founded to focus completely on these tools for developing with C# on iOS and Android. Getting a development machine ready for cross-platform application development can take some time. And to make matters worse, Apple and Google both have their own requirements for development on their respective platforms. Let's go over what needs to be installed on your machine.
[4]
Chapter 1
To get started on iOS, we'll need to install the following: • Xcode: This is the core IDE for developing iOS and Mac applications in Objective-C • Xcode Command Line Tools: These are installed inside Xcode, and provide common command-line tools and scripting languages that developers will find useful, such as Subversion, Git, Perl, and Ruby • The Mono runtime for Mac: This is required for compiling and running C# programs on OS X • Xamarin.iOS: This is Xamarin's core product for iOS development Android also requires the following software to be installed to get started: • Java: This is the core runtime for running Java applications on OS X • Android SDK: This contains Google's standard SDK, device drivers, and emulators for native Android development • The Mono runtime for Mac: This is required for compiling and running C# programs on OS X • Xamarin.Android: This is Xamarin's core product for Android development Each of these will take some time to download and install. If you can access a fast Internet connection, it will help speed up the installation and setup process. With everything ready to go, let's move ahead step-by-step, and hopefully, we can skip a few dead-ends you might otherwise run into. It is important to note that Xamarin can also be used on Windows and Visual Studio, even though it is not covered in this module. A Mac is required for iOS development, so Windows developers must connect Visual Studio to a Mac to compile for iOS. Luckily, most of what we learn in this module can be directly applied to using Xamarin on Windows.
[5]
Setting Up Xamarin
Installing Xcode
To make things progress more smoothly, let's start off by installing Xcode for Mac. Along with Apple's IDE, it will also install the most commonly used developer tools on the Mac. Make sure you have at least OS X 10.8 (Mountain Lion), and locate Xcode in the App Store, as shown in the following screenshot:
This will take quite some time to download and install. I'd recommend that you take the time to enjoy a nice cup of coffee or work on another project to pass the time. When that is out of the way, launch Xcode for the first time and progress through the initial startup dialog. Next, navigate to Xcode | Preferences… to open Xcode's main settings dialog. In the Downloads tab, you'll notice several additional packages you can install inside Xcode. Here, you can download the official iOS documentation, which the Xamarin installer will make use of. Optionally, you can install older iOS simulators, but we can just use the default one for the content in this module. When you're finished, your Xcode's Components section should look something similar to the following screenshot:
[6]
Chapter 1
Installing Xcode installs the iOS SDK, which is a requirement for iOS development in general. As a restriction from Apple, the iOS SDK can only run on a Mac. Xamarin has done everything possible to make sure they follow Apple's guidelines for iOS, such as restricting dynamic code generation. Xamarin's tools also leverage features of Xcode wherever possible to avoid reinventing the wheel.
Installing Xamarin
After installing Xcode, there are several other dependencies that need to be installed in order prior to developing with Xamarin's tools. Luckily, Xamarin has improved the experience by creating a neat all-in-one installer. Install the free Xamarin Starter Edition by performing the following steps: 1. Go to http://Xamarin.com and click on the large Download now button. 2. Fill out some basic information about yourself. 3. Download the XamarinInstaller.dmg file and mount the disk image. 4. Launch Install Xamarin.app and accept any OS X security warnings that appear. [7]
Setting Up Xamarin
5. Progress through the installer; the default options will work fine. You can optionally install Xamarin.Mac, but this topic is not covered in this module. The Xamarin installer will download and install prerequisites such as the Mono runtime, Java, the Android SDK (including the Android emulator and tools), and everything else you need to get up and running. You will end up with something similar to what is shown in the following screenshot, and we can move on to conquer bigger topics in cross-platform development:
Choosing a Xamarin license
Xamarin's tools can seem a bit pricy to the casual observer, but I tend to think of it as how much time you will save using a more productive language such as C#. Additionally, their products will save you a good percentage of development time by enabling you to develop a cross-platform application instead of writing it twice in Java and Objective-C. Xamarin has several editions, so it is good to know the differences in order to determine which license you might need to purchase. The editions are as follows: • Starter Edition: This is available to individuals only, and it has a limit of 64 KB of compiled user code. Certain features are unavailable such as the Xamarin.Forms framework and calling into third-party native libraries. [8]
Chapter 1
• Indie Edition: This is available to individuals only, and it does not include Visual Studio support. • Business Edition: This is available for companies; it adds features for Visual Studio and includes better Xamarin product support. • Enterprise Edition: This includes prime components in the Xamarin Component Store for free and many more Xamarin support options such as hotfixes and less than 24 hours response time to issues.
Setting up the Android emulator
The Android emulator has historically been known to be sluggish compared to developing on a physical device. To help solve this issue, Google has produced a new x86 emulator that supports hardware acceleration on desktop computers. It isn't installed by default in the Android Virtual Device (AVD) Manager, so let's set that up. The x86 Android emulator can be installed by performing the following steps: 1. Open Xamarin Studio. 2. Navigate to Tools | Open Android SDK Manager…. 3. Scroll down to Extras; install Intel x86 Emulator Accelerator (HAXM). This will download an installer that we have to run. 4. Open Finder and press Command + Shift + G to open the navigation popup. 5. Navigate to ~/Library/Developer/Xamarin/android-sdk-macosx/ extras/intel and install the appropriate package (based on your Mac OS X version). 6. Scroll to Android 4.4.2 (API 19); install Intel x86 Atom System Image. 7. Optionally, install any other packages you are interested in. As a shortcut, the Android SDK Manager automatically selects certain packages for you to install by default. 8. Close the Android SDK Manager and switch back to Xamarin Studio. 9. Navigate to Tools | Open Android Emulator Manager…. 10. Click on Create…. 11. Enter an AVD name of your choice, such as x86 Emulator. 12. Pick a generic device that will be appropriately sized for your display, such as one with a 4" WVGA display.
[9]
Setting Up Xamarin
13. As Target, make sure that you select Intel x86 Atom System Image. 14. After creating the device, go ahead and click on Start… to make sure the emulator runs properly. The emulator will take some time to start up, so it is a good idea to leave the emulator running while performing Android development. Xamarin is using the standard Android tools here, so you would have the same issue while developing with Java. If everything starts properly, you will see an Android boot screen followed by a virtual Android device ready for deploying applications from Xamarin Studio, as shown in the following screenshot:
[ 10 ]
Chapter 1
Enrolling in the iOS Developer Program
To deploy to an iOS device, Apple requires membership to its iOS Developer Program. Membership is $99 USD per year and gives you access to deploy 200 devices for development purposes. You also get access to test servers for implementing more advanced iOS features such as in-app purchases, push notifications, and iOS Game Center. Testing your Xamarin.iOS applications on a physical device is important, so I recommend that you get an account prior to starting iOS development. Performance is very different in a simulator running on your desktop versus a real mobile device. There are also a few Xamarin-specific optimizations that only occur when running on a real device. We'll fully cover the reasons for testing your apps on devices in the later chapters. Signing up for the iOS Developer Program can be performed through the following steps: 1. Go to https://developer.apple.com/programs/ios. 2. Click on Enroll Now. 3. Sign in with an existing iTunes account or create a new one. This can't be changed later, so choose one that is appropriate for your company. 4. Enroll either as an individual or a company. Both are priced at $99; but, registering as a company will require paperwork to be faxed to Apple with the assistance of your company's accountant. 5. Review the developer agreement. 6. Fill out Apple's survey for developers. 7. Purchase the $99 developer registration. 8. Wait for a confirmation e-mail.
[ 11 ]
Setting Up Xamarin
You should receive an e-mail that looks something similar to the following screenshot within two business days:
From here, we can continue setting up your account: 1. Either click on Log in now from the e-mail you received or go to https://itunesconnect.apple.com. 2. Log in with your earlier iTunes account. 3. Agree to any additional agreements that appear on the home page of your dashboard. 4. From the iTunes Connect dashboard, navigate to Agreements, Tax, and Banking. 5. In this section, you will see three columns for Contact Info, Bank Info, and Tax Info. 6. Fill out the appropriate information for your account in all of these sections. Assistance from an accountant will most likely be needed for a company account.
[ 12 ]
Chapter 1
When all is said and done, your Contracts, Tax, and Banking section should look something similar to the following screenshot:
With your iOS developer account successfully registered, you will now be able to deploy to iOS devices and publish your apps to the Apple App Store.
Registering as a Google Play developer
Unlike iOS, deploying your applications to Android devices is free and just requires a few changes in your device settings. A Google Play developer account has only a one-time fee of $25 and doesn't have to be renewed each year. However, just like iOS, you will need a Google Play account to develop in-app purchases, push notifications, or Google Play game services. I would recommend that you set up an account ahead of time if you inevitably plan on submitting an app to Google Play or need to implement one of these features. To register as a developer for Google Play, perform the following steps: 1. Go to https://play.google.com/apps/publish. 2. Log in with an existing Google Account or create a new one. This can't be changed later, so choose one that is appropriate for your company if needed.
[ 13 ]
Setting Up Xamarin
3. Accept the agreement and enter your credit card information. 4. Choose a developer name and enter other important information for your account. Again, choose names appropriate for your company to be seen by users in the app store. If everything is filled out correctly, you will end up with the following Google Play Developer Console:
If you plan on selling paid apps or in-app purchases, at this point, I would recommend that you set up your Google merchant account. This will enable Google to pay you the proceeds toward your app sales by applying the appropriate tax laws in your country. If you are setting this up for your company, I would recommend that you get the assistance of your company's accountant or bookkeeper.
[ 14 ]
Chapter 1
The following are the steps to set up a Google merchant account: 1. Click on the set up a merchant account button. 2. Log in with your Google account a second time. 3. Fill out the appropriate information for selling apps: address, phone number, tax information, and a display name to appear on your customers' credit card bill. When done, you will see that the help tip for setting up a merchant account is now missing from the developer console, as shown in the following screenshot:
[ 15 ]
Setting Up Xamarin
At this point, one would think that our account would be fully set up, but there is one more crucial step prior to being able to sell apps: we have to enter the banking information. Setting up banking for your Google merchant account can be performed with the following steps: 1. Go back to the Google Play Developer Console at https://play.google. com/apps/publish. 2. Click on the Financial Reports section. 3. Click on the small link titled Visit your merchant account for details. 4. You should see a warning indicating that you do not have a bank account set up. Click on the Specify a Bank Account link to get started. 5. Enter your banking information. Again, a company accountant might be needed. 6. In a few days, look for a small deposit in your account from Google. 7. Confirm the amount by going to http://checkout.google.com/sell. 8. Click on the Settings tab, then Financials. 9. Next, click on Verify Account. 10. Enter the amount that appeared on your bank account and click on Verify deposit. Your Google merchant account is also the place where you can cancel or refund customer orders. Google Play is different from the iOS App Store in that all customer issues are directed to the developers.
Summary
In this chapter, we discussed Xamarin's core products for developing Android and iOS applications in C#: Xamarin Studio, Xamarin.iOS, and Xamarin.Android. We installed Xcode and then ran the Xamarin all-in-one installer, which installs Java, the Android SDK, Xamarin Studio, Xamarin.iOS, and Xamarin.Android. We set up the x86 Android emulator for a faster, more fluid experience when debugging applications. Finally, we set up iOS and Google Play developer accounts for distributing our applications. In this chapter, you should have acquired everything you need to get started on building cross-platform applications with Xamarin. Your development computer should be ready to go and you should have all the native SDKs installed and ready for creating the next great app to take the world by storm. [ 16 ]
Chapter 1
The concepts in this chapter will set us up for more advanced topics that will require the proper software installed as well as developer accounts with Apple and Google. We will be deploying applications to real devices and implementing more advanced features such as push notifications. In the next chapter, we'll create our first iOS and Android application and cover the basics of each platform.
[ 17 ]
Hello, Platforms! If you are familiar with developing applications using Visual Studio on Windows, then using Xamarin Studio should be very straightforward. Xamarin uses the same concept of a solution containing one or more projects, and it has created several new project types for iOS and Android applications. There are also several project templates to jump-start your development of common applications. Xamarin Studio supports several out-of-the-box project types, including standard .NET class libraries and console applications. You cannot natively develop Windows applications on a Mac with Xamarin Studio, but you can certainly develop the shared code portion of your application in Xamarin Studio. We'll focus on sharing code in the later chapters, but keep in mind that Xamarin enables you to share a common C# backend between most platforms that support C#. In this chapter, we will cover: • Creating a "Hello World" application for iOS • Apple's MVC pattern • Xcode and storyboards • Creating a "Hello World" application for Android • Android activities • Xamarin's Android designer
Hello, Platforms!
Building your first iOS application
Launch Xamarin Studio and start a new solution. Just like in Visual Studio, there are lots of project types that can be created from the New Solution dialog. Xamarin Studio, formerly MonoDevelop, supports the development of many different types of projects such as C# console applications targeting the Mono runtime, NUnit test projects, and even other languages besides C#, such as VB or C++. Xamarin Studio supports the following project types for iOS: • iPhone or iPad project: These categories of projects use storyboards to lay out the UI and target either the iPad or iPhone only. • Universal project: This category supports both iPhone and iPad in the same iOS application. This is the preferred project type if you need to target both types of devices. • Single View Application: This is the basic project type that sets up an iOS storyboard along with a single view and controller. • Tabbed Application: This is a project type that automatically sets up UITabViewController for applications with a tab layout. • WebView Application: This project type is for creating hybrid applications that are partially HTML and partially native. The application is set up to take advantage of the Razor templating features of Xamarin Studio. • iOS binding project: This is an iOS project that can create C# bindings for an Objective-C library. • iOS unit test project: This is a special iOS application project that can run NUnit tests. • iOS library project: This is a class library used within other iOS application projects. To get started, navigate to iOS | iPhone, and create Single View Application in the directory of your choice, as shown in the following screenshot:
[ 20 ]
Chapter 2
You'll notice that several files and folders are automatically created from the project template. These files are as follows: • Components: This folder will contain any components added from the Xamarin Component Store. See Chapter 9, Third-party Libraries, for more information about the Xamarin Component Store. • Resources: This directory will contain any images or plain files that you want to be copied directly to your application bundle. Note that this will contain a black splash screen image, by default. This ensures that your iOS application runs full screen on the iPhone 5. • AppDelegate.cs: This is Apple's main class that handles application-level events in your app. • Entitlements.plist: This is a settings file Apple uses to declare permissions for certain iOS features such as push notifications and iCloud. You will generally only have to use it for advanced iOS features.
[ 21 ]
Hello, Platforms!
• *ViewController.cs: This is the controller that represents the first screen in your app. It will have the same name as your project. • Info.plist: This is Apple's version of a manifest file that can declare various settings for your application such as the app title, icon, splash screens, and other common settings. • Main.cs: This file contains the standard entry point for a C# program: static void Main(). It's most likely that you will not need to modify this file. • MainStoryboard.storyboard: This is the storyboard definition file for your application. It will contain the layouts for the views in your app, list of controllers, and the transitions used to navigate throughout your app. Now, let's run the application to see what we get by default from the project template. Click on the large play button in the top-left corner of Xamarin Studio. You will be greeted by the simulator running your first iOS application, as shown in the following screenshot:
[ 22 ]
Chapter 2
So far, your app is just a plain white screen, which is not very exciting or useful. Let's get a little more background on iOS development before moving forward. Depending on your application's minimum iOS target, you can also run the application on different versions of the iOS simulator. Apple also provides simulators for iPad and all the different iOS devices currently in the market. It is also important to know that these are simulators and not emulators. An emulator will run an encapsulated version of the mobile OS (just as Android does). Emulators generally exhibit slower performance but give you a closer replica of the real OS. Apple's simulators run in native Mac applications and are not true operating systems. The benefit is that they are very fast in comparison to Android emulators.
Understanding Apple's MVC pattern
Before getting too far with iOS development, it is really important to get a foundation with Apple's design pattern to develop on iOS. You might have used the Model View Controller (MVC) pattern with other technologies such as ASP.NET, but Apple implements this paradigm in a slightly different way. The MVC design pattern includes the following: • Model: This is the backend business logic that drives the application. This can be any code that, for example, makes web requests to a server or saves data to a local SQLite database. • View: This is the actual user interface seen on the screen. In iOS terms, this is any class that derives from UIView. Examples are toolbars, buttons, and anything else the user would see on the screen and interact with. • Controller: This is the workhorse of the MVC pattern. The controller interacts with the Model layer and updates the View layer with the results. Similar to the View layer, any controller class will derive from UIViewController. This is where a good portion of the code in iOS applications resides. The following figure shows you the MVC design pattern:
[ 23 ]
Hello, Platforms!
To understand this pattern better, let's walk you through the following example of a common scenario: 1. We have an iOS application with a search box that needs to query a website for a list of jobs. 2. The user will enter some text into the UITextField textbox and click on the UIButton button to start the search. This is the View layer. 3. Some code will respond to the button by interacting with the view, display a UIActivityIndicatorView spinner, and call a method in another class to perform the search. This is the Controller layer. 4. A web request will be made in the called class and a list of jobs will be returned asynchronously. This is the Model layer. 5. The controller will then update the view with the list of jobs and hide the spinner. For more information on Apple's MVC pattern, see the documentation site at https://developer.apple.com/ library/mac/documentation/general/conceptual/ devpedia-cocoacore/MVC.html.
A point to note is that you are free to do anything you want in the Model layer of your application. This is where we can use plain C# classes that can be reused on other platforms such as Android. This includes any functionality using the C# Base Class Libraries (BCL), such as working with web services or a database. We'll dive deeper into cross-platform architecture and code-sharing concepts later in the module.
Using the iOS designer
Since our plain white application is quite boring, let's modify the View layer of our application with some controls. To do this, we will modify the MainStoryboard. storyboard file in your project in Xamarin Studio. Optionally, you can open the storyboard file in Xcode, which was previously the method of editing storyboard files before the designer was available in Xamarin Studio. Using Xcode can still be useful if there is a feature in iOS storyboards that isn't available yet in the Xamarin designer or if you need to edit an older iOS format such as XIB files. However, Xcode is not quite as good of an experience, since custom controls in Xcode render as a plain white square. Xamarin's designer actually runs your drawing code in custom controls, so that you get an accurate view of what your application will look like at runtime.
[ 24 ]
Chapter 2
Let's add some controls to our app by performing the following steps: 1. Open the project you created earlier in this chapter in Xamarin Studio. 2. Double-click on the MainStoryboard.storyboard file. 3. The iOS designer will open, and you will see the layout for the single controller in your application. 4. In the Document Outline tab on the right-hand side, you'll see that your controller contains a single view in its layout hierarchy.
[ 25 ]
Hello, Platforms!
5. In the top-left corner, you'll notice a toolbox that contains several types of objects that you can drag and drop onto your controller's view. 6. In the search box, search for UILabel and drag the label onto your view at a location of your choice. 7. Double-click on the label to edit the text of the label to anything you wish. You can also fill out this value from the Properties tab in the bottom-right corner. 8. Likewise, search for UIButton and drag the button onto your view somewhere above or below the label. You can edit the text on the button using the Properties tab. Double-clicking on the button will add a click event handler as you might be familiar in Visual Studio when developing for other platforms. 9. Run the application. Your application should start looking a lot more like a real application, as shown in the following screenshot:
[ 26 ]
Chapter 2
Now you might be wondering about adding user interaction options to the app at this point. In Xcode's iOS designer, you can make an outlet that will make each view visible from C#. An outlet is a reference to a view in a storyboard or XIB file that will be filled out with an instance of the view at runtime. You can compare this concept to naming a control in other technologies such as ASP.NET MVC, WebForms, or Windows Presentation Foundation (WPF). Luckily, Xamarin's iOS designer is a bit simpler than setting up an outlet in Xcode. You merely fill out the Name field in the Properties tab, and Xamarin Studio will generate a property in partial class, which gives you access to the label and button from your controller. Additionally, you can wire an action from a storyboard file, which is a method that will be called when an event occurs. Xamarin Studio exposes iOS actions as partial methods to be implemented in your classes. Let's add some interactions to the app as follows: 1. Switch back to Xamarin Studio. 2. Double-click on the MainStoryboard.storyboard file again. 3. Select the label you created earlier and go to the Properties pane and make sure that you have the Widget tab selected. 4. Enter the name label in the Name field. 5. Repeat this process for the button, and enter the name button into its Name field. Xamarin has improved this experience greatly from what the experience used to be in Xcode. Xcode has a strange interface for those used to Visual Studio. The method used to create an outlet involved clicking and dragging from the control onto an Objective-C header file. Merely filling out a Name field is much simpler and much more intuitive for developers that have a C# background. Now that we have two outlets defined, two new properties will be available from your controller. Expand the *ViewController.cs file in your solution and open the *ViewController.designer.cs file. You will see your properties defined as follows: [Outlet] [GeneratedCode ("iOS Designer", "1.0")] MonoTouch.UIKit.UILabel label { get; set; } [Outlet] [GeneratedCode ("iOS Designer", "1.0")] MonoTouch.UIKit.UIButton button { get; set; }
It is not a good idea to modify this file since Xamarin Studio can rebuild it if you make further changes in the designer or Xcode. Nevertheless, it is a good practice to learn how things are actually working behind the scenes. [ 27 ]
Hello, Platforms!
Open your *ViewController.cs file, and let's enter the following code in your controller's ViewDidLoad method: public override void ViewDidLoad() { base.ViewDidLoad(); int count = 0; button.TouchUpInside += (sender, e) => label.Text = string.Format("Count: {0}", ++count); }
When the ViewDidLoad method is called, your controller's view is loaded for the first time. This happens once in the lifetime of your controller. We subscribed to the TouchUpInside event, which is fired when the button is clicked; iOS does not have a click event, which might be what you are used to on Windows platforms. We also used C#'s convenient lambda expression syntax to update the label when the event is fired. A lambda expression is shorthand for an anonymous method, which is a feature that has been part of C# since .NET 4.0. Run your application, and you will be able to interact with your button and increment the value displayed in the label, as shown in the following screenshot:
[ 28 ]
Chapter 2
Next, we need to make a transition from one controller to another. To do this, iOS has a concept called segue, which is basically some kind of animation that switches from one controller to the next. There are several types of segues, but the most common segue slides transition to a new controller from the right or bottom of the screen. Now, let's add a second controller to the application as follows: 1. Return to your project in Xamarin Studio. 2. Double-click on the MainStoryboard.storyboard file. 3. Drag a new controller from the object library that is usually in the bottom-left corner next to the first controller. 4. Click on the controller to select it. 5. Select the Properties pane and make sure you are on the Widget tab. 6. Enter a name such as SecondController for the controller into the Class field. 7. Now let's add a segue for the transition from the first controller to this one. Hold the Ctrl key while clicking on the button from the original controller to your new controller. A blue line will appear followed by a small pop-up menu. 8. Select modal from the pop-up menu. 9. Run the application from Xamarin Studio. Since we set up a modal segue from the first controller's button, your second controller will appear while clicking on it. However, there isn't a way to exit the new controller yet. If you return to Xamarin Studio, you'll notice that a SecondController.cs file and a SecondController.designer.cs file have been automatically created for you. Let's add a button to SecondController as follows: 1. Return to Xamarin Studio. 2. Double-click on the MainStoryboard.storyboard file. 3. Drag a button from the object library onto the second controller. 4. Navigate to the Properties pane and Widget tab. 5. Set the Name of the button to close. 6. Set the Title of the button to Close. Open the SecondController.cs file and add the following method: public override void ViewDidLoad() { base.ViewDidLoad(); close.TouchUpInside += (sender, e) => DismissViewController(true, null); } [ 29 ]
Hello, Platforms!
If you compile and run your application, clicking on the button will increment the value on the label and display the modal second controller. You can then close the second controller by tapping on the Close button. Notice the neat sliding animation; iOS automatically applies these kinds of transition effects and are very easy to customize on iOS:
Since we have gone over the basics of laying out controls in Xamarin's iOS designer and interacting with outlets in C#, let's go over the standard lifecycle of an iOS application. The primary location for handling application-level events is in the AppDelegate class. If you open your AppDelegate.cs file, you can override the following methods: • FinishedLaunching: This is the first entry point for the application, which should return true. • DidEnterBackground: This means that the user clicked on the home button on their device or another app, such as a phone call, came to the foreground. You should perform any action needed to save the user's progress or state of the UI as the iOS might close your application to save memory once pushed to the background. While your application is in the background, the user could be navigating through the home screen or opening other apps. Your application is effectively paused in memory until resumed by the user. [ 30 ]
Chapter 2
• WillEnterForeground: This means that the user has reopened your application from the background. You might need to perform other actions here such as refreshing the data on the screen and so on. • OnResignActivation: This happens if the operating system displays a system popup on top of your application. Examples of this are calendar reminders or the menu the user can swipe down from the top of the screen. • OnActivated: This happens immediately after the OnResignActivation method is executed as the user returns to your app. • ReceiveMemoryWarning: This is a warning from the operating system to free up the memory in your application. It is not commonly needed with Xamarin because of the C#'s garbage collector, but if there are any heavy objects such as images throughout your app, this is a good place to dispose them. If enough memory cannot be freed, the operating system can terminate your application. • HandleOpenUrl: This is called if you implement a URL scheme, which is the iOS equivalent of file extension associations on a desktop platform. If you register your app to open different types of files or URLs, this method will be called. Likewise, in your *ViewController.cs file, you can override the following methods on your controller: • ViewDidLoad: This occurs when the view associated with your controller is loaded. It will occur only once on devices running iOS 6 or higher. • ViewWillAppear: This occurs prior to your view appearing on the screen. If there are any views that need to be refreshed while navigating throughout your app, this is generally the best place to do it. • ViewDidAppear: This occurs after the completion of any transition animations and your view is displayed on the screen. In some uncommon situations, you might need to perform actions here instead of in ViewWillAppear. • ViewWillDisappear: This method is called prior to your view being hidden. You might need to perform some cleanup operations here. • ViewDidDisappear: This occurs after any transition animations are completed for displaying a different controller on the screen. Just like the methods for appearing, this occurs after ViewWillDisappear.
[ 31 ]
Hello, Platforms!
There are several more methods available to override, but many are deprecated for recent versions of iOS. Familiarize yourself with Apple's documentation site at http://developer.apple.com/library/ios. It is very helpful to read the documentation on each class and method when trying to understand how Apple's APIs work. Learning how to read (not necessarily code) Objective-C is also a useful skill to learn so that you are able to convert Objective-C examples to C# when developing iOS applications.
Building your first Android application
Setting up an Android application in Xamarin Studio is just as easy as it is for iOS and is very similar to the experiences in Visual Studio. Xamarin Studio includes several project templates that are specific for Android to jump-start your development. Xamarin Studio includes the following project templates: • Android application: A standard Android application that targets the newest Android SDKs installed on your machine. • Android Honeycomb application: A project that targets Android Honeycomb, which is API (Application Programming Interface) level 12 and higher. • Android Ice Cream Sandwich application: A project that targets Android Ice Cream Sandwich, which is API level 15 and above. • Android library project: A class library that can only be referenced by Android application projects. • Android Java bindings library: A project for setting up a Java library to be called from C#. • Android OpenGL application: A project template to use low-level OpenGL for 3D or 2D rendering. • Android WebView application: A project template for a hybrid app using HTML for certain parts. Support for Razor templating is available. • Android unit test project: A project for running NUnit tests on Android. Launch Xamarin Studio and start a new solution. From the New Solution dialog, create a new Android Application under the Android section.
[ 32 ]
Chapter 2
You will end up with a solution looking something similar to what is shown in the following screenshot:
You'll see that the following files and folders specific to Android have been created for you: • The Components folder: This is the same as for iOS projects; the place where components from the Xamarin Component Store can be added. • The Assets folder: This directory will contain files with a build action of AndroidAsset. This folder will contain raw files to be bundled with an Android application. • The Properties/AndroidManifest.xml file: This file contains standard declarations about your Android applications, such as the application name, ID, and permissions.
[ 33 ]
Hello, Platforms!
• The Resources folder: Resources are images, layouts, strings, and so on that can be loaded via Android's resource system. Each file will have an ID generated in Resources.designer.cs that you can use to load the resource. • The Resources/drawable folder: Any images used by your application are generally placed here. • The Resources/layout folder: This contains any *.axml (Android XML) files that Android uses to declare UIs. Layouts can be used for an entire activity, fragment, dialog, or child control to be displayed on the screen. • The Resources/values folder: This contains XML files to declare key-value pairs for strings (and other types) throughout an application. This is how localization for multiple languages is normally set up on Android. • The MainActivity.cs file: This is the MainLauncher action and the first activity of your Android application. There is no static void Main function in Android apps; execution begins on the activity that has MainLauncher set to true. Now let's perform the following steps to run the application: 1. Click on the play button to compile and run the application. 2. A Select Device dialog will appear. 3. Select the emulator of your choice and click on Start Emulator. If you have set up the x86 emulator in Chapter 1, Setting Up Xamarin, I would recommend that you use it. 4. Wait a few seconds for the emulator to start. Once it starts, it is a good idea to leave it running as long as you are working on an Android project. This will save you a good deal of time waiting. 5. You should see the emulator now enabled in the list of devices; select it, and click on OK. 6. The very first time you deploy to an emulator or device, Xamarin Studio will have to install a few things such as the Mono shared runtime and Android platform tools. 7. Switch over to the Android emulator and your application will appear.
[ 34 ]
Chapter 2
When all is done, you have deployed your first Android application, complete with a single button. Your app will look like what is shown in the following screenshot:
Android activities
The Android operating system is very focused on the concept of an activity. An activity is a task or unit of work that users can perform on their screen. For example, users would perform a phone activity to dial a number and carry out a second activity that involves interacting with their address book to locate the number. Each Android application is a collection of one or more activities that users can launch and press the hardware's back key on their device to exit or cancel. The user's history is kept in the Android back stack, which you can manipulate from code in special cases. When a new activity starts, the previous one is paused and maintained in memory for later use, unless the operating system is running low on memory.
[ 35 ]
Hello, Platforms!
Activities are loosely coupled with each other; in some ways, you can think of them as having completely separate states from one another in memory. Static values will persist the life of the application as in .NET applications, but the common practice is to pass a state through an Android bundle. An Android bundle is a set of key-value pairs used to pass data from one Android object to another. This is useful to pass an identifier for an item displayed in a list to edit that item in a new activity. Activities have the following lifecycle callback methods that you can override: • OnCreate: This is the first method called when your activity is created. Set up your views and perform other loading logic here. Most importantly, you will call SetContentView here to set up your activity's view. • OnResume: This is called when your activity's view is visible on the screen. It is called if your activity is displayed for the first time, and when the user returns to it from another activity. • OnPause: This is called to notify that the user has left your activity. It can happen prior to navigating to a new activity within your app, locking the screen, or hitting the home button. Assume that the user might not return, so you need to save any changes the user made here. • OnStart: This occurs immediately before OnResume when the activity's view is about to be displayed on the screen. It occurs when an activity starts and when a user returns to it from another activity. • OnStop: This occurs immediately after OnPause when the activity's view is no longer displayed on the screen. • OnRestart: This method occurs when the user returns to your activity from a previous activity. • OnActivityResult: This method is used to communicate with other activities in other applications on Android. It is used in conjunction with StartActvityForResult; for example, you will use this to interact with the Facebook application to log in a user. • OnDestroy: This is called when your activity is about to be freed from memory. Perform any additional cleanup that could help the operating system here, such as disposing of any other heavyweight objects the activity was using. A flowchart of the Android lifecycle is as follows:
[ 36 ]
Chapter 2
Unlike iOS, Android does not enforce any design patterns upon its developers. However, it is not possible to get away without understanding the Android activity lifecycle to some degree. Many concepts with activities are parallel to controllers on iOS; for example, OnStart is equivalent to ViewWillAppear and OnResume is equivalent to ViewDidAppear.
[ 37 ]
Hello, Platforms!
Other methods for working with activities are as follows: • StartActivity(Type type): This method starts a new activity within your application and passes no extra information to the activity. • StartActivity(Intent intent): This is an overload method to start a new activity with Intent. This gives you the ability to pass additional information to the new activity, and you can also launch activities in other applications. • StartActivityForResult: This method starts a new activity with the anticipation of receiving OnActivityResult when the activity's operation is completed. • Finish: This will close the current activity and invoke OnDestroy when it is completely closed and no longer displayed on the screen. Depending on what is currently on the back stack, the user will return to a previous activity or the home screen. • SetContentView: This method sets the primary view to be displayed for an activity. It should be called within the OnCreate method prior to the activity being displayed on the screen. • FindViewById: This is a method to locate the view displayed in your activity. It has a generic version to return a view of the appropriate type. You can think of intent as an object that describes the transition from one activity to another. You can pass additional data through intents as well as modify how the activity is displayed and the user's navigation history. In addition to activities, Android has the concept of a fragment. You can think of a fragment to be a miniature activity that is displayed inside a parent activity. Fragments are useful for reusing different pieces of a UI throughout your apps and can also help you implement split screen navigation on tablets.
Xamarin's Android designer
The default template for Android projects has a little more built-in functionality than iOS. Android user interface layouts are defined in XML files that are readable by humans and editable. However, Xamarin Studio has provided an excellent design tool that allows you to drag and drop controls to define your Android layouts. Let's add some more features to your application and start using the Android designer.
[ 38 ]
Chapter 2
Return to Xamarin Studio and carry out the following steps to add features to your app: 1. Open the Android project you created earlier in this chapter in Xamarin Studio. 2. Navigate to Resources | layout in your project and open Main.axml. 3. You will see the Android designer open in Xamarin Studio. 4. Drag TextView from the Toolbox section on the right to the layout just above the button labeled Hello World, Click Me! 5. Type some default text such as Count: 0 into the label. 6. In the Properties pane on the right, you'll see the id value is set to @+id/ textView1. Let's change it to @+id/myText, in order to be consistent with the button. 7. While we're here, go ahead and change the text on the button to something more appropriate such as Add. 8. Click on the play button to compile and run the application. If you still have the Android emulator, you can simply switch to it. Otherwise, you will have to start it again. Your Android application will now look identical to the changes you made in the designer as follows:
[ 39 ]
Hello, Platforms!
Now, let's interact with the new label from the code. Switch back to Xamarin Studio and open MainActivity.cs. Let's modify the activity to interact with the TextView field instead of the button. We use the FindViewById method to retrieve a view by the ID we set up in the layout file. Xamarin Studio has also auto-generated a static class named Resource to reference your identifiers. So let's retrieve the instance of the TextView field by placing this code in OnCreate as follows: TextView text = FindViewById(Resource.Id.myText);
The Resource class is a static class that the Xamarin designer will populate for you. For future reference, you might have to build your Android project for new IDs and other resources to show up in your C# files in Xamarin Studio. Next, let's update the Click event on the button: button.Click += delegate { text.Text = string.Format("Count: {0}", ++count); };
This will rewire the button to update the text in TextView instead of on the button itself. Now if we run the application, we'll get an Android app that functions identically to the iOS one in the previous chapter. The Android app will look like what is shown in the following screenshot:
Since we added some of our own views to our layout, let's add a second activity to build on our understanding of activities in Android.
[ 40 ]
Chapter 2
Return to Xamarin Studio and perform the following steps: 1. If needed, open the Android project you created earlier in the chapter in Xamarin Studio. 2. Create a new Android activity in the project under the Android section. Name it SecondActivity.cs. 3. Navigate to Resources | layouts, and create a new Android layout named Second.axml. 4. Open SecondActivity.cs and add the following code to OnCreate: SetContentView(Resource.Layout.Second);
5. Open MainActivity.cs and add the following line of code to the Click event of your button: StartActivity(typeof(SecondActivity));
6. Open Second.axml and drag a button into the view. Set its text to Finish, for example, and set its ID to @+id/finish. 7. Finally, open SecondActivity.cs and add the following lines to its OnCreate method: var finish = FindViewById(Resource.Id.finish); finish.Click += (sender, e) => Finish();
8. Build and run your application. Your application's button will now launch a new activity in addition to incrementing the count on the label. Once SecondActivity is visible, you can click on its button to finish the activity and return to the first activity. Down the road, if you need to pass information from one activity to another, you will need to create an Intent object to pass to StartActivity. The second activity of your app is shown in the following screenshot:
[ 41 ]
Hello, Platforms!
Summary
In this chapter, we created our first iOS application in Xamarin Studio. We covered Apple's MVC design pattern to better understand the relationship between UIViewController and UIView and also covered how to use the iOS designer in Xamarin Studio to edit storyboard files. Next, we created our first Android application in Xamarin Studio and learned the activity lifecycle in Android. We also used Xamarin's Android designer to make changes to Android XML layouts. From the topics covered in this chapter, you should be fairly confident in developing simple apps for iOS and Android using Xamarin's tools. You should have a basic understanding of the native SDKs and design patterns to accomplish tasks on iOS and Android. In the next chapter, we'll cover various techniques used to share code across platforms with Xamarin Studio. We'll go over different ways of architecting your cross-platform application and how to set up Xamarin Studio projects and solutions.
[ 42 ]
Code Sharing between iOS and Android Xamarin's tools promise to share a good portion of your code between iOS and Android while taking advantage of the native APIs on each platform where possible. Doing so is an exercise in software engineering more than a programming skill or having the knowledge of each platform. To architect a Xamarin application to enable code sharing, it is a must to separate your application into distinct layers. We'll cover the basics of this in this chapter as well as specific options to consider in certain situations. In this chapter, we will cover: • • • • • •
The MVVM design pattern for code sharing Project and solution organization strategies Portable Class Libraries (PCLs) Preprocessor statements for platform-specific code Dependency injection (DI) simplified Inversion of Control (IoC)
Learning the MVVM design pattern
The Model-View-ViewModel (MVVM) design pattern was originally invented for Windows Presentation Foundation (WPF) applications using XAML for separating the UI from business logic and taking full advantage of data binding. Applications architected in this way have a distinct ViewModel layer that has no dependencies on its user interface. This architecture in itself is optimized for unit testing as well as cross-platform development. Since an application's ViewModel classes have no dependencies on the UI layer, you can easily swap an iOS user interface for an Android one and write tests against the ViewModel layer.
Code Sharing between iOS and Android
The MVVM design pattern is also very similar to the MVC design pattern discussed in the previous chapters. The MVVM design pattern includes the following: • Model: The Model layer is the backend business logic that drives the application and any business objects to go along with it. This can be anything from making web requests to a server to using a backend database. • View: This layer is the actual user interface seen on the screen. In the case of cross-platform development, it includes any platform-specific code for driving the user interface of the application. On iOS, this includes controllers used throughout an application, and on Android, an application's activities. • ViewModel: This layer acts as the glue in MVVM applications. The ViewModel layers coordinate operations between the View and Model layers. A ViewModel layer will contain properties that the View will get or set, and functions for each operation that can be made by the user on each View. The ViewModel layer will also invoke operations on the Model layer if needed. The following figure shows you the MVVM design pattern:
It is important to note that the interaction between the View and ViewModel layers is traditionally created by data binding with WPF. However, iOS and Android do not have built-in data binding mechanisms, so our general approach throughout the module will be to manually call the ViewModel layer from the View layer. There are a few frameworks out there that provide data binding functionality such as MVVMCross (not covered in this module) and Xamarin.Forms.
Implementing MVVM in an example
To understand this pattern better, let's implement a common scenario. Let's say we have a search box on the screen and a search button. When the user enters some text and clicks on the button, a list of products and prices will be displayed to the user. In our example, we use the async and await keywords that are available in C# 5 to simplify asynchronous programming.
[ 44 ]
Chapter 3
To implement this feature, we will start with a simple model class (also called a business object) as follows: public class Product { public int Id { get; set; } //Just a numeric identifier public string Name { get; set; } //Name of the product public float Price { get; set; } //Price of the product }
Next, we will implement our Model layer to retrieve products based on the searched term. This is where the business logic is performed, expressing how the search needs to actually work. This is seen in the following lines of code: // An example class, in the real world would talk to a web // server or database. public class ProductRepository { // a sample list of products to simulate a database private Product[] products = new[] { new Product { Id = 1, Name = "Shoes", Price = 19.99f }, new Product { Id = 2, Name = "Shirt", Price = 15.99f }, new Product { Id = 3, Name = "Hat", Price = 9.99f }, }; public async Task SearchProducts( string searchTerm) { // Wait 2 seconds to simulate web request await Task.Delay(2000); // Use Linq-to-objects to search, ignoring case searchTerm = searchTerm.ToLower(); return products.Where(p => p.Name.ToLower().Contains(searchTerm)).ToArray(); } }
It is important to note here that the Product and ProductRepository classes are both considered as a part of the Model layer of a cross-platform application. Some might consider ProductRepository as a service that is generally a self-contained class to retrieve data. It is a good idea to separate this functionality into two classes. The Product class's job is to hold information about a product, while the ProductRepository class is in charge of retrieving products. This is the basis for the single responsibility principle, which states that each class should only have one job or concern. [ 45 ]
Code Sharing between iOS and Android
Next, we will implement a ViewModel class as follows: public class ProductViewModel { private readonly ProductRepository repository = new ProductRepository(); public string SearchTerm { get; set; } public Product[] Products { get; private set; } public async Task Search() { if (string.IsNullOrEmpty(SearchTerm)) Products = null; else Products = await repository.SearchProducts(SearchTerm); } }
From here, your platform-specific code starts. Each platform will handle managing an instance of a ViewModel class, setting the SearchTerm property, and calling Search when the button is clicked. When the task completes, the user interface layer will update a list displayed on the screen. If you are familiar with the MVVM design pattern used with WPF, you might notice that we are not implementing INotifyPropertyChanged for data binding. Since iOS and Android don't have the concept of data binding, we omitted this functionality. If you plan on having a WPF or Windows 8 version of your mobile application or are using a framework that provides data binding, you should implement support for it where needed.
[ 46 ]
Chapter 3
Comparing project organization strategies You might be asking yourself at this point, how do I set up my solution in Xamarin Studio to handle shared code and also have platform-specific projects? Xamarin.iOS applications can only reference Xamarin.iOS class libraries, so setting up a solution can be problematic. There are several strategies for setting up a cross-platform solution, each with its own advantages and disadvantages. Options for cross-platform solutions are as follows: • File Linking: For this option, you will start with either a plain .NET 4.0 or .NET 4.5 class library that contains all the shared code. You would then have a new project for each platform you want your app to run on. Each platform-specific project will have a subdirectory with all of the files linked in from the first class library. To set this up, add the existing files to the project and select the Add a link to the file option. Any unit tests can run against the original class library. The advantages and disadvantages of file linking are as follows: °°
Advantages: This approach is very flexible. You can choose to link or not link certain files and can also use preprocessor directives such as #if IPHONE. You can also reference different libraries on Android versus iOS.
°°
Disadvantages: You have to manage a file's existence in three projects: core library, iOS, and Android. This can be a hassle if it is a large application or if many people are working on it. This option is also a bit outdated since the arrival of shared projects.
• Cloned Project Files: This is very similar to file linking. The main difference being that you have a class library for each platform in addition to the main project. By placing the iOS and Android projects in the same directory as the main project, the files can be added without linking. You can easily add files by right-clicking on the solution and navigating to Display Options | Show All Files. Unit tests can run against the original class library or the platform-specific versions: °°
Advantages: This approach is just as flexible as file linking, but you don't have to manually link any files. You can still use preprocessor directives and reference different libraries on each platform.
°°
Disadvantages: You still have to manage a file's existence in three projects. There is additionally some manual file arranging required to set this up. You also end up with an extra project to manage on each platform. This option is also a bit outdated since the arrival of shared projects. [ 47 ]
Code Sharing between iOS and Android
• Shared Projects: Starting with Visual Studio 2013 Update 2, Microsoft created the concept of shared projects to enable code sharing between Windows 8 and Windows Phone apps. Xamarin has also implemented shared projects in Xamarin Studio as another option to enable code sharing. Shared projects are virtually the same as file linking, since adding a reference to a shared project effectively adds its files to your project: °°
Advantages: This approach is the same as file linking, but a lot cleaner since your shared code is in a single project. Xamarin Studio also provides a dropdown to toggle between each referencing project, so that you can see the effect of preprocessor statements in your code.
°°
Disadvantages: Since all the files in a shared project get added to each platform's main project, it can get ugly to include platformspecific code in a shared project. Preprocessor statements can quickly get out of hand if you have a large team or have team members that do not have a lot of experience. A shared project also doesn't compile to a DLL, so there is no way to share this kind of project without the source code.
• Portable Class Libraries: This is the most optimal option; you begin the solution by making a Portable Class Library (PCL) project for all your shared code. This is a special project type that allows multiple platforms to reference the same project, allowing you to use the smallest subset of C# and the .NET framework available in each platform. Each platform-specific project will reference this library directly as well as any unit test projects: °°
Advantages: All your shared code is in one project, and all platforms use the same library. Since preprocessor statements aren't possible, PCL libraries generally have cleaner code. Platform-specific code is generally abstracted away by interfaces or abstract classes.
°°
Disadvantages: You are limited to a subset of .NET depending on how many platforms you are targeting. Platform-specific code requires use of dependency injection, which can be a more advanced topic for developers not familiar with it.
Setting up a cross-platform solution
To understand each option completely and what different situations call for, let's define a solution structure for each cross-platform solution. Let's use the product search example used earlier in the chapter and set up a solution for each approach.
[ 48 ]
Chapter 3
To set up file linking, perform the following steps: 1. Open Xamarin Studio and start a new solution. 2. Select a new Library project under the general C# section. 3. Name the project ProductSearch.Core, and name the solution ProductSearch. 4. Right-click on the newly created project and select Options. 5. Navigate to Build | General, and set the Target Framework option to .NET Framework 4.5. 6. Add the Product, ProductRepository, and ProductViewModel classes to the project used earlier in the chapter. You will need to add using System. Threading.Tasks; and using System.Linq; where needed. 7. Navigate to Build | Build All from the menu at the top to be sure that everything builds properly. 8. Now, let's create a new iOS project by right-clicking on the solution and navigating to Add | Add New Project. Then, navigate to iOS | iPhone | Single View Application and name the project ProductSearch.iOS. 9. Create a new Android project by right-clicking on the solution and navigating to Add | Add New Project. Create a new project by navigating to Android | Android Application and name it ProductSearch.Droid. 10. Add a new folder named Core to both the iOS and Android projects. 11. Right-click on the new folder for the iOS project and navigate to Add | Add Files from Folder. Select the root directory for the ProductSearch.Core project. 12. Check the three C# files in the root of the project. An Add File to Folder dialog will appear. 13. Select Add a link to the file and make sure that the Use the same action for all selected files checkbox is selected. 14. Repeat this process for the Android project. 15. Navigate to Build | Build All from the menu at the top to double-check everything. You have successfully set up a cross-platform solution with file linking.
[ 49 ]
Code Sharing between iOS and Android
When all is done, you will have a solution tree that looks something like what you can see in the following screenshot:
You should consider using this technique when you have to reference different libraries on each platform. You might consider using this option if you are using MonoGame, or other frameworks that require you to reference a different library on iOS versus Android. Setting up a solution with the cloned project files approach is similar to file linking, except that you will have to create an additional class library for each platform. To do this, create an Android library project and an iOS library project in the same ProductSearch.Core directory. You will have to create the projects and move them to the proper folder manually, then re-add them to the solution. Right-click on the solution and navigate to Display Options | Show All Files to add the required C# files to these two projects. Your main iOS and Android projects can reference these projects directly. [ 50 ]
Chapter 3
Your project will look like what is shown in the following screenshot, with ProductSearch.iOS referencing ProductSearch.Core.iOS and ProductSearch. Droid referencing ProductSearch.Core.Droid:
Working with Portable Class Libraries
A Portable Class Library (PCL) is a C# library project that can be supported on multiple platforms, including iOS, Android, Windows, Windows Store apps, Windows Phone, Silverlight, and Xbox 360. PCLs have been an effort by Microsoft to simplify development across different versions of the .NET framework. Xamarin has also added support for iOS and Android for PCLs. Many popular cross-platform frameworks and open source libraries are starting to develop PCL versions such as Json.NET and MVVMCross.
Using PCLs in Xamarin Let's create our first portable class library:
1. Open Xamarin Studio and start a new solution. 2. Select a new Portable Library project under the general C# section. [ 51 ]
Code Sharing between iOS and Android
3. Name the project ProductSearch.Core and name the solution ProductSearch. 4. Add the Product, ProductRepository, and ProductViewModel classes to the project used earlier in the chapter. You will need to add using System.Threading.Tasks; and using System.Linq; where needed. 5. Navigate to Build | Build All from the menu at the top to be sure that everything builds properly. 6. Now, let's create a new iOS project by right-clicking on the solution and navigating to Add | Add New Project. Create a new project by navigating to iOS | iPhone | Single View Application and name it ProductSearch.iOS. 7. Create a new Android project by right-clicking on the solution and navigating to Add | Add New Project. Then, navigate to Android | Android Application and name the project ProductSearch.Droid. 8. Simply add a reference to the portable class library from the iOS and Android projects. 9. Navigate to Build | Build All from the top menu and you have successfully set up a simple solution with a portable library. Each solution type has its distinct advantages and disadvantages. PCLs are generally better, but there are certain cases where they can't be used. For example, if you were using a library such as MonoGame, which is a different library for each platform, you would be much better off using a shared project or file linking. Similar issues would arise if you needed to use a preprocessor statement such as #if IPHONE or a native library such as the Facebook SDK on iOS or Android. Setting up a shared project is almost the same as setting up a portable class library. In step 2, just select Shared Project under the general C# section and complete the remaining steps as stated.
Using preprocessor statements
When using shared projects, file linking, or cloned project files, one of your most powerful tools is the use of preprocessor statements. If you are unfamiliar with them, C# has the ability to define preprocessor variables such as #define IPHONE , allowing you to use #if IPHONE or #if !IPHONE.
[ 52 ]
Chapter 3
The following is a simple example of using this technique: #if IPHONE Console.WriteLine("I am running on iOS"); #elif ANDROID Console.WriteLine("I am running on Android"); #else Console.WriteLine("I am running on ???"); #endif
In Xamarin Studio, you can define preprocessor variables in your project's options by navigating to Build | Compiler | Define Symbols, delimited with semicolons. These will be applied to the entire project. Be warned that you must set up these variables for each configuration setting in your solution (Debug and Release); this can be an easy step to miss. You can also define these variables at the top of any C# file by declaring #define IPHONE, but they will only be applied within the C# file. Let's go over another example, assuming that we want to implement a class to open URLs on each platform: public static class Utility { public static void OpenUrl(string url) { //Open the url in the native browser } }
The preceding example is a perfect candidate for using preprocessor statements, since it is very specific to each platform and is a fairly simple function. To implement the method on iOS and Android, we will need to take advantage of some native APIs. Refactor the class to look as follows: #if IPHONE //iOS using statements using MonoTouch.Foundation; using MonoTouch.UIKit; #elif ANDROID //Android using statements using Android.App; using Android.Content; using Android.Net; #else
[ 53 ]
Code Sharing between iOS and Android //Standard .Net using statement using System.Diagnostics; #endif public static class Utility { #if ANDROID public static void OpenUrl(Activity activity, string url) #else public static void OpenUrl(string url) #endif { //Open the url in the native browser #if IPHONE UIApplication.SharedApplication.OpenUrl( NSUrl.FromString(url)); #elif ANDROID var intent = new Intent(Intent.ActionView, Uri.Parse(url)); activity.StartActivity(intent); #else Process.Start(url); #endif } }
The preceding class supports three different types of projects: Android, iOS, and a standard Mono or .NET framework class library. In the case of iOS, we can perform the functionality with static classes available in Apple's APIs. Android is a little more problematic and requires an Activity object to launch a browser natively. We get around this by modifying the input parameters on Android. Lastly, we have a plain .NET version that uses Process.Start() to launch a URL. It is important to note that using the third option would not work on iOS or Android natively, which necessitates our use of preprocessor statements. Using preprocessor statements is not normally the cleanest or the best solution for cross-platform development. They are generally best used in a tight spot or for very simple functions. Code can easily get out of hand and can become very difficult to read with many #if statements, so it is always better to use it in moderation. Using inheritance or interfaces is generally a better solution when a class is mostly platform specific.
[ 54 ]
Chapter 3
Simplifying dependency injection
Dependency injection at first seems like a complex topic, but for the most part it is a simple concept. It is a design pattern aimed at making your code within your applications more flexible so that you can swap out certain functionality when needed. The idea builds around setting up dependencies between classes in an application so that each class only interacts with an interface or base/abstract class. This gives you the freedom to override different methods on each platform when you need to fill in native functionality. The concept originated from the SOLID object-oriented design principles, which is a set of rules you might want to research if you are interested in software architecture. There is a good article about SOLID on Wikipedia, (http:// en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29) if you would like to learn more. The D in SOLID, which we are interested in, stands for dependencies. Specifically, the principle declares that a program should depend on abstractions, not concretions (concrete types). To build upon this concept, let's walk you through the following example: 1. Let's assume that we need to store a setting in an application that determines whether the sound is on or off. 2. Now let's declare a simple interface for the setting: interface ISettings { bool IsSoundOn { get; set; } }. 3. On iOS, we'd want to implement this interface using the NSUserDefaults class. 4. Likewise, on Android, we will implement this using SharedPreferences. 5. Finally, any class that needs to interact with this setting will only reference ISettings so that the implementation can be replaced on each platform. Downloading the example code You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this module elsewhere, you can visit http://www. packtpub.com/support and register to have the files e-mailed directly to you.
[ 55 ]
Code Sharing between iOS and Android
For reference, the full implementation of this example will look like the following snippet: public interface ISettings { bool IsSoundOn { get; set; } } //On iOS using MonoTouch.UIKit; using MonoTouch.Foundation; public class AppleSettings : ISettings { public bool IsSoundOn { get { return NSUserDefaults.StandardUserDefaults; BoolForKey("IsSoundOn"); } set { var defaults = NSUserDefaults.StandardUserDefaults; defaults.SetBool(value, "IsSoundOn"); defaults.Synchronize(); } } } //On Android using Android.Content; public class DroidSettings : ISettings { private readonly ISharedPreferences preferences; public DroidSettings(Context context) { preferences = context.GetSharedPreferences( context.PackageName, FileCreationMode.Private); } [ 56 ]
Chapter 3 public bool IsSoundOn { get { return preferences.GetBoolean("IsSoundOn", true"); } set { using (var editor = preferences.Edit()) { editor.PutBoolean("IsSoundOn", value); editor.Commit(); } } } }
Now you will potentially have a ViewModel class that will only reference ISettings when following the MVVM pattern. It can be seen in the following snippet: public class SettingsViewModel { private readonly ISettings settings; public SettingsViewModel(ISettings settings) { this.settings = settings; } public bool IsSoundOn { get; set; } public void Save() { settings.IsSoundOn = IsSoundOn; } }
[ 57 ]
Code Sharing between iOS and Android
Using a ViewModel layer for such a simple example is not necessarily needed, but you can see it would be useful if you needed to perform other tasks such as input validation. A complete application might have a lot more settings and might need to present the user with a loading indicator. Abstracting out your setting's implementation has other benefits that add flexibility to your application. Let's say you suddenly need to replace NSUserDefaults on iOS with the iCloud instead; you can easily do so by implementing a new ISettings class and the remainder of your code will remain unchanged. This will also help you target new platforms such as Windows Phone, where you might choose to implement ISettings in a platformspecific way.
Implementing Inversion of Control
You might be asking yourself at this point in time, how do I switch out different classes such as the ISettings example? Inversion of Control (IoC) is a design pattern meant to complement dependency injection and solve this problem. The basic principle is that many of the objects created throughout your application are managed and created by a single class. Instead of using the standard C# constructors for your ViewModel or Model classes, a service locator or factory class will manage them throughout the application. There are many different implementations and styles of IoC, so let's implement a simple service locator class to use through the remainder of this module as follows: public static class ServiceContainer { static readonly Dictionary services = new Dictionary(); public static void Register(Func function) { services[typeof(T)] = new Lazy(() => function()); } public static T Resolve() { return (T)Resolve(typeof(T)); } public static object Resolve(Type type) { Lazy service; if (services.TryGetValue(type, out service)) { return service.Value; [ 58 ]
Chapter 3 } throw new Exception("Service not found!"); } }
This class is inspired by the simplicity of XNA/MonoGame's GameServiceContainer class and follows the service locator pattern. The main differences are the heavy use of generics and the fact that it is a static class. To use our ServiceContainer class, we will declare the version of ISettings or other interfaces that we want to use throughout our application by calling Register, as seen in the following lines of code: //iOS version of ISettings ServiceContainer.Register(() => new AppleSettings()); //Android version of ISettings ServiceContainer.Register(() => new DroidSettings()); //You can even register ViewModels ServiceContainer.Register(() => new SettingsViewModel());
On iOS, you can place this registration code in either your static void Main() method or in the FinishedLaunching method of your AppDelegate class. These methods are always called before the application is started. On Android, it is a little more complicated. You cannot put this code in the OnCreate method of your activity that acts as the main launcher. In some situations, the Android OS can close your application but restart it later in another activity. This situation is likely to cause an exception somewhere. The guaranteed safe place to put this is in a custom Android Application class which has an OnCreate method that is called prior to any activities being created in your application. The following lines of code show you the use of the Application class: [Application] public class Application : Android.App.Application { //This constructor is required public Application(IntPtr javaReference, JniHandleOwnership transfer): base(javaReference, transfer) { } public override void OnCreate()
[ 59 ]
Code Sharing between iOS and Android { base.OnCreate(); //IoC Registration here } }
To pull a service out of the ServiceContainer class, we can rewrite the constructor of the SettingsViewModel class so that it is similar to the following lines of code: public SettingsViewModel() { this.settings = ServiceContainer.Resolve(); }
Likewise, you will use the generic Resolve method to pull out any ViewModel classes you would need to call from within controllers on iOS or activities on Android. This is a great, simple way to manage dependencies within your application. There are, of course, some great open source libraries out there that implement IoC for C# applications. You might consider switching to one of them if you need more advanced features for service location or just want to graduate to a more complicated IoC container. Here are a few libraries that have been used with Xamarin projects: • TinyIoC: https://github.com/grumpydev/TinyIoC • Ninject: http://www.ninject.org/ • MvvmCross: https://github.com/slodge/MvvmCross includes a full MVVM framework as well as IoC • Simple Injector: http://simpleinjector.codeplex.com • OpenNETCF.IoC: http://ioc.codeplex.com
Summary
In this chapter, we learned about the MVVM design pattern and how it can be used to better architect cross-platform applications. We compared several project organization strategies for managing a Xamarin Studio solution that contains both iOS and Android projects. We went over portable class libraries as the preferred option for sharing code and how to use preprocessor statements as a quick and dirty way to implement platform-specific code.
[ 60 ]
Chapter 3
After completing this chapter, you should be able to speed up with several techniques for sharing code between iOS and Android applications using Xamarin Studio. Using the MVVM design pattern will help you divide your shared code and code that is platform specific. We also covered several options for setting up cross-platform Xamarin solutions. You should also have a firm understanding of using dependency injection and Inversion of Control to give your shared code access to the native APIs on each platform. In our next chapter, we will begin with writing a cross-platform application and dive into using these techniques.
[ 61 ]
XamChat – a Cross-platform App The best way to truly learn a programming skill, in my opinion, is to take on a simple project that requires you to exercise that skill. This gives new developers a project where they can focus on the concepts they are trying to learn without the overhead of fixing bugs or following customer requirements. To increase our understanding of Xamarin and cross-platform development, let's develop a simple app called XamChat for iOS and Android. In this chapter, we will cover the following topics: • Our sample application concept • The Model layer of our application • Mocking a web service • The ViewModel layer of our application • Writing unit tests
Starting our sample application concept
The concept is simple: a chat application that uses a standard Internet connection as an alternative to send text messages. There are several popular applications like this on the Apple App Store, probably due to the cost of text messaging and support for devices such as the iPod Touch or iPad. This will be a neat real-world example that can be useful for users, and will cover specific topics in developing applications for iOS and Android.
XamChat – a Cross-platform App
Before we start with the development, let's list the set of screens that we'll need: • Login / sign up: This screen will include a standard login and sign-up process for the user • List of conversations: This screen will include a button to start a new conversation • List of friends: This screen will provide a way to add new friends when we start a new conversation • Conversation: This screen will have a list of messages between you and another user, and an option to reply So a quick wireframe layout of the application will help you grasp a better understanding of the layout of the app. The following figure shows you the set of screens to be included in your app:
Developing our Model layer
Since we have a good idea of what the application is, the next step is to develop the business objects, or Model layer, of this application. Let's start by defining a few classes that will contain the data to be used throughout the app. It is recommended, for the sake of organization, to add these to a Models folder in your project. This is the bottom layer of the MVVM design pattern. Let's begin with a class that represents a user. The class can be created as follows: public class User { public string Id { get; set; }
[ 64 ]
Chapter 4 public string Username { get; set; } public string Password { get; set; } }
Pretty straightforward so far; let's move on to create classes representing a conversation and a message as follows: public class Conversation { public string Id { get; set; } public string UserId { get; set; } public string Username { get; set; } public string LastMessage { get; set; } } public class Message { public string Id { get; set; } public string ConversationId { get; set; } public string UserId { get; set; } public string Username { get; set; } public string Text { get; set; } public DateTime Date { get; set; } }
Notice that we are using strings as identifiers for the various objects. This will simplify our integration with Azure Mobile Services in the later chapters. UserId is the value that will be set by the application to change the user that the object is associated with. Now, let's go ahead and set up our solution by performing the following steps: 1. Start by creating a new solution and a new Portable Library project. 2. Name the project XamChat.Core and the solution XamChat. 3. You can also choose to use a Shared Project for this project, but I chose to use a portable class library because it encourages better programming practices in general.
Writing a mock web service
Many times when developing a mobile application, you might need to begin the development of your application before the real backend web service is available. To prevent the development from halting entirely, a good approach would be to develop a mock version of the service. This is also helpful when you need to write unit tests, or just need to add a real backend to your app later. [ 65 ]
XamChat – a Cross-platform App
First, let's break down the operations our app will perform against a web server. The operations are as follows: 1. Log in with a username and password. 2. Register a new account. 3. Get the user's list of friends. 4. Add friends by their usernames. 5. Get a list of the existing conversations for the user. 6. Get a list of messages in a conversation. 7. Send a message. Now let's define an interface that offers a method for each scenario. The method is as follows: public interface IWebService { Task Login(string username, string password); Task Register(User user); Task GetFriends(string userId); Task AddFriend(string userId, string username); Task GetConversations(string userId); Task GetMessages(string conversationId); Task SendMessage(Message message); }
As you can see, we're simplifying any asynchronous communication with a web service by leveraging the Task Parallel Library (TPL) from the .NET base class libraries. Since communicating with a web service can be a lengthy process, it is always a good idea to use the Task class for these operations. Otherwise, you can inadvertently run a lengthy task on the user interface thread, which will prevent user input during the operation. Task is definitely needed for web requests, since users could be using a cellular Internet connection on iOS and Android, and it will give us the ability to use the async and await keywords down the road. Now let's implement a fake service that implements this interface. Place classes such as FakeWebService in the Fakes folder of the project. Let's start with the class declaration and the first method of the interface: public class FakeWebService {
[ 66 ]
Chapter 4 public int SleepDuration { get; set; } public FakeWebService() { SleepDuration = 1; } private Task Sleep() { return Task.Delay(SleepDuration); } public async Task Login( string username, string password) { await Sleep(); return new User { Id = "1", Username = username }; } }
We started off with a SleepDuration property to store a number in milliseconds. This is used to simulate an interaction with a web server, which can take some time. It is also useful for changing the SleepDuration value in different situations. For example, you might want to set this to a small number when writing unit tests so that the tests execute quickly. Next, we implemented a simple Sleep method to return a task that introduces a delay of a number of milliseconds. This method will be used throughout the fake service to cause a delay on each operation. Finally, the Login method merely used an await call on the Sleep method and returned a new User object with the appropriate Username. For now, any username or password combination will work; however, you might wish to write some code here to check specific credentials. Now, let's implement a few more methods to continue our FakeWebService class as follows: public async Task Register(User user) { await Sleep(); return user; }
[ 67 ]
XamChat – a Cross-platform App public async Task GetConversations(string userId) { await Sleep(); return new[] { new Conversation { Id = "1", UserId = "2", "bobama", LastMessage = "Hey!" }, new Conversation { Id = "1", UserId = "3", "bobloblaw", LastMessage = "Have you seen that new movie?" new Conversation { Id = "1", UserId = "4", "gmichael", LastMessage = "What?" }, }; }
Username = Username = }, Username =
For each of these methods, we used exactly the same pattern as the Login method. Each method will delay and return some sample data. Feel free to mix the data with your own values. Now, let's implement the GetConversations method required by the interface as follows: public async Task GetConversations(string userId) { await Sleep(); return new[] { new Conversation { Id = "1", UserId = "2" }, new Conversation { Id = "2", UserId = "3" }, new Conversation { Id = "3", UserId = "4" }, }; }
Basically, we just create a new array of the Conversation objects with arbitrary IDs. We also make sure to match up the UserId values with the IDs we have used on the User objects so far.
[ 68 ]
Chapter 4
Next, let's implement GetMessages to retrieve a list of messages as follows: public async Task GetMessages(string conversationId) { await Sleep(); return new[] { new Message { Id = "1", ConversationId = conversationId, UserId = "2", Text = "Hey", Date = DateTime.Now.AddMinutes(-15), }, new Message { Id = "2", ConversationId = conversationId, UserId = "1", Text = "What's Up?", Date = DateTime.Now.AddMinutes(-10), }, new Message { Id = "3", ConversationId = conversationId, UserId = "2", Text = "Have you seen that new movie?", Date = DateTime.Now.AddMinutes(-5), }, new Message { Id = "4", ConversationId = conversationId, UserId = "1", Text = "It's great!", Date = DateTime.Now, }, }; }
Once again, we are adding some arbitrary data here, and mainly making sure that UserId and ConversationId match our existing data so far. [ 69 ]
XamChat – a Cross-platform App
And finally, we will write one more method to send a message, as follows: public async Task SendMessage(Message message) { await Sleep(); return message; }
Most of these methods are very straightforward. Note that the service doesn't have to work perfectly; it should merely complete each operation successfully with a delay. Each method should also return test data of some kind to be displayed in the UI. This will give us the ability to implement our iOS and Android applications while filling in the web service later. Next, we need to implement a simple interface for persisting application settings. Let's define an interface named ISettings as follows: public interface ISettings { User User { get; set; } void Save(); }
We are making ISettings synchronous, but you might want to set up the Save method to be asynchronous and return Task if you plan on storing settings in the cloud. We don't really need this with our application since we will only be saving our settings locally. Later on, we'll implement this interface on each platform using Android and iOS APIs. For now, let's just implement a fake version that will be used later when we write unit tests. We will implement the interface with the following lines of code: public class FakeSettings : ISettings { public User User { get; set; } public void Save() { } }
Note that the fake version doesn't actually need to do anything; we just need to provide a class that will implement the interface and not throw any unexpected errors.
[ 70 ]
Chapter 4
This completes the Model layer of the application. Here is a final class diagram of what we have implemented so far:
Writing the ViewModel layer
Now that we have our Model layer implemented, we can move on to write the ViewModel layer. The ViewModel layer will be responsible for presenting each operation to the UI and offering properties to be filled out by the View layer. Other common responsibilities of this layer are input validation and simple logic to display busy indicators.
[ 71 ]
XamChat – a Cross-platform App
At this point, it would be a good idea to include the ServiceContainer class from the previous chapter in our XamChat.Core project, as we will be using it throughout our ViewModel layer to interact with the Model layer. We will be using it as a simple option to support dependency injection and Inversion of Control. However, you can use another library of your preference for this. Normally, we start off by writing a base class for all the ViewModel layers within our project. This class always has the functionalities shared by all the classes. It's a good place to put some parts of the code that are used by all the methods in the classes; for example, notification changes, methods, or similar instances. Place the following code snippet in a new ViewModels folder within your project: public class BaseViewModel { protected readonly IWebService service = ServiceContainer.Resolve(); protected readonly ISettings settings = ServiceContainer.Resolve(); public event EventHandler IsBusyChanged = delegate { }; private bool isBusy = false; public bool IsBusy { get { return isBusy; } set { isBusy = value; IsBusyChanged(this, EventArgs.Empty); } } }
The BaseViewModel class is a great place to place any common functionality that you plan on reusing throughout your application. For this app, we only need to implement some functionality to indicate whether the ViewModel layer is busy. We provided a property and an event that the UI will be able to subscribe to and display a wait indicator on the screen. We also added some fields for the services that will be needed. Another common functionality that could be added would be validation for user inputs; however, we don't really need it for this application.
[ 72 ]
Chapter 4
Implementing our LoginViewModel class
Now that we have a base class for all of the ViewModel layers, we can implement a ViewModel layer for the first screen in our application, the Login screen. Now let's implement a LoginViewModel class as follows: public class LoginViewModel : BaseViewModel { public string Username { get; set; } public string Password { get; set; } public async Task Login() { if (string.IsNullOrEmpty(Username)) throw new Exception("Username is blank."); if (string.IsNullOrEmpty(Password)) throw new Exception("Password is blank."); IsBusy = true; try { settings.User = await service.Login(Username, Password); settings.Save(); } finally { IsBusy = false; } } }
In this class, we implemented the following: • We subclassed BaseViewModel to get access to IsBusy and the fields that contain common services • We added the Username and Password properties to be set by the View layer • We added a User property to be set when the login process is completed • We implemented a Login method to be called from View, with validation on the Username and Password properties • We set IsBusy during the call to the Login method on IWebService • We set the User property by awaiting the result from Login on the web service [ 73 ]
XamChat – a Cross-platform App
Basically, this is the pattern that we'll follow for the rest of the ViewModel layers in the application. We provide properties for the View layer to be set by the user's input, and methods to call for various operations. If it is a method that could take some time, such as a web request, you should always return Task and use the async and await keywords. Note that we used try and finally blocks for setting IsBusy back to false. This will ensure that it gets reset properly even when an exception is thrown. We plan on handling the error in the View layer so that we can display a native pop up to the user displaying a message.
Implementing our RegisterViewModel class
Since we have finished writing our ViewModel class to log in, we will now need to create one for the user's registration. Let's implement another ViewModel layer to register a new user: public class RegisterViewModel : BaseViewModel { public string Username { get; set; } public string Password { get; set; } public string ConfirmPassword { get; set; } }
These properties will handle inputs from the user. Next, we need to add a Register method as follows: public async Task Register() { if (string.IsNullOrEmpty(Username)) throw new Exception("Username is blank."); if (string.IsNullOrEmpty(Password)) throw new Exception("Password is blank."); if (Password != ConfirmPassword) throw new Exception("Passwords don't match."); IsBusy = true; try { settings.User = await service .Register(new User { Username = Username, Password = Password, }); settings.Save(); } [ 74 ]
Chapter 4 finally { IsBusy = false; } }
The RegisterViewModel class is very similar to the LoginViewModel class, but has an additional ConfirmPassword property for the UI to be set. A good rule to follow for when to split the ViewModel layer's functionality is to always create a new class when the UI has a new screen. This helps you keep your code clean and somewhat follow the Single Responsibility Principal (SRP) for your classes. The SRP states that a class should only have a single purpose or responsibility. We'll try to follow this concept to keep our classes small and organized, which can be more important than usual when sharing code across platforms.
Implementing our FriendViewModel class
Next on the list is a ViewModel layer to work with a user's friend list. We will need a method to load a user's friend list and add a new friend. Now let's implement the FriendViewModel as follows: public class FriendViewModel : BaseViewModel { public User[] Friends { get; private set; } public string Username { get; set; } }
Now we'll need a method to load friends. This method is as follows: public async Task GetFriends() { if (settings.User == null) throw new Exception("Not logged in."); IsBusy = true; try { Friends = await service .GetFriends(settings.User.Id); } finally { IsBusy = false; } } [ 75 ]
XamChat – a Cross-platform App
Finally, we'll need a method to add a new friend and then update the list of friends contained locally: public async Task AddFriend() { if (settings.User == null) throw new Exception("Not logged in."); if (string.IsNullOrEmpty(Username)) throw new Exception("Username is blank."); IsBusy = true; try { var friend = await service .AddFriend(settings.User.Id, Username); //Update our local list of friends var friends = new List(); if (Friends != null) friends.AddRange(Friends); friends.Add(friend); Friends = friends.OrderBy(f => f.Username).ToArray(); } finally { IsBusy = false; } }
Again, this class is fairly straightforward. The only thing new here is that we added some logic to update the list of friends and sort them within our client application and not the server. You can also choose to reload the complete list of friends if you have a good reason to do so.
Implementing our MessageViewModel class
Our final required ViewModel layer will handle messages and conversations. We need to create a way to load conversations and messages and send a new message.
[ 76 ]
Chapter 4
Let's start implementing our MessageViewModel class as follows: public class MessageViewModel : BaseViewModel { public Conversation[] Conversations { get; private set; } public Conversation Conversation { get; set; } public Message[] Messages { get; private set; } public string Text { get; set; } }
Next, let's implement a method to retrieve a list of conversations as follows: public async Task GetConversations() { if (settings.User == null) throw new Exception("Not logged in."); IsBusy = true; try { Conversations = await service .GetConversations(settings.User.Id); } finally { IsBusy = false; } }
Similarly, we need to retrieve a list of messages within a conversation. We will need to pass the conversation ID to the service as follows: public async Task GetMessages() { if (Conversation == null) throw new Exception("No conversation."); IsBusy = true; try { Messages = await service.GetMessages(Conversation.Id); }
[ 77 ]
XamChat – a Cross-platform App finally { IsBusy = false; } }
Finally, we need to write some code to send a message and update the local list of messages as follows: public async Task SendMessage() { if (settings.User == null) throw new Exception("Not logged in."); if (Conversation == null) throw new Exception("No conversation."); if (string.IsNullOrEmpty (Text)) throw new Exception("Message is blank."); IsBusy = true; try { var message = await service.SendMessage(new Message { UserId = settings.User.Id, ConversationId = Conversation.Id, Text = Text }); //Update our local list of messages var messages = new List(); if (Messages != null) messages.AddRange(Messages); messages.Add(message); Messages = messages.ToArray(); } finally { IsBusy = false; } }
[ 78 ]
Chapter 4
This concludes the ViewModel layer of our application and the entirety of the shared code used on iOS and Android. For the MessageViewModel class, you could have also chosen to put the GetConversations and Conversations properties in their own class, since they can be considered as a separate responsibility, but it is not really necessary. Here is the final class diagram of our ViewModel layer:
Writing unit tests
Since all the code we've written so far is not dependent on the user interface, we can easily write unit tests against our classes. This step is generally taken after the first implementation of a ViewModel class. Proponents of Test Driven Development (TDD) would recommend writing tests first and implementing things afterward, so choose which method is best for you. In either case, it is a good idea to write tests against your shared code before you start using them from the View layer, so that you can catch bugs before they hold up your development on the UI. [ 79 ]
XamChat – a Cross-platform App
Xamarin projects take advantage of an open source testing framework called NUnit. It was originally derived from a Java testing framework called JUnit, and it is the de-facto standard for unit testing C# applications. Xamarin Studio provides several project templates for writing tests with NUnit.
Setting up a new project for unit tests
Let's set up a new project for unit tests by performing the following steps: 1. Add a new NUnit Library Project to your solution, which is found under the C# section. 2. Name the project XamChat.Tests to keep things consistent. 3. Next, let's set the library to a Mono/.NET 4.5 project under Project Options, then navigate to Build | General | Target Framework. 4. Right-click on Project References and choose Edit References. 5. Under the Projects tab, add a reference to XamChat.Core. 6. Now, open the Test.cs file and you will notice the following required attributes that make up a unit test using NUnit: °°
using NUnit.Framework: This attribute is the main statement to
°°
[TestFixture]: This decorates a class to indicate that the class has
°°
[Test]: This decorates a method to indicate a test
be used to work with NUnit
a list of methods for running tests
In addition to the required C# attributes, there are several others that are useful for writing tests, and they are as follows: • [TestFixtureSetUp]: This decorates a method that runs before all the tests contained within a text fixture class. • [SetUp]: This decorates a method that runs before each test in a test fixture class. • [TearDown]: This decorates a method that runs after each test in a test fixture class. • [TestFixtureTearDown]: This decorates a method that runs after all the tests in a text fixture class have been completed. • [ExpectedException]: This decorates a method that is intended to throw an exception. It is useful for test cases that are supposed to fail.
[ 80 ]
Chapter 4
• [Category]: This decorates a test method and can be used to organize different tests; for example, you might categorize them as fast and slow tests.
Writing assertions
The next concept to learn about writing tests with NUnit is how to write assertions. An assertion is a method that will throw an exception if a certain value is not true. It will cause a test to fail and give a descriptive explanation as to what happened. NUnit has a couple of different sets of APIs for assertions; however, we will use the more readable and fluent versions of the APIs. The basic syntax of a fluent-style API is using the Assert.That method. The following example this: Assert.That(myVariable, Is.EqualTo(0));
Likewise, you can assert the opposite: Assert.That(myVariable, Is.Not.EqualTo(0));
Or any of the following: • Assert.That(myVariable, Is.GreaterThan(0)); • Assert.That(myBooleanVariable, Is.True); • Assert.That(myObject, Is.Not.Null); Feel free to explore the APIs. With code completion in Xamarin Studio, you should be able to discover useful static members or methods on the Is class to use within your tests. Before we begin writing specific tests for our application, let's write a static class and method to create a global setup to be used throughout our tests. You can rewrite Test.cs as follows: public static class Test { public static void SetUp() { ServiceContainer.Register(() => new FakeWebService { SleepDuration = 0 }); ServiceContainer.Register(() => new FakeSettings()); } }
[ 81 ]
XamChat – a Cross-platform App
We'll use this method throughout our tests to set up fake services in our Model layer. Additionally, this replaces the existing services so that our tests execute against new instances of these classes. This is a good practice in unit testing to guarantee that no old data is left behind from a previous test. Also, notice that we set SleepDuration to 0. This will make our tests run very quickly. We will begin by creating a ViewModels folder in our test project and adding a class named LoginViewModelTests as follows: [TestFixture] public class LoginViewModelTests { LoginViewModel loginViewModel; ISettings settings; [SetUp] public void SetUp() { Test.SetUp(); settings = ServiceContainer.Resolve(); loginViewModel = new LoginViewModel(); } [Test] public async Task LoginSuccessfully() { loginViewModel.Username = "testuser"; loginViewModel.Password = "password"; await loginViewModel.Login(); Assert.That(settings.User, Is.Not.Null); } }
Notice our use of a SetUp method. We recreate the objects used in every test to make sure that no old data is left over from the previous test runs. Another point to note is that you must return a Task when using async/await in a test method. Otherwise, NUnit would not be able to know when a test completes.
[ 82 ]
Chapter 4
To run the test, use the NUnit menu found docked to the right of Xamarin Studio, by default. Go ahead and run the test using the Run Test button that has a gear icon. You will get a successful result similar to what is shown in the following screenshot:
You can also view the Test Results pane, which will show you the extended details if a test fails, as shown in the following screenshot:
[ 83 ]
XamChat – a Cross-platform App
To see what happens when a test fails, go ahead and modify your test to assert against an incorrect value as follows: //Change Is.Not.Null to Is.Null Assert.That(settings.User, Is.Null);
You will get a very descriptive error in the Test Results pane, as shown in the following screenshot:
Now let's implement another test for the LoginViewModel class; let's make sure that we get the appropriate outcome if the username and password fields are blank. The test is implemented as follows: [Test] public async Task LoginWithNoUsernameOrPassword() { //Throws an exception await loginViewModel.Login(); }
If we run the test as it is, we will get an exception and the test will fail. Since we expect an exception to occur, we can decorate the method to make the test pass only if an exception occurs as follows: [Test, ExpectedException(typeof(Exception), ExpectedMessage = "Username is blank.")]
Note that in our ViewModel, the Exception type is thrown if a field is blank. You can also change the type of expected exception in cases where it is a different exception type.
[ 84 ]
Chapter 4
More tests are included with the sample code along with this module. It is recommended that you write tests against each public operation on each ViewModel class. Additionally, write tests for any validation or other important business logic. I would also recommend that you write tests against the Model layer; however, this is not needed in our project yet since we only have fake implementations.
Summary
In this chapter, we went through the concept of building a sample application called XamChat. We also implemented the core business objects for the application in the Model layer. Since we do not have a server to support this application yet, we implemented a fake web service. This gives you the flexibility to move forward with the app without building a server application. We also implemented the ViewModel layer. This layer will expose operations in a simple way to the View layer. Finally, we wrote tests covering the code we've written so far using NUnit. Writing tests against shared code in a cross-platform application can be very important, as it is the backbone of more than one application. After completing this chapter, you should have completed the shared library for our cross-platform application in its entirety. You should have a very firm grasp on our application's architecture and its distinct Model and ViewModel layers. You should also have a good understanding of how to write fake versions of parts of your application that you might not be ready to implement yet. In the next chapter, we will implement the iOS version of XamChat.
[ 85 ]
XamChat for iOS In this chapter, we will develop the iOS portion of our cross-platform XamChat application. Since we are using the MVVM design pattern, most of the work we will be doing will be in the View layer of the application. We will mainly be working with native iOS APIs and understanding how we can apply them leverage the shared code in our portable class library. Since Xamarin.iOS enables us to call Apple APIs directly, our iOS app will be indistinguishable from an application developed in Objective-C or Swift. To begin writing the iOS version of XamChat, create a new Single View Application under the iOS section. Name the project XamChat.iOS or some other appropriate name of your choice. The project template will automatically create a controller with an unfamiliar name; go ahead and delete it. We will create our own controllers as we go. In this chapter, we will cover the following: • The basics of an iOS application • The use of UINavigationController • Implementing a login screen • Segues and UITableView • Adding a friends list • Adding a list of messages • Composing messages
XamChat for iOS
Understanding the basics of an iOS app
Before we start developing our app, let's review the main settings of the application. Apple uses a file named Info.plist to store important information about any iOS app. These settings are used when an iOS application is installed on a device by the Apple App Store. We will begin development on any new iOS application by filling out the information in this file. Xamarin Studio provides a neat menu to modify values in the Info.plist file, as shown in the following screenshot:
The most important settings are as follows: • Application Name: This is the title below an app's icon in iOS. Note that this is not the same as the official name of your app in the iOS App Store.
[ 88 ]
Chapter 5
• Bundle Identifier: This is your app's bundle identifier or bundle ID. It is a unique name to identify your application. The convention is to use a reverse domain naming style beginning with your company name, such as com. packt.xamchat. • Version: This is a version number for your application such as 1.0.0. • Devices: In this field you can select iPhone/iPod, iPad, or Universal (all devices) for your application. • Deployment Target: This is the minimum iOS version your application runs on. • Main Interface: This is the main storyboard file for your app that declares most of the UI of your application. iOS will automatically load this file and open the root controller as the initial screen to be displayed. • Supported Device Orientations: These are the different positions your application will be able to rotate to and support. There are other settings for app icons, splash screens, and so on. You can also toggle between the Advanced or Source tabs to configure additional settings that Xamarin does not provide a user-friendly menu for. Configure the following settings for our application: • Application Name: XamChat • Bundle Identifier: com.yourcompanyname.xamchat; make sure that you name future apps beginning with com.yourcompanyname • Version: This can be any version number you prefer, but it should just not be left blank • Devices: iPhone/iPod • Deployment Target: 7.0 (you can also target 8.0, but we aren't using any iOS 8-specific APIs in this app) • Supported Device Orientations: Only select Portrait You can find some additional settings for Xamarin iOS applications if you right-click on your project and select Options. It is a good idea to know what is available for iOS-specific projects in Xamarin Studio.
[ 89 ]
XamChat for iOS
Let's discuss some of the most important options. 1. Navigate to the iOS Build | General tab as shown in the following screenshot:
You have the following options under this tab: °°
SDK version: This is the version of the iOS SDK to compile your application with. It is generally best to use Default.
°°
Linker behavior: Xamarin has implemented a feature called linking. The linker will strip any code that will never be called within your assemblies. This keeps your application small and allows them to ship a stripped-down version of the core Mono framework with your app. Except for debug builds, it is best to use the Link SDK assemblies only option. We will cover linking in the next chapter.
°°
Optimize PNG files for iOS: Apple uses a custom PNG format to speed up the loading of PNGs within your app. You can turn this off to speed up builds, or if you plan on optimizing the images yourself.
[ 90 ]
Chapter 5
°°
Enable debugging: Turning this on allows Xamarin to include extra information with your app to enable debugging from Xamarin Studio.
°°
Additional mtouch arguments: This field is for passing extra command-line arguments to the Xamarin compiler for iOS. You can check out the complete list of these arguments at http://iosapi. xamarin.com.
2. Navigate to iOS Build | Advanced tab as shown in the following screenshot:
You have the following options under this tab: °°
Supported architectures: Here, the options are ARMv7, ARMv7s, and a FAT version that includes both. These are instruction sets that different iOS device processors support. If you really care about performance, you might consider selecting the option to support both; however, this will make your application larger.
[ 91 ]
XamChat for iOS
°°
Use LLVM optimizing compiler: Checking this compiles the code that is smaller and runs faster, but it takes longer to compile. LLVM stands for Low Level Virtual Machine.
°°
Enable generic value type sharing: This is an option specific to Mono that draws better performance from C# generics with value types. It has the downside of making the application slightly larger, but I would recommend that you leave it on.
°°
Use SGen generational garbage collector: This uses the new Mono garbage collector in your app. I would recommend that you turn this on if you really need good performance with the garbage collector (GC) or are working on an app that needs to be responsive in real time, such as a game. It is probably safe to turn this on by default now, as the SGen garbage collector is very stable.
°°
Use the reference counting extension (preview): This is currently an experimental feature, but improves the general memory usage of native objects that are accessible from C#. These native object's reference is managed by the GC instead of a backing field on the object when using this setting. Since it is still in preview, you should be careful when using this option.
3. You have the following options under iOS Bundle Signing: °°
Identity: This is the certificate to identify the app's creator for deploying the application to devices. We'll cover more on this in later chapters.
°°
Provisioning profile: This is a specific profile that deploys the app to a device. This works in tandem with Identity, but also declares the distribution method, and the devices that can install the app.
°°
Custom Entitlements: This file contains additional settings to be applied to the provisioning profile, and it contains other specific declarations for the app such as iCloud or push notifications. The project template for iOS apps includes a default Entitlements. plist file for new projects.
4. iOS Application: These settings are identical to what you see in the Info.plist file. For this application, you can leave all these options at their defaults. When making a real iOS application on your own, you should consider changing many of these as per your application's needs.
[ 92 ]
Chapter 5
Using UINavigationController
In iOS applications, the key class that manages navigation between different controllers is the UINavigationController class. The navigation controller is the most basic building block of navigation on iOS, so it is the best choice to start with most of the iOS applications. It is a parent controller that contains several child controllers in a stack. Users can move forward by putting new controllers on top of the stack or using a built-in back button to pop a controller off the stack and navigate to the previous screen.
Methods in Navigation Controllers The following are the methods in Navigation Controllers:
• SetViewControllers: This sets an array of child controllers. It has a value to optionally animate the transition. • ViewControllers: This is a property to get or set the array of child controllers without an option for animations. • PushViewController: This places a new child controller at the top of the stack with an option to display an animation. • PopViewControllerAnimated: This pops off the child controller at the top of the stack with an option to animate the transition. • PopToViewController: This pops to the specified child controller, removing all controllers above it. It provides an option to animate the transition. • PopToRootViewController: This removes all the child controllers except the bottom-most controller. It includes an option to display an animation. • TopViewController: This is a property that returns the child controller that is currently on top of the stack. It is important to note that using the option for animations will cause a crash if you try to modify the stack during the animation. To fix this situation, either use the SetViewControllers method and set the entire list of child controllers, or refrain from using the animations during a combination of transitions.
Setting up a Navigation Controller Perform the following steps to set up a Navigation Controller:
1. Double-click on the MainStoryboard.storyboard file to open it in Xamarin Studio. [ 93 ]
XamChat for iOS
2. Remove the controller that was created by the project template. 3. Drag a Navigation Controller element from the Toolbox pane on the right-hand side onto the storyboard. 4. Notice that a default View Controller element was created as well as a Navigation Controller element. 5. You will see a segue that connects the two controllers. We'll cover this concept in more detail later in the chapter. 6. Save the storyboard file. If you run the application at this point, you will have a basic iOS app with a status bar at the top, a navigation controller that contains a navigation bar with a default title, and a child controller that is completely white, as shown in the following screenshot:
[ 94 ]
Chapter 5
Implementing the login screen
Since the first screen of our application will be a login screen, let's begin by setting up the appropriate views in the storyboard file. We will implement the login screen using Xamarin Studio to write the C# code, and its iOS designer to create iOS layouts in our storyboard file.
Creating a LoginController class
Return to the project in Xamarin Studio and perform the following steps: 1. Double-click on the MainStoryboard.storyboard file to open it in the iOS designer. 2. Select your view controller and click on the Properties pane and select the Widget tab. 3. Enter LoginController into the Class field. 4. Notice that the LoginController class is generated for you. You can create a Controllers folder and move the file in it if you wish. The following screenshot shows you what the controller's settings will look like in Xamarin Studio after the changes have been made:
Modifying the controller's layout
Now let's modify the layout of the controller by performing the following steps: 1. Double-click on the MainStoryboard.storyboard file a second time to return to the iOS designer. 2. Tap on the navigation bar and edit the Title field to read Login. 3. Drag two text fields onto the controller. Position and size them appropriately for the username and password entries. You might also want to remove the default text to make the fields blank. [ 95 ]
XamChat for iOS
4. For the second field, check the Secure Text Entry checkbox. This will set the control to hide the characters for the password field. 5. You might also want to fill out the Placeholder field for Username and Password respectively. 6. Drag a button onto the controller. Set the button's Title to Login. 7. Drag an activity indicator onto the controller. Check the Animating and Hidden checkboxes. 8. Next, create an outlet for each of the controls by filling out the Name field. Name the outlets username, password, login, and indicator respectively. 9. Save the storyboard file and take a look at LoginController.designer.cs. You will see that Xamarin Studio has generated properties for each of the outlets:
Go ahead and compile the application to make sure that everything is okay. At this point, we also need to add a reference to the XamChat.Core project created in the previous chapter.
[ 96 ]
Chapter 5
Registering and subscribing view models and services Next, let's set up our iOS application to register all of its view models and other services that will be used throughout the application. We will use the ServiceContainer class that we created in Chapter 4, XamChat – a Cross-platform App, to set up the dependencies throughout our application. Open AppDelegate.cs and add the following method: public override bool FinishedLaunching( UIApplication application, NSDictionary launchOptions) { //View Models ServiceContainer.Register(() => new LoginViewModel()); ServiceContainer.Register(() => new FriendViewModel()); ServiceContainer.Register(() => new RegisterViewModel()); ServiceContainer.Register(() => new MessageViewModel()); //Models ServiceContainer.Register(() => new FakeSettings()); ServiceContainer.Register(() => new FakeWebService()); return true; }
Down the road, we will replace the fake services with the real ones. Now, let's add the login functionality to LoginController.cs. First, add LoginViewModel to a member variable at the top of the class as follows: readonly LoginViewModel loginViewModel = ServiceContainer.Resolve();
This will pull a shared instance of LoginViewModel into a local variable in the controller. This is the pattern that we will use throughout the module in order to pass a shared view model from one class to another.
[ 97 ]
XamChat for iOS
Next, override ViewDidLoad to hook up the view model's functionality with the views set up in outlets as follows: public override void ViewDidLoad() { base.ViewDidLoad(); login.TouchUpInside += async(sender, e) => { loginViewModel.Username = username.Text; loginViewModel.Password = password.Text; try { await loginViewModel.Login(); //TODO: navigate to a new screen } catch (Exception exc) { new UIAlertView("Oops!", exc.Message, null, "Ok").Show(); } }; }
We'll add the code to navigate to a new screen later in the chapter. Next, let's hook up the IsBusyChanged event to actually perform an action as follows: public override void ViewWillAppear(bool animated) { base.ViewWillAppear(animated); loginViewModel.IsBusyChanged += OnIsBusyChanged; } public override void ViewWillDisappear(bool animated) { base.ViewWillDisappear(animated); loginViewModel.IsBusyChanged -= OnIsBusyChanged; } void OnIsBusyChanged(object sender, EventArgs e) { username.Enabled = password.Enabled = login.Enabled = indicator.Hidden = !loginViewModel.IsBusy; }
[ 98 ]
Chapter 5
Now you might be wondering, why we subscribe to the event in this manner. The problem is that the LoginViewModel class will last through your application's lifetime, while the LoginController class will not. If we subscribed to the event in ViewDidLoad, but didn't unsubscribe later, then our application will have a memory leak. We also avoided using a lambda expression for the event since it would otherwise be impossible to unsubscribe the event. Note that we don't have the same problem with the TouchUpInside event on the button, since it will live in memory as long as the controller does. This is a common problem with events in C#, which is why it is a good idea to use the preceding pattern on iOS. If you run the application now, you should be able to enter a username and password, as shown in the following screenshot. When you press Login, you should see the indicator appear and all the controls disabled. Your application will correctly be calling the shared code, and should function correctly when we add a real web service.
[ 99 ]
XamChat for iOS
Using segues and UITableView
A segue is a transition from one controller to another. In the same way, a storyboard file is a collection of controllers and their views attached together by segues. This, in turn, allows you to see the layouts of each controller and the general flow of your application at the same time. There are just a few categories of segue, which are as follows: • Push: This is used within a navigation controller. It pushes a new controller to the top of the navigation controller's stack. Push uses the standard animation technique for navigation controllers and is generally the most commonly used segue. • Relationship: This is used to set a child controller of another controller. For example, the root controller of a navigation controller, container views, or split view controllers in an iPad application. • Modal: On using this, a controller presented modally will appear on top of the parent controller. It will cover the entire screen until dismissed. There are several types of different transition animations available. • Custom: This is a custom segue that includes an option for a custom class, which subclasses UIStoryboardSegue. This gives you fine-grained control over the animation and how the next controller is presented. Segues also use the following pattern while executing: • The destination controller and its views are created. • The segue object, a subclass of UIStoryboardSegue, is created. This is normally only important for custom segues. • The PrepareForSegue method is called on the source controller. This is a good place to run any custom code before a segue begins. • The segue's Perform method is called and the transition animation is started. This is where the bulk of the code resides for a custom segue. In the Xamarin.iOS designer, you have the choice of either firing a segue automatically from a button or table view row, or just giving the segue an identifier. In the second case, you can start the segue yourself by calling the PerformSegue method on the source controller using its identifier.
[ 100 ]
Chapter 5
Now, let's set up a new segue by setting up some aspects of our MainStoryboard. storyboard file by performing the following steps: 1. Double-click on the MainStoryboard.storyboard file to open it in the iOS designer. 2. Add a new Table View Controller to the storyboard. 3. Select your view controller and navigate to the Properties pane and the Widget tab. 4. Enter ConversationsController into the Class field. 5. Scroll down under the View Controller section and enter Title of Conversations. 6. Create a segue from LoginController to ConversationsController by clicking while holding Ctrl and dragging the blue line from one controller to the other. 7. Select the push segue from the popup that appears. 8. Select the segue by clicking on it and give it an Identifier of OnLogin. 9. Save the storyboard file. Your storyboard will look something similar to what is shown in the following screenshot:
[ 101 ]
XamChat for iOS
Open LoginController.cs, and modify the line of code that we marked as TODO earlier in this chapter as follows: PerformSegue("OnLogin", this);
Now if you build and run the application, you will navigate to the new controller after a successful log in. The segue will be performed, and you will see the built-in animation provided by the navigation controller. Next, let's set up the table view on the second controller. We are using a powerful class on iOS called UITableView. It is used in many situations and is very similar to the concept of a list view on other platforms. The UITableView class is controlled by another class called UITableViewSource. It has methods that you need to override to set up how many rows should exist and how these rows should be displayed on the screen. Note that UITableViewSource is a combination of UITableViewDelegate and UITableViewDataSource. I prefer to use UITableViewSource for simplicity, since many times using both of the other two classes would be required.
Before we jump in and start coding, let's review the most commonly used methods on UITableViewSource, which are as follows: • RowsInSection: This method allows you to define the number of rows in a section. All the table views have a number of sections and rows. By default, there is only one section; however, it is a requirement to return the number of rows in a section. • NumberOfSections: This is the number of sections in the table view. • GetCell: This method must return a cell for each row and should be implemented. It is up to the developer to set up how a cell should look like; you can also implement code to recycle the cells as you scroll. Recycling cells will yield better performance while scrolling. • TitleForHeader: This method, if overridden, is the simplest way to return a string for the title. Each section in a table view has a standard header view, by default. • RowSelected: This method will be called when the user selects a row. There are additional methods that you can override, but these will get you by in most situations. You can also set up custom headers and footers if you need to develop a custom styled table view.
[ 102 ]
Chapter 5
Now, let's open the ConversationsController.cs file, and create a nested class inside ConversationsController as follows: class TableSource : UITableViewSource { const string CellName = "ConversationCell"; readonly MessageViewModel messageViewModel = ServiceContainer.Resolve(); public override int RowsInSection( UITableView tableView, int section) { return messageViewModel.Conversations == null ? 0 : messageViewModel.Conversations.Length; } public override UITableViewCell GetCell( UITableView tableView, NSIndexPath indexPath) { var conversation = messageViewModel.Conversations[indexPath.Row]; var cell = tableView.DequeueReusableCell(CellName); if (cell == null) { cell = new UITableViewCell( UITableViewCellStyle.Default, CellName); cell.Accessory = UITableViewCellAccessory.DisclosureIndicator; } cell.TextLabel.Text = conversation.Username; return cell; } }
We implemented the two required methods to set up a table view: RowsInSection and GetCell. We returned the number of conversations found on the view model and set up our cell for each row. We also used UITableViewCellAccessory. DisclosureIndicator to add an indicator for the users to know that they can click on the row.
[ 103 ]
XamChat for iOS
Notice our implementation of recycling cells. Calling DequeueReusableCell with a cell identifier will return a null cell the first time around. If null, you should create a new cell using the same cell identifier. Subsequent calls to DequeueReusableCell will return an existing cell, enabling you to reuse it. You can also define the TableView cells in the storyboard file, which is useful for custom cells. Our cell here is very simple, so it is easier to define it from the code. Recycling cells is important on mobile platforms to preserve memory and provide the user with a very fluid scrolling table. Next, we need to set up the TableView source on TableView. Add some changes to our ConversationsController class as follows: readonly MessageViewModel messageViewModel = ServiceContainer.Resolve(); public override void ViewDidLoad() { base.ViewDidLoad(); TableView.Source = new TableSource(); } public async override void ViewWillAppear(bool animated) { base.ViewWillAppear(animated); try { await messageViewModel.GetConversations(); TableView.ReloadData(); } catch(Exception exc) { new UIAlertView("Oops!", exc.Message, null, "Ok").Show(); } }
So when the view appears, we will load our list of conversations. Upon completion of this task, we'll reload the table view so that it displays our list of conversations. If you run the application, you'll see a few conversations appear in the table view after logging in, as shown in the following screenshot. Down the road, everything will operate in the same manner when we load the conversations from a real web service.
[ 104 ]
Chapter 5
Adding a friends list screen
The next fairly important screen is that of our friends list. When creating a new conversation, the app will load a list of friends to start a conversation with. We'll follow a very similar pattern to load our list of conversations. To begin, we'll create UIBarButtonItem that navigates to a new controller named FriendsController by performing the following steps: 1. Double-click on the MainStoryboard.storyboard file to open it in the iOS designer. 2. Add a new Table View Controller to the storyboard. 3. Select your view controller and click on the Properties pane and make sure you have selected the Widget tab. 4. Enter FriendsController in the Class field. 5. Scroll down to the View Controller section, enter Friends in the Title field.
[ 105 ]
XamChat for iOS
6. Drag a Navigation Item from the Toolbox pane onto the ConversationsController. 7. Create a new Bar Button Item element and place it in the top-right corner of the new navigation bar. 8. In the Properties pane of the bar button, set its Identifier to Add. This will use the built-in plus button, which is commonly used throughout iOS applications. 9. Create a segue from Bar Button Item to the FriendsController by holding Ctrl and dragging the blue line from the bar button to the next controller. 10. Select the push segue from the popup that appears. 11. Save the storyboard file. Your changes to the storyboard should look something similar to what is shown in the following screenshot:
You will see a new FriendsController class that Xamarin Studio has generated for you. If you compile and run the application, you'll see the new bar button item we created. Clicking on it will take you to the new controller. [ 106 ]
Chapter 5
Now, let's implement UITableViewSource to display our friends list. Start with a new nested class inside FriendsController as follows: class TableSource : UITableViewSource { const string CellName = "FriendCell"; readonly FriendViewModel friendViewModel = ServiceContainer.Resolve(); public override int RowsInSection( UITableView tableView, int section) { return friendViewModel.Friends == null ? 0 : friendViewModel.Friends.Length; } public override UITableViewCell GetCell( UITableView tableView, NSIndexPath indexPath) { var friend = friendViewModel.Friends[indexPath.Row]; var cell = tableView.DequeueReusableCell(CellName); if (cell == null) { cell = new UITableViewCell( UITableViewCellStyle.Default, CellName); cell.AccessoryView = UIButton.FromType(UIButtonType.ContactAdd); cell.AccessoryView.UserInteractionEnabled = false; } cell.TextLabel.Text = friend.Username; return cell; } }
Just as before, we implemented table cell recycling and merely set the text on the label for each friend. We used cell.AccessoryView to indicate to the user that each cell is clickable and starts a new conversation. We disabled the user interaction on the button just to allow the row to be selected when the user clicks on the button. Otherwise, we'd have to implement a click event for the button. Next, we'll need to modify FriendsController in the same way as we did for conversations, as follows: readonly FriendViewModel friendViewModel = ServiceContainer.Resolve(); public override void ViewDidLoad() [ 107 ]
XamChat for iOS { base.ViewDidLoad(); TableView.Source = new TableSource(); } public async override void ViewWillAppear(bool animated) { base.ViewWillAppear(animated); try { await friendViewModel.GetFriends(); TableView.ReloadData(); } catch(Exception exc) { new UIAlertView("Oops!", exc.Message, null, "Ok").Show(); } }
This will function exactly as the conversations list. The controller will load the friends list asynchronously and refresh the table view. If you compile and run the application, you'll be able to navigate to the screen and view the sample friend list we created in Chapter 4, XamChat – a Cross-platform App, as shown in the following screenshot:
[ 108 ]
Chapter 5
Adding a list of messages
Now let's implement the screen to view a conversation or list of messages. We will try to model the screen after the built-in text message application on iOS. To do so, we will also cover the basics of how to create custom table view cells. To start, we'll need a new MessagesController class to perform the following steps: 1. Double-click on the MainStoryboard.storyboard file to open it in the iOS designer. 2. Add a new Table View Controller to the storyboard. 3. Select your view controller and click on the Properties pane and make sure you have selected the Widget tab. 4. Enter MessagesController in the Class field. 5. Scroll down to the View Controller section, enter Messages in the Title field. 6. Create a segue from ConversationsController to MessagesController by holding Ctrl and dragging the blue line from one controller to the other. 7. Select the push segue from the popup that appears. Enter the Identifier OnConversation in the Properties pane. 8. Now, create two Table View Cells in the table view in MessagesController. You can reuse the existing one created, by default. 9. Enter MyMessageCell and TheirMessageCell respectively into the Class field for each cell. 10. Set the Identifier to MyCell and TheirCell respectively on each cell. 11. Save the storyboard file. Xamarin Studio will generate three files: MessagesController.cs, MyMessageCell. cs, and TheirMessageCell.cs. You might decide to keep things organized by creating a Views folder and moving the cells into it. Likewise, you can move the controller to a Controllers folder. Now let's implement a base class for both these cells to inherit from: public class BaseMessageCell : UITableViewCell { public BaseMessageCell(IntPtr handle) : base(handle) { } public virtual void Update(Message message) { } } [ 109 ]
XamChat for iOS
We will override the Update method later and take the appropriate action for each cell type. We need this class to make things easier while interacting with both the types of cells from UITableViewSource. Now open MessagesController.cs and implement UITableViewSource inside a nested class, as follows: class TableSource : UITableViewSource { const string MyCellName = "MyCell"; const string TheirCellName = "TheirCell"; readonly MessageViewModel messageViewModel = ServiceContainer.Resolve(); readonly ISettings settings = ServiceContainer.Resolve(); public override int RowsInSection( UITableView tableview, int section) { return messageViewModel.Messages == null ? 0 : messageViewModel.Messages.Length; } public override UITableViewCell GetCell( UITableView tableView, NSIndexPath indexPath) { var message = messageViewModel.Messages [indexPath.Row]; bool isMyMessage = message.UserId == settings.User.Id; var cell = tableView.DequeueReusableCell(isMyMessage ? MyCellName : TheirCellName) as BaseMessageCell; cell.Update(message); return cell; } }
We added some logic to check whether a message is from a current user to decide on the appropriate table cell identifier. Since we have a base class for both cells, we can cast to BaseMessageCell and use its Update method. Now let's make the changes to our MessagesController file to load our list of messages and display them: readonly MessageViewModel messageViewModel = ServiceContainer.Resolve(); public override void ViewDidLoad() { [ 110 ]
Chapter 5 base.ViewDidLoad(); TableView.Source = new TableSource(); } public async override void ViewWillAppear(bool animated) { base.ViewWillAppear(animated); Title = messageViewModel.Conversation.Username; try { await messageViewModel.GetMessages(); TableView.ReloadData(); } catch (Exception exc) { new UIAlertView("Oops!", exc.Message, null, "Ok").Show(); } }
The only thing new here is where we set the Title property to the username of the conversation. To complete our custom cells, we will need to make more changes in Xcode by performing the following steps: 1. Double-click on the MainStoryboard.storyboard file to open it in the iOS designer. 2. Drag a new Label onto both the custom cells. 3. Use some creativity to style both labels. I chose to make the text in MyMessageCell blue and TheirMessageCell green. I set Alignment on the label to the right aligned in TheirMessageCell. 4. For the Name of each cell, enter message. 5. Save the storyboard file and return. Now add the following Update method to both MyMessageCell.cs and TheirMessageCell.cs: public partial class MyMessageCell : BaseMessageCell { public MyMessageCell (IntPtr handle) : base (handle) { } public override void Update(Message message) { [ 111 ]
XamChat for iOS this.message.Text = message.Text; } }
It is a bit strange to have duplicated the code for each cell, but it is the simplest approach to take advantage of the outlets Xamarin Studio generated based on the storyboard file. You could also have chosen to use the same class for both cells (even with a different layout in Xcode); however, you then lose the ability to have different code in each cell. If you run the application now, you will be able to view the messages list, as displayed in the following screenshot:
[ 112 ]
Chapter 5
Composing messages
For the final piece of our application, we need to implement some custom functionality that Apple doesn't provide with their APIs. We need to add a text field with a button that appears to be attached to the bottom of the table view. Most of this will require writing code and wiring up a lot of events. Let's begin by adding some new member variables to our MessagesController class as follows: UIToolbar toolbar; UITextField message; UIBarButtonItem send; NSObject willShowObserver, willHideObserver;
We will place the text field and bar buttons inside the toolbar, as shown in the following code. The NSObject fields will be an example of iOS's event system called notifications. We'll see how these are used shortly: public override void ViewDidLoad() { base.ViewDidLoad(); //Text Field message = new UITextField(new RectangleF(0, 0, 240, 32)) { BorderStyle = UITextBorderStyle.RoundedRect, ReturnKeyType = UIReturnKeyType.Send, ShouldReturn = _ => { Send(); return false; }, }; //Bar button item send = new UIBarButtonItem("Send", UIBarButtonItemStyle.Plain, (sender, e) => Send()); //Toolbar
[ 113 ]
XamChat for iOS toolbar = new UIToolbar( new RectangleF(0, TableView.Frame.Height - 44, TableView.Frame.Width, 44)); toolbar.Items = new UIBarButtonItem[] { new UIBarButtonItem(message), send }; NavigationController.View.AddSubview(toolbar); TableView.Source = new TableSource(); TableView.TableFooterView = new UIView( new RectangleF(0, 0, TableView.Frame.Width, 44)) { BackgroundColor = UIColor.Clear, }; }
Much of this work involves setting up a basic UI. It is not something we can do inside Xcode, because it's a custom UI in this case. We create a text field, bar button item, and toolbar from C# and add them to our navigation controller's view. This will display the toolbar at the top of the table view, no matter where it is scrolled to. Another trick we used was to add a footer view to the table view, which is of the same height as the toolbar. This will simplify some animations that we'll set up later. Now we will need to modify ViewWillAppear as follows: public async override void ViewWillAppear(bool animated) { base.ViewWillAppear(animated); Title = messageViewModel.Conversation.Username; //Keyboard notifications willShowObserver = UIKeyboard.Notifications.ObserveWillShow( (sender, e) => OnKeyboardNotification(e)); willHideObserver = UIKeyboard.Notifications.ObserveWillHide( (sender, e) => OnKeyboardNotification(e)); //IsBusy messageViewModel.IsBusyChanged += OnIsBusyChanged; try { await messageViewModel.GetMessages(); TableView.ReloadData(); [ 114 ]
Chapter 5 message.BecomeFirstResponder(); } catch (Exception exc) { new UIAlertView("Oops!", exc.Message, null, "Ok").Show(); } }
Most of these changes are straightforward, but notice our use of iOS notifications. Xamarin has provided a C# friendly way to subscribe to notifications. There is a static nested class named Notifications inside various UIKit classes that provide notifications. Otherwise, you would have to use the NSNotificationCenter class, which is not as easy to use. To unsubscribe from these events, we merely need to dispose NSObject that is returned. So let's add an override for ViewWillDisapper to clean up these events, as follows: public override void ViewWillDisappear(bool animated) { base.ViewWillDisappear(animated); //Unsubcribe notifications if (willShowObserver != null) { willShowObserver.Dispose(); willShowObserver = null; } if (willHideObserver != null) { willHideObserver.Dispose(); willHideObserver = null; } //IsBusy messageViewModel.IsBusyChanged -= OnIsBusyChanged; }
Next, let's set up our methods for these events, as follows: void OnIsBusyChanged (object sender, EventArgs e) { message.Enabled = send.Enabled = !messageViewModel.IsBusy; } void ScrollToEnd() {
[ 115 ]
XamChat for iOS TableView.ContentOffset = new PointF( 0, TableView.ContentSize.Height TableView.Frame.Height); } void OnKeyboardNotification (UIKeyboardEventArgs e) { //Check if the keyboard is becoming visible bool willShow = e.Notification.Name == UIKeyboard.WillShowNotification; //Start an animation, using values from the keyboard UIView.BeginAnimations("AnimateForKeyboard"); UIView.SetAnimationDuration(e.AnimationDuration); UIView.SetAnimationCurve(e.AnimationCurve); //Calculate keyboard height, etc. if (willShow) { var keyboardFrame = e.FrameEnd; var frame = TableView.Frame; frame.Height -= keyboardFrame.Height; TableView.Frame = frame; frame = toolbar.Frame; frame.Y -= keyboardFrame.Height; toolbar.Frame = frame; } else { var keyboardFrame = e.FrameBegin; var frame = TableView.Frame; frame.Height += keyboardFrame.Height; TableView.Frame = frame; frame = toolbar.Frame; frame.Y += keyboardFrame.Height; toolbar.Frame = frame; } //Commit the animation UIView.CommitAnimations(); ScrollToEnd(); }
[ 116 ]
Chapter 5
That is quite a bit of code, but not too difficult. OnIsBusyChanged is used to disable some of our views while it is loading. ScrollToEnd is a quick method to scroll the table view to the end. We need this for the sake of usability. Some math is required because Apple does not provide a built-in method for this. On the other hand, OnKeyboardNotification has quite a lot going on. We used the built-in animation system for iOS to set up an animation when the keyboard appears or hides. We use this to move views around for the onscreen keyboard. Using the animation system is quite easy; call UIView.BeginAnimations, modify some views, and then finish up with UIView.CommitAnimations. We also used a few more values from the keyboard to time our animation identically with the keyboard's animations. Last but not least, we need to implement a function to send a new message as follows: async void Send() { //Just hide the keyboard if they didn't type anything if (string.IsNullOrEmpty(message.Text)) { message.ResignFirstResponder(); return; } //Set the text, send the message messageViewModel.Text = message.Text; await messageViewModel.SendMessage(); //Clear the text field & view model message.Text = messageViewModel.Text = string.Empty; //Reload the table TableView.ReloadData(); //Hide the keyboard message.ResignFirstResponder(); //Scroll to end, to see the new message ScrollToEnd(); }
[ 117 ]
XamChat for iOS
This code is also fairly straightforward. After sending the message, we merely need to reload the table, hide the keyboard, and then make sure we scroll to the bottom to see the new message, as shown in the following screenshot. Using the async keyword makes this easy.
Summary
In this chapter, we covered the basic settings that Apple and Xamarin provide for developing iOS applications. This includes the Info.plist file and project options in Xamarin Studio. We covered UINavigationController, the basic building block for navigation in iOS applications, and implemented a login screen complete with username and password fields. Next, we covered iOS segues and the UITableView class. We implemented the friends list screen using UITableView, and the messages list screen, also using UITableView. Lastly, we added a custom UI functionality; a custom toolbar floating at the bottom of the messages list. After completing this chapter, you will have a partially functional iOS version of XamChat. You will have a deeper understanding of the iOS platform and tools, and fairly good knowledge to apply to building your own iOS applications. Take it upon yourself to implement the remaining screens that we did not cover in the chapter. If you get lost, feel free to review the full sample application included with this module. In the next chapter, we will develop the Android UI for XamChat using the native Android APIs. A lot of our steps will be very similar to what we did on iOS, and we will be working mainly with the View layer of the MVVM design pattern. [ 118 ]
XamChat for Android In this chapter, we will begin developing the Android UI for our XamChat sample application. We will use the native Android APIs directly to create our application and call into our shared portable class library similar to what we did on iOS. Similarly, our Xamarin.Android application will be indistinguishable from an Android application written in Java. To begin writing the Android version of XamChat, open the solution provided in the previous chapters, and create a new Android Application project. Name the project XamChat.Droid or some other appropriate name of your choice. In this chapter, we will cover: • The Android Manifest • Writing a login screen for XamChat • Android's ListView and BaseAdapter • Adding a friends list • Adding a list of messages
XamChat for Android
Introducing Android Manifest
All Android applications have an XML file called the Android Manifest, which declares the basic information about the app such as the application version and name, and is named AndroidManifest.xml. This is very similar to the Info.plist file on iOS, but Android puts much more emphasis on its importance. A default project doesn't have a manifest, so let's begin by creating one by navigating to Project Options | Android Application and clicking on Add Android Manifest. Several new settings for your application will appear.
Setting up the Manifest
The most important settings, shown in the following screenshot, are as follows: • Application name: This is the title of your application, which is displayed below the icon. It is not the same as the name selected on Google Play. • Package name: This is similar to that on iOS; it's your app's bundle identifier or bundle ID. It is a unique name used to identify your application. The convention is to use the reverse domain style with your company name at the beginning; for example, com.packt.xamchat. It must begin with a lower case letter and contain at least one "." character within. • Application icon: This is the icon displayed for your app on Android's home screen. • Version number: This is a one-digit number that represents the version of your application. Raising this number indicates a newer version on Google Play. • Version name: This is a user-friendly version string for your app that users will see in settings and on Google Play; for example, 1.0.0. • Minimum Android version: This is the minimum version of Android that your application supports. In modern Android apps, you can generally target Android 4.0, but this is a decision based on your application's core audience. • Target Android version: This is the version of the Android SDK your application is compiled against. Using higher numbers gives you access to new APIs, however, you might need to do some checks to call these APIs on newer devices. • Install Location: This defines the different locations your Android application can be installed to: auto (user settings), external (SD card), or internal (device internal memory).
[ 120 ]
Chapter 6
Common manifest permissions
In addition to these settings, there is a set of checkboxes labeled Required permissions. These are displayed to users on Google Play prior to the application being installed. This is Android's way of enforcing a level of security, giving users a way to see what kinds of access an app will have to make changes to their device. The following are some commonly used manifest permissions: • Camera: This provides access to the device camera • Internet: This provides access to make web requests over the Internet • ReadContacts: This provides access to read the device's contacts library • ReadExternalStorage: This provides access to read the SD card • WriteContacts: This provides access to modify the device's contacts library • WriteExternalStorage: This provides access to write to the SD card
[ 121 ]
XamChat for Android
In addition to these settings, a manual change to Android Manifest will be required many times. In this case, you can edit the manifest file as you would edit a standard XML file in Xamarin Studio. For a complete list of valid XML elements and attributes, visit http://developer.android.com/guide/topics/manifest/ manifest-intro.html. Now let's fill out the following settings for our application: • Application name: XamChat • Package name: com.yourcompanyname.xamchat; make sure to name future apps beginning with com.yourcompanyname • Version number: Just start with the number 1 • Version: This can be any string, but it is recommended to use something that resembles a version number • Minimum Android version: Select Android 4.0.3 (API Level 15) • Required permissions: Select Internet; we will be using it later At this point, we need to reference our shared code from our portable class library we created in Chapter 4, XamChat – a Cross-platform App. Right-click on the References folder for the project, then click on Edit References..., and add a reference to the XamChat.Core project. You will now be able to access all the shared code that was written in Chapter 4, XamChat – a Cross-platform App. Go to the Resources directory, and in the values folder, open Strings.xml; this is where all the text throughout your Android app should be stored. This is an Android convention that will make it very easy to add multiple languages to your application. Let's change our strings to the following:
XamChat Oops! Loading
We'll use these values later in the chapter. Feel free to add new ones in cases where you display the text to the user. If you need to add more languages, it is very straightforward; you can see the Android documentation on this subject at
http://developer.android.com/guide/topics/resources/localization.html.
[ 122 ]
Chapter 6
Creating and implementing the application class
Now let's implement our main application class; add a new Activity from the New File dialog. We won't be subclassing Activity in this file, but this template adds several Android using statements to the top of the file that imports the Android APIs to be used within your code. Create a new Application class where we can register everything in our ServiceContainer as follows: [Application(Theme = "@android:style/Theme.Holo.Light")] public class Application : Android.App.Application { public Application( IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { } public override void OnCreate() { base.OnCreate(); //ViewModels ServiceContainer.Register( () => new LoginViewModel()); ServiceContainer.Register( () => new FriendViewModel()); ServiceContainer.Register( () => new MessageViewModel()); ServiceContainer.Register( () => new RegisterViewModel()); //Models ServiceContainer.Register( () => new FakeSettings()); ServiceContainer.Register( () => new FakeWebService()); } }
[ 123 ]
XamChat for Android
We used the built-in Android theme, Theme.Holo.Light, just because it is a neat theme that matches the default style we used on iOS. Note the strange, empty constructor we have to create for this class to function. This is a current requirement of a custom Application class in Xamarin. You can just recognize this as boilerplate code, and you will need to add this in this case. Now let's implement a simple base class for all the activities throughout our app. Create an Activities folder in the XamChat.Droid project and a new file named BaseActivity.cs with the following content: [Activity] public class BaseActivity : Activity where TViewModel : BaseViewModel { protected readonly TViewModel viewModel; protected ProgressDialog progress; public BaseActivity() { viewModel = ServiceContainer.Resolve(typeof(TViewModel)) as TViewModel; } protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); progress = new ProgressDialog(this); progress.SetCancelable(false); progress.SetTitle(Resource.String.Loading); } protected override void OnResume() { base.OnResume(); viewModel.IsBusyChanged += OnIsBusyChanged; } protected override void OnPause() { base.OnPause(); viewModel.IsBusyChanged -= OnIsBusyChanged; } void OnIsBusyChanged (object sender, EventArgs e) { if (viewModel.IsBusy)
[ 124 ]
Chapter 6 progress.Show(); else progress.Hide(); } }
We did several things here to simplify the development of our other activities. First, we made this class generic, and made a protected variable named viewModel to store a ViewModel of a specific type. Note that we did not use generics on controllers in iOS due to platform limitations (see more on Xamarin's documentation website at http:// docs.xamarin.com/guides/ios/advanced_topics/limitations/). We also implemented IsBusyChanged, and displayed a simple ProgressDialog function with the Loading string from the Strings.xml file to indicate the network activity. Let's add one more method to display errors to the user, as follows: protected void DisplayError(Exception exc) { string error = exc.Message; new AlertDialog.Builder(this) .SetTitle(Resource.String.ErrorTitle) .SetMessage(error) .SetPositiveButton(Android.Resource.String.Ok, (IDialogInterfaceOnClickListener)null) .Show(); }
This method will display a pop-up dialog indicating that something went wrong. Notice that we also used ErrorTitle and the built-in Android resource for an Ok string. This will complete the core setup for our Android application. From here, we can move on to implement the UI for the screens throughout our app.
Adding a login screen
Before creating Android views, it is important to know the different layouts or view group types available in Android. iOS does not have an equivalent for some of these because iOS has a very small variation of screen sizes on its devices. Since Android has virtually infinite screen sizes and densities, the Android SDK has a lot of built-in support for auto-sizing and layout for views.
[ 125 ]
XamChat for Android
Layouts and ViewGroups in Andorid The following are the common types of layouts:
• ViewGroup: This is the base class for a view that contains a collection of child views. You normally won't use this class directly. • LinearLayout: This is a layout that positions its child views in rows or columns (but not both). You can also set weights on each child to have them span different percentages of the available space. • RelativeLayout: This is a layout that gives much more flexibility on the position of its children. You can position child views relative to each other so that they are above, below, to the left, or to the right of one another. • FrameLayout: This layout positions its child views directly on top of one another in the z order on the screen. This layout is best used for cases where you have a large child view that needs other views on top of it and perhaps docked to one side. • ListView: This displays views vertically in a list with the help of an adapter class that determines the number of child views. It also has support for its children to be selected. • GridView: This displays views in rows and columns within a grid. It also requires the use of an adapter class to supply the number of children. Before we begin writing the login screen, delete the Main.axml and MainActivity. cs files that were created from the Android project template, as they are not useful for this application. Next, create an Android layout file named Login.axml in the layout folder of the Resources directory in your project. Now we can start adding functionalities to our Android layout as follows: 1. Double-click on the Login.axml file to open the Android designer. 2. Drag two Plain Text views onto the layout found in the Text Fields section. 3. In the Id field, enter @+id/username and @+id/password respectively. This is a step that you will take for any control you want to work with from C# code. 4. For the password field, set its Input Type property to textPassword. 5. Drag a Button onto the layout and set its Text property to Login. 6. Set the button's Id property to @+id/login. We will be using this control from code.
[ 126 ]
Chapter 6
Your layout will look something like what is shown in the following screenshot when complete:
Implementing the login functionality
Now create a new Android Activity file named LoginActivity.cs in the Activites folder we created earlier. We will use this as the main activity that starts when the application runs. Let's implement the login functionality as follows: [Activity(Label = "@string/ApplicationName", MainLauncher = true)] public class LoginActivity : BaseActivity { EditText username, password; Button login; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Login);
[ 127 ]
XamChat for Android username = FindViewById(Resource.Id.username); password = FindViewById(Resource.Id.password); login = FindViewById(Resource.Id.login); login.Click += OnLogin; } protected override void OnResume() { base.OnResume(); username.Text = password.Text = string.Empty; } async void OnLogin (object sender, EventArgs e) { viewModel.Username = username.Text; viewModel.Password = password.Text; try { await viewModel.Login(); //TODO: navigate to a new activity } catch (Exception exc) { DisplayError(exc); } } }
Notice that we set MainLauncher to true to make this activity the first activity for the application. In some apps, a splash screen is used as the first activity, so keep this in mind if you need to add a splash screen. We also took advantage of the ApplicationName value and the BaseActivity class we set up earlier in the chapter. We also overrode OnResume to clear out the two EditText controls so that the values are cleared out if you return to the screen. Now if you launch the application, you will be greeted by the login screen we just implemented, as shown in the following screenshot:
[ 128 ]
Chapter 6
Using ListView and BaseAdapter
Now let's implement a conversations list on Android. The Android equivalent of the UITableView and UITableViewSource iOS classes are ListView and BaseAdapter. There are parallel concepts for these Android classes, such as implementing abstract methods and recycling cells during scrolling. There are a few different types of adapters used in Android such as ArrayAdapter or CursorAdaptor, although BaseAdapter is generally best suited for simple lists.
Implementing the conversations screen
Let's implement our conversations screen. Let's begin by making a new Android Activity in your Activities folder named ConversationsActivity.cs. Let's start with only a couple of changes to the class definition as follows: [Activity(Label = "Conversations")] public class ConversationsActivity : BaseActivity { //Other code here later }
[ 129 ]
XamChat for Android
Perform the following steps to implement a couple of Android layouts: 1. Create a new Android layout in the layout folder of the Resources directory named Conversations.axml. 2. Drag a ListView control from Toolbox onto the layout, and set its Id to @+id/conversationsList. 3. Create a second Android layout in the layout folder in the Resources directory named ConversationListItem.axml. 4. Drag a Text (Medium) and a Text (Small) control onto the layout from the Toolbox pane. 5. Set their IDs to @+id/conversationUsername and @+id/ conversationLastMessage. 6. Finally, let's set each of their Margins to 3dp in the Layout tab of the Properties box. This will set up all the layout files we'll need to use throughout the conversations screen. Your ConversationListItem.axml layout will look something like what's shown in the following screenshot:
[ 130 ]
Chapter 6
Now we can implement BaseAdapter as a nested class inside ConversationsActivity as follows: class Adapter : BaseAdapter { readonly MessageViewModel messageViewModel = ServiceContainer.Resolve(); readonly LayoutInflater inflater; public Adapter(Context context) { inflater = (LayoutInflater)context.GetSystemService ( Context.LayoutInflaterService); } public override long GetItemId(int position) { //This is an abstract method, just a simple implementation return position; } public override View GetView( int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflater.Inflate ( Resource.Layout.ConversationListItem, null); } var conversation = this [position]; var username = convertView.FindViewById( Resource.Id.conversationUsername); var lastMessage = convertView.FindViewById( Resource.Id.conversationLastMessage); username.Text = conversation.Username; lastMessage.Text = conversation.LastMessage; return convertView; } public override int Count { get { return messageViewModel.Conversations == null ? 0 : messageViewModel.Conversations.Length; } } public override Conversation this[int index] { get { return messageViewModel.Conversations [index]; } } } [ 131 ]
XamChat for Android
The following is a review of what is going on inside the adapter: 1. We subclassed BaseAdapter. 2. We passed in Context (our activity) so that we can pull out LayoutInflater. This class enables you to load XML layout resources and inflate them into a view object. 3. We implemented GetItemId. This is a general method used to identify rows, but we just returned the position for now. 4. We set up GetView, which recycles the convertView variable by only creating a new view if it is null. We also pulled out the text views in our layout to set their text. 5. We overrode Count to return the number of conversations. 6. We implemented an indexer to return a Conversation object for a position. Overall, this should be fairly similar to what we did on iOS.
Setting up the adapter
Now let's set up the adapter in our activity by adding the following to the body of ConversationsActivity: ListView listView; Adapter adapter; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Conversations); listView = FindViewById( Resource.Id.conversationsList); listView.Adapter = adapter = new Adapter(this); } protected async override void OnResume() { base.OnResume(); try { await viewModel.GetConversations(); adapter.NotifyDataSetInvalidated(); }
[ 132 ]
Chapter 6 catch (Exception exc) { DisplayError(exc); } }
This code will set up the adapter and reload our list of conversations when the activity appears on the screen. Note that we called NotifyDataSetInvalidated here so that ListView reloads its rows after the number of conversations has been updated. This is parallel to what we did on iOS by calling the UITableView's ReloadData method. Last but not least, we need to modify the OnLogin method we set up earlier in LoginActivity to start our new activity as follows: StartActivity(typeof(ConversationsActivity));
Now if we compile and run our application, we can navigate to a conversations list after logging in, as shown in the following screenshot:
[ 133 ]
XamChat for Android
Implementing the friends list
Before we start implementing the friends list screen, we must first add a menu item to ActionBar in our application. Let's begin by creating a new menu folder within the Resources folder of our project. Next, create a new Android layout file named ConversationsMenu.axml. Remove the default layout created by XML, and replace it with the following:
We set up a root menu with one menu item inside it. The following is a breakdown of what we set for the item in XML: • android:id: We will use this later in C# to reference the menu item with Resource.Id.addFriendMenu. • android:icon: This is an image resource used to display the menu item. We used a built-in Android one for a generic plus icon. • android:showAsAction: This will make the menu item visible if there is room for the item. If for some reason the device's screen is too narrow, an overflow menu would be displayed for the menu item. Now we can make some changes in ConversationsActivity.cs to display the menu item as follows: public override bool OnCreateOptionsMenu(IMenu menu) { MenuInflater.Inflate(Resource.Menu.ConversationsMenu, menu); return base.OnCreateOptionsMenu(menu); }
This code will take our layout and apply it to the menu at the top in our activity's action bar. Next, we can add some code to be run when the menu item is selected as follows: public override bool OnOptionsItemSelected(IMenuItem item) { if (item.ItemId == Resource.Id.addFriendMenu) {
[ 134 ]
Chapter 6 //TODO: launch the next activity } return base.OnOptionsItemSelected(item); }
Now let's implement the next activity. Let's begin by making a copy of Conversations.axml found in the layout folder in the Resources directory and rename it Friends.axml. The only change we'll make in this file will be to rename the ListView's ID to @+id/friendsList. Next, perform the following steps to create a layout that can be used for the list items in ListView: 1. Make a new Android layout called FriendListItem.axml. 2. Open the layout and switch to the Source tab found at the bottom of the screen. 3. Change the root LinearLayout XML element to a RelativeLayout element. 4. Switch back to the Content tab found at the bottom of the screen. 5. Drag a Text (Large) control from the Toolbox pane onto the layout and set its Id to @+id/friendName. 6. Drag an ImageView control from the Toolbox pane onto the layout; you can either let its Id be its default value or blank it out. 7. Change the image view's image to @android:drawable/ic_menu_add. This is the same plus icon we used earlier in the chapter. You can select it from the Resources dialog under the Framework Resources tab. 8. Set the Width and Height of both the controls to wrap_content. This is found under the Layout tab under the ViewGroup section. 9. Next, check the value for Align Parent Right on just the image view. 10. Finally, set the Margins of both the controls to 3dp in the Layout tab of the Properties box. Using the Xamarin designer can be very productive, but some developers prefer a higher level of control. You might consider writing the XML code yourself as an alternative, which is fairly straightforward, as shown in the following code:
[ 135 ]
XamChat for Android
Since we now have all the layouts we need for the new screen, let's create an Android Activity in the Activities folder named FriendsActivity.cs. Let's create the basic definition of the activity as follows, just like we did before: [Activity(Label = "Friends")] public class FriendsActivity : BaseActivity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); } }
Now, let's implement a nested Adapter class to set up the list view items as follows: class Adapter : BaseAdapter { readonly FriendViewModel friendViewModel = ServiceContainer.Resolve(); readonly LayoutInflater inflater; public Adapter(Context context) { inflater = (LayoutInflater)context.GetSystemService ( Context.LayoutInflaterService); } public override long GetItemId(int position) { return position; }
[ 136 ]
Chapter 6 public override View GetView( int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflater.Inflate( Resource.Layout.FriendListItem, null); } var friend = this [position]; var friendname = convertView.FindViewById( Resource.Id.friendName); friendname.Text = friend.Username; return convertView; } public override int Count { get { return friendViewModel.Friends == null ? 0 : friendViewModel.Friends.Length; } } public override User this[int index] { get { return friendViewModel.Friends [index]; } } }
There is really no difference in this adapter and the previous one we implemented for the conversations screen. We only have to set the friend's name, and we use the User object instead of the Conversation object. To finish setting up the adapter, we can update the body of the FriendsActivity class as follows: ListView listView; Adapter adapter; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Friends); listView = FindViewById(Resource.Id.friendsList); listView.Adapter = adapter = new Adapter(this); } protected async override void OnResume() { [ 137 ]
XamChat for Android base.OnResume(); try { await viewModel.GetFriends(); adapter.NotifyDataSetInvalidated(); } catch (Exception exc) { DisplayError(exc); } }
Last but not least, we can update OnOptionsItemSelected in the ConversationsActivity class as follows: public override bool OnOptionsItemSelected(IMenuItem item) { if (item.ItemId == Resource.Id.addFriendMenu) { StartActivity(typeof(FriendsActivity)); } return base.OnOptionsItemSelected(item); }
So, if we compile and run the application, we can navigate to a fully implemented friends list screen, as shown in the following screenshot:
[ 138 ]
Chapter 6
Composing messages
The next screen is a bit more complicated. We will need to create a ListView that uses multiple layout files for each row, depending on the type of the row. We'll also need to perform some layout tricks to place a view below the ListView and set up the ListView to autoscroll. For the next screen, let's begin by creating a new layout named Messages.axml in the layout folder of the Resources directory and then perform the following steps: 1. Drag a new ListView onto the layout. Set its Id to @+id/messageList. 2. Check the box for Stack From Bottom, and set Transcript Mode to alwaysScroll. This will set it up in order to display items from the bottom up. 3. Set the Weight value to 1 for the ListView in the Layout tab under the LinearLayout section. 4. Drag a new RelativeLayout onto the layout. Let its Id be the default value, or remove it. 5. Drag a new Button inside RelativeLayout. Set its Id to @+id/sendButton. 6. Check the box for Align Parent Right in the Layout tab. 7. Drag a new Plain Text found in the Text Field section inside RelativeLayout to the left of the button. Set its Id to @+id/messageText. 8. In the Layout tab, set To Left Of to @+id/sendButton, and set its Width to match_parent. 9. Check the box for Center in Parent to fix the vertical centering. When completed, the XML file will be as follows:
[ 139 ]
XamChat for Android
Next, perform the following steps to make two more Android layouts: 1. Create a new layout named MyMessageListItem.axml in the layout folder of the Resources directory. 2. Open the layout and switch to the Source tab. Change the root XML element to a RelativeLayout element. 3. Switch back to the Content tab, and drag two TextView controls onto the layout. 4. In the Id field, enter @+id/myMessageText and @+id/myMessageDate respectively. 5. For both the views, set Margin to 3dp, and Width and Height to wrap_content. 6. For the first TextView, set its Color to @android:color/holo_blue_bright under the Style tab. 7. For the second TextView, check the Align Parent Right checkbox under the Layout tab. 8. Create a new layout named TheirMessageListItem.axml and repeat the process. Select a different color for the first TextView in the new layout.
[ 140 ]
Chapter 6
Finally, we'll need to create a new activity for the screen. Create a new Android Activity named MessagesActivity.cs in the Activities directory. Let's begin with the standard code to set up an activity as follows: [Activity(Label = "Messages")] public class MessagesActivity : BaseActivity { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); } }
Next, let's implement a more complicated adapter than what we implemented earlier as follows: class Adapter : BaseAdapter { readonly MessageViewModel messageViewModel = ServiceContainer.Resolve(); readonly ISettings settings = ServiceContainer.Resolve(); readonly LayoutInflater inflater; const int MyMessageType = 0, TheirMessageType = 1; public Adapter (Context context) { inflater = (LayoutInflater)context.GetSystemService ( Context.LayoutInflaterService); } public override long GetItemId(int position) { return position; } public override int Count { get { return messageViewModel.Messages == null ? 0 : messageViewModel.Messages.Length; } } public override Message this[int index] { get { return messageViewModel.Messages [index]; }
[ 141 ]
XamChat for Android } public override int ViewTypeCount { get { return 2; } } public override int GetItemViewType(int position) { var message = this [position]; return message.UserId == settings.User.Id ? MyMessageType : TheirMessageType; } }
This includes everything except our implementation of GetView, which we'll get to shortly. Here, the first changes are some constants for MyMessageType and TheirMessageType. We then implemented ViewTypeCount and GetItemViewType. This is Android's mechanism for using two different layouts for list items in a list view. We use one type of layout for the user's messages and a different one for the other user in the conversation. Next, let's implement GetView as follows: public override View GetView( int position, View convertView, ViewGroup parent) { var message = this [position]; int type = GetItemViewType(position); if (convertView == null) { if (type == MyMessageType) { convertView = inflater.Inflate( Resource.Layout.MyMessageListItem, null); } else { convertView = inflater.Inflate( Resource.Layout.TheirMessageListItem, null); } } TextView messageText, dateText; if (type == MyMessageType) { messageText = convertView.FindViewById( Resource.Id.myMessageText); [ 142 ]
Chapter 6 dateText = convertView.FindViewById( Resource.Id.myMessageDate); } else { messageText = convertView.FindViewById( Resource.Id.theirMessageText); dateText = convertView.FindViewById( Resource.Id.theirMessageDate); } messageText.Text = message.Text; dateText.Text = message.Date.ToString("MM/dd/yy HH:mm"); return convertView; }
Let's break down our implementation through the following steps: 1. We first pull out the message object for the position of the row. 2. Next, we grab the view type that determines whether it is the current user's message or the other user in the conversation. 3. If convertView is null, we inflate the appropriate layout based on the type. 4. Next, we pull the two text views, messageText and dateText, out of the convertView. We have to use the type value to make sure that we use the correct resource IDs. 5. We set the appropriate text on both the text views using the message object. 6. We return convertView. Now let's finish MessagesActivity by setting up the rest of the adapter. First, let's implement some member variables and the OnCreate method as follows: ListView listView; EditText messageText; Button sendButton; Adapter adapter; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); Title = viewModel.Conversation.Username; SetContentView(Resource.Layout.Messages); listView = FindViewById(Resource.Id.messageList); messageText = FindViewById(Resource.Id.messageText); [ 143 ]
XamChat for Android sendButton = FindViewById(Resource.Id.sendButton); listView.Adapter = adapter = new Adapter(this); sendButton.Click += async (sender, e) => { viewModel.Text = messageText.Text; try { await viewModel.SendMessage(); messageText.Text = string.Empty; adapter.NotifyDataSetInvalidated(); listView.SetSelection(adapter.Count); } catch (Exception exc) { DisplayError(exc); } }; }
So far this activity is fairly standard compared to our previous activities in this chapter. We also had to wire up the Click event of sendButton in OnCreate so that it sends the message and refreshes the list. We also used a trick to scroll the list view to the end by setting its selection to the last item. Next, we'll need to implement OnResume to load the messages, invalidate the adapter, and then scroll the list view to the end as follows: protected async override void OnResume() { base.OnResume(); try { await viewModel.GetMessages(); adapter.NotifyDataSetInvalidated(); listView.SetSelection(adapter.Count); } catch (Exception exc) { DisplayError(exc); } }
[ 144 ]
Chapter 6
So finally, if you compile and run the app, you will be able to navigate to the messages screen, and add new messages to the list, as shown in the following screenshot:
Summary
In this chapter, we started out by going over the basic settings in the Android Manifest file. Next, we implemented a custom Application class to set up our ServiceContainer. We then went over the different types of Android layouts and implemented a login screen using native Android views. We implemented the friends list screen, and learned about the basics of ListView and adapters. Finally, we implemented the messages screen, and used the more advanced functionality available in list view adapters and layouts. After completing this chapter, you will have a partially functional Android version of XamChat. You will have gained a deeper understanding of the Android SDK and tools. You should be confident in developing your own Android applications using Xamarin. Take it upon yourself to implement the remaining screens that we did not cover in the chapter. If you get lost, feel free to review the full sample application included with this module. In the next chapter, we'll cover how to deploy to mobile devices and why is it very important to test your applications on real devices. [ 145 ]
Deploying and Testing on Devices Deploying to devices is both important and a bit of a hassle when you try it the first time. Testing on a device will commonly display performance issues that aren't present in the simulator/emulator of your application. You can also test things that are only possible on real devices such as GPS, camera, memory limitations, or cellular network connectivity. There are also common pitfalls that exist when developing for Xamarin, which will only surface when testing on a real device. In this chapter, we will cover the following topics: • iOS provisioning • Android device settings for debugging • The linker • Ahead-of-time (AOT) compilation • Common memory pitfalls with Xamarin Before we begin this chapter, it is important to note that an iOS Developer Program membership is required to deploy to iOS devices. Feel free to go back to Chapter 1, Setting Up Xamarin, to walk through this process.
iOS provisioning
Apple has a strict process for deploying applications to iOS devices. While being quite convoluted and sometimes painful for developers, Apple can enable a certain level of security by preventing the average user from side loading potentially malicious applications.
Deploying and Testing on Devices
Prerequisites for deploying to iOS
Before we can deploy our application to an iOS device, there are a few things we will need to set up in the iOS Dev Center. We will begin by creating an App ID or a bundle ID for your account. This is the primary identifier for any iOS application. We will begin by navigating to http://developer.apple.com and performing the following steps: 1. Click on the iOS Apps icon. 2. Sign in with your developer account. 3. Click on Certificates, Identifiers, & Profiles on the right-hand side navigation. 4. Click on Identifiers. 5. Click on the plus button to add a new iOS App ID. 6. In the Name field, enter something meaningful such as YourCompanyNameWildcard. 7. Select the Wildcard App ID radio button. 8. In the Bundle ID field, select a reverse domain styled name for your company such as com.yourcompanyname.*. 9. Click on Continue. 10. Review the final setting and hit Submit. Leave this web page open, as we will be using it throughout the chapter. We just registered a wildcard bundle ID for your account; use this as a prefix for all future applications you wish to identify with this account. Later, when you are preparing to deploy an app to the Apple App Store, you will create an Explicit App ID such as com.yourcompanyname.yourapp. This allows you to deploy the specific app to the store, while the wildcard ID is best used for deploying to devices for testing. Next, we need to locate the unique identifier on each device you plan on debugging your application on. Apple requires each device to be registered under your account with a limit of 200 devices per developer. The only way to circumvent this requirement is to register for the iOS Developer Enterprise program with a $299 yearly fee that is separate from the standard $99 developer fee.
[ 148 ]
Chapter 7
We will begin by launching Xcode and performing the following steps: 1. Navigate to Window | Devices in the top menu. 2. Plug in your target device with a USB cable. 3. On the left-hand side navigation, you should see your device's name. Click on it to select it. 4. Notice the Identifier value for your device. Copy it to your clipboard. The following screenshot shows what your screen should look like with your device selected in Xcode:
Return to http://developer.apple.com (hopefully, it is still open from earlier in the chapter) and perform the following steps: 1. Navigate to Devices | All on the left-hand side navigation. 2. Click on the plus button in the top-right corner of the page. 3. Enter a meaningful name for your device and paste the Identifier value from your clipboard into the UDID field. 4. Click on Continue. 5. Review the information you entered and hit Register. Down the road, when your account is fully set up, you can just click on the Use for Development button in Xcode and skip the second set of steps.
[ 149 ]
Deploying and Testing on Devices
The following screenshot shows you what your device list should look like when complete:
Next, we will need to generate a certificate to represent you as the developer for your account. Prior to Xcode 5, you had to create a certificate signing request using the Keychain app on your Mac. You can find this under Applications, or using the search spotlight on OS X via Command + Space. The newer versions of Xcode make things a lot easier by integrating a lot of this process into Xcode. Open Xcode and perform the following steps: 1. Navigate to Xcode | Preferences in the menu at the top. 2. Select the Accounts tab. 3. Click on the plus button, on the bottom-left, and then click on Add Apple ID. 4. Enter the e-mail and password for your developer account. 5. On creating the account, click on View Details on the bottom-right. 6. Click on the sync button on the bottom-left. 7. If this is a new account, Xcode will display a warning that no certificates exist yet. Check each box and click on Request to generate the certificates. Xcode will now automatically create a developer certificate for your account and install it into your Mac's keychain. The following screenshot shows what your screen will look after setting up your account:
[ 150 ]
Chapter 7
Creating a provisioning profile
Next, we need to create a provisioning profile. This is the final file that allows applications to be installed on an iOS device. A provisioning profile contains an App ID, a list of device IDs, and finally, a certificate for the developer. You must also have the private key of the developer certificate in your Mac's keychain to use a provisioning profile. The following are a few types of provisioning profiles: • Development: This is used for debug or release builds. You will actively use this type of profile when your applications are in development. • Ad Hoc: This is used mainly for release builds. This type of certificate is great for beta testing or distribution to a small set of users. You can distribute to an unlimited number of users using this method with an enterprise developer account.
[ 151 ]
Deploying and Testing on Devices
• App Store: This is used for release builds for submission to the App Store. You cannot deploy an app to your device using this certificate; it can only be used for store submission. Let's return to http://developer.apple.com and create a new provisioning profile by performing the following steps: 1. Navigate to Provisioning Profiles | All on the left-hand side pane. 2. Click on the plus button at the top-right of the page. 3. Select iOS App Development and click on Continue. 4. Select your wildcard App ID created earlier in the chapter and click on Continue. 5. Select the certificate we created earlier in the chapter and click on Continue. 6. Select the devices you want to deploy to and click on Continue. 7. Enter an appropriate Profile Name such as YourCompanyDev. 8. Click on Generate and your provisioning profile will be created. The following screenshot shows the new profile that you will end up with on creation. Don't worry about downloading the file; we'll use Xcode to import the final profile.
[ 152 ]
Chapter 7
To import the provisioning profile, return to Xcode and perform the following steps: 1. Navigate to Xcode | Preferences in the menu at the top of the dialog. 2. Select the Accounts tab. 3. Select your account and click on View Details. 4. Click on the sync button on the bottom-left. 5. After a few seconds, your provisioning profiles will appear. Xcode will automatically include any provisioning profiles you have created on the Apple developer site. Xcode will also create a few profiles on its own. In the latest version of Xamarin Studio, you can view these profiles but will not be able to sync them. Navigate to Xamarin Studio | Preferences | Developer Accounts to view the provisioning profiles from Xamarin Studio. You can also view Xamarin's documentation on iOS provisioning on their documentation website at http://docs. xamarin.com/guides/ios/getting_started/device_provisioning/.
Android device settings
Compared to the hassle of deploying your application on iOS devices, Android is a breeze. To deploy an application to a device, you merely have to set a few settings on the device. This is due to Android's openness in comparison to iOS. Android device debugging is turned off for most users, but it can be easily turned on by any developer who might wish to have a go at writing Android applications. We will begin by opening the Settings application. You might have to locate this by looking through all the applications on the device as follows: 1. Scroll down and click on the section labeled Developer options. 2. In the action bar at the top, you might have to toggle a switch to the ON position. This varies on each device. 3. Scroll down and check USB Debugging. 4. A warning confirmation message will appear. Then, click on OK. Note that some newer Android devices have made it a little more difficult for the average user to turn on USB debugging. You have to click on the Developer options item seven times to turn this option on.
[ 153 ]
Deploying and Testing on Devices
The following screenshot shows what your device will look like during the process:
After enabling this option, all you have to do is plug in your device via USB and debug an Android application in Xamarin Studio. You will see your device listed in the Select Device dialog. Note that if you are on Windows or have a nonstandard device, you might have to visit your device vendor's website to install drivers. Most Samsung and Nexus devices install their drivers automatically. On Android 4.3 and higher, there is also a confirmation dialog on the device that appears before beginning a USB debugging session. The following screenshot shows you what your device will look like for a Samsung Galaxy SII in the Select Device dialog. Xamarin Studio will display the model number, which is not always a name that you can recognize. You can view this model number in your device's settings.
[ 154 ]
Chapter 7
Understanding the linker
To keep Xamarin applications small and lightweight for mobile devices, Xamarin has created a feature for their compiler called the linker. Its main purpose is to strip unused code out of the core Mono assemblies (such as System.dll) and platform-specific assemblies (such as Mono.Android.dll and monotouch.dll). However, it can also give you the same benefits if it is set up to run on your own assemblies. Without running the linker, the entire Mono framework can be around 30 megabytes. This is why linking is enabled by default in device builds, which enables you to keep your applications small. The linker uses static analysis to work through the various code paths in an assembly. If it determines a method or class that is never used, it removes the unused code from that assembly. This can be a time-consuming process, so builds running in the simulator skip this step by default.
[ 155 ]
Deploying and Testing on Devices
Xamarin applications have the following three main settings for the linker: • Don't Link: In this setting, the linker compilation step is skipped. This is best used for builds running in the simulator or if you need to diagnose a potential issue with the linker. • Link SDK Assemblies Only: In this setting, the linker will only be run on the core Mono assemblies such as System.dll, System.Core.dll, and System.Xml.dll. • Link All Assemblies: In this setting, the linker is run against all the assemblies in your application, which include any class libraries or third-party assemblies you are using. These settings can be found in the Project options of any Xamarin.iOS or Xamarin. Android application. These settings are generally not present on class libraries, as it is generally associated with an iOS or Android application that will be deployed. The linker can also cause potential issues at runtime as there are cases in which its analysis determines incorrectly that a piece of code is unused. This can happen if you are using features in the System.Reflection namespace instead of accessing the method or property directly. This is one reason why it is important for you to test your application on physical devices, as linking is enabled for device builds. To demonstrate this issue, let's take a look at the following code example: //Just a simple class for holding info public class Person { public int Id { get; set; } public string Name { get; set; } } //Then somewhere later in your code var person = new Person { Id = 1, Name = "Chuck Norris" }; var propInfo = person.GetType().GetProperty("Name"); string value = propInfo.GetValue(person) as string; Console.WriteLine("Name: " + value);
Running the preceding code will work fine using the options for Don't Link or Link SDK Assemblies Only. However, if you try to run this, when using Link All Assemblies, you will get an exception similar to the following: Unhandled Exception:
[ 156 ]
Chapter 7 System.ArgumentException: Get Method not found for 'Name' at System.Reflection.MonoProperty.GetValue (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] index, System.Globalization.CultureInfo culture) at System.Reflection.PropertyInfo.GetValue (System.Object obj)
Since the Name property's getter was never used directly from code, the linker stripped it from the assembly. This caused the reflection code to fail at runtime. Even though potential issues can arise in your code, the option of Link All Assemblies is still quite useful. There are a few optimizations that can only be performed in this mode, and Xamarin can reduce your application to the smallest possible size. If performance or a tiny download size is the requirement for your application, you can try this option. However, thorough testing should be performed to verify that no problems are caused by linking your assemblies. To resolve issues in your code, Xamarin has included a complete set of workarounds to prevent specific parts of your code from being stripped away. Some of the options include the following: • Mark class members with [Preserve]. This will force the linker to include the attributed method, field, or property. • Mark an entire class with [Preserve(AllMembers=true)]. This will preserve all code within the class. • Mark an entire assembly with [assembly: Preserve]. This is an assembly-level attribute that will preserve all code contained within it. • Skip an entire assembly by modifying Additional mtouch arguments in your project options. Use --linkskip=System to skip an entire assembly. This can be used on assemblies that you do not have the source code for. • Custom linking via an XML file. This is the best option to use when you need to skip linking on a specific class or method that you do not have the source code for. Use –-xml=YourFile.xml in Additional mtouch arguments. The following is a sample XML file that demonstrates custom linking:
[ 157 ]
Deploying and Testing on Devices
Custom linking is the most complicated option and is usually the last resort. Luckily, most Xamarin applications will not have to work around many linker issues.
Understanding AOT compilation
The runtime behind Mono and .NET on Windows is based on a just in time (JIT) compiler. C# and other .NET languages are compiled into Microsoft Intermediate Language (MSIL). At runtime, MSIL is compiled into a native code to run on whatever type of architecture is running your application. Xamarin.Android follows this exact pattern. However, due to Apple's restrictions on dynamically generated code, a JIT compiler is not allowed on iOS. To work around this restriction, Xamarin has developed a new option called Ahead-of-time (AOT) compilation. In addition to making .NET possible on iOS, AOT has other benefits such as a shorter startup time and potentially better performance. AOT also has some limitations that are generally related to C# generics. To compile an assembly ahead of time, the compiler will need to run some static analysis against your code to determine the type of information. Generics throw a wrench into this situation. There are a few cases that are not supported by AOT, but they are completely valid in C#. The first is a generic interface, which is as follows: interface MyInterface { T GetMyValue(); }
[ 158 ]
Chapter 7
The compiler cannot determine the classes that can implement this interface ahead of time, especially when multiple assemblies are involved. The second limitation is related to the first. You cannot override virtual methods that contain generic parameters or return values. The following is a simple example: class MyClass { public virtual T GetMyValue() { //Some code here } } class MySubClass : MyClass { public override int GetMyValue() { //Some code here } }
Again, the static analysis of the compiler cannot determine which classes can override this method at compile time. Another limitation is that you cannot use DllImport in a generic class, as shown in the following code: class MyGeneric { [DllImport('MyImport")] public static void MyImport(); }
If you are not familiar with the language feature, DllImport is a way to call native C/C++ methods from C#. Using them inside generic classes is not supported. These limitations are another good reason why testing on devices is important since the preceding code will work fine on other platforms that can run C# code but not Xamarin.iOS.
[ 159 ]
Deploying and Testing on Devices
Avoiding common memory pitfalls
Memory on mobile devices is certainly not an unlimited commodity. Because of this, memory usage in your application can be much more important than on desktop applications. At times, you might find the need to use a memory profiler, or improve your code to use memory more efficiently. The following are the most common memory pitfalls: • The garbage collector (GC) is unable to collect large objects fast enough to keep up with your application • Your code inadvertently causes a memory leak • A C# object is garbage collected but is later attempted to be used by native code
Garbage collector
Let's take a look at the first problem where the GC cannot keep up. Let's say we have a Xamarin.iOS application with a button for sharing an image on Twitter as follows: twitterShare.TouchUpInside += (sender, e) => { var image = UImage.FromFile("YourLargeImage.png"); //Share to Twitter };
Now let's assume the image is a 10 MB image from the user's camera roll. If the user clicks on the button and cancels the Twitter post rapidly, there could be a possibility of your application running out of memory. iOS will commonly force close apps using too much memory, and you don't want users to experience this with your app. The best solution is to call Dispose on the image when you are finished with it as follows: var image = UImage.FromFile('YourLargeImage.png"); //Share to Twitter image.Dispose();
[ 160 ]
Chapter 7
An even better approach would be to take advantage of the C# using statement as follows: using(var image = UImage.FromFile('YourLargeImage.png")) { //Share to Twitter }
The C# using statement will automatically call Dispose in a try-finally block, so the object will get disposed even if an exception is thrown. I recommend that you take advantage of the using statement for any IDisposable class where possible. It is not always necessary for small objects such as NSString, but is always a good idea for larger, more heavyweight UIKit objects. A similar situation can occur on Android with the Bitmap class. Although slightly different, it is best to call both the Dispose and Recycle methods on this class along with using the BitmapFactory. Options settings for InPurgeable and InInputShareable.
Memory leaks
Memory leaks are the next potential issues. C# being a managed, garbage-collected language prevents a lot of memory leaks, but not all of them. The most common leaks in C# are caused by events. Let's assume that we have a static class with an event as follows: static class MyStatic { public static event EventHandler MyEvent; }
Now, let's say we need to subscribe to the event from an iOS controller as follows: public override void ViewDidLoad() { base.ViewDidLoad(); MyStatic.MyEvent += (sender, e) => { //Do something }; }
[ 161 ]
Deploying and Testing on Devices
The problem here is that the static class will hold a reference to the controller until the event is unsubscribed. This is a situation that a lot of developers might miss. To fix this issue on iOS, I would subscribe to the event in ViewWillAppear and unsubscribe ViewWillDisappear. On Android, use OnStart and OnStop, or OnPause and OnResume. You would correctly implement this event as follows: public override void ViewWillAppear() { base.ViewWillAppear(); MyStatic.MyEvent += OnMyEvent; } public override void ViewWillDisappear() { base.ViewWillDisappear (); MyStatic.MyEvent -= OnMyEvent; }
However, an event is not a surefire cause of a memory leak. Subscribing to the TouchUpInside event on a button inside the ViewDidLoad method, for example, is just fine. Since the button lives in memory just as long as the controller does, everything can get garbage collected without any problems.
Accessing objects disposed by GC
For the final issue, the garbage collector can sometimes remove a C# object. Later, an Objective-C object attempts to access it. The following is an example to add a button to UITableViewCell: public override UITableViewCell GetCell( UITableView tableView, NSIndexPath indexPath) { var cell = tableView.DequeueReusableCell('MyCell"); //Remaining cell setup here var button = UIButton.FromType(UIButtonType.InfoDark); button.TouchUpInside += (sender, e) => { //Do something }; cell.AccessoryView = button; return cell; } [ 162 ]
Chapter 7
We add the built-in info button as an accessory view to the cell. The problem here is that the button will get garbage collected, but its Objective-C counterpart will remain in use as it is displayed on the screen. If you click on the button after some period of time, you will get a crash that looks something similar to the following: mono-rt: Stacktrace: mono-rt: at mono-rt: at (wrapper managed-to-native) MonoTouch.UIKit. UIApplication.UIApplicationMain (int,string[],intptr,intptr) mono-rt: at MonoTouch.UIKit.UIApplication.Main (string[],string,string) ... Continued ... ================================================================= Got a SIGSEGV while executing native code. This usually indicates a fatal error in the mono runtime or one of the native libraries used by your application. ================================================================
It is not the most descriptive error message, but in general, you know that something went wrong in the native Objective-C code. To resolve the issue, create a custom subclass of UITableViewCell, and create a dedicated member variable for the button as follows: public class MyCell : UITableViewCell { UIButton button; public MyCell() { button = UIButton.FromType(UIButtonType.InfoDark); button.TouchUpInside += (sender, e) => { //Do something }; AccessoryView = button; } }
Now, your GetCell implementation will look something like the following code: public override UITableViewCell GetCell( UITableView tableView, NSIndexPath indexPath) { var cell = tableView.DequeueReusableCell('MyCell") as MyCell; //Remaining cell setup here return cell; } [ 163 ]
Deploying and Testing on Devices
Since the button is not a local variable, it will no longer get garbage collected sooner than needed. A crash is avoided, and in some ways this code is a bit cleaner. Similar situations can happen on Android with the interaction between C# and Java; however, it is less likely since both are garbage collected languages.
Summary
In this chapter, we started out learning the process of setting up iOS provision profiles to deploy to iOS devices. Next, we looked at the required device settings for deploying your application to an Android device. We discovered the Xamarin linker and how it can make your applications smaller and more performance-oriented. We went through the various settings to resolve problems caused by your code and the linker, and we explained AOT compilation on iOS and the limitations that occur. Finally, we covered the most common memory pitfalls that can occur with Xamarin applications. Testing your Xamarin application on mobile devices is important for various reasons. Some bugs are only displayed on the device due to the platform limitations that Xamarin has to work around. Your PC is much more powerful, so you will see different performances using the simulator rather than on a physical device. In the next chapter, we'll create a real web service using Windows Azure to drive our XamChat application. We will use a feature called Azure Mobile Services and implement push notifications on iOS and Android.
[ 164 ]
Web Services with Push Notifications Modern mobile applications are defined by their network connectivity. A mobile app that does not interact with a web server is both a rare find and not as interactive or social as it would otherwise be. In this module, we'll use the Windows Azure cloud platform to implement a server-side backend for our XamChat application. We'll use a feature called Azure Mobile Services, which is an excellent fit for our application and has the benefit of built-in push notifications. Once we are done with this chapter, our XamChat sample application will be much closer to being a real application and will allow its users to interact with one another. In this chapter, we will cover the following topics: • The services offered by Windows Azure • Setting up your Azure account • Azure Mobile Services as a backend for XamChat • Creating tables and scripts • Implementing a real web service for XamChat • Using the Apple Push Notification service • Sending notifications with Google Cloud Messaging
Web Services with Push Notifications
Learning Windows Azure
Windows Azure is an excellent cloud platform released by Microsoft in 2010. Azure provides both Infrastructure as a Service (IaaS) and Platform as a Service (PaaS) for building modern web applications and services. This means that it provides you with access to direct virtual machines within which you can deploy any operating system or software of your choice. This is known as IaaS. Azure also provides multiple platforms for building applications such as Azure Websites or SQL Azure. These platforms are known as PaaS since you deploy your software at a high level and do not have to deal directly with virtual machines or manage software upgrades. Let's go through the following more common services provided by Windows Azure: • Virtual machines: Azure provides you with access to virtual machines of all sizes. You can install practically any operating system of your choice. There are many premade distributions to choose from within Azure's gallery. • Websites: You can deploy any type of website that will run in Microsoft IIS from ASP.NET sites to PHP or Node.js. • SQL Azure: This is a cloud-based version of Microsoft SQL Server, which is a full-featured Relational Database Management System (RDMS) for storing data. • Mobile Services: This is a simple platform for building web services for mobile apps. It uses SQL Azure for backend storage and a simple JavaScript scripting system based on Node.js for adding business logic. In the latest version of Azure Mobile Services, you can also use C# and the ASP.NET Web API for developing server-side code. • Storage: Azure provides Blob storage, a method for storing binary files, and Table storage, which is a NoSQL solution for persisting data. • Service bus: This is a cloud-based solution for creating queues to facilitate communication between other cloud services. It also includes notification hubs as a simple way of providing push notifications to mobile apps. • Worker roles: A simple way to run a custom process in the cloud can be a plain Windows executable or a .NET worker role project. • Media services: A mechanism for providing streaming audio or video to nearly any device. It handles both encoding and delivery and can scale to support a large volume of users. • HDInsight: A version of Apache Hadoop running in Windows Azure for managing extremely large databases, also called big data.
[ 166 ]
Chapter 8
• Cloud services: This is a conglomeration of other services. Cloud services allow you to bundle multiple services together and create staging and production environments. It is a great tool for deployment; you can deploy changes to staging and swap staging and production to preserve uptime for your users. Apart from these services, there are many more, and new ones are added pretty regularly. We will use Azure Mobile Services, which leverages SQL Azure, to build our web service for XamChat. You can visit http://windowsazure.com for a full rundown of pricing and services offered. In this module, we chose to demonstrate a solution using Windows Azure as a web service backend for XamChat, since it is very easy to use with Xamarin applications because of the fantastic library found in the Xamarin Component Store. However, there are many more choices out there besides Azure that you might want to look at. Using Xamarin's development platform does not limit the types of web services your applications can interact with. Here are a few more common ones: • Parse: This service provides a product similar to that of Azure Mobile Services, complete with data storage and push notifications. This is a popular service among many mobile developers, even those not using Xamarin. You can get more information at http://parse.com. • Urban Airship: This service provides push notifications for mobile apps across multiple platforms. You can get more information at http:// urbanairship.com. • Amazon Web Services: This service is a complete cloud solution that is equivalent to Windows Azure. It has everything that you need to deploy applications in the cloud with total virtual machine support. The main difference is that Azure is very C# focused and built for .NET developers. There are also not as many PaaS options on Amazon. You can get more information at http://aws.amazon.com. Additionally, you can develop your own web services with on-premises web servers or inexpensive hosting services using the languages and technologies of your choice.
Setting up your Azure account
To start developing with Windows Azure, you can subscribe to a free one-month trial along with free credits worth $200. You can get even more perks if you have an MSDN subscription. To go along with this, many of Azure's services have free tiers that give you lower performance versions. So if your trial expires, you can continue your development at little or no cost, depending on the services you are using. [ 167 ]
Web Services with Push Notifications
Let's begin by navigating to http://windowsazure.com/en-us/pricing/freetrial and performing the following steps: 1. Click on the Try it now link. 2. Sign in with a Windows Live ID. 3. For security purposes, verify your account via phone or text message. 4. Enter the payment information. This is only used if you exceed your spending limits. You won't accidentally spend beyond budget by developing your app—it is fairly difficult to accidentally spend money until real users are interacting with your services. 5. Check I agree to the policies and click on Sign Up. 6. Review the final setting and click on Submit. 7. If all the required information is entered correctly, you will now finally have access to the Azure subscription page. Your subscription page will look similar to the following screenshot:
You can click on the Portal link in the top-right corner of the page to access your account. In future, you can manage your Azure services at http://manage. windowsazure.com: Complete the Windows Azure tour to get a quick rundown of the management portal's features. You can then access the main menu to create new Azure services, virtual machines, and so on. The main menu looks similar to the following screenshot:
[ 168 ]
Chapter 8
This concludes the sign-up process for Windows Azure. It is pretty simple compared to the Apple and Google Play developer programs. Feel free to play around, but don't be too worried about spending money. Azure has free versions of most services and also delivers a good amount of bandwidth for free. You can get more information on pricing at http://windowsazure.com/en-us/pricing/overview. Note that there are a lot of misconceptions about Windows Azure being expensive. You can do all of your development for an application on the free tier without spending a dime. When putting applications into production, you can easily scale up or down on the number of VM instances to keep your costs under control. In general, you will not be spending much money if you do not have a lot of users. Likewise, you should be earning plenty of revenue if you happen to have lots of users.
Exploring Azure Mobile Services
For the server side of XamChat, we'll use Azure Mobile Services to provide backend storage to the application. A mobile service is a neat solution to accelerate development for mobile applications that provide data storage and a REST-based API, which is a standards-based way of communicating with a web service over HTTP. Azure Mobile Services also includes a .NET client library for interacting with the service from C#.
[ 169 ]
Web Services with Push Notifications
A few nice features of Azure Mobile Services are as follows: • Storage of data in the cloud with SQL Azure or other Azure data services such as Blob or Table storage • Easy authentication with Windows Live ID, Facebook, Google, and Twitter • Push notifications with iOS, Android, and Windows devices • Code the server side with JavaScript and Node.js or C# • An easy-to-use .NET library for client-side development • Scale Azure Mobile Services to accommodate high volumes of data You can see why using Azure is a good choice for simple mobile applications. The benefits of accelerated development and the many features it provides are a great fit for our XamChat sample application. Let's navigate to your account at http://manage.windowsazure.com and perform the following steps to create a mobile service: 1. Click on the plus button in the bottom-left corner of the window. 2. Navigate to Compute | Mobile Service | Create through the menu. 3. Enter a domain URL of your choice such as yourname-xamchat. 4. We use the free database option for now. 5. Select a data center near your location in the Region dropdown. 6. For the Backend dropdown, leave the selection on JavaScript for this module. It will be simpler to set up the backend since we are focusing more on the client side. Feel free to use C# as an alternative, but keep in mind the examples in this module will be written in JavaScript. 7. Now, click on Next. 8. Use the default database name and choose New SQL database server. 9. Enter a login name and password for the SQL server, and keep this information in a safe place. 10. Make sure the region is the same as that of your mobile service to ensure good performance between your mobile service and its database. 11. Review your final settings and hit the Finish button. The management portal will display the progress, and it will take several seconds to create your mobile service and SQL Server instances. Remember that Azure is creating and starting new virtual machines for you under the hood, so it is really doing a decent amount of work to accommodate your request. [ 170 ]
Chapter 8
When completed, your account will have one Mobile Service and one SQL database in addition to the Default Directory that is included in all the accounts, as shown in the following screenshot:
If you take a look at the Scale tab for your mobile service, you'll notice that it is running under the Free tier by default. This is a great place for development. At the time of writing this module, it accommodates 500 devices. When deploying your applications to production, you might consider the Basic or Standard tiers, which also give you the option to add multiple instances.
Creating tables and scripts
The first step to implement something in Azure Mobile Services is to create a new table. By default, Azure Mobile Services uses a feature called dynamic schemas with its SQL database. When you insert a row from the client, new columns are dynamically added to the table. This prevents you from having to create the database schema manually and is a neat code-first approach to develop your backend database. You can always connect to the SQL database manually to fine tune things or make manual changes to the schema. Return to the management portal, select your mobile services instance, and perform the following steps: 1. Click on the Data tab. 2. Click on the Create button found at the bottom center of the page. 3. Enter User as the table name. 4. Leave everything else at its default value and click on the Save button. 5. Repeat this process to create three more tables named Friend, Message, and Conversation.
[ 171 ]
Web Services with Push Notifications
Now that we have our four tables, we need to create a script to facilitate the login process for the users of our app. Azure Mobile Services allows you to add custom logic to your tables by creating scripts that run in Node.js, a framework for developing web services with JavaScript. You can override what happens to each table during the insert, read, update, or delete operations. In addition to this, you can also create scripts that are completely customized if you need other functionalities. Click on the User table and then click on the Script tab. Make sure you are viewing the insert operation. By default, your script will be very simple, as shown in the following snippet: function insert(item, user, request) { request.execute(); }
Scripts in Azure Mobile Services have three parameters, which are stated as follows: • item: This parameter is the object that the client sends to the service. In our case, it will be the User object we created in the previous chapters. • user: This parameter includes information about the authenticated user. We won't be using this in our examples. • request: This parameter is an object used to run the table operation and send a response back to the client. Calling execute will complete the operation and return a successful response to the client. We need to modify the preceding script to only insert a new user when that user does not already exist. If the user does exist, we need to make sure that the password matches the username. Let's make a few changes to the script, as shown in the following lines of code: function insert(item, user, request) { var users = tables.getTable('user'); users.where({ username : item.Username }).read({ success: function(results) { if (results.length == 0) { //This is a new user request.execute(); } else { var user = results[0]; if (item.Password == user.Password) { request.respond(statusCodes.OK, user); } [ 172 ]
Chapter 8 else { request.respond(statusCodes.UNAUTHORIZED, "Incorrect username or password"); } } } }); }
Let's summarize what we did in the preceding JavaScript: 1. First, we grabbed the user table. Note that you can reference the name of the table using lowercase. 2. Next, we ran a query to pull out any existing users with the where function. We used item.Username since this matches our User object in C#. Notice how this method is similar to Linq in C#. 3. If there are no results, we let the request execute normally. 4. Otherwise, we compare the passwords and return statusCodes.OK if they match. 5. If the passwords do not match, we return statusCodes.UNAUTHORIZED. This will cause the client to receive an error. 6. For a complete list of available functions and methods, make sure you check out the server script reference on MSDN at http://tinyurl.com/ AzureMobileServices. From here, just make sure that you click on the Save button to apply your changes. Azure Mobile Services also has the option of providing source control for your scripts via Git. Feel free to take advantage of this feature if you want to make changes to the script in your favorite editor locally instead of the website editor. After this, we need to create one more script. The way XamChat was implemented earlier in the module allows, users to add friends by entering their friends' usernames. So, in order to insert into the Friend table, we will need to modify the insert script to look up users by their usernames. Let's modify the insert script for the Friends table as follows: function insert(item, user, request) { var users = tables.getTable('user'); users.where({ username : item.Username }).read({ success: function(results) { if (results.length === 0) { //Could not find the user [ 173 ]
Web Services with Push Notifications request.respond(statusCodes.NOT_FOUND, "User not found"); } else { var existingUser = results[0]; item.UserId = existingUser.id; request.execute(); } } }); }
This is pretty similar to what we did before; we ran a simple query to load the user table based on the Username value. We merely have to set the UserId value on the new friend table prior to the execution of the request.
Adding a backend to XamChat
With our server-side changes complete, the next step is to implement our new service in our XamChat iOS and Android applications. Luckily, as we used an interface named IWebService, all we need to do is implement this interface to get it working in our application. Now we can start setting up our service in our iOS application by performing the following steps: 1. Open the XamChat.Core project that we created in Chapter 4, XamChat – a Cross-platform App. 2. Create an Azure folder within the project. 3. Create a new class named AzureWebService.cs. 4. Create the public class and implement IWebService. 5. Right-click on IWebService in your code and navigate to Refactor | Implement Interface. 6. A line will appear; press Enter to insert the method stubs. When this setup is complete, your class will look something similar to the following: public class AzureWebService : IWebService { #region IWebService implementation public Task Login(string username, string password) { [ 174 ]
Chapter 8 throw new NotImplementedException(); } // -- More methods here -#endregion }
Adding the Azure Mobile Services NuGet package
To make development with Azure Mobile Services much easier, we need to add a reference to the .NET client library. To do this, we will use NuGet to add the library: 1. Right-click on the XamChat.Core project and navigate to Add | Add Packages. 2. Search for Azure Mobile Services using the search box. 3. Select the Azure Mobile Services package, which at the time of writing this module was version 1.2.5. 4. Click on Add Package. 5. Repeat this process for the XamChat.iOS and XamChat.Android projects. There is some platform-specific setup for each platform. You can also get the Azure Mobile Services library from the Xamarin Component Store if you like. It is very similar to using NuGet.
This will download the library and automatically add references to it in your projects. The NuGet package manager might complain of warnings, which can be ignored. NuGet was originally developed for Visual Studio on Windows, so any packages that contain PowerShell scripts or prompt for a license agreement might give you a warning. Now, let's modify our AzureWebService.cs file. Add using Microsoft. WindowsAzure.MobileServices to the top of the file, and then make the following changes: public class AzureWebService : IWebService { MobileServiceClient client = new MobileServiceClient( "https://your-service-name.azure-mobile.net/", "your-application-key"); [ 175 ]
Web Services with Push Notifications // -- Existing code here -}
Make sure you fill in your mobile service name and application key. You can find your key on the Dashboard tab of the Azure Management Portal under the Manage Keys section. Now let's implement our first method, Login, in the following manner: public async Task Login( string username, string password) { var user = new User { Username = username, Password = password }; await client.GetTable().InsertAsync(user); return user; }
This is fairly straightforward, because of how nice this library is to use. The GetTable method knows how to use a table named User based on the C# class name. Upon the first call, the dynamic schema feature will create two new columns named Username and Password based on the C# properties of our class. Note that the InsertAsync method will also fill in the user's Id property for later use in our application since we will need the Id for future calls to the mobile service. Next, open the AppDelegate.cs file to set up our new service and add the following code: //Replace this line ServiceContainer.Register( () => new FakeWebService()); //With this line ServiceContainer.Register( () => new AzureWebService());
Additionally, you will need to add some platform-specific setup for Azure Mobile Services. Add using Microsoft.WindowsAzure.MobileServices to the top of the file and add the following line of code to the bottom of FinishedLaunching in your AppDelegate.cs file: CurrentPlatform.Init(); [ 176 ]
Chapter 8
Now, if you compile and run your application after you log in, your app should successfully call Azure Mobile Services and insert a new user. Navigate to the Data tab of your Azure Mobile Service in the Azure Management Portal, and select the User table. You will see the user you just inserted, as shown in the following screenshot:
It is generally a bad idea to store passwords in plain text in your database. A simple approach to make things a bit more secure would be to store them as an MD5 hash. You should be able to make this change in the custom JavaScript that we are using to insert the password on the User table. A complete guide to securing Windows Azure applications can be found at http://msdn.microsoft.com/en-us/library/ windowsazure/hh696898.aspx.
Next, let's make a new class named Friend.cs. Add it to the Azure folder that is next to the other class specific to Azure as follows: public class Friend { public string Id { get; set; } public string MyId { get; set; } public string UserId { get; set; } public string Username { get; set; } }
We'll use this class to store the friends information about each user. Note that we also have an Id property, and all the classes saved in Azure Mobile Services should have a string property named Id. This will be the table's primary key in the SQL database. Next, let's modify the Message and Conversation classes to prepare for push notifications down the road. Add a new property to the Message class as follows: public string ToId { get; set; } [ 177 ]
Web Services with Push Notifications
Then, add the following new property to Conversation.cs: public string MyId { get; set; }
Here, we'll need to insert or seed some test data for our application to function correctly. The easiest way to insert data would be from C#, so let's implement the following simple method on our service to do so: public async Task LoadData() { var users = client.GetTable(); var friends = client.GetTable(); var me = new User { Username = "jonathanpeppers", Password = "password" }; var friend = new User { Username = "chucknorris", Password = "password" }; await users.InsertAsync(me); await users.InsertAsync(friend); await friends.InsertAsync(new Friend { MyId = me.Id, Username = friend.Username }); await friends.InsertAsync(new Friend { MyId = friend.Id, Username = me.Username }); }
Next, let's add the following method to AppDelegate.cs and call it from within FinishedLaunching: private async void LoadData() { var service = ServiceContainer.Resolve() as AzureWebService; await service.LoadData(); }
[ 178 ]
Chapter 8
If you run your application at this point, it will insert two users and make them friends with one another. Before doing so, let's add some more code to the LoadData method in AzureWebService.cs to insert conversations and messages as follows: var conversations = client.GetTable(); var messages = client.GetTable(); var conversation = new Conversation { MyId = me.Id, UserId = friend.Id, Username = friend.Username, LastMessage = "HEY!" }; await conversations.InsertAsync(conversation); await messages.InsertAsync(new Message { ConversationId = conversation.Id, ToId = me.Id, UserId = friend.Id, Username = friend.Username, Text = "What's up?", Date = DateTime.Now.AddSeconds(-60) }); await messages.InsertAsync(new Message { ConversationId = conversation.Id, ToId = friend.Id, UserId = me.Id, Username = me.Username, Text = "Not much", Date = DateTime.Now.AddSeconds(-30) }); await messages.InsertAsync(new Message { ConversationId = conversation.Id, ToId = me.Id, UserId = friend.Id, Username = friend.Username, Text = "HEY!", Date = DateTime.Now });
[ 179 ]
Web Services with Push Notifications
Now, if you run the application, it will seed the database with some good data to work with. I'd recommend that you remove the call to LoadData once it is successful the first time, and perhaps remove the method entirely when the development is complete. Before going further, let's implement the rest of our IWebService interface. It can be done as follows: public async Task Register(User user) { await client.GetTable().InsertAsync(user); return user; } public async Task GetFriends(string userId) { var list = await client.GetTable() .Where(f => f.MyId == userId).ToListAsync(); return list.Select(f => new User { Id = f.UserId, Username = f.Username }) .ToArray(); } public async Task AddFriend( string userId, string username) { var friend = new Friend { MyId = userId, Username = username }; await client.GetTable().InsertAsync(friend); return new User { Id = friend.UserId, Username = friend.Username }; }
Each method here is pretty simple. Register is very similar to Login, but the main complication for the other methods is the need to convert a Friend object to User. We used the ToListAsync method from the Azure library to get List; however, since our interface uses arrays, we quickly converted the list to an array. We also utilized a couple of basic Linq operators such as Where and Select to accomplish our implementation of IWebService. Now let's complete the methods related to conversations and messages, which are as follows: public async Task GetConversations( string userId) { var list = await client.GetTable() .Where(c => c.MyId == userId).ToListAsync(); [ 180 ]
Chapter 8 return list.ToArray(); } public async Task GetMessages(string conversationId) { var list = await client.GetTable() .Where(m => m.ConversationId == conversationId) .ToListAsync(); return list.ToArray(); } public async Task SendMessage(Message message) { await client.GetTable().InsertAsync(message); return message; }
This completes our implementation of IWebService. If you run the application at this point, it will function exactly as before with the exception that the app is actually talking to a real web server. New messages will get persisted in the SQL database, and our custom scripts will handle the custom logic that we need. Feel free to play around with our implementation; you might discover some features of Azure Mobile Services that will work great with your own applications. At this point, another good exercise would be to set up Azure Mobile Services in our Android application. To do so, you will merely need to add the Azure Mobile Services NuGet package. After that, you should be able to swap out the ServiceContainer.Register call in your Application class and call CurrentPlatform.Init(). Everything will function exactly like on iOS. Isn't cross-platform development great?
Using the Apple Push Notification service Implementing push notifications with Azure Mobile Services on iOS is very simple to set up from an Azure backend perspective. The most complicated part is working through Apple's process of creating certificates and provisioning profiles in order to configure your iOS application. Before we continue, make sure you have a valid iOS Developer Program account, as you will not be able to send push notifications without one. If you are unfamiliar with the concept of push notifications, take a look at Apple's documentation at http://tinyurl.com/XamarinAPNS. To send push notifications, you need to set up the following: • An explicit App ID registered with Apple • A provisioning profile targeting that App ID • A certificate for your server to trigger the push notification [ 181 ]
Web Services with Push Notifications
Apple provides both a development and production certificate that you can use to send push notifications from your server.
Setting up proper provisioning
Let's begin by navigating to http://developer.apple.com/account and performing the following steps: 1. Click on the Identifiers link. 2. Click on the plus button in the top-right corner of the window. 3. Enter a description, such as XamChat, for the bundle ID. 4. Enter your bundle ID under the Explicit App ID section. This should match the bundle ID you set up in your Info.plist file, for example, com. yourcompanyname.xamchat. 5. Under App Services, make sure you check Push Notifications. 6. Now, click on Continue. 7. Review your final settings and hit Submit. This will create an explicit App ID similar to what you can see in the following screenshot, which we can use for sending push notifications:
[ 182 ]
Chapter 8
Setting up your provisioning profile
For push notifications, we have to use a profile with an explicit App ID that is not a development certificate. Now let's set up a provisioning profile: 1. Click on the Development link under Provisioning Profiles in the right-hand side pane. 2. Click on the plus button in the top-right corner. 3. Check iOS App Development and click on Continue. 4. Select the App ID we just created and click on Continue. 5. Select the developer and click on Continue. 6. Select the devices you will be using and click on Continue. 7. Enter a name for the profile and click on Generate. 8. Download the profile and install it, or open XCode and use the sync button by navigating to Preferences | Accounts. When finished, you should arrive at a success web page that looks similar to the following screenshot:
[ 183 ]
Web Services with Push Notifications
Setting up a certificate signing request
Next, we perform the following steps to set up the certificate our server needs: 1. Click on the Development link under Certificates in the right-hand side pane. 2. Click on the plus button in the top-right corner. 3. Enable Apple Push Notifications service SSL (Sandbox) and click on Continue. 4. Select your App ID as before and click on Continue. 5. Create a new certificate signing request as per Apple's instructions. You can also refer to Chapter 7, Deploying and Testing on Devices, or locate the *.certSigningRequest file. 6. Next, click on Continue. 7. Upload the signing request file and click on Generate. 8. Next, click on Download. 9. Open the file to import the certificate into Keychain. 10. Locate the certificate in Keychain. It will be titled Apple Development iOS Push Services and will contain your bundle ID. 11. Right-click on the certificate and export it somewhere on your filesystem. Enter a password that you will remember. This will create the certificate that we need to send push notifications to our users from Azure Mobile Services. All that remains is to return to the Azure Management Portal and upload the certificate from the Push tab under Apple Push Notification Settings, as shown in the following screenshot:
This upload concludes the configuration we need from Apple's side. [ 184 ]
Chapter 8
Making client-side changes for push notifications
Next, let's return to our XamChat.iOS project in Xamarin Studio to make the necessary changes on the client side for push notifications. We will need to add a few new classes to our shared code to start with. Open IWebService.cs and add the following new method: Task RegisterPush(string userId, string deviceToken);
Next, let's implement this method in FakeWebService.cs (just so it compiles) as follows: public async Task RegisterPush(string userId, string deviceToken) { await Sleep(); }
Now, let's add a new class named Device.cs in the Core/Azure folder: public class Device { public string Id { get; set;} public string UserId { get; set; } public string DeviceToken { get; set; } }
Finally, we can implement the real method in AzureWebService.cs as follows: public async Task RegisterPush( string userId, string deviceToken) { await client.GetTable() .InsertAsync(new Device { UserId = userId, DeviceToken = deviceToken }); }
As for ViewModels, we need to add one more new method to LoginViewModel.cs: public async Task RegisterPush(string deviceToken) { if (settings.User == null) [ 185 ]
Web Services with Push Notifications throw new Exception("User is null"); await service.RegisterPush(settings.User.Id, deviceToken); }
Then, we need to add a small modification to MessageViewModel.cs. Add the following line when creating a new Message object in the SendMessage method: ToId = Conversation.UserId,
This modification concludes what we need to add to our shared code. We will reuse this new functionality when we add push notifications to Android, so go ahead and take the time to link in the new Device.cs file in your XamChat.Droid project to build your entire solution. Now, let's add the iOS platform-specific code we need. Add the following methods to your AppDelegate.cs file: public async override void RegisteredForRemoteNotifications( UIApplication application, NSData deviceToken) { var loginViewModel = ServiceContainer .Resolve(); try { string token = deviceToken.Description; token = token.Substring(1, token.Length - 2); await loginViewModel.RegisterPush(token); } catch (Exception exc) { Console.WriteLine("Error registering push: " + exc); } } public override void FailedToRegisterForRemoteNotifications( UIApplication application, NSError error) { Console.WriteLine("Error registering push: " + error.LocalizedDescription); }
We implemented a couple of important methods in the preceding code snippet. RegisteredForRemoteNotifications will occur when Apple successfully returns a device token from its servers. It is returned within angle brackets, so we do a little work to trim those off and pass the device token through LoginViewModel to Azure Mobile Services. We also implemented FailedToRegisterForRemoteNotifications just to report any errors that might occur throughout the process. [ 186 ]
Chapter 8
One last thing to do is to actually make a call to register for remote notifications. Open LoginController.cs and add the following line of code directly after the successful call to log in: UIApplication.SharedApplication .RegisterForRemoteNotificationTypes( UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound);
You can also call the method on startup; however, in our situation, we need a valid user ID to store in the Device table in Azure. Now let's switch to the Azure Management Portal and make the remaining changes in JavaScript on the server side. Under the Data tab, create a new table named Device with the default settings. Next, we need to modify the insert script so that the duplicate device tokens are not inserted: function insert(item, user, request) { var devicesTable = tables.getTable('device'); devicesTable.where({ userId: item.UserId, deviceToken: item.DeviceToken }) .read({ success: function (devices) { if (devices.length > 0) { request.respond(200, devices[0]); } else { request.execute(); } } }); }
Last but not least, we need to modify the insert script for the Message table to send push notifications to the user. The message was sent as follows: function insert(item, user, request) { request.execute(); var devicesTable = tables.getTable('device'); devicesTable.where({ userId : item.ToId }).read({ success: function(devices) { [ 187 ]
Web Services with Push Notifications devices.forEach(function(device) { var text = item.Username + ": " + item.Text; push.apns.send(device.DeviceToken, { alert: text, badge: 1, payload: { message: text } }); }); } }); }
After executing the request, we retrieve a list of devices from the database and send out a push notification for each one. To test push notifications, deploy the application and log in with the secondary user (if using our examples: chucknorris). After logging in, you can just background the app with the home button. Next, log in with the primary user on your iOS simulator and send a message. You should receive a push notification, as shown in the following screenshot:
Implementing Google Cloud Messaging
Since we have already set up everything we need in the shared code and on Azure, setting up push notifications for Android will be a lot less work at this point of time. To continue, you will need a Google account with a verified e-mail address; however, I would recommend that you use an account registered with Google Play if you have one. You can refer to the full documentation on Google Cloud Messaging (GCM) at http://developer.android.com/google/gcm. [ 188 ]
Chapter 8
Note that Google Cloud Messaging requires that Google Play be installed on the Android device and that the Android OS be at least version 2.2.
Let's begin by navigating to http://cloud.google.com/console and performing the following steps: 1. Click on the Create Project button. 2. Enter an appropriate project name such as XamChat. 3. Enter a project ID; you can use the generated one. I prefer to use my application's bundle ID and replace the periods with hyphens. 4. Agree to the Terms of Service. 5. Click on the Create button. 6. When creating your first project, you might have to verify the mobile number associated with your account. 7. Note the Project Number field on the Overview page. We will need this number later. The following screenshot shows the Overview tab:
Now we can continue with our setup as follows: 1. Click on APIs & auth in the left-hand side pane. 2. Scroll down and click on Google Cloud Messaging for Android. 3. Click on the OFF button at the top to enable the service. You might have to accept another agreement. 4. Click on Registered Apps in the left-hand side pane. 5. Click on the Register App button at the top. [ 189 ]
Web Services with Push Notifications
6. Enter XamChat in the App Name field and click on Register. You can leave the Platform selection on Web Application at its default value. 7. Expand the Server Key section and copy the API Key value to your clipboard. 8. Switch to the Azure Management Portal and navigate to the Push tab in your Azure Mobile Service instance. 9. Paste the API key in the google cloud messaging settings section and click on Save.
Next, let's modify our insert script for the Message table to support Android as follows: function insert(item, user, request) { request.execute(); var devicesTable = tables.getTable('device'); devicesTable.where({ userId : item.ToId }).read({ success: function(devices) { devices.forEach(function(device) { if (device.DeviceToken.length > 72) { push.gcm.send(device.DeviceToken, { title: item.Username, message: item.Text, }); } else { var text = item.Username + ": " + item.Text; push.apns.send(device.DeviceToken, { alert: text, badge: 1, payload: { message: text } }); } }); } }); } [ 190 ]
Chapter 8
Basically, we send any deviceToken values that are longer than 72 characters to GCM. This is one simple way to do it, but you can also add a value to the Device table that indicates whether the device is Android or iOS. GCM also supports sending custom values to be displayed in the notification area, so we send an actual title along with the message. This completes our setup on Azure's side. Setting up the next part in our Android application can be a bit difficult, so we will use a library called PushSharp to simplify our implementation. First, navigate to http://github.com/Redth/PushSharp and perform the following steps: 1. Download the project and place it in the same folder as your XamChat solution. 2. Add the PushSharp.Client.MonoForAndroid.Gcm project to your solution. You can locate the project in the PushSharp.Client subdirectory. 3. Reference the new project from your XamChat.Droid project. 4. If it's not already installed, you will need to install the Android SDK Platform for Android 2.2 (API 8). You can install this from the Android SDK manager that can be launched from the Tools menu in Xamarin Studio. Next, create a new class called PushConstants.cs as follows: public static class PushConstants { public const string BundleId = "your-bundle-id"; public const string ProjectNumber = "your-project-number"; }
Fill out the BundleId value with your application's bundle ID and the ProjectNumber value with the project number found on the Overview page of your Google Cloud Console. Next, we need to set up some permissions to support push notifications in our application. Above the namespace declaration in this file, add the following: [assembly: Permission( Name = XamChat.Droid.PushConstants.BundleId + ".permission.C2D_MESSAGE")] [assembly: UsesPermission( Name = XamChat.Droid.PushConstants.BundleId + ".permission.C2D_MESSAGE")]
[ 191 ]
Web Services with Push Notifications [assembly: UsesPermission( Name = "com.google.android.c2dm.permission.RECEIVE")] [assembly: UsesPermission( Name = "android.permission.GET_ACCOUNTS")] [assembly: UsesPermission( Name = "android.permission.INTERNET")] [assembly: UsesPermission( Name = "android.permission.WAKE_LOCK")]
You can also make these changes in our AndroidManifest.xml file; however, using C# attributes can be better since it gives you the ability to use code completion while typing. Next, create another new class named PushReceiver.cs as follows: [BroadcastReceiver( Permission = GCMConstants.PERMISSION_GCM_INTENTS)] [IntentFilter( new string[] { GCMConstants.INTENT_FROM_GCM_MESSAGE }, Categories = new string[] { PushConstants.BundleId })] [IntentFilter( new string[] { GCMConstants.INTENT_FROM_GCM_REGISTRATION_CALLBACK }, Categories = new string[] { PushConstants.BundleId })] [IntentFilter( new string[] { GCMConstants.INTENT_FROM_GCM_LIBRARY_RETRY }, Categories = new string[] { PushConstants.BundleId })] public class PushReceiver : PushHandlerBroadcastReceiverBase { }
The PushReceiver.cs class sets up BroadcastReceiver, which is Android's native way for different applications to talk with one another. For more information on this topic, check out the Android documentation at http://developer.android.com/ reference/android/content/BroadcastReceiver.html. Next, create one last class named PushService.cs as follows: [Service] public class PushHandlerService : PushHandlerServiceBase { public PushHandlerService() : base (PushConstants.ProjectNumber) { } }
[ 192 ]
Chapter 8
Now, right-click on PushHandlerServiceBase and navigate to Refactor | Implement abstract members. Next, let's implement each member one by one: protected async override void OnRegistered ( Context context, string registrationId) { Console.WriteLine("Push successfully registered!"); var loginViewModel = ServiceContainer.Resolve(); try { await loginViewModel.RegisterPush(registrationId); } catch (Exception exc) { Console.WriteLine("Error registering push: " + exc); } }
The preceding code is very similar to what we did on iOS. We merely have to send the registrationId value to loginViewModel. Next, we have to write the following code when the message is received: protected override void OnMessage ( Context context, Intent intent) { //Pull out the notification details string title = intent.Extras.GetString("title"); string message = intent.Extras.GetString("message"); //Create a new intent intent = new Intent(this, typeof(ConversationsActivity)); //Create the notification var notification = new Notification( Android.Resource.Drawable.SymActionEmail, title); notification.Flags = NotificationFlags.AutoCancel; notification.SetLatestEventInfo(this, new Java.Lang.String(title), new Java.Lang.String(message), PendingIntent.GetActivity(this, 0, intent, 0)); //Send the notification through the NotificationManager var notificationManager = GetSystemService( Context.NotificationService) as NotificationManager; notificationManager.Notify(1, notification); } [ 193 ]
Web Services with Push Notifications
This code will actually pull out the values from the notification and display them in the notification center of the Android device. We used the built-in resource for SymActionEmail to display an e-mail icon in the notification. Next, we just need to implement two more abstract methods. For now, let's just use Console.WriteLine to report these events as follows: protected override void OnUnRegistered( Context context, string registrationId) { Console.WriteLine("Push unregistered!"); } protected override void OnError ( Context context, string errorId) { Console.WriteLine("Push error: " + errorId); }
Down the road, you should consider removing registrations from the Device table in Azure when OnUnRegistered is called. Occasionally, a user's registrationId will change, so this is the place where your application is notified of this change. Next, open Application.cs and add the following lines to the end of OnCreate: PushClient.CheckDevice(this); PushClient.CheckManifest(this);
Next, open LoginActivity.cs and add the following line after a successful login: PushClient.Register(this, PushConstants.ProjectNumber);
[ 194 ]
Chapter 8
Now if you repeat the steps for testing push notifications on iOS, you should be able to send a push notification to our Android app. Even better, you should be able to send push notifications across platforms, since an iOS user can send a message to an Android user.
Summary
In this chapter, we went through what Windows Azure provides: Infrastructure as a Service and Platform as a Service. We set up a free Windows Azure account and set up an Azure Mobile Services instance. Next, we created all the tables we needed to store our data and wrote a few scripts to add the business logic to the web service. We implemented the client-side code for making requests against Azure Mobile Services. Lastly, we implemented push notifications for iOS using the Apple Push Notification Service and for Android using Google Cloud Messaging. Using Azure Mobile Services, we were able to get by without writing much of the server-side code—mainly a few simple scripts. It would be pretty challenging to implement push notifications yourself instead of leveraging Azure's functionality for this. In the next chapter, we'll explore how to use third-party libraries with Xamarin. This includes everything from the Xamarin Component Store to using native Objective-C or Java libraries.
[ 195 ]
Third-party Libraries Xamarin supports a subset of the .NET framework, but for the most part, it includes all the standard APIs you would expect in the .NET base class libraries. Because of this, a large portion of C#'s open source libraries can be used directly in Xamarin projects. Additionally, if an open source project doesn't have a Xamarin or portable class library version, porting the code to be used in a Xamarin project can often be very straightforward. Xamarin also supports calling native Objective-C and Java libraries, so we will explore these as additional means of reusing existing code. In this chapter, we will cover the following: • The Xamarin Component Store • NuGet • Porting existing C# libraries • Objective-C bindings • Java bindings
The Xamarin Component Store
The primary and obvious way to add third-party components to your project is via the Xamarin Component Store. The Component Store is fairly similar to NuGet, which we will cover later, except that the Component Store also contains premium components that are not free. All Xamarin components are required to include full sample projects and a getting started guide, while NuGet does not inherently provide documentation in its packages.
Third-party Libraries
All the Xamarin.iOS and Xamarin.Android projects come with a Components folder. To get started, simply right-click on the folder and select Get More Components to launch the store dialog, as shown in the following screenshot:
At the time of writing this module, there are well over 200 components available to enhance your iOS and Android applications. This is a great place to find the most common components to use within your Xamarin applications. Each component is complete with artwork. You might possibly need a demonstration video, reviews, and other information before purchasing a premium component.
The most common components
The most well-known and useful components are as follows: • Json.NET: This is the de facto standard for parsing and serializing JSON with C# • RestSharp: This is a commonly used simple REST client for .NET • SQLite.NET: This is a simple Object Relational Mapping (ORM) to use when working with local SQLite databases in your mobile applications • Facebook SDK: This is the standard SDK provided by Facebook to integrate its services into your apps
[ 198 ]
Chapter 9
• Xamarin.Mobile: This is a cross-platform library to access your device's contacts, GPS, photo library, and camera with a common API • ActionBarSherlock: This is a powerful ActionBar replacement for Android Note that some of these libraries are native Java or Objective-C libraries, while some are plain C#. Xamarin is built from the ground up to support calling native libraries, so the Component Store offers many of the common libraries that Objective-C or Java developers would leverage when developing mobile applications. You can also submit your own components to the Component Store. If you have a useful open source project or just want to earn some extra cash, creating a component is simple. We won't be covering it in this module, but you can navigate to http:// components.xamarin.com/submit for full documentation on the subject, as shown in the following screenshot:
Porting existing C# libraries
Even though Xamarin is becoming a popular platform, many open source .NET libraries are simply not up to speed with supporting Xamarin.iOS and Xamarin. Android. However, in these cases, you are definitely not out of luck. Often, if there is a Silverlight or Windows Phone version of the library, you can simply create an iOS or Android class library and add the files with no code changes.
[ 199 ]
Third-party Libraries
To help with this process, Xamarin has created an online service tool to scan your existing code and determine how far off a library is from being portable. Navigate to http://scan.xamarin.com and upload any *.exe or *.dll file to have its methods analyzed for cross-platform development. After the scanning process, you'll get a report of the porting percentage (how much your component / application is portable to all platforms: Android, iOS, Windows Phone, and Windows Store). The following screenshot is a sample report of the Lucene .NET client library:
If the library is running a high percentage on portability, you should have a relatively easy time porting it to Android or iOS. In most cases, it can even be easier to port the library to Xamarin than Windows Phone or WinRT. To illustrate this process, let's port an open source project that doesn't have Xamarin or a portable class library support. I have selected a dependency injection library called Ninject due to its usefulness and relationship to ninjas. You can find out more about the library at http://www.ninject.org. Let's begin setting up the library to work with Xamarin projects as follows: 1. First, download the source code for Ninject from https://github.com/ ninject/Ninject. 2. Open Ninject.sln in Xamarin Studio.
[ 200 ]
Chapter 9
3. Add a new iOS Library Project named Ninject.iOS. 4. Link all the files from the Ninject main project. Make sure you use the Add Existing Folder dialog to speed up this process. If you aren't familiar with GitHub, I recommend that you download the desktop client for Mac found at http://mac.github.com.
Now try to build the Ninject.iOS project; you will get several compiler errors in a file named DynamicMethodFactory.cs, as shown in the following screenshot:
Open DynamicMethodFactory.cs and notice the following code at the top of the file: #if !NO_LCG #region Using Directives using System; using System.Reflection; using System.Reflection.Emit; using Ninject.Components; #endregion /// *** File contents here *** #endif
It is not possible to use System.Reflection.Emit on iOS due to Apple's platform restrictions. Luckily, the library writers have created a preprocessor directive called NO_LCG (which stands for Lightweight Code Generation) to allow the library to run on platforms that do not support System.Reflection.Emit.
[ 201 ]
Third-party Libraries
To fix our iOS project, open the project options and navigate to the Build | Compiler section. Add NO_LCG to the Define Symbols field for both Debug and Release in the Configuration drop-down menu. Click on OK to save your changes. Notice how the entire file is now highlighted with a light gray color in Xamarin Studio, as shown in the following screenshot. This means that the code will be omitted from being compiled.
If you compile the project now, it will be completed successfully and a Ninject.iOS. dll file will be created, which you can reference from any Xamarin.iOS project. You can also reference the Ninject.iOS project directly instead of using the *.dll file. At this point, you might wish to repeat the process to create a Xamarin.Android class library project. Luckily, Xamarin.Android supports System.Reflection.Emit, so you can skip adding the additional preprocessor directive if you wish.
[ 202 ]
Chapter 9
Objective-C bindings
Xamarin has developed a sophisticated system to call native Objective-C libraries from C# in iOS projects. The core of Xamarin.iOS uses the same technology to call native Apple APIs in UIKit, CoreGraphics, and other iOS frameworks. Developers can create iOS binding projects to expose the Objective-C classes and methods to C# using simple interfaces and attributes. To aid in creating Objective-C bindings, Xamarin has created a small tool named Objective Sharpie that can process Objective-C header files for you and export the valid C# definitions to add to a binding project. This tool is a great starting point for most bindings that will get your binding three-fourths of the way complete, and you will want to hand-edit and fine-tune things to be more C#-friendly in a lot of cases. As an example, we will write a binding for the Google Analytics library for iOS. It is a simple and useful library that can track the user activities in your iOS or Android applications. At the time of writing, the version of the Google Analytics SDK was 3.10, so some of these instructions might change as new versions are released.
Working with Objective Sharpie
First, download and install Objective Sharpie from http://tinyurl.com/ ObjectiveSharpie, then perform the following steps: 1. Download the latest Google Analytics SDK for iOS available at https:// tinyurl.com/GoogleAnalyticsForiOS. 2. Create a new iOS Binding Project named GoogleAnalytics.iOS. 3. Run Objective Sharpie. 4. Select iOS 7.1 as Target SDK and click on Next. 5. Add all of the header (*.h) files included with the Google Analytics SDK; you can find these in the Library folder of the download. Click on Next. 6. Pick a suitable namespace such as GoogleAnalytics and click on Generate. 7. Copy the resulting ApiDefinition.cs file that was generated into your iOS binding project. 8. After a few seconds, your C# file will be generated. Click on Quit.
[ 203 ]
Third-party Libraries
You should have not received any error messages from Objective Sharpie during the process, and when finished, your screen should look like the following screenshot:
At the time of writing this module, Objective Sharpie does not work properly with Xcode 6.0 and higher. I recommend that you download Xcode 5.1.1 if you run into this issue. You can install two versions of Xcode side by side by renaming an existing one in Finder and installing a second one. You can find older Xcode downloads at https://developer.apple.com/downloads/index.action.
Now if you return to your binding project, you'll notice that Objective Sharpie has generated an interface definition for every class discovered in the header files of the library. It has also generated many enum values that the library uses and changed casing and naming conventions to follow C# more closely where possible. As you read through the binding, you'll notice several C# attributes that define different aspects about the Objective-C library such as the following: • BaseType: This declares an interface as an Objective-C class. The base class (also called superclass) is passed in to the attribute. If it has no base class, NSObject should be used.
[ 204 ]
Chapter 9
• Export: This declares a method or property on an Objective-C class. A string that maps the Objective-C name to the C# name is passed in. Objective-C method names are generally in the following form: myMethod:someParam:so meOtherParam. • Static: This marks a method or property as static in C#. • Bind: This is used on properties to map a getter or setter to a different Objective-C method. Objective-C properties can rename a getter or setter for a property. • NullAllowed: This allows null to be passed to a method or property. By default, an exception is thrown if this occurs. • Field: This declares an Objective-C field that is exposed as a public variable in C#. • Model: This identifies a class to Xamarin.iOS to have methods that can be optionally overridden. This is generally used on Objective-C delegates. • Internal: This flags the generated member with the C# internal keyword. It can be used to hide certain members that you don't want to expose to the outside world. • Abstract: This identifies an Objective-C method as required, which goes hand in hand with Model. In C#, it will generate an abstract method. The only other rule to know is how to define constructors. Xamarin had to invent a convention for this since C# interfaces do not support constructors. To define a constructor besides the default one, use the following code: [Export("initWithFrame:")] IntPtr Constructor(RectangleF frame);
This would define a constructor on the class that takes in RectangleF as a parameter. The method name Constructor and the return type IntPtr signal the Xamarin compiler to generate a constructor. Now, let's return to our binding project to finish setting up everything. If you compile the project at this point, you'll get a few compiler errors. Let's fix them one by one as follows: 1. Change the default namespace of the project to GoogleAnalytics. This setting is found in the project options by navigating to General | Main Settings. 2. Add libGoogleAnalyticsServices.a from the SDK download to the project.
[ 205 ]
Third-party Libraries
3. Add using statements for MonoTouch.Foundation, MonoTouch.UIKit, and MonoTouch.ObjCRuntime at the top of the ApiDefinition.cs file. 4. Remove the multiple duplicate declarations of GAILogLevel. You might also wish to move enumerations to the StructsAndEnums.cs file. 5. Remove the declaration for GAIErrorCode. 6. In the SetAll method of GAIDictionaryBuilder, rename the params parameter to parameters, as params is a reserved word in C#. 7. Remove the duplicate declarations for GAILogger, GAITracker, GAITrackedViewController, and any other duplicate classes you find. 8. Go through any Field declarations and change [Field("Foobar")] to [Field("Foobar", "__Internal")]. This tells the compiler where the field resides; in this case, it will be included internally in our binding project. 9. Remove all the Verify attributes. These are spots where Objective Sharpie was unsure of the operation it performed. In our example, all of them are fine so it is safe to remove them. One more error remains regarding Objective Sharpie not being able to generate C# delegates for methods that have callbacks. Navigate to the GAI interface and change the following method: [Export ("dispatchWithCompletionHandler:")] void DispatchWithCompletionHandler ( GAIDispatchResultHandler completionHandler);
You will also need to define the following delegate at the top of this file: public delegate void GAIDispatchResultHandler( GAIDispatchResult result);
After going through these issues, you should be able to compile the binding and get no errors. You could have read the Objective-C header files and written the definitions yourself by hand; however, using Objective Sharpie generally means a lot less work. At this point, if you try to use the library in an iOS project, you would get an error such as the following: Error MT5210: Native linking failed, undefined symbol: _FooBar. Please verify that all the necessary frameworks have been referenced and native libraries are properly linked in.
[ 206 ]
Chapter 9
We need to define the other frameworks and libraries that the Objective-C library uses. This is very similar to how references work in C#. If we review the Google Analytics documentation, it says that you must add CoreData, SystemConfiguration, and libz.dylib. Additionally, you must add a weak reference to AdSupport. Open libGoogleAnalyticsServices.linkwith.cs that was created automatically nested underneath the *.a file and make the following changes: [assembly: LinkWith ("libGoogleAnalyticsServices.a", LinkTarget.ArmV7 | LinkTarget.ArmV7s | LinkTarget.Simulator, LinkerFlags = "-lz", Frameworks = "CoreData SystemConfiguration", WeakFrameworks = "AdSupport", ForceLoad = true)]
We added references to frameworks in the following ways: • Frameworks: Add them to the Frameworks value on the LinkWith attribute, delimited by spaces. • Weak Frameworks: Add them to the WeakFrameworks property on the LinkWith attribute in the same manner. Weak frameworks are libraries that can be ignored if they are not found. In this case, AdSupport was added in iOS 6; however, this library will still work on older versions of iOS. • Dynamic Libraries: Libraries such as libz.dylib can be declared in LinkerFlags. Generally, you drop the .dylib extension and replace lib with –l. After these changes are implemented, you will be able to successfully use the library from iOS projects. For complete documentation on Objective-C bindings, visit the Xamarin documentation site at http://docs.xamarin.com/ios.
Java bindings
In the same manner as iOS, Xamarin has provided full support for calling into Java libraries from C# with Xamarin.Android. The native Android SDKs function in this way and developers can leverage the Android Java Bindings project to take advantage of other native Java libraries in C#. The main difference here is that not a lot has to be done by hand in comparison to Objective-C bindings. The Java syntax is very similar to that of C#, so many mappings are exactly one-to-one. In addition, Java has metadata information included with its libraries, which Xamarin uses to automatically generate the C# code required for calling into Java.
[ 207 ]
Third-party Libraries
As an example, let's make a binding for the Android version of the Google Analytics SDK. Before we begin, download the SDK from http://tinyurl.com/ GoogleAnalyticsForAndroid. At the time of writing, the version of the Android SDK 3.01, so some of these instructions might change over time. Let's begin creating a Java binding as follows: 1. Start a new Android Java Bindings Library project in Xamarin Studio. You can use the same solution as we did for iOS if you wish. 2. Name the project GoogleAnalytics.Droid. 3. Add libGoogleAnalyticsServices.jar from the Android SDK to the project under the Jars folder. By default, the build action for the file will be EmbeddedJar. This packs the jar file into the DLL, which is the best option for ease of use. 4. Build the project. You will get a few errors, which we'll address in a moment. Most of the time you spend working on Java bindings will be to fix small issues that prevent the generated C# code from compiling. Don't fret; a lot of libraries will work on the first try without having to make any changes at all. Generally, the larger the Java library is, the more work you have to do to get it working from C#. The following are the types of issues you might run into: • Java obfuscation: If the library is run through an obfuscation tool such as ProGuard, the class and method names might not be valid C# names. • Covariant return types: Java has different rules for return types in overridden virtual methods than C# does. For this reason, you might need to modify the return type for the generated C# code to compile. • Visibility: The rules that Java has for accessibility are different from those of C#; the visibility of methods in subclasses can be changed. Sometimes, you will have to change visibility in C# to get it to compile. • Naming collisions: Sometimes, the C# code generator can get things a bit wrong and generate two members or classes with the same name. • Java generics: The use of generic classes in Java can often cause issues in C#. So before we get started on solving these issues in our Java binding, let's first clean up the namespaces in the project. Java namespaces are of the form com.mycompany. mylibrary by default, so let's change the definition to match C# more closely. In the Transforms directory of the project, open Metadata.xml and add the following XML tag inside the root metadata node: GoogleAnalytics.Tracking [ 208 ]
Chapter 9
The attr node tells the Xamarin compiler what needs to be replaced in the Java definition with another value. In this case, we are replacing managedName of the package with GoogleAnalytics.Tracking because it will make much more sense in C#. The path value might look a bit strange, which is because it uses an XML matching query language named XPath. In general, just think of it as a pattern matching query for XML. For full documentation on XPath syntax, check out some of the many resources online such as http://w3schools.com/xpath. You might be asking yourself at this point, what is the XPath expression matching against? Return to Xamarin Studio and right-click on the solution at the top. Navigate to Display Options | Show All Files. Open api.xml under the obj/Debug folder. This is the Java definition file that describes all the types and methods within the Java library. If you notice, the XML here directly correlates to the XPath expressions we'll be writing. In our next step, let's remove all the packages (or namespaces) we don't plan on using in this library. This is generally a good idea for large libraries since you don't want to waste time fixing issues with parts of the library you won't even be calling from C#. Note that it doesn't actually remove the Java code; it just prevents the generation of any C# declarations for calling it from C#. Add the following declarations in Metadata.xml:
[ 209 ]
Third-party Libraries
Now when you build the library, we can start resolving issues. The first error you will receive will be something like the following: GoogleAnalytics.Tracking.GoogleAnalytics.cs(74,74): Error CS0234: The type or namespace name `TrackerHandler' does not exist in the namespace `GoogleAnalytics.Tracking'. Are you missing an assembly reference?
If we locate TrackerHandler within the api.xml file, we'll see the following class declaration:
So, can you spot the problem? We need to fill out the visibility XML attribute, which for some reason is blank. Add the following line to Metadata.xml: public
This XPath expression will locate the TrackerHandler class inside the com.google. analytics.tracking.android package and change visibility to public. If you build the project now, it will complete successfully with one warning. In Java binding projects, it is a good idea to fix warnings since they generally indicate that a class or method is being omitted from the binding. Notice the following warning: GoogleAnalytics.Droid: Warning BG8102: Class GoogleAnalytics.Tracking.CampaignTrackingService has unknown base type android.app.IntentService (BG8102) (GoogleAnalytics.Droid)
To fix this issue, locate the type definition for CampaignTrackingService in api. xml, which is as follows:
[ 210 ]
Chapter 9
The way to fix the issue here is to change the base class to the Xamarin.Android definition for IntentService. Add the following code to Metadata.xml: mono.android.app.IntentService
This changes the extends attribute to use the IntentService found in Mono. Android.dll. I located the Java name for this class by opening Mono.Android.dll in Xamarin Studio's Assembly Browser. Let's take a look at the Register attribute, as shown in the following screenshot:
To inspect the *.dll files in Xamarin Studio, you merely have to open them. You can also double-click on any assembly in the References folder in your project. If you build the binding project now, we're left with one last error, which is as follows: GoogleAnalytics.Tracking.CampaignTrackingService.cs(24,24): Error CS0507: `CampaignTrackingService.OnHandleIntent(Intent)': cannot change access modifiers when overriding `protected' inherited member `IntentService.OnHandleIntent(Android.Content.Intent)' (CS0507) (GoogleAnalytics.Droid)
If you navigate to the api.xml file, you can see the definition for OnHandleIntent as follows:
[ 211 ]
Third-party Libraries
We can see here that the Java method for this class is public, but the base class is protected. So, the best way to fix this is to change the C# version to protected as well. Writing an XPath expression to match this is a bit more complicated, but luckily Xamarin has an easy way to retrieve it. If you double-click on the error message in the Errors pad of Xamarin Studio, you'll see the following comment in the generated C# code: // Metadata.xml XPath method reference: path="/api/package[@name='com.google.analytics .tracking.android']/class[@name='CampaignTrackingService'] /method[@name='onHandleIntent' and count(parameter)=1 and parameter[1][@type='android.content.Intent']]"
Copy this value to path and add the following to Metadata.xml: protected
Now, we can build the project and get zero errors and zero warnings. The library is now ready for use within your Xamarin.Android projects. However, if you start working with the library, notice how the parameter names for the methods are p0, p1, p2, and so on. Here are a few method definitions of the EasyTracker class: public public public public
static EasyTracker GetInstance(Context p0); static void SetResourcePackageName(string p0); virtual void ActivityStart(Activity p0); virtual void ActivityStop(Activity p0);
You can imagine how difficult it would be to consume a Java library without knowing the proper parameter names. The reason the parameters are named this way is because the Java metadata for its libraries does not include the information to set the correct name for each parameter. So, Xamarin.Android does the best thing it can and autonames each parameter sequentially. To rename the parameters in this class, we can add the following to Metadata.xml: context
[ 212 ]
Chapter 9 packageName activity activity
On rebuilding the binding project, this will effectively rename the parameters for these four methods in the EasyTracker class. At this time, I would recommend that you go through the classes you plan on using in your application and rename the parameters so that it will make more sense to you. You might need to refer to the Google Analytics documentation to get the naming correct. Luckily, there is a javadocs.zip file included in the SDK that provides HTML reference for the library. For a full reference on implementing Java bindings, make sure you check out Xamarin's documentation site at http://docs.xamarin.com/android. There are certainly more complicated scenarios than what we ran into when creating a binding for the Google Analytics library.
Summary
In this chapter, we added libraries from the Xamarin Component Store to Xamarin projects and ported an existing C# library, Ninject, to both Xamarin.iOS and Xamarin. Android. Next, we installed Objective Sharpie and explored its usage to generate Objective-C bindings. Finally, we wrote a functional Objective-C binding for the Google Analytics SDK for iOS and a Java binding for the Google Analytics SDK for Android. We also wrote several XPath expressions to clean up the Java binding. There are several available options to use the existing third-party libraries from your Xamarin.iOS and Xamarin.Android applications. We looked at everything from using the Xamarin Component Store, porting existing code and setting up Java and Objective-C libraries to be used from C#. In the next chapter, we will cover the Xamarin.Mobile library as a way to access a user's contacts, camera, and GPS location.
[ 213 ]
Contacts, Camera, and Location Some of the most vital features used by mobile applications today are based on the new types of data that can be collected by our devices. Features such as GPS location and camera are staples in modern applications such as Instagram or Twitter. It's difficult to develop an application and not use some of these native features. So, let's explore our options to take advantage of this functionality with Xamarin. In this chapter, we will do the following: • Introduce the Xamarin.Mobile library • Read the address book on Android and iOS • Retrieve the GPS location of our device • Pull photos from the camera and photo library
Introducing Xamarin.Mobile
To simplify the development of these features across multiple platforms, Xamarin has developed a library called Xamarin.Mobile. It delivers a single API to access the contacts, GPS location, heading of the screen, camera, and photo library for iOS, Android, and even Windows platforms. It also takes advantage of the Task Parallel Libraries (TPL) to deliver a modern C# API that will make our developers more productive than what their native alternatives would. This gives you the ability to write nice, clean, asynchronous code using the async and await keywords in C#. You can also reuse the same code in iOS and Android, minus a few differences that are required by the Android platform.
Contacts, Camera, and Location
To install Xamarin.Mobile, open the Xamarin Component Store in Xamarin Studio, and add the Xamarin.Mobile component to a project as shown in the following screenshot. You're going to use the following features (of the component):
Before we dig further into using Xamarin.Mobile, let's review the namespaces and functionality available with the library: • Xamarin.Contacts: This contains classes that enable you to interact with the full address book. It includes everything from the contact's photo, phone numbers, address, e-mail, website, and so on. • Xamarin.Geolocation: This combined with the accelerometer gives you access to the device's GPS location, including the altitude, heading, longitude, latitude, and speed. You can track the device's position explicitly or listen for GPS position changes over time. • Xamarin.Media: This grants access to the device's cameras (if there is more than one) and built-in photo library. This is an easy way to add photo selection capabilities to any application.
[ 216 ]
Chapter 10
For the full documentation of Xamarin.Mobile, visit the API documentation with the Component Store at http://componentsapi.xamarin.com. You can also view it in the native Mono documentation browser by clicking on Open API Documentation when viewing the component in Xamarin Studio. Xamarin.Mobile is also an open source project with the standard Apache 2.0 license. You can contribute to the project or submit issues to the GitHub page at https:// github.com/xamarin/Xamarin.Mobile. Feel free to use Xamarin.Mobile in your applications or fork, and modify it for your own purposes.
Accessing contacts
To begin our exploration of what Xamarin.Mobile offers, let's access the address book within a Xamarin application. For iOS, the first step is to make a Single View Application project by navigating to iOS | iPhone Storyboard. Make sure you add Xamarin.Mobile to the project from the Component Store. Now, let's implement a simple UITableView with a list of contacts: 1. Open the MainStoryboard.storyboard file. Delete any existing controllers created by the project template. 2. Create UINavigationController with a UITableViewController as its root child controller. 3. Set the Class of UITableViewController to ContactsController by navigating to Properties | Widget in the iOS designer. 4. Save the storyboard file and return to Xamarin Studio. Open the automatically generated ContactsController.cs and start implementing the table view. Add using Xamarin.Contacts; to the top of the file and make the following changes to the controller: public partial class ContactsController : UITableViewController, IUITableViewDataSource { public ContactsController (IntPtr handle) : base (handle) { Title = "Contacts"; } }
[ 217 ]
Contacts, Camera, and Location
We filled out the title for the navigation bar, "Contacts", and set the class to implement IUITableViewDataSource. This is a new type of interface that Xamarin has created to simplify using Objective-C protocols from C#. It is exactly the same as creating a class that inherits from UITableViewSource, as we did in the earlier chapters, but you can do it from your controller as well. Xamarin has done some tricks here. They created an interface with the methods that can be optionally implemented, which isn't something that C# supports. This type of interface can make your code a bit cleaner by reducing the need for a new class, which is great for very simple controllers. Next, let's add some code to load the contacts: Contact[] contacts; public async override void ViewDidLoad() { base.ViewDidLoad(); try { var book = new AddressBook(); await book.RequestPermission(); contacts = book.ToArray(); } catch { new UIAlertView("Oops!", "Something went wrong, try again later.", null, "Ok").Show(); } }
To use Xamarin.Mobile for loading contacts, you must first create an AddressBook object. Next, we have to call RequestPermissions in order to ask the user for permission to access the address book. This is an important step since it is required by iOS devices before an application can access the user's contacts. This prevents potentially nefarious applications from retrieving contacts without the user's knowledge. Android, on the other hand, only presents these permissions before installing an application. Next, we used the System.Linq extension method ToArray to enumerate over the address book and store it in a member variable named contacts. You can also use foreach over the AddressBook object depending on your needs.
[ 218 ]
Chapter 10
If you were to compile and run the application at this point, you would be greeted by the standard iOS pop up requesting access to contacts, as shown in the following screenshot:
If you accidentally hit Don't Allow, you can change this setting by navigating to Settings | Privacy | Contacts on the device. In the iOS Simulator, you can also reset all the privacy prompts in the simulator by closing the application and navigating to Settings | General | Reset | Reset Location & Privacy. This is a good tip to know whether you need to retest during development. So, for the next step, we'll need to implement the IUITableViewDataSource interface so that we can work with the array of contacts and display them on the screen. Add the following methods to the controller just like you would to UITableViewSource: public override int RowsInSection( UITableView tableview, int section)
[ 219 ]
Contacts, Camera, and Location { return contacts != null ? contacts.Length : 0; } public override UITableViewCell GetCell( UITableView tableView, NSIndexPath indexPath) { var contact = contacts [indexPath.Row]; var cell = tableView.DequeueReusableCell(CellName); if (cell == null) cell = new UITableViewCell( UITableViewCellStyle.Default, CellName); cell.TextLabel.Text = contact.LastName + ", " + contact.FirstName; return cell; }
Also, add a CellName constant string to the class by selecting a string identifier such as ContactCell. Now, if you compile and run the program, you'll be able to see the list of contacts on the device. The following screenshot shows the default list of contacts in the iOS Simulator:
[ 220 ]
Chapter 10
Retrieving contacts on Android
In a very similar fashion, we can retrieve a list of contacts in Android with Xamarin. Mobile. All of the APIs in Xamarin.Mobile are identical in Android with the exception of the requirement of Android.Content.Context to be passed in a few places. This is because many native Android APIs require a reference to the current activity (or to the other context such as Application) in order to function properly. To begin, create a standard Android application project by navigating to Android | Android Application in Xamarin Studio. Make sure you add Xamarin.Mobile to the project from the Component Store. In a parallel iOS, let's create a simple ListView to display a list of contacts as follows: 1. Open the Main.axml file from the layout folder in the Resources directory in the Android designer. 2. Remove the default button from the project template and add ListView to the layout. 3. Set its Id to @+id/contacts. 4. Save the file and open MainActivity.cs so that we can make some changes to the code. Let's begin by removing most of the code; we don't need the code that came from the project template. You will also need to add a using statement for Xamarin. Contacts. Next, let's implement a simple BaseAdapter class inside the MainActivity class as follows: class ContactsAdapter : BaseAdapter { public Contact[] Contacts { get; set; } public override long GetItemId(int position) { return position; } public override View GetView( int position, View convertView, ViewGroup parent) { var contact = this [position]; var textView = convertView as TextView; if (textView == null) { textView = new TextView(parent.Context); }
[ 221 ]
Contacts, Camera, and Location textView.Text = contact.LastName + ", " + contact.FirstName; return textView; } public override int Count { get { return Contacts == null ? 0 : Contacts.Length; } } public override Contact this[int index] { get { return Contacts [index]; } } }
This will display each contact in TextView for each row in ListView. One thing that we've done here in order to simplify things is add a property for the array of contacts. This should be pretty straightforward and similar to what we did in the previous chapters. Now, let's set up the adapter in OnCreate as follows: protected async override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); var listView = FindViewById(Resource.Id.contacts); var adapter = new ContactsAdapter(); listView.Adapter = adapter; try { var book = new AddressBook(this); await book.RequestPermission(); adapter.Contacts = book.ToArray(); adapter.NotifyDataSetChanged(); } catch { new AlertDialog.Builder(this).SetTitle("Oops") .SetMessage("Something went wrong, try again later.") .SetPositiveButton("Ok", delegate { }).Show(); } }
[ 222 ]
Chapter 10
This code calls Xamarin.Mobile that is identical to what we did on the code for iOS except that here, this had to be passed for the Android Context in the constructor for AddressBook. Our code changes are complete; however, if you run the application right now, an exception would be thrown. Android requires permission in the manifest file, which will notify the user of its access to the address book when downloaded from Google Play. We must create an AndroidManifest.xml file and declare a permission as follows: 1. Open the project options for the Android project. 2. Select the Android Application tab under Build. 3. Click on Add Android manifest. 4. Under the Required permissions section, check ReadContacts. 5. Click on OK to save your changes. Now, if you run the application, you will get a list of all the contacts on the device, as shown in the following screenshot:
[ 223 ]
Contacts, Camera, and Location
Looking up GPS location
Using Xamarin.Mobile to track a user's GPS location is as simple as accessing their contacts. There is a similar process for setting up access on iOS and Android, but in the case of location, you don't have to request permission from code. iOS will automatically show the standard alert requesting the permission. Android, on the other hand, merely requires a manifest setting. As an example, let's create an application that displays a list of GPS location updates over time. Let's begin with an iOS example by creating a Single View Application project. This can be done by navigating to iOS | iPhone Storyboard and clicking on Single View Application, just like we did in the previous section. Make sure you add Xamarin.Mobile to the project from the Component Store. Now, let's implement a simple UITableView to display a list of GPS updates as follows: 1. Open the MainStoryboard.storyboard file. Delete any existing controllers created by the project template. 2. Create UINavigationController with UITableViewController as its root child controller. 3. Set the class of UITableViewController to LocationController by navigating to Properties | Widget in the iOS designer. 4. Save the storyboard file and return to Xamarin Studio. Open LocationController.cs and let's start by setting up our GPS to update a table view over time. Add using Xamarin.Geolocation; to the top of the file. We can set up some member variables and create our Geolocator object in the controller's constructor as follows: Geolocator locator; List messages = new List(); public LocationController (IntPtr handle) : base (handle) { Title = "GPS"; locator = new Geolocator(); locator.PositionChanged += OnPositionChanged; locator.PositionError += OnPositionError; }
[ 224 ]
Chapter 10
Next, we can set up our event handlers as follows: void OnPositionChanged (object sender, PositionEventArgs e) { messages.Add(string.Format("Long: {0:0.##} Lat: {1:0.##}", e.Position.Longitude, e.Position.Latitude)); TableView.ReloadData(); } void OnPositionError (object sender, PositionErrorEventArgs e) { messages.Add(e.Error.ToString()); TableView.ReloadData(); }
These will add a message to the list when there is an error or a location change. We used string.Format to only display the longitude and latitude up to two decimal places. Next, we have to actually tell Geolocator to start listening for GPS updates. We can do this in ViewDidLoad as follows: public override void ViewDidLoad() { base.ViewDidLoad(); locator.StartListening(1000, 50); }
In the preceding code, 1000 is a hint for the minimum time to update the GPS location, and the value 50 is a hint for the number of meters that will trigger a position update. Last but not least, we need to set up the table view. Set up LocationController to implement IUITableViewDataSource and add the following methods to the controller: public override int RowsInSection( UITableView tableview, int section) { return messages.Count; }
[ 225 ]
Contacts, Camera, and Location public override UITableViewCell GetCell( UITableView tableView, NSIndexPath indexPath) { var cell = tableView.DequeueReusableCell(CellName); if (cell == null) cell = new UITableViewCell( UITableViewCellStyle.Default, CellName); cell.TextLabel.Text = messages [indexPath.Row]; return cell; }
If you compile and run the application, you should see an iOS permission prompt followed by the longitude and latitude in the table view over time, as shown in the following screenshot:
Implementing GPS location on Android
Just as in the previous section, using Xamarin.Mobile for GPS location is almost identical to the APIs we used in iOS. To begin with our Android example, go to Android | Android Application in Xamarin Studio. Make sure you add Xamarin. Mobile to the project from the Component Store. [ 226 ]
Chapter 10
Let's create ListView to display a list of messages of the GPS location updates as follows: 1. Open the Main.axml file from the layout folder under the Resources directory in the Android designer. 2. Remove the default button from the project template and add ListView to the layout. 3. Set its Id to @+id/locations. 4. Save the file and open MainActivity.cs so that we can make some code changes. As usual, remove any extra code that was created by the project template. Next, add a using statement for Xamarin.Geolocation. Then, add a simple BaseAdapter to the MainActivity class as follows: class Adapter : BaseAdapter { List messages = new List(); public void Add(string message) { messages.Add(message); NotifyDataSetChanged(); } public override long GetItemId(int position) { return position; } public override View GetView( int position, View convertView, ViewGroup parent) { var textView = convertView as TextView; if (textView == null) textView = new TextView(parent.Context); textView.Text = messages [position]; return textView; } public override int Count { get { return messages.Count; } } public override string this[int index]
[ 227 ]
Contacts, Camera, and Location { get { return messages [index]; } } }
This is similar to other Android adapters we have set up in the past. One difference here is that we made a member variable that contains a List of messages and a method to add the new messages to the list. Now, let's add a few methods to the MainActivity class in order to set up the GPS location updates as follows: Geolocator locator; Adapter adapter; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); var listView = FindViewById(Resource.Id.locations); listView.Adapter = adapter = new Adapter(); locator = new Geolocator(this); locator.PositionChanged += OnPositionChanged; locator.PositionError += OnPositionError; } protected override void OnResume() { base.OnResume(); locator.StartListening(1000, 50); } protected override void OnPause() { base.OnPause(); locator.StopListening(); } void OnPositionChanged (object sender, PositionEventArgs e) { adapter.Add(string.Format("Long: {0:0.##} Lat: {1:0.##}", e.Position.Longitude, e.Position.Latitude)); } void OnPositionError (object sender, PositionErrorEventArgs e) { adapter.Add(e.Error.ToString()); } [ 228 ]
Chapter 10
Again, this looks identical to the code for iOS except for the constructor for Geolocator. If you run the application at this point, it would start with no errors. However, no events will be fired from the Geolocator object. We first need to add a permission to access the location from the Android Manifest file. It is also a good idea to start the locator in OnResume and stop it in OnPause. This will conserve the battery by stopping GPS location when this activity is no longer on the screen. Let's create an AndroidManifest.xml file and declare two permissions as follows: 1. Open the project options for the Android project. 2. Select the Android Application tab under Build. 3. Click on Add Android manifest. 4. Under the Required permissions section, check AccessCoarseLocation and AccessFineLocation. 5. Click on OK to save your changes. Now, if you compile and run the application, you will get the GPS location updates over time, as shown in the following screenshot:
[ 229 ]
Contacts, Camera, and Location
Accessing the photo library and camera
The last major feature of Xamarin.Mobile is the ability to access photos in order to give users the ability to add their own content to your applications. Using a class called MediaPicker, you can pull photos from the device's camera or photo library and optionally display your own UI for the operation. Let's create an application that loads an image from the camera or photo library on the press of a button and displays it on the screen. To begin with, create a Single View Application project by going to iOS | iPhone Storyboard | Single View Application in Xamarin Studio. Make sure to add Xamarin.Mobile to the project from the Component Store. Now, let's implement a screen with two UIButton and a UIImageView as follows: 1. Open the MainStoryboard.storyboard file. Delete any existing controllers created by the project template. 2. Create UIViewController with one UIImageView and two UIButton named Library and Camera. 3. Set the class of UITableViewController to ContactsController by navigating to Properties | Widget in the iOS designer. 4. Create the Name field for each view in the controller named imageView, library, and camera respectively. 5. Save the storyboard file and return to Xamarin Studio. Now, open PhotoController.cs and add the following code in ViewDidLoad: MediaPicker picker; public override void ViewDidLoad() { base.ViewDidLoad(); picker = new MediaPicker(); if (!picker.IsCameraAvailable) camera.Enabled = false; camera.TouchUpInside += OnCamera; library.TouchUpInside += OnLibrary; }
Note that we have to check IsCameraAvailable and disable the camera button. There are iOS devices such as the first generation iPad that could possibly not have a camera. Besides this, we just need to create an instance of MediaPicker that can be used when you click on each button. [ 230 ]
Chapter 10
Now, let's add a method for each button's TouchUpInside event and a couple of other helper methods as follows: async void OnCamera (object sender, EventArgs e) { try { var file = await picker.TakePhotoAsync( new StoreCameraMediaOptions()); imageView.Image = ToImage(file); } catch { ShowError(); } } async void OnLibrary (object sender, EventArgs e) { try { var file = await picker.PickPhotoAsync(); imageView.Image = ToImage(file); } catch { ShowError(); } } UIImage ToImage(MediaFile file) { using (var stream = file.GetStream()) { using (var data = NSData.FromStream(stream)) { return UIImage.LoadFromData(data); } } } void ShowError() { new UIAlertView("Oops!", "Something went wrong, try again later.", null, "Ok").Show(); }
[ 231 ]
Contacts, Camera, and Location
Using MediaPIcker is pretty straightforward; you merely have to call TakePhotoAsync or PickPhotoAsync to retrieve a MediaFile instance. Then, you can call GetStream to do what you want with the image data. In our case, we created UIImage to display directly in UIImageView. It is also necessary to use a try-catch block in case something unexpected happens or the user cancels. You should now be able to run the app and select a photo to be viewed on the screen. The following screenshot shows a nice default photo from the iOS simulator that I selected from the photo library:
[ 232 ]
Chapter 10
Accessing photos on Android
In comparison to iOS, we have to use a slightly different pattern on Android to retrieve photos from the camera or photo library. A common pattern in Android is that it calls StartActivityForResult to launch an activity from another application. When this activity is completed, OnActivityResult will be called from your activity. Because of this, Xamarin.Mobile could not use the same APIs on Android as the other platforms. To start our example, create an Android Application project by going to Android | Android Application in Xamarin Studio. Make sure you add Xamarin.Mobile to the project from the Component Store. Let's create two Buttons and an ImageView to mimic our UI on iOS as follows: 1. Open the Main.axml file from the layout folder under the Resources directory in the Android designer. 2. Remove the default button from the project template, and add two new Button named Library and Camera. 3. Set their Id to @+id/library and @+id/camera, respectively. 4. Create an ImageView with an Id of @+id/imageView. 5. Save the file and open MainActivity.cs so that we can make changes to our code. As usual, remove any extra code that was created by the project template. Next, add a using statement for Xamarin.Media. Then, we can add a new OnCreate method and some member variables for our activity as follows: MediaPicker picker; ImageView imageView; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); var library = FindViewById(Resource.Id.library); var camera = FindViewById(Resource.Id.camera); imageView = FindViewById(Resource.Id.imageView); picker = new MediaPicker(this); library.Click += OnLibrary; camera.Click += OnCamera; if (!picker.IsCameraAvailable) camera.Enabled = false; }
[ 233 ]
Contacts, Camera, and Location
We retrieved the instance of our views and created a new MediaPicker by passing our activity as Context to its constructor. We hooked up some Click event handlers, and disabled the camera button since a camera is not available. Next, let's implement the two Click event handlers as follows: void OnLibrary (object sender, EventArgs e) { var intent = picker.GetPickPhotoUI(); StartActivityForResult (intent, 1); } void OnCamera (object sender, EventArgs e) { var intent = picker.GetTakePhotoUI(new StoreCameraMediaOptions { Name = "test.jpg", Directory = "PhotoPicker" }); StartActivityForResult (intent, 1); }
In each case, we make a call to GetPickPhotoUI or GetTakePhotoUI in order to get an instance of an Android Intent object. This object is used to start the new activities within an application. StartActivityForResult will also start the Intent object, expecting a result to be returned from the new activity. We also set some values with StoreCameraMediaOptions to specify a filename and temporary directory to store the photo. Next, we need to implement OnActivityResult in order to handle what will happen when the new activity is completed: protected async override void OnActivityResult( int requestCode, Result resultCode, Intent data) { if (resultCode == Result.Canceled) return; var file = await data.GetMediaFileExtraAsync(this); using (var stream = file.GetStream()) { imageView.SetImageBitmap(await BitmapFactory.DecodeStreamAsync(stream)); } }
[ 234 ]
Chapter 10
If this is successful, we retrieve MediaFile and load a new Bitmap with the returned Stream. Next, all that is needed is to call SetImageBitmap to display the image on the screen. Let's create an AndroidManifest.xml file and declare two permissions as follows: 1. Open the project options for the Android project. 2. Select the Android Application tab under Build. 3. Click on Add Android manifest. 4. Under the Required permissions section, check Camera and WriteExternalStorage. 5. Click on OK to save your changes. You should now be able to run the application and load photos to be displayed on the screen, as shown in the following screenshot:
[ 235 ]
Contacts, Camera, and Location
Summary
In this chapter, we discovered the Xamarin.Mobile library and how it can accelerate common native tasks in a cross-platform way. We retrieved the contacts from the address book and set up GPS location updates over time. Lastly, we loaded photos from the camera and photo library. Using the native APIs directly would mean twice as much code on each platform, so we saw how the Xamarin.Mobile library is a useful abstraction that can reduce some development time. After completing this chapter, you should have a complete grasp of the Xamarin. Mobile library and the common functionality it provides for cross-platform development. It gives clean, modern APIs that offer the async/await functionality that can be accessed across iOS, Android, and Windows Phone. Accessing contacts, GPS, and photos across platforms is very straightforward with Xamarin.Mobile. In the next chapter, we will cover the steps of how to submit applications to the iOS App Store and Google Play. This will include how to prepare your app to pass the iOS guidelines as well as properly signing up your app for Google Play.
[ 236 ]
Xamarin.Forms Since the beginning of Xamarin's lifetime as a company, their motto has always been to expose the native APIs on iOS and Android directly to C#. This was a great strategy at the beginning, because applications built with Xamarin.iOS or Xamarin.Android were pretty much indistinguishable from a native Objective-C or Java application. Code sharing was generally limited to non-UI code that left a potential gap to fill in the Xamarin ecosystem: a cross-platform UI abstraction. Xamarin.Forms is the solution to this problem, a cross-platform UI framework that renders native controls on each platform. Xamarin.Forms is a great solution for those who know C# (and XAML), but also might not want to get into the full details of using the native iOS and Android APIs. In this chapter, we will cover the following: • Create "Hello World" in Xamarin.Forms • Discuss Xamarin.Forms architecture • Use XAML with Xamarin.Forms • Cover data binding and MVVM with Xamarin.Forms
Creating Hello World in Xamarin.Forms To understand how a Xamarin.Forms application is put together, let's begin by creating a simple "Hello World" application. Open Xamarin Studio and perform the following steps: • Create a new solution. • Navigate to the C# | Mobile Apps section. • Create a new Blank App (Xamarin.Forms Portable) solution. • Name your solution something appropriate such as HelloForms.
Xamarin.Forms
Notice the three new projects that were successfully created: HelloForms, HelloForms.Android, and HelloForms.iOS. In Xamarin.Forms applications, the bulk of your code will be shared, and each platform-specific project is just a small amount of code that starts the Xamarin.Forms framework. Let's examine the minimal parts of a Xamarin.Forms application: • App.cs in the HelloForms PCL library. This class holds the startup page of the Xamarin.Forms application. A simple static method, GetMainPage(), returns the startup page of the application. In the default project template, • A ContentPage is created with a single label that will be rendered as a UILabel on iOS and a TextView on Android. • MainActivity.cs in the HelloForms.Android Android project. This is the main launcher activity of the Android application. The important part for Xamarin.Forms here is the call to Forms.Init(this, bundle) that initializes the Android-specific portion of the Xamarin.Forms framework. Next is a call to SetPage(App.GetMainPage()) that displays the native version of the main Xamarin.Forms page. • AppDelegate.cs in the HelloForms.iOS iOS project. This is very similar to Android, except iOS applications startup via a UIApplicationDelegate class. Forms.Init() will initialize the iOS-specific parts of Xamarin.Forms, while App.GetMainPage().CreateViewController() will generate a native controller that can be used as the RootViewController of the main window of the application.
[ 238 ]
Chapter 11
Go ahead and run the iOS project; you should see something similar to the following screenshot:
[ 239 ]
Xamarin.Forms
If you run the Android project, you will get a UI very similar to the iOS, but using the native Android controls, as shown in the following screenshot:
Even though not covered in this module, Xamarin.Forms also supports Windows Phone applications. However, a PC running Windows and Visual Studio is required to develop for Windows Phone. If you can get a Xamarin.Forms application working on iOS and Android, then getting a Windows Phone version working should be a piece of cake.
[ 240 ]
Chapter 11
Understanding the architecture behind Xamarin.Forms
Getting started with Xamarin.Forms is very easy, but it is always good to look behind the curtain to understand what is happening behind the scenes. In the earlier chapters of this module, we created a cross-platform application using native iOS and Android APIs directly. Certain applications are much more suited for this development approach, so understanding the difference between a Xamarin.Forms application and a plain Xamarin application is important to know when choosing what framework is best suited for your app. Xamarin.Forms is an abstraction over the native iOS and Android APIs that you can call directly from C#. So, Xamarin.Forms is using the same APIs you would in a plain Xamarin application, while providing a framework that allows you to define your UIs in a cross-platform way. An abstraction layer such as this is in many ways a very good thing because it gives you the benefit of sharing the code driving your UI as well as any backend C# code that could have also been shared in a standard Xamarin app. The main disadvantage, however, is a slight hit in performance and being limited by the Xamarin.Forms framework as far as what types of controls are available. Xamarin.Forms also gives the option of writing renderers that allow you to override your UI in a platform-specific way. However, in my opinion, renderers are still somewhat limited in what can be achieved. See the difference in a Xamarin.Forms application and a traditional Xamarin app in the following diagram:
[ 241 ]
Xamarin.Forms
In both the applications, the business logic and backend code of the application can be shared, but Xamarin.Forms gives you an enormous benefit by allowing your UI code to be shared as well. Additionally, Xamarin.Forms applications have two project templates to choose from, so let's cover each option: • Xamarin.Forms Shared: This creates a shared project with all of your Xamarin.Forms code, an iOS project, and an Android project. • Xamarin.Forms Portable: This creates a portable class library that contains all the shared Xamarin.Forms code, an iOS project, and an Android project. In general, both the options will work fine for any application. Shared projects are basically a collection of code files that get added automatically to another project referencing it. Using a shared project allows you to use preprocessor statements to implement platform-specific code. Portable class library projects, on the other hand, create a portable .NET assembly that can be used on iOS, Android, and various other platforms. PCLs can't use preprocessor statements, so you generally set up platform-specific code with an interface or abstract/base classes. In most cases, I think a portable class library is a better option since it inherently encourages better programming practices. You can refer to Chapter 3, Code Sharing between iOS and Android, for details on the advantages and disadvantages of these two code-sharing techniques.
Using XAML in Xamarin.Forms
In addition to defining Xamarin.Forms controls from C# code, Xamarin has provided the tool to develop your UI in Extensible Application Markup Language (XAML). XAML is a declarative language that is basically a set of XML elements that map to a certain control in the Xamarin.Forms framework. Using XAML is comparable to what you would think of using HTML to define the UI on a web page, with the exception that XAML in Xamarin.Forms creates C# objects that represent a native UI. To understand how XAML works in Xamarin.Forms, let's create a new page with lots of UI on it: 1. Create a new Xamarin.Forms Portable solution by navigating to C# | Mobile Apps | Blank App (Xamarin.Forms Portable). 2. Name the project something appropriate such as UIDemo. 3. Add a new file by navigating to the Forms | Forms ContentPage XAML item template. Name the page UIDemoPage. 4. Open UIDemoPage.xaml. [ 242 ]
Chapter 11
Now, let's edit the XAML code. Add the following XAML code between the tag:
Go ahead and run the application on iOS and Android. Your application will look something like the following screenshot:
[ 243 ]
Xamarin.Forms
Then, on Android, Xamarin.Forms will render the screen in the same way, but with the native Android controls:
First, we created a StackLayout control, which is a container for other controls. It can layout controls either vertically or horizontally one by one as defined by the Orientation value. We also applied a padding of 10 around the sides and bottom, and 20 from the top to adjust the iOS status bar. You might be familiar with this syntax for defining rectangles if you are familiar with WPF or Silverlight. Xamarin.Forms uses the same syntax of the left, top, right, and bottom values delimited by commas. We also used several of the built-in Xamarin.Forms controls to see how they work: 1. Label: We used this earlier in the chapter. This is used only to display the text. This maps to a UILabel on iOS and a TextView on Android. 2. Button: This is a general purpose button that can be tapped by a user. This control maps to a UIButton on iOS and a Button on Android. [ 244 ]
Chapter 11
3. Entry: This control is a single-line text entry. It maps to a UITextField on iOS and an EditText on Android. 4. Image: This is a simple control to display an image on the screen, which maps to a UIImage on iOS and an ImageView on Android. We used the Source property of this control that loads an image from the Resources folder on iOS and the Resources/drawable folder on Android. You can also set URLs on this property, but it is best to include the image in your project for the performance. 5. Switch: This is an on/off switch or a toggle button. It maps to a UISwitch on iOS and a Switch on Android. 6. Stepper: This is a general-purpose input to enter numbers via two plus and minus buttons. On iOS, this maps to a UIStepper, while on Android Xamarin.Forms implements this functionality with two Button. This is just some of the controls provided by Xamarin.Forms. There are also more complicated controls such as the ListView and TableView you would expect to develop mobile UIs. Even though we used XAML in this example, you can also implement this Xamarin. Forms page from C#. Here is an example of what this would look like: public class UIDemoPageFromCode : ContentPage { public UIDemoPageFromCode() { var layout = new StackLayout { Orientation = StackOrientation.Vertical, Padding = new Thickness(10, 20, 10, 10), }; layout.Children.Add(new Label { Text = "My Label", XAlign = TextAlignment.Center, }); layout.Children.Add(new Button { Text ="My Button", }); layout.Children.Add(new Image {
[ 245 ]
Xamarin.Forms Source = "xamagon.png", }); layout.Children.Add(new Switch { IsToggled = true, }); layout.Children.Add(new Stepper { Value = 10, }); Content = layout; } }
So you can see that using XAML can be a bit more readable and is generally a bit better at declaring UIs. However, using C# to define your UIs is still a viable, straightforward approach.
Using data binding and MVVM
At this point, you should be grasping the basics of Xamarin.Forms, but you may be wondering how the MVVM design pattern fits into the picture. The MVVM design pattern was originally conceived for its use along with XAML and the powerful data binding features XAML provides, so it is only natural that it is a perfect design pattern to be used with Xamarin.Forms. Let's cover the basics of how data binding and MVVM is set up with Xamarin.Forms: 1. Your Model and ViewModel layers will remain mostly unchanged from the MVVM pattern we covered earlier in the module. 2. Your ViewModel layer should implement the INotifyPropertyChanged interface, which facilitates data binding. To simplify things in Xamarin.Forms, you can use the BindableObject base class and call OnPropertyChanged when the values change on your ViewModel. 3. Any page or control in Xamarin.Forms has a BindingContext property, which is the object that it is data bound to. In general, you can set a corresponding ViewModel to each view's BindingContext property. 4. In XAML, you can set up data binding using the syntax of the form Text="{Binding Name}". This example will bind the Text property of the control to a Name property of the object residing in the BindingContext. [ 246 ]
Chapter 11
5. In conjunction with data binding, events can be translated to commands using the ICommand interface. So, for example, a button's click event can be data bound to a command exposed by a ViewModel. There is a built-in Command class in Xamarin.Forms to support this. Data binding can also be set up from C# code in Xamarin.Forms via the Binding class. However, it is generally much easier to set up bindings from XAML, since the syntax has been simplified there.
Now that we have covered the basics, let's go through it step by step and partially convert our XamChat sample application discussed earlier in the module to use Xamarin.Forms. For the most part, we can reuse most of the Model and ViewModel layers, although we will have to make a few minor changes to support data binding from XAML. Let's begin by creating a new Xamarin.Forms application backed by a PCL named XamChat: 1. First, create three folders in the XamChat project named Views, ViewModels, and Models. 2. Add the appropriate ViewModels and Models classes from the XamChat application in the earlier chapter. These are found in the XamChat.Core project. 3. Build the project and just make sure that everything is saved. You will get a few compiler errors that we will resolve shortly. The first class that we will need to edit is the BaseViewModel class. Open it and make the following changes: public class BaseViewModel : BindableObject { protected readonly IWebService service = DependencyService.Get(); protected readonly ISettings settings = DependencyService.Get(); private bool isBusy = false; public bool IsBusy { get {return isBusy;} set {isBusy = value; OnPropertyChanged();} } }
[ 247 ]
Xamarin.Forms
First of all, we removed the calls to the ServiceContainer class, because Xamarin. Forms provides its own IoC container called DependencyService. It functions very similar to the container we built in the previous chapters, except that it only has one method, Get, and the registrations are set up via an assembly attribute that we will set up shortly. Additionally, we removed the IsBusyChanged event in favor of the INotifyPropertyChanged interface that supports data binding. Inheriting from BindableObject gives us the helper method, OnPropertyChanged, which we use to inform bindings that the value has changed in Xamarin.Forms. Notice that we didn't pass string, which contains the property name, to OnPropertyChanged. This method uses a lesser-known feature of .NET 4.0 called CallerMemberName, which will automatically fill in the calling property's name at runtime. Next, let's set up our required services with DependencyService. Open App.cs in the root of the PCL project, and add the following two lines above the namespace declaration: [assembly: Dependency(typeof(XamChat.Core.FakeWebService))] [assembly: Dependency(typeof(XamChat.Core.FakeSettings))]
DependencyService will automatically pick up these attributes and inspect the types
that we declared. Any interfaces that these types implement will be returned for any future callers of DependencyService.Get. I normally put all Dependency declarations in the App.cs file so that they are easy to manage and in one place. Next, let's modify LoginViewModel by adding a new property: public Command LoginCommand { get; set; }
We'll use this shortly to data bind a button's command. One last change in the View Model layer is to set up INotifyPropertyChanged for the MessageViewModel: Conversation[] conversations; public Conversation[] Conversations { get {return conversations; } set {conversations = value; OnPropertyChanged();} }
[ 248 ]
Chapter 11
Likewise, you can repeat this pattern for the remaining public properties throughout the ViewModel layer, but this is all that we will need for this example. Next, let's create a new Foms ContentPage Xaml item under the Views folder named LoginPage. In the code-behind file LoginPage.xaml.cs, we'll just need to make a few changes: public partial class LoginPage : ContentPage { readonly LoginViewModel loginViewModel = new LoginViewModel(); public LoginPage() { Title = "XamChat"; BindingContext = loginViewModel; loginViewModel.LoginCommand = new Command(async () => { try { await loginViewModel.Login(); await Navigation.PushAsync( new ConversationsPage()); } catch (Exception exc) { await DisplayAlert("Oops!", exc.Message, "Ok"); } }); InitializeComponent(); } }
We did a few important things here, including setting the BindingContext to our LoginViewModel. We set up LoginCommand, which basically invokes the Login method and displays a message if something goes wrong. It also navigates to a new page if successful. We also set the title, which will show up in the top navigation bar of the application. Next, open LoginPage.xaml, and we'll add the following XAML code inside the content page's content:
[ 249 ]
Xamarin.Forms
This will set up the basics of two text fields, a button, and a spinner complete with all the bindings to make everything work. Since we set up the BindingContext from the LoginPage code behind, all the properties are bound to the LoginViewModel. Next, create ConversationsPage as a XAML page as we did earlier, and edit the ConversationsPage.xaml.cs code behind: public partial class ConversationsPage : ContentPage { readonly MessageViewModel messageViewModel = new MessageViewModel(); public ConversationsPage() { Title = "Conversations"; BindingContext = messageViewModel; InitializeComponent (); Appearing += async (sender, e) => { try { await messageViewModel.GetConversations(); } catch (Exception exc) { await DisplayAlert("Oops!", exc.Message, "Ok"); } }; } }
In this case, we repeated a lot of the same steps. The exception is that we used the Appearing event as a way to load the conversations to display on the screen.
[ 250 ]
Chapter 11
Now let's add the following XAML code to ConversationsPage.xaml:
In this example, we used ListView to data bind a list of items and display on the screen. We defined a DataTemplate class that represents a set of cells for each item in the list that ItemsSource is data bound to. In our case, a TextCell displaying the Username is created for each item in the Conversations list. Last but not least, we must return to the App.cs file and modify the startup page: public static Page GetMainPage() { return new NavigationPage(new LoginPage()); }
We used NavigationPage here so that Xamarin.Forms can push and pop between different pages. This uses a UINavigationController on iOS so you can see how the native APIs are being used on each platform. At this point, if you compile and run the application, you will get a functional iOS and an Android application that can login and view a list of conversations:
[ 251 ]
Xamarin.Forms
Summary
In this chapter, we covered the basics of Xamarin.Forms and learned how it can be very useful to build your own cross-platform applications. Xamarin.Forms shines for certain types of apps, but can be limiting if you need to write more complicated UIs or take advantage of native drawing APIs. We discovered how to use XAML to declare our Xamarin.Forms UIs and understood how Xamarin.Forms controls are rendered on each platform. We also dived into the concepts of data binding and discovered how to use the MVVM design pattern with Xamarin.Forms. Last but not least, we began porting the XamChat application that was discussed earlier in the module to Xamarin.Forms and we were able to reuse most of the backend code.
[ 252 ]
App Store Submission Now that you have completed the development of your cross-platform application, the next obvious step is to distribute your app on the app stores. Xamarin apps are distributed in exactly the same way as Java or Objective-C apps; however, there are still a lot of hoops to jump through to successfully get your applications on the stores. iOS has an official approval process, which makes app store submission a much lengthier process than Android. Developers have to wait for a week, a month, or longer depending on how many times the app is rejected. Android requires some additional steps to submit the app on Google Play compared to debugging your application, but you can still get your application submitted in just a few hours. In this chapter, we will cover: • The App Store Review Guidelines • Submitting an iOS app to the App Store • Setting up Android signing keys • Submitting an Android app to Google Play • Tips for being successful on app stores
Following the iOS App Store Review Guidelines
Your application's name, app icon, screenshots, and other aspects are declared on Apple's website called iTunes Connect. Sales reports, app store rejections, contract and bank information, and app updates are all managed through the website at http://itunesconnect.apple.com.
App Store Submission
The primary purpose of Apple's guidelines is to keep the iOS App Store safe and free of malware. There is certainly little to no malware found on the iOS App Store. Generally, the worst thing an iOS application could do to you is bombard you with ads. To a certain extent, the guidelines also reinforce Apple's revenue share with payments within your application. Sadly, some of Apple's guidelines controversially eliminate one of their competitors in a key area on iOS. An example of this would be an app selling e-books, since it would be a direct competitor with iTunes and iBooks.
However, the key point here is to get your applications through the store approval process without facing the App Store rejections. As long as you are not intentionally trying to break the rules, most applications will not face much difficulty in getting approved by Apple. The most common rejections are related to mistakes by developers, which is a good thing, since you would not want to release an app with a critical issue to the public. The App Store Review Guidelines are quite lengthy, so let's break it down into the most common situations you might run into. A full list of the guidelines are found at https://developer.apple.com/appstore/resources/approval/guidelines. html. Note that a valid iOS developer account is required to view this site.
[ 254 ]
Chapter 12
General rules
Some general rules to follow are as follows: • Applications that crash, have bugs, or fail critically will be rejected • Applications that do not perform as advertised or contain hidden features will be rejected • Applications that use nonpublic Apple APIs, or read/write files from prohibited locations on the filesystem will be rejected • Apps that provide little value or that have been overdone (such as flashlight, burp, or fart apps) will be rejected • Applications cannot use trademarked words as the app name or keywords without the permission of the trademark holder • Applications cannot distribute copyrighted material illegally • Apps that can simply be implemented by a mobile-friendly website, such as apps with lots of HTML content that provide no native functionality can be rejected These rules make sense to keep the overall quality and safety of the iOS App Store higher than it would have otherwise been. It can be difficult to get a simple app with very few features into the store due to some of these rules, so make sure that your app is useful and compelling enough for the App Store review team to allow it to be available on the store.
Incorrect and incomplete information
Some rules related to the mistakes made by the developers or incorrect labeling in iTunes Connect are as follows: • Applications or metadata that mention other mobile platforms such as Android, for example, will be rejected • Applications that are labeled with an incorrect or inappropriate category/genre, screenshots, or icons will be rejected • Developers must give an appropriate age rating and keywords for the application • Support, privacy policy, and marketing URLs must be functional at the time the app is reviewed
[ 255 ]
App Store Submission
• Developers should not declare iOS features that are not used; for example, do not declare Game Center or iCloud usage if your application does not actually use these features • Applications that use features such as location or push notifications without the consent of the user will be rejected These can sometimes simply be a mistake on the developer's part. Just make sure you double-check all of your application's information before that final submission to the iOS App Store.
Content present in the application
Additionally, Apple has the following rules regarding content that can be contained within an application: • Applications that contain objectionable content or content that can be considered rude will be rejected • Applications that are designed to upset or disgust users will be rejected • Applications that contain excessive imagery of violence will be rejected • Applications that target a specific government, race, culture, or company as enemies will be rejected • Applications with icons or screenshots that do not adhere to the four plus age rating might be rejected The app store delivers apps to children and adults alike. Apple also supports an over 17 age restriction on applications; however, this will seriously limit the number of potential users for your app. It's best to keep applications clean and appropriate for as many ages as possible.
Apple's 70/30 revenue share
The next category of rules listed related to Apple's 70/30 revenue share from the App Store are as follows: • Applications that link to products or software sold on a website might be rejected. • Apps using a payment mechanism other than iOS in-app purchases (IAPs) will be rejected. • Applications that use IAPs to purchase physical goods will be rejected.
[ 256 ]
Chapter 12
• Apps can display digital content that is purchased outside the application as long as you cannot link to or purchase from within the app. All digital content purchased within the app must use IAPs. These rules are easy to follow as long as you are not trying to circumvent Apple's revenue share in the App Store. Always use IAPs to unlock the digital content within your applications.
General Tips
Last but not least, here are some general tips related to App Store rejections: • If your application requires a username and password, make sure you include the credentials under the Demo Account Information section for the app review team to use. • If your application contains IAPs or other features that the app review team must explicitly test, make sure you include instructions in Review Notes to reach the appropriate screen in your application. • Schedule ahead! Don't let your product's app rejection ruin a deadline; plan at least a month into your schedule for app store approval. • When in doubt, be as descriptive as possible in the Review Notes section of iTunes Connect. If your application does get rejected, most of the time there is an easy resolution. Apple's review team will explicitly reference the guidelines if a rule is broken and will include the relevant crash logs and screenshots. If you can correct an issue without submitting a new build, you can respond to the app review team via the Resolution Center option in the iTunes Connect website. If you upload a new build, this will put your application at the end of the queue to be reviewed. There are certainly more in-depth and specific rules for features in iOS, so make sure you take a look at the complete set of guidelines if you are thinking about doing something creative or out of the box with an iOS feature. As always, if you are unsure about a specific guideline, it is best to seek professional, legal advice on the matter. Calling Apple's support number will not shed any light on the subject since its support personnel are not allowed to give advice related to the App Store Review Guidelines.
[ 257 ]
App Store Submission
Submitting an app to the iOS App Store
Before we get started with submitting our application to the store, we need to review a short checklist to make sure you are ready to do so. It is a pain to reach a point in the process and realize you have something missing or haven't done something quite right. Additionally, there are a few requirements that will need to be met by a designer or the marketing team, which should not necessarily be left to the developer. Make sure you have done the following prior to beginning with the submission: • Your application's Info.plist file is completely filled out. This includes splash screen images, app icons, app name, and other settings that need to be filled out for advanced features. Note that the app name here is what is displayed under the application icon. It can be differ from the App Store name, and unlike the App Store name, it does not have to be unique from all the other apps in the store. • You have at least three names selected for your app on the App Store. A name might be unavailable even if it is not currently taken on the App Store, as it could have been previously taken by a developer for an app that was removed from the store for some reason. • You have a large 1024 x 1024 app icon image. There isn't a need to include this file in the application, unless you are distributing enterprise or ad hoc builds through iTunes (the desktop application). • You have at least one screenshot per device that your application is targeting. This includes iPhone 4 retina, iPhone 5, iPhone 6, iPhone 6 Plus, and iPad retina sized screenshots for a universal iOS application. However, I would strongly recommend that you fill out all the five screenshots slots. • You have a well-written and edited description for the App Store. • You have selected a set of keywords to improve the search for your application.
Creating a distribution provisioning profile
Once you have double-checked the preceding checklist, we can begin the process for submission. Our first step will be to create a provisioning profile for the App Store distribution. Let's begin by creating a new provisioning profile by carrying out the following steps: 1. Navigate to http://developer.apple.com/ios. 2. Click on Certificates, Identifiers & Profiles in the right-hand navigation bar. [ 258 ]
Chapter 12
3. Click on Provisioning Profiles. 4. Click on the plus button in the top-right corner of the window. 5. Select App Store under Distribution and click on Continue. 6. Select your app ID. You should have created one already in Chapter 7, Deploying and Testing on Devices; click on Continue. 7. Select the certificate for the provisioning profile. Normally, there will be only one option here. Click on Continue. 8. Give the profile an appropriate name such as MyAppAppStore. Click on Generate. 9. Once complete, you can download and install the profile manually or synchronize your provisioning profiles in Xcode under Preferences | Accounts, as we did earlier in the module. You will arrive at the following screen when successful:
Adding your app to iTunes Connect
For our next set of steps, we will start filling out the details of your application to be displayed on the Apple App Store.
[ 259 ]
App Store Submission
We can begin by performing the following set of steps to set up your app in iTunes Connect: 1. Navigate to http://itunesconnect.apple.com and log in. 2. Click on My Apps. 3. Click on the plus button in the top-left corner of the window, followed by New iOS App. 4. Enter an app Name to be displayed on the App Store. 5. Enter the Version to be displayed on the App Store. 6. Choose a Primary Language for your app. 7. Enter a value in the SKU field. This is used to identify your app in reports. 8. Select your Bundle ID. You should have already created one in Chapter 7, Deploying and Testing on Devices; click on Continue. 9. There is a lot of required information to fill out here. If you miss any, iTunes Connect is pretty helpful at displaying warnings. It should be fairly user-friendly since the site is meant to be used by marketing professionals as well as developers. 10. Click on Save after making your changes. There are a lot of optional fields too. Make sure you fill out Review Notes or Demo Account Information. If there is any additional information, the app review team will require to review your application. When complete, you will see your application with the status Prepare for Submission, as seen in the following screenshot:
Now we need to actually upload our app to iTunes Connect. You must either upload a build from Xcode or Application Loader. Either method will produce the same results, but some prefer to use Application Loader if a nondeveloper submits the app.
[ 260 ]
Chapter 12
Making an iOS binary for the App Store
Our last step for App Store submission is to provide our binary file that contains our application to the store. We need to create the Release build of our application, signed with the distribution provisioning profile we created earlier in this chapter. Xamarin Studio makes this very simple. We can configure the build as follows: 1. Click on the solution configuration dropdown in the top-left corner of Xamarin Studio and select AppStore. 2. By default, Xamarin Studio will set all the configuration options that you need to submit this build configuration. 3. Next, select your iOS application project and navigate to Build | Archive. After a few moments, Xamarin Studio will open the archived builds menu, as shown in the following screenshot:
The process creates a xarchive file that is stored in ~/Library/Developer/Xcode/ Archives. The Validate… button will check your archive for any potential errors that can occur during the upload, while Distribute… will actually submit the application to the store. Sadly, at the time of writing this module, the Distribute… button merely launches the Application Loader application, which cannot upload the xarchive files. Until Xamarin works this out, you can access these options for the archive in Xcode by navigating to Window | Organizer in the Archives tab. Go ahead and locate the archive in Xcode; you might have to restart Xcode if it does not appear, and perform the following steps: 1. Click on Distribute…. Don't worry, it will validate the archive before uploading. 2. Select Submit to the iOS App Store and click on Next.
[ 261 ]
App Store Submission
3. Log in with your credentials for iTunes Connect and click on Next. 4. Select the appropriate provisioning profile for the application and click on Submit. After several moments, depending on the size of your application, you will get a confirmation screen, and the status of your application will change to Upload Received. The following screenshot shows what a confirmation screen looks like:
If you return to iTunes Connect, and navigate to the Prerelease tab, you will see the build you just uploaded with a status of Processing:
[ 262 ]
Chapter 12
After a few minutes, the build will get processed and can be added to an App Store release. The next step is to select the build under the Versions tab, which is under the Build section, as shown in the following screenshot:
After hitting Save, you should be able to click on Submit for Review without any remaining warnings. Next, answer the three questions about export laws, ad identifiers, and so on and hit Submit as the final step to submit your app. At this point, you have no control over the status of your application while its waiting in line to be reviewed by an Apple employee. This can take one to two weeks, depending on the current workload of apps to be reviewed and the time of the year. Updates will also go through the same process, but the wait time is generally a bit shorter than a new app submission. Luckily, there are a few situations where you can fast track this process. If you navigate to https://developer.apple.com/appstore/contact/?topic=expedite, you can request for an expedited app review. Your issue must either be a critical bug fix or a time-sensitive event related to your application. Apple doesn't guarantee accepting an expedite request, but it can be a lifesaver in times of need. Additionally, if something goes wrong with a build you submitted, you can cancel the submission by going to the top of the app details page and selecting remove this version from review. In situations where you discover a bug after submission, this allows you to upload a new build in its place.
Signing your Android applications
All Android packages (the apk files) are signed by a certificate or a keystore file to enable their installation on a device. When you are debugging/developing your application, your package is automatically signed by a development certificate that was generated by the Android SDK. It is fine to use this certificate for development or even beta testing; however, it cannot be used on an application distributed to Google Play. [ 263 ]
App Store Submission
To create a production certificate, we can use a command-line tool included with the Android SDK named keytool. To create your own keystore file, run the following line in a terminal window: keytool -genkey -v -keystore .keystore -alias -keyalg RSA -keysize 2048 -validity 10000
Replace and with appropriate terms for your application. The keytool command-line tool will then prompt several questions for you to identify the party that is signing the application. This is very similar to an SSL certificate if you have ever worked with one before. You will also be prompted for a keystore password and a key password; you can let these be the same or change them, depending on how secure you want your key to be. Your console output will look something like what is shown in the following screenshot:
When complete, you should store your keystore file and password in a very safe place. Once you sign an application with this keystore file and submit it to Google Play, you will not be able to submit the updates of the application without signing it with the same key. There is no mechanism to retrieve a lost keystore file. If you do happen to lose it, your only option is to remove the existing app from the store, and submit a new app that contains your updated changes. This can potentially cause you to lose a lot of users. To sign an Android package, you can use another command-line tool included with the Android SDK named jarsigner. However, Xamarin Studio simplifies this process by providing a user interface to run your package.
[ 264 ]
Chapter 12
Open your Android project in Xamarin Studio and carry out the following steps to walk you through the process of signing an apk file: 1. Change your build configuration to Release. 2. Select the appropriate project and navigate to Project | Publish Android Application. 3. Select the keystore file that you just created. 4. Enter the values in the Password, Alias, and Key Password fields you used when creating the key. Click on Forward. 5. Choose a directory to deploy the apk file and click on Create. When successful, a pad in Xamarin Studio will appear that displays the progress. The pad that appears looks like what is shown in the following screenshot:
It is important to note that Xamarin.Android automatically runs a second tool called zipalign after signing the APK. This tool aligns the bytes within an APK to improve the start up time of your app. If you plan on running jarsigner from the command line itself, you must run zipalign as well. Otherwise, the app will crash on startup, and Google Play will also not accept the APK.
Submitting the app to Google Play
Once you have a signed the Android package, submitting your application to Google Play is relatively painless compared to iOS. Everything can be completed via the Developer Console tab in the browser without having to upload the package with an OS X application. Before starting the submission, make sure you have completed the tasks on the following checklist: • You have declared an AndroidManifest.xml file with your application name, package name, and icon declared. [ 265 ]
App Store Submission
• You have an apk file signed with a production key. • You have selected an application name for Google Play. This is not unique across the store. • You have a 512 x 512 high-resolution icon image for Google Play. • You have a well-written and edited description for the store. • You have at least two screenshots. However, I recommend that you use all the eight slots that include sizes for phones and 7- and 10-inch tablets. After going through the checklist, you should be fully prepared to submit your application to Google Play. The tab to add new apps looks like the following screenshot:
To begin with, navigate to https://play.google.com/apps/publish and log in to your account, then carry out the following steps: 1. Select the All Applications tab and click on Add new application. 2. Enter a name to be displayed for the app on Google Play and click on Upload APK. 3. Click on Upload your first APK to Production. 4. Browse to your signed apk file and click on OK. You will see the APK tab's checkmark turn green.
[ 266 ]
Chapter 12
5. Select the Store Listing tab. 6. Fill out all the required fields, including Description, High-res Icon, Categorization, and Privacy Policy (or select the checkbox that says you aren't submitting a policy), and provide at least two screenshots. 7. Click on Save. You will see the checkmark on the Store Listing tab turn green. 8. Select the Pricing & Distribution tab. 9. Select a price and the countries you wish to distribute to. 10. Accept the agreement for Content guidelines and US export laws. 11. Click on Save. You will see the checkmark on the Pricing & Distribution tab turn green. 12. Select the Ready to publish dropdown in the top-right corner, as shown in the following screenshot, and select Publish this app:
In a few hours, your application will be available on Google Play. No approval process is required, and the updates to your apps are equally painless.
Google Play Developer Program Policies
To provide a safe store environment, Google retroactively deletes applications that violate its policies and will generally delete the entire developer account—not just the application. Google's policies are are aimed at improving the quality of applications available on Google Play and are not quite as lengthy as the set of rules on iOS. That being said, the following is a basic summary of Google's policies: • Apps cannot have sexually explicit material, gratuitous violence, or hate speeches. • Apps cannot infringe upon copyrighted material.
[ 267 ]
App Store Submission
• Apps cannot be malicious in nature, or capture private information of users without their knowledge. • Apps cannot modify the basic functionalities of users' devices (such as modifying the home screen) without their consent. If applications include this functionality, it must be easy for the users to turn off this functionality. • All the digital content within your application must use Google Play's in-app billing (or in-app purchases). Physical goods cannot be purchased with IAPs. • Applications must not abuse the cellular network usage that could result in the user incurring high bill amounts. As with iOS, if you have a concern about one of the policies, it is best to procure professional, legal advice about the policy. For a complete list of the policies, visit https://play.google.com/about/developer-content-policy.html.
Tips to make a successful mobile app
From my personal experience, I have been submitting applications built with Xamarin to the iOS App Store and Google Play for quite some time. After delivering over 50 mobile applications totaling tens of millions downloads, a lot of lessons are to be learned about what makes a mobile application a success or a failure. Xamarin apps are indistinguishable from Java or Objective-C apps to the end user, so you can make your app successful by following the same patterns as standard iOS or Android applications.
[ 268 ]
Chapter 12
There is quite a bit you can do to make your app more successful. Here are some tips to follow: • Pricing it right: If your application appeals to almost anyone, consider a freemium model that makes revenue from ad placements or in-app purchases. However, if your app is fairly niche, you will be much better off pricing your app at $1.99 or higher. However, premium apps must hold a higher standard of quality. • Knowing your competition: If there are other apps in the same space as yours, make sure your application is better or offers a much wider feature set than the competition. It might also be a good idea to avoid the space altogether if there are already several apps with the same functionality as yours. • Prompting loyal users for reviews: It is a good idea to prompt users for a review after they open your application several times. This gives users who really like your application a chance to write a good review. • Supporting your users: Provide a valid support e-mail address or Facebook page for you to easily interact with your users. Respond to bug reports and negative reviews—Google Play even has the option to e-mail users who write reviews on your app. Adding a feedback button directly in the app is also a great option here. • Keeping your application small: Staying under the 100 MB limit on iOS or 50 MB on Google Play will allow users to download your application on their cellular data plan. Doing this negates the possibility of friction to install your app, as users will associate a lengthy download with a slow running application. • Submitting your app to review websites: Try to get as many reviews on the web as possible. Apple provides the ability to send coupon codes, but with the Android version of your app, you can send your actual Android package. Sending your app to review websites or popular Youtube channels can be a great way to free advertising. • Using an app analytics or a tracking service: Reporting your app's usage and crash reports can be very helpful to understand your users. Fixing crashes in the wild and modifying your user interface to improve spending behavior is very important. Examples of these would be to add Google Analytics or Flurry Analytics to your app.
[ 269 ]
App Store Submission
There is no silver bullet to having a successful mobile application. If your application is compelling, fulfills a need, and functions quickly and properly, you could have the next hit on your hands. Being able to deliver a consistent cross-platform experience using Xamarin will also give you a head start on your competitors.
Summary
In this chapter, we covered everything you need to know to submit your application to the iOS App Store and Google Play. We covered the App Store Review Guidelines and simplified them for the most common situations you might run into during the approval process. We went over the set up process for provisioning your app's metadata and upload your binary to iTunes Connect. For Android, we went over how to create a production signing key and sign your Android package (apk) file. We went over submitting an application to Google Play, and finished the chapter with tips on how to deliver a successful and hopefully profitable application to the app stores. I hope that with this module, you have experienced an end-to-end, practical walkthrough to develop real-world, cross-platform applications with Xamarin. Using C#, which is such a great language compared to the alternatives, you should be very productive. Additionally, you will save time by sharing code without, in any way, limiting the native experience for your users.
[ 270 ]
Module 2
Xamarin Cross-Platform Development Cookbook A recipe-based practical guide to get you up and running with Xamarin cross-platform development
One Ring to Rule Them All In this chapter, we will cover the following recipes: • Creating a cross-platform solution • Creating a cross-platform login screen • Using common platform features • Authenticating with Facebook and Google providers
Introduction
Xamarin.Forms is a cross-platform UI framework where the idea is no longer to share only your models, business logic, and data access layers similar to a traditional Xamarin solution but also the user interface (UI) across iOS, Android, and Windows Phone. With Xamarin.Forms, you can easily and quickly create great data-driven and utility applications or prototypes. To accomplish this, Xamarin uses the super-modern C# language, the power of the .NET Base Class Libraries (BCL), these are C# bindings to the native APIs, and two great IDEs, Xamarin Studio and Microsoft Visual Studio. You can't, however, create iOS applications if you don't have a Mac connected to the network and acting as a server to build and deploy your application with the help of the Xamarin Build Host. This module will provide you with real-world recipes and step-by-step development of the most common practices that you need to create professional cross-platform applications. You will learn how to create one UI across all platforms, customize the layout and views, and inject implementation per platform with a focus on patterns and best practices. [ 273 ]
One Ring to Rule Them All
In this chapter, we will dive into the details of creating a cross-platform solution, adding a login screen, storing values for each platform, and using the Xamarin.Auth component to allow your users to log in with Facebook and Google providers. Neat! Exactly what you need to create a real-world application.
Creating a cross-platform solution
Getting started with apps for Xamarin.Forms is very easy. The installer sets everything up, the IDE creates the project, and you're up and running in minutes! Lean!
Getting ready
Before we can start creating cross-platform apps, we need to get our tools in place using a single installer from Xamarin: 1. Go to http://xamarin.com/download
[ 274 ]
Chapter 1
2. Enter your registration details. 3. Click the Download Xamarin for OS X button. 4. Once the download has completed, launch the installer, following the onscreen instructions. The setup will continue to download and install all the required components. Once the install is finished, you will have a working installation of Xamarin Studio, the IDE designed for cross-platform development:
How to do it...
Creating a Xamarin.Forms solution is easy using the default templates in Xamarin Studio or Visual Studio. Depending on the IDE, you will get three (Xamarin Studio in Windows), four (Xamarin Studio in Mac), and four (Visual Studio) projects. Of course, you can open the solution in the desired IDE and add the projects missing while moving forward with development. In this section, we will create a Xamarin. Forms blank application in Xamarin Studio for Mac and then add the Windows Phone project in Visual Studio.
[ 275 ]
One Ring to Rule Them All
In Xamarin Studio, choose File | New | Solution, and in the Cross-platform section, App category, you will see two options: Blank Xamarin.Forms App and Single View App, as shown in the following screenshot:
Choose Blank Xamarin.Forms App and click the Next button. You get to a screen with the options App Name, the name of your applications, and Identifier, which will be used as your package name for Android and the bundle identifier for iOS and the platforms you want to target.
[ 276 ]
Chapter 1
The last option is if you want to use a Portable Class Library or a Shared Library as shown in the following screenshot. Shared Library will create a project that uses conditional compilation directives and it will be built in your main binary. With a Portable Class Library, on the other hand, you get to choose a profile you want to target and also distribute it via NuGet or the Xamarin Component Store. In this module, we only focus on the option of a Portable Class Library and its corresponding patterns and practices.
[ 277 ]
One Ring to Rule Them All
Click the Next button. Enter the project name, which will be used to create the names for each platform-specific project with the convention [ProjectName].[Platform] and a solution name as shown in the following screenshot. You can choose where to save the solution and if you want to use Git version control.
Click Create. Notice that we have a solution with four projects. You will learn later in Chapter 10, Test Your Applications, You Must how to create tests for our solution. This project is created only if we create the solution with Xamarin Studio.
[ 278 ]
Chapter 1
With no hesitation, choose your iPhone simulator and click the play button. You will see the simulator starting and a simple app running with the message Welcome to Xamarin Forms!. Congratulations, you can do the same for Android by rightclicking in the project and Set As Startup Project. For Android projects, you can use either Google's emulators or the Xamarin Android Player, https://xamarin.com/ android-player, which personally I find more efficient and faster. Click the play button and enjoy the same message in your favorite emulator. Sweet! Three clicks and we have an application running in iOS and Android! Now, before we examine the project structure, let's jump into Visual Studio and add the Windows Phone project. 1. In Visual Studio, go to File | Open | Project/Solution and choose the ProjectName.sln file; in the example's case, the FormsCookbook.sln file. 2. In the Solution Explorer, right-click your solution and Add | New Project.
[ 279 ]
One Ring to Rule Them All
3. In the section Store Apps | Windows Phone Apps, click in Blank App (Windows Phone Silverlight) and choose a name; to be consistent, let's name it [ProjectName].WinPhone , then press the OK button.
4. Choose either Windows Phone 8.0 or Windows Phone 8.1. Voila! Another platform is added but it's missing some parts to work with our Xamarin.Forms solution. We need to add the required packages/libraries and make a small modification to the application starting point code.
[ 280 ]
Chapter 1
5. Right-click in References of the Windows Phone project we just created and choose Manage NuGet Packages. 6. Search for the Xamarin.Forms package and hit Install to add it to the Windows Phone project.
[ 281 ]
One Ring to Rule Them All
7. We also need to reference the PCL library. Right-click to References | Add Reference, and in the section Solution you will find the PCL with the Project Name. Check the box and hit OK.
We're almost done. We just need some code changes to the XAML MainPage to convert it to an Xamarin.Forms application. 8. Select the MainPage.xaml file and modify the root tag from PhoneApplicationPage to FormsApplicationPage as shown in the following screenshot:
[ 282 ]
Chapter 1
9. You might need to bring the namespace in XAML. If you use any fancy tool like ReSharper you already took care of it; if not, add the following line to your project root tag that we just modified. Again, check the final result in the preceding screenshot. xmlns:winPhone="clrnamespace:Xamarin.Forms.Platform.WinPhone; assembly=Xamarin.Forms.Platform.WP8"
10. We need to check the MainPage.xaml.cs behind-code file as well and change it to inherit from FormsApplicationPage and the code in lines 13 and 14 as in the following screenshot:
11. Right-click to the Windows Phone project and set as Start Up Project; press F5 or from the Debug | Start Debugging menu. [ 283 ]
One Ring to Rule Them All
Congratulations! You have created a solution with three platform-specific projects (iOS, Android, and Windows Phone) and a core class library (PCL). Remember, the Windows Phone project will only be active when the solution is opened in Visual Studio.
How it works…
So, we still have the beauty of native applications but we get to share the UI code as well! Sweet! See in the following screenshot the Xamarin.Forms platform architecture that reflects what we just created:
Our three platform-specific projects are at the top architecture layer, the shared C# user interface code below, and the Shared C# App Logic at the bottom. Xamarin.Forms allows you to describe the UI once using a shared set of controls while still rendering a native UI. This is happening with the help of the native platforms' renderers; there is one for each supported control for every platform. So, when we add a Label control, the platform renderer will transform it to a UILabel for iOS, a TextView for Android, and a TextBlock for Windows Phone. In the core Portable Class Library project, expand References | From Packages. Referenced are the libraries Xamarin.Forms.Core and Xamarin.Forms.Xaml; the first one contains all the core logic of Xamarin.Forms and the second is specific to the XAML code, which we will discuss in detail in Chapter 2, Declare Once, Visualize Everywhere. Go to a platform-specific project now; let's see Android first. Expand References | From Packages, and we have again Xamarin.Forms.Core and Xamarin.Forms. Xaml, but also Xamarin.Forms.Platform.Android. This is the platform-specific library where all the Android renderers live and its responsibility is to take the UI abstractions and translate to a platform-specific UI.
[ 284 ]
Chapter 1
The same applies to our iOS project with the platform-specific library having the name Xamarin.Forms.Platform.iOS. In the references of the platform-specific projects you can see our core PCL reference too; it couldn't work without our base code, right? That is all great! But how do these platform-specific projects connect to the Xamarin. Forms application? For that we have to look in the code behind every platform application entry point. Start from the Windows Phone project. We modified our MainPage.xaml. cs code behind, and now it is not a Windows Phone Page type but Xamarin. Forms.Platform.WinPhone.FormsApplicationPage. The important calls is in the constructor, Forms.Init, and the LoadApplication methods. In the latter we pass a Xamarin.Forms.Appplication instance located in our PCL. Open the FormsCookbook.cs file in the PCL project. Here is our Xamarin.Forms entry point, with some UI code in the constructor. In the next section, we discuss and change this code. For the iOS project, you will find the related method calls in the AppDelegate. cs, FinishedLaunching method implementation. The difference is that the AppDelegate class inherits from Xamarin.Forms.Platform.iOS. FormsApplicationDelegate. And finally, in the Android project, the MainActivity.cs file is our MainLauncher activity and inherits from Xamarin.Forms.Platform.Android. FormsApplicationActivity. It initializes the platform specifics and loads the Xamarin.Forms application class in the OnCreate method implementation. Notice the difference in the Forms.Init method: it requires two parameters: an Activity and Bundle instances.
See also
• https://developer.xamarin.com/guides/cross-platform/xamarinforms/controls/views/
Creating a cross-platform login screen
Almost every application has an authentication screen. Here, we will examine the root application page of a Xamarin.Forms application by creating a login screen where the user can set his username, password and tap the login button to get access. You can create a new solution or continue from the previous section. [ 285 ]
One Ring to Rule Them All
How to do it... In our core PCL project:
1. Open the FormsCookbook.cs file. 2. Replace the code in the constructor with the following code snippet: var userNameEntry = new Entry { Placeholder = "username" }; var passwordEntry = new Entry { Placeholder = "password", IsPassword = true }; var loginButton = new Button { Text = "Login" }; loginButton.Clicked += (sender, e) => { Debug.WriteLine(string.Format("Username: {0} - Password: {1}", userNameEntry.Text, passwordEntry.Text)); }; MainPage = new ContentPage { Content = new StackLayout { VerticalOptions = LayoutOptions.Center, Children = { userNameEntry, passwordEntry, loginButton } } };
[ 286 ]
Chapter 1
3. Run the application for each platform; you should get the two native default text input controls and a button. 4. Set your username and password in the corresponding textboxes. 5. Click the Login button and watch the application output printing the message we set up in the Clicked delegate event handler. You should see something similar to the following screenshot: In iOS:
[ 287 ]
One Ring to Rule Them All
In Android:
In Windows:
[ 288 ]
Chapter 1
This is all great: the default layout looks OK and everything is aligned in the middle of the screen and to the edge of the screen. Of course, every view and layout container has properties that will align and add spacing between controls. Let's try to change things up a little bit. It's good to start organizing our code better too. 1. Right-click in the core PCL project and Add | New Folder; name it Custom Pages. 2. Right-click in the Custom Pages folder and Add | New File; choose Empty Class, set the name to MainPage, and click New. 3. In the constructor of the new class, write or paste the following code: public class MainPage: ContentPage { Entry userNameEntry; Entry passwordEntry; Button loginButton; StackLayout stackLayout; public MainPage() { userNameEntry = new Entry { Placeholder = "username" }; passwordEntry = new Entry { Placeholder = "password", IsPassword = true }; loginButton = new Button { Text = "Login" }; loginButton.Clicked += LoginButton_Clicked; this.Padding = new Thickness(20); stackLayout = new StackLayout { VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand, Orientation = StackOrientation.Vertical, Spacing = 10, Children = { userNameEntry, passwordEntry, loginButton } };
[ 289 ]
One Ring to Rule Them All this.Content = stackLayout; } void LoginButton_Clicked(object sender, EventArgs e) { Debug.WriteLine(string.Format("Username: {0} Password: {1}", userNameEntry.Text, passwordEntry.Text)); } }
4. Make the MainPage class a subclass of ContentPage. public class MainPage : ContentPage
5. Replace the FormsCookbook.cs constructor with the following code: MainPage = new MainPage ();
6. Run the application for each platform to see the results. You should see something similar to the following screenshot: In iOS:
[ 290 ]
Chapter 1
In Android:
In Windows:
[ 291 ]
One Ring to Rule Them All
How it works…
FormsCookbook.cs is the main application entry that contains a MainPage property
where we assign the application initial page.
For the Content of the MainPage we create a StackLayout, and for its children we add our three Views: userNameEntry, passwordEntry, and loginButton with VerticalOptions, HorizontalOptions, Orientation, Spacing. We assigned a delegate handler for the Clicked event of the loginButton to respond to our finger tap. Nothing fancy here, just printing a message to the Application Output window. The Xamarin.Forms UI hierarchy is based on a more adaptive layout; we don't specify positions with coordinates, but rather with layout containers, which we add child elements and specific rules. To fine-tune our login screen, we used the StackLayout's container properties, VerticalOptions and HorizontalOptions, to determine how the child content is positioned, spacing for the space between the controls and padding for around the element. When it comes to width and height, there is no straightforward way to set these properties in Xamarin.Forms views. What we actually do is request the width and height, and with no guarantee that it will happen the layout will try to complete your request. The Width and Height are read-only properties.
There's more…
When it comes to a native cross-platform application like Xamarin.Forms, don't forget that we are running in different platforms, and different platforms have different screen sizes with different measurement systems: iOS units, Android and Windows Phone, device-independent pixels. How does Xamarin.Forms deal with that? For example, if you say Spacing=10 then it will be 10 in the specific platform's measurement system, 10 units in iOS, and 10 dpi in Android and Windows Phone.
[ 292 ]
Chapter 1
Using common platform features
The biggest challenge when developing a cross-platform application is the specific platform features APIs. Many common platform features exists across the platforms, but there isn't always a simple way to use them. For instance, showing an alert dialog message box or opening a URL from your application is a supported feature for iOS, Android, and Windows Phone but the APIs are completely different and platform specific, and a Portable Class Library has no access to all these features. For our convenience, Xamarin.Forms supports some of the common platformspecific features out of the box. The architectural design and how you can create your own abstractions and implementations to use them in runtime in the portable library is discussed in Chapter 4, Different Cars, Same Engine.
How to do it...
We will explore the following Xamarin.Forms APIs: • Device.OpenUri – opens a URL in native browser • Device.StartTimer – triggers time-dependent tasks • Device.BeginInvokeOnMainThread – any background code that needs to update the user interface • Page.DisplayAlert – shows simple alert message dialogs • Xamarin.Forms.Map – a NuGet cross-platform map library Let's create an application to see the usage of the preceding APIs. 1. Create a cross-platform project from scratch using the default template named XamFormsCommonPlatform. 2. Create a new folder and add a new class named MainPage.cs. 3. Paste the following code: public class MainPage: ContentPage { private Button openUriButton; private Button startTimerButton; private Button marshalUIThreadButton; private Button displayAlertButton; private Button displayActionSheetButton; private Button openMapButton; private StackLayout stackLayout;
[ 293 ]
One Ring to Rule Them All public MainPage() { openUriButton = new Button { Text = "Open Xamarin Evolve" }; startTimerButton = new Button { Text = "Start timer" }; marshalUIThreadButton = new Button { Text = "Invoke on main thread" }; displayAlertButton = new Button { Text = "Display an alert" }; displayActionSheetButton = new Button { Text = "Display an ActionSheet" }; openMapButton = new Button { Text = "Open platform map" }; openUriButton.Clicked += OpenUriButton_Clicked; startTimerButton.Clicked += StartTimerButton_Clicked; marshalUIThreadButton.Clicked += MarshalUIThreadButton_Clicked; displayAlertButton.Clicked += DisplayAlertButton_Clicked; displayActionSheetButton.Clicked += DisplayActionSheetButton_Clicked; openMapButton.Clicked += OpenMapButton_Clicked; stackLayout = new StackLayout { Orientation = StackOrientation.Vertical, Spacing = 10, Padding = new Thickness(10), VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand, Children = { openUriButton, startTimerButton, marshalUIThreadButton, displayAlertButton, displayActionSheetButton, openMapButton } };
[ 294 ]
Chapter 1 Content = stackLayout; } void OpenMapButton_Clicked(object sender, EventArgs e) { } async void DisplayActionSheetButton_Clicked(object sender, EventArgs e) { string action = await DisplayActionSheet("Simple ActionSheet", "Cancel", "Delete", new string[] { "Action1", "Action2", "Action3", }); Debug.WriteLine("We tapped {0}", action); } async void DisplayAlertButton_Clicked(object sender, EventArgs e) { bool result = await DisplayAlert("Simple Alert Dialog", "Sweet!", "OK", "Cancel"); Debug.WriteLine("Alert result: {0}", result ? "OK" : "Cancel"); } void MarshalUIThreadButton_Clicked(object sender, EventArgs e) { Task.Run(async() => { for (int i = 0; i < 3; i++) { await Task.Delay(1000); Device.BeginInvokeOnMainThread(() => { marshalUIThreadButton.Text = string.Format("Invoke {0}", i); }); } }); } void StartTimerButton_Clicked(object sender, EventArgs e) { Device.StartTimer(new TimeSpan(0, 0, 1), () => { Debug.WriteLine("Timer Delegate Invoked");
[ 295 ]
One Ring to Rule Them All return true; // false if we want to cancel the timer. }); } void OpenUriButton_Clicked(object sender, EventArgs e) { Device.OpenUri(new Uri("http://xamarin.com/evolve")); } }
4. Ignore the OpenMapButton_Clicked method for now; we will need to make some configuration soon to make it work. 5. Run each application and test all the buttons except Open platform map, which has no functionality yet. You should see something similar to the following screenshot: In iOS:
[ 296 ]
Chapter 1
In Android:
In Windows Phone:
[ 297 ]
One Ring to Rule Them All
To add Cross-Maps to our application, we first need to make some platform-specific configurations. 1. For all the projects in our solution, add the Nuget package: Xamarin.Form. Maps. 2. For the iOS and Windows Phone application projects, add the following code after Forms.Init(). As a reminder, the call is in MainActivity.cs for Android and in AppDelegate.cs in iOS. Xamarin.FormsMaps.Init();
3. In the Android project, we need to pass the two required parameters as the Forms.Init call. Xamarin.FormsMaps.Init(this, bundle);
4. For the iOS 7 apps, there is no need for any modifications, but iOS 8 requires two keys in the info.plist file. Open the file and view its source, then add the following key/value pair:
5. For Android, you need to get a Google Maps API v2 key; visit the link to complete this requirement: https://developers.google.com/maps/ documentation/android/. 6. Open Properties/AndroidManifest.xml, view source, and add or update the following in your tag:
7. Right-click the Android project and choose Options. Select the Android Application section and in Required Permissions, check the following: °° Internet °° AccessNetworkState °° AccessCoarseLocation °° AccessFineLocation °° AccessLocationExtraCommands °° AccessMockLocation °° AccessWifiState 8. Open the Properties/WMAppmanifest.xml file in the Windows Phone Silverlight project and add the following in Capabilities: °° ID_CAP_MAP °° ID_CAP_LOCATION [ 298 ]
Chapter 1
The Xamarin.Forms.Maps library does not currently support Windows Phone 8.1 or Windows 8.1 projects.
9. Add a new page with a right-click in the Custom Pages folder and Add | New File. Name it MapPage. 10. Make the MapPage class a ContentPage subclass. 11. Add the following code: private Map map; private StackLayout stackLayout; public MapPage() { MapSpan span = MapSpan.FromCenterAndRadius(new Position(40.730599, -73.986581), Distance.FromMiles(0.4)); map = new Map(span) { VerticalOptions = LayoutOptions.FillAndExpand }; stackLayout = new StackLayout { Spacing = 0, Children = { map } }; Content = stackLayout; }
12. In MainPage.cs, add the following line of code in the OpenMapButton_ Clicked method: Navigation.PushModalAsync (new MapPage ());
13. Run your applications, tap the Open platform map, and check out Manhattan! We can also change the map type and add pins on a map. The available pin types are the following: • Generic • Place • SavedPin • SearchResult [ 299 ]
One Ring to Rule Them All
To add a pin on the map, use the following code: Position position = new Position(40.730599, -73.986581); Pin pin = new Pin { Type = PinType.Place, Position = position, Label = "New York", Address = "New York" }; map.Pins.Add(pin);
Yes, it is that easy! The Xamarin team did a great job abstracting this common feature to accomplish the most basic operations with a map. In iOS:
[ 300 ]
Chapter 1
In Android:
[ 301 ]
One Ring to Rule Them All
In Windows Phone:
How it works…
Xamarin has provided us with all the abstractions that we can reference in our PCL core library and the implementations of these platform-specific features for our native projects. Using the DependencyService (a service we will cook in Chapter 4, Different Cars, Same Engine), a Service Locator mechanism to resolve dependencies in runtime, Xamarin makes it possible for us to create native applications with these common features in a very short time. Device.OpenUri in runtime will inject implementation that will allow it to use the iOS UIAlertController, in Android the AlertDialog, and in Windows Phone the MessageBox platform-specific APIs. Device.StartTimer will hide the equivalent
timer APIs and the same practice goes for all the plugins we demonstrated.
The same rules apply for the Xamarin.Forms.Map library, but some configuration requirements are needed. [ 302 ]
Chapter 1
One objective of Xamarin and especially Xamarin.Forms is to make us more productive; this is where the Xamarin and Community plugins come into play. Always check before you start reinventing the wheel!
See also
• Chapter 4, Different Cars, Same Engine • https://developer.xamarin.com/guides/cross-platform/xamarinforms/controls/layouts/
• https://components.xamarin.com/
Authenticating with Facebook and Google providers
One common feature that most of the applications utilize is to allow you to log in with your favorite login provider. We will close this chapter learning how you can leverage the Xamarin.Auth plugin to connect and authenticate two of the most famous login providers, Facebook and Google, to save your users from registering and using custom login steps, and to save your application from extra work in the backend and frontend tiers.
How to do it...
1. In Xamarin Studio or Visual Studio, create a new cross-platform project; if you don't remember how to, check the Creating a cross-platform solution recipe in this chapter. Name it XamFormsAuthenticateProviders. 2. Add the plugin in the iOS and Android projects, but don't bother with Windows Phone for now. 3. Right-click the XamFormsAuthenticateProviders.iOS components folder, Edit Components, search for Xamarin.Auth, and add it to the project. Do the same for the XamFormsAuthenticateProviders.Droid project. 4. To be able to authenticate your users with Facebook and Google, you will need to get your own client ID and secret from the corresponding developer consoles. For Facebook, go to https://developers.facebook.com, and for Google, got to https://console.developers.google.com.
[ 303 ]
One Ring to Rule Them All
5. The process in every platform to get your credentials is almost the same for each OAuth 2.0 provider: you create a new project and retrieve the keys. Refer to the related platform documentation for more information. The tricky part is to not forget to add the same redirect URLs to allow the application to end somewhere after a successful login. 6. In the XamFormsAuthenticateProviders core project, right-click and choose Add | New Folder; name it Models. 7. In the newly created folder, add two helper classes: OAuthSettings and ProviderManager. 8. Add the following code: public class OAuthSettings { public string ClientId { get; set; } public string ClientSecret { get; set; } public string AuthorizeUrl { get; set; } public string RedirectUrl { get; set; } public string AccessTokenUrl { get; set; } public List < string > Scopes { get; set; } public string ScopesString { get { return Scopes.Aggregate((current, next) => string.Format("{0}+{1}", current, next)); } }
[ 304 ]
Chapter 1 public OAuthSettings() { Scopes = new List < string > (); } } public enum Provider { Unknown = 0, Facebook, Google } public static class ProviderManager { private static OAuthSettings FacebookOAuthSettings { get { return new OAuthSettings { ClientId = "YOUR_CLIENT_ID", ClientSecret = "YOUR_CLIENT_SECRET", AuthorizeUrl = "https://m.facebook.com/dialog/oauth/", RedirectUrl = "http://www.facebook.com/connect /login_success.html", AccessTokenUrl = "https://graph.facebook.com/ v2.3/oauth/access_token", Scopes = new List < string > { "" } }; } } private static OAuthSettings GoogleOAuthSettings { get { return new OAuthSettings { ClientId = " YOUR_CLIENT_ID ", ClientSecret = " YOUR_CLIENT_ID ", AuthorizeUrl = "https://accounts.google.com/o/oauth2/auth", RedirectUrl = "https://www.googleapis.com/plus/v1/people/me", AccessTokenUrl = "https://accounts.google.com/o/oauth2/token", Scopes = new List < string > { "openid" } }; [ 305 ]
One Ring to Rule Them All } } public static OAuthSettings GetProviderOAuthSettings(Provider provider) { switch (provider) { case Provider.Facebook: { return FacebookOAuthSettings; } case Provider.Google: { return GoogleOAuthSettings; } } return new OAuthSettings(); } }
9. Right-click on the core XamFormsAuthenticate project and Add | New Folder; name it Custom Pages. 10. Right-click on the newly created folder and add two classes, LoginPage and ProvidersAuthPage, and then write or paste-replace the file's contents with the following code: public class LoginPage : ContentPage { public OAuthSettings ProviderOAuthSettings { get; set; } public LoginPage (Provider provider) { ProviderOAuthSettings = ProviderManager. GetProviderOAuthSettings (provider); } } public class ProvidersAuthPage : ContentPage { StackLayout stackLayout; Button facebookButton; Button googleButton;
[ 306 ]
Chapter 1 public ProvidersAuthPage () { facebookButton = new Button { Text = "Facebook" }; facebookButton.Clicked += async (sender, e) => await Navigation.PushModalAsync(new LoginPage(Provider. Facebook)); googleButton = new Button { Text = "Google" }; googleButton.Clicked += async (sender, e) => await Navigation.PushModalAsync(new LoginPage(Provider.Google)); stackLayout = new StackLayout { VerticalOptions = LayoutOptions.Center, HorizontalOptions = LayoutOptions.Center, Orientation = StackOrientation.Vertical, Spacing = 10, Children = { facebookButton, googleButton } }; this.Content = stackLayout; } }
11. In the App constructor of the App.cs XamFormsAuthenticate project file, add the following to set the start page: MainPage = new ProvidersAuthPage();
Now we need to add the authentication code flow, but if you noticed when we added the Xamarin.Auth components in step 3, there are two different implementations: one for each platform. To accomplish our goal and run specific platform code for each project, we will use the PageRenderer class and an attribute that under the covers uses the Xamarin.Forms DependencyService. You can find more in-depth recipes in this module regarding these topics in Chapter 2, Declare Once, Visualize Everywhere and Chapter 4, Different Cars, Same Engine.
[ 307 ]
One Ring to Rule Them All
For the Android project
1. Create a new folder, name it Platform Specific, and add a new class file name LoginPageRenderer. Make it a PageRenderer subclass and add the following code: LoginPage page; bool loginInProgress; protected override void OnElementChanged(ElementChangedEventArgs < Page > e) { base.OnElementChanged(e); if (e.OldElement != null || Element == null) return; page = e.NewElement as LoginPage; if (page == null || loginInProgress) return; loginInProgress = true; try { // your OAuth2 client id OAuth2Authenticator auth = new OAuth2Authenticator( page.ProviderOAuthSettings.ClientId, // your OAuth2 client secret page.ProviderOAuthSettings.ClientSecret, // scopes page.ProviderOAuthSettings.ScopesString, //the scopes, delimited by the "+" symbol new Uri(page.ProviderOAuthSettings.AuthorizeUrl), // the redirect URL for the service new Uri(page.ProviderOAuthSettings.RedirectUrl), new Uri(page.ProviderOAuthSettings.AccessTokenUrl)); auth.AllowCancel = true; auth.Completed += async(sender, args) => { // Do something… await page.Navigation.PopAsync(); loginInProgress = false; }; auth.Error += (sender, args) => { Console.WriteLine("Authentication Error: {0}", args.Exception);
[ 308 ]
Chapter 1 }; var activity = Xamarin.Forms.Forms.Context as Activity; activity.StartActivity (auth.GetUI(Xamarin.Forms.Forms.Context)); } catch (Exception ex) { Console.WriteLine(ex); } }
2. We need some using statements to make all this and also a decoration attribute on the namespace. using using using using using
XamFormsAuthenticateProviders; XamFormsAuthenticateProviders.Droid; Xamarin.Forms.Platform.Android; Xamarin.Auth; Android.App;
[assembly: ExportRenderer(typeof(LoginPage), typeof(LoginPageRenderer))] namespace XamFormsAuthenticateProviders.Droid
For the iOS project
1. The same applies here as with the Android platform: create a folder and a new class file named LoginPageRenderer, and make it a subclass of PageRenderer. 2. This time, we need iOS-related code; you can find it in the following excerpt: LoginPage page; bool loginInProgress; protected override void OnElementChanged (VisualElementChangedEventArgs e) { base.OnElementChanged(e); if (e.OldElement != null || Element == null) return; page = e.NewElement as LoginPage; } public override async void ViewDidAppear(bool animated) { base.ViewDidAppear(animated); if (page == null || loginInProgress) return; [ 309 ]
One Ring to Rule Them All loginInProgress = true; try { // your OAuth2 client id OAuth2Authenticator auth = new OAuth2Authenticator( page.ProviderOAuthSettings.ClientId, // your OAuth2 client secret page.ProviderOAuthSettings.ClientSecret, // scopes page.ProviderOAuthSettings.ScopesString, // the scopes, delimited by the "+" symbol new Uri(page.ProviderOAuthSettings.AuthorizeUrl), // the redirect URL for the service new Uri(page.ProviderOAuthSettings.RedirectUrl), new Uri(page.ProviderOAuthSettings.AccessTokenUrl)); auth.AllowCancel = true; auth.Completed += async(sender, args) => { // Do something… await DismissViewControllerAsync(true); await page.Navigation.PopModalAsync(); loginInProgress = false; }; auth.Error += (sender, args) => { Console.WriteLine("Authentication Error: {0}", args.Exception); }; await PresentViewControllerAsync(auth.GetUI(), true); }catch (Exception ex) { Console.WriteLine(ex); } }
3. Add the using statements and the ExportRenderer attribute on the namespace. using using using using using using
System; Xamarin.Forms.Platform.iOS; Xamarin.Forms; XamFormsAuthenticateProviders; XamFormsAuthenticateProviders.iOS; Xamarin.Auth;
[assembly: ExportRenderer(typeof(LoginPage), typeof(LoginPageRenderer))] namespace XamFormsAuthenticateProviders.iOS
[ 310 ]
Chapter 1
The Windows Phone platform! Yes, the forgotten one. You must have noticed that there is no Windows Phone platform component in the Xamarin Component Store. Fear not, you have options. The magic answer is relying on the custom platformspecific PageRenderer that we add for each platform; you can create a platformspecific following the same steps and add your favorite Windows Phone OAuth library logic or you can follow the next steps to try out the experimental alpha channel Xamarin.Auth NuGet package. 1. Right-click in the Windows Phone project and add the Xamarin.Auth NuGet package. You have to change the channel to Alpha in order to find it. 2. Create a folder named Platform Specific and add a class file named LoginPageRenderer; subclass it to PageRenderer. 3. Override the OnElementChanged method and add your code or Xamarin.Auth-related code. Don't forget to check the code available with this module for the complete picture. That was a big milestone. Of course, it scratches the surface of access tokens, but we leave that to your preferences. Normally, you would get the access token, capture the expiration date, and persist it somewhere.
How it works…
You just had a taste of the power that Xamarin.Forms can provide you to create not only a unified application with shared code but also customize per platform when necessary. With the use of PageRenderer, you can completely provide your native views and pages. The first thing we did after we created our solution is to go to the developer console of Facebook and Google, set up a new project, enable the OAuth2 system, retrieve our keys, and add the redirect URL we need to navigate the users after a successful login. In a few words, OAuth and OAuth is an authentication system to verify that this user is actually real and exists in the provider's records. It enables the user to use the same secure credentials from a provider he trusts to give access to the application he is about to use. You can also ask for various scopes and retrieve personal information that he has provided to his trusted provider. Then we added two helper classes: one to provide us with all the common properties, OAuthSettings, that every provider needs in order to provide you with the desired access token; and a static class, ProviderManager, with a static method accepting a provider enumeration and returning an OAuthSettings instance for the required provider.
[ 311 ]
One Ring to Rule Them All
Our cross-platform UI consists of two pages: ProvidersAuthPage, which is our main page and has two buttons where in the Clicked event of each we navigate modally to the LoginPage passing the desired provider we need to authenticate our user. In the LoginPage constructor, we use the provider value to retrieve an instance of OAuthSettings and set to a public property. That's it, everything else is handled in our platform-specific renderers. Every PageRenderer we added has platform-specific functionality and is essentially our Xamarin.Forms page; so essentially in iOS, a UIViewController; in Android is almost like an Activity, but the lifecycle is a little bit different; and for Windows Phone, the same applies as a PhoneApplicationPage under the hood. There are more renderers that correspond to the type of page you want. We also decorate our renderers namespace with the ExportRenderer attribute. With this, Xamarin.Forms knows which implementation to load in runtime. For every PageRenderer, we override OnElementChanged and get the actual Xamarin.Forms page. For iOS, we use the ViewDidAppear iOS-specific method, but for Android we don't have any specific lifecycle methods so we write all our authentication code in the OnElementChanged method. We capture if the login is in progress in case we dismiss the modal page and OnElementChanged is invoked again to avoid showing again the authentication page. The Xamarin.Auth plugin has an OAuth2Authenticator class that we instantiate with the information from our OAuthSettings instance that we have in our Xamarin. Forms page ProviderOAuthSettings property that we have access in our renderer. The best of both worlds! Register the completed event that will be invoked in success and in cancel cases and the error event that will notify us if any error is occurred. With the Auth.GetUI() method, we get a platform-specific object that we can use to show our authentication view. Last but not least, in the Completed event we close the authentication view that we presented on each platform.
See also
• Chapter 2, Declare Once, Visualize Everywhere • Chapter 4, Different Cars, Same Engine • https://developer.xamarin.com/guides/cross-platform/xamarinforms/custom-renderer/
• https://components.xamarin.com/view/xamarin.auth
[ 312 ]
Declare Once, Visualize Everywhere In this chapter, we will cover the following recipes: • • • •
Creating a tabbed-page cross-platform application Adding UI behaviors and triggers Configuring XAML with platform-specific values Using custom renderers to change the look and feel of views
Introduction
Xamarin.Forms is a cross-platform UI framework. So far, we have seen examples of creating the UI in code using procedural code, creating objects, wiring them to events, and adding them to collections. That's OK, but when it comes to a more complicated UI structure, this code can get very large, hard to understand, and difficult to visualize what has been created. Another limitation is that we mix design code and behavior, which can become difficult to separate the roles between developer and designer, and also concerns like colors and fonts. This is where XAML comes to save us from the pain. It is a markup language originally created by Microsoft to describe the user interface in Windows Presentation Foundation (WPF). Using a declarative resource-style approach to describe the UI makes it clearer to visualize it and to separate the concerns between design and development. If you have used Microsoft XAML before, it will be very easy to get up and running. The specifications are exactly the same, although the tags are specific Xamarin.Forms controls. [ 313 ]
Declare Once, Visualize Everywhere
At the time of writing this module, there is no UI designer option, only XAML text editors. However, XAML language is toolable and extensible, and I'm pretty sure that Xamarin will soon introduce either a designer or an extension, for example for Blend! In this chapter, we will see examples of how to create pages using XAML, add layout controls, behavior, customize the look and feel of the native rendered controls, and apply platform-specific values. So, let's dive in!
Creating a tabbed-page cross-platform application
In Chapter 1, One Ring to Rule Them All recipes we set up our cross-platform shared UI layout using procedural behind code. There's nothing wrong with this, and you might need to continue doing it when creating custom controls. Xamarin.Forms has a big advantage utilizing XAML, which is a declarative language. Using XAML we can easily separate the design and the the behavior to speak a universal markup language between designers and developers while having reusable components, styles, and resources. If you've done XAML programming with Microsoft before, you will find it exactly the same since the specifications are the same. The difference is mainly the controls and layout containers that you use. We will take this approach in all the recipes of the remaining chapters. In this section, we will demonstrate how to use XAML and create a four-tabbed-page cross-platform application.
How to do it…
1. Create your cross-platform using Visual Studio or Xamarin Studio. 2. Add | New Folder in the core PCL project, and name it Pages. 3. Add | New File, choose top-section Forms, and then select Forms Content Page Xaml. Name it FormsTabPage.
[ 314 ]
Chapter 2
4. The newly created page is a ContentPage, so we need to change the root tag to TabbedPage.
Navigation.PushAsync(new PhotoPage());
[ 319 ]
Declare Once, Visualize Everywhere
6. Run the application, go to the Basketball tab page, and press the photo button. The result should be similar to the following screenshots: iOS:
[ 320 ]
Chapter 2
Android:
[ 321 ]
Declare Once, Visualize Everywhere
Windows Phone:
How it works…
In this recipe, we covered how you can navigate using the Xamarin.Forms TabbedPage. Tab applications is a common pattern in the mobile world that users from any platform are familiar with. As we see, creating a TabbedPage is straightforward. Setting the title of the tab item and the icon that is used only in the iOS platform, makes sense since it is the only platform that uses the concept of tab icons at the bottom tab bar. In Android, you have the tab on top of the page with only the title text. In Windows Phone, the native equivalent control is the pivot that allows only a title text to each item. Xamarin.Forms respects the native platform user interface guidelines and does its best to keep the look and feel of the platform's nature.
[ 322 ]
Chapter 2
TabbedPage is essentially a container of pages. It actually has a property, Children, that you can add pages programmatically, but since we use XAML it's implicit with adding the children inside the page's root tag, TabbedPage.
To accomplish referencing our pages in XAML we added a namespace with the key mypages and a xmlns statement. Then, for every page, we need to set the Title and Icon properties for each tab item. We added four tab items in our cross-platform application. If you try to add five or more tabs, it will produce different behavior for each platform. The iOS design decision is to allow five tab items, if more then the last tab to the right becomes a list page where the user can choose an option. In Android, the top tab bar becomes scrollable while adding tab items. In Windows Phone, the pivot is just adding more items. Adding navigation in your tab page is fairly simple using the navigation container. You could add a NavigationPage to wrap your FormsTabPage and utilize navigation for every page. In our example, we added navigation in only one of our pages to maintain the concept of customization and separation. In XAML the easy way to do this is wrap the page in a NavigationPage. Notice that the page is declared inside the tag; it is important for the instantiation of the NavigationPage in runtime using our page as a constructor parameter. It is also mandatory to set the Icon to the NavigationPage and the Title to both the NavigationPage and the ContentPage. There is no exception if it is omitted, but one title is for the tab item and the other for the navigation bar in the iOS platform.
See also
• https://developer.xamarin.com/guides/cross-platform/xamarinforms/user-interface/xaml-basics/
• https://developer.xamarin.com/guides/cross-platform/xamarinforms/controls/pages/
[ 323 ]
Declare Once, Visualize Everywhere
Adding UI behaviors and triggers
You already must have come to the common problem of where do we write business logic, how to organize it, and reflect the requirement or give a visual hint to the user. Validation rules and business flow is everywhere from the data layer to the UI layer, and we follow various practices to accomplish its level of input and data integrity. This is where behaviors and triggers are introduced. The first attaches independent testable functionality to our UI controls (think about it as an element input validator), while the later is used in XAML to express user interface changes based on monitoring properties, events, data, and a combination of expressions. In this section, we will take a login page, similar to the Creating a cross-platform login screen section of Chapter 1, One Ring to Rule Them All, but using XAML, and apply some validation rules.
How to do it…
There are four types of triggers, and we'll demonstrate all in this recipe. • Property trigger – executed when a property on a control is set to a particular value • Data trigger – same as the property trigger but uses data binding • Event trigger – occurs when an event is raised on the control • Multi trigger – allows multiple trigger conditions to be set before an action occurs First, let's add the login page and a property trigger; this trigger will allow us to define some setters if the property we are monitoring is changed to the value that is required. 1. Create a new cross-platform solution; we'll name it XamFormsBehaviorTriggers using your preferred IDE. Let's follow the Visual Studio way for this recipe. 2. Right-click, Add | New Folder to the core PCL XamFormsBehaviorTriggers project, and name it Pages. 3. Add | New Item, find and choose the Forms Xaml Page, and name it LoginPage. 4. Delete the default Label element that is automatically created and add the following code:
[ 324 ]
Chapter 2
5. Open the App.cs cross-platform entry application point and change the MainPage assignment to a LoginPage instance. MainPage = new LoginPage();
6. Run the application and you will get the desired UI for username, password, and a button to proceed with the authentication. 7. Create a ResourceDictionary with a style to apply the trigger to all the page elements. This will be applied to every TargetType in this page; in our example, we apply the style to every Entry view. Add the following code to the LoginPage.xaml after the opening tag ContentPage at the top. This will trigger a Setter when any Entry element's property IsFocused is changed value will turn the BackGroundColor to yellow and the TextColor to black. The trigger will fire by default every time the property is changed to true.
[ 325 ]
Declare Once, Visualize Everywhere
8. Test your Android platform.
Let's add a Data Trigger now, which will disable the button if there is no password entered. This kind of trigger works with Data Binding. If you want to see how Data Binding works, see Chapter 7, Bind to the Data. 1. Make the one-line Button tag to an open-close tag and add a DataTrigger.
[ 326 ]
Chapter 2
2. That's it. Run the iOS application and see the result logic in action.
When the case is taking an action when an element's event is raised, Event Trigger comes to the rescue. Event triggers are backed by a TriggerAction subclass with an abstract method that we have to implement. 1. Add | New Folder in the XamFormsBehaviorTriggers core PCL library; name it Triggers. 2. In the newly created folder, Add | New Item… and create a new class with the name LengthValidationTrigger.
[ 327 ]
Declare Once, Visualize Everywhere
3. The following code checks the Entry.Text.Length property and changes the background color accordingly. Modify the class with the following: public class LengthValidationTrigger : TriggerAction { protected override void Invoke(Entry sender) { bool isValid = sender.Text.Length > 6; sender.BackgroundColor = isValid ? Color.Yellow : Color.Red; } }
4. Open the LoginPage.xaml file. 5. To use our newly created LengthValidationTrigger, we need to define a xmlns namespace to the root ContentPage tag. xmlns:local="clrnamespace:XamFormsBahaviorTriggers.Triggers; assembly=XamFormsBahaviorTriggers"
6. Modify the passwordEntry element and add inside the enclosing tag the following trigger entry:
7. Run the Windows Phone platform application and type the first letters in the password entry control.
[ 328 ]
Chapter 2
For multiple condition requirements and business logic, we have the MultiTrigger. Follow the next steps to accomplish a case where both username and password should be filled to enable the Login button: 1. Go to the XamFormsBehaviorTriggers PCL core library and right-click Add | New Folder; name it Converters. 2. On the new folder Converters, right-click, Add | New Item…, and create a new class file with the name MultiTriggerConverter. Add the following code: public class MultiTriggerConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
[ 329 ]
Declare Once, Visualize Everywhere if ((int)value > 0) return true; // data has been entered else return false; // input is empty } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } }
3. Add the namespace for the Converter folder classes. Creating classes inside a Visual Studio folder adds the folder name to the created class namespace with the assembly name. xmlns:converters="clrnamespace:XamFormsBahaviorTriggers.Converters; assembly=XamFormsBahaviorTriggers"
4. Define a MultiTriggerConverter in the page's ResourceDictionary LoginPage.xaml file.
5. For our login button in the LoginPage.xaml page, add the following trigger in the Buttons.Triggers tag:
[ 330 ]
Chapter 2
6. Run your Android project, or any other platform you might want. See that you need to set at least one letter in both the username and password entry to enable the login button.
Nice and easy UI validation and modification for property changes, event notifications, use with data binding, and when a combination of conditions meet the requirements. Another exciting feature to modify the UI and attach functionality is Behaviors. Behaviors are written in code, a C# class, and attached in XAML. In this example, we will set the background property to red if the e-mail value is not a valid e-mail. 1. In Visual Studio, go to the core PCL XamFormsBehaviorTriggers project and Add | New Folder; name it Behaviors. 2. Right click in the created folder, Add | New Item…, create a new class file, and name it EmailValidatorBehavior.
[ 331 ]
Declare Once, Visualize Everywhere
3. Make the class a subclass of the Behavior generic class since we will attach it to an Entry element. 4. Add the following code to the class: const string EmailRegex = @"^(?("")("".+?(?
[ 336 ]
Chapter 2
2. Just run the app again and see the results. Magic in action! Android:
[ 337 ]
Declare Once, Visualize Everywhere
iOS:
[ 338 ]
Chapter 2
Windows Phone:
Using this feature in XAML is incredibly helpful, although sometimes we might want to handle a case in code.
[ 339 ]
Declare Once, Visualize Everywhere
In the next example, we will add a Label and see how to provide platform-specific values in code. 1. Open the MainPage.xaml and comment, or remove, the BoxView element. 2. Add a Label after the ContentPage.BackgroundColor tag.
3. In the behind-code file MainPage.xaml.cs, override the OnAppearing method and add the following code to set platform-specific values for the label we just added: protected override void OnAppearing() { base.OnAppearing(); Device.OnPlatform( iOS: () => { label.FontFamily = "HelveticaNeue-Thin"; label.FontSize = 10; }, Android: () => { label.FontFamily = "Arial Black"; label.FontSize = 20; }, WinPhone: () => { label.FontFamily = "Calibri"; label.FontSize = 30; } ); }
[ 340 ]
Chapter 2
4. That's it. Run the application and notice that Xamarin.Forms is handling our requirements for each platform. iOS:
[ 341 ]
Declare Once, Visualize Everywhere
Android:
[ 342 ]
Chapter 2
Windows Phone:
A very helpful platform-specific customization, which saves a lot of time! No abstractions with interfaces, no implementation classes, and dependency injection from our side. Set the properties and let the Xamarin.Forms magic happen!
[ 343 ]
Declare Once, Visualize Everywhere
How it works…
OnPlatform is a generic static class that has three properties: iOS, Android, and
Windows Phone of type T. Depending on which platform we are currently running, Xamarin.Forms implicitly casts to type T and returns the appropriate object. Its usage is really simple in XAML, by providing the x:TypeArguments property to the type that you target and then setting the three properties to the values that you want. Under the covers, Xamarin.Forms will provide the equivalent platform value with its default converter. In our XAML example, we set the BackgroundColor of the ContentPage to green for Android, red for iOS, and cyan to Windows Phone. Notice how we included the OnPlatform in the ContentPage.BackgroundColor tag and specified the type of the property we target setting the x:TypeArguments property to color. Xamarin.Forms then handles all the casting in the platform it is running on. To demonstrate the OnPlatform usage with a primitive type and another syntax, we added the OnPlatform tag in the BoxView.WidthRequest tag setting the x:TypeArgument to Double and the three properties to the desired values. You can also use this feature in code with two static methods in the Device class: OnPlatform and OnPlatform. In our example, we set the FontFamily and the FontSize of the label for each platform to a different value.
See also
• https://developer.xamarin.com/guides/cross-platform/xamarinforms/working-with/platform-specifics/
Using custom renderers to change the look and feel of views
A user interface built with Xamarin.Forms looks and feels native because it is native. Each of the available controls are rendered in the equivalent currently running platform view; for example, an Entry view is a UITextField in iOS, an EditText in Android, and a TextBox in Windows Phone. The great thing is that you can also create your own, or extend the existing ones. In this section, we will see how you can extend the view and customize the look and feel of the available Entry view.
[ 344 ]
Chapter 2
How to do it…
1. Start by creating a cross-platform project in Xamarin Studio or Visual Studio. For this example, we used Visual Studio because we don't have to do extra work to create and connect the Windows Phone to Xamarin.Forms. Name the project ViewCustomRenderer. 2. In the core PCL project, create a class file, Add | Class…, name it CustomEntry, and make it a subclass of Entry. 3. We will need a main page, Add | New Item…, and choose to create a Forms Xaml Page; name it LoginPage. 4. Add the following code. This will just put two CustomEntry views and an image with height 1 point to make it look like a separator line. Note that we have to declare the xmlns:local namespace to make our CustomEntry view available in XAML.
5. Open the App.cs file, and change the instantiation of the MainPage property in the constructor with a new LoginPage instance. MainPage = new LoginPage();
6. Run the application and you should see the default look and feel of the view for each platform. You must have noticed, for example, in the Android platform under the first EditText placeholder a line, then our separator, and again the same for the second EditText. In iOS, we get the default rounded edge UITextField, and a default TextBox in Windows Phone.
[ 345 ]
Declare Once, Visualize Everywhere
Let's try to make it a little bit flatter and provide a cross-platform look and feel with adding custom view renderers. 1. Go to the Android project and right-click, Add | Class…, give it the name CustomEntryRenderer, and make the newly created class a subclass of EntryRenderer. public class CustomEntryRenderer : EntryRenderer
2. Implement the OnElementChanged method, which is called when the corresponding Xamarin.Forms control is created. This will give us the opportunity to tweak the view post-initial setup. protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); if (Control != null) { Control.SetBackgroundDrawable(null); } }
3. A renderer requires an [assembly] attribute above the namespace to resolve the view type to the platform-specific renderer. [assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
4. There is nothing wrong running the application in any platform at this point, Xamarin.Forms will render the default view for any platform that don't have a platform-specific renderer implementation. Repeat the steps 1, 2 and 3 for the iOS platform. See the platform implementation following: [assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))] namespace ViewCustomRenderer.iOS { public class CustomEntryRenderer : EntryRenderer {
[ 346 ]
Chapter 2 protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); if (Control != null) { Control.BorderStyle = UIKit.UITextBorderStyle.None; } } } }
5. And the same for Windows Phone. [assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))] namespace ViewCustomRenderer.WinPhone { public class CustomEntryRenderer : EntryRenderer { protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); if (Control != null) { TextBox textBox = Control.Children[0] as TextBox; textBox.BorderBrush = new SolidColorBrush(Colors.Transparent); textBox.Background = new SolidColorBrush(Colors.Transparent); } } } }
[ 347 ]
Declare Once, Visualize Everywhere
6. Run all platforms and notice that the look and feel is more appropriate. iOS.
[ 348 ]
Chapter 2
Android:
[ 349 ]
Declare Once, Visualize Everywhere
Windows Phone:
How it works…
Declaring our control once in XAML and customizing the implementation for each platform is really powerful, especially when the OnPlatform feature doesn't cover your needs. In our case, we want to access the actual native control's properties. You can create a new control deriving by View or extend an existing one. Our case is straightforward: creating a class that simply derives from Entry. Lastly, we associated the renderer with the control through the [assembly: ExportRendererAttribute(Type view, Type renderer)]. This provides the dependency service of Xamarin.Forms to locate the specific renderer for the currently running platform.
[ 350 ]
Chapter 2
See also
• https://developer.xamarin.com/guides/cross-platform/xamarinforms/custom-renderer/
• https://developer.xamarin.com/videos/cross-platform/ xamarinforms-custom-renderers/ Downloading the example code You can download the example code files for this module from your account at http://www.packtpub.com. If you purchased this module elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you. You can download the code files by following these steps: 1. Log in or register to our website using your e-mail address and password. 2. Hover the mouse pointer on the SUPPORT tab at the top. 3. Click on Code Downloads & Errata. 4. Enter the name of the module in the Search box. 5. Select the module for which you're looking to download the code files. 6. Choose from the drop-down menu where you purchased this book from. 7. Click on Code Download. Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of: •
WinRAR/7-Zip for Windows
•
Zipeg/iZip/UnRarX for Mac
•
7-Zip/PeaZip for Linux
[ 351 ]
Native Platform-Specific Views and Behavior In this chapter, we will cover the following recipes: • Showing native pages with renderers • Attaching platform-specific gestures • Taking an in-app photo with the native camera page
Introduction
Xamarin.Forms cross-platform UI framework will provide you with all the mechanisms to write your code once and deliver to all three major mobile platforms on the market. You can write your cross-platform pages in the PCL shared project and everything will work. If you started this module from the beginning recipe by recipe, then you already encountered the usage of PageRenderers for platform-specific functionality and look-and-feel views customization. In this chapter, we will take the customization of Xamarin.Forms platform-specific pages, views, and behavior to its maximum usage. We start with adding native views for each platform, adding native behavior in views with a delay tap functionality in a view, adding additional views per platform in a page, and in the last recipe, using the native in-app photo capture pages. In the end, you will have gained all the skills and understanding about how you can customize specific scenarios and utilize the native APIs and features blending with the Xamarin.Forms framework: the best of both worlds!
[ 353 ]
Native Platform-Specific Views and Behavior
Showing native pages with renderers
While having all this cross-platform user interface behavior and code sharing, sometimes you still need to completely customize a page and blend it with native pages. In this recipe, we will create an example of loading native platform views for iOS XIB, Android AXML, and Windows Phone UserControl interfaces.
How to do it…
1. Create your cross-platform Xamarin.Forms application using a PCL to share code in Visual Studio or Xamarin Studio; let's name it XamFormsNativePages. We used Visual Studio in this example, as it's so easy to have all three projects ready for development! 2. Create a page to use it as our host MainPage. Right-click in the core PCL library and Add | Class…, and name it MainPage. 3. Use the following code. Here, we create a BindableProperty and a ButtonPressed event. public class MainPage : Page { public static readonly BindableProperty RandomNumberProperty = BindableProperty.Create("RandomNumber", typeof(int), typeof(MainPage), 0); public int RandomNumber { get { return (int)GetValue(RandomNumberProperty); } set { SetValue(RandomNumberProperty, value); } } public event EventHandler ButtonPressed; public void OnButtonPressed() { if (ButtonPressed != null) { ButtonPressed(this, EventArgs.Empty); } } }
[ 354 ]
Chapter 3
4. Go to App.cs and replace the code that initializes the MainPage property with instantiating our MainPage class we just created and register a handler to the ButtonPressed event. MainPage = new MainPage(); ((MainPage)MainPage).ButtonPressed += MainPageButtonPressed; private void MainPageButtonPressed(object sender, EventArgs e) { MainPage page = MainPage as MainPage; page.RandomNumber = new Random().Next(); }
5. In the Android project, go to the Resources/layout folder. In case it doesn't exist, create a new folder, right-click and Add | New Folder. In the layout folder, right-click and Add | New Item…. From the templates, choose Android Layout, name it MainDroidLayout.axml, and hit Add. 6. Now that we have our Android layout, paste the following code; it simply adds a button to the user interface:
7. We need to add a renderer where we will make it possible to inflate the native view. Right-click, Add | Class…, name it MainPageRenderer, make the new class a PageRenderer subclass, and add the following code. I know it's a lot, but we will see how it works later. public class MainPageRenderer : PageRenderer { private Android.Widget.Button _button; [ 355 ]
Native Platform-Specific Views and Behavior private Android.Views.View _view; private MainPage Page { get { return Element as MainPage; } } public MainPageRenderer() { Activity activity = (Activity)Forms.Context; _view = activity.LayoutInflater.Inflate (Resource.Layout.MainDroidLayout, this, false); _button = _view.FindViewById (Resource.Id.button); _button.Click += OnButtonClick; AddView(_view); } protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); var oldPage = e.OldElement as MainPage; if (oldPage != null) { oldPage.PropertyChanged -= OnPagePropertyChanged; } var newPage = e.NewElement as MainPage; if (newPage != null) { newPage.PropertyChanged += OnPagePropertyChanged; } UpdateButtonText(); } private void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e) {
[ 356 ]
Chapter 3 if (e.PropertyName == MainPage.RandomNumberProperty.PropertyName) { UpdateButtonText(); } } protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) { base.OnMeasure(widthMeasureSpec, heightMeasureSpec); _view.Measure(widthMeasureSpec, heightMeasureSpec); SetMeasuredDimension(_view.MeasuredWidth, _view.MeasuredHeight); } protected override void OnLayout(bool changed, int l, int t, int r, int b) { base.OnLayout(changed, l, t, r, b); _view.Layout(l, t, r, b); } private void UpdateButtonText() { if (Page != null) { _button.Text = Page.RandomNumber.ToString(); } } private void OnButtonClick(object sender, EventArgs e) { if (Page != null) { Page.OnButtonPressed(); } } }
[ 357 ]
Native Platform-Specific Views and Behavior
8. Don't forget, with PageRenderer there is always the need to instruct the Xamarin dependency service which implementation to use for each platform. If you miss it, no problem, but the default page will appear and this is not what you want! Add the following ExportRenderer above the namespace declaration: [assembly: ExportRenderer(typeof(MainPage), typeof(MainPageRenderer))]
9. There are some using statements of course, you can easily resolve with Ctrl+. in Visual Studio. 10. Let's jump on the iOS project. Right-click and Add | New Item…. From the templates, choose iPhone View Controller and create UIViewController with a XIB interface file with the name MainPageRenderer. Change the MainPageRenderer UIViewController derive class to PageRenderer; remember, PageRenderer in iOS is UIViewController. 11. Double-click the MainPageRenderer.xib file, which will open the user interface in the Xcode interface builder. At the center of the View, add a UIButton control and link it in the behind .h file as an outlet with the name button. Also, add a TouchUpInside action handler that will be invoked when the button is pressed; name it ButtonPressed. This process is exactly as you would do with the classic Xamarin iOS/Android application or creating a native iOS application. 12. Find next the class implementation of MainPageRenderer: public partial class MainPageRenderer : PageRenderer { private MainPage Page { get { return Element as MainPage; } } protected override void OnElementChanged(VisualElementChangedEventArgs e) { base.OnElementChanged(e); var oldPage = e.OldElement as MainPage; if (oldPage != null) { oldPage.PropertyChanged -= OnPagePropertyChanged; }
[ 358 ]
Chapter 3 var newPage = e.NewElement as MainPage; if (newPage != null) { newPage.PropertyChanged += OnPagePropertyChanged; } } public override void ViewDidLoad() { base.ViewDidLoad(); UpdateButtonText(); } private void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == MainPage.RandomNumberProperty.PropertyName) { UpdateButtonText(); } } private void UpdateButtonText() { if (IsViewLoaded && Page != null) { button.SetTitle(Page.RandomNumber.ToString(), UIControlState.Normal); } } partial void OnButtonPressed(UIButton sender) { if (Page != null) { Page.OnButtonPressed(); } } }
[ 359 ]
Native Platform-Specific Views and Behavior
13. Again, add the ExportRenderer attribute above the namespace. [assembly: ExportRenderer(typeof(MainPage), typeof(MainPageRenderer))]
14. And for Windows Phone, right-click, Add | New Item…. From the templates, choose Windows Phone User Control, give it the name WindowsPhoneControl.xaml, and add the following content in the Grid tag to add a Button:
15. Add | Class… and name it MainPageRenderer.cs. Copy the following code where we instantiate the newly created UserControl and add it to the Children property of the Windows Phone ViewGroup: public class MainPageRenderer : PageRenderer { private System.Windows.Controls.Button _button; private XamFormsNativePages.MainPage Page { get { return Element as XamFormsNativePages.MainPage; } } protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); var oldPage = e.OldElement as XamFormsNativePages.MainPage; if (oldPage != null) { oldPage.PropertyChanged -= OnPagePropertyChanged; } var newPage = e.NewElement as XamFormsNativePages.MainPage; if (newPage != null) { newPage.PropertyChanged += OnPagePropertyChanged; }
[ 360 ]
Chapter 3 WindowsPhoneControl ctrl = new WindowsPhoneControl(); _button = ctrl.button; _button.Click += OnButtonClick; Children.Add(ctrl); UpdateButtonText(); } private void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == XamFormsNativePages.MainPage. RandomNumberProperty.PropertyName) { UpdateButtonText(); } } private void UpdateButtonText() { if (Page != null) { _button.Content = Page.RandomNumber.ToString(); } } private void OnButtonClick(object sender, EventArgs e) { if (Page != null) { Page.OnButtonPressed(); } } }
16. No exception for the Windows platform. Add ExportRenderer above the namespace declaration. [assembly: ExportRenderer(typeof(XamFormsNativePages.MainPage), typeof(MainPageRenderer))]
[ 361 ]
Native Platform-Specific Views and Behavior
17. Voila! Run the application for each platform and press the button to get random numbers as the button's text. iOS:
[ 362 ]
Chapter 3
Android:
[ 363 ]
Native Platform-Specific Views and Behavior
Windows Phone:
How it works…
Xamarin.Forms might not be the perfect framework to create highly UI customizable applications, but it is definitely highly flexible. With the help of renderers, we can mix a cross-platform page with native views. We started the implementation creating a Page class, MainPage, adding a BindableProperty, RandomNumberProperty, and an event, ButtonPressed, that we can raise when a native button is pressed. Bindable properties are backing stores for properties that allow binding and raising property-changed events when you set a value. Our BindableProperty backing store is RandomNumber of type int.
[ 364 ]
Chapter 3
In the App.cs constructor, we set the MainPage property, the root page of our application, to our MainPage class and we register an event handler for the ButtonPressed event. When this event is raised, we set the backing store of our BindableProperty, RandomNumber, to a random number using the Random class. Simple stuff for our recipe purposes. In our Android platform, Resources/layout folder, we added an Android UI interface layout, AXML file. If you are familiar with native Android applications, this is a declarative XML-style file that we describe as a user interface. There are only two tags: the LinearLayout main tag that includes a button with the resource name button. We are ready to create a PageRenderer class, MainPageRenderer, which exposes Activity methods, but remember that the lifecycle events we receive are similar but not fully supporting the Android activity lifecycle events. We customize the native appearance and behavior in the class by declaring two fields: one for the native Android Button, _button, and one for the Android main View, _view, of our AXML UI interface layout, and a helper property to access the cross-platform Page instance. In the constructor, we grab the native Activity instance using the Forms.Context static property and with this, we inflate our AXML user interface and assign it to our _view property. Having the main View of the layout interface, we use it to grab a reference of the Button instance and registering a handler for its OnClick event. In the end, we use the method AddView(View) to present our custom native UI interface for this Xamarin.Forms page to the client! We override the OnElementChanged method, where we have the chance to check if there is an OldElement MainPage reference. This gives us the chance to clean any event handlers registered to avoid memory leaks or other resources. In our case, we unregister the PropertyChanged event notification. We then check for a NewElement MainPage reference and assign the handler to the PropertyChanged event. A Page class inherits from VisualElement, which inherits Element, which in turn inherits BindableObject, which implements the INotifyPropertyChanged interface and gives us the opportunity to get notified for any BindableProperty assignment. In the OnPagePropertyChanged handler, we check if the property event raised from RandomNumberProperty and we update the native Button text property with a method called UpdateButonText using the Page.RandomNumber property value. The Button.OnClick handler raises in its turn the Page.ButtonPressed event using the method helper, Page.OnButtonPressed, where it fires the series of events again and updates the native Button.Text property. The preceding logic is pretty much the same for the iOS and Windows Phone platforms. Let's take a quick look at the iOS platform. [ 365 ]
Native Platform-Specific Views and Behavior
The native user interface layout in iOS is represented by XIB files or Storyboards. We added in our platform project a XIB file, MainPageRenderer.xib, backed with a UIViewController, MainPageRenderer.cs. Since PageRenderer in iOS is UIViewController, we change the class to a PageRenderer subclass. In the XIB file, we add a UIButton view with the name button and create an action method for the TouchUpInside notification with the signature OnButtonPressed(UIButton sender), which we implement in our MainPageRenderer class. The rest of the implementation is the same as the Android platform. In the Windows Platform, we created a UserControl XAML file, WindowsPhoneControl.xaml, added a Button, and in the equivalent MainPageRenderer class, we created an instance of the WindowsPhoneControl class and added it in the ViewGroup.Children collection of the native page. Happy customization (if needed)!
See also
• https://developer.xamarin.com/api/type/Xamarin.Forms. BindableObject/
• https://developer.xamarin.com/api/type/Xamarin.Forms. BindableProperty/
• Chapter 7, Bind to the Data • Using custom renderers to change the look and feel of views recipe from Chapter 2, Declare Once, Visualize Everywhere
Attaching platform-specific gestures
Every mobile platform provides us with a way of handling gestures. The approach is not the same for each platform, but the point is the same: accept touch events from the user. Xamarin.Forms, as of this writing, has cross-platform support for the tap gesture, but worry not, view renderers come to the rescue once again. In this recipe, we will demonstrate how to add long press behavior to BoxView. The equivalent for each platform is iOS CGContext, Android ViewGroup, and Rectangle for the Windows Phone equivalent.
[ 366 ]
Chapter 3
How to do it…
1. Create a Xamarin cross-platform application using Visual Studio. Xamarin Studio works too of course; just add the Windows platform if you need to use Visual Studio later. Let's give it the name XamFormsPlatformGesture. 2. In the PCL project, right-click and Add | New Item…, choose Forms Xaml Page, and name it MainPage. 3. In the App.cs constructor, change the assignment of the MainPage property with a new instance of our newly created MainPage.xaml page. 4. We will need to create our custom control. Since in this example we use the BoxView control, right-click again and Add | Class…, name it CustomBoxView, and make it a subclass of BoxView. 5. Open the MainPage.xaml file and in the ContentPage root tag, add the project namespace so that we can use our new CustomBoxView control. xmlns:local="clrnamespace:XamFormsPlatformGesture; assembly=XamFormsPlatformGesture"
6. Now, let's add CustomBoxView control to the page.
7. In the Android platform, right-click and Add | Class…. We will add a renderer class for CustomBoxView, so give it the name CustomBoxViewRenderer. 8. To catch gestures in Android, we will use SimpleOnGestureListener. So right-click and choose Add | Class…; give it the name CustomBoxViewGestureListener. Make it a subclass of SimpleOnGestureListener and override the OnLongPress method. Check the following implementation: public class CustomBoxViewGestureListener : GestureDetector.SimpleOnGestureListener { public override void OnLongPress(MotionEvent e) { Console.WriteLine("OnLongPress"); base.OnLongPress(e); } }
[ 367 ]
Native Platform-Specific Views and Behavior
9. CustomBoxViewRenderer has to derive from BoxRenderer. Add the following code to attach the native view to our CustomBoxViewGestureListener: public class CustomBoxViewRenderer : BoxRenderer { private readonly CustomBoxViewGestureListener _listener; private readonly GestureDetector _detector; public CustomBoxViewRenderer() { _listener = new CustomBoxViewGestureListener(); _detector = new GestureDetector(_listener); } protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); if (e.NewElement == null) { if (this.GenericMotion != null) { this.GenericMotion -= HandleGenericMotion; } if (this.Touch != null) { this.Touch -= HandleTouch; } } if (e.OldElement == null) { this.GenericMotion += HandleGenericMotion; this.Touch += HandleTouch; } } void HandleTouch(object sender, TouchEventArgs e) { _detector.OnTouchEvent(e.Event); }
[ 368 ]
Chapter 3 void HandleGenericMotion(object sender, GenericMotionEventArgs e) { _detector.OnTouchEvent(e.Event); } }
10. And of course a common mistake for a renderer is to forget adding the dependency ExportRendererAttribute above the namespace declaration. [assembly: ExportRenderer(typeof(CustomBoxView), typeof(CustomBoxViewRenderer))]
11. For the iOS platform, add a new class, right-click and Add | Class…. You guessed it right: name it CustomBoxViewRenderer and find the code next to add UILongPressGestureRecognizer to the view; public class CustomBoxViewRenderer : BoxRenderer { UILongPressGestureRecognizer longPressGestureRecognizer; protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); longPressGestureRecognizer = new UILongPressGestureRecognizer(() => Debug.WriteLine("Long Press")); if (e.NewElement == null) { if (longPressGestureRecognizer != null) { this.RemoveGestureRecognizer (longPressGestureRecognizer); } } if (e.OldElement == null) { this.AddGestureRecognizer (longPressGestureRecognizer); } } }
12. Repeat step 10 for the iOS platform dependency registration of CustomBoxViewRenderer. [ 369 ]
Native Platform-Specific Views and Behavior
13. Add a class to the Windows Phone platform project. Right-click and Add | Class…. This is the Windows renderer, so give it the name CustomBoxViewRenderer. The difference is in the Windows platform is that we derive from BoxViewRenderer, a slight name difference to the Android and iOS platforms. Find the implementation of registering to the following Hold event: public class CustomBoxViewRenderer : BoxViewRenderer { protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); if (e.NewElement == null) { this.Hold -= OnHold; } if (e.OldElement == null) { this.Hold += OnHold; } } private void OnHold(object sender, GestureEventArgs e) { Debug.WriteLine("OnHold"); } }
14. Run your applications and long press in the rectangle in the center of the screen. You can see a message in the application output window that is the equivalent debug message.
How it works…
As we saw in the preceding example, while Xamarin.Forms doesn't provide us with all types of gestures, it's easy to attach listeners and events using platform-specific renderers as we would do with Xamarin iOS and Xamarin Android classic approach in the native application layer. To start, we need the view that the behavior will be attached. For this recipe, we used the simple BoxView element. To extend it, we create an empty subclass of BoxView, CustomBoxView, and then for each platform, we added the equivalent renderer.
[ 370 ]
Chapter 3
In Android, we added a SimpleOnGestureListener, CustomBoxViewGestureListener, and implemented the OnLongPress method. In the CustomBoxViewRenderer constructor, we create an instance of our listener and then create GestureDetector passing the listener. In the OnElementChanged method, we check if the e.NewElement is null and unregister the event handler of the GenericMotion and Touch properties of the view. If there is an instance of the e.OldElement property, we only register the event handlers; this is how we make sure we're not reusing the control. The handlers are simply sending the e.Event in the GestureDetector. OnTouchEvent method, then the application output prints the message OnLongpress. iOS is working with gesture recognizers that you can add in a view. There are numerous recognizers that you can use for every case that you want to handle, or you can also implement the TouchesBegan, TouchesEnded, TouchesCancelled, and TouchesMoved methods to own the whole process of touches on the screen.
Here, we simply attach UILongPressGestureRecognizer to our view, passing an action delegate in the constructor to print the message Long press in the output window. The Windows platform is no exception: CustomBoxViewRenderer registers a handler to the OnHold event of the element and prints the message OnHold.
See also
• Using custom renderers to change the look and feel of views recipe from Chapter 2, Declare Once, Visualize Everywhere • Adding gesture recognizers in XAML recipe from Chapter 9, Gestures and Animation
Taking an in-app photo with the native camera page
This chapter is all about mix and match cross-platform UI and native platform UI. In the last recipe of the chapter, we will create a Xamarin.Forms solution and utilize the native APIs of each platform to capture a photo.
[ 371 ]
Native Platform-Specific Views and Behavior
How to do it…
1. As usual, create a Xamarin.Forms project in Visual Studio using a PCL class library for our core shared project. Give it the name XamFormsInAppPhoto. 2. Right-click our core PCL project and Add | Class…, name it InAppCameraPage, and click Add. 3. Make it a ContentPage subclass. 4. Go to the App.cs constructor and replace the code in the MainPage property assignment with a new InAppCameraPage instance. MainPage = new InAppCameraPage();
That's all we need for our core project setup. We'll move now to the Android platform. 1. To get access in the related camera APIs of Android, we will need a custom PageRenderer for each platform. Right-click the Android project, Add | Class…, name the class InAppCameraPageRenderer, and click Add. 2. Make it a subclass of PageRenderer and implement the TextureView. ISurfaceTextureListener interface. public class InAppCameraPageRenderer : PageRenderer, TextureView.ISurfaceTextureListener
3. Let's add ExportRender attribute on top of the namespace declaration now to instruct DependencyService in Xamarin.Forms that we want to use this PageRenderer for our InAppCameraPage. It's a vital piece. If you find yourself in a situation that your custom PageRenderer has not loaded, the first thing to check is if you're missing ExportRender attribute. [assembly: ExportRenderer(typeof(InAppCameraPage), typeof(InAppCameraPageRenderer))]
4. To load our camera live feed, we will need an Android layout file. Rightclick in the Resources/layout folder and Add | New Item…, choose Android Layout, name it InAppPhotoLayout.axml, and click Add. If there is no layout folder in the Resources folder, create one by right-clicking in Resources and Add | New Folder. 5. In the newly created camera feed layout, add the following code:
6. Go to the InAppCameraPageRenderer.cs class file again and add the following private fields: Android.Hardware.Camera camera; Android.Widget.Button takePhotoButton; Activity activity; TextureView textureView; Android.Views.View view; ImageView snapshotImageView;
7. Override the OnElementChanged method. protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); if (e.OldElement != null || Element == null) return; activity = this.Context as Activity; view = activity.LayoutInflater.Inflate (Resource.Layout.InAppPhotoLayout, this, false); textureView = view.FindViewById (Resource.Id.textureView);
[ 373 ]
Native Platform-Specific Views and Behavior textureView.SurfaceTextureListener = this; takePhotoButton = view.FindViewById (Resource.Id.takePhotoButton); takePhotoButton.Click += OnTakePhoto; snapshotImageView = view.FindViewById (Resource.Id.snapshotView); AddView(view); }
You might have already noticed a warning regarding the Android.Hardware. Camera Android API. This API is marked as obsolete and is deprecated as of Android 5.0. However, it is fine to use the API until your minSdkVersion is 21 or higher where you will need to use the replacement Android.Hardware.Camera2. 1. Add the OnTakePhoto method to capture the photo and set it to ImageView. private void OnTakePhoto(object sender, EventArgs e) { camera.StopPreview(); snapshotImageView.SetImageBitmap (textureView.Bitmap); camera.StartPreview(); }
2. Now, let's implement the TextureView.ISurfaceTextureListener methods. public void OnSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { camera = Android.Hardware.Camera.Open((int)CameraFacing.Back); textureView.LayoutParameters = new FrameLayout.LayoutParams(width, height); camera.SetPreviewTexture(surface); PrepareAndStartCamera(); } public bool OnSurfaceTextureDestroyed (SurfaceTexture surface) { camera.StopPreview(); camera.Release();
[ 374 ]
Chapter 3 return true; } public void OnSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { PrepareAndStartCamera(); } public void OnSurfaceTextureUpdated(SurfaceTexture surface) { // Nothing }
3. Add the PrepareAndStartCamera method used just now. We need this configuration to overcome a bug with certain hardware. For details, you can refer to https://code.google.com/p/android/issues/detail?id=1193. private void PrepareAndStartCamera() { camera.StopPreview(); var display = activity.WindowManager.DefaultDisplay; if (display.Rotation == SurfaceOrientation.Rotation0) { camera.SetDisplayOrientation(90); } if (display.Rotation == SurfaceOrientation.Rotation270) { camera.SetDisplayOrientation(180); } camera.StartPreview(); }
4. To make our native layout visible, we need to override the OnLayout method of PageRenderer and set the size and position. protected override void OnLayout(bool changed, int l, int t, int r, int b) { base.OnLayout(changed, l, t, r, b);
[ 375 ]
Native Platform-Specific Views and Behavior var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly); var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly); view.Measure(msw, msh); view.Layout(0, 0, r - l, b - t); }
5. Ready! Now you can test the project on a device or an emulator that supports a camera. The next screenshot is taken with my Android tablet device I use to test hardware APIs. It is always better to test hardware capabilities and APIs on a device to make sure everything works as we scheduled.
It's Apple's turn. Focus on the XamFormsInAppPhoto.iOS project. We will use Xamarin Studio to accomplish the next steps since we will use Xcode Interface Builder to create our native UI. 1. Right-click the iOS platform project and Add | New File…. In the iOS tab, select iPhone View Controller, name it InAppCameraPage, and click Add. [ 376 ]
Chapter 3
2. Derive from PageRenderer and you can also remove the constructor overload safely, since there is no such overload for our PageRenderer base class. public partial class InAppCameraPage : PageRenderer
3. Add ExportRender attribute to make our custom PageRenderer load in runtime. [assembly: ExportRenderer(typeof(InAppCameraPage), typeof(XamFormsInAppPhoto.iOS.InAppCameraPage))]
4. For this step, we will polish our native UI skills and open XamFormsInCameraPage.xib created with our UIViewController in Xcode. Double-click the file. 5. Now, in the UIView of Interface Builder, drop two UIViews inside. Be careful, because both of the UIViews must be children of the main UIView and siblings to each other; this is important in creating an overlay view to make other views visible on top of the camera live stream. Check the following screenshot of our example and also refer to the code of this module for more details. We added constraints to make the layout appear appropriately in all different-sized devices.
[ 377 ]
Native Platform-Specific Views and Behavior
6. In addition to UIViews, we add a UIImageView to preview our captured photo and a UIButton to capture one. 7. Next, let's add the outlets needed in the code and an action for the UIButton; the action will be invoked when the event TouchUpInside is raised. 8. If you are familiar with Xcode Interface Builder, holding down the control button on a view, dragging next to the assistant editor window with the left mouse button pressed, and releasing the mouse button creates an outlet if we are editing the header file (.h), or an action in the implementation file (.m). See next the outlets we created in our header file and the action added in the implementation file for our example. Outlets:
[ 378 ]
Chapter 3
Action:
9. Returning to Xamarin Studio, it will automatically update the Xcode changes made. Now our outlets are available as properties and the action is a partial method we need to implement in our custom PageRenderer, InAppCameraPage. 10. Open InAppCameraPage.cs and add the following private fields. These are the classes we need to work with the iOS camera API. AVCaptureSession captureSession; AVCaptureDeviceInput captureDeviceInput; AVCaptureStillImageOutput stillImageOutput;
11. Add the following method, AuthorizeCameraUseAsync. iOS requires that you get the consent of the user to get access to some APIs; camera is one of them. Let's also put some code in ViewDidLoad to ask the user for permission and continue setting up the camera live feed. public override void ViewDidLoad () { base.ViewDidLoad (); AuthorizeCameraUseAsync ().ContinueWith ((antecedent) => { bool result = antecedent.Result; if (result) { [ 379 ]
Native Platform-Specific Views and Behavior SetupCameraLiveFeed (); } }); } public async Task AuthorizeCameraUseAsync() { var authorizationStatus = AVCaptureDevice.GetAuthorizationStatus (AVMediaType.Video); if (authorizationStatus != AVAuthorizationStatus.Authorized) { return await AVCaptureDevice.RequestAccessForMediaTypeAsync (AVMediaType.Video); } else if (authorizationStatus == AVAuthorizationStatus.Authorized) { return true; } return false; }
12. Add the SetupCameraLiveFeed method and a helper ConfigureCameraForDevice method. public void SetupCameraLiveFeed() { captureSession = new AVCaptureSession(); AVCaptureVideoPreviewLayer videoPreviewLayer = new AVCaptureVideoPreviewLayer(captureSession) { Frame = cameraViewContainer.Bounds }; cameraViewContainer.Layer.AddSublayer(videoPreviewLayer); AVCaptureDevice captureDevice = AVCaptureDevice.DefaultDeviceWithMediaType (AVMediaType.Video); ConfigureCameraForDevice(captureDevice); captureDeviceInput = AVCaptureDeviceInput.FromDevice(captureDevice);
[ 380 ]
Chapter 3 NSMutableDictionary dictionary = new NSMutableDictionary(); dictionary[AVVideo.CodecKey] = new NSNumber((int)AVVideoCodec.JPEG); stillImageOutput = new AVCaptureStillImageOutput() { OutputSettings = new NSDictionary() }; captureSession.AddOutput(stillImageOutput); captureSession.AddInput(captureDeviceInput); captureSession.StartRunning(); } public void ConfigureCameraForDevice(AVCaptureDevice device) { NSError error = new NSError(); if (device.IsFocusModeSupported (AVCaptureFocusMode.ContinuousAutoFocus)) { device.LockForConfiguration(out error); device.FocusMode = AVCaptureFocusMode.ContinuousAutoFocus; device.UnlockForConfiguration(); } else if (device.IsExposureModeSupported (AVCaptureExposureMode.ContinuousAutoExposure)) { device.LockForConfiguration(out error); device.ExposureMode = AVCaptureExposureMode.ContinuousAutoExposure; device.UnlockForConfiguration(); } else if (device.IsWhiteBalanceModeSupported (AVCaptureWhiteBalanceMode.ContinuousAutoWhiteBalance)) { device.LockForConfiguration(out error); device.WhiteBalanceMode = AVCaptureWhiteBalanceMode.ContinuousAutoWhiteBalance; device.UnlockForConfiguration(); } } [ 381 ]
Native Platform-Specific Views and Behavior
13. Implement the CameraPhotoTouchUpInside method and add an async method to capture the photo. partial void CapturePhotoTouchUpInside (UIKit.UIButton sender) { CapturePhotoAsync(); } public async Task CapturePhotoAsync() { var videoConnection = stillImageOutput.ConnectionFromMediaType (AVMediaType.Video); var sampleBuffer = await stillImageOutput.CaptureStillImageTaskAsync (videoConnection); var jpegImageAsNsData = AVCaptureStillImageOutput.JpegStillToNSData (sampleBuffer); var image = new UIImage (jpegImageAsNsData); }
14. Let's clean up resources by overriding the Dispose method. protected override void Dispose(bool disposing) { captureSession.Dispose(); captureDeviceInput.Dispose(); stillImageOutput.Dispose(); base.Dispose(disposing); }
15. The iOS platform is ready. See next a screenshot from my iPhone 6 device:
[ 382 ]
Chapter 3
Last but not least. Microsoft Windows Phone offers, of course, APIs to work directly with the camera, or use one of the chooser tasks. In our example, we will use the CameraCaptureTask API to capture a photo and assign it to an image control of our native UserControl. 1. Back to Visual Studio, right-click in XamFormsInAppPhoto.WinPhone, Add | New Item…, select Windows Phone User Control, give it the name PreviewImageUserControl, and click Add. 2. Double-click the PreviewImageUserControl.xaml file and add an Image view inside the Grid layout view.
[ 383 ]
Native Platform-Specific Views and Behavior
3. Right-click the project again and Add | Class…, name it InAppCameraPageRenderer, and click Add. Make it a PageRenderer subclass. 4. Add ExportRender attribute above the namespace declaration of the InAppCameraPageRenderer.cs file. [assembly: ExportRenderer(typeof(InAppCameraPage), typeof(InAppCameraPageRenderer))]
5. Add two private member fields to have a reference of our native PreviewImageUserControl and CameraCaptureTask. CameraCaptureTask cameraCaptureTask; PreviewImageUserControl previewImageUserControl;
6. Override the OnElementChanged method and add the following code. We simply instantiate the CameraCaptureTask field, the custom PreviewImageUserControl, and add it in the Children collection of the view. At the end, we Show the CameraCaptureTask. protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); if (e.OldElement != null || Element == null) return; cameraCaptureTask = new CameraCaptureTask(); cameraCaptureTask.Completed += OnCameraCaptureTaskCompleted; previewImageUserControl = new PreviewImageUserControl(); Children.Add(previewImageUserControl); cameraCaptureTask.Show(); }
7. Add the OnCameraCaptureTaskCompleted event handler. private void OnCameraCaptureTaskCompleted(object sender, PhotoResult e) { if (e.TaskResult == TaskResult.OK) {
[ 384 ]
Chapter 3 BitmapImage bmp = new BitmapImage(); bmp.SetSource(e.ChosenPhoto); previewImageUserControl.previewImage.Source = bmp; } }
8. You can of course use a device to test the Windows Phone platform, but the emulator supports a camera. For this example, we used the Windows Phone 8.1 emulator. When we start the project, CameraCaptureTask immediately shows the following:
[ 385 ]
Native Platform-Specific Views and Behavior
9. Click the camera button at the bottom. It will close the task chooser and return to our page showing the image captured!
[ 386 ]
Chapter 3
How it works…
For the Android and iOS platforms, we used native APIs to access the stream of the camera. In Android, we used the Android.Hardware.Camera API and not the new Android. Hardware.Camera2 because we want to have backward compatibility lower to Android SDK 21. We created a layout with a root FrameLayout, a TextureView to display our camera's stream, an ImageView to preview a snapshot captured, and a Button to capture a snapshot. We implemented TextureView.ISurfaceTextureView to get notified when the surface texture associated with this TextureView is available. In the OnElementChanged method, we get our TextureView instance and set SurfaceTextureListener to our InAppCameraPageRenderer instance. When is available the OnSurfaceTextureAvailable implemented method is invoked and we open the camera, set the LayoutParameters of the TextureView, set the camera's preview texture with the provided surface, and in the end we prepare and start the camera. In OnSurfaceTextureDestroyed, we stop the stream preview and release it from memory, and in OnSurfaceTextureSizeChanged, we again call PrepareAndStartCamera to make sure that the stream appears correctly to orientation and size changes. For takePhotoButton, we subscribed a delegate handler to the Click event where we stop the stream, assign TextureView.Bitmap to snapshotImageView, and start the stream again. Since we load our native Android XML layout in our InAppCameraPageRenderer, we override the OnLayout method to provide the inflated root View with the appropriate layout size. iOS native APIs live in the AVFoundation framework kit. In the ViewDidLoad method, we check and request for authorization if needed and then set up the live feed if it is allowed using AVCaptureSession passed to AVCaptureVideoPreviewLayer. Adding it to our cameraViewContainer. Layer as a sublayer, configure the default video media AVCaptureDevice and get AVCaptureDeviceInput from AVCaptureDevice. We then create an AVCaptureStillImageOutput instance and pass it to AVCaptureSession. AddOutput. Pass AVCaptureDeviceInput to AVCaptureSession.AddInput and invoke the AVCaptureSession.StartRunning() method. Implementing the partial CapturePhotoTouchUpInside method, we use AVCaptureStillImageOutput to get AVCaptureConnection, passing it to the CaptureStillImageTaskAsync method and then translate the returned CMSamplBuffer to an NSData instance with the AVCaptureStillImageOutput. JpegStillToNSData static method. Then we just create a UIImage passing the NSData instance and set to the captureImageView.Image property. [ 387 ]
Native Platform-Specific Views and Behavior
We also override the Dispose method to free some memory upon release of the InAppCameraPageRenderer instance. Using the iOS camera live stream is much more complicated than the Android example and it is strongly recommended to review the native APIs for a greater understanding and control of the AVFoundation framework. The Windows Phone CameraCaptureTask is straightforward: creating an instance, registering to the completed event, and showing the task. When we capture a photo, the completed event is invoked and we set the photo to our loaded UserControl image property.
See also
• https://developer.xamarin.com/recipes/android/other_ux/ textureview/
• https://developer.xamarin.com/api/type/Android.Views.TextureVie w+ISurfaceTextureListener/
• https://developer.xamarin.com/recipes/android/other_ux/ textureview/display_a_stream_from_the_camera/
• https://developer.apple.com/library/prerelease/ios/
documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/04_ MediaCapture.html
[ 388 ]
Different Cars, Same Engine In this chapter, we will cover the following recipes: • Sharing code between different platforms • Using the dependency locator • Adding a third-party Dependency Injection Container • Architecture design with Model-View-ViewModel (MVVM) pattern • Using the event messenger • Adding localization
Introduction
In this chapter, we will learn the different approaches, Shared Projects and Portable Class Libraries (PCL), to share code across all platforms. DependencyService is the out-of-the-box service locator from Xamarin.Forms and
we will learn how to use it to resolve implementation classes for our interfaces in runtime. Using a service locator is a solution to register the implementation classes you need to use for your abstracted interfaces in the application. In this chapter, we will explore Xamarin.Forms' built-in DependencyService to map interfaces to implementation classes.
[ 389 ]
Different Cars, Same Engine
Moving forward, we will explore aspect oriented programming (AOP), which is accomplished using Dependency Injection. With Dependency Injection, the class is given its dependencies automatically via a constructor with all required parameters. The Dependency Injection Container is responsible for resolving the dependencies needed; we will use Ninject to see how we can apply Dependency Injection in our solution. Many times, you have two components in your application where none is referencing the other, but still want to receive notifications of an action. You can accomplish this with a global event messenger. There are two types of event messaging: instance events and global events. Using the Xamarin.Forms event messenger, we will learn how to publish global events and subscribe clients that will listen for these notifications. Mobile applications may target an international audience, meaning multiple languages will have to be supported. Xamarin.Forms supports localization, and the last section demonstrates how you can accomplish this task.
Sharing code between different platforms
Code sharing is one of the key concepts when developing applications with Xamarin. In this section, we will learn how to use the available options of sharing code between platforms when creating a Xamarin solution, Shared Project and Portable Class Library, and what the key differences are.
How to do it…
We will start with creating a solution based in Shared Project. 1. Start Visual Studio. In the top menu, select File | New | Project… and in the Templates | Mobile Apps tab choose Blank App (Xamarin.Forms Shared). Name it XamFormsSharedProject, choose your directory path, and click OK. 2. With that, you get four projects: the XamoFormsSharedProject core shared project and the corresponding Android, iOS, and Windows Phone platform projects. 3. Create a new class in the core XamFormsSharedProject project, right-click, Add | Class…, and give it the name NameService.cs. Make it public. An advantage of Shared Project is that the code is actually compiled with the platform project that is referenced and you can use conditional directives to use platform APIs from the core project. [ 390 ]
Chapter 4
4. In the NameService class, above the namespace add the following code to import using statements per platform: #if WINDOWS_PHONE using Windows.Storage; #elif __IOS__ using Foundation; #elif __ANDROID__ using Android.Content.Res; using Android.Content; using Xamarin.Forms; #endif
5. Copy the following code of the NameService class. What we are essentially doing here is using each platform API to read an asset .txt file and adding the content to the greeting return value. public class NameService { public async Task GetGreeting (string firstName, string lastName) { string fullName = string.Format("{0} {1}", firstName, lastName); string content = string.Empty; #if WINDOWS_PHONE string platformAssetFilePath = @"Assets\PlatformAsset.txt"; StorageFolder InstallationFolder = Windows.ApplicationModel.Package.Current. InstalledLocation; StorageFile file = await InstallationFolder.GetFileAsync(platformAssetFilePath); using (StreamReader stream = new StreamReader(await file.OpenStreamForReadAsync())) { content = await stream.ReadToEndAsync(); }
[ 391 ]
Different Cars, Same Engine #elif __ANDROID__ AssetManager assets = Forms.Context.Resources.Assets; using (StreamReader stream = new StreamReader (assets.Open ("PlatformAsset.txt"))) { content = await stream.ReadToEndAsync(); } #elif __IOS__ string path = NSBundle.MainBundle.PathForResource ("PlatformAsset", "txt"); using (StreamReader stream = new StreamReader (path)) { content = await stream.ReadToEndAsync(); } #endif fullName = string.Format("Hello {0} from {1}", fullName, content); return fullName; } }
6. Open the App.cs file and change the constructor code with the following. Change first name and last name GetGreeting method parameters to your name. public App () { // The root page of your application Label label = new Label { XAlign = TextAlignment.Center }; NameService nameService = new NameService(); nameService.GetGreeting("Georgios", "Taskos") .ContinueWith((antecedent) => { label.Text = antecedent.Result; }, TaskScheduler.FromCurrentSynchronizationContext());
[ 392 ]
Chapter 4 MainPage = new ContentPage { Content = new StackLayout { VerticalOptions = LayoutOptions.Center, Children = { label } } }; }
7. We need to add the txt file in each platform. Start with the Android platform, right-click, Add | New Item…, and choose text file. Name it PlatformAsset.txt and click Add. 8. In the newly created text file, add the content Android! 9. Deploy the Android application in your favorite emulator; you should get the following result with your name:
[ 393 ]
Different Cars, Same Engine
10. In the XamFormsSharedProject.iOS, in the Resources folder, right-click, Add | New Item…, and choose text File. Name it PlatformAsset.txt and as content put iOS! 11. That's all you need. Run iOS in the simulator to get the result like in the following screenshot:
12. Select XamFormsSharedProject.WinPhone, the Asset folder, rightclick, Add | New Item…, and choose text File. The name is the same, PlatformAsset.txt, and for content add Windows Phone! Press F4 with the newly created file selected and in the Properties window set Build Action to Content. Build Action indicates what Visual Studio does with a file when a build is executed; it can have several values. Setting Build Action to Content means that the text file will be copied to the target machine; we actually deploy the file with our application. 13. Run the application in the Windows Phone emulator.
[ 394 ]
Chapter 4
The second approach is using a PCL as our core library to host the shared code. 1. Back to Visual Studio and File | New | Project…, but this time from Templates | Mobile Apps choose Blank App (Xamarin.Forms Portable), name it XamFormsPortable, and click OK. 2. Again, we get a core project, but you might already have noticed the difference visually from our previous example with a Shared Project; this is an actual assembly, but more on how it works soon. When using PCL as a core code, we can't have every platform API available; it is limited in the PCL profile we choose, meaning that we will need to create abstractions and inject platform implementation. We will introduce DependencyService here, but for now just know that it is used to assign an implementation file for an Interface type; more on the subject in the next recipe, Using the dependency locator.
[ 395 ]
Different Cars, Same Engine
3. On the PCL, right-click, Add | New Item…, and choose Interface this time. Give it the name INameService and click Add. For this interface, we will have only one method signature, similar to our Shared Project example. public interface INameService { Task GetGreeting(string firstName, string lastName); }
4. Open the App.cs file and replace the constructor code with the following: public App() { // The root page of your application INameService nameService = DependencyService.Get(); Label label = new Label { XAlign = TextAlignment.Center }; nameService.GetGreeting("Georgios", "Taskos").ContinueWith((antecedent) => { label.Text = antecedent.Result; }, TaskScheduler.FromCurrentSynchronizationContext()); MainPage = new ContentPage { Content = new StackLayout { VerticalOptions = LayoutOptions.Center, Children = { label } } }; }
5. Same as before, we will add the PlatformAsset.txt file for each platform. Repeat steps 7, 11, and 13 from the Shared Project section earlier to create the asset file.
[ 396 ]
Chapter 4
6. Now we can create the implementation classes that implement the INameService interface and instruct DependencyService to inject the implementation for this interface in runtime. Start with the Android platform: right-click and choose Add | Class…, name it NameService, and insert the following code: public class NameService : INameService { public async Task GetGreeting(string firstName, string lastName) { string fullName = string.Format("{0} {1}", firstName, lastName); string content = string.Empty; AssetManager assets = Forms.Context.Resources.Assets; using (StreamReader stream = new StreamReader(assets.Open("PlatformAsset.txt"))) { content = await stream.ReadToEndAsync(); fullName = string.Format("Hello {0} from {1}", fullName, content); return fullName; } } }
7. We need to add the following attribute above the XamFormsPortable.Droid namespace to instruct DependencyService about our implementation class: [assembly: Xamarin.Forms.Dependency (typeof(XamFormsPortable.Droid.NameService))]
8. Repeat step 6 for the XamFormsPortable.iOS project. See the following equivalent implementation and Dependency attribute needed: [assembly: Xamarin.Forms.Dependency (typeof(XamFormsPortable.iOS.NameService))] namespace XamFormsPortable.iOS { public class NameService : INameService { public async Task GetGreeting(string firstName, string lastName) { string fullName = string.Format("{0} {1}",firstName, lastName); string content = string.Empty;
[ 397 ]
Different Cars, Same Engine string path = NSBundle.MainBundle.PathForResource ("PlatformAsset", "txt"); using (StreamReader stream = new StreamReader(path)) { content = await stream.ReadToEndAsync(); } fullName = string.Format("Hello {0} from {1}", fullName, content); return fullName; } } }
9. And for Windows Phone: [assembly: Xamarin.Forms.Dependency (typeof(XamFormsPortable.WinPhone.NameService))] namespace XamFormsPortable.WinPhone { public class NameService : INameService { public async Task GetGreeting(string firstName, string lastName) { string fullName = string.Format("{0} {1}", firstName, lastName); string content = string.Empty; string platformAssetFilePath = @"Assets\PlatformAsset.txt"; StorageFolder InstallationFolder = Windows.ApplicationModel.Package. Current.InstalledLocation; StorageFile file = await InstallationFolder .GetFileAsync(platformAssetFilePath); using (StreamReader stream = new StreamReader(await file.OpenStreamForReadAsync())) { content = await stream.ReadToEndAsync(); } fullName = string.Format("Hello {0} from {1}", fullName, content); return fullName; } } }
[ 398 ]
Chapter 4
10. Running the projects should give you the exact same result with our previous Shared Project screenshots.
How it works…
We saw how to use two main cross-platform options of sharing code: Shared Project and PCL. There is another option called file-linking, but this is omitted since using it makes testing painful and refactoring a nightmare, I find file-linking useful when the same content file might be shared between all platforms. Shared Project is a project that shares the files with the target project and is actually compiled in the binary. It requires Visual Studio 2013 update 2. It copies the source files and assets in the target project. It is defined by a new project type, .shproj. You can define the build type of the included files but no assembly DLL file is generated. In our example, we used the conditional compilation strategy, which is an easy way to manage parts of the code that utilizes platform-specific APIs, and we want to make it available only when compiling to a specific platform. There are five main symbol definitions you might want to use: • • • • •
__MOBILE__ __IOS__ __ANDROID__ WINDOWS_PHONE SILVERLIGHT
For the Android platform, you can also define the API level. For example, __ ANDROID_11__ defines code that should only be included if Android 3.0 Honeycomb or the newer API level is supported. Windows Phone 8.1 also defines WINDOWS_PHONE_APP, and for Universal Windows Apps, WINDOWS_APP. Other than conditional directives, you can use class mirroring, where you use a class in your Shared Project but define the implementation in the platform-specific projects and also use partial classes and methods. These are all valid ways to go, but it can easily become ugly and a maintainability nightmare. PCL comes to the rescue.
[ 399 ]
Different Cars, Same Engine
PCL are assemblies that are produced, and you can reference them to target projects supporting PCL libraries or distribute to other developers as well. They are tied to a set of classes depending on the target frameworks you want to support. For more information, visit the following link: https://msdn.microsoft.com/en-us/ library/gg597391(v=vs.110).aspx. Using a PCL as a core project means that any platform-specific API is missing. To make this functionality available, our code must use a loosely coupling architectural pattern defining interfaces in the core project and injecting implementation classes conforming to the core interface from our platform projects using the Inversion of Control principle. This means that an object should not know how to construct its dependencies. In our example, we created an interface with a method signature that accepts two string arguments and returns a string. Using the DependencyService service locator, we retrieved our implementation class registered in our platform-specific projects. What kind of code can you share? Any code that you write targeting the Base Class Library .NET Framework, like POCO classes, Model classes, repositories that connect to a web service or access a local database, like SQLite, and whatever calculation, processing, and business logic are candidates for code sharing. Any other API might be available depending on the PCL profile you choose.
See also
• Using the dependency locator recipe from this chapter • Adding a third-party Dependency Injection Container recipe from this chapter
Using the dependency locator
When we design a loosely coupled architecture, which nowadays is a standard practice to separate concerns between our layers, we program against interfaces and not implementations. The problem we're solving with abstractions is tight coupling, which is when implementation layers are reference one each other directly; for example, the view has a reference of the presentation layer, which in turn has a repository reference that has a service reference class, meaning that the view is coupled to the service layer at the bottom.
[ 400 ]
Chapter 4
To achieve loose coupling, we somehow need to tell the project in runtime that for the interface we are programming against to create this implementation class. There are three ways to achieve this: using a DI container, Dependency Injection, or with a Service Locator, where you can map an implementation to an interface. Xamarin.Forms offers an out-of-the-box dependency resolver, DependencyService, that you can use to register dependencies and in runtime ask for the interface mapped to an implementation class.
How to do it…
1. In Visual Studio, create a new Blank App (Xamarin Forms Portable) solution, File | New Project…, and name it XamFormsDependencyService. 2. Right-click the XamFormsDependencyService (Portable) core project, Add | New Item…, choose Interface from the templates, give it the name IPlatform, and make it public. 3. Add a method signature: it will be very simple, accepting no arguments, and returning a string value with the description of the platform we're running on. public interface IPlatform { string GetPlatformDescription(); }
4. Programming against the interface introduces the need to resolve a dependency, in our case the IPlatform interface, in runtime with an implementation. Open the App.cs file and refactor the constructor like the following: public App() { // The root page of your application IPlatform platform = DependencyService.Get(); Label label = new Label { XAlign = TextAlignment.Center, Text = platform.GetPlatformDescription() }; MainPage = new ContentPage { Content = new StackLayout { [ 401 ]
Different Cars, Same Engine VerticalOptions = LayoutOptions.Center, Children = { label } } }; }
5. Create the Android implementation class. Right-click the Android project, Add | Class…, name it PlatformAndroid and click Add, and conform to the IPlatform interface. public class PlatformAndroid : IPlatform { public string GetPlatformDescription() { return "Android"; } }
6. For every native platform implementation, we have to register the class with DependencyService. Above the namespace declaration, add the Xamarin. Forms.Dependency attribute. [assembly: Xamarin.Forms.Dependency(typeof(XamFormsDependencyService .Droid.PlatformAndroid))]
7. Repeat steps 5 and 6 for the iOS and Windows Phone platforms. The following is the iOS code: [assembly: Xamarin.Forms.Dependency (typeof(XamFormsDependencyService.iOS.PlatformiOS))] public class PlatformiOS : IPlatform { public string GetPlatformDescription() { return "iOS"; } }
For Windows Phone: [assembly: Xamarin.Forms.Dependency (typeof(XamFormsDependencyService.WinPhone .PlatformWinPhone))] public class PlatformWinPhone : IPlatform { public string GetPlatformDescription() [ 402 ]
Chapter 4 { return "Windows Phone"; } }
The result is the description of each platform in the middle of the screen. Android:
[ 403 ]
Different Cars, Same Engine
iOS:
[ 404 ]
Chapter 4
Windows Phone:
How it works…
DependencyService resolves interfaces to platform-specific implementations.
Under the hood is a service locator, which uses a container that maps abstractions (interfaces) to concrete registered types, then the client uses the locator to acquire these dependencies. In this recipe, we created a simple interface (IPlatform) that returns a string, in our case the platform description. For each platform we created, a class implements the IPlatform interface and returns the name of the platform. The key is to register the corresponding implementation to DependencyService. For this, Xamarin has provided us the dependency attribute that we use to decorate our class above the namespace and then from our core project we resolve the dependency implementation using the method DependencyService.Get, which returns the corresponding implementation for the interface. [ 405 ]
Different Cars, Same Engine
Note that the interface must be implemented in all platforms and the class must have a parameterless constructor or DependencyService will not be able to create an instance for you.
See also
• https://developer.xamarin.com/guides/cross-platform/xamarinforms/dependency-service/
Adding a third-party Dependency Injection Container
Dependency Injection is one of the solutions when it comes to resolving dependencies in runtime. Let's look at the Wikipedia description: Dependency Injection is a software design pattern that implements Inversion of Control (IoC) for resolving dependencies.
Working with a Dependency Injection Container, you will see that it isn't only an IoC container but a set of software design patterns and principles that solves the problem of tight coupling. Loosely coupled code enables us to write extensible code; testing becomes much easier with mocking the Subject Under Testing component dependencies; dynamic construction in runtime with injected dependencies; and of course maintainability, code that is easy to navigate in different modules, fix bugs, and add new features. There are different patterns to inject dependencies to modules: • Constructor Injection • Property Injection • Method Injection • Service Locator • Others Which one you choose depends on your architecture and the team's approach to solving the problem. [ 406 ]
Chapter 4
In this recipe, we will use the Ninject DI Container, Constructor Injection, and the XLabs.IoC Service Locator. We will program against two interfaces: IDataService and ISettingsRepository, IDataService will depend on ISettingsRepository to retrieve the username key value from a dictionary appending the platform-specific description we are running.
How to do it…
1. Start with creating a new Blank App (Xamarin.Forms Portable) solution in Visual Studio, set the name to XamFormsDependencyInjection, and click OK. 2. Right-click the Portable Class Library, XamFormsDependencyInjection project, and choose Manage NuGet Packages. 3. Search and install the following packages: °° Portable.Ninject °° XLabs.IoC.Ninject (will install the XLabs.IoC NuGet package dependency) 4. Right-click and choose Add | New Item…, choose Forms Xaml Page, give it the name MainPage.xaml, and click Add. 5. Replace the default label view with the following code:
6. Open App.cs file and in the constructor, change the MainPage property assignment to the following: MainPage = new MainPage();
7. Time to create the interfaces. Right-click the PCL project and Add | New Item…, choose Interface, provide the name IDataService, and click Add. Make the interface public. 8. The interface will have an ISettingsRepository property signature that we will create in the next step. ISettingsRepository SettingsRepository { get; set; }
9. Repeat step 7 to create the ISettingsRepository interface and add the method signatures like the following: void Save(string key, string value); string GetValue(string key);
[ 407 ]
Different Cars, Same Engine
10. Right-click, Add | Class…, name it DataService, conform to the IDataService interface, and add a default constructor accepting an ISettingsRepository parameter. public class DataService : IDataService { public ISettingsRepository SettingsRepository { get; set; } public DataService(ISettingsRepository settingsRepository) { SettingsRepository = settingsRepository; } }
11. Go to the MainPage.xaml.cs behind code and override the OnAppearing method. 12. In the OnAppearing method, we will use Xlabs.Ioc.Resolver to resolve our IDataService dependency and invoke the ISettingsRepository Save method to store a key/value in memory and GetValue to retrieve the key's value assigning it to the MainPage.Label.Text property. protected override void OnAppearing() { base.OnAppearing(); IDataService dataService = Resolver.Resolve(); dataService.SettingsRepository.Save ("Username", "George"); label.Text = dataService.SettingsRepository.GetValue("Username"); }
That's all we need to be able to code against our interfaces in our shared code. Let's configure the platform-specific implementations and the Ninject DI Container starting with the Android platform. 1. Right-click the XamFormsDependencyInjection.Droid project and create a new class, Add | Class…, name it SettingsDroidRepository.cs, and click Add. 2. The new class will implement ISettingsRepository. The implementation classes will be the same for all platforms with the difference of hardcoding the platform description, and the naming (not mandatory). public class SettingsDroidRepository : ISettingsRepository { Dictionary _containerDictionary = new Dictionary(); [ 408 ]
Chapter 4 public void Save(string key, string value) { if (!_containerDictionary.ContainsKey(key)) { _containerDictionary.Add(key, value); } } public string GetValue(string key) { string value = string.Empty; if (_containerDictionary.TryGetValue(key, out value)) { value = string.Format("{0} - Droid", value); } return value; } }
Help from the XLabs.IoC container is needed. It is another service locator that will encapsulate our Ninject DI dependency resolver and enable us to access the Ninject IoC from our PCL library. This might not be the best practice; for example, when you introduce the MVVM architectural pattern in your applications, you want your ViewModels to be injected in your views automatically and utilize data-binding instead of using service location. To learn how to do this, refer to this chapter recipe, Architecture design with Model-View-ViewModel (MVVM) pattern and Chapter 7, Bind to the Data.
3. Set up the Resolver, the NinjectContainer, and register our dependencies. Go to MainActivity.cs and create the following SetupDependencyContainer method: private void SetupDependencyContainer() { StandardKernel standardKernel = new StandardKernel(); NinjectContainer resolverContainer = new NinjectContainer(standardKernel); standardKernel.Bind(). To().InSingletonScope(); standardKernel.Bind(). To().InSingletonScope(); Resolver.SetResolver(resolverContainer.GetResolver()); } [ 409 ]
Different Cars, Same Engine
4. Call the method after Forms.Init in the OnCreate method of the MainActivity class. global::Xamarin.Forms.Forms.Init(this, bundle); SetupDependencyContainer(); LoadApplication(new App());
For the platforms iOS and Windows Phone, the code is repetitive. In step 1, name the repository implementations; in this module's source code, we created SettingsTouchRepository and SettingsPhoneRepository respectively. In step 2, change the hardcoded description from Droid to iOS and WinPhone. In step 3, set up the dependencies in AppDelegate.cs and MainPage.xaml.cs. For details, please refer to the sample code of this module. Run the application for each platform and you should get results similar to the following screenshots: Android:
[ 410 ]
Chapter 4
iOS:
[ 411 ]
Different Cars, Same Engine
Windows Phone:
How it works…
A Dependency Injection with an IoC is very similar to a service locator, but using a DI we don't need the client to constantly ask for the dependencies; the container creates all the objects needed and injects them to the component that requires them through the constructor. This happens automatically in runtime when a component is constructed via the container.
[ 412 ]
Chapter 4
As we saw in the recipe, we have a DataService class that implements the IDataService interface and has a dependency of an ISettingsRepository object. Setting up the DI container means registering your dependencies. We mapped for each platform an interface to a concrete implementation class that we want the container to instantiate every time it is needed. Using Ninject, we bind the IDataService interface to the platform-specific implementation class; we do the same for the ISettingsRepository dependency in our DataService class. Both are defined as Singleton, a pattern that will create the object once and reuse it through the application lifecycle. That way, the container knows every time that an IDataService or ISettingsService is needed and it will construct it, injecting or returning it to the client. To separate the concerns and since we need to use Ninject to resolve dependencies, we will have to introduce another Service Locator; this is resolved from Xamarin with the XLabs.IoC.Ninject Nuget package. Using this plugin, we can set the resolver to our Ninject container and request a dependency from our portable class library. Remember, the Ninject DI container has to be used from the platform-specific projects to register the implementations. With this in place and setting the resolver to the NinjectContainer, we can request IDataService. When Ninject tries to create a DataService class, since this is what we registered for this interface in our setup, it finds a constructor accepting an ISettingsRepository argument, checks its internal dictionary if such an implementation class is registered, and in turn creates one and passes it to the constructor. Magic!
See also
• http://www.ninject.org/ • https://github.com/XLabs/Xamarin-Forms-Labs • https://github.com/XLabs/Xamarin-Forms-Labs/wiki/IOC • Architecture design with Model-View-ViewModel (MVVM) pattern recipe in this chapter
Architecture design with Model-ViewViewModel (MVVM) pattern
The key aspect of using Xamarin is sharing code, classic approach or Xamarin.Forms you'll have to put all the business logic, navigation, dialogs, and data representation in a place that you can reuse and share. This problem is solved using the MVVM pattern and Xamarin.Forms is a perfect fit for it. [ 413 ]
Different Cars, Same Engine
The MVVM architectural pattern is a variation of the Model-View-Controller (MVC) pattern and both are presentation patterns. Although MVVM can be achieved without data binding, the real power is when the platform supports it; the code is much simplified when we are using MVVM and data binding, hence it is natural to use MVVM with XAML-based applications like Xamarin.Forms, Windows Store, Windows Phone, and WPF. The goal using MVVM is a clean separation of the different modules in our code. A major achievement is the abstraction between designer and developer. Ensure that any code that manipulates presentation only manipulates presentation, pushing all domain and data source logic into clearly separated areas of the program. - Martin Fowler This recipe demonstrates how to create a solution using the MVVM pattern and data binding. For more details on data binding, go to Chapter 7, Bind to the Data.
How to do it…
1. Start Visual Studio and create a new Blank App (Xamarin.Forms Portable) project. Provide the name XamFormsMVVM. 2. Every project in the solution has NuGet dependencies, the XLabs.Forms package and its dependencies, right-click the solution, choose Manage NuGet Packages…, and search for the package and install it. 3. In the XamFormsMVVM portable class library, create four folders: Data, Models, ViewModels, and Views. To create a folder, right-click the project and select Add | New Folder. 4. In the Models folder, right-click, Add | Class…, and name it Contact.cs. This will be the model, theoretically constructed with values from a database or a web service. It is a simple POCO class with three properties. public class Contact { public string FirstName { get; set; } public string LastName { get; set; } public string Profession { get; set; } }
[ 414 ]
Chapter 4
5. In the Data folder, right-click, Add | Class…, and give it the name DataService.cs. This is a static class to mock some data; in our case, we will return two instances of contact type. public static class DataService { public static IEnumerable GetAll() { return new List { new Contact() { FirstName = "Chris", LastName = "Smith", Profession = "Computer Scientist" }, new Contact() { FirstName = "Georgios", LastName = "Taskos", Profession = "Software Engineer" } }; } }
6. Right-click the ViewModels folder and Add | Class…; name it ContactViewModel. Repeat the same for ContactListViewModel and ContactDetailsViewModel. All ViewModels will inherit from XLabs. Forms.Mvvm.ViewModel. public class ContactViewModel : XLabs.Forms.Mvvm.ViewModel { private readonly Contact _contact; public string Profession { get { return _contact.Profession; } set { if (_contact.Profession != value) {
[ 415 ]
Different Cars, Same Engine _contact.Profession = value; NotifyPropertyChanged(); } } } public string FullName { get { return string.Format("{0} {1}", _contact.FirstName, _contact.LastName); } } public ContactViewModel(Contact contact) { _contact = contact; } } public class ContactListViewModel : XLabs.Forms.Mvvm.ViewModel { private IList _contacts; public IList Contacts { get { return _contacts; } set { _contacts = value; NotifyPropertyChanged(); } } public ICommand ItemSelectedCommand { get; set; } private void OnItemSelected(ContactViewModel contactVM) { Navigation.PushAsync ((viewmodel, page) => { viewmodel.Contact = contactVM;
[ 416 ]
Chapter 4 }); } public ContactListViewModel() { ItemSelectedCommand = new Command(OnItemSelected); Contacts = new ObservableCollection( DataService.GetAll().Select(p => new ContactViewModel(p))); } } public class ContactDetailsViewModel : XLabs.Forms.Mvvm.ViewModel { private ContactViewModel _contact; public ContactViewModel Contact { get { return _contact; } set { _contact = value; NotifyPropertyChanged(); } } }
7. The last piece of our MVVM parts is the views. In the Views folder, right-click and Add | New Item…, choose Forms Xaml Page, and create ContactDetailsPage. Repeat this for ContactListPage. For the contents, copy the following XAML code in each page:
8. Open the ContactListPage.xaml.cs behind-code file and add the following code. Why we need this is explained in the How it works… section. public const string ItemSelectedCommandPropertyName = "ItemSelectedCommand"; public static BindableProperty ItemSelectedCommandProperty = BindableProperty.Create( propertyName: "ItemSelectedCommand", returnType: typeof(ICommand), declaringType: typeof(ContactListPage), defaultValue: null); public ICommand ItemSelectedCommand { get { return (ICommand)GetValue(ItemSelectedCommandProperty); } set { SetValue(ItemSelectedCommandProperty, value); } } protected override void OnBindingContextChanged() { base.OnBindingContextChanged(); RemoveBinding(ItemSelectedCommandProperty); SetBinding(ItemSelectedCommandProperty, new Binding(ItemSelectedCommandPropertyName)); [ 418 ]
Chapter 4 } private void HandleItemSelected(object sender, SelectedItemChangedEventArgs e) { if (e.SelectedItem == null) { return; } var command = ItemSelectedCommand; if (command != null && command.CanExecute(e.SelectedItem)) { command.Execute(e.SelectedItem); } }
An important step is to connect all these pieces together. Model and ViewModel is the easy part. ContactViewModel depends on a Contact, so we make sure whenever you create a ContactViewModel instance, you pass a Contact instance to the default constructor. To connect ViewModel to the corresponding View needs a little bit of architecture. There are some ways to achieve this, and none is wrong, but we should always go after the cleanest way possible. You can use a Service Locator to register your ViewModels and create a static ViewModelLocator class to access them, or just create a ViewModel in the View behind code and assign it to the BindingContext. Wouldn't it be great to just register your ViewModel to the View and magically have it assigned? Fear not! XLabs to the rescue! How it works…soon!
9. Go to App.cs, change the constructor, and add a RegisterViews method like the following. This will provide us with the desired witchcraft. public App() { // The root page of your application RegisterViews(); MainPage = new NavigationPage((ContentPage)ViewFactory. CreatePage()); } private void RegisterViews() { [ 419 ]
Different Cars, Same Engine ViewFactory.Register(); ViewFactory.Register(); }
10. For the platform-specific projects, the code is the same. We just need to register the IoC container needed by XLabs.Forms.Mvvm. Go to the Android project and open the MainActivity.cs. In the OnCreate method and after the Forms.Init method call, insert the following code: if (!Resolver.IsSet) { Resolver.SetResolver (new SimpleContainer().GetResolver()); }
11. Go to the XamFormsMVVM.iOS project, open AppDelegate.cs, and in the FinishedLaunching method, after the Forms.Init call add the same line of code in step 10. 12. In XamFormsMVVM.WinPhone, open the MainPage.xaml.cs file and in the constructor after the Forms.Init call, add the line of code in step 10. 13. Run the application for all platforms and admire your MVVM architecture live! Android:
[ 420 ]
Chapter 4
iOS:
[ 421 ]
Different Cars, Same Engine
Windows Phone:
Tapping a row will take you to the ContactDetailsPage showing the FullName property value.
[ 422 ]
Chapter 4
How it works…
Implementing the MVVM pattern in Xamarin.Forms, we achieved a clean crossplatform mobile solution. The concept is to abstract as much as we can from the platform specifics, program against interfaces, use Dependency Injection, separation of concerns and single responsibility of components. We started creating a simple class that represents our Model, Contact, that is a POCO class with the data we would normally retrieve from a local database or from a web service. The data is retrieved with the help of the DataService static class; in this recipe, we just hardcoded two contacts for demonstration purposes. For the Views, the requirements are two pages: one to list all the contacts and one to show the selected contact details when we tap a row. The first and main View is the ContactListPage, and for the details we added the ContactDetailsPage. [ 423 ]
Different Cars, Same Engine
The last part of the MVVM pattern is ViewModels. Every View is backed by a ViewModel; it's the View's Model, classes that are responsible to present data and execute actions. For this solution, we need three ViewModels, so let's take a small tour of the implementation. One ViewModel is ContactListViewModel and its responsibility is to show the list of contacts, has one property of IList, in reality it's an observable collection. Xamarin.Forms understands this in runtime. Each row is a ContactViewModel, a wrapper around our Contact model to present data to the View. As you see here, we have a composition of ViewModels, which is totally valid. To add some behavior, we added an ICommand property that will invoke its assigned delegate method when we select a list item, in our case ContactViewModel. In the constructor of ContactListViewModel, we use DataService to retrieve all contacts; we also initialize ICommand with the method OnItemSelected that navigates to ContactDetailsViewModel. We set the selected ContactViewModel to the public contact property when the Action parameter is invoked. Look at that! More helper goodies from Xamarin.Forms Labs, and navigation is already implemented for each platform; we can use it because it is part of the XLabs.Forms.Mvvm.ViewModel class. Note that this property will be null if NavigationPage is not used. But why did we use that code behind in ContactListPage.xaml.cs? In ContactListViewModel, we have a property that is a list of ContactViewModel, so every list row is binded to a ContactViewModel instance, which means that a row tap command will need to be handled in that ViewModel. Unfortunately, at the moment of this writing there is no out-of-the-box command that will delegate the tap row to the page's binded ViewModel, and for that we need a workaround code that handles the delegation of the event to a command in ContactListViewModel. This is achieved via the ItemSelected event handler of ListView and a custom BindableProperty. Refer to the module's code for a closer look at the code to reuse it if needed in your solutions. The MVVM implementation in this recipe is one of the ways to do it, MVVM is a family of patterns; for example, instead of copying the properties of the Contact Model in ContactViewModel, you could inherit the Model from the ObservableObject class and expose it directly to the View. Although I've worked fine in previous projects before with this practice and it's completely valid, it breaks the encapsulation and makes available the Model to the View. In the MVVM pattern, the concept of keeping a clean separation of concerns is very important and to maintain this we should let ViewModel coordinate everything between the View and the Model.
[ 424 ]
Chapter 4
To make data binding work correctly, we need to implement the INotifyPropertyChanged interface; this interface contains an event, PropertyChanged. When raised, it notifies the View's bindable properties to update with the new value. Since this interface is already implemented in the XLabs.Forms. Mvvm.ViewModel class, actually in the XLabs.Data.ObservableObject, we don't need to do anything else than inherit from this class. Boring boilerplate code work is ready for us! Sweet! The last step in our core cross-platform library is to register our ViewModels to the Views; the method RegisterViews handle this. The XLabs ViewFactory class is used to register which ViewModel we want as a BindingContext in our View, and we also use it to construct our main page in the App constructor. The platform-specific projects are really simple: we just have to register XLabs.IoC. Resolver; this simple Dependency Injection Container is used by XLabs internally to offer cool things like navigation, injecting our ViewModels, and other abstractions.
There's more…
Nothing feels better than a nice and clean architecture, right? We accomplished a great code foundation in our application implementing the MVVM pattern, binding our ViewModels to pages with no major additional work, and data binding to update our UI on a property value change. One thing can get better though: we use a static DataService class to retrieve data. In the real world, we would abstract the data access components and program against an interface and inject the actual implementation using Dependency Injection. For more information how to do this, refer to Adding a third-party Dependency Injection Container recipe of this chapter and Chapter 5, Dude, Where's My Data? Data binding is key in MVVM XAML applications. To learn more about it, go to Chapter 7, Bind to the Data.
See also
• https://github.com/XLabs/Xamarin-Forms-Labs • https://developer.xamarin.com/guides/cross-platform/xamarinforms/user-interface/xaml-basics/data_bindings_to_mvvm/
[ 425 ]
Different Cars, Same Engine
Using the event messenger
When writing C# code, we rely a lot on instance events. A class might have some events that another class instance can register a handler and get notified when something happened; for example, classes that implement the INotifyPropertyChanged interface raise the PropertyChanged event when a property value is changed, or maybe we start a lengthy asynchronous task in another thread from a component we reference and we want to get notified about the progress. It's a great feature, but to make use of events the component that hooks to an instance event of another component must depend on it. Imagine if we have many cases of this communication: we will end up with spaghetti code and dependency references from component to component. A common requirement is the communication of unrelated components, a ViewModel to a ViewModel or a service to a ViewModel is common use cases. An event messenger is solving this problem to send loosely coupled messages between components that are not aware of each other with the Publish/Subscribe pattern and a messaging key contract. A good example would be when a configuration setting has changed and some components need to act on it, maybe refresh data, or have a central component that will take action for all the handled exceptions caught in the application. In this recipe, we will do exactly that.
How to do it…
1. Open Visual Studio and create a new cross-platform solution from the top menu File | New | Project, find the Blank App (Xamarin.Forms Portable), name it XamFormsMessagingCenter, and click OK. 2. Right-click the XamFormsMessagingCenter PCL, Add | New Item…, choose the Forms Xaml Page template, set the name to MainPage, and click Add. 3. Change the label's Text property of the newly created page to Hello Messenger!
4. Go to the App.cs constructor and set the MainPage property to a new instance of our newly created MainPage page. public App() { // The root page of your application MainPage = new MainPage(); } [ 426 ]
Chapter 4
5. The messaging key for subscribers and publishers is a string parameter. You can of course use hardcoded strings but this practice is error prone and typos could cost you debugging time. Create an enum to use as a key contract. public enum MessagingKey { HandledException = 0 }
6. Open the MainPage.xaml.cs behind-code file and override the OnAppearing method. In this method, we will intentionally throw a NotSupportedException, catch it, and publish a message to all the subscribers of the MessagingKey.HandledException.ToString() key using the MessegingCenter.Send method. protected override void OnAppearing() { base.OnAppearing(); try { throw new NotSupportedException("Need to fix this!"); } catch(NotSupportedException ex) { MessagingCenter.Send(this, MessagingKey.HandledException.ToString(), ex); } }
7. Right-click the core portable library project and create a class with Add | Class…, rename to CrashManager, and click Add. The class has a default constructor, subscribing to an event, and a Dispose method to unsubscribe from the event. It's always good to help the system with cleaning resources. public class CrashManager { public void Dispose() { MessagingCenter.Unsubscribe(this, MessagingKey.HandledException.ToString()); } public CrashManager () { MessagingCenter.Subscribe(this, MessagingKey.HandledException.ToString(), (obj, ex) => { [ 427 ]
Different Cars, Same Engine Debug.WriteLine("Exception: {0}", ex); }); }
8. Go to the App.cs file and in the constructor before creating the MainPage, add the following code that creates a new instance of CrashManager: new CrashManager();
9. Run the project to any platform you want; the behavior is exactly the same. When the screen appears, you will see in the Output window a message like the following: [0:] Exception: System.NotSupportedException: Need to fix this! at XamFormsMessagingCenter.MainPage.OnAppearing ()………..
How it works…
Basically, the MessagingCenter is the mediator between our components. These classes don't know anything about each other except for the messenger and a signature key, the message that they are subscribing or sending. The MessagingCenter is simple: the Send method takes a mandatory generic parameter, the type of the sender, and a second optional generic parameter, the type of the argument. Then we pass two or three parameters. The first is the sender instance, the second is the messaging key signature, and if we have arguments we pass them in the third parameter. The Subscribe method works almost the same, with a difference in the third parameter that is an Action delegate; we can access the sender instance and any arguments if applicable, meaning if you defined a second generic parameter.
See also
• https://developer.xamarin.com/guides/cross-platform/xamarinforms/messaging-center/
Adding localization
Creating a mobile application is an opportunity to reach out to the whole world; no barriers! This means that you will want to talk to your users' language, so you need a way to globalize your application.
[ 428 ]
Chapter 4
Xamarin.Forms uses a built-in mechanism for localizing .NET applications. You can use RESX files, add your strings for each language code, and depending on the system language settings your application will load the appropriate value. This is feasible in code and from XAML pages. In this recipe, we will explore the two options and present the string value Hello World! in English, and in French Bonjour le monde!.
How to do it…
1. Start by opening Visual Studio and creating a cross-platform solution, File | New | Project…, choose Blank App (Xamarin.Forms Portable) from the templates, name it XamFormsLocalization, and click OK. 2. Create a default RESX file and a French RESX file in the portable library, right-click and Add | New Item…, from the templates choose Resources File, name it AppResources.resx , click Add. This is the base resource language strings. Repeat the same step but this time give the file the name AppResources.fr.resx. 3. For the AppResources.resx file, add the Name and Value strings like the following screenshot:
4. And for our French resources, the following are the Name and Value strings:
5. We need a XAML page for our application MainPage. Right-click, Add | New Item…, choose Forms Xaml Page from the templates, rename to MainPage.xaml, and click Add.
[ 429 ]
Different Cars, Same Engine
6. We added the resource files with the strings, but we somehow need to know the system language that the user selected; this needs some abstractions and the use of DependencyService to get this information in the platform-specific application layer. In our XamFormsLocalization portable library, right-click, Add | New Item…, choose Interface, rename to ILocalize.cs, and add a method signature. You can find the details in the next code snippet: public interface ILocalize { CultureInfo GetCurrentCultureInfo(); }
7. Add an interface implementation for each platform, right-click on the platform projects and Add | Class…, name it Localize.cs, implement the ILocalize interface, and append the Dependency attribute to register the implementation with the interface. // Android [assembly: Dependency(typeof(XamFormsLocalization.Droid.Localize))] namespace XamFormsLocalization.Droid { public class Localize : ILocalize { public CultureInfo GetCurrentCultureInfo() { Locale androidLocale = Locale.Default; string netLanguage = androidLocale.ToString().Replace("_", "-"); Debug.WriteLine("NetLanguage: {0}", netLanguage); return new CultureInfo(netLanguage); } } } // iOS [assembly: Dependency(typeof(XamFormsLocalization.iOS.Localize))] namespace XamFormsLocalization.iOS { public class Localize : ILocalize { public CultureInfo GetCurrentCultureInfo() { string netLanguage = "en"; string prefLanguageOnly = "en"; if (NSLocale.PreferredLanguages.Length > 0) { [ 430 ]
Chapter 4 var pref = NSLocale.PreferredLanguages[0]; prefLanguageOnly = pref.Substring(0, 2); netLanguage = pref.Replace("_", "-"); Debug.WriteLine("Preferred language:" + netLanguage); } CultureInfo ci = null; try { ci = new CultureInfo(netLanguage); } catch { // iOS locale not valid .NET culture (eg. "en-ES" : English in Spain) // fallback to first characters, in this case "en" ci = new CultureInfo(prefLanguageOnly); } return ci; } } } // Windows Phone [assembly: Dependency(typeof(XamFormsLocalization. WinPhone.Localize))] namespace XamFormsLocalization.WinPhone { public class Localize : ILocalize { public CultureInfo GetCurrentCultureInfo() { return Thread.CurrentThread.CurrentUICulture; } } }
8. For the iOS platform, now we need to declare the languages we support in the Info.plist, so add the following key/values:
[ 431 ]
Different Cars, Same Engine
9. In Windows Phone, we have to set the supported languages in the options. Right-click the project and Properties. In the section Application | Supported Cultures, check French (France) along with the English options.
10. Open the XamFormsLocalization portable library App.cs file and replace the constructor default code with the following snippet: public App() { if (Device.OS != TargetPlatform.WinPhone) { AppResources.Culture = DependencyService.Get() .GetCurrentCultureInfo(); } MainPage = new MainPage(); }
You must have noticed that we only retrieve CultureInfo for the platforms iOS and Android, and that is because the Windows Phone resource framework gets the selected language automatically.
11. You're ready to test the localization setup we did in code. Open the MainPage.xaml and set a name for the Label control so that you can access it in the behind code.
[ 432 ]
Chapter 4
12. Go to MainPage.xaml.cs and add the following method: protected override void OnAppearing() { base.OnAppearing(); label.Text = AppResources.HelloWorld; }
13. Run the code and in all the platforms, you should get the same result. The following is a screenshot of the Android application:
14. Comment the code we added in step 10. We will enable our application to use the resource files in XAML.
[ 433 ]
Different Cars, Same Engine
15. To translate the user interface controls directly in XAML, we need to create a class implementing IMarkupExtension, which will expose the RESX resources. In the XamFormsLocalization portable library, right-click, Add | Class…: // You exclude the 'Extension' suffix when using in Xaml markup [ContentProperty("Text")] public class TranslateExtension : IMarkupExtension { readonly CultureInfo cultureInfo; const string ResourceId = "XamFormsLocalization.AppResources"; public TranslateExtension() { cultureInfo = DependencyService.Get(). GetCurrentCultureInfo(); } public string Text { get; set; } public object ProvideValue(IServiceProvider serviceProvider) { if (string.IsNullOrEmpty(Text)) return ""; ResourceManager resmgr = new ResourceManager(ResourceId , typeof(TranslateExtension).GetTypeInfo().Assembly); var translation = resmgr.GetString(Text, cultureInfo); if (translation == null) { throw new ArgumentException( String.Format("Key '{0}' was not found in resources '{1}' for culture '{2}'.", Text, ResourceId, cultureInfo.Name),"Text"); } return translation; } }
[ 434 ]
Chapter 4
16. Go to MainPage.xaml and add the namespace needed to make available TranslateExtension and use the binding syntax to access the resource strings by name. The following is the MainPage.xaml code changed to use the HelloWorld string resource in the Text property:
17. Run the application for each platform and you should get the same results. We changed the language preference to French (France) in the iOS and Windows Phone platforms. iOS:
[ 435 ]
Different Cars, Same Engine
Windows Phone:
How it works…
Fairly easy, you must say! Xamarin.Forms using the .NET mechanism of globalization made it possible for us to create our RESX files in a PCL and share it between platforms. Of course there are couple of extra steps, but it's a small price to pay for major advantages. The solution supports English, the default AppResources.resx file, and French (France), AppResources.fr.resx. All the languages-supporting RESX files follow a naming convention defining the language-specific code after the name and before the extension, AppResources.{language-code}.resx. You can use the two-letter language code but there are languages that you will want to be more specific, like Chinese or Brazilian Portuguese, for example.
[ 436 ]
Chapter 4
In the AppResources static class, there is a property, Culture, where we need to set to the system's language preference. The implementation of retrieving this information is platform specific, so we will follow the service location pattern using the available Xamarin.Forms DependencyService. In our portable class library, we create an ILocalize interface to program with, and for all the platforms' Localize implementations we implement this interface and register to DependencyService. In the Xamarin.Forms application creation, App.cs constructor, we set the culture retrieving the platform implementation ILocalize via DependencyService. We don't have to do this for the Windows Phone platform as the framework sets the culture automatically, but we still need the implementation as we use it for the IMarkupExtension implementation. So far, we've set up everything needed to access the values in code; if you followed the steps, you've already seen it in action. Xamarin.Forms has the power of XAML, and instead of boilerplate behind code or wiring up binding properties in ViewModels, we want to set the value directly in XAML. By implementing the IMarkupExtension interface and using the binding syntax in XAML, Xamarin.Forms can retrieve the value. In the TranslateExtension class, an IMarkupExtension, we retrieve using DependencyService the CultureInfo, decorate the class with the ContentPropertyAttribute and set it to Text. In the ProvideValue implemented method, we retrieve a ResourceManager instance using the ResourceId constant private field; this is important and it has to be in the format {Namespace}. {AppResources} , with the second parameter the assembly that we get with the help of reflection, and with the ResourceManager we retrieve the translation string. We add some troubleshooting help when in debug to throw an exception to make sure the key exists in AppResources. Last step: we declare the namespace to access the TranslateExtension class and request a value for the specified key using the {Binding} syntax. Happy globalizing!
There's more…
It is possible in Xamarin.Forms to localize platform-specific elements like images or the application name. For more information on how to do this, refer to the Xamarin documentation. https://developer.xamarin.com/guides/cross-platform/xamarin-forms/ localization/.
[ 437 ]
Dude, Where's my Data? In this chapter, we will cover the following recipes: • Creating a shared SQLite data access • Performing CRUD operations in SQLite • Consuming REST web services • Leveraging native REST libraries and making efficient network calls
Introduction
There are three types of applications: online, offline, and both, where in some way the device synchronizes its local data with some remote data in a server. In the disconnected mobile world, we have three local storage options to store data in your own sandbox space, the areas where you have the required permission to read and write data: • Preferences (simple key\value pairs user settings) • Direct access to the filesystem (JSON, XML, text, binary) • Local database (SQLite, NoSQL) You can use one, none, or all of these options depending on your needs and your architecture decisions. Usually, my personal preference for local storage is to save user settings in preferences and when I need to query data, manipulate, and save back, the standard is a SQLite database built-in in the iOS and Android platforms and some minimal effort to make it available in Windows Phone.
[ 439 ]
Dude, Where's my Data?
For connected applications, you will have the need of a server, which will query a database and return the data in a formatted representation structure, usually JSON or XML. There are different formats you might work with over the network, but the standard and most famous are JSON and XML querying a web service. For the first half of the chapter, we will learn how to create a shared data access SQLite connection, create tables, query data, insert, update, and delete, known as CRUD operations. We will get our hands on the available HTTP client API and perform CRUD operations. In the end, we will see how, with the help of some open source libraries we can improve the performance, schedule the priority, and handle the concurrency of network requests. Dive in!
Creating a shared SQLite data access
This recipe demonstrates how to set up a cross-platform Xamarin.Forms solution to use SQLite and create the core most important object: a SQLite connection.
How to do it…
1. Create a Blank App (Xamarin.Forms Portable) named XamFormsSQLiteDataAccess in Visual Studio from File | New | Project… 2. For each project, we will add two NuGet packages: SQLite.Net-PCL and SQLite.Net.Async-PCL, a cross-platform SQLite client and ORM. Rightclick in the XamFormsSQLiteDataAccess portable core library and choose Manage NuGet Packages, search for the packages, and add them. 3. Repeat step two for the Android, iOS, and Windows Phone projects. 4. iOS and Android support SQLite out of the box, whereas Windows Phone needs an extra step. Go to the Visual Studio top menu and click Tools | Extensions and Updates and search for sqlite for windows phone. In the results returned, choose the extension SQLite for Windows Phone.
[ 440 ]
Chapter 5
5. In the Windows Phone project, right-click the References folder and choose Add Reference In the templates, click Windows Phone SDK 8.0, then Extensions, and check the SQLite for Windows Phone extension available. Ready to go!
[ 441 ]
Dude, Where's my Data?
6. If you try to build the Windows Phone project, you will receive an error. It has to do with the platform architecture: only x86 architecture is supported by SQLite for the Windows Phone library. Right-click the solution and choose Configuration Manager; change the XamFormsDataAccess. WinPhone platform cell to x86.
7. Right-click the XamFormsSQLiteDataAccess library and Add | New Item…, choose Interface, and name it ISQLiteConnection.cs. See the code in the following snippet: public interface ISQLiteConnection { string GetDataBasePath(); SQLiteAsyncConnection GetConnection(); }
8. Go to the Android project and create a class named SQLiteConnectionDroid.cs that will implement the ISQLiteConnection interface. Right-click in the project and choose Add | Class…. Find the implementation code next: [assembly: Dependency(typeof (XamFormsSQLiteDataAccess.Droid.SQLiteConnectionDroid))] namespace XamFormsSQLiteDataAccess.Droid { [ 442 ]
Chapter 5 public class SQLiteConnectionDroid : ISQLiteConnection { private SQLiteAsyncConnection _connection; public string GetDataBasePath() { string filename = "MyDb.db3"; string path = System.Environment.GetFolderPath (System.Environment.SpecialFolder.Personal); return Path.Combine(path, filename); } public SQLiteAsyncConnection GetConnection() { if (_connection != null) { return _connection; } SQLiteConnectionWithLock connectioonWithLock = new SQLiteConnectionWithLock( new SQLitePlatformAndroid(), new SQLiteConnectionString(GetDataBasePath(), true)); return _connection = new SQLiteAsyncConnection(() => connectioonWithLock); } } }
9. In the iOS project, create a class named SQLiteConnectionTouch.cs. See the following implementation: [assembly: Dependency(typeof(XamFormsSQLiteDataAccess. iOS.SQLiteConnectionTouch))] namespace XamFormsSQLiteDataAccess.iOS { public class SQLiteConnectionTouch : ISQLiteConnection { private SQLiteAsyncConnection _connection;
[ 443 ]
Dude, Where's my Data? public string GetDataBasePath() { string filename = "MyDb.db3"; string docFolder = Environment.GetFolderPath (Environment.SpecialFolder.Personal); string libFolder = Path.Combine(docFolder, "..", "Library", "Databases"); if (!Directory.Exists(libFolder)) { Directory.CreateDirectory(libFolder); } return Path.Combine(libFolder, filename); } public SQLiteAsyncConnection GetConnection() { if (_connection != null) { return _connection; } SQLiteConnectionWithLock connectioonWithLock = new SQLiteConnectionWithLock( new SQLitePlatformIOS(), new SQLiteConnectionString (GetDataBasePath(), true)); return _connection = new SQLiteAsyncConnection(() => connectioonWithLock); } } }
10. And for the Windows Phone platform, create a class named SQLiteConnectionWinPhone.cs. [assembly: Dependency(typeof(XamFormsSQLiteDataAccess. WinPhone.SQLiteConnectionWinPhone))] namespace XamFormsSQLiteDataAccess.WinPhone { public class SQLiteConnectionWinPhone : ISQLiteConnection { [ 444 ]
Chapter 5 private SQLiteAsyncConnection _connection; public string GetDataBasePath() { string filename = "MyDb.db3"; string path = Windows.Storage.ApplicationData. Current.LocalFolder.Path; return Path.Combine(path, filename); } public SQLiteAsyncConnection GetConnection() { if (_connection != null) { return _connection; } SQLiteConnectionWithLock connectioonWithLock = new SQLiteConnectionWithLock( new SQLitePlatformWP8(), new SQLiteConnectionString (GetDataBasePath(), true)); return _connection = new SQLiteAsyncConnection(() => connectioonWithLock); } } }
11. To test that we are ready to create and work against a local SQLite database in our application from our core portable class library, replace the App.cs constructor code with the following: public App() { // The root page of your application ISQLiteConnection connection = DependencyService.Get(); if (connection.GetConnection () != null) { Debug.WriteLine ("SQLite connection is ready!"); } MainPage = new ContentPage { [ 445 ]
Dude, Where's my Data? Content = new StackLayout { VerticalOptions = LayoutOptions.Center, Children = { new Label { XAlign = TextAlignment.Center, Text = connection.GetDataBasePath() } } } }; }
12. Run the application for each platform. You will get a message in the output window that says SQLite connection is ready!" and the database path in the center of the screen. iOS:
[ 446 ]
Chapter 5
Android:
[ 447 ]
Dude, Where's my Data?
Windows Phone:
How it works…
This is the basic setup you need to create a shared SQLite data access connection. The challenge here is the different filesystem APIs for each platform, but we solved this with the out-of-the-box DependencyService injecting the implementation and programming against the interface. In a real-world application, you might want to leverage Dependency Injection and push the ISQLiteConnection instance in your repository classes via constructor; you can see how to do this in the recipe Adding a third-party Dependency Injection Container of Chapter 4, Different Cars, Same Engine. For Windows Phone, an extra step is needed, SQLite is not built-in for the platform but an extension is available providing the necessary library; steps 4 and 5 explain how to do it.
[ 448 ]
Chapter 5
SQLite.Net-PCL has an object called SQLiteAsyncConnection; you need an
instance of this object to perform any operation on the database. To create an instance of this class, we need access to the native platform to retrieve the database folder path and specify the platform with an ISQLitePlatform implementation. To accomplish this, we created a class for each platform implementing an interface and using DependencyService to resolve it. If you want to know more about how to use DependencyService, go to Using the dependency locator recipe of Chapter 4, Different Cars, Same Engine.
See also
• https://github.com/oysteinkrog/SQLite.Net-PCL
Performing CRUD operations in SQLite CRUD operations (create, read, update, delete) are the four basic functions in persistent storage. Whatever the storage option, you will need to perform these actions.
There are patterns and best practices to achieve reusable data access components and the most common implementation is the Repository Pattern. In this recipe, we create a generic repository that we use to perform CRUD operations against a SQLite database.
How to do it…
1. In Visual Studio, create a Blank App (Xamarin.Forms Portable) project named XamFormsCRUDSQLite from the top menu, File | New | Project…. 2. For all the projects in the solution, we need the SQLite.Net-PCL and SQLite. Net.Async-PCL NuGet packages. Right-click on every project and choose Manage NuGet Packages; search and install the packages. 3. Create a folder named Data: in the XamFormsCRUDSQLite PCL, right-click Add | New Folder. 4. In the newly created folder, we assume that you created ISQLiteAsyncConnectionService and also the platform implementations to retrieve a SQLite connection via DependencyService. Refer to the code of this recipe to see the details or go to the recipe Creating a shared SQLite data access in this chapter.
[ 449 ]
Dude, Where's my Data?
5. Create a generic interface named IRepository.cs: right-click the Data folder, Add | New Item…, choose Interface, and click Add. See the following method signatures: public interface IRepository where T : class, new() { Task GetAllAsync(); Task InsertAsync(T entity); Task UpdateAsync(T entity); Task DeleteAsync(T entity); }
6. One generic implementation is needed for this repository. Create a class by right-clicking the Data folder, Add | Class…, and name it Repository.cs. Copy the following implementation: public class Repository : IRepository where T : class, new() { ISQLiteAsyncConnectionService _connectionService; SQLiteAsyncConnection _connection; public Repository(ISQLiteAsyncConnectionService connectionService) { _connectionService = connectionService; _connection = _connectionService.GetConnection(); _connection.CreateTableAsync(); } public Task GetAllAsync() { return _connection.Table().ToListAsync(); } public Task InsertAsync(T entity) { return _connection.InsertAsync(entity); } public Task UpdateAsync(T entity) { return _connection.UpdateAsync(entity); }
[ 450 ]
Chapter 5 public Task DeleteAsync(T entity) { return _connection.DeleteAsync(entity); } }
7. Create a folder named Models: right-click Add | New Folder. 8. Right-click the folder Models and Add | Class…. Name it Contact.cs, make it public, and add three simple properties: FirstName, LastName, and Id. See the following implementation: public class Contact { [PrimaryKey] public Guid Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
Notice the PrimaryKeyAttribute decoration in the Id property? This is needed for the database. We define a primary key in the database to uniquely identify the row, for indexing, but also you will not be able to delete a row without it.
9. Create a folder named Views: right-click and Add | New Folder. 10. Right-click the Views folder and Add | New Item…, choose Forms Xaml Page from the templates, name it MainPage.xaml, and click Add. 11. Repeat step 11 for a page named ContactDetailPage.xaml. 12. Go to the App.cs constructor and replace the code with the following single line of code: MainPage = new NavigationPage(new MainPage());
Almost ready. All we need is to wire up some user interface and add functionality in the pages' behind code. Here, we omit the XAML code. If you want to see the XAML pages' contents, refer to this recipe code. To learn more about Xamarin.Forms XAML, go to Chapter 2, Declare Once, Visualize Everywhere; for data binding, visit Chapter 7, Bind to the Data; and for the ListView control, go to Chapter 8, A List to View.
[ 451 ]
Dude, Where's my Data?
13. In MainPage.xaml.cs, we retrieve the connection and pass it to the contacts repository constructor. We keep a field of the repository to invoke the CRUD methods. private IRepository _contactRepo; public MainPage() { InitializeComponent(); ISQLiteAsyncConnectionService connectionService = DependencyService.Get(); _contactRepo = new Repository(connectionService); }
14. Add the following code to refresh the list from the database, add a row, delete a row, and go to the details of a row to update a contact: private async void OnAddContactClick (object sender, EventArgs e) { await _contactRepo.InsertAsync(new Contact { Id = Guid.NewGuid(), FirstName = "FirstName: " + new Random().Next(10), LastName = "LastName: " + new Random().Next(10) }); await RefreshAsync(); } public async void OnItemSelected (object sender, SelectedItemChangedEventArgs e) { if (e.SelectedItem == null) return; ((ListView)sender).SelectedItem = null; await Navigation.PushAsync (new ContactDetailPage((Contact)e.SelectedItem)); } public async void OnCellClicked (object sender, EventArgs e) { Button button = (Button)sender; Guid id = (Guid) button.CommandParameter;
[ 452 ]
Chapter 5 await _contactRepo.DeleteAsync (((List)BindingContext).FirstOrDefault(p => p.Id == id)); await RefreshAsync(); } protected async override void OnAppearing() { base.OnAppearing(); await RefreshAsync(); } private async Task RefreshAsync() { BindingContext = await _contactRepo.GetAllAsync(); }
15. Go to ContactDetailsPage.xaml.cs and add the following code snippet. In this page, we allow the user to update a contact's details. private IRepository _contactRepo; public ContactDetailPage(Contact contact) { InitializeComponent(); BindingContext = contact; ISQLiteAsyncConnectionService connectionService = DependencyService.Get(); _contactRepo = new Repository(connectionService); } private async void OnSaveContactClick (object sender, EventArgs e) { await _contactRepo.UpdateAsync ((Contact)BindingContext); await Navigation.PopAsync(); }
[ 453 ]
Dude, Where's my Data?
16. Ready! See next the screenshots of running the solution. Add, delete, and update a row. Android:
[ 454 ]
Chapter 5
Android contact details:
[ 455 ]
Dude, Where's my Data?
Windows Phone:
[ 456 ]
Chapter 5
iOS:
How it works…
With Xamarin.Forms, a little help from NuGet and the community, a bit of architecture setup, and working with the SQLite local database, a shared repository for all platforms becomes easy. Having created a service for each platform to retrieve the connection and injected via DependencyService, we create a generic interface, IRepository, and a generic implementation repository class, Repository. With this solution, we encapsulate all the data access in a reusable single responsibility container. The IRepository interface defines four methods: fetching all the related entities of the corresponding table and inserting, updating, and deleting an entity.
[ 457 ]
Dude, Where's my Data?
In the Repository generic implementation, we pass the ISQLiteAsyncConnectionService dependency in our constructor, keeping the important instance of the SQLiteAsyncConnection in a private field and creating asynchronously the T table; if the tables exist, nothing happens. For each IRepository implemented method, we use the SQLiteAsyncConnection instance and its corresponding methods for CRUD operations. SQLite is a non-thread-safe database. Fear not! The SQLite.Net-PCL NuGet package is utilizing a connection with a lock that makes it thread safe. Neat! In this recipe, we used only one model class, contact, mapping against the database to a contact table. Decorating the Id property with PrimaryKeyAttribute gave the necessary instruction to the SQLite-Net-PCL library to create a column Id as the primary column key. If you want to see how you can define relationships and load records with their corresponding children, refer to https://bitbucket.org/ twincoders/sqlite-net-extensions. The XAML UI is out of the scope of the recipe in this topic, so we removed the code. You can easily check it out in the module code or visit the chapters in the See also section to learn more details about how it works. To see how ListView works, go to Chapter 8, A List to View. In the behind pages' constructors, we retrieve the ISQLiteAsyncConnectionService dependency and create a Repository; this will create a contact table if it does not exist and provide us with all the CRUD operations. In MainPage.xaml.cs, we have wired event handlers for navigation, retrieving all the contact records, insert, and delete a record. The ContactDetailPage.xaml.cs constructor accepts a contact argument and contains an event handler to update the contact and go back to the parent page.
See also
• https://github.com/oysteinkrog/SQLite.Net-PCL • https://bitbucket.org/twincoders/sqlite-net-extensions • Creating a shared SQLite data access recipe in this chapter • Chapter 2, Declare Once, Visualize Everywhere • Chapter 7, Bind to the Data • Chapter 8, A List to View [ 458 ]
Chapter 5
Consuming REST web services
Almost all applications today connect to a server to persist data. Some are connected, meaning an active Internet connection is required, whereas others have only offline data, with a local database on the device. Many utilize a combination of offlineonline applications with an implementation of data syncing. In this recipe, we will demonstrate how to connect to a REST web service and perform CRUD operations in a connected architecture application. REST (short for Representational State Transfer) is not a protocol or a specific implementation of a design pattern, but you can think of it as an architectural style that relies on a stateless, client-server, cacheable communications protocol, and in most of the cases the HTTP protocol is used. REST is the standard architectural style of most web services that you will see today.
How to do it…
1. In Visual Studio, create a new Blank App (Xamarin.Forms Portable) named XamFormsREST. In the top menu, select File | New | Project…. There are numerous libraries to install and work with REST web services. One of the most well-known, and one that works great with Xamarin, is the Microsoft.Net.Http NuGet package. For the payload of our requests, we are using JSON format, and to serialize objects to JSON representation, we are using the Newtonsoft.Json NuGet package.
2. Right-click the core XamFormsREST library and choose Manage NuGet Packages. Search for Microsoft.Net.Http and install the package. Repeat the search and install step for the Newtonsoft.Json NuGet package. 3. Repeat step 2 only for the XamFormsREST.WinPhone project and install the Microsoft.Net.Http NuGet package. 4. Back to the XamFormsREST core library, create two folders named Models and Views by right-clicking the project and choosing Add | New Folder. 5. Right-click the Models folder and create a new class, Add | Class…, named Order.cs. The class has three simple properties. See the following implementation: public class Order { public string ObjectId { get; set; } public string OrderNumber { get; set; } public string ClientComments { get; set; } } [ 459 ]
Dude, Where's my Data?
A REST web service is needed. For the sake of our recipe, we are using Parse, an easy to set up, simple, and powerful cloud database that exposes a REST API to work with. For information on how to get it up and running (in a few minutes), go to https://www.parse.com/. Just make sure you create a class named Order in your Parse application.
6. Right-click the Models folder and create a class named DataService. cs. This is the data access class to perform all required CRUD operations against the REST web service and the Order class endpoint. Check the full implementation next: public class DataService { private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; const string Url = "https://api.parse.com/1/classes/Order"; private HttpClient GetClient() { HttpClient client = new HttpClient(); client.DefaultRequestHeaders.Add("X-Parse-ApplicationId", "YOUR_APPLICATION_ID"); client.DefaultRequestHeaders.Add("X-Parse-REST-APIKey", "YOUR_REST_API_KEY"); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); return client; } public async Task GetAllAsync() { using (HttpClient client = GetClient()) { HttpResponseMessage httpResponseMessage = await client.GetAsync(Url); httpResponseMessage.EnsureSuccessStatusCode(); string content = await httpResponseMessage.Content.ReadAsStringAsync(); JObject jsonObject = JObject.Parse(content); string ordersJson = jsonObject["results"].ToString();
[ 460 ]
Chapter 5 return JsonConvert.DeserializeObject (ordersJson); } } public async Task InsertAsync(Order order) { using (HttpClient client = GetClient()) { HttpResponseMessage httpResponseMessage = await client.PostAsync ("https://api.parse.com/1/classes/Order", new StringContent(JsonConvert.SerializeObject(order, _jsonSerializerSettings), Encoding.UTF8, "application/json")); httpResponseMessage.EnsureSuccessStatusCode(); string content = await httpResponseMessage.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(content); } } public async Task UpdateAsync(Order order) { using (HttpClient client = GetClient()) { HttpResponseMessage httpResponseMessage = await client.PutAsync(Url + "/" + order.ObjectId, new StringContent(JsonConvert.SerializeObject(order, _jsonSerializerSettings), Encoding.UTF8, "application/json")); httpResponseMessage.EnsureSuccessStatusCode(); } } public async Task DeleteAsync(string objectId) { using (HttpClient client = GetClient()) { HttpResponseMessage httpResponseMessage = await client.DeleteAsync(Url + "/" + objectId); httpResponseMessage.EnsureSuccessStatusCode(); } } } [ 461 ]
Dude, Where's my Data?
7. We will create two views to interact with the data. Right-click the Views folder and Add | New Item…; from the templates, choose Forms Xaml Page and name it MainPage.xaml. Repeat this for a page named OrderDetailsPage.xaml. Almost ready. All we need is to wire up some user interface and add functionality in the pages' behind code. Here, we omit the XAML code. If you want to see the XAML pages' contents, refer to this recipe code. To learn more about Xamarin.Forms XAML, go to Chapter 2, Declare Once, Visualize Everywhere; for data binding, visit Chapter 7, Bind to the Data; and for ListView control, go to Chapter 8, A LIst to View.
8. In the MainPage.xaml.cs behind code, we add the following implementation: public partial class MainPage : ContentPage { private readonly DataService _dataService = new DataService(); public MainPage() { InitializeComponent(); } private async void OnAddUserClick (object sender, EventArgs e) { await _dataService.InsertAsync(new Order { OrderNumber = new Random().Next(100).ToString() }); await RefreshAsync(); } public async void OnItemSelected(object sender, SelectedItemChangedEventArgs e) { if (e.SelectedItem == null) return; ((ListView)sender).SelectedItem = null; await Navigation.PushAsync(new OrderDetailsPage((Order)e.SelectedItem)); } public async void OnCellClicked(object sender, EventArgs e)
[ 462 ]
Chapter 5 { Button button = (Button)sender; string id = (string)button.CommandParameter; await _dataService.DeleteAsync (((List)BindingContext).FirstOrDefault(p => p.ObjectId == id).ObjectId); await RefreshAsync(); } protected async override void OnAppearing() { base.OnAppearing(); await RefreshAsync(); } private async Task RefreshAsync() { BindingContext = await _dataService.GetAllAsync(); } }
9. Go to OrderDetailsPage.xaml.cs and set up the class like the following snippet: public partial class OrderDetailsPage : ContentPage { private readonly DataService _dataService = new DataService(); public OrderDetailsPage(Order order) { InitializeComponent(); BindingContext = order; } private async void OnSaveUserClick(object sender, ventArgs e) { await _dataService.UpdateAsync((Order)BindingContext); await Navigation.PopAsync(); } }
[ 463 ]
Dude, Where's my Data?
10. Go to App.cs constructor and replace the code with the following: MainPage = new NavigationPage(new MainPage());
11. Run the applications and interact with the REST web service to fetch, insert, update, and delete order records. Android:
[ 464 ]
Chapter 5
Android – order details:
[ 465 ]
Dude, Where's my Data?
Windows Phone:
[ 466 ]
Chapter 5
iOS:
How it works…
Working against REST web services has a lot of detail, but it can be simplified with the use of one of the client libraries out there: one of the most well-known and proven to work great with Xamarin is the Microsoft.Net.Http NuGet package. Many backend platforms provide SDKs too. In our case, using Parse simplifies even the database design details and provide us with the advantage of getting up and running in two minutes. They also provide an SDK, depending on the platform, but we utilized the plain REST endpoints, flexible for prototyping your Xamarin application and then switch to your server implementation by changing two or three lines of code.
[ 467 ]
Dude, Where's my Data?
Our user interface is simple: we have a MainPage with a ListView to present rows of Order data, and a toolbar button to add an order with a random OrderNumber in the database through the REST web service endpoint. Tapping a row navigates to the OrderDetailsPage where you can update the OrderComments of an order. In the behind code of the pages, we construct and use a DataService instance class to do all the work between the client and the REST web service. DataService is the core of the data exchange with the server. For this recipe, we
kept it simple, but you could implement the Repository pattern to make it generic for all the additional classes added in the future. To see how to do this, go to the section Performing CRUD operations in SQLite. You might want to also eliminate behind code and make your code reusable for additional future platforms implementing the MVVM pattern. Details on how to do this are in the Architecture design with ModelView-ViewModel (MVVM) pattern recipe of Chapter 4, Different Cars, Same Engine. The class has a constant string property named Url, which is the base URL to reach the order resource URI. We added a private method, GetClient, returning an HttpClient instance, the main object we need to make requests to the web service. In the GetAllAsync method, we retrieve all the rows from the server database through the REST endpoint making a GET request, we invoke the EnsureSuccessStatusCode method to make sure everything went ok with our request call, and in the end we use the JSON.Net (Newtonsoft.Json NuGet package) library to deserialize the JSON string representation format to Order class instances. The InsertAsync method accepts an Order instance; here, we do a POST with the HttpClient.PostAsync method, which accepts two arguments: the URL and an HttpContent. For the latter, we create StringContent serializing the Order with a JsonSerializerSettings instance to meet the camelCase JSON representation format standard, set the encoding to UTF8 and the content-type to application/json, ensure the success status code, and retrieve the response payload, which is the order with objectId set by the server. In the UpdateAsync method, we pass an Order instance. We only use the ObjectId property in the URL endpoint and with a PUT method, HttpClient.PutAsync, we pass the order serialized as in the InsertAsync method. We ensure the success status code and that's it, the order has been updated. For DeleteAsync, we just need ObjectId and a DELETE method, HttpClient. DeleteAsync, passing ObjectId in the URL endpoint. Ensure the success status code and the order is deleted. This is all you need to work against any REST web service out there. Happy data consuming! [ 468 ]
Chapter 5
Leveraging native REST libraries and making efficient network calls
In a connected world, while your application grows, you will see that calls to web services are getting stressed and often, and all these calls will be asynchronous concurrent network requests. Issuing a lot of requests will end up making your app slow, and managing these requests by hand, such as who has priority and when, or how many calls at a time are allowed, will break a lot of encapsulation between different components, resulting in spaghetti code. It's ok! We can fix this. There are two libraries out there, created by Paul Betts, to save the day: modernhttpclient and Punchclock. Let's see how to install and use them in our network calls.
How to do it…
1. In Visual Studio, create a Blank App (Xamarin.Forms Portable) solution named XamFormsEfficientNetworking from the top menu, File | New | Project…. 2. Right-click the portable class library, XamFormsEfficientNetworking, and choose Manage NuGet Packages. Search and install the following packages: °° Microsoft.Net.Http °° Newtosoft.Json °° modernhttpclient °° Punchclock 3. Install only the modernhttpclient NuGet package in the XamFormsEfficientNetworking.Droid and XamFormsEfficientNetworking.iOS projects. 4. Right-click the XamFormsEfficientNetworking.WinPhone project and choose Manage NuGet Packages. Find and install the Microsoft.Net.Http package. 5. There is no modernhttpclient specific Windows Phone project, but it is easy to support it. All we need is to create an empty NativeMessageHandler class derived from HttpClientHandler. The modernhttpclient library will handle picking it up and using it in runtime. Right-click the project and Add | Class…, name it NativeMessageHandler, and click Add. Find the class details next: public class NativeMessageHandler : HttpClientHandler { } [ 469 ]
Dude, Where's my Data?
6. Create a folder named Models. Right-click XamFormsEfficientNetworking, Add | New Folder. 7. Right-click the newly created folder Models and choose Add | Class…. Give it the name Order.cs and click Add. The following is the simple implementation: public class Order { public string ObjectId { get; set; } public string OrderNumber { get; set; } [JsonIgnore] public List Items { get; set; } }
8. Right-click the Models folder and choose Add | Class…. Name it Item.cs and click Add. Find the implementation details in the following snippet: public class Item { public string ObjectId { get; set; } public string OrderNumber { get; set; } public string Code { get; set; } }
9. Right-click the portable class library and create a folder named Views with Add | New Folder. 10. Right-click the newly created Views folder and choose Add | New Item…. From the templates, choose Forms Xaml Page, name it MainPage.xaml, and click Add. The XAML code for MainPage is omitted for brevity. Please refer to the recipe's code to see the ListView implementation or refer to Chapter 8, A List to View to learn how to work with the ListView control.
11. Go to the App.cs constructor and change the MainPage property assignment to the following: MainPage = new NavigationPage(new MainPage());
12. Right-click the Models folder and create a class named DataService.cs. This is the class where all the interesting things are happening. Check the following implementation details: using ModernHttpClient; using Newtonsoft.Json; [ 470 ]
Chapter 5 using using using using using using using using
Newtonsoft.Json.Linq; Newtonsoft.Json.Serialization; Punchclock; System.Collections.Generic; System.Net.Http; System.Net.Http.Headers; System.Text; System.Threading.Tasks;
public class DataService { private readonly OperationQueue _opQueue = new OperationQueue(2); private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; const string OrdersUrl = "https://api.parse.com/1/classes/Order"; const string ItemsUrl = "https://api.parse.com/1/classes/Item"; private HttpClient GetClient() { HttpClient client = new HttpClient(new NativeMessageHandler()); client.DefaultRequestHeaders.Add("X-Parse-ApplicationId", "YOUR_APPLICATION_KEY"); client.DefaultRequestHeaders.Add("X-Parse-REST-APIKey", "YOUR_API_KEY"); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); return client; } public async Task GetAllOrdersAsync(int skip = 0) { using (HttpClient client = GetClient()) { Dictionary values = new Dictionary() { { "skip", skip.ToString() } }; FormUrlEncodedContent encodedContent = new FormUrlEncodedContent(values); [ 471 ]
Dude, Where's my Data? string param = await encodedContent.ReadAsStringAsync(). ConfigureAwait(false); HttpResponseMessage httpResponseMessage = await _opQueue.Enqueue(10, () => client.GetAsync(OrdersUrl + "?" + param)).ConfigureAwait(false); httpResponseMessage.EnsureSuccessStatusCode(); string content = await httpResponseMessage.Content. ReadAsStringAsync().ConfigureAwait(false); JObject jsonObject = JObject.Parse(content); string ordersJson = jsonObject["results"].ToString(); IEnumerable orders = JsonConvert.DeserializeObject (ordersJson); List tasks = new List(); foreach (Order order in orders) { tasks.Add(GetItemsByOrderNumberAsync (order.OrderNumber).ContinueWith((antecedent) => { order.Items = new List(antecedent.Result); })); } await Task.WhenAll(tasks).ConfigureAwait(false); return orders; } } public async Task GetItemsByOrderNumberAsync(string orderNumber) { using (HttpClient client = GetClient()) { string orderNumberJson = "{\"orderNumber\": \"" + orderNumber + "\"}"; Dictionary values = new Dictionary() { { "where", orderNumberJson } }; FormUrlEncodedContent encodedContent = new FormUrlEncodedContent(values); string param = await encodedContent.ReadAsStringAsync(). ConfigureAwait(false);
[ 472 ]
Chapter 5 HttpResponseMessage httpResponseMessage = await _opQueue.Enqueue(15, () => client.GetAsync(ItemsUrl + "?" + param)).ConfigureAwait(false); httpResponseMessage.EnsureSuccessStatusCode(); string content = await httpResponseMessage.Content. ReadAsStringAsync().ConfigureAwait(false); JObject jsonObject = JObject.Parse(content); string ordersJson = jsonObject["results"].ToString(); IEnumerable items = JsonConvert.DeserializeObject (ordersJson); return items; } } public async Task InsertOrderAsync(Order order) { using (HttpClient client = GetClient()) { HttpResponseMessage httpResponseMessage = await _opQueue.Enqueue(1, () => client.PostAsync(OrdersUrl, new StringContent(JsonConvert.SerializeObject(order, _jsonSerializerSettings), Encoding.UTF8, "application/json"))).ConfigureAwait(false); httpResponseMessage.EnsureSuccessStatusCode(); string content = await httpResponseMessage.Content. ReadAsStringAsync().ConfigureAwait(false); Order newOrder = JsonConvert.DeserializeObject(content); List tasks = new List(); foreach (Item item in order.Items) { tasks.Add(InsertItemAsync(item)); } await Task.WhenAll(tasks).ConfigureAwait(false); return newOrder; } } public async Task InsertItemAsync(Item item) { using (HttpClient client = GetClient()) [ 473 ]
Dude, Where's my Data? { HttpResponseMessage httpResponseMessage = await _opQueue.Enqueue(3, () => client.PostAsync(ItemsUrl, new StringContent(JsonConvert.SerializeObject (item, _jsonSerializerSettings), Encoding.UTF8, "application/json"))).ConfigureAwait(false); httpResponseMessage.EnsureSuccessStatusCode(); string content = await httpResponseMessage.Content. ReadAsStringAsync().ConfigureAwait(false); return JsonConvert.DeserializeObject(content); } } }
13. Go to MainPage.xaml.cs and add the following code: private readonly DataService _dataService = new DataService(); public MainPage() { InitializeComponent(); } private void OnRefreshClick(object sender, EventArgs e) { RefreshAsync(); } protected override void OnAppearing() { base.OnAppearing(); //RandomOrdersHelper.InsertOrdersAsync(100); // Add some records to the remote database } private Task RefreshAsync() { int skip = BindingContext != null ? ((IEnumerable)BindingContext).Count() : 0; return _dataService.GetAllOrdersAsync(skip). ContinueWith((antecedent) => { Device.BeginInvokeOnMainThread(() => { if (BindingContext == null) { BindingContext = new ObservableCollection(antecedent.Result); } else [ 474 ]
Chapter 5 { foreach (Order item in antecedent.Result) { ((ObservableCollection) BindingContext).Add(item); } } }); }, TaskContinuationOptions.OnlyOnRanToCompletion); }
14. You need some records. The recipe code has a helper method to insert random Order and Item records to your database. Refer to this module's code to see the implementation, uncomment the code line in the OnAppearing method, and you will start creating some records. You can also apply your own dummy insert class; it's up to you! 15. After adding some records, this is what the application will look like, if you followed the random OrderNumber implementation of this module's code: Android:
[ 475 ]
Dude, Where's my Data?
iOS:
[ 476 ]
Chapter 5
Windows Phone:
How it works…
Starting with the models, we use two POCO classes with simple properties: Order and Item. An Order has many Items that are ignored when serialized in JSON. Depending on your architecture, this is something you may or may not want. If you use a document database and you want a JSON field in your Order table, it's fine; for this recipe we used a separate table for the Item data, as it gives us the opportunity to make more calls for demonstration purposes. In MainPage.xaml.cs, the important method is RefreshAsync. Here, we check if we have loaded any orders. If yes, call the DataService.GetAllOrdersAsync(Skip) method with the count of the list items or 0 if there are no items yet fetched. The continuation delegate will run only on successful completion of the Task. In the completion action, we check if BindingContext is null and if true, create an ObservableCollection with the results; if false, we append items in the collection. [ 477 ]
Dude, Where's my Data?
All the efficiency and handling of concurrent calls happen in the interesting DataService class. Let's examine why this class handles network requests better as if we are using the default configuration. In the GetClient method, we create and return an HttpClient instance; it's a factory method we use for each HttpClient call because we immediately dispose the instance. In this method, we use the ModernHttpClient NuGet package NativeMessageHandler class and pass an instance of the handler in the HttpClient constructor. What essentially this handler does is to bypass the default HttpClient web protocol calls on each platform and make use of two native libraries: NSURLSession in the iOS platform and OkHttp in the Android platform. There is no specific ModernHttpClient Windows Phone handler implementation, but it is easy to instruct the library to pick up our custom NativeMessageHandler by creating a HttpClientHandler derived class. This makes our calls drastically faster! GetAllOrdersAsync will fetch a default limit of 100 records from the remote
database. We also have an optional parameter where you can skip records and fetch the next 100. Pagination is something you should also consider when creating apps that load large lists of data. We make the HttpClient.GetAsync call, deserialize the list of orders, and fetch the corresponding order items calling GetItemsByOrderNumber, in parallel. Notice the wrapper of OperationQueue, _opQueue.Enqueue(Priority, and Func) around HttpClient.GetAsync. This is a class in the Punchclock library, the orchestrator that will schedule and prioritize our network requests via a priority integer parameter. When we created the OperationQueue instance, we set the maximum calls at two at a time. In this method, we fetch the orders with a priority of 10, then in the same method we make multiple calls of GetItemsByOrderNumberAsync, which consequently issues internally another HttpClient.GetAsync method wrapped in OperationQueue.Enqueue has a higher priority than GetAllOrdersAsync, meaning whatever is happening in the queue, I will cut in to get priority, if of course there's no equivalent or higher-priority items in the queue. The InsertOrderAsync and InsertItemAsync methods have low priority. This was for our recipe demonstration purposes, but in your application based on your requirements, you might want inserts to cut in the queue while fetching data requests. With these two simple changes in our DataService, we have major performance increase and queue management of network requests, which gives us powerful handling of priority and parallel execution of the number of simultaneous calls.
[ 478 ]
Chapter 5
There's more…
There are a couple of other libraries from Paul Betts that you might find interesting: Fusillade, https://github.com/paulcbetts/fusillade, inspired by Volley, an Android asynchronous HTTP requests library, and Picasso, image downloading and caching for Android. Refit, https://github.com/paulcbetts/refit, a type-safe REST library inspired by Retrofit, HTTP client for Android and Java. You can decorate an interface with attributes and it will automatically generate a class implementation for this interface. It is worth noting Flurl, https://tmenier.github.io/Flurl/, created by Todd Menier, a fluent URL builder and testable HTTP library. This will simplify your HttpClient constructs in a fun and simple fluent way. There is not one solution to rule them all, but you can install and mix and match for every architecture that suits your applications best!
[ 479 ]
One for All and All for One In this chapter, we will cover the following recipes: • Creating cross-platform plugins • Taking or choosing photos • Querying the GPS location • Querying the OS contacts
Introduction
With Xamarin.Forms you can also create a cross-platform UI and customize it or mix and match native-Xamarin.Forms. Amazing! That's all good, but you can't access the native platform features from shared code and in our shared code we have all our POCO data objects, networking service classes, data access components, Models, ViewModels, and business logic. If you try to reference shared code in your native code and start applying logic then you start repeating code between platforms, messing with spaghetti code, and that leads to complex, non-readable, and hard to debug code. All platforms have common capabilities. Android, iOS, and Windows have battery, GPS, notifications, settings, Bluetooth, text to speech, and maps, but all are used with their corresponding native APIs. What are the solutions? Abstractions, service location, and dependency injection! You program against an interface where the implementation is loaded in runtime for each platform.
[ 481 ]
One for All and All for One
In the first recipe, Creating cross-platform plugins, we will explore a technique called Bait and Switch used by almost all the plugins out there, at least most of those created by Xamarin, and it's an exciting way of thinking when programming crossplatform applications. In the remaining three recipes, we will be using some of the currently available plugins to demonstrate how simple it is to produce your forms app with help from the community. It is worth bearing in mind that as with Xamarin.Forms, plugins will change over time and therefore you may have to alter the examples to enable the code to compile.
Creating cross-platform plugins
Okay, you're tired of repeating the same copy and paste code in every project you create for this native implementation that there is no available plugin from Xamarin or a Xamarin developer out there! You create your interface, create the implementation classes, register to a DI container or just a service locator, and resolve the dependencies in runtime. It works! Now, for a minute imagine creating your own plugin. If the client remembers to register your plugin with your implementation for each platform then everything's OK, and you will be depended in a custom internal or third-party service locator and DI container? Doesn't sound very practical, right? Yes, there is a better way to do things. A good friend named it Bait and Switch PCL, and Miguel De Icaza from Xamarin has called it the Advanced PCL pattern. What this pattern essentially solves is the process of creating interface abstractions to program against and injecting implementation classes conforming to the interface in your application via Dependency Injection or a Service Locator. The importance of this pattern is not to think of a PCL as an entity on its own, so let's see some facts to understand this better. • There is no such thing as a portable application. • The platform that the PCL profile defines doesn't actually exist. An application will always run under a profile with more features • A critical aspect of NuGet for the pattern to work is that it will always prefer a platform library to a PCL. Meaning that if you write a NuGet dll for each platform, NuGet will always choose the platform-specific one when added to the project. • The switch of the assembly is accomplished via method invocation, which means, as long as the platform binary matches the assembly name, version, and class structure, we can replace it with the platform binary. [ 482 ]
Chapter 6
Let's see how to do it.
How to do it...
1. In Visual Studio, go to the top menu, select Tools | Extensions and Updates and search for plugin for Xamarin templates; select and click Download and then Close.
[ 483 ]
One for All and All for One
2. Select File | New | Project… and find the template plugin for Xamarin; give it the name XamFormsCrossPlugin and click OK.
3. Right-click the solution and select Add | New Solution Folder; give it the name Client. 4. Right-click the newly created solution folder Client and choose Add | New Project…, select the template Blank App (Xamarin.Forms Portable), name it XamFormsPluginClient and click OK. 5. Go to the Plugin.XamFormsCrossPlugin.Abstractions library, open the IXamFormsCrossPlugin.cs file and add one method signature in the IXamFormsCrossPlugin interface. string PlatformHelloWorld();
[ 484 ]
Chapter 6
6. In the Plugin.XamFormsCrossPlugin portable library, notice that the CrossXamFormsCrossPlugin.cs class file is linked to every native platform project library. Inside there is a lazy property that creates a new XamFormsCrossPluginImplementation class if the platform is not PORTABLE or else null. 7. Go to Plugin.XamFormsCrossPlugin.Android, open the XamFormsCrossPluginImplementation and implement the IXamFormsCrossPlugin interface. public class XamFormsCrossPluginImplementation : IXamFormsCrossPlugin { public string PlatformHelloWorld() { return "Hello from Android"; } }
8. Repeat step 7 for Plugin.XamFormsCrossPlugin.iOSUnified. public class XamFormsCrossPluginImplementation : IXamFormsCrossPlugin { public string PlatformHelloWorld() { return "Hello from iOS"; } }
9. And for Plugin.XamFormsCrossPlugin.WindowsPhone8: public class XamFormsCrossPluginImplementation : IXamFormsCrossPlugin { public string PlatformHelloWorld() { return "Hello from Windows Phone 8"; } }
[ 485 ]
One for All and All for One
10. Go to the Client solution folder in the XamFormsPluginClient portable class library and right-click the References folder. Choose Add Reference and in the Projects | Solution section, check the Plugin. XamFormsCrossPlugin and Plugin.XamFormsCrossPlugin.Abstractions libraries and click OK.
11. Open App.cs and in the constructor, replace the assignment of the Text property in the ContentPage initialization. Text = CrossXamFormsCrossPlugin.Current.PlatformHelloWorld()
12. Right-click the References folder in XamFormsPluginClient.Droid, choose Add Reference and in the Project | Solution section check the Plugin. XamFormsCrossPlugin.Android and Plugin.XamFormsCrossPlugin. Abstractions libraries and click OK. 13. Repeat step 12 for XamFormsPluginClient.iOS and check the Plugin. XamFormsCrossPlugin.iOSUnified and Plugin.XamFormsCrossPlugin. Abstractions libraries.
[ 486 ]
Chapter 6
14. Repeat step 12 for XamFormsPluginClient.WinPhone and this time add the Plugin.XamFormsCrossPlugin.WindowsPhone8 and Plugin. XamFormsCrossPlugin.Abstractions libraries. 15. Run the application for each platform and admire your awesome new feature of printing a hello message for every platform. iOS:
[ 487 ]
One for All and All for One
Android:
[ 488 ]
Chapter 6
Windows Phone:
How it works…
Do you need to create a plugin that works in various platforms? No problem. James Montemagno from Xamarin created this helper template based on the Bait and Switch pattern to make development rapid.
[ 489 ]
One for All and All for One
The template creates two portable class libraries. The abstraction one contains our core shared types like interfaces, enums, and any constants or models dependencies; in our case, we only have one interface, IXamFormsCrossPlugin. The second portable contains only one class, the bait, CrossXamFormsCrossPlugin, that lazy creates an instance of our class implementing the IXamFormsCrossPlugin interface, XamFormsCrossPluginImplementation, the switch! We have access to the platform instance from the current static property where if the Implementation.Value is null it throws a NotImplementedException. You can also see where the switch is happening with an #ifdef conditional check in the CreateXamFormsCrossPlugin method, if PORTABLE then returns null or else the native platform implementation. How is the switch happening? In the five native platform libraries we get from the template, there is a link to the CrossXamFormsCrossPlugin.cs file and a XamFormsCrossPluginImplementation.cs file for each native platform library. Well, that saved us from some complexity. It might seem confusing in the beginning, but if you explore the code for some time it makes a lot of sense. Open XamFormsCrossPluginImplementation of the Plugin.XamFormsCrossPlugin. Android library; notice that the instance creation of a class is now highlighted, so when this library is compiled the native class implementation is exposed to the platform project even if we reference the PCL and work from our core shared library with the abstractions. In runtime, the platform-specific implementation will be used. Take note that the namespace, version, and assembly name are the same for all the native platform libraries and the portable library; this is key for the switch to work. In the end, we reference the abstractions and portable library to our core portable client library, and for every platform the native equivalent library and the abstractions library.
There's more…
You're almost ready to ship your cool plugin! But you need to package it and upload it to NuGet. No problem. Xamarin has a template for this too. • Right-click the solution and Add | New Item…, find the template plugin for Xamarin NuSpec and name it exactly the same with our plugin solution. In the recipe, the name is XamFormsCrossPlugin. Click Add.
[ 490 ]
Chapter 6
That's all you need. The Nuspec is ready; edit as needed if you removed or added any supported platforms and then build the plugin in release mode to get ready to ship your cross-platform Xamarin plugin.
See also
• https://developer.xamarin.com/guides/cross-platform/advanced/ nuget/
Taking or choosing photos
It's very common to work with the camera in applications: you want to take a photo or video, post on social media, or just keep it as records, from expenses to business cards. For this recipe, we will use a Xamarin plugin to simplify the process and achieve the goal of using the camera for capturing photos and videos with a couple of code lines.
[ 491 ]
One for All and All for One
How to do it…
1. Create a Blank App (Xamarin.Forms Portable) solution from File | New | Project…, named XamFormsTakingPhotos and click OK. 2. For each project created, we will add the Xam.Plugin.Media NuGet package. Right-click the projects one by one, choose Manage NuGet Packages and search and install the Xam.Plugin.Media package. 3. Three permissions are required in the Android Manifest. These are added automatically when installing the NuGet package, but just check to make sure. Right-click on the Android package and choose Properties, select the Android Manifest tab and check for the following permission: °° WRITE_EXTERNAL_STORAGE °° READ_EXTERNAL_STORAGE °° CAMERA
[ 492 ]
Chapter 6
4. Go to the Windows Phone platform, expand the Properties folder and double-click WMAppManifest.xml. Select the Capabilities tab and check the ID_CAP_ISV_CAMERA to allow camera access. 5. The Android project should contain a MainApplication.cs file. If not, make sure to upgrade to the latest Xamarin.Forms NuGet package. Open MainApplication.cs and in the OnCreate method, add the following code to register the dependency: Xamarin.Forms.DependencyService.Register ();
6. Add the same line of code from step 5 in the AppDelegate.cs of the iOS platform after the Forms.Init method. 7. Repeat the same for the Windows Phone platform in the MainPage.xaml.cs behind code after the Forms.Init method. 8. Go to the XamFormsTakingPhotos PCL, right-click and Add | New Item…; select Forms Xaml Page, name it MainPage.xaml and click Add. 9. Replace the ContentPage tag contents with the following:
10. Open App.cs and in the constructor, replace the MainPage property assignment with the following code: MainPage = new MainPage();
11. Go to MainPage.xaml.cs and at the top of the file, add a field to retrieve the IMedia implementation. private readonly IMedia Media = DependencyService.Get();
12. To capture a photo, we will add the corresponding OnTakePhotoButtonClicked event handler. async void OnTakePhotoButtonClicked(object sender, EventArgs args) { if (!Media.IsCameraAvailable || !Media.IsTakePhotoSupported) { DisplayAlert("No Camera", "No camera available.", "OK"); [ 493 ]
One for All and All for One return; } MediaFile file = await Media.TakePhotoAsync(new StoreCameraMediaOptions { Directory = "Sample", Name = "test.jpg" }); if (file != null) { ImageSource imageSource = ImageSource.FromStream(() => { var stream = file.GetStream(); return stream; }); imagePhoto.Source = imageSource; Debug.WriteLine("Photo File Path: {0}", file.Path); file.Dispose(); } }
13. To capture a video, add the OnTakeVideoButtonClicked event handler: async void OnTakeVideoButtonClicked(object sender, EventArgs args) { if (!Media.IsCameraAvailable || !Media.IsTakeVideoSupported) { DisplayAlert("No Camera", ":( No camera available.", "OK"); return; } MediaFile file = await Media.TakeVideoAsync(new StoreVideoOptions { Directory = "Sample", Name = "test.mp4" }); if (file != null) { ImageSource imageSource = ImageSource.FromStream(() => { var stream = file.GetStream(); return stream; [ 494 ]
Chapter 6 }); Debug.WriteLine("Video File Path: {0}", file.Path); file.Dispose(); } }
14. The app is now ready to take photos and videos. With this plugin, your Xamarin app is created easily. Compile the code and test it. You need an Android emulator that supports a camera or a physical device, an iOS device, and the Windows Phone emulator or a device to make sure it will work. iOS:
[ 495 ]
One for All and All for One
Windows Phone:
How it works…
You don't have to reinvent the wheel or write custom code with renderers for every case. In this recipe, with the use of Xam.Plugin.Media, we can take or choose photos easily with minimal code. First, you install the Xam.Plugin.Media NuGet package and add the required permissions for Android and the capabilities in Windows Phone, and then you register the dependency in every native platform. iOS has required permissions but it is automatically requested in code from the plugin implementation. The user has to only allow the use of the camera at that point of action.
[ 496 ]
Chapter 6
In our simple XAML user interface, we have two buttons: one for taking a photo and one for recording a video. In the behind-code OnTakePhotoButtonClicked event handler, we check if a camera is available and taking a photo is supported and invoke the IMedia. takePhotoAsync method passing a StoreCameraMediaOptions instance setting the photo directory and filename. When the user returns to the page, we create an ImageSource from the returned MediaFile and the method GetStream and assign it to the Image.Source view property. Taking a video uses the exact same steps, only we check the property IsTakeVideoSupported and invoke the IMedia.TakeVideoAsync method passing a StoreVideoOptions instance. This returns a MediaFile, which we can use to get the stream. There are two ways to access the plugin: from the static CrossMedia.Current property or registering the interface and program against it. To keep the best practices of cross-platform mobile development, we program against the interface.
There's more…
We have demonstrated how to take photos. You can also choose photos from the device library. If you examine the IMedia signature or go to the GitHub Xam.Plugin.Media source code (see the following link), you can find out the IsPickPhotoSupported, IsPickVideoSupported, PickPhotoAsync, and PickVideoAsync methods. It is repetitive code like our example, only replacing the conditional checks and the method call.
See also
• https://github.com/jamesmontemagno/Xamarin.Plugins/tree/master/ Media
Getting the GPS location
Getting and monitoring the GPS location for each platform is a common capability. Every smartphone in the market right now has a GPS sensor built in, but each OS has its own native APIs to retrieve the position. The good news is that you don't have to create a plugin as there is one already created by James Montemagno from Xamarin that you can freely use.
[ 497 ]
One for All and All for One
How to do it…
1. Create a Blank App (Xamarin.Forms Portable) cross-platform mobile solution named XamFormsQueryGps in Visual Studio with File | New | Project…. 2. Right-click all the projects one by one and choose Manage NuGet Packages to search and install Xam.Plugin.Geolocator. 3. Go to the XamFormsQueryGps portable library and open the App.cs file; add the following code to the file: Label gpsLabel; public App() { IGeolocator locator = DependencyService.Get(); locator.DesiredAccuracy = 50; locator.AllowsBackgroundUpdates = true; locator.StartListening(1, 1, true); locator.PositionChanged += LocatorPositionChanged; Button button = new Button { Text = "Get Position!" }; button.Clicked += async (sender, e) => await GetCurrentLocationAsync(locator); gpsLabel = new Label { Text = "GPS Coordinates" }; // The root page of your application MainPage = new ContentPage { Content = new StackLayout { VerticalOptions = LayoutOptions.Center, Children = { gpsLabel, button } }
[ 498 ]
Chapter 6 }; } async Task GetCurrentLocationAsync(IGeolocator locator) { Position position = await locator.GetPositionAsync(10000); PrintPosition(position); } private void LocatorPositionChanged(object sender, PositionEventArgs e) { PrintPosition(e.Position); } private void PrintPosition(Position position) { gpsLabel.Text = string.Format( "Time: {0} \n" + "Lat: {1} \n" + "Long: {2} \n " + "Altitude: {3} \n" + "Altitude Accuracy: {4} \n" + "Accuracy: {5} \n " + "Heading: {6} \n " + "Speed: {7}", position.Timestamp, position.Latitude, position.Longitude, position.Altitude, position.AltitudeAccuracy, position.Accuracy, position.Heading, position.Speed); }
4. In the Android project, we have to add two permissions. Right-click XamFormsQueryGps.Droid and choose Properties. In the required permissions section, add the following: °° ACCESS_COARSE_LOCATION °° ACCESS_FINE_LOCATION With Android 6.0 Marshmallow, you will have to ask for Fine and Course Location runtime permission: https://blog.xamarin.com/ requesting-runtime-permissions-in-android-marshmallow/.
[ 499 ]
One for All and All for One
5. Open MainActivity.cs and after the Forms.Init method call in the OnCreate method, register the GeolocatorImplementation type. DependencyService.Register();
6. For the iOS to support background updates, meaning we will receive a notification even if our application is not currently in the foreground, go to XamFormsQueryGps.iOS. We need to make some changes in the Info.plist file. Double-click the file, go to the Background Modes tab section, check Enable Background Modes and check the Location update too.
7. We next need to add two new keys to the Info.plist file. If you are using Xamarin Studio, this can be achieved by double-clicking the Info.plist file, selecting Advanced and then selecting the add new key icon. If you are using Visual Studio, you can edit the file using a text editor (such as notepad) or the built-in source code editor. To do this, right-click the Info.plist file and choose Open With…, select Source Code (Text) Editor With Encoding and click OK. Add the following key value pairs inside the tag: NSLocationWhenInUseUsageDescription Need Location! NSLocationAlwaysUsageDescription Need Location Always!
[ 500 ]
Chapter 6
8. Go to the AppDelegate.cs file and after the Forms.Init method call in the FinishedLaunching method, add the following dependency registration: DependencyService.Register();
9. In the Window Phone project, expand the Properties folder, double-click the WMAppManifest.xml file, go to the Capabilities section tab and check the ID_CAP_LOCATION capability. 10. Open the MainPage.xaml.cs file and after the Forms.Init method call in the constructor, add the dependency registration of the plugin. DependencyService.Register();
11. Run the application to the simulator or device and you will start receiving updates. Pressing the Get Location button will refresh the values as well. With Xamarin Android player, you can tap the settings icon and change the location. With iOS simulator, you can go to the Debug menu and in Location, choose a location; in the Windows Phone emulator, you are able to change the location by expanding the tools. iOS:
[ 501 ]
One for All and All for One
Android:
[ 502 ]
Chapter 6
Windows Phone:
How it works…
Each plugin has a GeolocatorImplementation class. In that file, the plugin utilizes the platform native API to provide location updates. The majority of Xamarin plugins that you will see follow the Bait and Switch technique. To learn how to create Xamarin cross-platform plugins, go to the section Creating cross-platform plugins. The Android platform is using LocationManager, in iOS the CLLocationManager API, and Windows Phone GeoCoordinateWatcher. Of course, the full implementation has more detail. You can refer to the source code and explore how each platform works. [ 503 ]
One for All and All for One
We can access the plugin through a static property, the CrossGeolocator.Current property, but as a best practice we register the dependency implementation class and request the interface from the core PCL library using DependencyService. Having an IGeolocator interface implementation instance, we set DesiredAccuracy to 50. DesiredAccuracy provides more granularity and control of the accuracy of the position results; the higher the value, the more your application will require the most accurate data available. Since we want to allow background updates, we set AllowsBackgroundUpdates to true, invoke the StartListening method, and register an event handler for the PositionChanged event. Our UI is very simple, with a Label showing the GPS position details when the LocatorPositionChanged event handler is invoked or if you request it when clicking the Get Position! button
See also
• https://github.com/jamesmontemagno/Xamarin.Plugins/tree/master/ Geolocator
Show and schedule local notifications
Notifications are everywhere in today's mobile applications. In our example, for this recipe we will explore how to use a cross-platform NuGet package and minimize the amount of code to show local notifications to the user.
How to do it…
1. Open Visual Studio and create a Blank App (Xamarin.Forms Portable) cross-platform solution from the top menu, File | New | Project…, named XamFormsLocalNotifications. 2. For each native platform project, right-click and choose Manage NuGet Package; search and install the Xam.Plugins.Notifier NuGet package and click Install. 3. Go to the App.cs file in the XamFormsLocalNotifications class library and replace the constructor with the following code: public App() { ILocalNotifications localNotifications = DependencyService.Get(); Button showNotificationButton = new Button(); showNotificationButton.Text = "Show Local Notification"; [ 504 ]
Chapter 6 showNotificationButton.Clicked += (sender, e) => localNotifications.Show("Test", "Local notification alert", 1); Button cancelNotificationButton = new Button(); cancelNotificationButton.Text = "Cancel Local Notification"; cancelNotificationButton.Clicked += (sender, e) => localNotifications.Cancel(1); MainPage = new ContentPage { Content = new StackLayout { VerticalOptions = LayoutOptions.Center, Children = { showNotificationButton, cancelNotificationButton } } }; }
4. Open MainActivity.cs in the XamFormsLocalNotifications.Droid project and register the plugin implementation dependency after the Forms. Init method call. DependencyService.Register();
5. Go to AppDelegate.cs in the XamFormsLocalNotifications.iOS project and in the FinishedLaunching method, add the same dependency plugin registration from step 5 after the Forms.Init method call. 6. For the iOS platform, an extra step is required to request access for showing notifications. In AppDelegate.cs, add the following code in the FinishedLaunching method: var settings = UIUserNotificationSettings.GetSettingsForTypes( UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound, new NSSet()); UIApplication.SharedApplication.RegisterUserNotificationSettings(s ettings);
[ 505 ]
One for All and All for One
Run the applications and press the button Show Local Notification to show a notification. iOS:
[ 506 ]
Chapter 6
Android:
How it works…
Two lines of code and the local notification APIs for each platform are available at your command! Starting from the cross-platform UI, we retrieved the ILocalNotifications interface implementation and created two buttons: one to show a notification and one to cancel it. The ILocalNotifications.Show method accepts three arguments: title, description, and an ID. To cancel a notification, by invoking the ILocalNotifications.Cancel method, the ID has to match. [ 507 ]
One for All and All for One
There's more…
Xam.Plugins.Notifier only supports Windows Phone 8.1+. By default, at the time
of writing this module, creating a Visual Studio Xamarin.Forms solution will target the Windows Phone Silverlight 8.0 version. Follow the next steps to upgrade to Windows Phone Silverlight 8.1 and add local notification:
1. Right-click the XamFormsLocalNotifications.WinPhone project and select Retarget to Windows Phone 8.1.
2. System.Windows.Interactivity has to be included after this change to the project. Right-click and select Add Reference, go to the section Assemblies | Extensions, check the desired library and click OK.
[ 508 ]
Chapter 6
3. Right-click the project and select Manage NuGet Packages. Click the Browse tab and search for Xam.Plugins.Notifier. Select it and click the Install button. 4. Open MainPage.xaml.cs. After the global::Xamarin.Forms.Init() method, add the registration of the LocalNotificationsImplementation Windows Phone platform implementation. 5. DependencyService.Register(); 6. Run the Windows Phone 8.1 application and click the Show Local Notification button. You will receive a message in the notification bar and on the top of the screen.
[ 509 ]
Bind to the Data In this chapter, we will cover the following recipes: • Binding data in code • Binding data in XAML • Configuring two-way data binding • Using value converters
Introduction
As a mobile developer, you create different types of applications and the majority will interact with data; they are data-driven. These apps display and manipulate data from a source, local database, filesystem, or from a remote server. From any type of persistent storage, you create classes that represent the data and in many cases transform and present them to a view. In Xamarin applications, the MVVM pattern is a common approach to design such applications. To learn more about this pattern, refer to Chapter 5, Dude, Where's my Data? and Chapter 4, Different Cars, Same Engine. How are these data values presented to the view and also pushed back to the source (ViewModel, Model) when a value is changed? Traditionally, you might set the value to the UI control property and register to a value-changed event of the control to set the new value back to the model property. This might not sound terrifying, but when your application starts growing, maintenance and business logic changes can become a nightmare, and a small change will result in a chain of refactoring changes. Data binding solves this problem with an intermediary object that will handle updating the value between the source (your model public property) and the target (a UI control property), creating a loose coupling between the components.
[ 511 ]
Bind to the Data
Binding data in code
This recipe introduces the concept of data binding: binding properties of an object (source) to the view controls (target) in the behind code of a page.
How to do it…
1. Start by creating a Visual Studio Blank App (Xamarin.Forms Portable) solution. In the top menu, click File | New | Project… and give it the name XamFormsCodeBinding. 2. In the XamFormsCodeBinding PCL library, right-click and choose Add | Class…; give it the name Person.cs and click Add. 3. Copy the following code to add two simple properties in Person.cs: public class Person { public string FirstName { get; set; } public string LastName { get; set; } }
4. Right-click XamFormsCodeBinding PCL and Add | New Item…; choose Forms Xaml Page, name it MainPage.xaml, and click Add. In the Content tag, add the following code that adds two Label controls:
5. Go to MainPage.xaml.cs and change the constructor with the following code to bind a Person class instance: public MainPage(Person person) { InitializeComponent(); Binding firstNameBinding = new Binding { Path = "FirstName", Source = person }; firstNameLabel.SetBinding(Label.TextProperty, firstNameBinding); Binding lastNameBinding = new Binding { Path = "LastName", [ 512 ]
Chapter 7 Source = person }; lastNameLabel.SetBinding(Label.TextProperty, lastNameBinding); }
6. Go to the App.cs constructor and change the code like the following; change the FirstName and LastName properties with your own information: public App() { Person person = new Person { FirstName = "George", LastName = "Taskos" }; MainPage = new MainPage(person); }
7. Ready to go! Running the application will present your name in the center of the screen. See the following iOS platform screenshot: iOS:
[ 513 ]
Bind to the Data
How it works…
To create a binding, we need three pieces: • the Source • the Path • the Target The Source can be any object, the Path is any public property of the object, and the Target has to be a BindableProperty in a BindableObject to bind with the source path. In our example, the Source (a Person class instance) binds to two Label controls, the Target, and in the MainPage.xaml.cs behind code we create the Binding setting the Source and the Path; the Path is represented as a string. In runtime, Xamarin.Forms will use reflection to read the property value. Τhe source Path can be expressed in other ways too. If you want to bind to a navigation child property then you use NavigationProperty.Child. If you have a dictionary Property[Key], with an array you use the Property[Index] syntax and there is the .binding path that references the object directly; in the recipe example, . will reference the person instance set as the Source and invoke the ToString() method for the presentation value. The SetBinding method on the Label element associates our binding setup. We must set the Target property, which you can find as a static BindableProperty on the element class, and pass the binding we created.
Binding data in XAML
This recipe introduces the concept of data binding some object instance properties to view controls in XAML.
How to do it…
1. Start by creating a Visual Studio Blank App (Xamarin.Forms Portable) solution. In the top menu, click File | New | Project… and give it the name XamFormsXamlBinding. 2. In the XamFormsXamlBinding PCL library, right-click and choose Add | Class…; give it the name Person.cs and click Add.
[ 514 ]
Chapter 7
3. Copy the following code to add two simple properties in Person.cs: public class Person { public string FirstName { get; set; } public string LastName { get; set; } }
4. Right-click the XamFormsCodeBinding PCL and Add | New Item…; choose Forms Xaml Page, name it MainPage.xaml, and click Add. In the Content tag, add the following code that adds two Label controls to bind to Person properties directly in XAML:
5. Go to MainPage.xaml.cs and change the constructor with the following code to set a Person class instance to the BindingContext property: public MainPage(Person person) { InitializeComponent(); BindingContext = person; }
6. Go to the App.cs constructor and change the code to the following; change the FirstName and LastName properties with your own information: public App() { Person person = new Person { FirstName = "George", LastName = "Taskos" }; MainPage = new MainPage(person); }
[ 515 ]
Bind to the Data
7. Run the application in your Android emulator or device. See the following screenshot: Android:
How it works…
In XAML, Xamarin.Forms simplified how we bind our UI controls to a source instance object with the {Binding} expression and with the help of BindingContext. The binding expression will create a Binding object and assign the required association. In our labels, the binding expressions assigned to the property Text, which is the Target property, and the Path are FirstName and LastName. XAML knows the source object instance because we set BindingContext. [ 516 ]
Chapter 7
A lot of times, you will have to bind an object to a page and create bindings for some properties and children of the instance. That would require you to create a lot of binding objects and assign them to the elements, but the Page and the UI controls classes have a shortcut instance property, BindingContext, which you can use to define the source of the binding in the level you want. In our case, we bind to the page level a Person instance, which makes our expressions search BindingContext and set the FirstName and LastName properties to the Target property.
Configuring two-way data binding
Binding data to a view via code or XAML is easy; nothing special is needed to present data from an object to the view. Binding a POCO class will only give you that though. Updating the source object and expecting to reflect the change to the target view or trying to push the values changed by the user from the target to the source will not work out of the box.
How to do it…
1. Start by creating a Visual Studio Blank App (Xamarin.Forms Portable) solution. In the top menu, click File | New | Project… and give it the name XamFormsBindingModes. 2. In the XamFormsBindingModes PCL library, right-click and choose Add | Class…; give it the name Person.cs and click Add. 3. We will create an extension method extending the PropertyChangedEventHandler delegate handler type. Right-click the portable class library again and choose Add | Class…; name the class PropertyChangedEventHandlerExtensions.cs and click Add. 4. Make the class static and add the following three helper extension methods: public static class PropertyChangedEventHandlerExtensions { public static void Raise(this PropertyChangedEventHandler handler, object sender, Expression propertyExpression) { var body = propertyExpression.Body as MemberExpression; if (body == null) throw new ArgumentException("'propertyExpression' should be a member expression"); var expression = body.Expression as ConstantExpression; if (expression == null) [ 517 ]
Bind to the Data throw new ArgumentException("'propertyExpression' body should be a constant expression"); handler.Raise(sender, body.Member.Name); } public static void Raise(this PropertyChangedEventHandler handler, object sender, params Expression[] propertyExpressions) { foreach (var propertyExpression in propertyExpressions) { handler.Raise(sender, propertyExpression); } } public static void Raise(this PropertyChangedEventHandler propertyChangedHandler, object sender, [CallerMemberName] string propertyName = "") { var handler = propertyChangedHandler; if (handler != null) { handler(sender, new PropertyChangedEventArgs(propertyName)); } } }
5. Go to the Person.cs class and implement the INotifyPropertyChanged interface. We will add an extra method utilizing the PropertyChangedEventHandlerExtensions class we created earlier. private string _firstName; public string FirstName { get { return _firstName; } set { SetFieldAndRaise(ref _firstName, value, () => FirstName, () => FullName); } [ 518 ]
Chapter 7 } private string _lastName; public string LastName { get { return _lastName; } set { SetFieldAndRaise(ref _lastName, value, () => LastName, () => FullName); } } public string FullName { get { return string.Format("{0} {1}", FirstName, LastName); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual bool SetFieldAndRaise(ref T field, T value, params Expression[] propertyExpressions) { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; PropertyChanged.Raise(propertyExpressions); return true; }
6. Right-click the XamFormsBindingModes PCL and choose Add | New Item…. Select Xaml Forms Page, give it the name MainPage.xaml, and click Add. 7. Double-click MainPage.xaml and add the following code inside the ContentPage tag:
0, 20, 0, 0 [ 519 ]
Bind to the Data
0, 0, 0, 0
0, 0, 0, 0
8. Go to the MainPage.xaml.cs code-behind file and change the constructor to accept a Person parameter and setting the BindingContext. public MainPage(Person person) { InitializeComponent(); BindingContext = person; }
9. In App.cs, change the constructor to the following code: public App() { // The root page of your application MainPage = new MainPage(new Person()); }
10. Run the application, insert your first and last name, and you should see the full name label updating on the property has changed.
[ 520 ]
Chapter 7
Android:
[ 521 ]
Bind to the Data
iOS:
[ 522 ]
Chapter 7
Windows Phone:
How it works…
The important piece needed to accomplish two-way data binding is implementing the INotifyPropertyChanged interface and enable two-way binding mode scenarios. The INotifyPropertyChanged interface has only one member, an event of delegate type PropertyChangedEventHandler named PropertyChanged. As the name implies, this event should be raised when a property value is changed and any component is registered to this event will be notified.
[ 523 ]
Bind to the Data
In this recipe, we took an extra step in the name of best practices and created an extension class for the PropertyChangedEventHandler type. The extension class has three methods, all named Raise but with different signatures. The first Raise extension method accepts the sender that the event is raised and an Expression parameter that we use to extract from the expression body the property name; with this practice, we avoid typos that may easily occur when dealing with string values. The second method is a helper method that accepts the sender and a params of Expression array iterating for each expression and call the first overload to extract the property name, which in turn calls the Raise overload that actually raises the event. This is very handy in cases like this recipe where we need to raise the PropertyChanged event for more than one property, FirstName and FullName. Creating the extension methods makes our code reusable. You can have one or more base classes that implement INotifyPropertyChanged and use the methods to raise the PropertyChanged event easily using expressions for each property name. In the Person class, we implement INotifyPropertyChanged, and add the protected virtual generic method SetFieldAndRaise so that you can access it from derived classes and also provide your own version if needed. This is a practice that you can transfer to your base classes and all derived models/viewmodels can invoke in the properties setters. The XAML code in the MainPage.xaml page is simple. We use OnPlatform in StackLayout.Padding to set 20 points from the top in the iOS platform. If we leave it at 0, our UI controls will start from the very top edge of the device screen and the status bar will overlap with our controls. We continue by setting the binding expressions for the Entry controls and also set the Mode property of Binding to TwoWay. For input controls such as Entry, this is the default value, but for code clarity and demonstration purposes we explicitly set it. For Label, the default value is OneWay, meaning one-way updates from the source to the target; we omitted setting it explicitly. Last but not least, we need to set BindingContext to a Person instance passed as a parameter in the default constructor, one that we create in the App.cs constructor. This chapter focuses on data binding using a custom object class instance as a source, but with binding you can do more things in XAML. For example, View-To-View binding binds a slider's value property to a label's text property, or create animations while the value is changed. You can have bindings to more than one view's properties, just by adding a binding expression for each property of the view. [ 524 ]
Chapter 7
BindingContext is available in the page level or you can set it up in the element level. An example would be to have an object BindingContext set in the page level and then set an object's property child to BindingContext of an element in the page.
There are also other ways to set BindingContext. One is using StaticResource or the x:Static markup extension.
See also
• https://developer.xamarin.com/guides/cross-platform/xamarinforms/user-interface/xaml-basics/data_binding_basics/
Using value converters
Often when we consume data from the network or a local database, the values we retrieve aren't always in the appropriate format for user presentation, such as dates as we demonstrate in this recipe example. You have a property type of DateTime, and you want to display it to the user's screen in a detailed form such as MM/dd/yyyy HH:mm:ss.fff. To achieve this in the following recipe, we are using a value converter.
How to do it…
1. Create a Visual Studio Blank App (Xamarin.Forms Portable) solution named XamFormsValueConverter, from the top menu File | New | Project…. 2. Right-click the XamFormsValueConverter portable class library and choose Add | Class…; name it NotifyPropertyChangedExtension.cs and click Add. 3. Make the newly created class static and add the contents like the following: public static class NotifyPropertyChangedExtension { public static void Raise(this PropertyChangedEventHandler handler, object sender, Expression propertyExpression) { var body = propertyExpression.Body as MemberExpression; if (body == null) throw new ArgumentException("'propertyExpression' should be a member expression"); var expression = body.Expression as ConstantExpression; if (expression == null) [ 525 ]
Bind to the Data throw new ArgumentException("'propertyExpression' body should be a constant expression"); handler.Raise(sender, body.Member.Name); } public static void Raise(this PropertyChangedEventHandler handler, object sender, params Expression[] propertyExpressions) { foreach (var propertyExpression in propertyExpressions) { handler.Raise(sender, propertyExpression); } } public static void Raise(this PropertyChangedEventHandler propertyChangedHandler, object sender, [CallerMemberName] string propertyName = "") { var handler = propertyChangedHandler; if (handler != null) { handler(sender, new PropertyChangedEventArgs(propertyName)); } } }
4. Right-click the PCL core library again and Add | Class… to create a model class named Order.cs and click Add. Insert the following code: public class Order : INotifyPropertyChanged { private DateTime _dateOrdered; public DateTime DateOrdered { get { return _dateOrdered; } set { SetFieldAndRaise(ref _dateOrdered, value); } [ 526 ]
Chapter 7 } public event PropertyChangedEventHandler PropertyChanged; protected virtual bool SetFieldAndRaise(ref T field, T value, [CallerMemberName] string propertyName = "") { if (EqualityComparer.Default.Equals(field, value)) return false; field = value; PropertyChanged.Raise(this, propertyName); return true; } }
5. Right-click to add another class, Add | Class…, named DateTimeToStringConverter.cs and click Add. Add the following code to convert the values when the source to target or target to source value changed is triggered: public class DateTimeToStringConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { DateTime dateValue = (DateTime)value; string stringDate = dateValue.ToString("MM/dd/yyyy HH:mm:ss.fff", CultureInfo.InvariantCulture); Debug.WriteLine("DateTime to string: {0}", stringDate); return stringDate; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { DateTime dateValue; if (!DateTime.TryParse(value as string, out dateValue)) { throw new NotSupportedException(); } Debug.WriteLine("string value to DateTime: {0}", dateValue.ToString()); return dateValue; } } [ 527 ]
Bind to the Data
6. Right-click the core PCL again, Add | New Item…, choose Xaml Forms Page, name it MainPage.xaml. and click Add. Find the contents of the page next:
7. Go to MainPage.xaml.cs and change the constructor to the following code: public MainPage(Order order) { InitializeComponent(); BindingContext = order; }
8. Open the App.cs file and change the constructor to the following code instantiating our newly created MainPage: public App() { MainPage = new MainPage(new Order { DateOrdered = DateTime.Now }); }
[ 528 ]
Chapter 7
9. Run the application and you should immediately see debug messages to the Output windows like the following. You can test and change the value; the messages will appear every time the value is changed from the source and before updating the source. DateTime to string: 12/16/2015 21:16:09.807 … string value to DateTime: 12/16/2015 9:16:09 PM
Android:
[ 529 ]
Bind to the Data
iOS:
[ 530 ]
Chapter 7
Windows Phone:
How it works…
If you come from the .NET world to the Xamarin.Forms world, you already must have noticed that there's no difference in the way you use value converters and data binding. The crucial piece of the puzzle is a class implementing the IValueConverter interface. For this recipe, we created an Order class with only one property, DateOrdered, of type DateTime. We implemented the INotifyPropertyChanged interface and added a helper method to raise property-changed events. For more details on how to do this and how it works, refer to the section Configuring two-way data binding.
[ 531 ]
Bind to the Data
To get access in the flow of the interaction between the source and the target when a value-changed event is raised, and the binding is triggered with the new value, we created the class DateTimeToStringConverter. This class implements the IValueConverter interface and will act as a proxy between the source and the target. There are two methods we need to implement: • Convert • ConvertBack The Convert method will be invoked when the value is updated in the source, in our example when the DateOrdered property is changed. When the value is returned from the method, it will be assigned to the element property. This is where we convert the DateTime property to a string with the format MM/dd/yyyy HH:mm:ss.fff. The ConvertBack method is invoked when the target value is changed and will update the source, changing the element's value, will invoke this method before assigning the value to the source. In both of the methods, we added debug messages to monitor this behavior in the output window. To use the DateTimeToStringConverter class in our MainPage.xaml, we create a resource in the ContentPage.Resources ResourceDictionary. Don't forget that we need to import the namespace that the class lives in to make XAML aware of the class. To complete the pieces, we assign the static resource to the Converter property of the binding expression of the Entry element. The last part is standard, setting the BindingContext to an Order instance in the MainPage constructor passed from the App.cs constructor. To learn more about BindingContext, go to the section Configuring two-way data binding. Using an IValueConverter implementation is not the only way to achieve formatting. You can use the StringFormat property available in the Binding expression. Check the following example code for how to do this:
[ 532 ]
Chapter 7
See also
• https://developer.xamarin.com/guides/cross-platform/xamarinforms/user-interface/xaml-basics/data_bindings_to_mvvm/
[ 533 ]
A List to View In this chapter, we will cover the following recipes: • • • •
Displaying a collection and selecting a row Adding, removing, and refreshing items Customizing the row template Adding grouping and a jump index list
Introduction
99 percent of applications are using a control that you can scroll and view a collection of data. This collection of data, sometimes more than one type, can be represented in many ways to show the corresponding information. In this chapter, we will see how the Xamarin.Forms ListView control works with our collections. A collection is a stream of data and the ListView control in Xamarin.Forms helps significantly with how we present the data to the user in a vertical scrollable area, and enable actions such as adding, removing, or editing rows. This is certainly one of the most important controls that you will spend a lot of time on during the development of any project. The Xamarin.Forms ListView is translated in the native control for each platform: a UITableView for iOS, a ListView for Android, and a LongListSelector for the Windows Phone platform. We will investigate the simple setup of a ListView setting a collection and its default layout; how to select, add, and remove a row; and the out-of-the-box pull-to-refresh feature. Next, we take a step further and see how to customize a row using one of the built-in cell types and also provide your complete cell layout using the ViewCell type. Finally, we explore features such as as grouping and the jump list supported by the iOS platform only. [ 535 ]
A List to View
Displaying a collection and selecting a row
The main requirement for ListView to work is to provide a collection of data where each item will be translated to a row. ListView exposes an event that will notify you when a row is tapped; usually, you'll
want to register an event handler to navigate to another page.
How to do it…
1. In Visual Studio, go to the top menu and select File | New | Project, choose the Blank App (Xamarin.Forms Portable) template, name it XamFormsDisplayCollections, and click OK. 2. The first thing we need is to create our model class and some dummy data to load in ListView. Right-click the PCL, Add | Class…, name it Character, and click Add. Find next the abbreviated version of the Character class; refer to the module code for this section for the list of characters returned in the Characters property: public class Character { public string Name { get; set; } public string Species { get; set; } public string ImageUrl { get; set; } public override string ToString() { return string.Format("{0}, {1}", Name, Species); } public static IList Characters { get { return new List { new Character { … }, ……. }; } } } [ 536 ]
Chapter 8
3. Right-click again and select Add | New Item…. Choose Forms Xaml Page, name it MainPage.xaml, and click Add. Find the contents of the newly created page next:
4. Repeat step 3 and create a page named CharacterPage. Find the contents next:
5. Go to CharacterPage.xaml.cs and change the constructor like the following code: public CharacterPage(Character character) { InitializeComponent(); BindingContext = character; }
6. Go to MainPage.xaml.cs and add the OnItemTapped event handler. private async void OnItemTapped(object sender, ItemTappedEventArgs args) { Character character = args.Item as Character; await Navigation.PushAsync(new CharacterPage(character)); } [ 537 ]
A List to View
7. Go to the App.cs file and change the constructor like the following code: public App() { // The root page of your application MainPage = new NavigationPage(new MainPage()); }
8. Run the application to all the platforms and may the force be with you! iOS:
[ 538 ]
Chapter 8
Android:
[ 539 ]
A List to View
Tapping a row will navigate you to CharacterPage showing the character's name in the center of the page. Windows Phone:
How it works…
To set up a ListView and show some data in its simplest form like in our recipe doesn't need more than a few lines of code. All you need is to set a list of some type to the ListView.ItemsSource property. There are a couple of ways to set the list to ItemsSource, and in this example we used the x:Static XAML extension and data binding to bind an IList of some Star Wars characters.
[ 540 ]
Chapter 8
For simplicity, we created a static property that returns the list. With the help of the x:Static XAML extension, we retrieved the data and set it to BindingContext of MainPage in XAML; convenient in our case but in a real-world scenario you would create a data repository, fetch the data in ViewModel, and bind the list property of ViewModel on ListView.ItemsSource. Please refer to Chapter 5, Dude, Where's My Data? and Architecture design with Model-View-ViewModel (MVVM) pattern recipe from Chapter 4, Different Cars, Same Engine to learn how to apply these practices. Having a list set to BindingContext, we can access the data with a Binding expression on the ListView.ItemsSource property. To learn more about data binding, go to Chapter 7, Bind to the Data. Without further row customization, the default cell will invoke the ToString() method of the list item type we overridden this method and returned a string representation of Name, Species. Finally, to navigate to CharacterPage, we wired up an event handler for the ListView.ItemTapped event. When this method is invoked, we have access to the current item that is tapped in the ItemTappedEventArgs method argument. With that, we cast the ItemTappedEventArgs.Item property to a Character and pass it to CharacterPage where we navigate using the Navigation.PushAsync method. Using the MVVM pattern, the ItemTapped event will not be a great help; fortunately, ListView exposes the SelectedItem property that you can programmatically get and set a selected item. You could use binding on the SelectedItem property in XAML and track the selected item of ListView. Don't forget that the Navigation property will be null if we don't wrap MainPage in NavigationPage.
See also
• https://developer.xamarin.com/guides/xamarin-forms/userinterface/listview/data-and-databinding/
Adding, removing, and refreshing items
It is very common that you want to manipulate the data while your application is running; change data on your server, or locally; do calculations; refresh the representation of your data; and allow your user to add items and update or delete an item. This is as easy as working with a collection type in C#. [ 541 ]
A List to View
How to do it…
1. In Visual Studio, go to the top menu and select File | New | Project. Choose the Blank App (Xamarin.Forms Portable) template, name it XamFormsAddingRemovingItems, and click OK. 2. In this recipe, we demonstrate the pull-to-refresh feature added in Xamarin. Forms 1.4. Make sure you have updated the Xamarin.Forms NuGet package for the solution. To do this, right-click the solution and select Manage NuGet Packages for Solution, go to the Updates tab, check the Xamarin.Forms package, and click Install for the latest stable version. 3. Right-click the PCL, Add | Class…, name it Character, and click Add. Find next the abbreviated version of the Character class; refer to the module's code for this section for the list of characters returned in the Characters property: public class Character { private static IList _characters; public string Name { get; set; } public string Species { get; set; } public string ImageUrl { get; set; } public override string ToString() { return string.Format("{0}, {1}", Name, Species); } public static IList Characters { get { return _characters ?? (_characters = new ObservableCollection { new Character { … }, ……. }; } } } [ 542 ]
Chapter 8
4. Right-click again and select Add | New Item…. Choose Forms Xaml Page, name it MainPage.xaml, and click Add. Find the contents of the newly created page next:
[ 543 ]
A List to View
5. Go to MainPage.xaml.cs and add the OnToolbarClick, OnRefreshing, and OnDelete event handlers. private void OnToolbarClick(object sender, EventArgs args) { if (Character.Characters.SingleOrDefault(p => p.Name.Equals("Anakin Skywalker")) == null) { Character.Characters.Add(new Character { Name = "Anakin Skywalker", Species = "Human", ImageUrl = "http://static.comicvine.com/uploads/ original/11125/111250671/4775035-a1.jpg" }); } } private void OnDelete(object sender, EventArgs args) { MenuItem menuItem = sender as MenuItem; Character character = menuItem.BindingContext as Character; Character.Characters.Remove(character); } private async void OnRefreshing(object sender, EventArgs args) { ListView listView = sender as ListView; listView.IsRefreshing = true; // Code to refresh ... // Called on the UI thread. Debug.WriteLine("Refreshing!"); await Task.Delay(2000); listView.IsRefreshing = false; }
[ 544 ]
Chapter 8
6. In the App.cs file, change the constructor with the following code: public App() { // The root page of your application MainPage = new NavigationPage(new MainPage()); }
7. Run the application in your Android emulator or device. Click the ADD toolbar button and Anakin Skywalker will be added to ListView. Android:
[ 545 ]
A List to View
8. We have added a destructive MenuItem with the text Delete in the TextCell.ContextActions. In iOS, if you swipe the row to the left, it will show the button to delete the row; in Android and Windows Phone, a long tap is required. iOS:
[ 546 ]
Chapter 8
9. Pull ListView down and you will get an indicator that something is happening. See the following Android screenshot: Android:
Note that as of Xamarin.Forms 1.4.3, pull-to-refresh is not supported on Windows Phone 8.1. On Windows Phone 8, pull-to-refresh is not a native platform feature, so an implementation of pull-to-refresh is provided by Xamarin.Forms. Finally, be aware that pull-to-refresh will not work on Windows Phone if all elements in the list can fit on the screen (in other words, if vertical scrolling isn't required).
[ 547 ]
A List to View
How it works…
The static property Characters, in the Character class, returns ObservableCollection. ObservableCollection is a list that provides methods to add and delete items. Whenever you make a change to the collection, it is reflected in ListView; this is possible due to ObservableCollection raising event notifications when the collection is changed with implementing the INotifyCollectionChanged interface. Note that this applies to collection changes and not for any updates of underlying items; for example, updating the properties of a collection item, a Character, will not trigger such a notification. For this, we need to implement the INotifyPropertyChanged interface for the item's class, in our case the Character class.
You might already have noticed that ListView doesn't support any actions manipulating its internal list of items. Instead, you always work directly with the underlying data source bind to the ItemsSource property. In the recipe, we are using data binding, setting the Character.Characters in the BindingContext property of MainPage in XAML with the x:Static XAML extension, and using the {Binding} expression for the ListView.ItemsSource property binding to the actual BindingContext object. To learn more about data binding, go to Chapter 7, Bind to the Data. For more information about the architecture in fetching data and showing to the view, check out Chapter 5, Dude, Where's My Data? and Architecture design with Model-View-ViewModel (MVVM) pattern recipe from Chapter 4, Different Cars, Same Engine. ContentPage has a property of ToolbarItems. Adding a toolbar item will provide
an equivalent action for each platform. We register an event handler named OnToolbarClicked for the Activated event to programmatically add a Character. For Toolbar.Icon, we utilize the handy OnPlatform object to instruct a specific icon for the Windows Phone platform. This is needed due to the default representation of the Windows Phone toolbar icon of an X icon, which misleads the functionality to the user. For the deletion of an item, we have modified the row layout by adding MenuItem that is set as destructive and the text to Delete in the TextCell.ContextActions collection. Providing DataTemplate in ListView.ItemTemplate, we return the built-in TextCell. This DataTemplate will be used for each item in our collection, and for every platform it will provide a way of accessing the action. In iOS, you get the familiar button option when you swipe the row cell to the left; in Android and Windows Phone, you need to long tap the row and the option will appear. [ 548 ]
Chapter 8
For more details on how to customize a row template, refer to the section in this chapter, Customizing the row template. ListView provides a very easy way for the very common pull-to-refresh gesture of a scrolling control. It is as easy as setting IsPullToRefreshEnabled to true and registering an event handler for the Refreshing event in the behind code. In the event handler, you show and hide the indicator by setting the IsRefreshing property of ListView. Note that with the preceding information, in the Windows Phone platform there are version support limitations: refresh will not fire if the collection is not large enough where a vertical scroll is not required and Windows Phone 8.1 is not supported.
There's more…
As a side note, sometimes you might modify a collection from another thread, but this must always be done on the UI thread or you will receive a runtime exception and your application will crash. There are some ways to resolve this issue. Using Task, if a call is initiated asynchronously from the UI thread, for example, you begin to fetch data from the network or a local database, you can define a continuation delegate and when Task is completed, the delegate will be invoked in the synchronization context that it started. When the async/await keywords are used, by default the call returns to the current context that initiated, but you can explicitly define this with the ConfigureAwait(bool) method of the Task object setting the Boolean argument to true, which is the default value. Another way is by using the Device.BeginInvokeOnMainThread(Action) static method and providing an Action that will be invoked on the UI thread. If we needed to do this in our code, the OnToolbarClicked handler would look like the following: Device.BeginInvokeOnMainThread(() => { Character.Characters.Add(new Character { Name = "Anakin Skywalker",
[ 549 ]
A List to View Species = "Human", ImageUrl = "http://static.comicvine.com/uploads/ original/11125/111250671/4775035-a1.jpg" }); });
The binding system also provides a static method where you can pass the collection and a delegate to ensure that your collection is modified on the UI thread. In our solution, you would do something like the following code in the MainPage. xaml.cs constructor: BindingBase.EnableCollectionSynchronization( Character.Characters, null, (list, context, action, writeAccess) => { lock (list) { action(); } });
See also
• https://developer.xamarin.com/guides/xamarin-forms/userinterface/listview/interactivity/
Customizing the row template Xamarin.Forms has four built-in cell types:
• EntryCell – a cell with a Label and a single-line text Entry field • SwitchCell – a cell with a Label and an on/off switch • TextCell – a cell with primary and secondary text • ImageCell – a cell that also includes an image But this is not enough for every case that you need, or sometimes will not provide you with a nice result. Xamarin.Forms has another type, ViewCell, that you can use to customize the layout as desired. You are welcome to extend any of these types to introduce custom functionality.
[ 550 ]
Chapter 8
How to do it…
1. In Visual Studio, go to the top menu and select File | New | Project. Choose the Blank App (Xamarin.Forms Portable) template, name it XamFormsCustomizingRows, and click OK. 2. Right-click the PCL, Add | Class…, name it Character, and click Add. Find next the abbreviated version of the Character class. Refer to this section in the module code for the list of characters returned in the Characters property. public class Character { public string Name { get; set; } public string Species { get; set; } public string ImageUrl { get; set; } public override string ToString() { return string.Format("{0}, {1}", Name, Species); } public static IList Characters { get { return new List { new Character { … }, ……. }; } } }
3. Right-click again and select Add | New Item…. Choose Forms Xaml Page, name it MainPage.xaml, and click Add. Find the contents of the newly created page next:
[ 551 ]
A List to View
ImageCell Text="{Binding Name}" Detail="{Binding Species}" ImageSource ="{Binding ImageUrl}"/>
4. Go to the App.cs file and change the constructor like the following: public App() { // The root page of your application MainPage = new NavigationPage(new MainPage()); }
5. Run the application for each platform. Android:
[ 552 ]
Chapter 8
Windows Phone:
[ 553 ]
A List to View
iOS:
Using the built-in ImageCell, the layout of the image and details look fine on the Android platform, but not so great in Windows Phone, which crops the images, and with misalignment in iOS. This is due to the lack of control in ImageCell.
[ 554 ]
Chapter 8
Fortunately, using the ViewCell type, we can customize the layout and behavior of the row. You can also derive from it or any other cell type and extend the functionality. Let's add ViewCell and customize the layout in XAML. 1. Go to the MainPage.xaml file and replace the ImageCell tag inside DataTemplate with the following contents:
2. In ListView, add the following property to set the height of the row: RowHeight="60"
3. Replace the App.cs constructor with the following code: public App() { // The root page of your application MainPage = new NavigationPage(new MainPage()); }
[ 555 ]
A List to View
4. Run the application and you will notice that for each platform, the results are much better. It's exactly what we instructed inside ViewCell. See the following iOS screenshot: iOS:
How it works…
In order to customize the visual appearance of the ListView rows, you need to provide an ItemTemplate of type DataTemplate. DataTemplate must always return a Cell. Presenting for each Star Wars character a row with an image, the name, and the species can be easily supported with the out-of-the-box ImageCell type. In ImageCell, we bind to the properties of a single item of the ItemsSource underlying data source. Text property is assigned to Name, Detail to Species, and ImageSource to the ImageUrl properties. [ 556 ]
Chapter 8
Working with the built-in cell types is perfect for simple customization but not always a good fit for our needs. In our solution, we load some images from some Internet URLs regarding each Star Wars character. Each image size is not equal, so for every platform it will behave differently by default. Android has a descent resizing feature, Windows Phone crops images, and iOS resizes the image and messes the alignment. The solution to this problem is not to use ImageCell, but the ViewCell type, which allows us to return a single view. In our case, we return a Grid and inside that we have Image and StackLayout that contains two Label controls. We now have full control over the layout representation of the row with respect to resizing and layout. For example, note the Image.Aspect property set to AspectFit or Label.FontSize to medium or small. ListView sets RowHeight automatically, but we set the value explicitly. If you don't set RowHeight, ListView will go through the data and set the height based on the
highest element that it finds.
You are always welcome to also derive from any built-in cell type and provide the implementation required in code.
There's more…
ListView supports a header and footer via the the properties Header and Footer. You can set these properties or bind them to a string value where it will automatically render a Label with the value on the top and bottom of ListView.
One solution is to provide a View to ListView.Header and ListView.Footer like the following simple example:
That introduces another problem when implementing the MVVM pattern. To learn how to apply the pattern in your project, go to Architecture design with Model-ViewViewModel (MVVM) pattern recipe from Chapter 4, Different Cars, Same Engine. Binding a ViewModel property to ListView.Header is unacceptable because it is violating the pattern principle that ViewModel shouldn't be aware of any Viewrelated code and dependencies; well, the good news is that there is a way to overcome this. [ 557 ]
A List to View
ListView has a property named HeaderTemplate and you can use Binding expressions. BindingContext is what you set to the ListView.Header property;
see the following example:
In this scenario, the DataTemplate returns a View and not a cell type. The {Binding} expression in the Label.Text property inside the DataTemplate corresponds to the ListView.Header value as the BindingContext.
See also
• https://developer.xamarin.com/guides/xamarin-forms/userinterface/listview/customizing-cell-appearance/
Adding grouping and a jump index list
Grouping is essential in many cases. Most often, you will see grouping in a settings page that breaks the rows into categories. It can be applied for anything that makes sense in a parent-child fashion; maybe you want to show grouped orders with their corresponding items. In iOS, we enable the jump list built-in support, which will make visible a small list to the right of the screen that you can tap and navigate to the corresponding index of the collection.
How to do it…
1. In Visual Studio, go to the top menu and select File | New | Project. Choose the Blank App (Xamarin.Forms Portable) template, name it XamFormsAddGrouping, and click OK. 2. Right-click the PCL, Add | Class…, name it Character.cs, and click Add. Find next the abbreviated version of the Character class; refer to this section in the module code for the list of characters returned in the Characters property: [ 558 ]
Chapter 8 public class Character { public string Name { get; set; } public string Species { get; set; } public string ImageUrl { get; set; } public override string ToString() { return string.Format("{0}, {1}", Name, Species); } public static IList Characters { get { return new List { new Character { … }, ……. }; } } }
3. Right-click again and select Add | New Item…. Choose Forms Xaml Page, name it MainPage.xaml, and click Add. Find the contents of the newly created page next:
[ 559 ]
A List to View
4. Right-click in the XamFormsAddGrouping PCL and select Add | Class…. Name it GroupingObservableCollection.cs and click Add. See next the implementation of the newly created class: public class GroupingObservableCollection : ObservableCollection { public K Key { get; private set; } public GroupingObservableCollection(K key, IEnumerable items) : base(items) { Key = key; } }
5. Go to MainPage.xaml.cs and add the following code in the constructor: BindingContext = new ObservableCollection( Character.Characters .OrderBy(c => c.Name) .GroupBy(c => c.Name[0].ToString(), c => c) .Select(g => new GroupingObservableCollection(g.Key, g)));
6. Replace the App.cs constructor with the following code: public App() { MainPage = new NavigationPage(new MainPage()); }
[ 560 ]
Chapter 8
7. Run the application and you will notice that only the iOS platform shows a small list to the right of your screen that you can use to jump to a letter; Android and Windows Phone are not currently supported. iOS:
[ 561 ]
A List to View
Android:
[ 562 ]
Chapter 8
Windows Phone:
How it works…
ListView supports grouping out of the box, setting the IsGroupingEnabled property to true. Additionally, it is needed to set the GroupDisplayBinding
property, which will define the parent group key.
Each item in the list that you will provide has to be a list. In our solution, we applied a practice of defining our custom GroupingObservableCollection, where K is the key type and derives from ObservableCollection. In this way, we can extend the collection and add additional properties like the Key property. The Key property is the parent value and the collection items are the children.
[ 563 ]
A List to View
As an extra feature for iOS users, we bind the GroupShortNameBinding property to the Key property, which will automatically add a small list on the right of UITableView with the key letters available; tapping to a letter will jump directly to the corresponding section. Unfortunately, at the moment this is supported only in iOS. For each row, we provided a custom DataTemplate. For details on how to do this, refer to the recipe Customizing the row template. Finally, in the MainPage.cs constructor, we set the page's BindingContext to ObservableCollection of GroupingObservableCollection. In the constructor of GroupingObservableCollection, we pass IEnumerable ordered by character name and grouped by the first letter of the name using LINQ.
There's more…
Setting GroupDisplayBinding to the key will automatically create a header for each grouping elements as a Label. There is a way that you can customize the header for each section in ListView by setting GroupHeaderTemplate to DataTemplate where you have to return a Cell type.
See also
• https://developer.xamarin.com/guides/xamarin-forms/userinterface/listview/customizing-list-appearance/#Grouping
[ 564 ]
Gestures and Animations In this chapter, we will cover the following recipes: • Adding gesture recognizers in XAML • Handling gestures with native platform renderers • Adding cross-platform animations
Introduction
Mobile devices today are controlled, almost exclusively, by touches. It is the main interaction between the user and the device for input. As a developer, you understand the importance for the success of an application. It's all in the user's hands when it goes from the store to a mobile device, and for a demanding user, any touches or gestures have to be essential and intuitive. Users also value fireworks. They like fancy and crisp apps, the ones that are alive, and how to make an app alive is through animations! It is also, I believe, the reason for the success of the two top platforms today, with the weight more to Apple. After all, the iPhone's success is what made the gesture touch system part of our life; not that this means it was the inventor of it. The touch and animation system for each native platform is significantly different in how it works, from its philosophy to the API. In this chapter, we will explore built-in gesture recognizers, add native platform gesture recognizers and touch events for each platform, and add cross-platform animations.
[ 565 ]
Gestures and Animations
Adding gesture recognizers in XAML
The most common gestures in every mobile platform are tap, pinch, swipe, and long tap. All these can be combined. Maybe you have also encountered applications that use more than one finger; for example, the two-finger swipe. Every platform has a completely different system to handle touches and gestures. I find Apple's philosophy the best architected, and it seems Xamarin.Forms does too. If you are coming from iOS, you will find the same naming of the gesture recognizers. You are able to add multiple gesture recognizers to a UI element. Unfortunately, at the time of this writing there is support for the tap and pinch gestures only, but the Xamarin.Forms team is adding gesture recognizers, as they proved in the framework releases, and so I believe they will support more if not all in the future.
How to do it…
1. In Visual Studio, go to the top menu and select File | New | Project. Choose the Blank App (Xamarin.Forms Portable) template, name it XamFormsGestures, and click OK. 2. Right-click on the solution and select Manage NuGet Packages for Solution. Go to the Updates tab and update the Xamarin.Forms NuGet package to the latest version. 3. Right-click the XamFormsGesture PCL project, select Add | Class…, and choose the Forms Xaml Page template. Give it the name MainPage.xaml and click Add. 4. Replace the XAML code with the following code:
[ 566 ]
Chapter 9
5. Go to MainPage.xaml.cs and copy the following event handlers for the Tapped and PinchUpdated events of the gesture recognizers: private void OnImageTapped(object sender, EventArgs args) { Debug.WriteLine("Image double-tapped!"); } private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e) { Debug.WriteLine("Image pinched!"); }
6. Change the constructor to show the MainPage. public App() { // The root page of your application MainPage = new MainPage(); }
7. Run the application and you should get a page with an Anakin Skywalker image like the following iOS screenshot. 8. Try to tap on the image or simulate the pinch gesture. If you are testing on a simulator, the output console will print similar messages to the following: 2016-01-08 19:22:46.209 XamFormsGesturesiOS[1618:46444] Image double-tapped! 2016-01-08 19:22:51.433 XamFormsGesturesiOS[1618:46444] Image pinched!
[ 567 ]
Gestures and Animations
How it works…
Every UI element has a GestureRecognizers property that you can use to add or remove gesture recognizers. A gesture recognizer will handle the gesture event on the view and invoke your delegate action that you provided. Xamarin.Forms will translate the gesture recognizer to the corresponding platform APIs. For more on how to manually add native platform gestures and touches behavior, go to the Handling gestures with native platform renderers recipe of this chapter.
There's more…
TapGestureRecognizer also supports the number of taps required by setting the property NumberOfTapsRequired.
If your application uses the MVVM pattern and leveraging data-binding you would like to bind your ICommand properties and execute when the gesture is applied. You can easily do this with the Command and CommandParameter properties of the gesture recognizer.
See also
• You might find this commercial plugin that handles more cross-platform gestures useful: http://www.mrgestures.com/
Handling gestures with native platform renderers
Built-in gesture recognizers work well for a simple tap and pinch on a view, but sometimes you need more flexibility and control over the touches behavior.
[ 568 ]
Chapter 9
Xamarin.Forms custom renderers will save the day. Creating a custom renderer for a view will provide you with the native functionality that is needed in runtime. There is a custom renderer for every Xamarin.Forms view. To learn more about how to create custom renderers, refer to Using custom renderers to change the look and feel of views recipe from Chapter 2, Declare Once, Visualize Everywhere. For this section we will create a custom Image class and add custom renderers with native gestures implementation printing messages in the output for testing purposes.
How to do it…
1. In Visual Studio, go to the top menu and select File | New | Project. Choose the Blank App (Xamarin.Forms Portable) template, name it XamFormsRendererGestures and click OK. 2. Right-click the XamFormsRendererGestures PCL and select Add | Class…, name it GestureImage and click Add. The class has to derive from Image; no custom code is required for our example. Find the GestureImage code next: public class GestureImage : Image { }
3. Right-click again and select Add | New Item…. Choose the Forms Xaml Page template, name it MainPage.xaml and click Add. Find the contents of the page next:
[ 569 ]
Gestures and Animations
4. Go to the XamFormsRendererGestures.Droid project, right-click and select Add | Class…. Name the class GestureImageListener.cs and click Add. This will be a SimpleOnGestureListener. public class GestureImageListener : GestureDetector.SimpleOnGestureListener { public override void OnLongPress(MotionEvent e) { Console.WriteLine("OnLongPress"); base.OnLongPress(e); } public override bool OnDoubleTap(MotionEvent e) { Console.WriteLine("OnDoubleTap"); return base.OnDoubleTap(e); } public override bool OnDoubleTapEvent(MotionEvent e) { Console.WriteLine("OnDoubleTapEvent"); return base.OnDoubleTapEvent(e); } public override bool OnSingleTapUp(MotionEvent e) { Console.WriteLine("OnSingleTapUp"); return base.OnSingleTapUp(e); } public override bool OnDown(MotionEvent e) { Console.WriteLine("OnDown"); return base.OnDown(e); } public override bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { Console.WriteLine("OnFling"); return base.OnFling(e1, e2, velocityX, velocityY); }
[ 570 ]
Chapter 9 public override bool OnScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { Console.WriteLine("OnScroll"); return base.OnScroll(e1, e2, distanceX, distanceY); } public override void OnShowPress(MotionEvent e) { Console.WriteLine("OnShowPress"); base.OnShowPress(e); } public override bool OnSingleTapConfirmed (MotionEvent e) { Console.WriteLine("OnSingleTapConfirmed"); return base.OnSingleTapConfirmed(e); } }
5. Right-click again, select Add | Class…, name it GestureImageDroidRenderer.cs and click Add. Copy the implementation from the following code snippet: [assembly: ExportRenderer(typeof(GestureImage), typeof(GestureImageDroidRenderer))] namespace XamFormsRendererGestures.Droid { public class GestureImageDroidRenderer : ImageRenderer { private readonly GestureImageListener _imageGestureListener; private readonly GestureDetector _gestureDetector; public GestureImageDroidRenderer() { _imageGestureListener = new GestureImageListener(); _gestureDetector = new GestureDetector(_imageGestureListener); } protected override void OnElementChanged(ElementChangedEventArgs e) { [ 571 ]
Gestures and Animations base.OnElementChanged(e); if (e.NewElement == null) { GenericMotion -= HandleGenericMotion; Touch -= HandleTouch; } if (e.OldElement == null) { GenericMotion += HandleGenericMotion; Touch += HandleTouch; } } void HandleTouch(object sender, TouchEventArgs e) { _gestureDetector.OnTouchEvent(e.Event); } void HandleGenericMotion(object sender, GenericMotionEventArgs e) { _gestureDetector.OnTouchEvent(e.Event); } } }
6. Run the XamFormsRendererGestures.Droid application. For every gesture interaction on the Jedi image, you will receive messages printed in the output console.
[ 572 ]
Chapter 9
01-08 20:58:32.184 I/mono-stdout( 1730): OnDown 01-08 20:58:32.269 I/mono-stdout( 1730): OnSingleTapUp 01-08 20:58:32.476 I/mono-stdout( 1730): OnSingleTapConfirmed
[ 573 ]
Gestures and Animations
7. Go to the XamFormsRendererGestures.iOS project, right-click, select Add | Class…, name it GestureImageTouchRenderer.cs and click Add. [assembly: ExportRenderer(typeof(GestureImage), typeof(GestureImageTouchRenderer))] namespace XamFormsRendererGestures.iOS { public class GestureImageTouchRenderer : ImageRenderer { UILongPressGestureRecognizer longPressGestureRecognizer; UIPinchGestureRecognizer pinchGestureRecognizer; UIPanGestureRecognizer panGestureRecognizer; UISwipeGestureRecognizer swipeGestureRecognizer; UIRotationGestureRecognizer rotationGestureRecognizer; protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e); longPressGestureRecognizer = new UILongPressGestureRecognizer(() => Console.WriteLine("Long Press")); pinchGestureRecognizer = new UIPinchGestureRecognizer(() => Console.WriteLine("Pinch")); panGestureRecognizer = new UIPanGestureRecognizer(() => Console.WriteLine("Pan")); swipeGestureRecognizer = new UISwipeGestureRecognizer(() => Console.WriteLine("Swipe")); rotationGestureRecognizer = new UIRotationGestureRecognizer(() => Console.WriteLine("Rotation")); if (e.NewElement == null) { if (longPressGestureRecognizer != null) { RemoveGestureRecognizer (longPressGestureRecognizer); } if (pinchGestureRecognizer != null) { [ 574 ]
Chapter 9 RemoveGestureRecognizer(pinchGestureRecognizer); } if (panGestureRecognizer != null) { RemoveGestureRecognizer(panGestureRecognizer); } if (swipeGestureRecognizer != null) { RemoveGestureRecognizer(swipeGestureRecognizer); } if (rotationGestureRecognizer != null) { RemoveGestureRecognizer (rotationGestureRecognizer); } } if (e.OldElement == null) { AddGestureRecognizer(longPressGestureRecognizer); AddGestureRecognizer(pinchGestureRecognizer); AddGestureRecognizer(panGestureRecognizer); AddGestureRecognizer(swipeGestureRecognizer); AddGestureRecognizer(rotationGestureRecognizer); } } } }
8. Go to the XamFormsRendererGestures.WinPhone project, right-click and select Add | Class…. Name it GestureImagePhoneRenderer.cs and click Add. [assembly: ExportRenderer(typeof(GestureImage), typeof(GestureImagePhoneRenderer))] namespace XamFormsRendererGestures.WinPhone { public class GestureImagePhoneRenderer : ImageRenderer { protected override void OnElementChanged(ElementChangedEventArgs e) { base.OnElementChanged(e);
[ 575 ]
Gestures and Animations if (e.NewElement == null) { Tap -= OnTap; DoubleTap -= OnDoubleTap; ManipulationStarted -= OnManipulationStarted; ManipulationDelta -= OnManipulationDelta; ManipulationCompleted -= OnManipulationCompleted; } if (e.OldElement == null) { Tap += OnTap; DoubleTap += OnDoubleTap; ManipulationStarted += OnManipulationStarted; ManipulationDelta += OnManipulationDelta; ManipulationCompleted += OnManipulationCompleted; } } // ManipulationStarted is the same thing as DragCompleted. private void OnManipulationCompleted(object sender, ManipulationCompletedEventArgs e) { Debug.WriteLine("OnManipulationCompleted"); } // ManipulationDelta represents either a drag or a pinch. private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e) { Debug.WriteLine("OnManipulationDelta"); } // ManipulationStarted is the same thing as DragStarted. private void OnManipulationStarted(object sender, System.Windows.Input.ManipulationStartedEventArgs e) { Debug.WriteLine("OnManipulationStarted"); }
[ 576 ]
Chapter 9 private void OnDoubleTap(object sender, System.Windows.Input.GestureEventArgs e) { Debug.WriteLine("OnDoubleTap"); } private void OnTap(object sender, System.Windows.Input.GestureEventArgs e) { Debug.WriteLine("OnTap"); } } }
9. Run the the application and you will see messages printed in your output like the following when applying gestures on the image: 2016-01-08 21:17:34.509 XamFormsRendererGesturesiOS[2619:120824] Pinch 2016-01-08 21:17:35.880 XamFormsRendererGesturesiOS[2619:120824] Pan
How it works…
Creating GestureImage that is derived from the Image class and adding it to the MainPage XAML page means we are able to create our platform renderers and add native platform code for the custom view. Having access to the native application layer, in Android, we created a SimpleOnGestureListener derive class, GestureImageListener, and overridden methods that will be invoked when gestures are applied such as long press, single tap, double tap, and so on. For every Xamarin.Forms view, there is a renderer. In this case, we derive from Xamarin.Forms.Platform.Android.ImageRenderer. In the GestureImageDroidRenderer constructor, we create Android.Views. GestureDetector passing a GestureImageListener instance. Finally, we register handlers for the GenericMotion and Touch events of Android.Views.View; the view in our case is GestureImage rendered native view. In the handler methods, we pass the Event object from the arguments to GestureDetector. All Android goodies here!
[ 577 ]
Gestures and Animations
For iOS, things are very similar with the built-in Xamarin.Forms gesture recognizers. Every view has a GestureRecognizers property where you can add one or more gesture recognizers. In this example, we added the long press, pinch, pan, swipe, and rotation gesture recognizers. The Windows Phone UIElement class provides all the necessary events to register your handlers and do any manipulation needed, something we implemented in GestureImagePhoneRenderer.
Adding cross-platform animations
Creating a great data-driven cross-platform application is a challenge, and Xamarin. Forms is the key to such success. It will provide everything needed to deliver a crossplatform mobile solution to your users. Users are demanding, and as a user yourself you understand how critical it is to provide a rich user experience, fine-tune your app performance, and provide crisp graphics and animations! Putting all these together means your users intuitively understand how much you care for what you built, and they will always reward you with ratings, membership, coming back, and advertisement to other potential users. Xamarin.Forms offers a cross-platform animation API. Next, we will create an example using the built-in extension methods.
How to do it…
1. In Visual Studio, go to the top menu and select File | New | Project. Choose the Blank App (Xamarin.Forms Portable) template, name it XamFormsAddAnimations and click OK. 2. Right-click the XamFormsAddAnimations PCL select Add | New Item… and choose the Forms Xaml Page. Name it MainPage.xaml and click OK.
We used a local embedded resource image of Master Yoda in this example. If you want to learn more about working with images, refer to the following documentation link: https://developer.xamarin. com/guides/xamarin-forms/working-with/images/.
3. Go to MainPage.xaml.cs and add the OnButtonClicked event handler method. private async void OnButtonClicked(object sender, EventArgs args) { await Task.WhenAll( image.FadeTo(1.0, 400, Easing.BounceIn), image.RotateTo(360, 400, Easing.BounceOut), image.LayoutTo(new Rectangle(200, image.Y, image.Width, image.Height), 2000, Easing.Linear)) .ContinueWith(async (antecedent) => { if (antecedent.Status == TaskStatus.RanToCompletion) { await image.ScaleTo(1.5, 200); await image.ScaleTo(1.0, 200); await image.LayoutTo(new Rectangle(0, image.Y, image.Width, image.Height), 2000, Easing.Linear); } }, TaskScheduler.FromCurrentSynchronizationContext()); }
[ 579 ]
Gestures and Animations
4. Replace the App.cs constructor with the following code to initialize the app with the MainPage.xaml page: public App() { MainPage = new MainPage(); }
5. Run the application to any platform; the result is exactly the same: cross-platform goodies! 6. Click the bottom ANIMATE button and watch Master Yoda fading, rotating, and moving across the screen, with the X axis kicking in and returning back to the left edge of the screen, similar to the following Android screenshot: Android:
[ 580 ]
Chapter 9
iOS:
[ 581 ]
Gestures and Animations
Windows Phone:
How it works…
Xamarin.Forms provides three built-in animation types: fading in/out, rotation, and translation of a view via the extension methods FadeTo, RotateTo, LayoutTo, and ScaleTo. All the extension methods accept two last arguments as optional parameters: one is the length of the animation with a default value of 250 ms and an Easing parameter with a default value of null. Easing will define how the motion of the animation will be executed. At the time of writing this module, there are 11 easing modes that you can choose from. Check the following link to learn more about these modes: https://developer.xamarin.com/api/type/Xamarin.Forms.Easing/. [ 582 ]
Chapter 9
With FadeTo, you can pass the opacity level you want as a result of the completion of the animation. RotateTo accepts a first argument of the rotation value in degrees of the view. In LayoutTo, pass an instance of Rectangle, which has a constructor accepting the X and Y coordinates where you want to change the location of the view and the width and height. ScaleTo accepts a scale value, which will scale the view from the center.
All the extension methods are of type Task. To accomplish concurrent animations, just add the animations to the Task.WhenAll, Task.WaitAny, or Task.WaitAll methods. Depending on your scenario, you can mix and match concurrency. Master Yoda in our example fades in, moves across the X axis of the screen, rotates, scales, and moves back to point X=0. The first three animations (fade-in, rotate, and layout translation) are concurrent and when the layout finishes, we continue with another two scale and one layout animations, all awaiting serial execution. The animating force is with you!
There's more…
Fade is a simple animation on the view's opacity and it makes things stable, but this might not be the case with all your scenarios; for example, when moving or scaling a view inside relative or stack layouts. Choosing the correct layout is key for animations to work as expected. In this recipe, we worked with StackLayout, but the example is mostly linear: moving the view from point A to point B in the X axis of the screen, scaling it in a steady position, and then moving it back to point A from point B worked great. The problem comes for more complex layout operations, for example translating the X and Y position of a view from point A to point B. To make sure your views are aligned correctly and the desired effect is achieved, or if you experience strange results, you need to change your layout container. The safest choice is AbsoluteLayout, which the position of views is free of relative view calculations and repositioning elements on the screen.
[ 583 ]
Test Your Applications, You Must In this chapter, we will cover the following recipes: • • • •
Creating unit tests Creating acceptance tests with Xamarin.UITest Using the Xamarin.UITest REPL runtime shell to test the UI Uploading and running tests in Xamarin Test Cloud
Introduction
You definitely must! Testing your applications is very important. Is there anyone who never had bugs in core components of the layers of the application or UI behavior meet the requirements for all the features supported with one pass? We need to ensure that our code delivered is of high quality and the application does what the requirements acceptance criteria define. In traditional testing, we have software testers perform testing on the device, but with automated testing the risk of missing a problem in a component is minimized; also, we don't fully depend on a tester's skills. With test automation, we ensure that new code will work as expected and that we didn't break any existing part of the code even if we didn't work on that part. Remind you of something? I know. In mobile development, the problem is bigger with traditional testing: there are so many devices from different vendors with different characteristics and OS versions, so how will everything work as expected in all these different form factors? You have to take into consideration other factors as well: connectivity, service availability, and hardware device APIs or battery. Manually testing and covering these factors with abstractions and mocking frameworks is time consuming and error-prone. [ 585 ]
Test Your Applications, You Must
There are two types of testing approaches using test-driven development (TDD): Inside-Out and Outside-In, known also as Classic school (bottom-up) and London school (top-down). In this chapter, we will learn how to create tests for both approaches, then take our acceptance tests and upload them to Xamarin Test Cloud and run them against real physical mobile devices.
Creating unit tests
Inside-Out testing, commonly known as unit testing, performs tests in individual components and its methods are completely independent of the UI. So, if you take a Classic-school architecture, bottom-up, you might have classes in layers like the data access repositories, models, viewmodels, and business logic components. Unit tests are making sure that individual parts (units) of the application work independently, with any outside dependencies stubbed out and made irrelevant. First, we will create a unit test project for the shared portable code and then two projects for specific platform device capabilities.
How to do it…
1. We'll start with Classic-school testing. In Visual Studio, go to top menu and select File | New | Project. Choose the Blank App (Xamarin.Forms Portable) template, name it XamFormsUnitTesting and click OK. 2. Right-click the XamFormsUnitTesting PCL and select Add | Class…, name it ReverseService.cs and click Add. The following is the code for the newly created class: public class ReverseService { public string ReverseWord(string word) { char[] arr = word.ToCharArray(); Array.Reverse(arr); return new string(arr); } }
3. Right-click the solution and select Add | New Project…. In Templates, select the Test section and choose Unit Test Project, give it the name UnitTestCore and click OK.
[ 586 ]
Chapter 10
4. Right-click the UnitTestCore project and select Add | Class…. Name it ReverseServiceTests.cs and click Add. Find the code for the newly created class next: [TestClass] public class ReverseServiceTests { [TestMethod] public void Should_Reverse_Word() { / Arrange string word = "ABC"; string reversedWord = "CBA"; ReverseService reverseService = new ReverseService(); // Act string result = reverseService.ReverseWord(word); // Assert Assert.AreEqual(result, reversedWord); } }
5. Right-click the References folder in the UnitTestCore project and choose Add Reference. Go to the Projects | Solution section, check the XamFormsUnitTesting PCL project and click OK. 6. Build the XamFormsUnitTesting and UnitTestCore projects. 7. In the Visual Studio menu, go to Test | Windows | Test Explorer. In the Test Explorer window, press the Run All button link; the test Should_ Reverse_Word should become green.
[ 587 ]
Test Your Applications, You Must
8. A green checkmark indicator will also appear on top of the Should_ Reverse_Word test method; clicking it will provide you with the results of the test completion.
9. Continuing, we will create two platform-specific libraries for testing. Rightclick on the solution and select Add | New Project…. In Templates, select the Test section and choose Unit Test App (Android), give it the name UnitTestDroid and click OK. 10. We will not add any tests for this example, but go to the TestSample. cs file and explore all the SetUp, TearDown, and Test methods. Run the UnitTestDroid project in the simulator or device and click Run Tests; you will see tests information on the screen. Clicking on the screen, you can go to individual tests. In this project, you can add any platform-specific tests you want.
[ 588 ]
Chapter 10
11. Right-click the solution and select Add | New Project…. In Templates, select the Test section and choose Unit Test App (iOS), give it the name UnitTestTouch and click OK. 12. At the time of writing this module, creating a Unit Test App (iOS) project in Visual Studio lacked the startup code to set a TouchRunner ViewController as RootViewController. Go to AppDelegate.cs and replace the FinishedLaunching method with the following code. This is an iOS-specific testing library. TouchRunner runner; public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) {
[ 589 ]
Test Your Applications, You Must // Override point for customization after application launch. // If not required for your application you can safely delete this method // create a new window instance based on the screen size Window = new UIWindow (UIScreen.MainScreen.Bounds); runner = new TouchRunner (Window); // register every tests included in the main application/assembly runner.Add (System.Reflection.Assembly.GetExecutingAssembly ()); Window.RootViewController = new UINavigationController (runner.GetViewController ()); // make the window visible Window.MakeKeyAndVisible (); return true; }
13. Right-click the UnitTestTouch project and select Add | Class…. Give it the name Tests.cs and click Add. Next, find the contents of the class: [TestFixture] public class Tests { [Test] public void Pass () { Assert.True (true); } [Test] public void Fail () { Assert.False (true); } [Test] [Ignore ("another time")] public void Ignore () { Assert.True (false); } } [ 590 ]
Chapter 10
14. Run the UnitTestTouch project on the simulator or your device and click the Run Everything button.
How it works…
We created three unit test projects; the first one is a full .NET framework class library. My machine uses the current .NET 4.5.2 version. Note that Visual Studio by default creates a project using MSTest. If you are using Xamarin Studio, NUnit is the default testing framework.
NUnit has an advantage that works in a PCL, but with the full .NET framework, you can use mocking frameworks; one of my favorites is Moq. [ 591 ]
Test Your Applications, You Must
In our very simple sample component class, ReverseService, we have one method, ReverseWord. In the UnitTestCore project, we created a test class named ReverseServiceTests to test the unit under the testing component ReverseService. Every test has to validate one or more conditions and is structured in three parts: • Arrange: this is where we do any setup for our test like initialize some variables or instantiate your unit under a testing component passing any dependencies • Act: is the stage that you are performing the test; this is the place to invoke the method you want to test and get results • Assert: is where you verify that the conditions are met and the method works as expected; you should at least have one assertion The test will be considered successful if it is passes and no unexpected exception is thrown. Assert is a static class with many useful validation methods. Refer to the class documentation for a complete list: https://msdn.microsoft.com/en-us/library/ microsoft.visualstudio.testtools.unittesting.assert.aspx. We tested the PCL code with the .NET 4.5.2 testing class library. To test the platformspecific components and features, the NUnitLite framework is used, a unit testing framework that allows you to run unit tests on simulators or devices. There is a template for creating an Android unit test project and an iOS unit test project. Working with NUnitLite is the same as working with NUnit. Be aware that simulators don't have all the device capabilities available; for example, you won't be able to test a sensor API that is not available.
Creating acceptance tests with Xamarin. UITest
Outside-In testing is the acceptance testing we are performing to make sure that the application meets the specifications. These tests start from the user interface testing and might go to the bottom of the architecture. These behavior tests are responsible for testing the user interface functionality and behavior. In this section, we will look into the Xamarin.UITest acceptance testing framework and create a cross-platform UITest for the Android and iOS platforms just like a user would interact with it.
[ 592 ]
Chapter 10
Xamarin.UITest is based on NUnit, and on the open source framework Calabash, one of the world's most popular behavior-driven development acceptance frameworks for mobile applications. Beware, though, as these are not unit tests. In this recipe, we are using Xamarin Studio on a Mac. Creating a Xamarin.UITest cross-platform project is supported in Visual Studio; however, you will be able to test your Android tests only. iOS UI testing is not yet supported in Visual Studio. Windows Phone is not supported by Xamarin.UITest. The team is looking into supporting the platform, but no specific ETA has been announced at the time of this writing.
How to do it…
1. Open Xamarin Studio on your Mac and go to File | New | Solution…. In the dialog that appears, choose the template in the section Cross-platform | App | Xamarin.Forms App and click Next.
[ 593 ]
Test Your Applications, You Must
2. In the next screen, you need to configure your Xamarin.Forms app. Set the app name to XamFormsAcceptanceUITesting. Target platforms by default are iOS and Android and the Shared Code option is Use Portable Class Library. Click Next.
3. Continuing in the next screen, configuring the project, uncheck the Use Xamarin Insights option. We will not be using this feature for this example. If you want to learn how to monitor your application for live insights, go to the Using Xamarin Insights recipe from Chapter 11, Three, Two, One – Launch and Monitor. Check the option Add an automated UI test project and click Create.
[ 594 ]
Chapter 10
4. The solution is created. Go to the XamFormsAcceptanceUITesting.UITests project and expand the folder Packages. Right-click the Xamarin.UITest package and click Update. 5. Right-click the XamFormsAcceptanceUITesting PCL project and select Add | New File…. In the Forms template section, select Forms Content Page Xaml, give it the name MainPage and click New. 6. Open the newly created MainPage.xaml file and replace the contents with the following code:
[ 595 ]
Test Your Applications, You Must
7. Open the MainPage.xaml.cs behind-code file and add the OnAddContactClick event handler method. private async void OnAddContactClick(object sender, EventArgs args) { await Navigation.PushAsync (new ContactPage ()); }
8. Repeat step 5 for a Forms Content Page Xaml page named ContactPage. xaml. Add the following XAML contents to the newly created page:
9. Open AddCotactPage.xaml.cs and add the OnSaveClick event handler method. private async void OnSaveClick(object sender, EventArgs args) { if (!string.IsNullOrWhiteSpace (nameEditText.Text) && string.IsNullOrWhiteSpace (emailEditText.Text)) { await Navigation.PopAsync (true); } }
[ 596 ]
Chapter 10
10. Go to the XamFormsAcceptanceUITesting.cs file and replace the constructor with the following code: public App () { MainPage = new NavigationPage(new MainPage()); }
11. Open the Tests.cs file created automatically in the XamFormsAcceptanceUITesting.UITests project and add the following tests: [Test] public void Add_Contact_Button_Should_Navigate_To_Contact_Page () { // Act // Screenshot names are only supported in Test Cloud. app.Screenshot ("MainPage screen."); app.Tap (p => p.Button ("addContactButton")); AppResult[] results = app.Query (p => p.Marked ("addContactPage")); // Assert Assert.True (results.Any()); } [Test] public void Save_Contact_Button_Should_Navigate_Back_To_MainPage() { // Act // Screenshot names are only supported in Test Cloud. app.Screenshot ("MainPage screen."); app.Tap (p => p.Button ("addContactButton")); app.WaitForElement (p => p.Marked ("addContactPage")); app.EnterText (p => p.Marked ("nameEditText"), "George Taskos"); app.EnterText (p => p.Marked ("emailEditText"), "[email protected]"); app.Tap (p => p.Button ("saveButton")); app.WaitForElement (p => p.Marked ("mainPage")); AppResult[] results = app.Query (p => p.Marked ("mainPage")); // Assert Assert.True (results.Any()); }
[ 597 ]
Test Your Applications, You Must
12. Go to the XamFormsAcceptanceUITesting.iOS project and open the AppDelegate.cs file. In the FinishedLaunching method and after the global:Xamarin.Forms.Forms.Init() method call and inside the ENABLE_TEST_CLOUD if a directive condition, add the following code: global::Xamarin.Forms.Forms.ViewInitialized += (sender, e) => { // http://developer.xamarin.com/recipes/testcloud/setaccessibilityidentifier-ios/ if (null != e.View.StyleId) { e.NativeView.AccessibilityIdentifier = e.View.StyleId; } };
13. Go to the XamFormsAcceptanceUITesting.Droid project and open the MainActivity.cs file. In the OnCreate method and after the global:Xamarin.Forms.Forms.Init(this, bundle) method call, add the following code: // http://forums.xamarin.com/discussion/21148/calabash-andxamarin-forms-what-am-i-missing global::Xamarin.Forms.Forms.ViewInitialized += (object sender, Xamarin.Forms.ViewInitializedEventArgs e) => { if (!string.IsNullOrWhiteSpace(e.View.StyleId)) { e.NativeView.ContentDescription = e.View.StyleId; } };
14. Rebuild the solution. 15. Start your favorite Android emulator. In this module, we test the Android applications in the Xamarin Android Player, Nexus 5 (Lollipop) (API 22) emulator. 16. In the Xamarin Studio top menu, click View | Pads | Unit Tests. The Unit Tests window will appear.
[ 598 ]
Chapter 10
17. Right-click Tests(Android) and select Run Test. After watching the Android acceptance tests playing, repeat the same for Tests(iOS).
How it works…
The UITests are contained in a class library and described as NUnit tests. To execute the UITests against your app, it utilizes an automation library named Xamarin Test Cloud Agent. Xamarin.UITest uses a client/server architecture that communicates over HTTP and JSON. There is a Xamarin Test Cloud Server installed in your device or simulator that drives the app using platform automation APIs. On Android, the server is a separate process signed with the same keystore as the app, which is bypassing the normal sandbox conditions. In iOS, the server is bundled into the application binary. This server component is started in the AppDelegate.cs file, the FinishedLaunching method. As a best practice, you should use a conditional compilation directive so that you don't deploy the Calabash server to your final release binary. By default, Xamarin uses the directive ENABLE_ TEST_CLOUD.
[ 599 ]
Test Your Applications, You Must
UITests are based on Calabash, and Xamarin.UITest provides a C# abstraction on top of the framework. You can find more information about the open source framework at http://calaba.sh/. Note that the acceptance tests can only test your application and cannot interact in any way with the system. You can't, for example, automate pressing the home button or going to the system settings and changing a value.
To drive the application when testing, we use the IApp interface. Xamarin provides two implementations of the interface, one for iOS and one for Android. The concrete implementation is provided to us via the AppInitializer.StartApp(Platform) static method. The IApp interface is the bread and butter of Xamarin.UITest. To interact directly with your user interface, you can query the interface and tap a button, enter text in input fields, wait for an element to appear or disappear on the screen, apply gestures, and so on. Find the complete reference of the interface at the following link: https://developer.xamarin.com/api/type/Xamarin.UITest.IApp/. You may have noticed that in the XAML code we set the StyleId property to identify the elements when we are querying the interface via the IApp interface. This is necessary to define the UI elements of the native-built application from Xamarin.Forms. Setting StyleId in Android will set ContentDescription and on iOS, the Accessibility identifier. This does not work out of the box, so a little bit of code is needed: an event handler to the static event Xamarin.Forms.Forms. ViewInitialized for both platforms in steps 12 and 13. This event will be raised each time an element is initialized and we set the corresponding property to the value of StyleId so that we can query the views appropriately. In the Unit Tests window, you have the option to choose where you want to test the application: simulator/emulator or a device. Right-click a platform in the Test Apps section and choose the desired target. To change from simulator to a device in iOS, you will have to select Debug | iPhone in the project option.
[ 600 ]
Chapter 10
Note that to run the Xamarin.UITest acceptance tests in your device, you have to make sure that some settings are correct. You can find a guide that will instruct you with all the related information at the following link: https://developer.xamarin.com/guides/testcloud/uitest/ working-with/testing-on-devices/.
There's more…
You can use Xamarin.UITest to test any native Android or iOS application. You have the option to create a UITest project, write your tests, and run them on the binaries provided to you. You can configure this in the AppInitializer.cs file StartApp static method. There are three ways to identify the application we want to test against: 1. Specify the full path on the local disk to a built iOS or Android binary. ConfigureApp.Android.ApkFile ("../../path/myapp.apk"); ConfigureApp.iOS.AppBundle ("../../path/mybundle.app");
[ 601 ]
Test Your Applications, You Must
2. Identify a specific package or bundle identifier for an installed application. ConfigureApp.iOS.InstalledApp ("com.packtpub.xamformscookbook"); ConfigureApp.Android.InstalledApp ("com.packtpub.xamformscookbook");
3. Associate a project in the solution, which must be in the same folder as the UITest project (Xamarin Studio support only). This is the default when creating a solution in Xamarin Studio with the option to create a UITest project, like in this recipe. Hybrid HTML applications are supported in the Xamarin.UITest framework; for example, you can query the interface using IApp.CssClass("#mycssclass"). There is another helpful method in AndroidAppConfigurator and iOSAppConfigurator that enables local screenshot saving. EnableLocalScreenshots is a feature enabled by default in Test Cloud. If you want to enable this feature locally, invoke this method for each platform configurator in the AppInitializer.StartApp(Platform) method. This will save a screenshot in the format screenshot-1.png in your bin folder. Title is only available in Test Cloud. In this recipe, we invoke the IApp.Screenshot(String) method to capture screenshots.
See also
• https://developer.xamarin.com/guides/testcloud/uitest/intro-touitest/
• https://developer.xamarin.com/guides/testcloud/uitest/ cheatsheet/
Using the Xamarin.UITest REPL runtime shell to test the UI
Creating your acceptance tests is challenging. Identifying the controls, structuring your commands, and replicating the desired user testing gestures is not easy, especially when you are testing a binary. Even if you are writing the source code and you have set your StyleId properties to your views, it's impossible to remember them or stop and start a test until you have the correct commands in place.
[ 602 ]
Chapter 10
To find all the information about the controls and interact with the interface in real time, there is a command-line tool named REPL, which stands for Read-EvaluatePrint-Loop. In this recipe, we will see how in real time you can start a test project, query the interface for the available views, interact with the views, and copy the history in the memory to start off your test method.
How to do it…
1. Open Xamarin Studio on your Mac and go to File | New | Solution…. In the dialog that appears, choose the template in the section Cross-platform | App | Xamarin.Forms App and click Next.
[ 603 ]
Test Your Applications, You Must
2. In the next screen, you need to configure your Xamarin.Forms app. Set the app name to XamFormsReplTest. The target platforms by default are iOS and Android and the Shared Code option is Use Portable Class Library. Click Next.
3. Continuing in the next screen, configuring the project, uncheck the Use Xamarin Insights option. We will not be using this feature for this example. If you want to learn how to monitor your application for live insights, go to the Using Xamarin Insights recipe from Chapter 11, Three, Two, One – Launch and Monitor. Check the option Add an automated UI test project and click Create.
[ 604 ]
Chapter 10
4. The solution is created, so go to the XamFormsReplTest.UITests project and expand the folder Packages. Right-click the Xamarin.UITest package and click Update. 5. Right-click the XamFormsReplTest PCL project and select Add | New File…. In the Forms template section, select Forms Content Page Xaml, give it the name MainPage and click New. 6. Open the newly created MainPage.xaml file and replace the contents with the following code:
[ 605 ]
Test Your Applications, You Must
7. Open the MainPage.xaml.cs behind-code file and add the OnLogInClick event handler method. private async void OnLogInClick(object sender, EventArgs args) { if (string.IsNullOrWhiteSpace (usernameEntry.Text) || string.IsNullOrWhiteSpace (passwordEntry.Text)) { await DisplayAlert ("Log In Error", "Username or password is empty!", "OK", "Cancel"); } else { await DisplayAlert ("Log In", "Nice!", "OK", "Cancel"); } }
8. Go to the XamFormsReplTest.iOS project and open the AppDelegate.cs file. In the FinishedLaunching method and after the global:Xamarin.Forms. Forms.Init() method call and inside the ENABLE_TEST_CLOUD, if a directive condition, add the following code: global::Xamarin.Forms.Forms.ViewInitialized += (sender, e) => { // http://developer.xamarin.com/recipes/testcloud/setaccessibilityidentifier-ios/ if (e.View.StyleId != null) { e.NativeView.AccessibilityIdentifier = e.View.StyleId; } };
9. Go to the XamFormsReplTest.Droid project and open the MainActivity.cs file. In the OnCreate method and after the global:Xamarin.Forms.Forms. Init(this, bundle) method call, add the following code: // http://forums.xamarin.com/discussion/21148/calabash-andxamarin-forms-what-am-i-missing global::Xamarin.Forms.Forms.ViewInitialized += (object sender, Xamarin.Forms.ViewInitializedEventArgs e) => { if (!string.IsNullOrWhiteSpace(e.View.StyleId)) { NativeView.ContentDescription = e.View.StyleId; } }; [ 606 ]
Chapter 10
10. Go to the XamFormsReplTest.UITests project and open the Tests.cs file. There is only one test created by default; rename it to the DummyTest method that just passes. [Test] public void DummyTest() { Assert.IsTrue (true); }
11. In the Tests.cs file, go to the BeforeEachTest method and add the method call app.Repl() like the following: [SetUp] public void BeforeEachTest () { app = AppInitializer.StartApp (platform); // REPL is ignored when running on the cloud app.Repl (); }
12. Open the Unit Tests pad from the View | Pads | Unit Tests top menu, expand the tree, right-click the Tests (Android) and select Run Tests. This will start the Android application and open a terminal window.
[ 607 ]
Test Your Applications, You Must
13. Type tree and hit Enter. This will give you the interface controls hierarchy on the screen with plenty of information like the text and ID.
14. Write app. As soon as you hit the '.' character, you will notice that you get IntelliSense.
[ 608 ]
Chapter 10
15. Using the Query method and passing a selector, you can get more information for a specific interface control. Type app.Query(p => p.Marked("logInButton")) and hit Enter. Look at the information regarding the Android button widget.
16. Tap the Log In button using the Tap command, type app.Tap(p => p.Button("logInButton")) and hit Enter. Notice that we get an alert dialog with the message that the Username or Password is empty! It's like we are actually interacting with the application using our fingers.
[ 609 ]
Test Your Applications, You Must
Android:
[ 610 ]
Chapter 10
17. Type the command tree again and you will notice that button1 has the text OK. Type app.Tap(p => p.Button("button1")), hit Enter, and the dialog goes away.
18. Now, enter some text to the input fields using REPL commands. Type app. EnterText(p => p.TextField("usernameEntry"), "George") and hit Enter. Notice the word George is automatically inserted into the username field. Note that if you have connected the simulator keyboard to your Mac keyboard, the iOS keyboard might not appear. If this is the case then the value will not be typed in the field. To avoid this happening, uncheck the Connect Hardware Keyboard option from the Hardware | Keyboard menu of the iOS simulator.
[ 611 ]
Test Your Applications, You Must
19. Type app.EnterText(p => p.TextField("passwordEntry"), "aPassword") and hit Enter. The password entry field is filled with the word aPassword.
20. Let's tap the button from the command line now, app.Tap(p => p.Button("logInButton")), and notice the success dialog message now that the entry fields have values.
[ 612 ]
Chapter 10
21. Type the command copy in the REPL runtime shell. This command will copy the history in the clipboard. Go to Xamarin Studio and paste the contents from your clipboard in DummyTest. This will give you a start for your test function. [Test] public void DummyTest() { app.Tap(p => p.Button("logInButton")); app.Tap(p => p.Button("button1")); app.EnterText(p => p.TextField("usernameEntry"), "George"); app.EnterText(p => p.TextField("passwordEntry"), "aPassword"); app.Tap(p => p.Button("logInButton")); Assert.IsTrue (true); }
22. Go to the BeforeEachTest method and comment app.Repl(). Run the test again. You will see the acceptance-testing movie playing!
How it works…
To launch this runtime shell and interact with the connected application, you just need to invoke the IApp.Repl() method in the test. If you'd like to use the terminal for more than one test then add the IApp.Repl() call in the BeforeEachTest overridden method and after AppInitializer.StartApp(platform), as we did in this recipe. When you execute tests, the REPL runtime shell will appear with your application connected and ready to accept commands. Xamarin.UITest has a rich API to accomplish almost everything as a user would normally interact with the application. You can find the documentation for the IApp interface at the following link: https://developer.xamarin.com/api/type/ Xamarin.UITest.IApp/. To identify elements on our interface, we have the Query method that accepts a selector This returns an AppResults[] array with zero or more results that match the predicate filter. The selector syntax is similar to the C# lambda expression. In the expression, you will usually filter for a specific view, passing the ID or text value in strongly typed methods like Button(id) or TextField(id), using Marked(id/ text) or Id(id), though there are other ways to query elements like using class queries for specific types, Class("UITextField"). Querying hybrid web applications are supported as well; you can use the Id(id). Css("#cssclass") method. [ 613 ]
Test Your Applications, You Must
See also
• https://developer.xamarin.com/guides/testcloud/uitest/workingwith/repl/
Uploading and running tests in Xamarin Test Cloud
Your tests are ready and are testing all your screens, setting text values, tapping buttons, and interacting with gestures. Everything is great, tested in the simulator and in your device. Powerful! But this is not enough. Even with two or three devices you might have, the nicest and most polished application you've finished is tested in a managed environment. Developers tend to forget about the changes they made to settings or the OS version is updated to the latest one. For Android, it gets even more complicated; scenarios such as different vendors with different versions of the OS, where some are updated some not, and various API levels on each device are encountered. Xamarin Test Cloud is a cloud-based Acceptance Testing service where you can deploy your tests exactly as is from your cross-platform and execute them in parallel in over 2,000 physical different mobile devices. See the list of devices at the following link: https://testcloud.xamarin.com/devices. For this recipe, we will create a Xamarin.Forms project and deploy the tests directly from Xamarin Studio.
How to do it…
1. To start with Xamarin Test Cloud, you have to log in with your Xamarin account in https://testcloud.xamarin.com/. You can also register with a different account if this is what you want. Xamarin Test Cloud provides you with 60 minutes/month if you are a licensed Xamarin developer and a 25-hour time gift that will start counting after your monthly time if you are a Xamarin University subscriber. You may use your trial evaluation Xamarin account, or try a free demo at the following link: https://xamarin.com/test-clouddemo. In this recipe, we will show how you can push your tests as if you have an account.
[ 614 ]
Chapter 10
2. Open Xamarin Studio on your Mac and go to File | New | Solution…. In the dialog that appears, choose the template in the section Cross-platform | App | Xamarin.Forms App and click Next.
3. In the next screen, you need to configure your Xamarin.Forms app. Set the app name to XamFormsTestCloud. The target platforms by default are iOS and Android, and the Shared Code option is Use Portable Class Library. Click Next.
[ 615 ]
Test Your Applications, You Must
4. Continuing in the next screen, configuring the project, uncheck the Use Xamarin Insights option. We will not be using this feature for this example. If you want to learn how to monitor your application for live insights, go to the Using Xamarin Insights recipe from Chapter 11, Three, Two, One – Launch and Monitor. Check the option Add an automated UI test project and click Create.
5. The solution is created. Go to the XamFormsTestCloud.UITests project and expand the folder Packages. Right-click the Xamarin.UITest package and click Update. 6. Right-click the XamFormsTestCloud PCL project and select Add | New File…. In the Forms template section, select Forms Content Page Xaml, give it the name MainPage and click New. 7. Open the newly created MainPage.xaml file and replace the contents with the following code:
[ 616 ]
Chapter 10
8. Open the MainPage.xaml.cs file and add the OnDetailsClick event handler. private async void OnDetailsClick(object sender, EventArgs args) { await Navigation.PushAsync (new DetailsPage ()); }
9. Right-click the XamFormsTestCloud PCL project and select Add | New File…. In the Forms template section, select Forms Content Page Xaml, give it the name DetailsPage and click New. 10. Open the newly created DetailsPage.xaml file and replace the contents with the following code:
11. Open the DetailsPage.xaml.cs file and add the OnOkClick event handler. private async void OnOkClick(object sender, EventArgs args) { await Navigation.PopAsync (); }
[ 617 ]
Test Your Applications, You Must
12. Go to the XamFormsTestCloud.iOS project and open the AppDelegate. cs file. In the FinishedLaunching method and after the global:Xamarin. Forms.Forms.Init() method call and inside the ENABLE_TEST_CLOUD, if a directive condition, add the following code: global::Xamarin.Forms.Forms.ViewInitialized += (sender, e) => { // http://developer.xamarin.com/recipes/testcloud/ set-accessibilityidentifier-ios/ if (null != e.View.StyleId) { e.NativeView.AccessibilityIdentifier = e.View.StyleId; } };
13. Go to the XamFormsTestCloud.Droid project and open the MainActivity. cs file. In the OnCreate method and after the global:Xamarin.Forms. Forms.Init(this, bundle) method call, add the following code: // http://forums.xamarin.com/discussion/21148/calabash-andxamarin-forms-what-am-i-missing global::Xamarin.Forms.Forms.ViewInitialized += (object sender, Xamarin.Forms.ViewInitializedEventArgs e) => { if (!string.IsNullOrWhiteSpace(e.View.StyleId)) { e.NativeView.ContentDescription = e.View.StyleId; } };
14. In the XamFormsTestCloud.UITests project, open Tests.cs and paste the following test method: [Test] public void Should_Navigate_To_Details_And_Back_To_MainPage () { // Act app.Screenshot ("On mainPage"); app.Tap(p => p.Button("detailsPageButton")); app.WaitForElement(p => p.Marked("detailsPage")); app.Screenshot ("Navigated to detailsPage"); app.WaitForElement(p => p. Marked ("mainPageButton")); app.Tap(p => p.Button("mainPageButton")); AppResult[] results = app.WaitForElement(p => p. Marked ("mainPage")); app.Screenshot ("Navigated back to mainPage"); // Assert Assert.True(results.Any()); } [ 618 ]
Chapter 10
15. Open the Unit Tests pad from the top menu View | Pads | Unit Tests, rightclick the test, and choose Run Test to run the unit tests in your simulator/ emulator or devices for both platforms to ensure that everything is working fine. If you want to learn more about how to create and execute UITests, go to Creating acceptance tests with Xamarin.UITest and Using the Xamarin. UITest REPL runtime shell to test the UI recipe in this chapter. 16. From the configuration build option in the upper-left corner, choose Debug | iPhone. 17. To upload and execute the test in Test Cloud, open the Unit Tests pad and in the Tests(iOS) section, right-click and choose Run in Test Cloud.
[ 619 ]
Test Your Applications, You Must
18. A window will appear with the option XamFormsTestCloud.iOS selected. As you can see, Release build has to be selected to run Android tests in the cloud. Click Upload and Run.
19. Xamarin Studio will now compile the package and upload the tests to Test Cloud. You can see details in the Test Cloud output window that appears.
[ 620 ]
Chapter 10
20. When the upload is complete, you will be transferred to the web browser with the option to select devices that you would like the tests to run on. In the following screenshot, you can see I selected top 10 phones. Click the button in the bottom-right corner to continue.
21. In the next screen, we will leave the default settings and click Done.
[ 621 ]
Test Your Applications, You Must
22. Next, your tests will prepare and run. When the tests are finished, you will also receive an e-mail.
23. Click on the test when finished and you will transfer to the Overview page.
24. Clicking the Should Navigate To Details And Back To Main Page test will show all the devices that the test is executed on. Select a device and you will get all the related insights. You can also select to see the screenshots from the left menu.
[ 622 ]
Chapter 10
How it works…
To upload your Xamarin.UITest acceptance tests in Xamarin Test Cloud and choose to run them in the physical devices of your choice is as simple as right-clicking and selecting Run in Test Cloud on a test or multiple tests. There is no special directives or code that is needed for your tests. The only option to take into consideration is that the build configuration has to be the same setup as a local device: Debug-iOS Device build for iOS and Release build for Android. Xamarin Studio will take care of all the details of building and uploading the tests in Test Cloud. When this step is complete, you will be redirected to the Xamarin Test Cloud website and there is just the small matter of some configuration setup.
[ 623 ]
Test Your Applications, You Must
There's more…
Test Cloud is organized by teams and members. You can add a new team or add members to an existing team by navigating to your Account settings by clicking your profile name in the top-right corner and then clicking the section Teams & Apps on the left menu.
You have the option to show the API key, configure an existing team, or create a new one. Each team can manage different apps and members. You can add admins to manage teams. Click the section Subscription overview, and on the right of the page there is the option to create a new admin.
[ 624 ]
Chapter 10
There are some CI systems that supports Test Cloud such as Team City, Jenkins, Visual Studio Online, or any other system with custom post-build commands. The Xamarin.UITest NuGet package includes command-line tools to upload and execute your tests; you can find more information at the following link: https:// developer.xamarin.com/guides/testcloud/uitest/cheatsheet/#Running_ UITests_Using_the_Command_Line.
See also
• https://xamarin.com/test-cloud/
[ 625 ]
Three, Two, One – Launch and Monitor In this chapter, we will cover the following recipes: • Using Xamarin Insights • Publishing iOS applications • Publishing Android applications • Publishing Windows Phone applications
Introduction
After all these character bytes, logic, best practices, reading, researching, meetings, and pursuing of the quality, time, and budget triangle, the time has come to release your creation to the world! Deployment it is. Yes, it comes with that feeling of fear, as no application is ever released without bugs. In this chapter, we will explore how to use the Xamarin Insights solution and track issues, send events, and extra data to gain a better understanding of how your application is performing and what your users like the most. Next, we will go through the configuration to prepare your iOS, Android, and Windows Phone applications for deployment.
[ 627 ]
Three, Two, One – Launch and Monitor
Using Xamarin Insights
Did you ever deploy an application and wonder how it works for the user? Is it performing well? Unhandled exceptions causing frustration to users? What features are the users using most? It's pretty scary when you know nothing about what's going on after deploying the code to the world. You can of course depend on a custom support feature you have embedded in your application, but 95 percent of your users will not send you a message; it's easier to uninstall it and give you a bad rating. Most probably you will never make it again on their home screen! Worry not! Xamarin has a product in the family named Xamarin Insights, which is a solution for performance monitoring. With Xamarin Insights, you can: • Track unhandled exceptions and report handled exceptions • Receive runtime device information • Attach custom values to retrieve users or application information • Send events to track anything you want • Monitor network performance The preceding features describe the platform capabilities; however, there is a pricing structure when it comes to Xamarin Insights: all Xamarin subscribers get the basic plan with some features, and then you can upgrade (if it makes sense for your team) to a business account or to the enterprise plan. Learn more at the following link: https://xamarin. com/insights.
[ 628 ]
Chapter 11
How to do it…
1. Open Xamarin Studio and create a new cross-platform solution from the top menu File | New | Solution…. 2. On the New Project initial window screen, select Cross-platform | App | Xamarin.Forms App and click Next.
[ 629 ]
Three, Two, One – Launch and Monitor
3. Set the app name to XamFormsInsights, leave the default platforms to both iOS and Android, the default selection Use Portable Class Library, and click Next.
4. Next, choose a directory location for the project. Make sure to uncheck the option Use Xamarin Insights. We will set up Xamarin Insights manually and learn how it works. Click the Create button.
[ 630 ]
Chapter 11
5. Go to https://insights.xamarin.com and log in with your Xamarin account. 6. After logging in, click the New App button on the right of the website.
7. You will be transferred to a page to enter a name for this insights application in the first step. Enter a name and click the Create new app button.
[ 631 ]
Three, Two, One – Launch and Monitor
8. The second step with your API key appears. You will use this key in all your platform applications. The API key is unique and therefore different from the following screenshot:
9. Go to your application and add the NuGet package. For Xamarin Forms, you need to add the package to each project, including the PCL. In Xamarin Studio, right-click each project, select Add | Add Nuget Packages…, search for Xamarin.Insights, check the box, and click Add Package.
[ 632 ]
Chapter 11
If you have a signed application, you should use the Xamarin.Insights.Signed NuGet package.
10. Go to XamFormsInsights.cs and add the the API key as a public constant at the top of the App class. public const string InsightsApiKey = @"";
11. Now, for each platform, you need to add the initialization code. Check the following iOS application entry point, Main.cs, where we initialize the component: static void Main (string[] args) { Xamarin.Insights.Initialize (App.InsightsApiKey); UIApplication.Main (args, null, "AppDelegate"); }
12. In Android, initialization happens either in the application class or in your main activity launcher in the OnCreate overridden method. There is an extra argument to pass the Context instance. protected override void OnCreate (Bundle bundle) { base.OnCreate (bundle); Xamarin.Insights.Initialize (App.InsightsApiKey, this); global::Xamarin.Forms.Forms.Init (this, bundle); LoadApplication (new App ()); }
13. In the Windows Phone project, you add the initialize method call in the App. cs, App() constructor, and it is the same as iOS. Refer to Chapter 1, One Ring to Rule Them All to learn how to add a Windows Phone project in an existing cross-platform solution created in Xamarin Studio.
[ 633 ]
Three, Two, One – Launch and Monitor
14. Enable the Internet permission in Android. Right-click the XamFormsInsights.Droid project, click Options, select the Android Application section, find the Internet option in Required permissions and check the checkbox.
Android requires version 4.0 – Ice Cream Sandwich (API level 14) or higher.
15. Don't leave the required permissions window. Although optional, you should allow Xamarin Insights to capture more details regarding your Android application. See the following list of permissions and what data Insights is enabled to retrieve. °°
BindNotificationListenerService (BIND_NOTIFICATION_ LISTENER_SERVICE) – catch Android native crashes
°°
AccessNetworkState & AccessWifiState (ACCESS_NETWORK_STATE & ACCESS_WIFI_STATE) – check the state of the network before
making a request to the Xamarin Insights service [ 634 ]
Chapter 11
°°
BatteryStats (BATTERY_STATS) – get the battery statistics
°°
ReadExternalStorage (READ_EXTERNAL_STORAGE) – get the available
°°
ReadPhoneState (READ_PHONE_STATE) – get the device ID
storage of any external storage
16. In Windows Phone 8.0/8.1 Silverlight versions only, the Windows RT API is different and doesn't require any capabilities to be enabled. You need to open WMAppManifest.xml, go to the Capabilities tab, find ID_CAP_IDENTITY_ DEVICE and check it. With this in place, Insights is enabled to uniquely identify the device and hardware. 17. Let's try to catch unhandled and handled exception. Right-click on the XamFormsInsights PCL project, select Add | New File…, choose Forms | Forms ContentPage Xaml, set the name to MainPage and click New. Find the XAML code next:
18. Open MainPage.xaml.cs and add the event handlers. private void OnUnhandledExceptionClick(object sender, EventArgs args) { int i = 0; var result = 1 / i; } private void OnHandledExceptionClick(object sender, EventArgs args) { try { string aString = null; aString.Equals("will_throw"); } [ 635 ]
Three, Two, One – Launch and Monitor catch (NullReferenceException ex) { Xamarin.Insights.Report (ex); } }
19. Open the XamFormsInsights.cs file and replace the constructor by setting a new MainPage instance to the MainPage property. public App () { MainPage = new MainPage(); }
20. Run the application in your preferred platform and click the Unhandled Exception button. The application should crash immediately. You're in debug mode, so hit command + return to continue execution; the application exits immediately. Run the application one more time, which will give Xamarin Insights the chance to upload any pending cached reports. As a developer, you will receive an e-mail with the crash details. 21. Click the button Handled Exception. This will immediately send a caught exception report to Insights or it will be cached until an Internet connection is available. 22. Go to https://insights.xamarin.com and in the XamFormsInsights project, you will see two issues.
[ 636 ]
Chapter 11
23. Click DivideByZeroException and it will transfer you to the details page.
24. Explore all the details available. stacktrace is the most important part of a crash and the nice thing with Xamarin Insights is that it provides the managed code stacktrace. Native stacktrace is supported and you might receive one if it wasn't possible to bubble up the managed environment.
How it works…
Xamarin Insights is a full, detailed product of the Xamarin family for performance monitoring. Insights will help you track issues; collect runtime information like the operating system, version, and device type; and analytics like occurrences per day, users affected, and which version is top affected; add extra data; add custom events; identify users, and more.
[ 637 ]
Three, Two, One – Launch and Monitor
To enable Xamarin Insights in your project, you need the client SDK, which is available via NuGet. Officially, the SDK supports Xamarin iOS/Android, Xamarin Forms and Windows Phone 8.0/8.1. Essentially, it is a PCL library connecting to a REST service and it can theoretically work with any .NET platform. As a minimum, you need to have a valid Xamarin subscription to have access to the Xamarin Insights dashboard. In the dashboard, you register an app, then you include the NuGet library in your applications and finally initialize and deploy with Xamarin Insights support. When an event happens, the SDK will try to send it. If there is no Internet connection, it will be cached until network connectivity is available. The SDK provides a large API to use. The complete documentation is at the following link: https://developer.xamarin.com/api/type/Xamarin.Insights/.
There's more…
Xamarin Insights allows you to add custom information to handled exceptions by passing a Dictionary as an argument in Insights. Report(Exception, Dictionary). Xamarin.Insights.Report (ex, new Dictionary { { "userId", "user-id" } });
To symbolicate the crash report, you need to map the symbol addresses with the dSYM bundle containing the DWARF debug information and it will be used to create a human-readable stacktrace. dSYM files store the debug symbols for your app. Xamarin Insights uses it to replace the symbols in the crash logs with the appropriate methods names so it will be human readable and make sense for the developer to understand the origin of the crash and debug the application. The benefit of using dSYM is that you don't need to ship your app with debug symbols reducing the binary size.
You can manually upload the dSYM file, which is generated for every build of your application in Xamarin Insights, from the section Settings | DSYMS.
[ 638 ]
Chapter 11
In the preceding screenshot, you can see another option to upload dSYM using the web API. Notice the curl command to the API address. To find the dSYM file on a Mac, go to the Debug/Release/bin folder. If you are using Visual Studio on a Windows machine connected to a Mac host build server, go to Tools | iOS | Show IPA file on the build server.
[ 639 ]
Three, Two, One – Launch and Monitor
You can turn off data collection through static properties on the Insights class. See the following properties and their description: • DisableCollection – disables all Insights automated behavior (bool) • DisableDataTransmission – turns off transmission to the dashboard (bool) • DisableCollectionTypes – bit flag from Insights.CollectionTypes to turn off specific collection types Insights.DisableCollectionTypes = // Do not track: Insights.CollectionTypes.HardwareInfo // Hardware device types | Insights.CollectionTypes.Jailbroken // Jailobroken devices | Insights.CollectionTypes.Locale // User locale | Insights.CollectionTypes.OSInfo // OS version type | Insights.CollectionTypes.NetworkInfo // Network connection
If you have a business or enterprise license, there are some additional features you can leverage: • Track users • Add additional user information • Events tracking • Third-party integrations (GitHub issues, Slack, HipChat) For more information on the preceding features, refer to the official Xamarin Insights website: https://xamarin.com/insights.
Publishing iOS applications
After all this hard work coding and polishing your app, delivering requirements, and figuring out best practices for your new iOS project, the moment has arrived: your creation has to reach users' pockets, and this can be done by getting into the Apple Store submission process. To submit an iOS application, it is necessary to have a Mac with Xcode installed, Xamarin iOS uses the same tool that Objective-C and Swift native applications use. You also need an Apple Developer account; to enroll in the program, go to https://developer.apple.com/membercenter. Following this recipe, you will learn how to prepare your application and package it for submission to the Apple Store.
[ 640 ]
Chapter 11
How to do it…
1. You can start off with an already created Xamarin Forms cross-platform solution, but if you want to it is easy to just create a new one. In the Xamarin Studio top menu, choose File | New | Solution… and choose Xamarin. Forms App in the Cross-platform | App section. Click Next.
[ 641 ]
Three, Two, One – Launch and Monitor
2. Next, choose an app name; we simply set it to PublishDemo. Also notice the organization identifier, which is important when publishing. Don't worry though, you can change it later. Click Next.
3. In the last screen, check the Use Xamarin Insights checkbox; it is always a good option to include insights in your application. To learn more about how to use Xamarin Insights, go to the section Using Xamarin Insights. Click Next.
[ 642 ]
Chapter 11
4. Expand the PublishDemo.iOS project and double-click the Info.plist file. Here, you will find some application settings. This file is the same file with a native iOS application. Make sure the Application Name, Bundle Identifier, and Version are set, as these identify your app uniquely to the user and device. Bundle Identifier has to match your Apple distribution provisioning profile.
[ 643 ]
Three, Two, One – Launch and Monitor
Info.plist contains various information regarding the application like what devices you support, the deployment target SDK, icons, launch images, maps integration, and background modes. Make sure you provide all the application assets needed; the application icon is very important for our example.
5. Set the deployment target to 6.0, which is the minimum that Xamarin.Forms uses. If you know that your application uses APIs that don't support a lowerversion SDK then feel free to change this to your needs. 6. Double-click PublishDemo.iOS and go to the iOS Build tab. This tab has some important settings that you should configure. In Configuration, choose Release mode, and for Platform, choose iPhone. 7. In Supported architectures, choose ARMv7 + ARM64. 8. Now, check the Use the LLVM optimizing compiler checkbox and under that Use Thumb-2 instruction set for ARMv7 and ARMv7s. 9. In the iOS SDK version option, choose the latest one; in my case, I would choose 9.2, which is the latest. 10. In the Linker behavior option, choose Link SDK assemblies only. 11. Go to the iOS Bundle Signing tab. Here, you will have to choose Signing Identity and Provisioning Profile. Apple provides information about how to create provisioning profiles for distribution at this link: https://
developer.apple.com/library/ios/documentation/IDEs/Conceptual/ AppDistributionGuide/Introduction/Introduction.html. In a nutshell,
find next the steps you will commonly follow to install a distribution profile in your machine. Of course, you must enroll to the developer program to be able to submit an application to the Apple Store. 1. In the iOS Dev Center, https://developer.apple.com/ membercenter, click Certificates, Identifiers & Profiles. 2. In the iOS Apps panel, click Provisioning Profiles. 3. Click +. 4. Select App Store and click Continue. 5. Select an App ID to associate with the provisioning profile and click Continue. 6. To be able to use one development provisioning profile across multiple apps, select a wildcard App ID, if available.
[ 644 ]
Chapter 11
7. Select one or more certificates for production to include in the provisioning profile and click Continue. 8. Only certificates for production are listed. 9. Enter a name for the profile and click Generate. 10. Click Download to download the provisioning profile. 12. Next, you need to set up an Apple Store entry in the iTunes website. Go to https://itunesconnect.apple.com. To learn more regarding iTunes Connect and how to set up an application, refer to the Apple documentation at https://developer.apple.com/library/ios/documentation/ LanguagesUtilities/Conceptual/iTunesConnect_Guide/Chapters/ OverviewofiTunesConnect.html#//apple_ref/doc/uid/TP40011225CH12-SW1.
13. In the Xamarin Studio top menu, choose Build | Archive for Publishing. The setting of the build on the upper-left section of Xamarin Studio has to be Release | iOS Device. The list of the archives window will open.
[ 645 ]
Three, Two, One – Launch and Monitor
14. Click Sign and Distribute, and in the modal window, choose AppStore.
15. Next, you will choose the distribution provisioning profile and click Next. 16. Click the Publish button and save the .ipa file on your hard disk. 17. After saving the file, you can choose Open Application Loader and click the button. 18. Log in with your Apple Developer credentials. Click Deliver Your App and click Choose. Select the .ipa file you previously saved on your hard disk and click Open. 19. Application Loader will connect to iTunesConnect and provide information that matches the application package. Click Next to upload the application. 20. You now need to return to https://itunesconnect.apple.com and find the build you just uploaded. Select the build, click Save, and then click Submit for Review. [ 646 ]
Chapter 11
21. There are a couple more steps answering questions and that's it, you are officially waiting for Apple to review your application and respond.
How it works…
First things first, no application can be submitted if you are not a registered Apple developer. There is a fee and with it the power of deploying your cool app ideas in the Apple Store is right in front of you. It's not the most straightforward process when it comes to submitting apps in the Apple Store. The identity part is especially cumbersome, but believe me, it is better than five years ago, and it gets better after a while working with the development and distribution profiles. In application settings, choosing only ARM64 might make sense if you just want to support the latest devices; this may be something you want, but to target a larger audience we simply choose ARMv7 + ARM64, which will target 32 bit devices as well. The LLVM compiler will generate code that will be smaller and perform better. Note that this option works only in release builds; it is not supported with the debugger. Choosing the latest SDK is a requirement from Apple. It is not a good practice to leave as default, as this selects the latest and it may cause you build problems in case Apple releases an SDK version that might introduce exceptions to your application. Always build and test your application when Apple releases a new SDK to make sure there are no surprises. This option has nothing to with compatibility; the SDK does not define the platforms the app will run. You can choose 9.2 and still have a deployment target of iOS 8.0. This option can be found in the Info.plist configuration file. The linker checks all your code and chooses which methods are not used and should be removed from the build. The Don't link option will not remove anything and results in a bigger size of the binary. Link SDK assemblies only will filter the SDK assemblies for the removal of redundant code, and Link all assemblies will go through everything that you're using in the applications. This is pretty dangerous, especially if reflection is used in an assembly, as the linker will not detect which methods you are using and will remove them, which will of course cause crashes. The safest option to go with is what we've selected: Link SDK assemblies only.
[ 647 ]
Three, Two, One – Launch and Monitor
Publishing Android applications
You did it! The Android world is the major market users in percentage in the world. It is polished and ready, so now you have to publish it to the store. Don't hurry. There are a couple of settings that need to be reviewed and configured, the distributed application package needs to be created, and then the package can be submitted to the store. You may choose any Android store you want. In this recipe, we will introduce Google Play Store, which is Google's official Android store, the most well known in the world.
How to do it…
1. You can start off with an already created Xamarin.Forms cross-platform solution, but if you want it is easy to just create a new one. In the Xamarin Studio top menu, choose File | New | Solution… and choose Xamarin. Forms App in the Cross-platform | App section. Click Next.
[ 648 ]
Chapter 11
2. Next, choose an app name; we simply set it to PublishDemo. Also notice Organization Identifier, which is important for publishing. Don't worry though, you can change it later. Click Next.
[ 649 ]
Three, Two, One – Launch and Monitor
3. In the last screen, check the Use Xamarin Insights checkbox, as it is always good to include insights in your application. To learn more about how to use Xamarin Insights, go to the Using Xamarin Insights recipe. Click Next.
4. Right-click the PublishDemo.Droid project and select the Android Build section. 5. Set Configuration to Release. 6. Check the Enable ProGuard checkbox.
[ 650 ]
Chapter 11
7. Select the Advanced tab in the Android Build section. Here, select the CPU architecture binaries you want to include in the package, select armeabi and armeabi-v7a options, but bear in mind that you can always select another, for example if you are planning to target arm64-v8a CPU architectures like the new Nexus 9. 8. Select the Android Application section and make sure that you have set Package name. The package name must be lowercase, no spaces, and follow a reverse-DNS format.
[ 651 ]
Three, Two, One – Launch and Monitor
9. Next, in the Application icon, select a PNG image from the list. This listing is from the Resources/drawable folder. 10. Version number is an integer and it is used as a build number. You can change this value to whatever makes sense for you; it is never shown to the user. 11. Version name is the release version identifier from which the user is able to identify the application version. 12. For Minimum Android version, set the minimum API level that your application supports. 13. In Target Android version, define what your application is tested and guarantee that runs best.
[ 652 ]
Chapter 11
14. There is a third setting regarding the Android version. Select the General section, you will find at the top the Target framework option. Here, we usually select the latest released framework, which instructs the API level that your application will compile against.
15. We're ready to create a package. In the Xamarin Studio top menu, select Build | Archive for Publishing and wait until the Archives window is shown. 16. In the Archives window, select the archive created and click Sign and Distribute....
[ 653 ]
Three, Two, One – Launch and Monitor
17. There are two options: you can save the APK to your disk or upload it directly from Xamarin Studio to Google Play Store.
[ 654 ]
Chapter 11
18. Click the Ad-Hoc option. You will need to create a new signing identity to move forward. 19. Click Create a New Key and fill out all the fields.
[ 655 ]
Three, Two, One – Launch and Monitor
20. Click OK, select the newly created key and click Next.
21. This is the last screen to export an APK to the disk. Click Publish, choose a location to save the file, and then enter the key password of the signing identity.
[ 656 ]
Chapter 11
22. Having the APK file, you can now manually upload it to the Google Developer Console, manually install it to your device, or send it via e-mail to beta testers. 23. To upload the application from Xamarin Studio to Google Play via the Xamarin Studio IDE, refer to the detailed guide in the following link that will take you through the process of the Google Developer Console: https:// developer.xamarin.com/guides/android/deployment,_testing,_ and_metrics/publishing_an_application/part_3_-_publishing_an_ application_on_google_play/.
[ 657 ]
Three, Two, One – Launch and Monitor
How it works…
The Enable ProGuard setting will obfuscate and remove unnecessary code from your application byte code, meaning any unused methods from your binary and libraries that you are referencing will be stripped out creating a smaller application package. This option is supported only in Release configuration mode. Choosing many architectures in you package will increase the size of the APK file. Consider the option in the General tab of the Android Build section, Generate one package (.apk) per selected ABI, if you want to create a package for each CPU architecture build. We assume that you don't have an existing key. If you already have one, you already have submitted or exported an application package before and you can reuse this key for all your applications. You can install an APK manually on a device by copying the file to the device storage using the device file browser, the adb, or several GUI tools available on the Internet. Also, in Device Settings | Security, you need to enable the Unknown sources option for installation to begin.
There's more…
Right-click the Android project and in the Android Build section, General tab, there's a setting, Enable Multi-Dex. Android applications contain executable byte code. The Dalvik executable file (.dex) format has a limitation of 65k of total native methods referenced. The Enable ProGuard setting will dramatically help reduce the application method references by removing unused code, but it is possible for an application to hit this limitation. Enabling this option will split the .dex file into separate files. For enterprise license users only, Xamarin provides a feature that you can select in the Android Build section, General tab, Embed assemblies in native code. This option will take any .NET assemblies and wrap them into a native Android library that makes it harder for someone who wants to de-obfuscate and read your code.
[ 658 ]
Chapter 11
On the Android Build, General settings, there is a section at the bottom named Code Generation with the option Enable AOT (Experimental), available to business subscribers. This is a feature that is in the testing phase, at the time of writing this module, by Xamarin. Xamarin.Android supports JIT code generation, and enabling this option means you will have pre-compiled code that results in higher performance and faster startup times. Bear in mind that with the new ART Android runtime, there is support for Ahead of Time compilation on the device, but this has no relation to the option discussed here. The Xamarin option is targeting the managed code of the application and ART is meant for the Android native part of the application.
See also
• https://developer.xamarin.com/guides/android/deployment,_ testing,_and_metrics/publishing_an_application/
Publishing Windows Phone applications
Maybe the Windows Store is not as popular as its iOS and Android competitors, and the multitude of applications available is not so great, but this is an opportunity! Now that you finished your Xamarin.Forms Windows Phone platform, let's see how to prepare it for publishing. Microsoft currently has some different phone/tablet versions starting from Windows Phone 8.0/8.1 Silverlight, Windows Phone 8.1 (WinRT), Universal Windows, Windows 8.1, and Windows 10. The Visual Studio template, as of the writing of this module, will create Windows Phone 8.1 (WinRT), Windows 8.1, and Universal Windows projects along with a PCL core library, the iOS, and the Android projects. Xamarin additionally continues supporting the Windows Phone 8.0/8.1 Silverlight project types. For this recipe, we will configure and package the Windows Phone 8.1 application, but the settings and packaging is the same for all the Windows platforms.
[ 659 ]
Three, Two, One – Launch and Monitor
How to do it…
1. Start Visual Studio and in the top file menu, select File | New | Project…. In the Cross-Platform section, choose Blank App (Xamarin.Forms Portable), set the name to PublishDemoPhone and click OK.
2. Right-click the PublishDemoPhone.WinPhone (Windows Phone 8.1) project and select Set as StartUp Project. 3. From the top toolbar menu, change the configuration to Release.
[ 660 ]
Chapter 11
4. Right-click the PublishDemoPhone.WinPhone (Windows Phone 8.1) project and select Properties.
5. In the Application tab, click the Assembly Information… button. Here, try to fill out as much metadata information as you can; it's straightforward what input is expected.
[ 661 ]
Three, Two, One – Launch and Monitor
6. Microsoft took care of the build process for you and haven't left you with much to configure. The most important setting is in the Build tab, the Optimize code checkbox. Changing the configuration mode to Release notice that it is already checked by default.
7. Go to the Application tab and click the Package Manifest… button. 8. In the Package.appxmanifest Application tab, change Display name and Description to something more meaningful than a namespace.
9. Click the Visual Assets tab. Here, you will need to add the application icon, tile icons, badge logo, splash logo, and so on. Assets is a big topic and there are specific UX guidelines from Microsoft in detail. Click the More information link button, which will take you to the following link: https:// msdn.microsoft.com/en-us/library/windows/apps/mt412102.aspx. The default images are located in the Assets folder of the project. [ 662 ]
Chapter 11
10. For the Requirements and Capabilities tabs during development and testing, you have already added what is needed. Just make sure you check one more time. 11. Go to the Packaging tab. Here, you should change Publisher display name and Application version. 12. Right-click PublishDemoPhone.WinPhone and select Build. Go to the bin/ Release folder, grab the .xap file, and upload it to the Windows Store.
How it works…
Packaging an application in Visual Studio is simple when it comes to comparing with iOS and Android submission preparations of the binary. Setting some metadata information takes five minutes. Adding the visual assets is more complicated, but fortunately, Microsoft has documented this part very well. Pay attention to the Optimize code option we checked in the Application tab. This option will optimize your code and make it smaller and faster in runtime. This hasn't caused any problems in my experience so far, but if you notice that your application behaves differently in Release mode than Debug mode then you should consider unchecking it.
To have access in Windows Store and upload applications, you need to enroll to the Windows Developer program, which you can easily do by paying the one-time fee of $19/$99 USD (always subject to change) for single developer and enterprise licenses. See more details and information at the following link: https://dev.windows.com/ en-us/programs/join.
There's more…
Microsoft has provided two ways to beta test your application. It is always nice to fix any major issues detected by users that were not part of the development process before your actual deployment. You might of course want to regularly update the beta testers with a version of a beta release cycle. One way is with Beta Publication, which supports sending the application via e-mail invitation in up to 10,000 devices. An alternative is uploading the app to the Windows Store, but not make it visible in customers' searches. This way, only users with a specified URL link can install the application. [ 663 ]
Three, Two, One – Launch and Monitor
Also, consider using the Windows App Certification Kit, a utility that you should use to validate that your application binary passes all the tests before submitting to the Windows Store. More details are at the following link: https://dev.windows.com/ en-us/develop/app-certification-kit. Windows Store is similar to Apple Store and Google Play. It has submission policies and manual live testing for the application submission process. Visit the following link for an overview of the policies: https://msdn.microsoft.com/en-us/ library/windows/apps/dn764944.aspx. • Have a good description for customers to download the app • No customer misleading • No abuse of the ecosystem or customers • No offensive or illegal activity • The application must be testable
[ 664 ]
Module 3
Mastering Cross-Platform Development with Xamarin Master the skills required to steer cross-platform applications from drawing board to app store(s) using Xamarin
Developing with Xamarin This chapter examines the Xamarin framework and architecture on different target platforms and identifies the differences and similarities. It also includes introductory information and tips on preparing the development environment for Xamarin and covers some of the Xamarin development essentials. This chapter is divided into the following sections: • Cross-platform projects with Xamarin • Target platforms • Setting up the development environment • Emulator options • A typical Xamarin solution structure • Quality in cross-development
Cross-platform projects with Xamarin
Developers are enjoying a new era in which development is not restricted to one single application platform but spans across various media such as cellphones, tablets, personal computers, and even wearable devices. The shared code and assets between the development projects improves the elegance and the quality of the work. There is also a direct correlation between the robustness, the effort required for maintaining a multi-platform application, and the reusable modules. Universal application is a term previously used to identify applications targeting devices running on the iOS operating system (the iPhone and iPad). However, the same term is now used to describe Windows Runtime applications (Windows Store and Windows Phone 8.1 - WinRT) and Android applications for phones and tablets. With the release of Xamarin, a truly universal application concept was born. When considering Xamarin applications, the term, universal, refers to applications that run on all three platforms and adapt to the system resources. [ 667 ]
Developing with Xamarin
In this universal application context, developers are now finding it difficult to get the necessary solutions for common tasks on all three platforms. Moreover, taking on each platform as a separate development project results in wasted developer hours even though the main driving factors for such an application, namely data, business logic, and UI, are conceptually almost identical on all platforms. Development strategies and patterns for the Xamarin platform, some of which are described in the rest of this module, try to resolve some of these problems and provide the developers with the tools and strategies necessary to produce crossplatform, manageable, and quality products.
Xamarin as a platform
Xamarin was initially born as a community effort to port the .NET libraries and common language runtime compilers to different operating systems. Initial attempts intended to create a set of binaries to develop, compile, and run applications written in C#, the indigenous language of .NET, on Unix-based platforms. This project, Mono, was later ported to many other operating systems, including iOS (Mono-Touch) and Android (Mono for Android). The emergence of the Xamarin development platform created a new development niche creating products for three separate platforms at the same time, while allowing users to adapt their existing .NET development skills to these new platforms and produce applications for a wider range of devices and operating systems. Microsoft has been a strong supporter of Xamarin platform and toolset since the early phases. As you will see in the remainder of the chapter, Xamarin tools were fully integrated into Visual Studio and finally included in the Visual Studio 2015 setup. This partnership lasted until the eventual acquisition of Xamarin by Microsoft which was publicly announced in March 2016.
Xamarin provides compilers for each of the mentioned platforms so that the code written in the .NET framework (-alike) is compiled into native applications. This process provides highly efficient applications that differ greatly from interpreted mobile HTML applications. As well as native compilation, Xamarin also provides access to strongly typed platform-specific features. These features are used in a robust manner with compile-time binding to the underlying platform. Platform-specific execution can also be extended with native invocations which is possible with the interop libraries.
[ 668 ]
Chapter 1
Xamarin as a product
Xamarin, as a development suite, comes in different flavors. Developers with different sets of knowledge and experience can use these tools to set up their development environment according to their own needs. The Xamarin development environment can be configured on different operating systems. However, it is currently not possible to develop for all three platforms on the same operating system. For developers who are looking to use the familiar interface of Visual Studio and leverage existing skills, Xamarin extensions for Visual Studio offer a suitable option. Once the extensions are installed, the environment is ready to develop Android and Windows Phone applications. This extension lets the developers take full advantage of Visual Studio, which includes designers for both of these platforms. In order to develop iOS applications, you need to go through the so-called pairing process of Visual Studio with an Apple OS X build machine. The build machine is used in return to visualize storyboards in the development environment (Visual Studio), compile iOS code, and debug applications. The second option is to use Xamarin Studio. Xamarin Studio is a complete IDE with some of the features you are familiar with from Visual Studio, such as intellisense (smart code completion), code analysis, and code formatting. If you run Xamarin Studio on Apple OS X, you can develop for Android and iOS platforms with this IDE. However, with Xamarin Studio on Windows, you can only target the Android platform. An important part of this development suite is the real-time monitoring tool called Xamarin Insights. Xamarin Insights lets developers monitor their live applications to help detect and diagnose performance issues and exceptions, and discover how the application is used. Xamarin Insights can also be connected to other applications so, for instance, application errors can be directly pushed into a bug tracking system.
Target platforms
As mentioned, Xamarin created a new platform in which the development efforts target multiple operating systems and a variety of devices. Most importantly, compiled applications do not run an interpreted sequence but have a native code base (such as Xamarin.iOS) or an integrated .NET application runtime (such as Xamarin.Android). In essence, Xamarin replaces the Common Language Runtime and IL for .NET applications with compiled binaries and an execution context, the so-called mono runtime.
[ 669 ]
Developing with Xamarin
Xamarin on Android
With Android applications, mono runtime is placed right on top of the Linux kernel. This creates a parallel execution context to the Android runtime. Xamarin code is then compiled into IL and accessed by mono runtime. On the other hand, Android runtime is accessed by the so-called Managed Callable Wrappers (MCW) which is a marshalling wrapper between the two runtimes. The MCW layer is responsible for converting managed types to Android runtime types and invoking Android code at execution time. Every time that .NET code needs to invoke Java code, this JNI (Java Interop) bridge is used. MCW provides a wide range of applications including inheriting Java types, overriding methods and implementing Java interfaces. The following image shows the Xamarin.Android architecture:
Figure 1: Xamarin.Android Architecture
Android.* and Java.* namespaces are used throughout the MCWs to access
device- and platform-specific features in Android runtime and Java APIs such as facilities like audio, graphics, OpenGL, and telephony . Using the interop libraries, it is also possible to load native libraries and execute native code in the execution context with Xamarin.Android. The reverse callback execution in this case is handled through Android Callable Wrappers (ACW). ACW is a JNI bridge which allows the Android runtime to access the .NET domain. An ACW is generated at compile-time for each managed class that is directly or indirectly related to Java types (those that inherit Java.Lang.Object).
[ 670 ]
Chapter 1
Xamarin on iOS
In iOS applications, the use of an integrated parallel runtime is (unfortunately) not permissible under the iOS SDK agreement. According to the iOS SDK agreement, interpreted code can only be used if all of the the scripts and code are downloaded and run by Apple's WebKit framework. With this restriction in place, developers can still develop applications in .NET and share code over the other three platforms. At compile time, projects are first compiled into IL code and then (with the Mono Touch Ahead-Of-Time compiler—mtouch) into static native iOS bits. This means that iOS applications developed with Xamarin are completely native applications.
Figure 2: Xamarin.iOS Compilation
Xamarin.iOS, like Xamarin.Android, contains an interop engine that bridges the .NET world with the Objective-C world. Through this bridge, under the ObjCRuntime namespace, users are able to access iOS C-based APIs, as well as using the Foundation namespace, and can use and derive from native types and access Objective-C properties. For instance, Objective-C types like NSObject, NSString, and NSArray are exposed in C# and provide binding to underlying types. These types can be used either as memory references or as strongly-typed objects. This improves the development experience and also increases type-safety.
[ 671 ]
Developing with Xamarin
This static compilation is the main reason for using a build machine to develop iOS applications with Xamarin on the Windows platform. Therefore, there is no reverse-callback functionality in Xamarin.iOS where calls to native runtime from .NET code are supported but calls from native code back to .NET domain are not. There are other features that are disabled because of the way that Xamarin.iOS applications are compiled. For example, no generic types are allowed to inherit from NSObject. Another important limitation is the fact that no dynamic type creation is allowed at runtime which, in return, disables the use of dynamic keywords in Xamarin.iOS applications. Xamarin.iOS application packages, if built in a debug configuration, are much larger than their Release counterparts when compared to other platforms. These packages are instrumented and not optimized by the linker. Profiling of these packages is not allowed in Xamarin.iOS applications.
In a similar way to Xamarin.Android development, with Xamarin.iOS, it is also possible to re-use native code and libraries from managed code. To do this, Xamarin provides a project template called a binding library. A binding library helps developers create managed wrappers for native Objective-C code.
Windows Runtime apps
Even though Xamarin does not include Windows Runtime as a target platform nor provide specialized tools for it (other than Xamarin.Forms), cross-platform projects that involve Xamarin can and generally do include Windows Runtime projects. Since .NET and C# are indigenous to Windows Runtime, most of the shared projects (such as portable libraries, shared projects, and Xamarin.Forms projects) can be reused in Windows Runtime with no further modification. With Windows Runtime, developers can create both Windows Phone 8.1 and Windows Store applications. Windows Phone 8 and Windows Phone 8.1 Silverlight can also be targeted and included in the PCL description.
Setting up the development environment Xamarin projects can be carried out in various development environments. Since a number of platforms are involved in such projects, the operating system, the IDE selection, and the configuration are all crucial parts of the preparation.
[ 672 ]
Chapter 1
Environment setup not only depends on the target application platforms but also on the Xamarin license. A comparison between different licensing options and pricing information can be found on the Xamarin website (https://store.xamarin.com/).
Choosing the right development OS
Android applications can be developed and compiled on Windows using both Xamarin Studio and Visual Studio with Xamarin extensions installed, as well as on an Apple OS X operating system with Xamarin Studio for Mac installed. For iOS application development, whether using Visual Studio on Windows or Xamarin Studio on Apple OS X, an Apple Macintosh computer, running at least OS X Mountain Lion, is required. The build machine should have the Xcode development tools with iOS SDK together with the Xamarin.iOS suite installed. On the other hand, Windows Store applications can only be developed on the Windows platform. Apple OS X
Microsoft Windows
Xamarin Studio
Xamarin Studio
iOS Apps
Yes
Android Apps
Yes
Visual Studio Yes (with OS X Build Machine)
Yes
Yes Yes
Windows Store Apps Figure 3: Development IDEs on OS X and Windows
On the virtualization front, developers are also limited. OS X cannot be installed and run on a non-Apple branded machine nor can it be virtualized, according to the end user agreement. On the other hand, you can set up a virtual machine on an OS X development machine for Microsoft Windows and Visual Studio. However, in this case, the system should be running nested virtualization for Hyper-V to run Visual Studio for Windows Phone and Android emulators. Even though Parallels and VMWare Fusion support nested virtualization, Microsoft doesn't support nesting Hyper-V and, therefore, such machines may be unstable.
[ 673 ]
Developing with Xamarin
Xamarin Studio setup and configuration
Xamarin Studio can be set up on both the Windows and OS X operating systems. Developers can download it from www.xamarin.com and follow the installation instructions. Xamarin components for target platforms (for example, Xamarin.iOS, Xamarin.Android, and so on) together with the dependencies for these platforms (for example, Android SDK) should be downloaded and installed on the development machine. One required component for OS X, which has to be installed separately and configured, is the iOS SDK with the Xcode development environment.
Figure 4: Xamarin Setup on Mavericks (OS X 10.9)
[ 674 ]
Chapter 1
On Microsoft Windows, it is important to mention that Xamarin Studio only supports the development of Android applications. Neither Windows Phone nor iOS application (even with the remote build machine) projects can be viewed, modified, or compiled with Xamarin Studio on Windows.
Figure 5: Xamarin Dev. Environment Setup on OS X
While developing on OS X, the only option for developing Windows Phone applications together with iOS and Android, is to use a Windows virtual machine and run Visual Studio in parallel with Xamarin Studio. This setup is also helpful for developers who use Team Foundation Server as the source control, since they can use the enhanced integration offered by Visual Studio Client rather than the standalone TFS Everywhere. It can also be set up so that the OS host machine can be paired with Visual Studio to become the build host for iOS applications.
[ 675 ]
Developing with Xamarin
Visual Studio setup and configuration
A typical Windows development platform configuration for Xamarin projects includes Visual Studio 2013 or 2015, an Apple OS X build host and Hyper-V and/or VirtualBox to be able to use Android and Windows Phone emulators. Xamarin.iOS applications are then compiled and emulated on the Apple OS X build host.
Figure 6: Windows Platform Xamarin Development Environment
In spite of the fact that it is technically possible to run OS X with a virtual machine in the Microsoft Windows environment, Apple's license agreement does not allow this: "2.H. Other Use Restrictions: The grants set forth in this License do not permit you to, and you agree not to, install, use or run the Apple Software on any non-Apple-branded computer, or to enable others to do so."
On Microsoft Windows, the Xamarin installation is similar to the Xamarin Studio setup on Apple OS X. All of the prerequisites for Xamarin development are installed with the Xamarin for Windows package, together with the Visual Studio extension.
[ 676 ]
Chapter 1
Figure 7: Visual Studio 2015 Setup
One of the key differences between OS X and Microsoft Windows is that Visual Studio 2015 now includes cross-platform development tools such as Android SDK, development kits, and Xamarin project templates. Therefore, the Xamarin installation is only responsible for installing the extensions for the requested platforms (that is, Xamarin.iOS and/or Xamarin.Android).
[ 677 ]
Developing with Xamarin
In order to develop and test iOS applications and visualize and edit storyboards with Visual Studio, an Apple OS X machine must be connected to Visual Studio as a build host. Xamarin 4.0 introduced the concept of Xamarin Mac Agent, which is a background process on the OS X machine providing the required SSH connection to Visual Studio (a secure connection over port 22). Prior to Xamarin 4.0, the build host machine needed to run the so-called Mac build host which was used to pair the Mac host with Visual Studio. The only prerequisites for Xamarin Mac Agent are to have Xamarin.iOS installed on both the Windows workstation and the OS X build host and the build host to have a remote login enabled for the current user. In Visual Studio, the Find Xamarin Mac Agent dialog helps establish the remote connection.
Figure 8: Xamarin.iOS Build Host
It is important to keep in mind that the Mac machine paired with Visual Studio has to have Xcode with iOS SDK installed. A developer account (either enrolled into the app developer program or not) must also be added to the accounts configuration section of Xcode. If the account associated with Xcode does not have a paid subscription to the developer program, the platform for the iOS projects can only be set for simulator and debug selection to one of the simulator options, not an actual device. Otherwise, the user will be presented with an error message such as, No valid iOS code signing keys found in keychain. [ 678 ]
Chapter 1
Emulator options
There are a number of emulators for compiled Xamarin projects for the target platform and the development environment. Developers have most flexibility with the emulator for the Android platform, whereas the options for iOS and Windows Store Apps are limited to the SDK-provided emulators.
Emulators for Android
Android applications can be run and tested on a number of emulators on both Microsoft Windows and Apple OS X operating systems. Android SDK comes with the default emulator that is installed on the development machine. This emulation option is available both on OS X and Windows operating systems.
Figure 9: AVD and Genymotion Emulators
This Android emulator uses the Android Virtual Devices (AVD) to emulate the Linux kernel and the Android runtime. It does not require any additional virtualization software to run, however, the lack of virtualization support makes AVD much less responsive and makes the startup time relatively longer. It also provides a wide range of emulation options for developers, from SMS and telephony to hardware, peripherals, and power events.
[ 679 ]
Developing with Xamarin
The Genymotion emulator (https://www.genymotion.com/) is one of the most popular emulation options for Xamarin and Android developers. Although it is available with a free license, the free version only allows for GPS and camera emulation. The Genymotion emulator runs on (and is installed with) VirtualBox virtualization software. VirtualBox together with Hyper-V Virtual Box software cannot be run alongside Hyper-V virtualization software, which is required for Windows Phone development and emulation on Windows operating systems. In order to use both the Windows Phone emulator and the Genymotion Android emulator, you can create a dual boot option to disable and enable Hyper-V on Windows start-up. bcdedit /copy {current} /d "No Hyper-V" bcdedit /set {} hypervisorlaunchtype off
This would create a second boot option to start Windows without the Hyper-V feature so that the virtualization can be used by VirtualBox.
The last and the most recent Android emulation option is the Visual Studio Android emulator. This Android emulator runs on Hyper-V and provides various device API versions and emulation options for developers.
Figure 10: Visual Studio Android Emulator
[ 680 ]
Chapter 1
The Visual Studio Android emulator is installed as part of the Visual Studio 2015 installation and can also be installed as an extension later. The emulator provides a similar experience to the Windows Phone emulator and allows developers and testers to use almost the same set of emulation options with different device profiles as well as different API levels.
iOS emulation
iOS emulation is only possible with the Xcode tools and iOS SDK. The iOS simulator can be started either directly on Apple OS X while developing with Xamarin Studio, or by pairing the build machine with the Visual Studio Xamarin extension running on Microsoft Windows. It also can be used to test both iPhone and iPad applications.
A typical Xamarin solution structure
A Xamarin solution can be composed of different types of projects. Some of these projects are platform-specific projects and the others are shared project types or modules that make it possible to reuse code across platforms.
Figure 11: Xamarin project solution structure on Visual Studio and Xamarin Studio
[ 681 ]
Developing with Xamarin
Portable class libraries
Portable class libraries are the most common way of sharing code between cross-platform projects. PCLs provide a set of common reference assemblies that enable .NET libraries and binaries to be used on any .NET-based runtime or with Xamarin compilers—from phones to clients, to servers and clouds. PCL modules are designed to use only a specific subset of the .NET framework and can be set to target different platforms.
Figure 12: Portable Class Library Targets
Microsoft has a designation for each target combination and each profile also gets a NuGet target. A subset of .NET libraries for portable class libraries were released through NuGet with the release of Visual Studio 2013. This makes it possible for developers to release their work through NuGet packages, targeting a wide range of mobile platforms (see the NuGet packages section for more information).
[ 682 ]
Chapter 1
The currently preferred profile and the greatest common subset for Xamarin projects is the so-called Profile 259. The Microsoft support designation for this profile is the .NET Portable Subset (.NET Framework 4.5, Windows 8, Windows Phone 8.1, Windows Phone Silverlight 8) and the NuGet target framework profile is portable-net45+netcore45+wpa81+wp8.
While creating a PCL, the biggest drawback is the fact that no platform-specific code can be included in or referenced by the project. This caveat is generally addressed by the abstraction of platform-specific requirements or by using dependency injection or similar methods to introduce the implementation in platform-specific projects. For instance, in the device-specific peripheral example below, the common portable class library has a constructor that accepts two separate interfaces which can be injected with a dependency injection container or can be initialized with a device-specific implementation. The common library, in return, creates a business logic implementation, as shown: namespace Master.Xamarin.Portable { public class MyPhotoViewer { private readonly IStorageManager m_StorageManager; private readonly ICameraManager m_CameraManager; public MyPhotoViewer(IStorageManager storageManager, ICameraManager cameraManager) { m_StorageManager = storageManager; m_CameraManager = cameraManager; } public async Task TakePhotoAsync() { var photoFileIdentifier = await m_CameraManager.TakePhotoAndStoreAsync(); var photoData = await m_StorageManager .RetrieveFileAsync(photoFileIdentifier); // TODO: Do something with the photo buffer } } /// /// Should be implemented in Platform Specific Library [ 683 ]
Developing with Xamarin /// public interface IStorageManager { Task StoreFileAsync(byte[] buffer); Task RetrieveFileAsync(string fileIdentifier); } /// /// Should be implemented in Platform Specific Library /// public interface ICameraManager { Task TakePhotoAndStoreAsync(); } }
Shared projects
The term, shared project, was initially coined by the Microsoft team with the release of Universal Apps for Windows Phone and Windows Runtime (that is, Visual Studio 2013). With the arrival of Xamarin, shared projects can also be referenced by Android and iOS projects. These types of projects are essentially wrappers or containers for shared code and resource files that are linked to multiple projects and platforms. Shared file assets are included in the referencing projects later and compiled as part of these modules.
Figure 13: Shared Projects
[ 684 ]
Chapter 1
While using shared projects, developers should be careful when including platformspecific code since the shared elements will be included in all the referencing projects and compiled. Compiler directives (for example, #if __ANDROID__) can be introduced in shared projects to denote that certain parts of the code are only for a specific platform. Visualizing platform-specific code in shared projects With Visual Studio (2013 or higher), it is possible to visualize different execution paths according to the combinations of conditional compilation constants.
Figure 14: Visual Studio shared project editor
Visual Studio provides a dropdown in the top corner of the editor window which determines the platform-specific projects that are referencing the shared project. By selecting the project, you can see the disabled sections of the code, according the target platform.
If we used the same example to take a photo, we would need to create two completely different implementations for the same action, as shown here: private async Task TakePhotoAsync() { string resultingFilePath = ""; var fileName = String.Format("testPhoto_{0}.jpg", Guid.NewGuid()); #if __ANDROID__ Intent intent = new Intent(MediaStore.ActionImageCapture); var file = new File(m_Directory, fileName); intent.PutExtra(MediaStore.ExtraOutput, Net.Uri.FromFile(_file)); [ 685 ]
Developing with Xamarin // TODO: Need an event handler with TaskCompletionSource for // Intent's result m_CurrentActivity.StartActivityForResult(intent, 0); resultingFile = file.AbsolutePath; #elif WINDOWS_PHONE_APP ImageEncodingProperties imgFormat = ImageEncodingProperties. CreateJpeg();
// create storage file in local app storage var file = await LocalStore.CreateFileAsync(fileName); resultingFilePath = file.Path; // take photo await capture.CapturePhotoToStorageFileAsync(imgFormat, file); #endif return resultingFile; }
Xamarin.Forms
Xamarin.Forms is the unified library for creating UI implementations for target platforms to be rendered with native controls. Xamarin.Forms projects are generally created as PCL projects and can be referenced by Xamarin.iOS, Xamarin.Android, and Windows Phone development projects. Xamarin.Forms components can also be included in shared projects and can utilize platform-specific features.
[ 686 ]
Chapter 1
Developers can effectively create common UI implementations with these forms, either declaratively (with XAML), or by using the API provided. These views, which are constructed with Xamarin.Forms components, are then rendered at runtime with platform-specific controls. Development projects can be realized with Xamarin.Forms by creating the data access model up until the UI components with a shared implementation, thus raising the amount of shared code between the platforms to as much as, or at times more than, 90%.
NuGet packages
NuGet, which was initially an open source Microsoft initiative to share code among developers, has now become a much larger ecosystem. While NuGet servers can be used as an open source library-sharing platform, many development teams use NuGet as a private company repository for compiled libraries. NuGet packaging is a viable code-sharing and reuse strategy for Xamarin projects since it is supported by both Xamarin Studio and Visual Studio (with no further installation following Visual Studio 2012). The NuGet target framework moniker for Xamarin projects is mono and there are further groupings such as MonoAndroid10, which refers to projects with a target framework of MonoAndroid version 1.0 or higher. Other platform targets are: • MonoAndroid: Xamarin.Android • Xamarin.iOS: Xamarin.iOS Unified API (supports 64-bit) • Xamarin.Mac: Xamarin.Mac's mobile profile • MonoTouch: iOS Classic API
[ 687 ]
Developing with Xamarin
Developers are free to either re-use publicly available NuGet packages or create their own repository to store compiled packages to include in Xamarin projects.
Creating NuGet packages in Visual Studio 2015 With the release of Visual Studio 2015, there is a new project template that should help developers to create and reuse NuGet packages.
Figure 15: The Visual Studio NuGet package project template
More information on creating NuGet packages and publishing them can be found on the NuGet documentation hub: (http://docs.nuget. org/create/creating-and-publishing-a-package)
[ 688 ]
Chapter 1
Components
Components are another approach to re-using compiled libraries and modules in Xamarin projects. The Component Store is built into both Xamarin Studio and Visual Studio and it has gathered a number of re-usable submissions from developers since its release in 2013. Components can be downloaded and installed into projects in the same way as for NuGet packages by using the Xamarin Component Store. The Xamarin Component Store can be found at https://components.xamarin.com.
Quality in cross-development
Some development terms help developers create robust, maintainable, high-quality code when developing for multiple platforms. These code descriptors help development teams identify architectural problems, software issues and random errors.
Reusability
"How much of the code can be reused throughout the project?" Reusability is one of the key quality identifiers in cross-platform development projects. Xamarin, especially with the release of Xamarin.Forms, has provided developers with extensive resources to create platform-agnostic components that can decrease redundancy and reduce developer hours in complex projects. Code quality matrices generated by Visual Studio and unit test coverage results can convert this descriptor into a quantifiable measure.
Abstraction
"How much do the shared components know about the platform?" It is almost unavoidable not to include platform-specific bits in cross-platform solutions. The level that these modules are abstracted to increases the robustness of the shared components and is closely related to how loosely the implemented logic is coupled with the underlying platform. In this way, the shared components can be tested easily with mock or fake libraries without having to create platform-specific test harnesses. Unit test code coverage results help determine the testability of the application.
[ 689 ]
Developing with Xamarin
Loose-coupling
"How easy is it to transpose the project into another platform?" On top of the platform-specific abstracted implementation, an autonomous shared implementation layer creates flexible solutions which can easily be adapted to other platforms. Reducing the dependencies of the shared logic to the underlying platform not only inherently increases the reusability but also the agility of the development projects. The number of conditional compilation blocks or if or else loops for the underlying platform on shared projects identifies the amount of code executed according to the platform.
Nativity
"How much does your application blend into the platform?" Even though the ultimate goal while developing with Xamarin is to create an application that can be easily compiled onto multiple targets, the applications created with Xamarin should look, feel and behave as if they were designed for that specific platform. The UI paradigms and user interaction mechanisms of each platform should be respected while creating a common foundation. Nativity is more of a nominal and subjective measure when compared to the aforementioned code descriptors.
Summary
In this chapter, we have discussed some of the key features of the Xamarin development suite and development on previously described platforms and looked at Xamarin essentials for developing mobile applications. The remaining chapters refer to these key features and the differences between the platforms to identify valuable patterns and strategies to create cross-platform applications with Xamarin. The architectural overview of the target platforms and how Xamarin applications are developed and compiled on these platforms were also discussed. The most important difference between these platforms is that Xamarin.Android (and also Windows Phone) uses .NET binaries and mono (and .NET) runtime to execute code, whereas Xamarin.iOS applications have a completely different setup and double compilation (Ahead-of-Time) to make use of .NET binaries, but not to run them directly.
[ 690 ]
Chapter 1
Whilst developing for Android and iOS platforms with Xamarin, developers are also forced to select between different OS platforms and development IDEs. The selection and configuration of the development environment depends on the targeted platforms. IDE features and emulator and simulator options play an important role in this selection. While providing a familiar interface and letting the developers transfer their .NET-related skills and know-how, the OS X operating system together with Xamarin Studio is currently a more viable option for developing iOS applications. Another important refresher was for the Xamarin solution structure. We talked about how developers can share code between different platforms and re-use public or private stores to include shared modules. Shared projects make up the basis for most cross-platform development patterns and strategies together with portable class libraries. Overall, when using the Xamarin specifications and features, the main objective of developers should be to create loosely coupled, platform-agnostic modules that increase productivity and improve the quality of cross-platform development projects.
[ 691 ]
Memory Management This chapter investigates how memory is managed on iOS and Android with Xamarin runtime. Whilst drawing parallels to the .NET platform, it will provide examples of memory management problems and issues that can cause leaks, and also look at useful patterns that can help developers save valuable resources. This chapter is divided into the following sections: • Application Component lifecycle • Garbage collection • Platform-specific concepts • Troubleshooting and diagnosis • Patterns and best practices
Application Component lifecycle
Each platform in the Xamarin ecosystem has certain processes and states that the applications go through during their execution lifetime. Developers can implement certain methods and subscribe to lifecycle events such as application start, suspension, termination, and backgrounding to handle much needed application state and release resources which are no longer required.
Activity lifecycle (Android)
In Android applications, contrary to the conventional application development model, any activity can be the access point to the application (as long as it is designated as such). Any activity in the application can be initialized at start-up or can be resumed directly when the application is resuming or restarting from a crash. In order to manage the lifecycle of the activities, there are distinct states and events which help developers organize memory resources and program features. [ 693 ]
Memory Management
Active/Running
An activity is said to be in the active state when an application is the application in focus and the activity is in the foreground. At this state, unless extraordinary measures are required by the operating system (for example, in case of system out of memory or application becoming unresponsive), the developer does not need to worry about the memory and resources, as the application has the highest priority. In a creation cycle, OnCreate is the first method that is called by the application. This is the initialization step where the views are created, the variables are introduced, and static data resources are loaded. OnStart or OnRestart (if the activity is restarting after it was backgrounded) is the
second event method in a creation cycle. This method(s) can be overridden if specific data reload procedures need to be implemented. This is the last method called before the activity becomes visible. The OnResume method is called after a successful launch of the activity. This method is the indication that the application is ready for user interaction. It can be used to (re)subscribe to external events, display alerts/user messages, and communicate with device peripherals.
Paused
An activity is paused when either the device goes to sleep having this activity in the foreground, or the activity is partially hidden by another dialog or activity. In this state, the activity is still "alive" but cannot interact with the user. The OnPause event method is called right before the activity goes into the Paused state. This event method is the ideal place to unsubscribe from any external event providers, commit any unsaved changes and clean up any objects consuming memory resources since the user interaction is not possible in the Paused state. The activity will call only the OnResume method when once again the activity has the highest priority, it will not go through the full creation cycle.
Backgrounded
An activity goes into the Backgrounded state when the user presses the home button or uses the app switcher. In this state, it is not guaranteed that the activity will stay alive until the user "restarts" the application.
[ 694 ]
Chapter 2
The OnStop method is called when the application is backgrounded or stopped. The difference between the Backgrounded and Stopped states is that the activity is in the Stopped state when it is being prepared for destruction and it will be followed by the OnDestroy method since the application is dismissed and will not be used by the user anymore. If the user resumes the application, the activity will call the OnRestart method and a full creation process will follow.
Stopped
The Stopped state represents the end of the lifecycle for the activity. The activity enters this state when the user presses the back button signifying that the application is not needed anymore. However, it is also possible that the activity is taken into this state because the system is starved of memory resources and it needs to take down activities that are on the lower priority states like paused or backgrounded. The OnDestroy method follows the Stopped state and it is the last lifecycle event method that is called. It is the last chance for the application to stop long running processes that might cause leaks or clean up other persistent resources. It is advisable to implement most of the resource clean up in OnPause and OnStop methods, since OnDestroy can be called unexpectedly by the system contrary to the user initiated OnPause and OnStop methods.
Restarted
An activity is said to be "restarted" when it comes back to user interaction after it was backgrounded. Restarted activities can reload any saved state information and create an uninterrupted user experience. After going through the initialization steps, the application goes into the Running state again.
Application lifecycle (iOS)
On iOS, the application lifecycle is handled through UI application delegates. Once the delegate methods are implemented and registered, the methods will be invoked by the execution context. public class Application { static void Main(string[] args) { UIApplication.Main(args, null, "AppDelegate"); } }
[ 695 ]
Memory Management [Register("AppDelegate")] public partial class AppDelegate : UIApplicationDelegate { //Implement required methods }
Application events on iOS are a little more complicated than the top-down execution of events on Android. Developers can insert their methods into transitive states using the state-related methods implemented in the AppDelegate.
Figure 1: iOS Application State Transitions
The most important state-related methods are the following: • WillFinishLaunching is the first chance of the application to execute code at launch time. It indicates the application has started to launch but the state has not yet been restored. • FinishedLaunching is called once the state restoration occurs after the WillFinishLaunching is completed. • OnActivated and OnResignActivation are similar to OnPause and OnResume event methods on the Android platform.
[ 696 ]
Chapter 2
• DidEnterBackground is called when the application enters the Backgrounded state. It is similar to the OnStop method on Android but there is a time constriction on this method; the method should execute in less than 5 seconds, and the method exits without notification after the allocated time. If more time is needed to execute certain methods in this delegate, applications can start a background task to complete the execution. • WillEnterForeground and WillTerminate can follow the DidEnterBackground execution. If the former method is called, the application is about to be brought back to foreground and active state, otherwise, the application is prepared to be terminated because the system needs more memory, or the user is closing a backgrounded application.
Garbage collection
Garbage collection (GC) is one of the most effective automated memory management techniques on modern application development platforms. In simple terms, with automated garbage collection, memory resources are allocated for objects used by the application and reclaimed for resources no longer needed by the application. In spite of the fact that garbage collection, as an automated process, takes over the burden of managing memory allocations, it can have a significant impact on performance. This performance handicap is one of the main reasons why there is no garbage collection mechanism on the iOS platform.
In theory, GC is responsible for reclaiming memory resources occupied by runtime elements that cannot be reached by the current executing application. However, this mechanism cannot always identify these unreachable resources correctly and/or have unexpected results while purging the identified memory pointers. Memory leaks occur when an application fails to identify and/or free the resources occupied by unreachable code elements, which can lead to memory exhaustion problems. Dangling pointers happen when a memory region is freed while references still exist in the execution context. These references are then removed and memory can be re-allocated for another use. Double free bugs occur when a memory region is already reclaimed and the application or garbage collector tries to free this region once more.
[ 697 ]
Memory Management
GC on Xamarin projects
Managed code, as defined by the Common Language Runtime in the .NET framework, is application code where the memory resources are managed by the native garbage collector. GC, on initialization, allocates a segment of the memory to store and manage memory resources, which is called the "managed heap". The garbage collection in CLR happens on three different generations where objects with different lifespans live in the heap. Promotion between the generation and survival of objects depend on which generation they are placed in and how they survived prior GC cycles.
SGen garbage collector
SGen garbage collector is the generational garbage collector used in most Xamarin projects (both Xamarin.iOS and Xamarin.Android). SGen performs more frequent garbage collections over smaller sets of objects which makes it more efficient over the conservative Boehm GC. SGen utilizes three heaps, namely The Nursery, Major Heap, and Large Object Space, to allocate memory segments for objects according to their memory requirements, and objects are promoted between the heaps when they survive through GC cycles. In this setup, The Nursery, similar to Generation 0 in CLR on .NET, is where most objects are created and destroyed and most of the GC cycles occur to release memory resources. Objects surviving the minor GC cycles can be promoted to the major heap. The major heap only has major GC passes in case the heap itself is running out of memory. The last heap is only for larger objects that have higher memory requirements, and does not accept promotion from other heaps. It is important to remember that during a garbage collection cycle all the threads registered with the runtime, including the main run loop thread are paused. One exception to this execution pause is the separate process that continues to run the iOS animations.
Boehm garbage collector (iOS only)
Boehm GC (aka Boehm-Demers-Weiser garbage collector) is an open-source garbage collector implementation that was initially created for C/C++ language implementations. As a conservative garbage collector, it still has procedures for leak detection, supports "finalized" semantics, and has limited support for generational implementations which makes it an attractive candidate for implementations and ports on various platforms. An implementation of Boehm GC is only available for Xamarin.iOS applications using the Classic API, in which it is the default garbage collector. [ 698 ]
Chapter 2
Platform-specific concepts
In order to understand the memory management techniques and pitfalls, one must understand some platform-related concepts. Even though Xamarin provides an almost platform agnostic development experience, iOS and Android platforms deal with memory allocations and references slightly differently from .NET CLR and each other.
Object reference types
Referred objects can be classified according to application needs. This classification helps the garbage collector decide whether the memory allocation can be released for the referred objects. A strong reference protects the object from being "garbage collected". A referred object is said to be strongly referenced/reachable when the class instance is directly used by the current execution context. Weak references can be used for class instances when the need for the reference does not interfere with garbage collection. When the referred object is weakly reachable, the dependent section of code has to check whether the object is still alive before using the referenced object. Weak references have two types in CLR according to the dispose and finalization processes implemented by the declaring types: long and short weak references. Long weak references are types that can live on to be recreated and can be finalized by a destructor rather than being disposed or garbage collected. Soft and phantom references are specific to Android runtime. Soft references, in simple terms, are a little more persistent than the weak references, and would only be cleared up by the garbage collector under memory pressure even though the object is no longer strongly reachable. Phantom references are the weakest reference in Android runtime. They are only used to implement specialized object finalization methods and have to be associated with a reference queue for processing.
Automatic Reference Counting (ARC)
Automatic Reference Counting is a compiler feature that was introduced in iOS 5. It is referred to as a compiler feature since it cannot be classified as a garbage collection implementation. It is a static analysis implementation where the compiler analyses the code execution tree and inserts retain and release messages according to the object persistence requirements. With ARC, traditional memory management calls are not allowed to be inserted in the application to allocate memory and release memory addresses. [ 699 ]
Memory Management
Troubleshooting and diagnosis
Profiling is the term used to describe the dynamic system analysis while the target application is running. Profilers generally collect data about metrics such as CPU utilization, framerate values, and most importantly data about memory allocations. Especially with Xamarin projects, since we are dealing with multiple platforms, profiling becomes an important part of testing and diagnostics. There are numerous tools that one can use to profile memory usage on Xamarin projects, Xamarin Profiler being the only one that can be used both for Xamarin.iOS and Xamarin.Android applications.
Xamarin Profiler
Xamarin Profiler is the newest addition to the Xamarin Suite. This profiler has the advantage over other platform-specific applications since it can be run either on OS X or Windows targeting Xamarin.Android or Xamarin.iOS applications.
Figure 2: Xamarin Profiler
[ 700 ]
Chapter 2
It was designed to give developers almost real time (depending on the sampling rate) information about the memory heaps for Xamarin applications. It can also save memory allocation snapshots which can later on be accessed and analyzed. It can be started directly from Visual Studio or Xamarin Studio and can be used with both emulator and real device build/run configurations. Currently there are two instruments you can select in the initial popup window.
Allocations instrument
The first instrument is the Allocations template which provides detailed information on the memory segments and allocations. In this view, developers can see a generalized list of allocations grouped by the class name under the Summary tab. The Call Tree tab gives a list of threads in the application and how they relate to the memory objects. Allocation list provides live data about the object allocations, and the Snapshots tab gives information about the memory snapshots stored.
Time Profiler
Time Profiler is the second instrument that can be used in Xamarin Profiler. It provides valuable information on how much time the application spent executing a certain method. Developers can see a whole stack trace on each method.
Device Monitor (Android only)
Android Device Monitor is hitherto the main diagnostic tool for Android development. And for Xamarin developers, when Android SDK is installed, device monitor can be accessed directly from a tool box item on Visual Studio and under the tools menu on Xamarin Studio. On the main page of the device monitor there is a tree-view displaying each device or simulator that can be attached to with the device monitor. Only a single debugger can be attached to any device at a time, therefore other debuggers have to be detached before using the device monitor.
[ 701 ]
Memory Management
Once the device is selected, developers can get allocation information and the heap state using the graphical interface. It is also possible to trigger garbage collection cycles using the device monitor.
Figure 3: Android Device Monitor attached to Visual Studio Emulator
[ 702 ]
Chapter 2
Instruments (iOS only)
Instruments is a valuable application that is installed together with the Xcode toolset. In this application developers are provided with a big set of diagnostic tools varying from energy consumption, graphic resources, to memory allocations. The allocations instrument has a very similar interface to Xamarin Profiler, and gives almost real-time data about memory objects.
Figure 4: Instruments Profiling Xamarin Application
[ 703 ]
Memory Management
The Xcode Instruments tool can be used both together with an actual device or the iOS simulator. It can be started directly from Xamarin Studio. Once the application is started on the iOS simulator or on the actual device, it becomes available in the target selection window.
Figure 5: Instruments with iOS Simulator set as target
If you are developing Xamarin.iOS applications on Microsoft Windows with an OS X build machine, you will not be able to access the Instruments directly from the development station. Once the application is either on the test device or the simulator, you can start the instruments on the build machine and choose the correct target to analyze.
Monotouch Profiler (iOS only)
Monotouch Profiler was the Xamarin tool used to diagnose memory issues with Xamarin.iOS applications before it was superseded by Xamarin Profiler. It can still be accessed using the Run with Mono HeapShot menu item under the Project menu in Xamarin Studio. While providing useful information about memory allocations and the heap, it currently does not go further than being a lightweight application to take memory snapshots.
[ 704 ]
Chapter 2
Patterns and best practices
While dealing with managed runtime and garbage collection, there are certain patterns and anti-patterns developers must be careful with. If not handled properly, both managed and native objects can produce noncollectable traces, which in turn can cause memory leaks and unnecessary resource consumption.
Disposable objects
The resources managed by the garbage collector are generally limited to memory allocations. Other resources like network sockets, database handles, UI elements, and file/device descriptors need to have additional definitions or mechanisms. In managed runtime, these object resources can be cleaned up in two different ways. The first, less efficient, unpredictable way is to implement a destructor/finalizer. With a finalizer implementation, once the garbage collector decides the object is no longer strongly reachable, the resources such as network sockets can be disposed. However, finalizable objects have to wait for the following GC cycle to be cleaned up and cannot be finalized with developers' initiatives. Another way to clean-up application resources is to implement the IDisposable interface in the class that has the references to the resources. This interface requires only a single Dispose method implementation to get rid of managed resources. The garbage collector also offers a method (GC.SuppressFinalize) to avoid finalization since the object is going to be disposed using the IDisposable interface. public class DisposableFinalizableClass : IDisposable { private ManagedResource m_Resource; // reference to a resource public DisposableFinalizableClass() { m_Resource = new ManagedResource(); // allocates the resource } /// /// /// /// /// ///
Destructor for the DisposableFinalizableClass
Note that we are not overriding the object.Finalize method but providing a destructor for the Finalize method to call
[ 705 ]
Memory Management /// ~DisposableFinalizableClass() { Dispose(false); } /// /// Implementing the Dispose method /// public void Dispose() { Dispose(true); // Letting the GC know that there is no more need for // Finalization, the resources are already cleaned-up GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { if (m_Resource != null) m_Resource.Dispose(); } else { // Called from Finalize // Clean-up any unmanaged resources } } }
The fact that disposable objects can be used together with using blocks, gives a deterministic way for developers to release associated resources as soon as the object is no longer needed.
The lapsed listener problem
One of the most common patterns used with UI elements or legacy API implementations is the observer pattern. As you might know, there are two stakeholders in this pattern, the observer and provider. The observer subscribes to the event provided by the provider to receive updates.
[ 706 ]
Chapter 2
The lapsed listener problem occurs when the observer pattern is implemented incorrectly or better yet incompletely. In this pattern, after the subscription, the provider keeps a strong reference to the observer. If this subscription is not removed before the subscriber goes out of context, the application will leak the subscriber object since it cannot be garbage collected (for example, an Android activity, or a view model). In order to demonstrate this problem, we will use a singleton implementation of Fibonacci sequence with asynchronous methods as the event provider. public event EventHandler CalculationCompleted; public event EventHandler RangeCalculationCompleted; /// /// Calculates n-th number in Fibonacci sequence /// /// Ordinal of the number to calculate public void GetFibonacciNumberAsync(int ordinal) { var task = Task.Run(() => { var result = GetFibonacciNumberInternal(ordinal); if (CalculationCompleted != null) CalculationCompleted(this, result); }); // Avoiding Deadlocks on the UI thread task.ConfigureAwait(false); } /// /// Calculates a range of numbers in Fibonnaci sequence /// /// Ordinal of the first number /// Ordinal of the last number public void GetFibonacciRangeAsync(int firstOrdinal, int lastOrdinal) { var task = Task.Run(() => { var result = GetFibonacciRangeInternal(firstOrdinal, lastOrdinal);
[ 707 ]
Memory Management if (RangeCalculationCompleted != null) RangeCalculationCompleted(this, result); }); task.ConfigureAwait(false); } public static FibonacciSource Instance { get { return m_Instance ?? (m_Instance = new FibonacciSource()); } }
We will implement two separate view models using MvvmCross and use associated views to invoke the asynchronous methods, then navigate back to the main view using the Close method on the view models. In the constructor of each view model, we will be subscribing to the respective event on the FibonacciSource.
Figure 6: Fibonacci Calculator App
[ 708 ]
Chapter 2
In order to investigate any memory leaks, we navigate back and forth between the main and the calculation views. After a couple of iterations on both of the views (that is, single and range), we have the results shown below on the Xamarin Profiler (just using the "Allocations" template.)
Figure 7: Xamarin Profiler Results
You will notice that none of the instances of SingleCalculationViewModel are alive after garbage collection (you can trigger a GC run with GC.Collect()), however RangeCalculationViewModel instances are persistent. The reason for this is the missing unsubscribe call in the close command of the RangeCalculationViewModel. private MvxCommand m_CloseCommand; public ICommand CloseCommand { get {
[ 709 ]
Memory Management m_CloseCommand = m_CloseCommand ?? new MvxCommand(DoClose); return m_CloseCommand; } } private void DoClose() { // FibonacciSource.Instance.RangeCalculationCompleted -= OnCalculationCompleted; Close(this); }
We could have also used the OnPause event on this Android application or any other relevant event on other platforms to get rid of the subscription before the subscriber or the view component that holds the subscriber goes out of context. In this scenario, another solution would be to use a TaskCompletionSource to convert the observable pattern to an awaitable one. Wrapping up the observable Fibonacci source would give you a better control over the subscription and the resulting asynchronous task would be better suited for mobile development and MVVM pattern. private Task CalculateFibonacciRangeAsync(int firstOrdinal, int secondOrdinal) { TaskCompletionSource taskCompletionSource = new TaskCom pletionSource(); EventHandler calcCompletedEventHandler = null; calcCompletedEventHandler = (sender, e) => { FibonacciSource.Instance.RangeCalculationCompleted -= calcCompletedEventHandler; taskCompletionSource.SetResult(e); }; FibonacciSource.Instance.RangeCalculationCompleted += calcCompletedEventHandler; FibonacciSource.Instance.GetFibonacciRangeAsync(firstOrdinal, secondOrdinal); return taskCompletionSource.Task; } [ 710 ]
Chapter 2
Finally, this async task would be called with a ContinueWith statement to set the result in the view model. private void DoCalculate() { if (!string.IsNullOrWhiteSpace(Input1) && !string. IsNullOrWhiteSpace(Input2)) { int numberOrdinal1, numberOrdinal2; if (int.TryParse(Input1, out numberOrdinal1) && int. TryParse(Input2, out numberOrdinal2)) { InfoText = "Calculating"; var fibonacciTask = CalculateFibonacciRangeAsync(numberOrd inal1, numberOrdinal2) .ContinueWith(task => { Result = string.Join(",", task.Result); InfoText = ""; }); fibonacciTask.ConfigureAwait(false); return; } } InfoText = "Invalid Input"; }
Weak references
Weak references can be of great assistance while dealing with loosely coupled application layers. In these type of scenarios, where objects need to be managed outside the class domain, weak referencing can be used to remove these instances from the GC protection based on the notion of reachability because of the strong references they have to other layers of the application.
[ 711 ]
Memory Management
Let us assume in the previous example that the Fibonacci sequence items are handled as reference values with a class called FibonacciItem. This class carries the value calculated and the time it was calculated. public class FibonacciItem { public int Value { get; private set; } private readonly DateTime m_Calculated; public FibonacciItem(int value, DateTime calculatedTime) { Value = value; m_Calculated = calculatedTime; } }
To decrease the processing time, we can now implement a caching mechanism which would force the source to recalculate the value according to the ordinal if it does not already exist in the cache or just does not sound right is disposed of in favor of memory resources. For this purpose we can use the WeakReference to cache Fibonacci items. public class FibonacciCache { // Dictionary to contain the cache. private static Dictionary _cache; public FibonacciCache() { _cache = new Dictionary(); } /// /// Accessor to FibonacciItem references /// /// /// FibonacciItem if it is still alive public FibonacciItem this[int ordinal] { get { if (!_cache.ContainsKey(ordinal)) return null;
[ 712 ]
Chapter 2 if (_cache[ordinal].IsAlive) { // Object was obtained with the weak reference. FibonacciItem cachedItem = _cache[ordinal].Target as FibonacciItem; return cachedItem; } else { // Object reference is already disposed of return null; } } set { _cache[ordinal] = new WeakReference(value); } } }
Cross-domain objects
In Xamarin applications, one of the most common memory issues, cross-heap references, occur when there is a cross-over between the native runtime and mono runtime. This issue stems from the fact that mono runtime is almost handled as a separate domain and managed in a heap only with GC handles to the native domain. In an Android scenario, where Java objects are referenced by managed C# objects or vice versa, the communication between the two runtimes becomes expensive. For instance, if we were implementing the Fibonacci calculator without using the ViewModel pattern, we would want to create a data adaptor to load the range calculation results into a list view. private void OnFibonacciCalculationCompleted(object sender, List result) { RunOnUiThread(() => { var listView = FindViewById(Resource.Id.lstResult); listView.Adapter = new ArrayAdapter(this, Resource. Layout.ListViewItem,
[ 713 ]
Memory Management result.Select(val => val.Value.ToString()).ToArray()); }); }
This implementation has a higher cost of being garbage collected. It also has performance penalties considering the language crossing, not to mention the fact that objects from each world are effectively mirrored increasing the memory allocation costs. The solution here would be to do as much work as possible in the managed world and let the runtime take care of the rest. So instead of using the native ArrayAdapter, we could implement a base adapter that would feed the FibonacciItem instances to the ListView. public class FibonacciResultsAdapter : BaseAdapter { List m_ResultItems; Activity m_Context; public FibonacciResultsAdapter(Activity context, List items) { m_Context = context; m_ResultItems = items; } public override long GetItemId(int position) { return position; }
public override FibonacciItem this[int position] { get { return m_ResultItems[position]; } } public override int Count { get { return m_ResultItems.Count; } } public override View GetView(int position, View convertView, ViewGroup parent)
[ 714 ]
Chapter 2 { View view = convertView; if (view == null) view = m_Context.LayoutInflater.Inflate(Resource.Layout. ListViewItem, null); view.FindViewById(Android.Resource.Id.txtOutput). Text = m_ResultItems[position].Value.ToString(); return view; } }
By implementing the adapter we removed the usage of Java type ArrayAdapter, ArrayList and the Java references to the FibonacciItem instances. The same applies to scenarios where native objects are being inherited in the managed domain. These, so-called, "special objects" are handled differently by the garbage collector. They have to be rescanned for all the references they carry with each garbage collection cycle.
Cyclic references (cycles)
Cyclic references occur, in general terms, when the underlying platform uses some type of reference counting as memory management strategy and the memory is cleaned up according to the number of references to that specific object instance. Reference counting was abandoned by Microsoft with the release of .NET and the introduction of the generational tracing garbage collection. SGen in mono runtime on Android devices also uses some form of a mark and sweep algorithm. In both runtimes, the references are traced from so called "application roots". These objects are the ones that are "presumed" to be alive at the time of a garbage collection cycle. The roots can be: • References to global objects • References to static objects • References to static fields • References on the stack to local objects • References to objects waiting to be finalized • References in CPU registers to objects on the managed heap [ 715 ]
Memory Management
However, as mentioned before, on iOS, garbage collection was abandoned in favor of performance and yet ARC (automatic reference counting) fails to deal with what is called a retain cycle. Retain cycle occurs when the lower elements (aka children) in the creation hierarchy require references to the parent items. In this scenario, when the child or the parent sends a release, the dealloc methods never get to run since there is an extra reference keeping each of the items alive.
Figure 8: Retain Cycle
This native iOS problem becomes a problem in Xamarin applications when managed objects derive from native objects (that is, any object deriving from NSObect) such as UI controls. When managed classes are inheriting from native objects, in order to keep them from getting garbage collected, Xamarin.iOS creates a GCHandle. These GCHandles, together with the managed references between the objects, create the described (indirect) retain cycle. If we were dealing with a parent UIView that holds an array of children and the child view objects that were retaining a reference to the parent object: public class RetainingParentClass : UIView { public RetainingParentClass() { } }
[ 716 ]
Chapter 2 public class RetainingChildClass : UIView { private RetainingParentClass m_Parent; public RetainingChildClass(RetainingParentClass parent) { m_Parent = parent; } }
The following piece of code would create a retain cycle and would cause memory leaks in the application: var parent = new RetainingParentClass(); parent.Add(new RetainingChildClass(parent));
If we were to execute this code in the constructor of a view, every time the application navigates to this view, we would be creating a new parent object, never to be garbage collected.
Figure 9: Instruments view for retained objects
In this case, the easiest fix would be to use a WeakReference while we are creating a reference to the parent object from the child one. Using the weak reference avoids the retain cycle situations and does not interfere with the garbage collection. public class RetainingChildClass : UIView { private WeakReference m_Parent;
[ 717 ]
Memory Management public RetainingChildClass(RetainingParentClass parent) { m_Parent = new WeakReference(parent); } }
Another option would be to implement IDisposable interface to remove the strong link between the objects by setting the references to null before GC.
Summary
In order to manage application resources, one must have a deeper understanding of the application lifecycle. Application lifecycle events, outlined in this chapter, are the main access points to underlying platform runtime on both iOS and Android. If used properly, the event delegates and event methods on both platforms can help developers save valuable resources and avoid memory problems. Other concepts discussed were garbage collection, object references, and automatic reference counting. These concepts make up the foundation of memory management on target Xamarin platforms. We also had a closer look at the diagnostic and profiling tools for target platforms and how they can be used effectively. While iOS and Android platforms each have a native app to analyze memory allocations, Xamarin Profiler provides a unified solution for both platforms. Finally, useful patterns were outlined for different memory related issues and pitfalls. To analyze these patterns, Xamarin Profiler and Instruments were used for Android and iOS applications respectively. In the next chapter, we will be looking at asynchronous implementation techniques and investigate various patterns of multi-threading and background execution.
[ 718 ]
Asynchronous Programming This chapter deep-dives into the asynchronous and multithreaded programming concepts. We will discuss platform-specific problems and give an in-depth description of how threading scenarios are executed on different platforms. The chapter is divided into following sections: • Multithreading on Xamarin • Asynchronous methods • Parallel execution • Patterns and best practices • Background tasks
Multithreading on Xamarin
Xamarin platforms together with Windows Runtime follow the basic principles of a single-threaded apartment model. In this model, in simple terms, a process is assigned a single thread which acts as the main trunk for all the other possible branches to be created from and yield back to. While developers still have the ability to create and consume multiple threads, in modern applications on Xamarin target platforms, this model has been extended with concurrency implementations that delegate the responsibility of thread management to runtime and allow the developer only to define execution blocks which may or may not be executed on a separate thread.
[ 719 ]
Asynchronous Programming
Single thread model
In Android and iOS, each mobile application is started and run on a single thread that is generally referred to as the main or the UI thread. Most of the UI interaction and process lifecycle event handlers and delegates are executed on this thread. In this model, developers' main concern should be keeping the main thread accessible to UI interaction as long as possible. If we were to execute a blocking call on this thread, it immediately would be reflected to the user as a screen freeze or an application nonresponsive error, which will inevitably get terminated by the so-called watch-dog implementation of the underlying platform. In addition to the platform-specific restrictions, users also expect a responsive UI at all times and cannot tolerate frozen screens even for a fraction of a second. If the screen freeze lasts any longer, they will try to forcefully terminate the application (see the Feedback section of Chapter 7, View Elements). Developers can still create, consume, and monitor other threads from the main thread. It is possible to use background threads and invoke long running processes in the background. For this purpose, the System.Threading namespace and threading related classes are available on Xamarin.iOS and Xamarin.Android projects. Moreover, each platform has its own threading implementation under the hood. For example, let's imagine we want to execute a long running process and we do not want this method to block the UI thread. With classic threading, the implementation would look similar to: //var threadStart = new ThreadStart(MyLongRunningProcess); //(new Thread(threadStart)).Start(); // Or simply (new Thread(MyLongRunningProcess)).Start();
Each Thread can give information about the current execution state, and it can be canceled, started, interrupted, or even joined by another thread. Developers can use the threading model for throttling their application and/or executing their code more efficiently without committing the cardinal sin of blocking the UI thread. It might get a little complicated when the process you are executing on a separate thread needs to update a UI component. This would be a cross-thread violation.
[ 720 ]
Chapter 3
For instance, if we wanted to update a UI component from a separate thread in an Android activity, we would need to execute it on the activity as follows (using Activity.RunOnUiThread): this.RunOnUiThread(() => { UpdateUIComponent(); });
The same execution on iOS would look similar to (using NSObject. InvokeOnMainThread): this.InvokeOnMainThread(() => { UpdateUIComponent(); });
For reference, on Windows Runtime the same execution would look like this: CoreApplication.MainView .CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { UpdateUIComponent(); });
The implementation in classic threading gets even more complex when there is an exception or the operation has to be canceled, not to mention the fact that synchronization between threads and thread-safe data flow is completely left to developers or third-party implementations. Another important mishap of using the System.Threading namespace and the classic threading model in Xamarin, is that this namespace and thread-related classes cannot be used in PCLs.
Task-based Asynchronous Pattern
Since the introduction of the Tasks framework in .NET 4.0 and its later adoption by Mono, the Task-based Asynchronous Pattern (TAP) has become de-facto the main implementation strategy for mobile applications. While providing the required abstraction from the treading structure, it also gives the development teams the opportunity to create easily readable, manageable, and scalable application projects. As mentioned earlier, since each Xamarin target platform has the threading implemented according to the underlying runtime, this abstraction that the Tasks framework provides makes it the perfect candidate for asynchronous implementations in cross-platform projects and an invaluable part of portable class libraries.
[ 721 ]
Asynchronous Programming
In this pattern, each execution block is represented by a Task or a Task according to the return value of the block (for example, if the block is returning void, it should be converted to return Task and if the block is returning an int, the method signature should be Task). Tasks can be executed either synchronously or asynchronously, can be awaited for a result or executed as a promise with a callback on completion, can be pushed to another thread-pool or executed on the main thread taking processor time when available. Tasks are especially suited for computationally intensive operations, since they provide excellent control over when and how the asynchronous method is executed. Cancellation and progress support on these methods makes long running processes easily manageable.
Concurrency model on iOS
Concurrency and operation blocks on iOS runtime are Apple's take on the same issues that the Tasks framework is trying to resolve. In essence, the Tasks framework and concurrency model on iOS are the solution to creating multitasking, robust, and easily scalable applications by creating an abstraction layer so that applications do not directly manage threads, but let the operating system decide on where and when to execute operations. The iOS runtime uses operation or dispatch queues to asynchronously dispatch tasks in a first-in-first-out (FIFO) manner. This approach provides automatic thread-pool management as well as a simple programming interface. While the iOS runtime constructs such as NSOperation, NSBlockOperation, and NSOperationQueue are implemented in the Xamarin.iOS framework and are ready to use, the implementations would only be targeting the iOS platform while Tasks can be used on all three platforms.
Asynchronous methods
The Task Parallel Library (TPL) constitutes the core part of parallel computing in the .NET framework and has inherently the same stature in Xamarin runtime(s). Asynchronous method execution, together with the async and await keywords (introduced with C# 5.0), can make the apps more responsive and efficient and decrease the complexity of implementing multithreading and synchronization. Without having the need to implement a parameterized thread, start and push are delegated to a background thread, with so called "awaitables." You can convert your methods to async promises easily with Task or Task as the return type. In return, the runtime chooses the best time to execute the code and returns the result to your execution context. [ 722 ]
Chapter 3
For instance, the previous thread creation example with Tasks would be as simple as: Task.Run(() => MyLongRunningProcess()); // Or Task.Factory.StartNew(MyLongRunningProcess, TaskCreationOptions. LongRunning);
However, the Tasks framework is not only about creating threads or executing nonblocking methods, but also about coordinating and managing these asynchronous tasks in the easiest way possible. There are many static helper methods as well as methods implemented for Tasks that help developers to easily implement some of these coordination scenarios.
Continuation
The ContinueWith function on the Task class allows the developers to chain dependent Tasks together and execute them as one Task as a whole. The continuation delegate is executed once the result from the first task is posted back to the task scheduler. It is important to mention that the first task and the continuation methods are not necessarily executed on the same thread. The code is as follows: Task.Run(() => MyLongRunningProcess()) .ContinueWith(task => MySecondLongRunningProcess());
In case the second task was dependent on the result from the first task: Task.Run(() => MyLongRunningProcess()) .ContinueWith(task => MySecondLongRunningProcess(task. Result));
Cancellation
CancellationToken and CancellationTokenSource are used as the remote token
to control the execution lifetime of an async method, thread, or a number of threads and the event source that the token reflects the events of. In simple terms, CancellationTokenSource is responsible for throwing either time-based or manual cancel events and these events can be retrieved through the token in the context of the asynchronous method.
[ 723 ]
Asynchronous Programming
You can create a cancellation source using the default constructor and time-based cancellation can be added to the token: m_CancellationSource = new CancellationTokenSource(); var token = m_CancellationSource.Token; // You can cancel it after a certain amount of time, which would trigger an OperationCanceledException // m_CancellationSource.CancelAfter(2000);
Once we are executing an async method, we can use the token from this source, or we can associate it with a TaskFactory to create a cooperating list of tasks: Task.Run(() => { // Executing my long running method if (token.IsCancellationRequested) { token.ThrowIfCancellationRequested(); } }, token);
Or: var taskFactory = new TaskFactory(m_CancellationSource.Token); taskFactory.StartNew(() => { // Executing my long running method if (Task.Factory.CancellationToken != CancellationToken.None && Task.Factory.CancellationToken.IsCancellationRequested) { Task.Factory.CancellationToken .ThrowIfCancellationRequested(); } });
Finally, you can also cancel the thread or a group of threads using the Cancel or CancelAfter (with a time delay) methods of CancellationTokenSource.
[ 724 ]
Chapter 3
Progress
Another asynchronous control feature that helps keep the user informed about the operations being invoked in the background is the progress callback implementation. Just like CancellationToken, we can supply the asynchronous tasks with an event handler for progress events that the asynchronous method can invoke to pass progress information back to the main thread. For simple progress reporting, it is enough to expand asynchronous methods with an additional parameter that derives from the IProgress interface. For instance, if we were to implement a progress event handler in the GetFibonacciRangeAsync method, we could use the number of values to be calculated and the current ordinal being calculated to report an overall progress in percentages: public async Task GetFibonacciRangeAsync(int firstOrdinal, int lastOrdinal, IProgress progress = null) { var results = new List(); for (var i = firstOrdinal; i < lastOrdinal; i++) { results.Add(await GetFibonacciNumberAsync(i)); decimal currentPercentage = (decimal) lastOrdinal - i/ (decimal) lastOrdinal - firstOrdinal; if (progress != null) progress.Report((int)(currentPercentage * 100); } return results; }
[ 725 ]
Asynchronous Programming
In order to be able to use the progress value in our view model, we can make use of the Progress class, which is the default implementation of IProgress. The code is as follows: Action reportProgress = (value) => { InfoText = string.Format("{0}% Completed", value); }; var progress = new Progress(reportProgress); m_SourceAsync.GetFibonacciRangeAsync(numberOrdinal1, numberOrdinal2, progress) .ContinueWith(task => { Result = string.Join(",", task.Result.Select(val=>val)); InfoText = ""; });
Task batches
In task-based asynchronous pattern, there are other ways than continuation to execute tasks in a batch, even in parallel. The example from the previous section was awaiting each number calculation separately and executing the next call. However, the manner in which the inner methods were implemented made them independent from each other. Hence, it is not actually necessary to wait for them one by one to return the result. The code is as follows: List calculations = new List(); Mvx.Trace("Starting Calculations"); for (var i = firstOrdinal; i < lastOrdinal; i++) { var currentOrdinal = i; calculations.Add(Task.Factory.StartNew(() => GetFibonacciNumberInternal(currentOrdinal).Value, TaskCreationOptions.LongRunning)); } Mvx.Trace("Starting When All", DateTime.Now); int[] results = await Task.WhenAll(calculations); Mvx.Trace("Calculations Completed", DateTime.Now); return results.OrderBy(value=>value).ToList(); [ 726 ]
Chapter 3
The Mvx static class and the Trace method are provided by the MvvmCross library. It will be further discussed in later chapters.
Now, each Fibonacci number in the sequence is calculated in parallel and when the sequence range is complete, an array of result values is returned. Finally, we sort the array and return the list of values. We can extend this implementation by adding a progress notifier with an interlocked (thread-safe) counter: calculations.Add(Task.Factory.StartNew(() => GetFibonacciNumberInternal(currentOrdinal).Value, TaskCreationOptions.LongRunning) .ContinueWith(task => { if (progress != null) { var currentTotal = Interlocked.Increment(ref currentCount); decimal currentPercentage = (decimal) currentTotal/ (decimal) totalCount; progress.Report((int)(currentPercentage * 100)); } return task.Result; }));
The resulting log traces from the calculations above are as follows: 09-07 21:18:29.232 I/mono-stdout( 3094): mvx:Diagnostic: 40.80 Starting Calculations 09-07 21:18:29.352 I/mono-stdout( 3094): mvx:Diagnostic: 40.92 Starting When All 09-07 21:18:30.432 I/mono-stdout( 3094): mvx:Diagnostic: 42.00 Calculations Completed
The total time for the calculations was about 1.2 seconds. Repeating the same calculations with an await on each method would give the following output (calculating ordinal 4 until 11): 09-07 21:26:58.716 I/mono-stdout( 3281): mvx:Diagnostic: 10.60 Starting Calculations 09-07 21:26:58.724 I/mono-stdout( 3281): mvx:Diagnostic: 10.61 Starting calculating ordinal 4 … [ 727 ]
Asynchronous Programming 09-07 21:27:03.900 I/mono-stdout( 3281): mvx:Diagnostic: 15.78 Starting calculating ordinal 11 09-07 21:27:05.028 I/mono-stdout( 3281): mvx:Diagnostic: 16.91 Calculations Completed
The same calculations took around 6.3 seconds overall. On top of WhenAll, developers are also equipped with the WhenAny, WaitAll, WaitAny methods on the Task class and ContinueWhenAll and ContinueWhenAny on the TaskFactory class.
Parallel execution
In the previous section, the discussion was centered on the System.Threading.Tasks namespace and the Task class. Even though tasks are the cornerstone of the task-based asynchronous model and so-called Task Parallelism, the concurrent collections namespace makes up the Data Parallelism side of the async model and provides developers with tools to execute code most efficiently and in a thread-safe manner. BlockingCollection is one of the concurrent collection implementations
that encapsulates the core synchronization and coordination between threads and provides a thread-safe data storage to implement a provider-consumer model in Xamarin applications. Using BlockingCollection, we can easily implement a new method that makes use of the parallel execution from the previous example. In this implementation, our view model will be the consumer and the Fibonacci source and the range calculation tasks will be the provider. If we were to rewrite the range calculation method using a blocking collection, our method signature would be similar to: public async Task GetFibonacciRangeAsync(int firstOrdinal, int lastOrdinal, BlockingCollection blockingCollection)
So in a way, the consumer is going to create the blocking collection and will pass it to the provider to fill it up with the calculated values. The provider in return will need to push each calculated value from the parallel tasks with the TryAdd or Add methods. The code is as follows: for (var i = firstOrdinal; i < lastOrdinal; i++) { var currentOrdinal = i;
[ 728 ]
Chapter 3 calculations.Add(Task.Factory.StartNew(() => GetFibonacciNumberInternal(currentOrdinal).Value, TaskCreationOptions.LongRunning) .ContinueWith(task => { blockingCollection.Add(task.Result); return task.Result; })); }
Finally, once all the calculations are completed, the provider needs to mark the collection as add-completed. The code is as follows: // // Collection is filled completely await Task.WhenAll(calculations).ContinueWith(task => { blockingCollection.CompleteAdding(); });
While these tasks are being executed on the provider side, we can create the consumer in our view model with a while loop, checking on certain intervals if there is a new item with TryTake until it is completed. However, there is already an implemented method on the concurrent collection for this purpose: GetConsumingEnumerable. Using this method makes the execution on the consumer thread as simple as a foreach block. The code is as follows: var blockingCollection = new BlockingCollection(); var fibonacciTask = (new FibonacciSourceAsync()) .GetFibonacciRangeAsync(numberOrdinal1, numberOrdinal2, blockingCollection); fibonacciTask.ConfigureAwait(false); // // Starting the Consumer thread Task.Factory.StartNew(() => { foreach (var item in blockingCollection.GetConsumingEnumerable()) { var currentItem = item; if (Result != string.Empty) Result += ",";
[ 729 ]
Asynchronous Programming Result += currentItem; } InfoText = "Done"; }, TaskCreationOptions.LongRunning);
In this model, the provider thread (together with each parallel task being executed) and the consumer thread are executed almost instantaneously and the results are reflected to the UI almost immediately through the view model. In the previous implementation, even though possibly multiple values are added to the blocking collection and the blocking collection's support for multiple consumers, the foreach loop follows a more linear execution. We can extend this model by adding multiple consumers using the Parallel.ForEach extension method from the System.Threading.Tasks.Parallel namespace. The code is as follows: Task.Factory.StartNew(() => { var result = Parallel.ForEach(blockingCollection. GetConsumingEnumerable(), item => { UpdateUIWithItem(item); }).IsCompleted; if (result) InfoText = "Done"; }, TaskCreationOptions.LongRunning);
There are other constructs and implementation patterns that developers can use and adapt on concurrent scenarios such as Partitioner, ActionBlock, ConcurrentScheduler, among others. However, these concepts are beyond the scope of this module.
Patterns and best practices
It is possible to draw parallels with and even convert from the classic threading and eventing patterns while implementing asynchronous tasks. However, async methods have to be implemented with caution to avoid deadlocks and uncaught exceptions.
[ 730 ]
Chapter 3
Async pattern conversions
The Observer pattern—also known as the Event-based Asynchronous Pattern (EAP)—used to be the main development tool for long running processes and service/remote application APIs. Events and delegates still make up a considerable amount of UI-related implementation in modern applications. However, asynchronous tasks and awaitables provide a much more convenient way to deal with long running processes and chain completion methods. Fortunately, it is possible to implement conversion patterns from other async patterns to task-based implementations. These types of scenarios involve using the TaskCompletionSource class. In order to demonstrate this, we will be using a simplified version of the Fibonacci source implementation from previous examples: public event EventHandler CalculationCompleted; public event EventHandler CalculationFailed; /// /// Calculates n-th number in Fibonacci sequence /// /// Ordinal of the number to calculate public void GetFibonacciNumber(int ordinal) { try { var result = GetFibonacciNumberInternal(ordinal); if (CalculationCompleted != null) CalculationCompleted(this, result); } catch (Exception ex) { if (CalculationFailed != null) CalculationFailed(this, ex.Message); } }
[ 731 ]
Asynchronous Programming
In this example, we have an event handler for successful calculations and another one for failed calculations (for example, if the ordinal is less than 0, it should throw an ArgumentOutOfRangeException). Our aim here is to implement an asynchronous method, which we can execute and yield the result to the UI without having to subscribe to the event every time a new FibonacciSource is created. For this purpose, we can implement a new version of FibonacciSource and expose only async methods instead of event-based methods. The code is as follows: public class FibonacciSourceAsync : FibonacciSource { public new Task GetFibonacciNumberAsync(int ordinal) { var myTaskSource = new TaskCompletionSource(); EventHandler onCalculationCompleted = null; EventHandler onCalculationFailed = null; // // Subscribe to TaskCompleted: When the CalculationCompleted event is fired, set result. onCalculationCompleted = (sender, args) => { // Not forgetting to release the event handler CalculationCompleted -= onCalculationCompleted; myTaskSource.SetResult(args.Value); }; // // Subscribe to TaskFailed: If there is an error in the execution, set error. onCalculationFailed = (sender, args) => { CalculationFailed -= onCalculationFailed; myTaskSource.SetException(new Exception(args)); }; CalculationCompleted += onCalculationCompleted; CalculationFailed += onCalculationFailed;
[ 732 ]
Chapter 3 // Finally execute the task and return the associated Task promise. base.GetFibonacciNumberAsync(ordinal); return myTaskSource.Task; } }
Now calls to calculate Fibonacci numbers would look similar to: public async Task CalculateFibonacciValueAsync(int ordinal) { var fibonacciSource = new FibonacciSourceAsync(); try { return (await fibonacciSource.GetFibonacciNumberAsync(ordin al)); } catch (Exception ex) { // TODO: Do something with exception } }
This implementation can be further extended with progress and cancellation token implementations.
Multi-threading with tasks
One important thing to realize about asynchronous calls is that they don't necessarily run on a separate thread. As a matter of fact, calls are "scheduled" to run in a so-called Synchronization Context on the main thread unless they are instructed otherwise. Synchronization Context is the message queue that takes care of the scheduling of the async calls that need to be awaited. Once the async method (or Task in most of the cases) is successfully executed, the result is posted back onto the Synchronization Context (that is, the main UI thread). For demonstration purposes, we will be using the async implementation (EAP conversion) from the previous example with some additional diagnostic calls to get additional information about the threads and synchronization contexts being used.
[ 733 ]
Asynchronous Programming
The TraceThreadInfo method and the associated ThreadInfo class used in the examples here is a custom implementation used through dependency injection. The reason for this is that threading namespace only contains task-related classes in PCLs and the only way to actually get the current thread ID is to use platform-specific implementation. Platform-specific implementation patterns will be discussed in detail in later chapters.
In the tracing method, we will be logging the current thread ID and the current synchronization context: public IThreadInfo ThreadInfo { get { return Mvx.GetSingleton(); } } private void TraceThreadInfo(string message) { Debug.WriteLine("{0} on Thread '{1}'", message, ThreadInfo.CurrentThreadId); Debug.WriteLine("Current Synchronization Context is {0}", SynchronizationContext.Current); }
The calculation command attached to the calculate button is: TraceThreadInfo("Begin DoCalculate"); if (!string.IsNullOrWhiteSpace(Input)) { int numberOrdinal; if (int.TryParse(Input, out numberOrdinal)) { InfoText = "Calculating"; TraceThreadInfo("Calling GetFibonacciNumberAsync"); var result = await GetFibonacciNumberAsync(numberOrdinal); TraceThreadInfo("Response from GetFibonacciNumberAsync"); Result = result.ToString();
[ 734 ]
Chapter 3 InfoText = string.Empty; TraceThreadInfo("End DoCalculate"); return; } } InfoText = "Invalid Input";
The associated trace log will look like this:
Trace for in-line execution of tasks
Looking at the trace messages of the execution stack above, one can easily see that in spite of the fact we are dealing with async tasks, the whole execution takes place on the main thread except for the actual call for the internal method of the source (that is, it is executed on Thread 106). The rest of the method calls have Android.App. SyncContext as the synchronization context and the execution order is no different than the call sequence that is implemented.
[ 735 ]
Asynchronous Programming
Changing the implementation a little bit and using the ContinueWith function of the Task item, we get slightly different results. The code is as follows: TraceThreadInfo("Calling GetFibonacciNumberAsync");
await GetFibonacciNumberAsync(numberOrdinal).ContinueWith(task => { TraceThreadInfo("Response from GetFibonacciNumberAsync"); Result = task.Result.ToString(); InfoText = string.Empty; }); TraceThreadInfo("End DoCalculate");
The trace log for this implementation looks like this:
Async execution of Tasks
[ 736 ]
Chapter 3
As the trace log suggests, ContinueWith lambda is executed on a separate thread but the execution is still sequential. An important note here is that we are assigning the results back to the ViewModel on a separate thread. In this example, the cross-thread invocation is handled by the MvvmCross framework. If we were to deal with this assignment, the call would look similar to: await GetFibonacciNumberAsync(numberOrdinal). ContinueWith(task => { TraceThreadInfo("Response from GetFibonacciNumberAsync"); this.RunOnUiThread(() => { txtResult.Text = task.Result.ToString(); }); txtInfo.Text = ""; });
In the preceding example, once the execution gets onto a separate thread, the synchronization context is nullified. In .NET runtime, asynchronous tasks that are not tracked by the main synchronization context are actually assigned a TaskScheduler instance and the execution is done through this context. In this case, TaskScheduler is responsible to redirect the success post messages back to the main thread if the task is configured to use the same context (that is, ConfigureAwait(true)). However, the way synchronization context works in .NET and the configured task invocations yield back to the main thread can cause deadlocks if the asynchronous tasks are called synchronously (that is, with Task.Result or Task.Wait()) on the main thread. In such a scenario, once the async call finishes executing and tries to yield back into the main context, the main context will still not be accessible since it is actually waiting for the async task itself to complete.
[ 737 ]
Asynchronous Programming
ConfigureAwait(false) informs the scheduler not to look for and post back
the result to the same execution context where the task was invoked, but rather just execute and run to completion on the execution context. This avoids the deadlock scenario. This deadlock scenario is specific for .NET runtime and because of the way mono runtime on Android and Mono.Touch compiler deal with the task executions; the deadlocks currently do not happen on these platforms. However, it is important to follow the coding conventions associated with asynchronous tasks and awaitables to avoid any unexpected behavior. In order to execute the whole task on a separate thread we can use Task.Run (which will push the task to the ThreadPool) or Task.Factory.StartNew. Using the StartNew method would let you define which type of a method you are about to execute in this task and let the runtime make an informed decision about using a different thread. The code is as follows: var task = Task.Factory.StartNew(async () => { TraceThreadInfo("Calling GetFibonacciNumberAsync"); var result = await GetFibonacciNumberAsync(numberOrdinal); TraceThreadInfo("Response from GetFibonacciNumberAsync"); Result = result.ToString(); InfoText = string.Empty; }, TaskCreationOptions.LongRunning); task.ConfigureAwait(false);
[ 738 ]
Chapter 3
In the trace below, the biggest difference with the previous examples is that the DoCalculate method exits before even the execution starts for the task we created for the calculations. This type of execution would eloquently fit with the MVVM pattern applied on a cross platform mobile app project. It would avoid any UI blocking issues and create a sense of continuity for the user.
Starting a new async task
[ 739 ]
Asynchronous Programming
If we want to analyze the same execution on the iOS application (that is, calculate the number on the Fibonacci sequence on a certain ordinal), we can easily identify the threading pattern with the Xcode Instruments "System Trace" template.
Calculating four different Fibonacci numbers
Exception handling
Handling exceptions can become cumbersome if correct asynchronous paths are not followed in multithreaded implementations. However, in most cases, the async/ await language constructs take the load of the developers. If the async chain is not broken and calls are implemented correctly, catching exceptions in asynchronous context is no different than catching them with linear code.
[ 740 ]
Chapter 3
Using our example from previous sections: try { var result = await GetFibonacciNumberAsync(numberOrdinal); Result = result.ToString(); InfoText = ""; } catch (Exception ex) { Debug.WriteLine("Error:" + ex.Message); InfoText = "EX:" + ex.Message; }
In this example, if the ordinal we pass in as a parameter is a negative number, it would throw an exception with the message Cannot calculate Fibonacci number for a negative number and we would be displaying the error message in the info textbox. However, if we were to use the ContinueWith construct to execute the same code the outcome would have been a little different: try { await GetFibonacciNumberAsync(numberOrdinal).ContinueWith(result => { Result = result.Result.ToString(); InfoText = string.Empty; }); } catch (Exception ex) { Debug.WriteLine("Error:" + ex.Message); InfoText = "EX:" + ex.Message; }
[ 741 ]
Asynchronous Programming
In this example, the exception message we would receive would be One or more errors occurred. The reason for this is that the exception thrown in the second scenario is an AggregateException because of the async chain we created.
AggregateException in async chain
The result would have been the same if we were using the .Result or .Wait() calls on the task itself. The important part of this implementation is where we are calling await on the asynchronous method. The catch block would never have been called if this was not the case. Without the await keyword, the try/catch block would have been just checking if the preparation of the Task went as expected, not the actual execution. Another type of async execution that cannot be caught with a try/catch block is the type of async methods that return void instead of a Task or Task. Similar to having an exception thrown in an event handler, the only two places they would be caught are the AppDomain.UnhandledException or Application. ThreadException events. It is always a better practice for asynchronous methods to return Task and then return void.
[ 742 ]
Chapter 3
However, in the ContinueWith implementation, with the reference to the Task at hand, we can also check for the status of the task once it runs to completion before we make the result assignment. This assignment is what actually causes the exception to bubble-up to upper layers. In this case, we do not need a try/catch block. The code is as follows: await GetFibonacciNumberAsync(numberOrdinal).ContinueWith(result => { TraceThreadInfo("Response from GetFibonacciNumberAsync"); if (result.IsFaulted) { Result = string.Empty; InfoText = string.Join("\r\n", result.Exception .InnerExceptions.Select(exception => exception.Message)); } else { Result = result.Result.ToString(); InfoText = string.Empty; } });
Initialization pattern
Especially in scenarios where a service is involved, a common requirement is to have an initialization function that would prepare the communication channel and/or make a "ping" or an authentication call. When developers are confronted with this type of a scenario, the biggest mistake they can make is to call the asynchronous initialization function in the constructor with a .Result and/or .Wait() statement (making it a synchronous call). For this scenario, let's assume we have a service that implements an interface with two simple async method implementations. public interface IService { Task AuthenticateAsync(string username, string password); Task ServiceMethodAsync(string myParameter); }
[ 743 ]
Asynchronous Programming
In order to be able to call ServiceMethodAsync, we first need to make an AuthenticateAsync call and receive the authentication token from the service. The code is as follows: public MyService(string username, string password) { // // Following call would block the constructor // IMPORTANT: If it was being called from the main UI thread, it might cause a deadlock // Blocking Call Example 1: // AuthenticateAsync(username, password).Wait(); // Blocking Call Example 2: m_Token = AuthenticateAsync(username, password).Result; }
In this example, we implemented the call for authentication in the constructor of the service. Even though the implementation might work in some cases, if the service constructor is called from the main UI thread, the thread would go into a deadlock as it was described in the previous section. The easiest solution would be to either expose the initialization function to outer layers or have the service call initialization before each service call. For this purpose we can wrap the authentication call in an initialization method. The code is as follows: public MyService(string username, string password) { m_Username = username; m_Password = password; } private async Task EnsureInitializationAsync() { if (string.IsNullOrEmpty(m_Token)) { m_Token = await AuthenticateAsync(m_Username, m_Password); } }
The service method call would look similar to this: public async Task ServiceMethodAsync(string myParameter) { await EnsureInitializationAsync();
[ 744 ]
Chapter 3 try { int result = await InternalServiceMethodAsync(myParameter); return result; } catch (Exception ex) { // TODO: throw; } }
As mentioned, we can also expose the initialization through an interface: /// /// Describes the service as requiring async initialization /// public interface IAsyncInitialization { /// /// The result of the asynchronous initialization. /// Task Initialization { get; } } public class MyService : IService, IAsyncInitialization { ... public Task Initialization { get; private set; } public MyService(string username, string password) { m_Username = username; m_Password = password; Initialization = EnsureInitializationAsync(); } private async Task EnsureInitializationAsync() { if (string.IsNullOrEmpty(m_Token)) { [ 745 ]
Asynchronous Programming m_Token = await AuthenticateAsync(m_Username, m_Password); } } ... }
In this case, the caller method needs to check if the service needs async initialization and check for the required task results. The code is as follows: if (serviceInstance is IAsyncInitialization) { /// Wait for the results of the initialization await serviceInstance.Initialization; } await serviceInstance.ServiceMethodAsync("param");
Semaphores
Synchronization and throttling methodology on the asynchronous context is a little different than the classic .NET runtime implementations. For instance, lock blocks are not allowed on async calls and you will not be able to use Mutex for synchronization. Mutex is inapplicable as a mutex can only be owned by a single thread and async executions are not guaranteed to complete on the same thread as they started. The code is as follows: // // Error: The 'await' operator cannot be used in the body of a lock statement //lock (m_FibonacciSource) //{ // var result = await GetFibonacciNumberAsync(numberOrdinal); //} // // Warning: Might work but not guaranteed m_Mutex.WaitOne(200); await GetFibonacciNumberAsync(numberOrdinal).ContinueWith((task) => { TraceThreadInfo("Response from GetFibonacciNumberAsync"); Result = task.Result.ToString(); [ 746 ]
Chapter 3 InfoText = string.Empty; }); m_Mutex.ReleaseMutex();
In order to handle the non-deterministic execution and threading model of asynchronous tasks, a new construct was added to .NET: Semaphore. Semaphore (implementation of WaitHandle) and SemaphoreSlim (lightweight version of Semaphore that is implemented with monitors) types do not enforce thread identity on the Wait and Release calls and can be asynchronously awaited. For instance, let's execute a number of parallel calculations orchestrated with a semaphore that allows 3 access count (SemaphoreSlim(3) or SemaphoreSlim(3,3)) such as: var semaphoreSlim = new SemaphoreSlim(3); int count = 11; for (var i = 0; i < 7; i++) { Task.Factory.StartNew(() => { return semaphoreSlim.WaitAsync().ContinueWith((waitTask) => { return Task.Factory.StartNew(() => { return GetFibonacciNumberAsync(count = Interlocked .Increment(ref count)).ContinueWith( (calculateTask) => { TraceThreadInfo(string.Format( "Current count on Semaphore: {0}", semaphoreSlim.Release() + 1)); }); }, TaskCreationOptions.LongRunning); }); }, TaskCreationOptions.LongRunning);
[ 747 ]
Asynchronous Programming
This parallel execution can be easily spotted on the average system time view of Instruments' System Trace template. This is shown in the screenshot below where each ordinal calculation would give the exact number of peaks on the selected calculation threads):
System time average on synchronized threads
Background tasks
Threading and task solutions are not the only option when there is a need to execute a not time-bound, long running process. Moreover, both of these options are for executing code when your application is in the foreground or in an active state. When the application is entering the backgrounded or suspended state, the application might still require the execution of some longer method before the volatile data is lost, or it might require a process to be running in the background when the application is not in an interactive state. For these types of scenarios, both iOS and Android offer backgrounding and background operation options. [ 748 ]
Chapter 3
Background tasks on iOS
Background tasks on iOS are the easiest way for your application to execute processing tasks without the need for the UI thread or having to respond to lifecycle delegates. There are three types of background tasks that can be executed for different needs: • Background-safe tasks: These tasks are the ones that can be executed at any stage of the process lifetime. They are not affected and/or interrupted by the application going into the background. The code is as follows: nint taskId = UIApplication.SharedApplication .BeginBackgroundTask(() => { // TODO: Do something if the allotted time runs out }); // TODO: Implement the processing logic if (taskId != UIApplication.BackgroundTaskInvalid) { UIApplication.SharedApplication.EndBackgroundTask(taskId); }
• DidEnterBackground tasks: Another type of background task is executed to pass the state-save or clean-up logic to a background process. The DidEnterBackground lifetime delegate is used to initialize these tasks and continue processing even after the application goes into the background state. The creation of these tasks is similar to the background-safe tasks. The only difference is that the EndBackgroundTask method has to be called inside the execution block rather than the calling thread, since the calling process might have already returned not waiting for the execution of the background task. The code is as follows: public override void DidEnterBackground (UIApplication application) { nint taskId = UIApplication.SharedApplication .BeginBackgroundTask(() => { // TODO: Do something if the allotted time runs out }); Task.Run(() => { [ 749 ]
Asynchronous Programming // TODO: Implement the processing logic UIApplication.SharedApplication.EndBackgroundTask(taskId); }); }
• Background transfers: These tasks are specific to iOS 7+ and provide a longer processing time (a strict limit on other background tasks is 600 seconds, while background transfers can last up to 10,000 seconds). Background transfer tasks are used to perform long lasting network operations and upload/download large files over the wire.
Services (Android only)
On the Android platform, once the activities enter the backgrounded state, they cannot perform tasks and are usually stopped soon after entering the background. Services are application components introduced to provide an interface for developers to start and stop long running processes in the background. Even though services are created as part of an application, they have their own separate lifecycle and can run even if the application and activity that started them was stopped or destroyed. A service can take one or both of two forms: • Started: A service is "started" when an activity explicitly starts it by calling the StartService method using an intent. A started service is generally used as a BackgroundWorker and once the processing operation is finished, the service itself or the activity stops it. • Bound: A "bound" service generally acts as the provider to clients in the activities of the application or even other applications. A bound service is kept alive as long as another component is bound to it. Both of these initialization patterns use the callback methods similar but not limited to OnStartCommand, OnBind, OnCreate, and OnDestroy to start background processing and deal with its lifetime. There are various base classes implemented in the Android namespace and according to the requirements, these base classes can be implemented and started or bound.
[ 750 ]
Chapter 3
In order to implement a started service to do some background processing, the first step of the implementation process would be to create the IntentService class: [Service] [IntentFilter(new String[] { "com.xamarin.MyDemoService" })] public class MyDemoService : IntentService { public MyDemoService() : base("MyDemoService") { } protected override void OnHandleIntent(Intent intent) { var myParameter = intent.GetStringExtra("parameter"); // Invoke long running process DoWork(myParameter); } }
The IntentService base class already deals with lifecycle events such as OnStart, so all we have to implement is the OnHandleIntent method to respond to intent requests from activities. The two attributes of the class, Service and IntentFilter, are used by Xamarin compiler to generate the entries in the application manifest. The debug build for this implementation gives out the following service entry in the application manifest. The code is as follows:
With this implementation in an activity, the intent service can be started by either using the intent filter entry or using the type of the service. //StartService (new Intent (this, typeof(MyDemoService))); StartService(new Intent("com.xamarin.MyDemoService"));
[ 751 ]
Asynchronous Programming
Summary
Overall, asynchronous/concurrent implementation patterns and background tasks allow the developers to push the heavy-lifting away from the UI thread and create responsive applications in the single-threaded paradigm of modern mobile applications. The Task-based Asynchronous Pattern provides an efficient and scalable way to implement asynchronous operations with ease. Moreover, progress, cancellation, and concurrent collections help monitor, scale, and manage the execution of these asynchronous blocks while providing a way to cooperate between each other. Implementing these blocks, developers do not need to carry the burden of threads, synchronization, and scaling the threads according to the hardware resources. After analyzing memory and CPU-related topics so far in this module, in the next chapter we will discuss local storage and how to use it efficiently.
[ 752 ]
Local Data Management In this chapter, you will find patterns and techniques to efficiently use, manage, and roam data on mobile devices. It also investigates SQLite database creation and usage strategies. The chapter is divided into the following sections: • Data in mobile applications • Application data • Local filesystem • Data roaming • SQLite • Patterns and best practices • Backup/roaming
Data in mobile applications
The term "data" can refer to different types of information and storage locations in mobile app development. It can be used to describe a volatile state that is created and destroyed each time a view in the application is used, or it might refer to persisted settings and configuration information that are required to run the application, or even the data stored in the local filesystem. Each type of data is created and persisted or destroyed throughout the lifecycle of the application or a view in the application. We can talk about four distinct groups for this discussion.
[ 753 ]
Local Data Management
Each data type is stored and accessed from different locations and each location has its own unique restriction and access models.
Data type storage locations
State
Mobile applications are generally stateful. Transient data that is used to visualize items on the UI or the data created by the user of the application falls into this category. The purpose of state is to maintain a consistent app experience across sessions, devices, and/or process lifecycle. Application settings or the current state of the view is a good example for this category.
App data
App data generally refers to the data that is essential for the execution of the application. This data is created, stored, and managed by the application itself. It can be structured data storage or it might be the cached version of online application resources. This type of data can be raw, in the form of a SQLite database, or stored by other facilities on the current device by the current application.
[ 754 ]
Chapter 4
App data stored in different locations can survive through different stages of an application lifecycle.
App data lifecycle
Local files
Local files are the stored items in the local filesystem. These files are generally created outside the lifecycle and/or scope of the application and are only made use of by the application. For instance, a photo taken by the user can later on be used by the mail client app as an attachment item.
External data
External data can be described as the combination of all the other data sources that are used by the application during runtime. This can include network or web resources.
Application data
Application data makes up the core of the data storage on Xamarin platforms and Windows Runtime. This data is specific to your application. It lives and eventually dies with it, and in most cases it is not relevant or even accessible by other applications running on the same device or even by the user who is using the application (at least directly). The application has unrestricted access to application data, or so-called isolated storage, without having to ask for permission from the user or add a declaration and can (in most cases) write, read, and query items in this storage according to the type of the application data location.
[ 755 ]
Local Data Management
Installation directory
The installation directory is the innermost part of the accessible data storages and is the most intimate location for the application. Access to this location by the application is unrestricted but read-only. The access models on iOS, Android, and Windows Runtime vary greatly.
Android
For Xamarin.Android applications, the installation directory essentially refers to the compressed Android package (the .apk file), and the defined subdivisions are just abstractions of folders packaged and added to the manifest during the compilation. The installation directory and subfolders can be accessed in various ways.
Android package and the project tree
The most important location in the installation directory for Android apps is the Resources folder. Resources can be generalized as the UI-related items that will be used to render the views of the application. One of the resource types that can be included in the application package is the drawable type. Drawable resources are image resources and can exist in alternate flavors for different conditions and devices that the application runs on (see Chapter 9, Reusable UI Patterns). In order for the compiler to include the resources in the application package, the build action of each item in this folder has to be set to AndroidResource.
[ 756 ]
Chapter 4
It is important to mention that Android packages do not allow filenames to contain uppercase characters, and yet Xamarin developers can include these types of files in their projects. Xamarin.Android deals with this by renaming the resources during compilation (for example, see the XamarinLogo.png file in the drawable folder).
Programmatically, they can be accessed using the generated Resource class to get the assigned resource ID and the Resources static class that provides access methods, or by using the android.resource:// protocol and the resource identifier (or the package name together with the resource name). However, in most scenarios, using only the assigned ID to use the resources with UI controls will suffice. The code is as follows: var myImageResourceId = Resource.Drawable.XamarinLogo; var myImageView = (ImageView) FindViewById(Resource.Id.MyImageView); // Set the Image resource using the id. myImageView.SetImageResource(myImageResourceId); // OR: // Retrieving the resource itself and then assigning it. Drawable myImageResource = Resources.GetDrawable(myImageResourceId); myImageView.SetImageDrawable(myImageResource);
In the declarative UI (layouts), the drawable resources folder can be accessed with the alias @drawable. Similarly, string resources can be accessed with @string. The code is as follows:
[ 757 ]
Local Data Management
Another important location in the installation directory is the Assets folder. The Assets folder is used for any raw assets that you want deployed together with your application (other than the Resources folder) and not to be processed by the compiler or the runtime. Assets can be retrieved with the AssetManager class, and the Assets property in the Activity class can be used to access the AssetManager class. The code is as follows: Task.Run(async () => { using (var dataPackageStream = Assets.Open("Data.csv")) using (var streamReader = new StreamReader(dataPackageStream)) { var content = await streamReader.ReadToEndAsync(); // TODO: Do something with the comma separated content. } });
Other resource types in the installation location, such as layouts, raw, and string resources can also be accessed in the described manner using the abstraction provided by Android runtime.
iOS
The building units of an iOS application, such as executable code and associated resources, are contained in a so-called bundle. A bundle is part of the application sandbox and the path to the bundle is determined by the operating system during installation. Similar to Android applications, iOS application projects can also include compiled image resources (bundle resources). These items are then accessed using the abstraction layer provided by the runtime. For instance, in order to access an image resource from the bundle directory, you would need to call the FromFile method on the UIImage class: var image = UIImage.FromFile("XamarinLogo.png"); // // OR making a roundtrip (get the path, read the file, create // image // Similar to /data/Containers/Bundle/Application// XamarinMasteriOS.app/ XamarinLogo.png
[ 758 ]
Chapter 4 var imagePath = NSBundle.MainBundle. GetUrlForResource("XamarinLogo", "png").Path; var fileContent = System.IO.File.ReadAllBytes(imagePath); var secondImage = UIImage. LoadFromData(NSData.FromArray(fileContent));
Similar to the access model in Android applications, the bundle container is read-only and should not be modified. The simple reason for this is that iOS application bundles are signed by the publisher key and any change in the bundle container would invalidate the package signature.
Local storage
There is a both in the second part as well. Android and iOS runtimes provide different storage facilities for application data both in the form of structured data and raw content files.
Android
On the Android platform, Shared Preferences and Internal Storage are the two local storage options. Both of these options have different access models and your applications have read/write access to these locations. Using SharedPreferences is the most basic way of storing data on the Android platform. This class provides a simple persistent dictionary implementation that allows the application to create, modify, and retrieve primitive data types (that is, boolean, float, int, long, string, and string_array) and their associated key. The size on these values is only restricted by the data type itself. As the name suggests, SharedPreferences is generally used to store configuration options selected by the user and is persisted across user sessions. There is also a base activity implementation, PreferenceActivity, to easily create and reuse a view for user preferences that makes use of the SharedPreferences for the application.
[ 759 ]
Local Data Management
The usage pattern for SharedPreferences class is straightforward. In order to use the default preferences for the activity or a custom preference file, the Activity class provides specialized methods: // Retrieve an object for accessing private to this activity ISharedPreferences myPreferences = GetPreferences(FileCreationMode.Private); // Retrieve and hold the contents of the preference file 'MyCustomPreferences' ISharedPreferences myCustomPreferences = GetSharedPreferences("MyCustomPreferences", FileCreationMode.Private);
After the retrieve call, the preference file is created according to the FileCreationMode class selected if it did not exist already. To get the value of a preference entry, you can use one of the get methods provided by the class. The code is as follows: var myStringValue = myCustomPreferences.GetString("MyStringValue", string.Empty); var myIntValue = myCustomPreferences.GetInt("MyIntValue", default(int));
To edit the values, the Editor class for the SharedPreferences class can be used. The code is as follows: ISharedPreferencesEditor myEditor = myCustomPreferences.Edit(); myEditor.PutString("MyStringValue", myStringValue); myEditor.PutInt("MyIntValue", myIntValue); // Apply the current changes from the editor back // to the Singleton SharedPreferences class myEditor.Apply(); // OR // Commit the changes to the singleton instance // AND the disk immediately myEditor.Commit();
Internal Storage is the dedicated storage for your application. The application is free to create and retrieve any type of file (and folder) in this directory.
[ 760 ]
Chapter 4
FileCreationMode is an access modifier used in Android runtime to define the access type and permission levels of a file. •
Append: If the file already exists, then write data to the end of the existing file instead of erasing. This is to be used with Android.Content.Context.OpenFileOutput.
•
EnableWriteAheadLogging: When this database's open flag is set, the database is opened with write-ahead logging enabled by default.
•
MultiProcess: legacy behavior in and before Gingerbread (Android 2.3) and is implied when targeting such releases. For applications targeting higher SDK versions, it must be set explicitly. When used together with SharedPreferences, the file on disk will be checked for modifications even if the shared preferences instance is already loaded in this process. This behavior is desired when the application has multiple processes accessing the same file.
•
Private: This is the default file creation mode where the created file can only be accessed by the calling application (or all applications sharing the same user ID).
•
WorldReadable/WorldWritable: Both deprecated in API level 17 for security vulnerabilities, they can pose to enable file access to application files.
Files in this folder, without any manifest declaration, can be accessed with the designated methods on the application context or by using the Xamarin/Mono implementation of IO methods. The code is as follows: // Creating a file in the application internal storage root using(var fileStreamInRootPath = this.OpenFileOutput("FileInRootPath", FileCreationMode.Private)) using (var streamWriter = new StreamWriter(fileStreamInRootPath)) { streamWriter.Write("Hello World!"); } // // Reading the contents of the file using(var fileStreamInRootPath = this.OpenFileInput("FileInRootPath"))
[ 761 ]
Local Data Management using (var streamReader = new StreamReader(fileStreamInRootPath)) { var stringContent = streamReader.ReadToEnd(); } // Getting the file path. // e.g.: /data/data/Xamarin.Master.Android/files/FileInRootPath var filePath = FilesDir.AbsolutePath + "/" + "FileInRootPath"; // Using the Xamarin (Mono) implementation. System.IO.File.AppendAllText(filePath, "\r\nAdditional Content"); var allText = System.IO.File.ReadAllText(filePath);
In addition to basic CRUD operations, you can also create additional folders and enumerate files and folders.
iOS
The simplest data storage option on iOS applications are the property lists. (the .plist files). These files are designed to be used for relatively small amounts of data that can be represented with primitive data types. They can be defined as dictionaries or arrays that are serialized and persisted in XML format. You can read and write to a property list directly using the associated classes (NSArray and NSDictionary). For instance, a simple implementation that creates and reads a property list would look similar to the following code (with additional diagnostic entries): myNSDictionary.WriteToFile(dictionaryPath, true); Debug.WriteLine("File Contents:"); var fileContents = System.IO.File.ReadAllText(dictionaryPath); Debug.WriteLine(fileContents); var myNewNSDictionary = NSDictionary.FromFile(dictionaryPath); Debug.WriteLine("Values read from plist:"); foreach (var key in myNewNSDictionary.Keys) { var keyValue = myNewNSDictionary[key]; Debug.WriteLine(string.Format("Value for the key '{0}' is '{1}'", key, keyValue)); }
[ 762 ]
Chapter 4
The output from the preceding implementation would look like this: File Contents:
firstKey firstValue secondKey secondValue thirdKey 8
Values read from plist: Value for the key 'firstKey' is 'firstValue' Value for the key 'secondKey' is 'secondValue' Value for the key 'thirdKey' is '8'
When it comes to the local file storage, the iOS filesystem reserves several locations for applications; each of these locations have a specific purpose from the application's perspective. • Documents/: The Documents library is generally designated for user-generated content. This folder should be used if the contents of the files are to be exposed to the user. Contents of this folder are backed up by iTunes. • Documents/Inbox: The Inbox folder is where files that are requested to be opened by the application are kept. An application can read and delete these files; it does not have privileges to modify these documents. • Library/: The Library folder is the root directory for the files that you don't want to expose to the user. Applications can create files and additional folders in this directory. • Library/Application Support: This subdirectory in the library folder is generally used to contain files managed by your application, such as configuration files, templates, saved data, and purchases. Contents destined for this folder should be placed in a custom subdirectory with the bundle identifier or company ID of your app. [ 763 ]
Local Data Management
• Library/Caches: The Caches folder is used for non-essential, application created files. • Library/Preferences: App-specific preferences are stored in this folder. However, access to this folder should be done through the preferences API. • tmp/: The tmp folder is another location for non-essential temporary files. Access to these library locations is possible using the System.IO namespace and associated classes.
Temporary storage
Temporary storage and/or the cache directory is another location that the application does not need any specific permission. This is where non-essential files can be saved by the application to decrease network or processing time. The persistence of these folders is not guaranteed by the operating system. In both Android and iOS systems, designated cache and/or temp locations are accessed through the context properties and the CRUD operations can be performed using the System.IO namespace and the related classes. On Android, the cache directory can be accessed with the CacheDir property on the context: // Path similar to /data/data/Xamarin.Master.Android/cache var cacheFilePath = this.CacheDir.AbsolutePath + "/" + "CacheFile"; // Writing to the file System.IO.File.AppendAllLines(cacheFilePath, new[] { "Cached Content" }); // Reading the file var cachedContent = System.IO.File.ReadAllText(cacheFilePath);
On iOS, there are two separate locations for temporary files (/temp/) and cache files (Library/Caches/). Cache files are persisted for longer than the temporary data, but they still might be deleted by the system to free up disk space. The code is as follows: // getting the root application sandbox path var documents = Environment.GetFolderPath (Environment.SpecialFolder. MyDocuments); // paths to caches and temporary files directories.var cache = Path. Combine(documents, "..", "Library", "Caches"); var tmp = Path.Combine(documents, "..", "tmp");
Neither of these directories are backed up or synchronized to iCloud. [ 764 ]
Chapter 4
Local filesystem
On iOS, applications cannot programmatically access files external to the application sandbox (for example, an iOS application cannot programmatically navigate to the user's picture directory and pick a file). The bridge between the local filesystem and the iOS app's sandbox was limited to the image picker controller until iOS 8. iOS 8 introduces the new document picker controller and document provider API. In this interaction model, the application implementing the document provider extension creates the document picker UI and the host application uses this provided UI to let the user select the documents to be used in the host application execution (similar to the file open picker and provider capability on the Windows Runtime platform).
UIImagePickerController
For Android, on top of the local file storage that is only app-specific, applications have access to two other locations: public and private external storage (depending on the hardware). External storage in this context refers to SD card storage, which is not available on iOS systems. On Android runtime, applications can have access to the root path (OS root path) and iterate through public folders. [ 765 ]
Local Data Management
Let's have a look at the returned paths for some of the internal and external paths on an Android filesystem: Trace.WriteLine(Environment.RootDirectory, "FileSystem"); Trace.WriteLine(Environment.DataDirectory, "FileSystem"); Trace.WriteLine(this.GetExternalFilesDir(Environment. DirectoryDownloads).AbsolutePath, "FileSystem"); Trace.WriteLine(this.GetExternalFilesDir(Environment. DirectoryDocuments).AbsolutePath, "FileSystem"); // Call with GetExternalFilesDir Trace.WriteLine(this.GetExternalFilesDir(Environment. DirectoryMovies).AbsolutePath, "FileSystem"); Trace.WriteLine(this.GetExternalFilesDir(Environment. DirectoryMusic).AbsolutePath, "FileSystem"); Trace.WriteLine(this.GetExternalFilesDir(Environment. DirectoryPictures).AbsolutePath, "FileSystem"); Trace.WriteLine(Environment.GetExternalStoragePublicDirectory (Environment.DirectoryMovies).AbsolutePath, "FileSystem"); Trace.WriteLine(Environment.GetExternalStoragePublicDirectory (Environment.DirectoryMusic).AbsolutePath, "FileSystem"); Trace.WriteLine(Environment.GetExternalStoragePublicDirectory (Environment.DirectoryPictures).AbsolutePath, "FileSystem"); Trace.WriteLine(Environment.DownloadCacheDirectory, "FileSystem"); Trace.WriteLine(Environment.ExternalStorageDirectory, "FileSystem");
The output to these calls identifies the app-specific and public locations: I/mono-stdout(10079): FileSystem: /system I/mono-stdout(10079): FileSystem: /data I/mono-stdout(10079): FileSystem: /storage/emulated/0/Android/data/ Xamarin.Master.Android/files/Download I/mono-stdout(10079): FileSystem: /storage/emulated/0/Android/data/ Xamarin.Master.Android/files/Documents I/mono-stdout(10079): FileSystem: /storage/emulated/0/Android/data/ Xamarin.Master.Android/files/Movies I/mono-stdout(10079): FileSystem: /storage/emulated/0/Android/data/ Xamarin.Master.Android/files/Music I/mono-stdout(10079): FileSystem: /storage/emulated/0/Android/data/ Xamarin.Master.Android/files/Pictures
[ 766 ]
Chapter 4 I/mono-stdout(10079): I/mono-stdout(10079): I/mono-stdout(10079): I/mono-stdout(10079): I/mono-stdout(10079):
FileSystem: FileSystem: FileSystem: FileSystem: FileSystem:
/storage/emulated/0/Movies /storage/emulated/0/Music /storage/emulated/0/Pictures /cache /storage/emulated/0
In spite of the fact that Android developers have access to a vast set of options for storage access methods, they are required to implement their own file picker dialogs or use interfaces provided by other installed applications (Android runtime also offers a provider-consumer type of file sharing implementation between applications).
A sample file browser implementation (Xamarin recipes)
If there is an application that, by default, handles the file dialogs (the activity that can handle ActionGetContent intent), it can be invoked with an intent and the result can be accessed through the OnActivityResult callback method.
SQLite
SQLite database implementations provide a relational persisted data structure in mobile application projects. Unlike the general server/client model that is used by relational databases, SQLite is a local database implementation and the data is stored in application local storage. Both Xamarin.iOS and Xamarin.Android application projects can include a SQLite database and associated implementations.
[ 767 ]
Local Data Management
In order to use SQLite, developers are to choose to between the cross-platform implementation of ADO.Net, where the SQL queries are supposed to be created and included as plain text, or use the linq-2-entities access model of the SQLite.Net portable class library. It is available as a NuGet package and a component.
SQLite.Net PCL
For the following demonstration, we will use the asynchronous version of the SQLite.Net library. Implementation of the SQLite data access layer with SQLite.Net generally follows a code first database programming paradigm. In this pattern, developers first define their data model by creating entity classes and defining the data structure using the provided attributes. The code is as follows: public class LocationInfo { [PrimaryKey, AutoIncrement] public int LocationInfoId { get; set; }
[ 768 ]
Chapter 4 public string Name { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } }
Once the data model implementation is finished, we can start creating the SQLite access methods. In order to create a SQLite connection, first an application storage location has to be defined for the database file. The code is as follows: public TravelContext(string sqlitePath, ISQLitePlatform platform) { var connectionString = new SQLiteConnectionString(sqlitePath, false); var connectionWithLock = new SQLiteConnectionWithLock(platform, connectionString); m_SqliteConnection = new SQLiteAsyncConnection(() => connectionWithLock); // OR with non-async connection //var connection = new SQLiteConnection(platform, sqlitePath); }
In this implementation, ISQLitePlatform provides the much needed abstraction for the platform-specific APIs. After the SQLite connection is ready for use, we can implement the data tables' access and creation methods. The code is as follows: private void InitTablesAsync() { var tasks = new List(); tasks.Add (m_SqliteConnection.CreateTableAsync()); tasks.Add(m_SqliteConnection.CreateTableAsync()); tasks.Add(m_SqliteConnection.CreateTableAsync()); tasks.Add(m_SqliteConnection.CreateTableAsync()); // OR
[ 769 ]
Local Data Management //var initTask = m_SqliteConnection. CreateTablesAsync(); var initTask = Task.WhenAll(tasks); initTask.ConfigureAwait(false); }
We can now expose the tables through public properties in our data context, so the upper layers can execute queries against these tables. The code is as follows: var dbPath = Path.Combine(this.FilesDir.Path, "myTravelDb.db3"); // TODO: Use Dependency Injection var platform = new SQLitePlatformAndroid(); var myDbContext = new TravelContext(dbPath, platform); var landmarksInCityTask = await myDbContext.Landmarks .Where(item => item.CityId == cityId).ToListAsync();
It is possible to extend the data model with entity relations and cascade operations. There are also available extensions for the SQLite.Net PCL library for lazy loading and child-related operations.
Patterns and best practices
In this section, we will have a look at two common patterns that are common to mobile applications and how to implement these usage scenarios in a platform agnostic manner.
Application preferences
Application preferences is a common scenario in mobile applications. In order to use the previously described property list on iOS and SharedPreferences on Android, a common dictionary interface is often the most appropriate approach. The interface would then be inherited on platform-specific projects and can be injected into the common library. For a simple demonstration, we can define a simple interface that will retrieve and save string values. The code is as follows: public interface ISettingsProvider { string this[string key] { get; set; } } [ 770 ]
Chapter 4
The implementation on the Android side would use a simple dictionary using a shared preference implementation. The code is as follows: public class SettingsProvider : ISettingsProvider { private readonly ISharedPreferences m_SharedPreferences; public SettingsProvider(string name = "default") { // Retrieve and hold the contents of the preference file' m_SharedPreferences = Application.Context.GetSharedPreferences(name, FileCreationMode.Private); } public string this[string key] { get { if (m_SharedPreferences.Contains(key)) m_SharedPreferences.GetString(key, string.Empty); return string.Empty; } set { var editor = m_SharedPreferences.Edit(); editor.PutString(key, value); editor.Apply(); } } }
On the iOS side, the implementation would use an NSMutableDictionary class to facilitate the preferences being edited by the user. The code is as follows: public string this[string key] { get { if (m_MyNSMutableDictionary.ContainsKey(new NSString(key))) {
[ 771 ]
Local Data Management return MyNSMutableDictionary [key].ToString(); } return string.Empty; } set { MyNSMutableDictionary [key] = new NSString(value); MyNSMutableDictionary.WriteToFile(GetPropertyListPath(), true); } }
Now that the persisted dictionary has been implemented on both platforms, we can include the application settings as a singleton to be used with a dependency injection. This implementation can be extended using the Settings API on the iOS platform and using the preferences views (PreferencesFragment and PreferencesActivity) on the Android platform to create a more native-looking implementation.
File picker
In a cross-platform application project, if we are following an MVVM pattern, the view-model should reside in a shared project or a PCL so that the business logic can be shared between the apps. However, if we have a requirement to pick a file for processing, the method implementation should reside in the view itself since the platform-specific project that holds the view has access to platform features. Although it would be moving the business logic to the UI components, the work has to be done by the view. You can, however, delegate the responsibility of the view model to the view without compromising the MVVM implementation. The delegation process can be executed through Inversion of Control (IOC) into the interface that defines the file picking operation. To demonstrate this usage, we will use an interface called IFilePickerService. In this example, we just want to let the user pick a file and return the resulting file path back to the view-model and maybe the model. The code is as follows: public interface IFilePickerService { Task PickFileAsync(); }
[ 772 ]
Chapter 4
We will use this interface in the view-model to call for the view to execute the logic. The code is as follows: return new MvxCommand(() => { m_FilePickerService.PickFileAsync() .ContinueWith(task => { Debug.WriteLine("File Picked:" + task.Result); }); });
For Android implementation, we will be using the default file manager application that supports the respective intent type. We need to convert intent execution and the callback call on the OnActivityResult class into an asynchronous implementation. In order to do this, we will be using a task completion source. The code is as follows: private TaskCompletionSource m_PickFileCompletionSource;
The private variable will be initialized every time the intent is called and the result will be set in the callback method. With this pattern in mind, the interface method implementation would look similar to this: public Task PickFileAsync() { m_PickFileCompletionSource = new TaskCompletionSource(); Intent intent = new Intent(); intent.SetType("*/*"); intent.SetAction(Intent.ActionGetContent); intent.AddCategory(Intent.CategoryOpenable); try { StartActivityForResult(intent, 0); } catch(ActivityNotFoundException ex) { throw new InvalidOperationException("Could not find a file manager"); } return m_PickFileCompletionSource.Task; } [ 773 ]
Local Data Management
Finally, the callback method implementation would be just setting the result on the TaskCompletionSource class. The code is as follows: protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); if (resultCode == Result.Ok) { m_PickFileCompletionSource.TrySetResult(data.Data.Path); } else if(resultCode == Result.Canceled) { m_PickFileCompletionSource.SetCanceled(); } }
Now that we have the IFilePickerService interface created, at least on the Android side, we have to register the type with the dependency injection provider we are using, and then we can rely on it to resolve the type in the view-model initialization. (We will be using the MVVMCross framework in this example.) The code is as follows: public MainView() { Mvx.RegisterType(()=>this); }
The resulting application would execute the pick file command and open up the file browser, returning the file path back to the view model. If the user cancels the file selection, the task would throw an exception notifying that the operation has been cancelled.
[ 774 ]
Chapter 4
Default file browser
For the iOS side of the story, our job is a little easier: public Task PickFileAsync() { var taskCompletionSource = new TaskCompletionSource(); var documentTypes = new string[] { UTType.PNG, UTType.Image, UTType.BMP }; var filePicker = new UIDocumentPickerViewController(documentTypes, UIDocumentPickerMode.Open); EventHandler documentPickedHandler = (sender, args) => { taskCompletionSource.SetResult(args.Url.Path); }; filePicker.DidPickDocument += documentPickedHandler; return taskCompletionSource.Task; }
With this completed, we just need to register the type and we finally have a cross-platform implementation of a command relying on the dependency injected, platform-specific methods.
[ 775 ]
Local Data Management
Backup/Roaming
Xamarin target platforms both offer cloud sync and backup mechanisms. While the Android backup strategy is more of an async background process where backup and restore operations have to be initiated by the calling application, the iOS and iCloud roaming strategy provides seamless integration to the filesystem.
Android and Backup API
Android Backup API and Google-provided backup transport services provide an easily accessible way for application developers to back up and restore application data to remote cloud storage. It is possible to restore data after a factory reset or one device to another using the APIs provided by the BackupManager. Backup operations are executed by the BackupManager in Android runtime and operations related to the application data are delegated to the BackupAgent registered in the application manifest. It is important to remember the fact that your application has to be registered in the Android Backup Service. It is crucial to include the backup service key that you receive from the registration in the package manifest. In order to create a BackupAgent, you must implement the OnBackup and OnRestore methods of the BackupAgent abstract class. In these methods, the old and new states of your data are served in the form of ParcelFileDescriptor (file metadata that can be used to access the actual file). In the restore method, you also receive the application version that might be helpful if the data structure has changed between application updates. Another way to create an agent is to use the existing agent template (BackupAgentHelper) and use the existing helper classes to back up and restore certain subsets of your application data. For instance, the SharedPreferencesBackupHelper class is a generic implementation of a backup operator on SharedPreferences files that are used by your application. The preferences groups for the application can be passed onto the helper and the helper class can deal with the backup logic implementation. Another helper class is the FileBackupHelper class that can be used to back up and restore application files.
[ 776 ]
Chapter 4
In order to demonstrate the Backup API and a usual backup scenario, we can create a backup agent that will trace out the backup events and method executions. The implementation class should derive from the BackupAgentHelper class: public class PreferencesBackupService : BackupAgentHelper { // TODO: Override the methods we might need }
To include this backup agent in our application, we can either edit the application manifest or use the ApplicationAttribute attribute in the assembly info. Both AssemblyInfo.cs and AndroidManifest.xml can be found under the Properties project folder.
Application manifest and AssemblyInfo
[ 777 ]
Local Data Management
Using the ApplicationManifest.xml file, let's add the backup agent and backup services key:
The preceding application manifest entry is how it would look if we were dealing with Java class libraries, not Xamarin and the JNI Bridge. In fact, this registration would throw an error as soon as a backup request is received. The code is as follows: 09-22 18:28:33.647 E/ActivityThread(32153): Agent threw during creation: java.lang.ClassNotFoundException: Didn't find class "Xamarin.Master. Android.PreferencesBackupService" on path: DexPathList[[zip file "/data/ app/Xamarin.Master.Android-1.apk"],nativeLibraryDirectories=[/data/applib/Xamarin.Master.Android-1, /system/lib]]
To register the PreferencesBackupService class with the Android runtime, we need to add an identifier for the type itself. Since we are not using a namespace qualifier in the manifest declaration, we can register the class in the application default namespace: [Register("Xamarin.Master.Android.PreferencesBackupService")] public class PreferencesBackupService : BackupAgentHelper
If we were to use the Application attribute to register our backup agent without the application manifest entries, the attributes would look similar to the following using the AssemblyInfo.cs file: [assembly: Application(AllowBackup = true, BackupAgent = typeof(PreferencesBackupService))] [assembly: MetaData("com.google.android.backup.api_key", Value = "...")]
In this case, the android callable wrapper (ACW) is created with the default naming convention for our backup agent and inserted into the application manifest, so we didn't need to register our class additionally. The generated entry for the application manifest contains the MD5 hash of the pair namespace and the containing assembly: md5d06a1058f86cf8319abb1555c0b54fbf.PreferencesBackupService
[ 778 ]
Chapter 4
If you are developing with Visual Studio and running your application on Emulator, you can see the generated MD5 values for the Android exposed classes in the \obj\\ android\src directory.
Android source directory
Once the registration is complete, we can override a couple of methods in the agent class to get the trace information. The code is as follows: public override void OnCreate() { var preferencesHelper = new SharedPreferencesBackupHelper(this, "ApplicationSettings"); AddHelper("ApplicationPreferences", preferencesHelper); Debug.WriteLine("PreferencesBackupService was created", "BackUp"); base.OnCreate(); }
[ 779 ]
Local Data Management
You can now open an Android Adb Console and use the following commands to trigger a backup request: adb shell bmgr enable true adb shell bmgr run
Once your data segments change, you can use the DataChanged method of the BackupManager class and use it to request restore operations. (Restore operations are, under normal circumstances, scheduled and performed by Android backup services, so the app does not need to explicitly call it.) The code is as follows: BackupManager backupManager = new BackupManager(this); // Notifying the backup manager about data changes backupManager.DataChanged(); // Using an implementations of RestoreObserver class to request restore backupManager.RequestRestore(new MyRestoreObserver());
iOS and ubiquitous storage
In order to use iCloud features in your iOS applications, they must be configured in the Apple Provisioning Portal and the project manifest. In the provisioning portal, while creating the App ID, iCloud must be selected as one of the enabled services. Then, using the . format, the container identifier must be inserted into the Entitlements.plist file. The keys that have to be edited are as follows: com.apple.developer.ubiquity-kvstore-identifier com.apple.developer.ubiquity-container-identifiers
On iOS, the simplest synchronization mechanism provided is for primitive data types in the form of key/value pairs. This is used for simple user preferences or application required values that need to be synchronized between separate clients. The total size of a key/value pair cannot exceed 64 kilobytes, while the maximum value size is 64 kB and key size is 64 bytes.
[ 780 ]
Chapter 4
The synchronizing context can be accessed through the NSUbiquitousKeyValueStore class. The code is as follows: /// /// Synchronizes local values to the cloud /// private void SyncUpSettings() { var store = NSUbiquitousKeyValueStore.DefaultStore; // // Can use designated set functions for different value types // string, bool, NSData, NSDictionary, NSObject[], long, double store.SetString("myStringValue", "New String Value"); store.SetLong("myLongValue", 1234); store.SetBool("myBoolValue", true); store.Synchronize(); }
Using the same store, you can access the values: /// /// Gets the values from synchronized local storage /// /// private Dictionary GetValues() { var results = new Dictionary(); var store = NSUbiquitousKeyValueStore.DefaultStore; // // Getting the synchronized LOCAL values results.Add("myStringValue",store.GetString("myStringValue")); results.Add("myLongValue", store.GetLong("myLongValue")); results.Add("myBoolValue", store.GetBool("myBoolValue")); return results; }
The synchronization process does not happen right after the synchronize method is invoked. The process is initiated according to iCloud's own schedule; up-sync generally happens within 5 seconds, while the only way to exactly know when the down-sync occurs is by adding an Observer delegate to the NSUbiquitousKeyValueStore events.
[ 781 ]
Local Data Management
The code is as follows: NSNotificationCenter.DefaultCenter.AddObserver( NSUbiquitousKeyValueStore.DidChangeExternallyNotification, (notification) => { NSDictionary userInfo = notification.UserInfo; // NInt: 0-ServerChange, 1-InitialSyncChange, // 2-QuotaViolationChange NSNumber reasonNumber = (NSNumber) userInfo. ObjectForKey(NSUbiquitousKeyValueStore.ChangeReasonKey); // NSString[] You can used the changed items list to sync only those values NSArray changedKeys = (NSArray) userInfo. ObjectForKey(NSUbiquitousKeyValueStore.ChangedKeysKey); // OR get the latest values from synchronized local storage var latestValues = GetValues(); });
For synchronized files, the implementation is a little more complicated. While backup and restore scenarios are automatically handled by the iOS application and iTunes, for keeping a synchronized file storage, developers need to implement the UIDocument class to prepare type of documents that needs to be synced between devices. The UbiquityContainer directory is managed by the so-called daemons to coordinate the synchronization and modifications of the files on the iCloud context. In order not to cause concurrency problems and interfere with the daemon processing, the files in question need to be accessed and modified with the NSFilePresenter and NSFileCoordinator classes. The easiest way to use the presenters and coordinators for file operations is to implement the UIDocument base class. There are two virtual methods that need to be implemented to read data and write data to documents. Let's assume that we want to keep a synchronized context for serialized entity data for our application. First, we need to declare our class as inheriting and implementing the required constructor from the UIDocument class. The code is as follows: public class EntityDocument : UIDocument {
[ 782 ]
Chapter 4 public EntityDocument(NSUrl url) : base(url) { m_Type = typeof(T); }
We then need to implement the two virtual methods. The following load method defined just deserializes the data from the cloud into the entity defined in the generic class type definition. The code is as follows: /// /// Content down-sync'd from the cloud /// public override bool LoadFromContents(NSObject contents, string typeName, out NSError outError) { // TODO: Implement a try/catch block to return (if any) errors as well as negative result (i.e. return false). outError = null; if (contents != null) { var serializedData = NSString.FromData((NSData)contents, NSStringEncoding.UTF8); m_Entity = JsonConvert.DeserializeObject(serializedData); } // LoadFromContents called when an update occurs NSNotificationCenter.DefaultCenter. PostNotificationName(string.Format("{0}DocumentModified", m_Type.Name), this); return true; }
Finally, we can implement the save method that will serialize the object and serve the stream to be saved in the ubiquitous container. The code is as follows: /// /// Content to up-sync to the cloud /// public override NSObject ContentsForType(string typeName, out NSError outError)
[ 783 ]
Local Data Management { // TODO: Implement a try/catch block to return (if any) errors as well as negative result (i.e. return false). outError = null; if (m_Entity != null) { var serializedData = JsonConvert.SerializeObject(m_Entity); NSData docData = new NSString(serializedData). Encode(NSStringEncoding.UTF8); return docData; } return null; }
In order to be able to use this implementation with an example class, named LocationInfo, we can first implement a load file procedure (we are using a single file query for each location loaded, but this can be extended using queries like ENDSWITH or CONTAINS). The code is as follows: private void GetLocationsInfo(string locationName) { var locationDataQuery = new NSMetadataQuery(); locationDataQuery.SearchScopes = new NSObject[] {NSMetadataQuery.UbiquitousDocumentsScope}; locationDataQuery.Predicate = NSPredicate.FromFormat(string.Format("{0} == %@", NSMetadataQuery.ItemFSNameKey), new NSString(locationName + "Data.txt")); NSNotificationCenter.DefaultCenter.AddObserver(this, new Selector("locationLoaded:"), NSMetadataQuery.DidFinishGatheringNotification, locationDataQuery); locationDataQuery.StartQuery(); }
[ 784 ]
Chapter 4
Once the query returns, we can expand the object into the data needed. The code is as follows: [Export("locationLoaded:")] private void DidFinishGatheringHandler(NSNotification notification) { var locationQuery = (NSMetadataQuery) notification.Object; locationQuery.DisableUpdates(); locationQuery.StopQuery(); NSNotificationCenter.DefaultCenter.RemoveObserver(this, NSMetadataQuery.DidFinishGatheringNotification, locationQuery); LoadLocationInfo(locationQuery); // listen for notifications that the document was modified via the // server NSNotificationCenter.DefaultCenter.AddObserver(this, new Selector("itemReloaded:"), new NSString("LocationInfoDocumentModified"), null); }
The LoadLocationInfo function in the example would simply try to open the file and deal with the loaded data. The code is as follows: private void LoadLocationInfo(NSMetadataQuery locationDataQuery) { if (locationDataQuery.ResultCount == 1) { NSMetadataItem item = (NSMetadataItem) locationDataQuery.ResultAtIndex(0); var url = (NSUrl)item. ValueForAttribute(NSMetadataQuery.ItemURLKey); m_LocationData = new EntityDocument(url); m_LocationData.Open((success) => { if (success) { var info = m_LocationData.Entity; // TODO: Do something with the location info loaded }
[ 785 ]
Local Data Management else Console.WriteLine("failed to open iCloud document"); }); } }
Notice that we are also subscribing to the data changed event with the notification name we defined in the EntityDocument class (string.Format("{0} DocumentModified", m_Type.Name ). The reload implementation is simply gathering the object from the notification itself. The code is as follows: [Export("itemReloaded:")] private void DataReloadedHandler(NSNotification notification) { var locationData = (EntityDocument) notification.Object; var entityData = locationData.Entity; // TODO: Do something with the location info loaded. }
For saving and synchronizing the data, we just need to assign the new data and update the change count on the UIDocument class. The code is as follows: private void SyncLocationDataChanges(LocationInfo info) { m_LocationData.Entity = info; m_LocationData.UpdateChangeCount(UIDocumentChangeKind.Done); }
This topic will be discussed further in Chapter 5, Networking.
Summary
In this chapter, we discussed some of the local storage containers and access strategies. In both of the Xamarin platforms, with the additional option to back up and synchronize the data to the cloud, developers can create a consistent user interface as well as stateful mobile applications. In the next chapter, we will discuss the network connectivity options and how to use connected data together with local storage options provided with the target Xamarin platforms.
[ 786 ]
Networking In this chapter, we will take a detailed look at the networking capabilities of Xamarin applications and various service integration scenarios. The chapter also includes real-world examples on how to use local storage for data caching on connected app scenarios. It is divided into the following sections: • Connected apps • Web services • Push notifications • SignalR • Patterns and best practices • Platform-specific concepts • Cloud integration
Connected apps
Mobile applications by definition should be as lightweight and resource-efficient as possible. You cannot expect to package media and other content into the application and then distribute the app or create an extravagant size of storage for user data, especially with applications whose main purpose is to provide user access to related content or store and manipulate the data. For instance, while dealing with cross-platform projects, one of the easiest ways to create unified business logic and storage is to create a web service layer and delegate the responsibility and logic to this layer. In this scenario, the application(s) would be simply responsible for serving the content provided by the service layer or communicating the user input to the service layer.
[ 787 ]
Networking
This approach not only increases the efficiency of the application(s) but also creates an abstraction between the logic implementation and the presentation. This allows the developers to be free from the platform constraints on technology choices for storage and execution. It is also important to mention that applications' dependency on external resources is not a matter of choice but has rather become a necessity, since applications are more and more dependent on third-party web service APIs and social media networks.
Web services
A web service is generally defined as an interoperable machine-to-machine communication over the wire (network). In the context of cross-platform application, the most important term in this definition would be "interoperable". Web services written in different frameworks or languages and running on different type of runtimes and hardware conform to the same standards, most of which can be consumed by applications running on a variety of platforms, including Xamarin target platforms. Xamarin target platforms, namely iOS and Android, and Windows Runtime, can access stateless web services using the TCP/IP (short for Transmission Control Protocol / Internet Protocol) stack over a secure or non-secure HTTP (short for Hypertext Transfer Protocol) transport layer. Even though various data representations can be consumed via web services, JSON and XML are the most common text-based notations used. While defining or accessing a web service, there are three basic elements that need to be taken into consideration. We can call these the A-B-C of a web service: Address, Binding, and Contract. The address is the remote access location to the service, binding defines the transport and security protocols, and contract defines the data types and the methods used by the service.
[ 788 ]
Chapter 5
While the methods and data types defined in the web service contract are very case-specific, transport and serialization protocols that can be used by Xamarin applications can be generalized. In web service scenarios, if the consumer is a Xamarin target platform, you should always be persistent about using asynchronous implementation for the client implementation. Asynchronous implementation for the web service clients decreases the chance of blocking the main thread, as discussed previously, and protects the application from network shortage related errors and crashes.
Transport
For Xamarin applications on both iOS and Android platforms, the main communication protocol is HTTP. HTTP transport can be secured on the client and/or message level using a certificate or credentials. The message-level security is optional in other versions of iOS and Xamarin.Android applications. In iOS 9, the App Transport Security (ATS) feature enforces secure connections to network resources. Even though it is possible to add certain domains to the exclusion list, or to turn off the ATS altogether for the target applications, it is strongly advised that you use secure transport over HTTP (or HTTPS) for Xamarin. iOS applications. Even though communication protocols for TCP, UDP, or web sockets over HTTP are fully or partially supported on Xamarin platforms, with the current service infrastructure implementation, these communication channels cannot be used with web services.
Messaging
Messaging specifications of a service define which format should be used while communicating data over the HTTP transport layer.
[ 789 ]
Networking
In Xamarin applications dealing with web services, messages should be constructed either according to the SOAP (Simple Object Access Protocol) or using POX (short for Plain Old XML) or JSON, depending on the service requirements.
Simple SOAP Communication Example
The messaging structure is mainly important for the serialization and deserialization of request and response pairs between the client and server implementations. Hence, it is possible to employ other types of data communication models, which would require additional custom implementation for the client and the server.
SOAP/XML services
SOAP web services use XML data objects enveloped in SOAP-defined schemas. Windows Communication Foundation (WCF) services and ASP.Net Legacy Services (ASMX) are both SOAP services and conform to the SOAP protocol.
[ 790 ]
Chapter 5
SOAP web service contracts are defined in Web Service Description Language (WSDL) and the WSDL document, together with other XML data schemas (for example, XSD files), are generally accessible through the web service URL. Using this document, web services can be defined in a consistent manner, irrespective of the underlying language, and can be interfaced with and consumed by various clients.
Service WSDL for a SOAP 1.1 Service
In Xamarin applications, one of the possible ways to create a so-called proxy (service consumer) is to use the Silverlight SDK to generate the access code. The main reason for using the Silverlight SDK is the fact that the Windows Communication Foundation client infrastructure is not fully included in the Xamarin core and only a subset of client features, very similar to the Silverlight framework, can be used to access web services.
[ 791 ]
Networking
In order to generate the client, you can simply use the command-line tool to execute the following command: slsvcutil http://localhost/ ReferenceService.svc /d:c:\bin\
SLSvcUtil can be found in various SDKs including Windows Phone 7, Windows Phone 8, Windows Phone 8.1 (Silverlight), as well as the actual Silverlight SDK directories: • • • •
C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.0\Tools\SlSvcUtil.exe C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v8.0\Tools\SlSvcUtil.exe C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v8.1\Tools\SlSvcUtil.exe C:\Program Files (x86)\Microsoft SDKs\ Silverlight\v5.0\Tools\SlSvcUtil.exe
The preceding command would generate a WCF client that can communicate with any web service that supports the SOAP 1.1 profile. If we were to consume a WCF service, the supported binding configurations would be BasicHttpBinding and WebHttpBinding (essentially a REST binding). WSHttpBinding and similar configurations use other SOAP profiles to envelope the data requests and responses.
Generating Silverlight Proxy
The generated client would have both the Event-Based and Asynchronous Programming Model (APM) asynchronous methods for accessing the client. [OperationContract (AsyncPattern=true, Action= "master.xamarin.com/ReferenceService/GetRegions", ReplyAction= "master.xamarin.com/ReferenceService/GetRegionsResponse")]
[ 792 ]
Chapter 5 IAsyncResult BeginGetRegions(Xamarin.Master.TravelTrace.Data.Region filter, AsyncCallback callback, object asyncState) List EndGetRegions(IAsyncResult result) public void GetRegionsAsync(Xamarin.Master.TravelTrace.Data.Region filter)
Another approach would be to create a web reference in Visual Studio or Xamarin Studio. A web reference can only be used to communicate with services that implement the WS-I Basic Profile 1.1 (in other words, SOAP 1.1). Web reference generated clients use the ASMX communication stack (.NET 2.0 Services Technology) as opposed to the WCF client infrastructure used by service references.
Add Web Reference Dialog (Visual Studio)
If we were to compare the generated clients from the web reference and the Silverlight SDK, we could easily identify the underlying technologies. // Web Service Generated Client. [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute(Name="BasicHttpBindi ng_ReferenceService", Namespace="master.xamarin.com")] [GeneratedCodeAttribute("System.Web.Services", "4.6.79.0")]
[ 793 ]
Networking public partial class AsmxReferenceServiceClient : System.Web.Services. Protocols.SoapHttpClientProtocol // WCF Generated Client [GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")] public partial class ReferenceServiceClient : System.ServiceModel. ClientBase, ReferenceService
Looking at the class diagram for both of the generated proxies, we can get some more insight into the method execution strategies:
Generated Proxy Comparison
The ideal way to integrate the generated proxy in a cross-platform project would be to add the service reference in a portable class library to be used by platform-specific projects. In order to be able to add a service reference in a PCL project in Visual Studio, you must remove Windows Phone 8.1 as one of the targets and/or add a reference to the System.ServiceModel namespace (Visual Studio will automatically remove Windows Phone 8.1 from the targets list). The Windows Phone 8.1 platform does not include the Windows Communication Foundation client assemblies. After this step, the Add Service Reference option will appear under the project context menu. For scenarios involving Windows Phone 8.1, the more appropriate solution would be to use a RESTful service and a client. [ 794 ]
Chapter 5
RESTful services
RESTful services are one of the most common distributed system implementations involving mobile applications. Compared to SOAP services, they don't have the overhead of SOAP protocols or the enveloping of the request/response pairs. In essence, network traffic caused by a SOAP method call is the same as the request/ response pair of a REST call. The simplicity of the Representational State Transfer (REST) model increases the performance and maintainability. Stateless and cacheable approaches of RESTful services makes them an optimal solution for Xamarin target platforms. REST services can essentially be described as static HTTP endpoints. The HTTP verbs (GET, PUT, POST, and DELETE) used to access these endpoints define the type of method to be invoked on the service layer (PUT for update, POST for create, and DELETE for delete actions). The messaging structure can vary from JSON to XML, even to ATOM. On Xamarin target platforms, there are various out-of-the-box options and additional components available for REST-based web services. Any of these options can be used to execute web requests and request/response pairs can be serialized/deserialized according to the requirements and chosen messaging media-type. Since we are making ordinary web requests to the REST endpoints, the simplest implementation would involve the HttpClient, which is included in the System. Net.Http namespace. For instance, if we were to implement a base class that will handle the CRUD (create, read, update, and delete) methods on the RESTful version of the web service used in the previous section (TravelTrace.ReferenceDataService), we could implement a per-call wrapper around the inner HTTP client layer. public BaseClient(string baseAddress, string securityToken) { if (string.IsNullOrEmpty(baseAddress)) throw new ArgumentNullExcep tion("baseAddress"); BaseAddress = new Uri(baseAddress); // Storing the security token in a class property of type string SecurityToken = securityToken.StartsWith("Bearer") ? securityToken.Substring(7) : securityToken; m_HttpClient = CreateHttpClient(); }
[ 795 ]
Networking
You will notice that we are using the base address as the server address and, if any, using the security token to initialize our client. In this implementation, the create method will simply create the HTTP client and use the authentication token as a default header. Another important requirement is to set the "Accept" header to announce which type of content the client is expecting from the server (JSON in this example). private HttpClient CreateHttpClient() { var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQuali tyHeaderValue("application/json")); if (string.IsNullOrEmpty(SecurityToken)) { httpClient.DefaultRequestHeaders.Authorization = new Authentic ationHeaderValue("Bearer", SecurityToken); } return httpClient; }
After the HTTP pipeline is ready to execute the requests, we can start implementing the base methods for the REST service. protected async Task GetStringAsync(string path) { // if we are using the BaseClient multiple times // we can create a new transport with each method //HttpClient httpClient = CreateHttpClient(); try { // Get the response from the server url and REST path for the data var response = await m_HttpClient .GetAsync(new Uri(BaseAddress, path)); if (response.StatusCode == HttpStatusCode.Unauthorized) { throw new UnauthorizedAccessException( "Access Denied"); }
[ 796 ]
Chapter 5 if (response.IsSuccessStatusCode) { return await response.Content.ReadAsStringAsync(); } throw new WebException(response.ReasonPhrase); } catch (Exception ex) { // TODO: throw ex; } }
Now, the GetRegions method looks like this: var regions = await GetStringAsync("regions");
The result of this request can be visualized in the debug screen:
JSON data returned from Web API
[ 797 ]
Networking
However, this is only the string representation of the service data, and we would need to extend our implementation to include a JSON serializer. There are multiple options available for serialization, including the standard libraries available through the Microsoft BCL package: System.Xml and System.Json namespaces. NewtonSoft Json.NET is still one of the most popular JSON libraries and available through NuGet. public async Task GetRegionsAsync(Region filter = null) { var result = new List(); var regions = await GetStringAsync("regions"); var resultingList = JToken.Parse(regions); await Task.Run(() => { result.AddRange(resultingList["value"] .Select(item => item.ToObject())); }); return result; }
Using this implementation, we can create generic methods in the base class implementation and push the serialization responsibility to this layer. protected async Task GetListAsync(string path) { List result = new List(); try { var response = await GetStringAsync(path); var resultingList = JToken.Parse(response); await Task.Run(() => { result.AddRange(resultingList["value"] .Select(item => item.ToObject())); }); } catch (Exception ex) { // TODO: throw ex; } return result; }
[ 798 ]
Chapter 5
We can extend this generic implementation for other web methods and create the basis for our RESTful client. The authentication scenario will be discussed further in the following section. There are many more REST consumer implementations available for the Xamarin developer and these modules can be included in cross-platform projects via components and NuGet packages (RestSharp, Hammock, and so on).
OData and OAuth
OData and OAuth are two widely accepted standards/protocols for RESTful communication scenarios. Xamarin mobile applications that deal with external resources, and especially third-party web service APIs, are generally implementing these protocols.
OData
Unlike SOAP, which is a communication protocol, REST is simply an architectural approach to web service implementations. RESTful services do not need to conform to certain specifications and may vary greatly. In order to identify the requirements for RESTful services and create a uniform structure for data being exchanged between the client applications and the server, OData was initiated by Microsoft in 2007. OData is now an internationally accepted protocol that is maintained by OASIS and supported/used by various applications, platforms, and companies (for example, Microsoft Azure Mobile Services, Microsoft Office 365 Web Access, Salesforce, SAP Netweaver Gateway Solution, IBM WebSphere, and so on). In OData protocol, each object set is defined by an endpoint in line with REST principles. For GET requests, these entity set endpoints can either accept object identifiers, which results in the details of that specific entity instance, or entities in the list can be queried with OData filter and other query options. Similar to the WSDL in SOAP/XML services, accessible endpoints (entity sets and functions) and types used in the service contracts are generally served through the metadata endpoint with a CSDL (OData Common Schema Definition Language) file in OData. To access the whole list of elements, visit http://localhost/Xamarin.Master. TravelTrace.Service.Api/odata/regions. To access a single element in the entity set endpoint, visit http://localhost/
Xamarin.Master.TravelTrace.Service.Api/odata/regions(guid'90222c1866fa-441a-b069-0115faa1e0f1').
[ 799 ]
Networking
To query the list of elements with a filter, visit http://localhost/Xamarin.
Master.TravelTrace.Service.Api/odata/regions?$filter=Continent eq 'Europe'.
Advanced OData queries involving additional property expansions, lambda operators, and functions are also possible with the OData protocol; however, these topics are beyond the scope of this module. There are multiple NuGet packages and components available both as open source and/or free to download that help with the client generation for OData services.
OAuth
OAuth is an open standard used generally by service providers for authorization. A general use case for OAuth would be to use third-party identity providers such as Live ID (Microsoft), Google, Facebook, or Twitter for authentication and authorization in a mobile or web application. A classic OAuth 2.0 implementation scenario is generally a two-step process. The first step involves the user granting access to the client application through the provider web interface. The second step is using the authorization code received from the provider's web interface to get an access token to access the provider's resources.
Facebook as Auth Provider
[ 800 ]
Chapter 5
The first step of the authorization process on a web application is generally an iframe displaying the provider's authorization page. In a Xamarin application, this step is executed using a web view control or a more specialized implementation (WebAuthenticationBroker is an out-of-box control on Windows Phone 8.1). Implementing the two-step authentication process can become quite cumbersome considering the fact that the provider's page makes a callback request to the client application page with the authorization token and the client app is responsible for parsing and extracting this token either from the callback URL or the body of the content.
Xamarin.Auth Components
To provide access to OAuth APIs and simplify the implementation, developers can make use of the available Xamarin OAuth component: Xamarin.Auth (available on Xamarin.iOS and Xamarin.Android platforms). There is also an accompanying component for social media provider APIs: Xamarin.Social. Using the Xamarin.Auth implementation, authenticating with the Facebook API can become as simple as a few lines of code. var authenticationBroker = new OAuth2Authenticator( clientId: "", scope: "", authorizeUrl: new Uri("https://m.facebook.com/dialog/oauth/"), redirectUrl: new Uri("http://www.facebook.com/connect/login_ success.html")); authenticationBroker.Completed += (sender, eventArgs) => { DismissViewController(true, null); if (eventArgs.IsAuthenticated) { // TODO: eventArgs.Account contains the authenticated user info } else { [ 801 ]
Networking // TODO: Possibly the user denied access to the account or // the user could not authenticate with the provider } }; // The GetUI method returns UINavigationControllers on iOS, and Intents on Android PresentViewController(authenticationBroker.GetUI(), true, null);
SignalR
ASP.NET SignalR is a web server-side technology that allows developers to pass real-time updates to their applications. SignalR works in a similar way to WCF duplex channels where the server side is accessible through the main service contract and the server-to-client communication occurs through the callback contract. While WCF duplex channels provide support for the same scenarios as SignalR, duplex channel implementation is currently not supported in any of the Xamarin target platforms. On the other hand, there is a component available for use on all Xamarin target platforms for SignalR.
SignalR Component
[ 802 ]
Chapter 5
SignalR takes advantage of WebSockets, which enables bidirectional communication over the HTTP transport. In essence, WebSockets works almost in the same way as TCP Sockets; however, the connection is established over the HTTP transport layer. Using SignalR, applications requiring real-time data can be implemented without resorting to polling or listener channel implementations, which is neither scalable nor efficient on mobile platforms. SignalR is generally implemented with a Hub application on the server-side, which creates different event sinks to be subscribed by different applications. Each client that subscribes to a certain channel gets event notifications and data over these channels in a normal broadcast scenario in a string format or already deserialized as a complex type. // Connect to the server var hubConnection = new HubConnection("http://xamarin.traveltrace. com/"); // Create a proxy to the 'MainHub' on the SignalR server var myHubProxy = hubConnection.CreateHubProxy("MainHub"); // Subscribe to message from the server myHubProxy.On("ServerStringCall", message => { // TODO: use the message update from the channel }); // Subscribe to message with a complex type myHubProxy.On("ServerComplexCall", message => { // TODO: use the message update from the channel }); // Start the connection await hubConnection.Start();
SignalR server implementations can, generally speaking, replace RESTful service actions. These duplex hubs can provide functions to be called by the consumers as well as update calls from the server to listening clients.
[ 803 ]
Networking
While different message formats can be used to exchange data, most implementations employ the JSON format to serialize and deserialize data, and Json.NET is the default serialization library used by the SignalR component. await myHubProxy.Invoke("MySimpleServerMethod", "myParameter"); await myHubProxy.Invoke("MyComplexServerMethod", new Region{Continent = Continent.Europe});
On top of the server invoked events, SignalR channels also offer lifetime events: • Received: Raised when any data is received on the connection. Provides the received data. • ConnectionSlow: Raised when the client detects a slow or frequently dropping connection. • Reconnecting: Raised when the underlying transport begins reconnecting. • Reconnected: Raised when the underlying transport has reconnected. • StateChanged: Raised when the connection state changes. Provides the old state and the new state. • Closed: Raised when the connection has disconnected. SignalR supports SSL transport security as well as having the ability to integrate with the existing authentication and authorization providers already being used by the web server and mobile applications.
Patterns and best practices
In mobile applications, developers often use certain reusable design patterns while using web services and other communication channels in development projects. These patterns aim to increase the efficiency and increase the code sharing not only between platforms but also among various execution domains of cross-platform mobile applications.
Async conversions
The generated proxies for WCF and/or SOAP/XML services generally include either an event-based async implementation or an asynchronous invoke pattern with begin and end methods. Both of these implementations can be converted to a task-based async pattern.
[ 804 ]
Chapter 5
In order to convert the event-based async service method to a task-based one, we can use TaskCompletionSource and return the task that is produced (refer to Chapter 3, Asynchronous Programming). public Task GetRegionsAsync(Region filter = null) { var taskAwaiter = new TaskCompletionSource(); var client = CreateServiceClient(); EventHandler completedDelegate = null; completedDelegate = (sender, args) => { if (args.Error != null) { taskAwaiter.SetException(args.Error); } taskAwaiter.SetResult(args.Result); client.GetRegionsCompleted -= completedDelegate; }; client.GetRegionsCompleted += completedDelegate; client.GetRegionsAsync(new Region { Continent = Continent.Europe }); return taskAwaiter.Task; }
For the async invoke pattern, we can use the designated methods from the TaskFactory. The FromAsync method of the TaskFactory uses the begin and end methods together with the async state object (which can, for example, be used for cancellation token or progress callback) and creates an awaitable task. public Task GetRegionsAsync(Region filter = null) { var client = (ReferenceService.ReferenceService) CreateServiceClient();
[ 805 ]
Networking var task = Task.Factory .FromAsync( (callback, o) => client.BeginGetRegions(filter, callback, o), result => client.EndGetRegions(result), null); return task; }
Data model abstraction
Following the quality identifiers that were put forward previously, in service-related scenarios, it is important to create a data model abstraction layer which can be used by different branches of a cross-platform application. Using the travelers' guide application example from previous sections, we can analyze the sharing strategy. In this example, as a development team or a single developer, we are responsible for: • Implementing the service layer responsible for accessing the database and connecting to external APIs, if necessary • Implementing the shared common logic which will be used by Xamarin applications • Implementing the Xamarin.iOS and Xamarin.Android applications • Implementing the Windows Phone 8.1 application • Implementing the web interface which will employ a Silverlight component (optional) For simplicity, we will be implementing only a single data type and a single GET method. For the contracts and the data objects, we can create a portable library that will be targeting Xamarin platforms together with .NET 4.5. The reason we are including the .NET profile is because we will be using the data model in the service layer implementation as well. The implementation starts by creating the Data Transfer Model objects. These objects are generally the reflection of the database tables used on the service layer. However, one-to-one mapping between DTOs and DBOs (Entity Framework items) is not absolutely necessary since the DTO abstraction layer's sole purpose is to create an abstraction layer over the actual data repository that we will be dealing with. public class Region { [ 806 ]
Chapter 5 [JsonProperty("id")] public Guid Id { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("continent")] public Continent Continent { get; set; } }
Notice that we are including Json.NET attributes to define class properties. They are used to format the JSON object attributes during serialization/deserialization to camel-case (for example, camelCase), which is the JavaScript convention, rather than the .NET convention of pascal-case (for example, PascalCase) for property names. These property definitions can be used with RESTful clients and web service implementations. This will not interfere with other service or client layer use cases.
After we create the model, we can define the interface(s) that will be used by the web service and associated clients. We will define two interfaces for synchronous implementation on the service layer and asynchronous consumption on the client side. namespace Xamarin.Master.TravelTrace.Common.Infrastructure { public interface IReferenceService { List GetRegions(Region filter = null); List GetCountries(Country filter = null); List GetCities(City filter = null); } public interface IReferenceServiceAsync { Task GetRegionsAsync(Region filter = null); Task GetCountriesAsync(Country filter = null); Task GetCitiesAsync(City filter = null); } } [ 807 ]
Networking
The service implementation strategy would normally be to use a RESTful layer. For demonstration purposes, let's implement the WCF service in a separate project, reusing the data model defined and the interface previously created.
Solution Structure
In this implementation, each service method will be calling a data repository (Entity Framework/MSSQL) and the repository will be returning the DTO objects by converting the database layer entities.
[ 808 ]
Chapter 5
The next section of the project that we need to implement would be the service data consumer layer. We will create a new portable library for this layer and use a generated WCF client. After creating the project and adding the reference to the System.ServiceModel namespace and the common portable library that contains the DTO model, an important detail to remember is to make sure that the generated proxy reuses the referenced libraries.
Service Reference Properties
If you are using the Silverlight SDK to generate the client, it is a little more complicated to include the existing libraries so that the types are reused. In order to do this, you can use the "reference" switch (or simply, /r: ) and point the utility to the assemblies that contain the implemented types. slsvcutil http://localhost/ReferenceService.svc /d:c:\bin\ /r:C:\Local\Xamarin.Master.TravelTrace. Common.dll
[ 809 ]
Networking
After creating the proxy, we have a structure in which the data model and the contracts are shared by different layers of the application including the service, data access layer, service proxy, and finally, the applications.
Shared service structure
The implementation, however, should be further extended with conversions to task-based async implementation on the service proxy. Another useful improvement would be to implement local DB caching and offline storage. For this caching layer, the same DTO implementation can be reused. If we were to include a Windows Phone 8.1 client in this cross-platform project, the only solution to the lack of WCF infrastructure would be to exchange the WCF service with a RESTful implementation.
Service cache
When dealing with network scenarios, it is important to keep in mind that mobile devices do not always have a good network connectivity or network at all. In order to make the Xamarin connected app usable even in offline scenarios, a caching layer can be implemented to store and return data items that do not often change.
[ 810 ]
Chapter 5
For instance, in travel guide applications, users will want to access guides, and possibly maps, even when they are with a roaming connection or, even worse, without any connection at all. To facilitate offline storage, we can implement a SQLite database that uses the existing data transfer objects as storage items and updates the data on certain intervals when there is Internet connectivity. The first step of the implementation would be to revise our DTO layer classes and add SQLite attributes if needed. This will create a dependency on the service layer for SQLite assemblies; the other option is either to use linked code files between the service layer and the client libraries or to recreate the DTO objects specifically for the SQLite data store. public class Region { public Region() { Countries = new List(); } [PrimaryKey] [JsonProperty("id")] public Guid Id { get; set; } [JsonProperty("name")] public string Name { get; set; } [JsonProperty("continent")] public Continent Continent { get; set; } [OneToMany(CascadeOperations = CascadeOperation.CascadeInsert | CascadeOperation.CascadeRead)] [JsonProperty("countries")] public List Countries { get; set; } }
[ 811 ]
Networking
In this scenario, in order to create a data context that will use the online storage if available and use the local data storage if Internet connectivity is limited, we can implement the same data interface that we created for the service proxy in the previous examples for the SQLite data source and create one parent handler for the data sync context.
Data Abstraction on App Tiers
In the sync context, for GET methods, the service calls will be used only for updating the local storage and actual results will be returned from the local storage. For PATCH, POST, and PUT calls, depending on the online connectivity, we will be either saving the data locally or pushing the deltas and new object instances to the service and updating the local data with the updates. public class DataSyncContext : IReferenceServiceAsync { public IReferenceServiceAsync LocalDataService { get; set; } public IReferenceServiceAsync RemoteDataService { get; set; } ... public async Task GetRegionsAsync(Region filter = null)
[ 812 ]
Chapter 5 { try { // Getting the online results var results = await RemoteDataService. GetRegionsAsync(filter); // If there were any online changes. SyncToLocal(results); } catch (Exception ex) { // TODO: } // Returning the local storage results (with or without updates) return await LocalDataService.GetRegionsAsync(filter); } ... }
For performance improvement in this implementation, when we are loading data for certain visualizations, we can first call the local data provider and continue with UI updates and then call the web service method and the same continuation delegate. Action onRegionsLoaded = regions => { // Update the view-model data or the UI. }; DataContext.LocalDataService.GetRegionsAsync() .ContinueWith((task) => { onRegionsLoaded(task.Result); }); DataContext.GetRegionsAsync() .ContinueWith((task) => { onRegionsLoaded(task.Result); });
[ 813 ]
Networking
Platform-specific concepts
There are other concepts and network communication methods on Xamarin platforms that are provided by the native runtime and supported by Xamarin.
Permissions
In order for an Android or Windows Phone application to access Internet, the application manifest should declare that the application will need to use the network to access resources. The permission on Android system is declared using the uses-permission tag in the manifest node of the XML file:
While this declaration will suffice in most use case scenarios, in order to access the current network status or the Wi-Fi status, you must also declare the network state permissions:
For a Windows phone, the app capability to declare would be ID_CAP_NETWORKING. Application manifests for both platforms can be edited through the application project properties in the designated configuration section.
[ 814 ]
Chapter 5
Android Manifest
iOS, other than the App Transport Security (ATS) that was mentioned previously, does not enforce any manifest setup or permissions for applications to use network connection.
NSUrlConnection/NSUrlSession (iOS Only) Apart from the different client libraries available for use with Xamarin target platforms, some native implementations can also be used to call and receive external web data. One of these available options for Xamarin.iOS platform is NSUrlConnection. With the help of NSUrlConnection, developers can make web requests and use the response.
[ 815 ]
Networking
A simple web request to retrieve the data from the previously demonstrated static data endpoint on iOS would look similar to this: public Task GetRegionsAsync(Region filter = null) { var nsUrlRequest = new NSUrlRequest(new NSUrl(myServiceEndpoint)); var taskSource = new TaskCompletionSource(); var nsUrlConnection = new NSUrlConnection(nsUrlRequest, new ConnectionSerializingHandler(taskSource)); nsUrlConnection.Start(); return taskSource.Task; }
The implementation for the connection delegate would involve the deserialization of the data and assigning the result to the TaskCompletionSource so the method execution can be finalized. public class ConnectionSerializingHandler : NSUrlConnectionDataDelegate where T:class,new() { private StringBuilder m_ResponseStore; private TaskCompletionSource m_TaskCompletion; public bool IsFinishedLoading { get; set; } public string ResponseContent { get; set; } public ConnectionSerializingHandler(TaskCompletionSource taskCompletionSource) : base() { m_ResponseStore = new StringBuilder(); m_TaskCompletion = taskCompletionSource; } public override void ReceivedData(NSUrlConnection connection, NSData data) { if (data != null) { m_ResponseStore.Append(data); }
[ 816 ]
Chapter 5 } public override void FinishedLoading(NSUrlConnection connection) { IsFinishedLoading = true; ResponseContent = m_ResponseStore.ToString(); // TODO: implement deserialization and m_TaskCompletion.SetResult(result); } }
Even though this implementation is possible on the iOS platform, considering the cost of passing the mono to iOS bridge (likewise on Android and JNC Bridge), this type of implementation should be avoided, and either only native or mono runtime code should be used to communicate over the network. In a similar manner, we can implement the usage scenario for the new NSUrlSession class in iOS. However, NSUrlSession can also be used in background download scenarios. Therefore, we will discuss it in the next section.
Background downloads
When the application requires larger network resources than the client UI can wait for, in Xamarin mobile applications we can resort to background downloads. Both iOS and Android platforms offer implementations for background downloads and these strategies can be executed on Xamarin runtime. For Xamarin.Android application developers, the easiest way to execute a background download is to use the Download Manager API service/application provided since API level 9. The download manager can be initialized with a request and the application can subscribe to event notification(s) regarding the download status. First, we need to create a request to pass onto the DownloadManager: global::Android.Net.Uri downloadUri = global::Android.Net.Uri. Parse(""); DownloadManager.Request request = new DownloadManager. Request(downloadUri); // Types of networks on which this download will be executed.
[ 817 ]
Networking request.SetAllowedNetworkTypes(DownloadNetwork.Wifi); // Allowed on Roaming connection? request.SetAllowedOverRoaming(false); // Allowed on Metered Connection? request.SetAllowedOverMetered(false); //Set the title of this downloaded request.SetTitle("My Background Download"); //Set the description of this downloaded request.SetDescription("Xamarin.Android download using DownloadManager"); //Set the local destination for the downloaded file request.SetDestinationInExternalFilesDir(this, global::Android. OS.Environment.DirectoryDownloads, "MyDownloadedData.xml"); // or use the request.SetDestinationUri()
Once the request is ready to be executed, we can get the DownloadManager instance and queue the download request: m_DownloadManager = (DownloadManager)GetSystemService(DownloadService ); // Enqueue the request // The download reference will be used to retrieve the status m_CurrentDownloadReference = m_DownloadManager.Enqueue(request);
The download reference can be used to get the current status information about the queued download or cancel the ongoing background download. To get the current status of the download or cancel it, we can use the respective methods on the DownloadManager instance. // Removing the queued request from the DownloadManager queue. m_DownloadManager.Remove(m_CurrentDownloadReference); // // Retrieving the current status of the download queue // Create a query to retrieve the download status(s)
[ 818 ]
Chapter 5 DownloadManager.Query myDownloadQuery = new DownloadManager.Query(); myDownloadQuery.SetFilterById(m_CurrentDownloadReference); // Request the queued download items as a data table. var cursor = m_DownloadManager.InvokeQuery(myDownloadQuery); var statusColumn = cursor.GetColumnIndex(DownloadManager.ColumnStatus); var status = (DownloadStatus)cursor.GetInt(statusColumn);
This implementation can be extended with the notification(s) that are received from the DownloadManager application using a BroadcastReceiver class. public class DownloadBroadcastReceiver : BroadcastReceiver { public override void OnReceive(Context context, Intent intent) { // Get the download reference from the intent broadcast long referenceId = intent.GetLongExtra(DownloadManager.ExtraDownloadId, -1); // TODO: Implement the delegated execution } }
We can now register the broadcast receiver with the DownloadManager instance and update the UI with a possible delegated implementation for updating it. //set filter to only when download is complete and register broadcast receiver IntentFilter filter = new IntentFilter(DownloadManager. ActionDownloadComplete); // TODO: We can extend the DownloadBroadcastReceiver with delegates RegisterReceiver(new DownloadBroadcastReceiver(), filter);
On top of the broadcasts mechanism, the Download Manager App UI can also be invoked within the Xamarin applications to give a uniform UI about on-going or completed transfers. On the iOS platform (at least post iOS 7), background transfers (both download and upload operations) are made possible with NSUrlSession. NSUrlSession provides an easy to implement interface that lets developers create an efficient and reliable transfer processes.
[ 819 ]
Networking
The implementation strategy for NSUrlSession initially involves the implementation of an NSUrlSessionDelegate, which will be the responsible "handler" for the transfer process. Basic methods related to the health and status of the transfer are exposed through this delegate and can be implemented to provide required information for the transfer or give real-time updates to the application user. • DidFinishEventsForBackgroundSession is called when the background session is complete • DidReceiveChallenge is invoked when the server requests credentials • DidBecomeInvalid is invoked when there is a problem with the session NSUrlSessionDelegate provides the base implementation for more specialized transfer delegates: NSUrlSessionDownloadDelegate for download operations and NSUrlSessionTaskDelegate for upload operations. These delegate classes expose
additional status methods related to the transfer tasks (for example, download delegate provides methods to retrieve notifications about the download progress).
For instance, if we were to use the same example as on Xamarin.Android with the BroadcastReceiver implementation, the NSUrlSessionDownloadDelegate implementation would require three basic methods for completion, error, and progress. public class DownloadTaskDelegate : NSUrlSessionDownloadDelegate { public override void DidFinishDownloading( NSUrlSession session, NSUrlSessionDownloadTask downloadTask, NSUrl location) { // TODO: Implement the delegate for download finished } public override void DidBecomeInvalid(NSUrlSession session, NSError error) { //base.DidBecomeInvalid(session, error); // TODO: Implement the delegate for error } public override void DidWriteData(NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite) {
[ 820 ]
Chapter 5 //base.DidWriteData(session, downloadTask, bytesWritten, // totalBytesWritten, totalBytesExpectedToWrite); // TODO: Implement the delegate for download progress } }
After the delegate implementation is complete, we can create the session and start the download operation using the NSUrlSession. NSUrlSessionConfiguration downloadSessionConfiguration = NSUrlSessionConfiguration.BackgroundSessionConfiguration ("com. TravelTravel.BackgroundTransfer"); m_DownloadSession = NSUrlSession .FromConfiguration(downloadSessionConfiguration, new DownloadTaskDelegate(), new NSOperationQueue()); NSUrl url = NSUrl.FromString(""); NSUrlRequest request = NSUrlRequest.FromUrl(url); m_DownloadTask = m_DownloadSession.CreateDownloadTask(request);
On top of the handler implementation, the iOS app can be woken to execute certain code, such as a local mobile notification to inform the user about the completed sessions. For the task complete event, one needs to use the iOS application delegate (refer to Chapter 2, Memory Management) for DidFinishEventsForBackgroundSession. Mobile notifications (also called pushed notifications for remote scenarios) are user notifications that are executed on the OS level to inform the user about application-related updates. They can be triggered both locally or by using a remote server.
Push notifications
Push notifications are subtle UI messages that can help an application to provide the user information about an asynchronous task being executed by the service layer or about an external event that is related to the application instance itself (for example, messages from social networks, approval for a travel reservation, and so on).
[ 821 ]
Networking
It is possible to create and receive push notifications on both Xamarin platforms and Windows Phone. These notifications are triggered by a secondary server/application (for example, service layer), brokered by the corresponding messaging infrastructure provider for the platform and displayed by the application on the target client. For the Android platform, the messaging provider is Google Cloud Messaging (GCM) and it is the Apple Notification Push Service (APNS) for iOS. Both of these service providers require your application to be registered to receive push notifications and the server application to have the credentials to be able to authenticate with the notification services. Similarly, Windows Notification Services (WNS) adopts a federated authentication mechanism. Both GCM and APNS use a subscription model in which the client app on a specific device subscribes/registers for the push notifications and an addressing token is created. The addressing token is used, later on, by the server to send push notifications to the message broker service (for example, GCM) and the queued messages are delivered to the specific client.
Push Notifications
[ 822 ]
Chapter 5
On top of the classic messaging model, GCM also supports topic-based and group-based messages where the receivers are not limited to a single device/ application pair. It is also possible with GCM to create a duplex channel where the client is able to send messages back to the server layer. Push notifications on these platforms can be used to trigger various tasks, the most common of which is to navigate to a certain view and continue the business process flow initialized by the notification. Although it is relatively elementary on the client side to subscribe to push notifications, cross-platform scenarios require complex implementation to introduce a single server environment to provide messages to both GCM and APNS. However, there are platform-agnostic implementations available for both of these platforms. The Microsoft Azure platform and the notification hub is one of these solutions, where communication with GCM and APNS are both supported through usage of the same business logic implementation.
Cloud integration
Even though there are multiple cloud service providers as development platforms for creating the backend for mobile applications, Microsoft Azure stands out among the competitors with its inherent natural bond to the .NET platform and subsequently Xamarin, considering its evolution. Most of the features supported by Azure have a specific implementation for Xamarin target platforms.
Azure Mobile Services
Azure Mobile Services is a scalable cloud development platform that helps developers add functionality to their mobile applications with ease. The patterns and features described in this chapter related to network services such as OData services, offline data storage, push notifications, and OAuth authentication providers are already included in the mobile services SDK and can be configured through the Azure management console. In order to demonstrate aforementioned features, we can incorporate them into our demo application.
[ 823 ]
Networking
The initial step would be to create a mobile service on the Azure management console. For this purpose, we will select a compute service and create the mobile service.
Create Compute Service
Then, we will set up the mobile service endpoint and create the SQL database to store the online data.
Mobile Service Setup
[ 824 ]
Chapter 5
Once the setup is complete, the "personalized" service layer project can be downloaded in order to integrate the mobile services into the application project.
Connect Mobile Services to an existing Xamarin app
In the service layer project, you will notice that there is only a single controller created for your convenience. We will be extending the project with additional controllers and adding a reference to our own DTO data model. In order to reuse the types created in the previous sections, instead of referencing the common data model project directly, we add the data type files as a reference to the new service project that we downloaded from the Azure portal. The reason for the referenced files is that the data objects in the service project have to derive from EntityData class. Another change we need to make is to convert the class definitions to partial and remove the SQLite references, for example, you can comment out the SQLite property descriptors or use conditional compilation.
[ 825 ]
Networking
In this example, we are using AZURE as the build constant for the Azure web service. public partial class Region { public Region() { Countries = new List(); } #if !AZURE [PrimaryKey] [JsonProperty("id")] public Guid Id { get; set; } #endif #if !AZURE [JsonProperty("name")] #endif public string Name { get; set; } #if !AZURE [JsonProperty("continent")] #endif public Continent Continent { get; set; } #if !AZURE [OneToMany(CascadeOperations = CascadeOperation.CascadeInsert | CascadeOperation.CascadeRead)] [JsonProperty("countries")] #endif public List Countries { get; set; } }
Finally, create a data object definition using a partial declaration for the Region class: public partial class Region : EntityData { }
[ 826 ]
Chapter 5
After this step, you can simply use the existing project item template for the controller to add the specialized data endpoint (Microsoft Azure Mobile Services Table Controller).
Microsoft Azure Mobile Services Table Controller
This will create a controller for the data object and insert the type into the data context. Once the project is published and the mobile services are running, SQL database tables are going to be migrated automatically. This migration also applies to data table column changes or future additions to the DTO model. Now we can add the NuGet package or the component to our client application and add the necessary initialization code, as described in the start page of mobile services section on the Azure management console. In the main activity, we create the following mobile service instance: public static MobileServiceClient MobileService = new MobileServiceClient( "https://traveltrace.azure-mobile.net/", "" );
Add the following to an event handler or the OnCreate function: // Intialization the mobile services on the mobile platform CurrentPlatform.Init(); // Adding a region item to the database var item = new Region {Continent = Continent.Europe, Name = "Balkan"};
[ 827 ]
Networking MobileService.GetTable().InsertAsync(item) .ContinueWith((result) => { System.Diagnostics.Debug.Write(result.Status); });
After the code is successfully executed, the data on the Azure database can be observed using SQL Management Studio or the Visual Studio SQL Server tools.
Azure Data Sample
Now that we have a working service layer and a client that can communicate with it, we can have a look at the local synchronization.
Azure offline data
For local data caching and offline scenarios, Azure Mobile Services SDK already implements a synchronization framework where the local data is stored in SQLite database and the synchronization is handled by pull and push commands (push requests upload local changes to the cloud store whereas pull requests download the latest changes from the server) using a default conflict handler. Each pull request automatically issues a push request where the local data is pushed to the cloud storage. Conflicts are resolved according to the created and updated fields, which are members of each object type defined using the EntityData base class. Before starting the implementation, we need to download and install the Azure Mobile Services SQLiteStore NuGet package.
[ 828 ]
Chapter 5
In order to initialize the default local data store, we will use the MobileServicesSQLiteStore implementation. Custom local store implementation can be incorporated using the IMobileServiceLocalStore interface. private async Task InitLocalStoreAsync() { // new code to initialize the SQLite store string path = Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.Personal), "traveltrace.db"); if (!File.Exists(path)) { File.Create(path).Dispose(); } var store = new MobileServiceSQLiteStore(path); store.DefineTable(); // Uses the default conflict handler, which fails on conflict await MobileService.SyncContext.InitializeAsync(store); }
After the local store is initialized and the synchronization context is created, we can implement the synchronization method that can be called every time the application starts. private async Task SyncAsync() { // IMobileServiceSyncTable RegionsTable = MobileService. GetSyncTable(); await MobileService.SyncContext.PushAsync(); await RegionsTable.PullAsync("AllRegions", RegionsTable. CreateQuery()); }
Both the PushAsync and PullAsync methods additionally accept filter expressions so one can limit the synchronization to certain entities. In this implementation, once the synchronization context is in place, if the service connection is not available, the IMobileServiceSyncTable interface implementations handle the offline data and the data is kept in the local store until the next push operation.
[ 829 ]
Networking
Azure authentication
The Azure platform provides various authentication mechanisms for Xamarin mobile applications. Each authentication mechanism can be integrated into existing mobile applications with a service backend through NuGet packages and/or components. Being a multi-tenant, cloud-based directory and identity management service, Azure Active Directory (Azure AD) provides application developers an easy way to create single sign-on experience on a large number of cloud SaaS applications. It is also possible to incorporate an existing Windows Server Active Directory into applications and leverage the existing on-premise identity stores. These features make the Azure AD an ideal candidate for LOB applications. Another authentication strategy for Azure Mobile Services is to configure an existing authentication provider such as Facebook, Google, Twitter, or Microsoft and secure the service requests using the Azure Mobile SDK. In order to register an authentication provider, the first step would be to create a consumer app on the target platform. For instance, if we were to use Live ID for our authentication scenarios, we would need to use the Live Connect App management site (https://account.live.com/ developers/applications/index). Similarly, for Twitter, we would need to create a Twitter consumer application on the Twitter application management console (https://apps.twitter.com/).
Live Connect app management site
[ 830 ]
Chapter 5
Once the application setup is in place, the Azure management console can be used to update the mobile services configuration.
Mobile Services Identity Configuration
After the identity provider for the mobile services has been set up, the web service project can be easily protected simply by adding the Authorize attribute. [AuthorizeLevel(AuthorizationLevel.User)] public class RegionController : TableController
On the client apps, the authentication is handled by simply using the LoginAsync method on the Azure Mobile Services SDK client with the correct authentication provider. MobileService.LoginAsync(this, MobileServiceAuthenticationProvider. MicrosoftAccount).ContinueWith((task) => { System.Diagnostics.Debug.WriteLine("Currently authenticated user's ID is {0}", task.Result.UserId); });
[ 831 ]
Networking
The result is the same authentication screen received using the Xamarin. Auth component.
Brokered Authentication
Azure Cloud integration scenarios extend far beyond the ones described here. The features that are included in this cloud-based development platform can help developers enhance their Xamarin apps with ease and scalability.
[ 832 ]
Chapter 5
Summary
This chapter provided an overview of various network channels that can be used in Xamarin applications to create connected applications. Web services are definitely on the essentials list for modern mobile applications because of the interoperability of the protocols in place for web services (both SOAP/ XML and REST/JSON). Unfortunately, XML services are a little harder to integrate with Windows Phone 8.1 runtime (even though they are still supported by Windows Phone Silverlight runtime) because the Windows Communication Foundation client infrastructure is not included in Windows Phone runtime. However, the same RESTful service proxies can be used by applications on each Xamarin target platform and Windows Phone. Cloud integration options such as mobile services and Azure Active Directory were discussed with demonstration samples. Each of these technologies provides additional connectivity and integration opportunities for Xamarin mobile apps. SignalR is another web technology that grants additional communication capabilities to mobile applications by means of bidirectional communication between the client apps and the server. Several common service and web implementation patterns were demonstrated using the TravelTrace application scope that we will be using for various scenarios in the remainder of this module. Each pattern described targets different quality identifiers initially mentioned. Finally, we discussed some of the platform-specific network options.
[ 833 ]
Platform Extras This chapter concentrates on platform-specific APIs and features. It explains some of the peripherals that can be employed in Xamarin applications. We will also discuss native libraries and how to include them in cross platform Xamarin applications. The following topics will be discussed: • Content sharing • Peripherals • Location data • Native libraries
Content sharing
Each Xamarin target platform implements a certain strategy to share formatted content between the applications. Sharing implementations increases the visibility of your applications by allowing users to open files from your application in any other app. In addition, these types of implementations provide added value to the quality of your cross platform projects from the nativity perspective. The inter-application sharing occurs with the underlying runtime acting as a broker between the sharing source and target applications. On iOS and Windows Store applications, the sharing is facilitated in the form of abstract file elements. Android applications, however, can take it one step further by sharing formatted data that can be manipulated by the receiving application, which essentially allows the source application to almost act as a data repository.
[ 835 ]
Platform Extras
On Windows Store, applications can actively share content such as media elements, URIs, text content, and other types of data. However, in this implementation strategy, that is, sharing contact implementation, the source application has to initiate the sharing process. The content sharing scenarios described in this module are about target applications accessing the content via the source application.
On Windows Runtime, applications interact with each other or with the operating system through the usage of so-called application contracts. With the help of contracts, applications can immerse into the runtime and get one step closer to become part of the runtime. The same functionality is achieved by the implementation of the base ContentProviders on Android and the implementation of document provider extensions on the iOS platform.
File pickers and contracts (Windows Store apps)
One of the most commonly used contracts is the File Open Picker contract on Windows Runtime. In this contract implementation, the source application has to implement the activation strategy for when it is called to provide file content for the target application. When the target application requires a certain type of file, the runtime lists all possible source applications that declares this type in their app manifest (for example, on a Windows Phone, when you want to attach a document on the mail client together along with a picture, the OneDrive application is displayed as one of the possible sources).
[ 836 ]
Chapter 6
The user then selects the file that they want to use in the current application and the provider app is responsible for either creating or providing the file to the target application.
File picker contract in Windows Runtime
In this methodology, the file does not necessarily need to be an actual document item, but it can be a conceptual one. For instance, if we were to implement the File Open Picker in the TravelTrace app, we would not need to use actual documents in the File Open Picker to provide content. The shared content items could be the previous trips that the user kept track of and the selected trip could provide a generated scrap book or a collage of images in an image format or as a PDF document according to the type of document that is being requested by the consumer app.
[ 837 ]
Platform Extras
Document Provider extensions (iOS)
The Document Provider extensions (introduced in iOS 8) allow applications, that is, consuming applications) to access documents outside their application sandbox. Document Provider extensions are twofold. The Document Picker View Controller extension provides a UI implementation for the operating system to display whenever the source application is selected as a document source in the document picker view. However, the File Provider extension is responsible for providing the document level operations. In order to create a provider extension, we can use the existing project template in Xamarin Studio.
The Document Picker extension project template
[ 838 ]
Chapter 6
Once the project is created, we are responsible for creating the view on the storyboard and implementing DocumentPickerViewController so that the available files are listed on the UI when our application is selected to provide files. DocumentPickerViewController initially has two methods that require our attention. The PrepareForPresentation method receives the picker mode (Import, Open, ExportToService, or MoveToService) so the user interface can be prepared according to the requested operation. The OpenDocument method is implemented just for our convenience to demonstrate the fact that once the user selects a document, we should prepare the corresponding file URL and pass it onto the runtime using the DismissGrantingAccess method. It is important to keep in mind that the URL provided from our Document Picker extension should already point to an actual file, or we should go on to implement the Document File Provider extension that will provide the files when either the consuming app displays the document picker and the user selects the file or the consuming app opens the file directly using the cached URL. In the Document File Provider extension project, the crucial implementation is located in the StartProvidingItemAtUrl method. This method uses the FileCoordinator class provided to create the file at the target URL (for example, generates the file or downloads it from a remote location). public override void StartProvidingItemAtUrl (NSUrl url, Action completionHandler) { NSError error, fileError = null; NSData fileData; // TODO: get the file data for file at from model fileData = new NSData (); FileCoordinator.CoordinateWrite (url, 0, out error, (newUrl) => fileData.Save (newUrl, 0, out fileError)); if (error != null) completionHandler (error); else completionHandler (fileError); }
[ 839 ]
Platform Extras
After the implementation of the extensions is complete, we have to prepare the project metadata entries. Each project (both extensions and the container application) needs to make use of the App Groups capability. This capability needs to be set up in the Entitlements option list. Other settings involve the base document storage URL, type of operations supported for the document picker, and so on. However, these configuration values are inserted in the Info.plist option list.
Entitlements for Document Provider extensions
In order to add the extensions to the containing application, the only thing we need to do is to add them as references to the main project. If you look at the project file of the main project, you will notice that the references are added with the IsExtension flag set to true.
ContentProvider and ContentResolver (Android)
Content providers on Android platform act as data repositories. These repositories are exposed to consuming applications through structured endpoint descriptions (similar to REST endpoints on web services). Using the metadata provided, providers' content is resolved by the consuming app through the implementation of ContentResolvers. Using content providers, applications can expose well-known data types such as contact list photos or calendar events, as well as custom data types and formatted data.
[ 840 ]
Chapter 6
On the consumer side of this infrastructure, there are many content providers already implemented by default on Android runtime, such as Contacts, MediaStore, UserDictionary, and so on. These providers can be accessed by implementing base classes such as ContentResolver and CursorAdapter. CursorAdapter is used to feed the data that is retrieved by ContentResolver to a UI list view control. The ContentProvider API operations can involve list queries and CRUD operations on individual records. Provider applications are responsible for registering an authority that is unique to the application. The authority entry can be described as the base content URI for a specific application. Either it can be added to the manifest file, or an attribute entry could be used on the class that is implementing ContentProvider. [ContentProvider(new string[] { "com.xamarin.master.traveltrace.TripProvider" })] public class TripDataProvider : ContentProvider
Another important piece of metadata that the content provider needs to provide is the Mime-Type information. In order to facilitate the use of CursorAdapter on consumer applications, the content provider needs to provide a Mime-Type for a list of items (starting with vnd.android.cursor.dir) as well as for a single item (starting with vnd.android.cursor.item). Finally, the content provider needs to expose the data columns for the data that is being made available to other applications. This is achieved by hiding the InterfaceConstants nested class from the base abstract class. public new { public public public public public public }
static class InterfaceConsts const const const const const const
string string string string string string
Id = "Id"; Name = "Name"; Description = "Description"; Date = "Date"; Location = "Location"; ContentPath = "ContentPath";
Another optional implementation would be to create a UriMatcher class that could ease the implementation process for the query methods. private UriMatcher GetUriMatcher() { var matcher = new UriMatcher(UriMatcher.NoMatch);
[ 841 ]
Platform Extras // to get data... matcher.AddURI(Authority, _basePath, (int) QueryType.List); matcher.AddURI(Authority, _basePath + "/#", (int) QueryType.Single); return matcher; }
The final implementation is related to the query, update, insert, and delete methods. Each of these methods needs to return the ICursor implementations according to the abstract class defined. public override global::Android.Database.ICursor Query(global::Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder) { switch ((QueryType) m_UriMatcher.Match(uri)) { case QueryType.List: // TODO: case QueryType.Single: // TODO: default: throw new Java.Lang.IllegalArgumentException("Unknown Uri: " + uri); } }
Overall, while providing more flexibility for content sharing, Android makes it a little more difficult for other applications to consume the data provided by the source application. The data provided by a content provider implementation on a Xamarin.Android application cannot be consumed by another without a specialized implementation.
Peripherals
In this section, we will discuss several communication protocols that enable applications to communicate with other platforms and other devices.
[ 842 ]
Chapter 6
Bluetooth
The Bluetooth communication protocol has become an invaluable feature on mobile devices. Especially with the emerging technologies related to IoT (Internet of Things), and various accessories we use in daily life, our dependency on the Bluetooth stack on mobile platforms has increased. While Xamarin.Android applications and Windows Runtime applications can make use of both GATT (Bluetooth Low Energy) and RFCOMM (Bluetooth Serial), iOS applications can only communicate through the Bluetooth LE protocol. The main reason for this discrepancy is the fact that Android and Windows Runtime implement the serial communication port according to shared specifications. However, Apple implements a propriety communication stack using an encryption system. This, unfortunately, limits the serial communication to between only Apple produced/compliant accessories or devices. For Xamarin.Android, Bluetooth APIs reside in the Android.Bluetooth namespace. Using the provided classes, developers can enhance their applications with features like: • Scanning for discoverable Bluetooth devices (including LE protocol) • Getting information on the local BT adapter and paired devices • Creating Serial Communication Sockets using the RFCOMM protocol • Acting both as a GATT client or a GATT server Bluetooth protocols can be accessed only with the user permission manifest entry for Bluetooth.
...
On Windows Runtime, Bluetooth-related features are implemented in the Windows. Devices.Bluetooth namespace. Similar to the feature-set in Android, Windows Runtime Bluetooth stack requires the applications to declare the adapter access requirement and the protocol to be used in the application manifest (for some specific devices and protocols, the Bluetooth capability declaration has to be inserted manually into the manifest). An important feature on this platform is that the Bluetooth connectivity can be facilitated and kept alive by background tasks, enabling the devices to continue their operations in the backgrounded or suspended states.
[ 843 ]
Platform Extras
For Xamarin.iOS, Bluetooth LE related implementations would need to use the CoreBluetooth framework. An important component that is currently in the Xamarin store for cross-platform peripherals integration is the Monkey.Robotics project. While implementing the basic APIs for Bluetooth LE and Wi-Fi, some other vendor-specific peripherals, such as health monitoring devices and smart watches, can be used with this component.
Wi-Fi Direct
Wi-Fi Direct is another communication protocol that allows Wi-Fi enabled devices to create peer-to-peer (P2P) networks and exchange information using the Wi-Fi adapter without using a common provider network connection. Out of the Xamarin target platforms that are described in this module, only the Android platform supports this protocol. The Windows 10 platform will support Wi-Fi Direct; however, this implementation will be targeting only Windows based devices. On Android devices, with the introduction of Wi-Fi P2P, developers can create applications that can communicate with higher speeds and through much longer distances than with Bluetooth adapters. Wi-Fi P2P features were introduced in Android 4.0 (API level 14) and they comply with the Wi-Fi Alliance's Wi-Fi Direct standards. In order to be able to use this feature, the application manifest should contain permissions for ACCESS_WIFI_STATE, CHANGE_WIFI_STATE, and INTERNET. Access to these services is provided with the WifiP2pManager, which is located in the Android.Net.Wifi.P2P namespace. Using this manager, applications can broadcast, create groups, request peers, and developers can create applications that can communicate over P2P sockets via Wi-Fi Direct.
Near Field Communication
The Near Field Communication (NFC) protocol provides an easy alternative to Bluetooth for pairing and advertising scenarios (for example, NFC tags). With NFC, it is possible to create sockets and transfer data between mobile devices that are in proximity to each other. Unfortunately, the NFC protocol is another unsupported communication protocol on iOS devices. (Reports suggest that iPhone 6 technically has the ability to use this protocol; however, this API is not made available to developers.)
[ 844 ]
Chapter 6
The NFC stacks on Windows Phone and Android devices, however, implement most of the same profiles. In essence, it is technically possible to communicate over NFC across Windows and Android devices in proximity (by default, the tap and send feature works as a cross-platform feature). In spite of the fact that Windows devices use a propriety messaging scheme (Windows:), there are third-party frameworks for NDEF. NDEF is a cross-platform messaging scheme that is currently the default for Android.
Location data
Nowadays, geo-context (location awareness) is becoming more and more crucial for applications, especially the ones running on mobile platforms. For instance, search engines optimize results according to the location information they gather from the client platform, social media and photo applications add geo-tags to posts and media items, and there are many more use cases for the data about not how or on which platform the application is running, but where. On Xamarin platforms, the location information is provided making use of several different sources. The most accurate of these sources is GPS (Global Position System). This option consumes the most power and, generally, is only available for foreground applications. Other options that can provide somewhat less accurate data are network providers such as Wi-Fi or Cellular data. iBeacon is another technology introduced by Apple and applicable to iOS 7+ devices. iBeacon-compatible devices transmit location information using the Bluetooth LE protocol, and this transmission is then used by the Bluetooth adapter on mobile phones and tablets. On Xamarin target platforms, location information can be accessed both proactively and through system events and triggers. In each platform, access to a location is limited by the privacy settings and it is always up to the user whether a certain (or every) application can access the location services.
Android location and Google Play services
In early versions of Android runtime, the android.location API was the framework-designated module for adding location awareness to applications. However, after the release of Google Play Services SDK (compatible with Android v2.2, API level 8, or higher), location APIs provided by Google became the preferred way to access location data on Android platform.
[ 845 ]
Platform Extras
LocationManager, a LocationServices implementation, is a system-wide
service and can be accessed through the application context in Xamarin.Android applications. In order to get location information, the application has to subscribe to the location updates with an implementation of ILocationListener. m_LocationService = GetSystemService(LocationService) as LocationManager; if (m_LocationService != null) { if (m_LocationService. IsProviderEnabled(LocationManager.GpsProvider)) { // Get updates in min every 5 seconds for every minimum 2m change m_LocationService. RequestLocationUpdates(LocationManager. GpsProvider, 5000, 2, m_LocationListener); } else if (m_LocationService. IsProviderEnabled(LocationManager.NetworkProvider)) { // Get updates in min every 10 seconds for every minimum // 10m change m_LocationService.RequestLocationUpdates (LocationManager.NetworkProvider, 10000, 10, m_LocationListener); } }
In the location listener interface, there are several events that can be utilized. Other than the location change information, developers are provided with the updates related to different location provider status changes. A simple location listener implementation used in the previous example would resemble this: public class LocationListener : Java.Lang.Object, ILocationListener { public void OnLocationChanged(Location location)
[ 846 ]
Chapter 6 { Trace.WriteLine(string.Format("Lat:{0}, Long {1}", location.Latitude, location.Longitude), "OnLocationChanged"); } public void OnProviderDisabled(string provider) { Trace.WriteLine(string.Format("Location Provider '{0}' is disabled", provider), "OnProviderDisabled"); } public void OnProviderEnabled(string provider) { Trace.WriteLine(string.Format("Location Provider '{0}' is enabled", provider), "OnProviderEnabled"); } public void OnStatusChanged(string provider, Availability status, Bundle extras) { Trace.WriteLine(string.Format("Location Provider '{0}' status changed to {1}", provider, status), "OnStatusChanged"); } }
The listener interface can be implemented on the current Activity itself or any other JavaObject class implementation. Using the backgrounding techniques defined in Chapter 3, Asynchronous Programming, the listener interface can also be implemented on a custom started service and the application can receive background updates on the location changes through the service data directly (bound scenario) or through information persisted by the service.
[ 847 ]
Platform Extras
Testing location information can be difficult on mobile applications. In order to facilitate GPS data testing and diagnostics, Android Emulator in Android SDK and Visual Studio Android Emulator are equipped with location emulation functionality.
Emulating a car travelling on a route
Visual Studio Android Emulator also provides features to emulate the usage of an automobile, or other means of transport, on a route or GPS location changing according to the defined pins with defined intervals.
[ 848 ]
Chapter 6
On top of the location information, using the location provider status information, location info can be gathered in a more efficient and reliable way (for example, switching between GPS and network provided information according to connectivity and requirement for accuracy). In order to get the optimal provider that is currently available for the application scope, you can use the GetBestProvider method with the desired criteria for accuracy (Coarse or Fine Location Info) and for power consumption (high, medium, and low). This intelligent switch between location data providers is the main advantage of using the Fused Location Provider (Google Play Services SDK) and Google Location Services over the default location API.
Google Play services Xamarin components
Xamarin binding libraries to Google Play Services SDK, which are available as components for Xamarin.Android v4.8+ developers, provide an easy way to integrate various services, including location APIs, into Xamarin.Android applications. These components implement the Java Binding projects and take care of the cumbersome implementation and compilation of the Google provided Android libraries.
[ 849 ]
Platform Extras
After installing the Google Play services' location component, while trying to build the Xamarin.Android application, you might receive a compilation error similar to this: "No resource found that matches the given name (at 'value' with value '@integer/ google_play_services_version')." The reason for this error is the fact that the Xamarin component is dependent on the Google Play Services SDK and the SDK modules are supposed to be installed manually using the Android SDK Manager.
Android Google Play SDK
After installing the SDK module, the Xamarin.Android application can be built without errors. Once the setup and configuration is complete, the GoogleApiClient class can be initialized and used in Xamarin applications. GoogleApiClient requires the implementation of two interfaces to gather information about the client connection status: GoogleApiClient.IConnectionCallbacks and GoogleApiClient. IOnConnectionFailedListener. [ 850 ]
Chapter 6
If the application you are implementing does not depend on continuous updates of location data, but rather just the current location, you can use the GetLastLocation method provided on the GoogleApiClient. This method provides a one-time reading option. m_GoogleClient = new GoogleApiClient.Builder(this) .AddApi(Gms.Location.LocationServices.API) .AddConnectionCallbacks(this) .AddOnConnectionFailedListener(this) .Build(); m_GoogleClient.Connect();
In order to receive real-time updates with the fused location provider, you must implement the ILocationListener interface for the Google Location Services API. This listener is different from the default one; it only contains a single event handler implementation for location changes. The events related to the data providers do not need to be implemented since the fused location provider itself is responsible for smart switching between the location data providers. Although the type of provider and provider status changes are not relevant for us using the fused location provider, it is still possible to let the fused provider know which type of accuracy and priority our application scope demands. For this purpose, we can use the SetPriority method with the appropriate flag on LocationRequest while subscribing to the location updates. • High accuracy (100): Requests the finest location available • Balanced power/accuracy (102) (default): Requests the block level accuracy (~100m accuracy) • Low power (104): Requests the city level accuracy (~10km accuracy) • No power (105): Sets the location updates to use passive mode; waits for location updates delivered to other client applications (also known as piggybacking) As well as the priority, a fused location provider lets developers set other important delineations of location updates, such as minimum interval, smallest displacement, and expiration time. private async Task RequestLocationUpdates(GoogleApiClient apiClient) { // Describe our location request
[ 851 ]
Platform Extras var locationRequest = new Gms.Location.LocationRequest() .SetInterval(5000) // Setting the interval to 5 seconds .SetSmallestDisplacement(5) // Setting the smallest update delta to 5 meters .SetPriority(Gms.Location.LocationRequest. PriorityHighAccuracy) // Setting the priority to Fine and High Power .SetExpirationDuration(20000); // Stopping the location updates after 20 seconds. // Request updates await Gms.Location.LocationServices.FusedLocationApi .RequestLocationUpdates(apiClient, locationRequest, m_LocationListener); }
Unfortunately, Google Play services are only preinstalled on Android SDK emulator and for the other emulators, the Google applications package has to be downloaded and installed on the emulator image.
Location services on iOS
On the iOS platform, the location data is accessed through the CoreLocation library, and similar to the android location API, location changes are sent to the subscribing application with the help of event delegates. The CLLocationManager class makes it a trivial task to get location data updates from the mobile device. The Xamarin.iOS location data access implementation starts with creating the required Info.plist entries, which will explain why the application requires access to the user's location. In order to do this, we have to edit the Info.plist file, adding one or both of the following entries:
[ 852 ]
Chapter 6
Info.plist entries for location info
In addition to the Info.plist entry, you should also keep in mind that starting with iOS 8, applications have to explicitly ask for permission to use the location data. In order to get consent from the user, the location manager exposes two methods: one for authorizing the app for continuous local data updates and the other one just to be used when the application is in the foreground. _LocationManager = new CLLocationManager(); _LocationManager.RequestWhenInUseAuthorization(); _LocationManager.RequestAlwaysAuthorization();
Finally, we can subscribe to the LocationsUpdated event to receive the latest location update information. if (CLLocationManager.LocationServicesEnabled) { _LocationManager.LocationsUpdated += (sender, eventArgs) => { Debug.WriteLine( string.Format("Lat:{0}, Long {1}",
[ 853 ]
Platform Extras eventArgs.Locations[0].Coordinate.Latitude, eventArgs.Locations[0].Coordinate.Longitude), "OnLocationChanged"); }; // Every ~500m an update _LocationManager.StartMonitoringSignificantLocationChanges(); // Every 10m send an update event _LocationManager.DistanceFilter = 10; _LocationManager.StartUpdatingLocation(); }
The location information can be further optimized for the application scope using the exposed criteria properties and methods. It is also possible to retrieve other types of information such as heading direction. However, it is important to first check if the service is available and request updates according to the system status information. if (CLLocationManager.HeadingAvailable) { // update the heading _LocationManager.StartUpdatingHeading(); _LocationManager.UpdatedHeading += (sender, eventArgs) => { Debug.WriteLine("New Heading: X: {0} Y: {1} Z: {2}", eventArgs.NewHeading.X, eventArgs.NewHeading.Y, eventArgs.NewHeading.Z); }; }
Location data on Windows Runtime
On Windows Runtime (Windows Store apps), the Windows.Device.Geolocation namespace is dedicated for tracking the device's location over time. The Geolocator class replaces the main access points in the previous platforms and can give ondemand data and information updates through events. Similar to iOS access request, the application can request consent from the application user with the RequestAccessAsync method and according to the response, methods or events can be accessed through the Geolocator class. var accessStatus = await Geolocator.RequestAccessAsync(); if(accessStatus == GeolocationAccessStatus.Allowed)
[ 854 ]
Chapter 6 { // Give update in every 5 meters Geolocator geolocator = new Geolocator { DesiredAccuracyInMeters = 5 }; // Use StatusChanged event for Geolocator status change geolocator.StatusChanged += OnStatusChanged; // Use PositionChanged event for Geolocator status change geolocator.PositionChanged += (sender, eventArgs) => { UpdateLocationData(eventArgs.Position); } // Get the current position Geoposition pos = await geolocator.GetGeopositionAsync(); UpdateLocationData(pos); }
Geofencing
A geofence is an abstract boundary that can be defined with location services so that the application which created the geofence receives an update from the mobile device whenever the user is entering or exiting this boundary. This eliminates the need for polling for the location info and opens up different implementation opportunities for mobile applications. The use cases for geofences vary from simple reminders on certain locations to virtual tours created by showing certain images or information according to the current region. All the Xamarin target platforms support the creation and usage of geofences. For instance, in order to create a geofence on an iOS platform, we would need to use CLCircularRegion and the location monitoring feature of the CoreLocation library. There are two events of interest that are fired when the mobile device enters and exists in the region. var region = new CLCircularRegion( new CLLocationCoordinate2D(43.8592, 018.4315), 600, "Old Town");
[ 855 ]
Platform Extras if (CLLocationManager.IsMonitoringAvailable(typeof (CLCircularRegion))) { _LocationManager.DidStartMonitoringForRegion += (sender, eventArgs) => { Debug.WriteLine(string.Format("Starting region monitoring for {0}", eventArgs.Region.Identifier)); }; _LocationManager.RegionEntered += (sender, eventArgs) => { CreateLocalNotification("Welcome to Old Town", "Don't forget to take stroll down the Bascarsija and visit the historic national library!"); }; _LocationManager.RegionLeft += (sender, eventArgs) => { Debug.WriteLine(string.Format("User left {0}", eventArgs.Region.Identifier)); }; _LocationManager.StartMonitoring(region); }
This implementation creates a geofence around the described region (with a center defined by the coordinates and a radius of 600 m) and sends out a notification when the specified fence is entered, giving information about the location.
Old town geofence
[ 856 ]
Chapter 6
The same implementation on Android platform would use LocationServices in conjunction with the GeofenceBuilder class to create IGeofence type boundaries and add them to the watch list. One important difference on the Android platform is that the events are handled through delegates and are generally implemented by an intent service. The implementation starts with creating GoogleApiClient like in the previous examples, and once the API client is connected, we can go ahead and create the geofence(s) and the intent service that is going to handle the callbacks. public void OnConnected(Bundle connectionHint) { var intent = new Intent(this, typeof(GeofenceListenerService)); var pendingIntent = PendingIntent.GetService(this, 0, intent, PendingIntentFlags.UpdateCurrent);
var geoFence = new GeofenceBuilder().SetRequestId("OldTown") .SetTransitionTypes(Geofence.GeofenceTransitionEnter | Geofence.GeofenceTransitionExit) .SetCircularRegion(43.8592, 018.4315, 600) .SetExpirationDuration(200000) // Expiration Duration is obligatory .Build(); var geofenceRequest = (new GeofencingRequest.Builder()).AddGeofence(geoFence).Build(); // // The async version can be used instead // await LocationServices.GeofencingApi. AddGeofencesAsync(m_GoogleClient, geofenceRequest, pendingIntent); LocationServices.GeofencingApi.AddGeofences(m_GoogleClient, geofenceRequest, pendingIntent); }
[ 857 ]
Platform Extras
The intent service implementation for sending out a local toast notification on location updates would look similar to this: [Service(Exported = false)] public class GeofenceListenerService : IntentService { public GeofenceListenerService() : base("GeoFenceListenerService") { } protected override void OnHandleIntent(Intent intent) { var geofencingEvent = GeofencingEvent.FromIntent(intent); if (geofencingEvent.HasError) { int errorCode = geofencingEvent.ErrorCode; // TODO: Log Error } else { var requestId = geofencingEvent.TriggeringGeofences[0].RequestId; switch (geofencingEvent.GeofenceTransition) { case Geofence.GeofenceTransitionEnter: if (requestId == "OldTown") { Toast.MakeText(Application.Context, "Don't forget to take stroll down the Bascarsija and visit the historic national library!", ToastLength.Short); } break; case Geofence.GeofenceTransitionExit: Debug.WriteLine(string.Format("User left {0}", requestId)); break; } } } }
[ 858 ]
Chapter 6
The classes used by Windows Store apps for geofences are the GeofenceMonitor and Geofence/GeoCircle descriptive classes. A simple Geofence class would consist of a Geocircle class and the associated ID. string fenceId = "OldTown"; // Define fence properties BasicGeoposition position; position.Latitude = 43.8592; position.Longitude = 018.4315; position.Altitude = 0.0; double radius = 600; // in meters Geocircle geocircle = new Geocircle(position, radius); // Create the geofence Geofence geofence = new Geofence(fenceId, geocircle);
Once the geofence is initialized, we can use the GeofenceMonitor class to add the geofence and subscribe to the events. GeofenceMonitor.Current.GeofenceStateChanged += OnGeofenceStateChanged; GeofenceMonitor.Current.StatusChanged += OnGeofenceStatusChanged;
It is also possible to use the geofence status change events as triggers for a background task so that the registering application does not need to be in the foreground or even in the running state.
Native libraries
In spite of the fact that the Xamarin framework and .NET core implementations on Xamarin.Android and Xamarin.iOS platforms provide a vast amount of features, in some cases it is unavoidable to include native code in cross-platform implementations. Fortunately, it is possible to bind or link native libraries on both of these platforms.
Managed callable wrappers (Android)
As mentioned in previous chapters, managed callable wrappers are generated managed code libraries which provide a way to interact with the Java Runtime over the JNI bridge to execute code from certain Java libraries.
[ 859 ]
Platform Extras
Java libraries are often packaged in Java archive files (JAR files) and it is possible, using the compiled Java library project, to create a binding library which can be included in Xamarin.Android applications. In order to demonstrate this usage, we will be creating a MCW for a simple JSON parsing library. The first step of creating our binding library would be to use the built-in project template to create our binding project.
Binding library project
Once the binding project is created, we can copy the JAR into the created Jars folder in the binding project. After the copying is completed, an important step would be to check the Build Action configuration for the JAR resource. The copied JAVA library files can be used in two ways: • InputJar: This is a Java library archive that is going to be used to generate the managed wrapper.
[ 860 ]
Chapter 6
• ReferenceJar: This is a Java library archive that is only going to be used as a reference and not to generate a wrapper.
Binding library structure and build action
After setting the Build Action field to InputJar (this simple library does not have any dependencies), we can build the library project. Once the build is successful, you can see the generated managed files in the \obj\Debug\ generated\src directory. namespace Org.Json.Simple.Parser { // Metadata.xml XPath class reference: path="/api/package[@name='org.json. simple.parser']/class[@name='JSONParser']" [global::Android.Runtime.Register ( "org/json/simple/parser/JSONParser", DoNotGenerateAcw=true)] public partial class JSONParser : global::Java.Lang.Object { ...
Looking at the main parser file, you will notice that the definition for a class consists of an Android runtime registration and a class deriving from a Java object. Metadata about the class or class members also has a metadata comment, which defines the path of the item in the Java library package.
[ 861 ]
Platform Extras
If we wanted to change the name of a namespace (by default, they are generated from the package names defined in the api.xml file), or the name of any members of a class or the class itself, we could make use of the Metadata.xml file that is located in the bindings project. The Metadata.xml file contains transforms on the api.xml document that is generated from the jar files. This API description document contains the class definitions and components in a format similar to that of GAPI that is used by mono compiler. With the transforms included in the Metadata.xml, we can redefine the managed names designated for the generated C# items. For instance, in order to change the namespace, we would use a description similar to this: Json.Simple
For changing the class names, the syntax is quite similar: JsonParser
Finally, the generated class declaration would look similar to this: namespace Json.Simple.Parser { // Metadata.xml XPath class reference: path="/api/package[@name='org.json.simple. parser']/class[@name='JSONParser']" [global::Android.Runtime.Register ("org/json/simple/parser/JSONParser", DoNotGenerateAcw=true)] public partial class JsonParser : global::Java.Lang.Object {
Linking versus binding (iOS)
While dealing with native code on the Xamarin.iOS platform, there are several options developers can use. If we were dealing with simple static utility libraries on C or Objective-C, it is possible to create so-called fat binaries and then link them at the compile time. Later in Xamarin runtime (remember there is no Xamarin runtime in iOS, everything is compiled into static code), methods from the native library can be invoked using the P/Invoke functionality in the Xamarin framework.
[ 862 ]
Chapter 6
The other option, which enables users to create a stronger "bridge" (at the cost of performance) with native libraries, is to create bindings to Objective-C classes and methods. Using this approach, similar to managed callable wrappers in Android runtime, we would need to create a C# wrapper over the Objective-C framework library and use the managed wrapper to access the native implementation. Even though this approach creates a more intuitive and manageable access point to native code, since the managed wrapper is, in essence, a high-level implementation and the mono compiler actually generates the P/Invoke and Imports for accessing native functionality, it is inherently a little more costly than native linking. Both implementations require the creation of the fat binary as a starting point. A fat binary is the colloquial term used to describe binary packages that contain native binary compilations for all possible CPU architectures (i386 for the Simulator and ARMv7/ARM64 for the devices). In order to create the universal binary that is suitable for use in all iOS development targets, one needs to make use of the command-line utility in Mac OS X. lipo -create -output libFatBinary.a libThinBinary-i386.a libThinBinaryarm64.a libThinBinary-armv7.a
After the universal binary is created, you can now copy the universal package into a project folder in the Xamarin.iOS application, set the build action to None, and instruct the mtouch compiler to link the binary in compile time. For build instructions, you would need to use the build arguments section in project properties and gcc flags. -gcc_flags "-L${ProjectDir} -lFatBinary -force_load ${ProjectDir}/ libFatBinary.a
Additional parameters might need to be included according to the frameworks being used or if the binary includes C++ code (for example, the –cxx flag for C++ code). The other option is to create a LinkWith declaration (in most cases, this is created automatically) in an Objective-C binding project. The code is as follows: [assembly: LinkWith ("libFatBinary.a", LinkTarget.ArmV7|LinkTarget.ArmV7s|LinkTarget.Simulator|LinkTarget. Simulator64|LinkTarget.Arm64, ForceLoad = true, Frameworks = "CoreFoundation CoreData CoreLocation", LinkerFlags = "-lz -lsqlite3", IsCxx = true)]
[ 863 ]
Platform Extras
In an Objective-C binding project, you must first familiarize yourself with the types, methods, and other constructs in the native library to be able to start implementing the responding managed types in the binding library. Objective Sharpie is a useful tool for creating managed wrappers for Objective-C libraries. Initially, an internal tool used by the Xamarin team, it soon was released to public. Even though the implementation is not complete and it is not fully supported as an official product, it can help accelerate the implementation against native libraries.
Summary
In this chapter, we talked about some platform-specific features related to inter-app communications, peripherals, and location data. Using platform-specific features can make your applications more attractive to platform users by providing scenarios that they are familiar with and increase the native look and feel of your applications. Platform-specific features related to different communication protocols, such as Bluetooth, NFC, and Wi-Direct, can be employed for various scenarios. However, most of the protocols and profiles described here target Android and Windows Phone. Xamarin.iOS applications can only benefit from the Bluetooth LE profile. Location awareness is another platform-specific implementation that all mobile applications can benefit from. By adding a location context to the business logic of applications, developers can create a more personalized experience for users. Finally, if needed, Xamarin provides important features for binding and linking native libraries for Android and iOS platforms, which transform a complex porting task into merely imports. In the next chapter, we will discuss the user interface components on different platforms and how they correlate with each other.
[ 864 ]
View Elements In this chapter, you will find introductory information about User Experience (UX) design concepts and explanations on the differences and similarities of design principles on Xamarin platforms. Correlation between the UI elements will be illustrated and useful design patterns will be demonstrated with real-life examples to create a consistent user experience across platforms without compromising the native look-and-feel. This chapter is divided into the following sections: • Design philosophy • Design elements • User interaction
Design philosophy
One of the biggest pitfalls while designing an application for cross-platform use is to impose the design patterns from one OS to the other one. In the mobile world, each platform and users of those platforms have certain expectations from an application. These expectations can be as insignificant as an icon on a common feature access button (for example, the share button on iOS and Android), or as important as the layout of a view (for example, tab buttons on the bottom and top of a view on iOS and Windows Phone, respectively). In this paradigm, the designer's responsibility becomes much more complex, since the design, while creating a brand for the application, would need to be inviting and appealing for the users of the platform.
[ 865 ]
View Elements
User expectations
Mobile platform users are creatures of habit. One of the key deciding factors of the adoption rate of a mobile application is how easy it is to use and how discoverable the features are for the platform users. It is important to remember that when users become acquainted with a specific platform, they will expect certain patterns and behaviors while interacting with that device. Trying to change these habits and forcing the users into usage patterns that they are not accustomed to might be costly.
Platform imperatives
Both iOS and Windows Runtime have well-defined design guidelines that were refined over the years with the help of Microsoft's and Apple's experience on various software platforms. Android, being an open source development platform, has been searching for an identity since the early versions and it was a general implementation principle to design first on iOS and port the design to Android. However, with the release of Material Design guidelines by Google, the Android platform and app developers finally seem to have found a scheme to adhere to and create a unified experience on the Android platform across different applications. With the emergence of minimalism and flat design patterns in software design, Microsoft was the pioneer to release the Microsoft design language (the Modern UI, codenamed Metro). Modern UI design heavily depended on typograph and geometry. The motto of this design pattern is "content over chrome", and application developers were encouraged to use the content itself to provide the interactivity and remove any unnecessary ornaments that are not crucial to the content or the functionality.
Panorama View from Windows Phone 7
[ 866 ]
Chapter 7
With the release of iOS 7, Apple joined the minimalist movement with an overhaul of their user interface, which is described by Jonathan Ive (Senior VP of Design) as bringing order to complexity. Translucency, typography, and layering were the highlighted features of this new design. It was a major change of Apple's design direction which, at the time, was famous for its skeuomorphic designs on various applications and platforms.
iOS 7 Home Page and an Android dialog
Google's take on flat design principles, Material Design (codenamed Quantum Paper), tries to address the same type of design concerns by reducing the design elements to their very basics and recreating interactive surfaces with strong typography resembling paper and ink in essence.
[ 867 ]
View Elements
Hardware dependency
Similar to web applications, on Xamarin target platforms, especially on Windows and Android, the hardware that the Xamarin application is going to be running or displayed on varies greatly. An application designed for a specific platform can be used on a low-end touchscreen device with a mediocre resolution or on a high-end phablet with an HD display on landscape or portrait rotations. This hardware dependency should be one of the main concerns while designing the UI for mobile applications. For instance, pre-Android 3.0 phones used to have hardware buttons that helped with the navigation throughout the application and the OS itself. These buttons consisted of a back, menu, search, and home buttons. Even though the hardware buttons were replaced with the bottom system navigation bar (software buttons) on later devices, this trait is followed by Windows Phone devices that still have the back, Windows, and search hardware buttons. On iOS, the navigation hierarchy implementation is completely up to the application and generally handled by the back button placed on the top navigation bar.
Design metrics on Android
For varying resolutions, in order to create an adaptive user interface, each platform uses different methodologies. However, in each platform, the important metric unit is the pixel density. Pixel density can be defined as the number of pixels that can fit into an inch in length. According to the pixel density (PPI or pixels per inch), independent from the total physical width and height of the screen, developers can create consistent views across various mobile devices. In other words, total screen resolution (pixel density multiplied by screen dimensions) is a declining trait that is taken into consideration while designing cross device/platform applications. On the Android platform, to create a uniform experience on different pixel densities, developers and designers are encouraged to use density-independent pixels (dp) unit for expressing various dimensions and measurements of UI controls. Density-independent pixels are calculated by considering the 160 pixel density as a norm and calculating the display size in normalized pixel values.
[ 868 ]
Chapter 7
Check out the following table for more information on Android density-independent pixels: Screen Density
Density Bucket
Screen Resolution (pixels)
Screen Resolution (dp)
120
LDPI
360 x 480
480 x 640
160
MDPI
480 x 640
480 x 640
240
HDPI
720 x 960
480 x 640
320
XHDPI
960 x 1280
480 x 640
Android Density-Independent pixels (dp)
In order to demonstrate the scaling and density independent pixels, we can compare the following views on different devices. Using the pixels to design the content would be visualized differently on different devices:
Using pixels to design the UI
However, if we were to use the same design elements with dp as the measurement unit, the UI would be much more uniform.
Using dp to design the UI
Similar to dp, another density-independent measure unit on Android is "sp", or scalable pixels. The main difference between dp and sp is that sp is scaled according to the user's font settings and generally associated with text content, while dp is managed by the operating system and the user generally does not have any control over it.
[ 869 ]
View Elements
For media resources (for example, images) and layouts, the Android solution structure supports creating specialized design elements. Icons and other graphics can be provided in alternative sizes and resolutions using the correct density bucket identifier as a suffix to the drawable folder (for example, drawable-xhdpi for extra high density). Similarly, if needed, multiple alternative layouts can be provided according to the screen size groups in the layouts folder (for example, layout-xlarge or layout-xlarge-land for portrait and landscape displays on an extra-large screen).
Design metrics on iOS
In the iOS ecosystem, there are only a handful of devices and screen resolutions. On this platform, the identifier on display scaling is the point (pt) notation. A point is displayed as one physical pixel on a non-retina display. On retina display and higher configurations (iPhone 6 Plus), the scaling factor is calculated as 2x and 3x, respectively, while the point measurements are kept as they are. iPhone 6 Plus has the scale factor of 3 and screen resolution of 414 x 736 points. This would translate to 1242 x 2208 pixel resolution. However, the physical supported resolution on this device is 1080 x 1920. For this reason, images rendered (or rasterized) with the 3x scale factor are then down-sampled with a ratio of 1:1.15 on this device.
Design metrics on Windows Runtime
On Windows Runtime, the scaling of the application view is taken care of by the scaling algorithms that normalize the size of controls, fonts, and other UI elements. This normalization process occurs on the runtime and developers generally do not need to deal with it. When designing Windows Runtime applications, the measurement unit is pixels. However, the pixels are referred to as effective pixels. Effective pixels are the normalized size unit of the operating system.
Effective Pixels on Windows Runtime
[ 870 ]
Chapter 7
A common example for the effective pixels is to consider a font of size 24px. Text visualized with this font is displayed the same way on a phone 5-10 cm away from the user and on a surface hub couple of meters away.
Design elements
In order to create a consistent layout across platforms, while conforming to the platform requirements, developers and designers need to familiarize themselves with each platform and draw parallels between the layouts and UI controls on these platforms. We will discuss this in the next chapter within the scope of Xamarin. Forms. The existence of parallels between these platforms makes the foundation of the Xamarin.Forms framework.
The basic layout
The main layout elements in all three platforms are very similar to each other. However, the placement of these elements differs greatly according to the platform.
The User Interface Layout
[ 871 ]
View Elements
On each platform, the status bar displaying the system information is located at the top of the screen (marked as "1" in the preceding screenshot). This section is one of the constant elements that should be kept in mind when designing applications for Xamarin target platforms. On Windows 10 operating system, the system bar can be expanded on user's initiative to give detailed information about the system. This expansion causes the elements to be hidden on the application canvas. However, this does not cause elements to offset, and the expansion occurs on a different layer of the screen.
On all three platforms, the second element is generally the navigation bar (marked as "2" on the screenshot). This element is only used to display information about the current view on Windows Phone. However, on iOS and especially on Android, the navigation bar has additional functions. The navigation bar on iOS applications can be used for hierarchical navigational items. However, on the Android platform, the so-called app-bar contains the context-related commands and navigation items. The context menu presenting the additional context-related commands that do not fit on the main app-bar (navigation panel on the right-hand side) and the Navigation Drawer that reveals the left navigation panel are the functional and structural elements of the main app-bar on Android applications. Having application and content-related buttons or links on the title area on Windows Phone applications has been discouraged. However, on Windows 10, similar to the Navigation Drawer on the Android platform, developers can implement an application-level switch. On Windows platform, context-related application commands and the additional items that are displayed inside a context menu are generally located at the bottom of the screen on the application bar (marked as "5"). Even though the application bar can be created on the top of the screen, this is generally a use case for applications that use the peer-to-peer/horizontal navigation patterns (refer to the next section, Navigation). The system navigation bar (marked as "4") is located at the bottom of the screen on the Android platform. This bar contains three buttons, namely Back, Home, and History. These buttons used to be hardware buttons prior to Android 4.0. Instead of the bottom app-bar on Windows Phone and the system navigation bar on Android, on iOS this area is generally occupied by the tab bar (marked as "3"). The tab bar provides the main navigation functionality in iOS applications and should be available on each screen of the application (similar to the peer-to-peer navigation app-bar on Windows Phone).
[ 872 ]
Chapter 7
Navigation
In application design, the navigation strategy should be one of the first decision items. According to the requirements of the application or the elements to focus on, developers can adopt different navigations strategies. While building the navigation tree and preparing the flowchart for the application, you can make use of two types of traverses: hierarchical (vertical) and peer-to-peer (horizontal). Horizontal navigation occurs when the user wants to navigate between pages that are on the same level of the navigation tree. Hierarchical navigation can be on either direction on the vertical path. As a rule of thumb, as the user navigates deeper, the number of similarly typed objects on the screen decreases and the details about a single object increases. In other words, it is rare to see list views in the lower nodes of a sub-tree in an app navigation hierarchy.
Navigation Hierarchy
On top of the traditional navigation methods, jump links among the pages on various levels and sub-trees can also be used to provide easy access to these nodes (for example, a Home link navigating from the bottom of the hierarchy back to the main page). In order to demonstrate the navigation design, we will be creating an interface for the TravelTrace application that was used as an example for functional implementations in the previous chapters.
[ 873 ]
View Elements
Horizontal navigation
Navigation between peers or siblings can provide an easy way to switch context between the items on the top level. In this case, peers would be representing the main features of an application that should generally be accessible to the user at all times. On this level, the navigation can be implemented with tabbed controls or application-level navigation providers such as the Navigation Drawer on the Android platform. The homepage should have clear design and focus; it should make a statement about what your app is tailored to do. For instance, if we were to use our travel application to demonstrate the top-level peers, we would first need to decide on the main features that the application has to offer. Possible features of this travel companion application could be: • Get detailed information on nearby attractions • Allow users to plan their trips • Create and share travel memorabilia (photos, notes, tips, and so on) Identifying features of the application could be: • Creating a social medium to share and re-use travel experience • Assisting the user before and during travels and cultural visits Overall, we want to emphasize the social aspect and also provide personal assistance for users during their visits. In the light of this "decision", we can start designing the initial concept for our application.
[ 874 ]
Chapter 7
Home Screen Sample
On the Windows Phone platform, the home screen can be either implemented as a hub or a pivot view. Although each view has similar navigational properties, pivot control is generally used to display segregated groups of content that carry similar traits.
[ 875 ]
View Elements
Hence, it is generally preferable to use a hub view as a homepage to make different top level sections of the application available and sub-nodes easily discoverable.
Hub View (Windows Phone)
When considering Windows Phone and HubView, the only possible way of navigating between the top-level items in the hierarchy is a swipe gesture, while it is possible to tap on the tab bar buttons on the other platforms. Another type of horizontal navigation can occur when navigating through different categories or filtered views of content items. On the Android platform, the main app-bar can host a filter dropdown to select the proper category to display content items. On iOS, the navigation bar, or a secondary bottom tool bar, can be used to create a button to display a picker (aka spinner) to select the proper sibling on the navigation tree. Another possible horizontal navigation provider control on iOS would be the SegmentedView control, which can be used to display different perspectives of the same type of content (for example, previous trips as opposed to future plans or recent guides and recent albums).
[ 876 ]
Chapter 7
SegmentedView control on iOS
On the Windows platform, it is generally a better idea to choose a master/detail type of implementation for use-cases with more than "several" categories where the possible categories are always visible and displayed side-by-side with the content area. It is also possible to use a drop-down menu on a fly-out attached to one of the command bar buttons. If the number of categories is limited, the PivotView control can be employed in the view implementation.
Command bar flyout on Windows Phone
It is also possible, on all platforms, to include in-content selection controls that help the user navigate between the categories (dropdowns, pickers, spinners, hyperlinks, buttons, and so on).
[ 877 ]
View Elements
For instance, a catalog view for our travel application that allows users to browse the uploaded content freely would need to categorize the country items on different continents.
Main App Bar Filter on Android
Finally, the Next/Previous buttons used on the top navigation bar and the main app-bar on iOS and Android, respectively, together with the swipe left/right gestures on Windows Phone, can create a pleasant experience when navigating between the siblings and/or collection items. This type of navigation is generally used at the bottom of the hierarchical navigation tree or at the bottom of a sub-tree.
Vertical navigation
Elements that have a parent-child relationship (for example, the parent page can be the country view and the child views can be the city details) can use the vertical traversal of the navigation tree. The simplest and most common way of vertical traversal is navigating to the details view of a content element when the user clicks on the item. A common mistake related to the details concept is to make it a two-step process where the user first needs to select the item and then click on a details command button. In modern applications, it is crucial to make the UI intuitive by means of using the content elements themselves as interaction elements.
[ 878 ]
Chapter 7
Once the user is in the details view, backward navigation to ascend in the navigation tree is implemented either with a back button on the main app-bar (on Android) and the navigation bar (on iOS), or by using the hardware back button (on Windows Phone) and the soft back button on the system bar (on Android). It is not recommended to use an additional back button on the Windows Phone platform since the design real estate is already limited and the same functionality can be implemented with the hardware button, as opposed to its desktop counterpart where there is no hardware button and the design canvas is relatively generous.
Semantic Zoom on Windows Phone
On the Windows Phone platform, another way of creating a different perspective on the content elements without having to implement a secondary view is to use the SemanticZoom control. The SemanticZoom control provides two views of the same list of content elements where the first one is generally a categorized view with a smaller number of elements and the second one is the full list view with additional details on content items. The navigation between the two views is generally implemented with pinch-in and pinch-out gestures (see the Gestures section for details).
[ 879 ]
View Elements
Jump navigation
Jump or cross navigation occurs when the application navigates between different nodes without conforming to the navigation hierarchy (for example, it is possible to navigate to the details view of an item that is on the third-level from the hub page that is on the top level of a Windows Phone application). This type of navigation is generally used with very particular features that do not relate to the general outline of the application. The navigation commands can be included on the navigation bar or as hyperlinks embedded into the content. It is also common to use the command bars to create item related navigation links.
Navigation Drawer on Android
Another possible way to create navigation access points for switching the context in an easy way is to use the Navigation Drawer type functionality on Android. A similar experience can be achieved with the persistent tab bar on iOS. As mentioned before, comparable functionality was added to the Windows Phone platform with the release of Windows 10.
Content elements
Each Xamarin target platform puts forward certain strategies and guidelines to visualize the content. Although developers are given the freedom to create appealing and innovative design blocks, especially on the Android and Windows Phone platforms, there are strict guidelines to adhere to. We can group these content blocks and controls in several categories.
[ 880 ]
Chapter 7
Collection views
Collection views provide an efficient way to display collection-based content elements. In most implementation use cases, collection elements are interactive and display attributes of the content items with text and image controls. It is also common to add item-related commands or flags on the content items themselves in the shape of tokens (for example, the command to add an item to favorites, display a status icon, and so on).
UITableView (iOS)
On the iOS platform, UITableView provides a flexible way to display collection data on a customizable layout. On a table view, each cell can be customized to display a batch of attributes from the content items and developers are free to make use of the inbuilt events and commands to implement additional command logic (for example, row actions).
Grouped table view & table view with details
[ 881 ]
View Elements
Another out-of-the-box feature of the UITableView and the associated controller (UITableViewSource) is the so-called indexing of the content elements. Indexing works in a similar way as the jump lists on the Windows platform and provides an easy way to catalog the content items and enables the user to easily jump into the correct section or the group. A search display controller can also be associated with a UITableView, creating a standard iOS search experience on a collection of items. Some of the possible artefacts that can be included in a table view cell by default are as follows: Checkmark
Signifies that the row is selected
Disclosure indicator
Signifies that another table is associated with the row
Detail disclosure indicator
Identifies that the user can click to see details about the current row (for example, Popover)
Row reorder
Identifies that the row can be dragged to reorder
Row insert
Adds a new row to the table
Delete view/hide
Reveals or hides the delete button for the current row
Delete button
Deletes the row
Table view artifacts
UICollectionView (iOS)
UICollectionView is used to create a grid-like layout on the iOS platform.
Collection views are also customizable using the in-built properties and base-classes. Collection views are more flexible in nature compared to the table views which are inherently bound by the table structure and contained cells.
[ 882 ]
Chapter 7
Collection views are also made up of cells that can be displayed in numerous layouts. The default layout can be customized using a UICollectionViewFlowLayout. The flow layout can define parameters such as the minimum line spacing between the rows, the minimum interim spacing between the items, item sizes, and section insets (margins assigned to the sections in the collection). The following code sample creates a simple flow layout structure: UICollectionViewFlowLayout flowLayout = new UICollectionViewFlowLayout(); flowLayout.MinimumLineSpacing = 20f; flowLayout.MinimumInteritemSpacing = 4f; flowLayout.SectionInset = new UIEdgeInset(4f, 4f, 4f, 4f); flowLayout.ItemSize = new SizeF(20f, 20f); myCollectionView.CollectionViewLayout = flowLayout;
Another option for customizing the layout of a collection view is to inherit the UICollectionViewLayout class and implement a custom layout. In the custom layout implementation, the class is responsible for providing the layout attributes such as the size and the location of the cells according to the collection size and available layout area. UICollectionViewController is used to normalize the data that is to be presented
and act as a delegate for the collection and item level events such as cell selection and context menus. Additionally, the SupplementaryView and DecorationView classes provide additional customizations by giving section related details and UI customizations on the collection view layer.
ListView (Android)
Listview is one of the most overused components on the Android Platform. While
it can be used to display a relatively small list of menu items, with adapters it can also be used to visualize data from other applications and services. It is possible to compare the ListView control to the UITableView control on the iOS platform and the data provider interfaces. Adapters on Android can be compared to UITableViewSource on iOS.
By default, ListView has 12 built-in views that can be accessed through the Android.Resource.Layout class. These layouts vary from simple single line of text to expandable grouped category views. Each layout uses several control references such as Text1, Text2, and Icon, which should be populated by the adapter assigning the values to the content fields. Implementing a custom layout is also possible by creating an AXML markup file and later referencing the markup in the adapter. [ 883 ]
View Elements
A sample custom layout implementation could look like the following:
We can also extend the style by adding visual state selectors (see the background color assignment in the previous sample). The custom visual state selector implementation could be defined as the following:
Finally, the list adapter implementation would look like: public class CountriesDataAdapter : BaseAdapter { private List m_Items; private Activity m_Context; public CountriesDataAdapter(Activity context, List items) { m_Context = context; m_Items = items; } public override long GetItemId(int position) { return position; } public override View GetView(int position, View convertView, ViewGroup parent) { var item = m_Items[position]; View view = convertView ?? m_Context.LayoutInflater .Inflate(Resource.Layout.CustomRowView, null); view.FindViewById(Resource.Id.Title).Text = item.Name; view.FindViewById(Resource.Id.Description).Text = string.Format("In {0} region of {1}", item.Region.Name, item.Region. Continent); view.FindViewById(Resource.Id.Image). SetImageResource(Resource.Drawable.Icon);
[ 885 ]
View Elements return view; } public override int Count { get { return m_Items.Count; } } public override Country this[int position] { get { return m_Items[position]; } } }
The preceding code should generate a view similar to the following screenshot:
List View with custom layout
[ 886 ]
Chapter 7
GridView (Android)
Other than the ListView control, on the Android platform, collections can be visualized in ViewGroup. View groups are used to bundle different visual trees and display the items in a scrollable view element. The most common implementation of the ViewGroup is the GridView widget. GridView is a scrollable grid control where content items are again provided with a ListAdapter implementation. GridView is generally used with a homogenous set of content items. These
content items consist of a set of text content and a related image item. Content items are generally referred to as tiles and they can also include several content related commands. Tiles are conceptually similar to the live tile blocks of Modern UI design of Windows applications. They are made up of primary and secondary content. The primary content fills the entire cell (for example, album cover in a photo gallery application), while the secondary is represented by icons or text. The primary action is, in most cases, a vertical descending navigation command (navigating to the details view). Context actions related to the content item are generally considered to be the secondary content on a tile. If the amount of actions on a content item or the content is not homogenous, it is advised to consider using cards rather than tiles in a grid view.
CardView (Android)
The CardView control was introduced in Android 5.0, and it can be described as a self contained content unit. The term self-contained here would refer to the fact that cards generally include multiple actions and various content-related items. Users generally do not need to resort to secondary actions (select and then use the context menu) to interact with these content items.
A standard card layout
[ 887 ]
View Elements
Cards are generally used when there is neither the need nor the possibility for direct comparison between the collection elements and the content consists of various types of data. Cards can be interactive through the use of action buttons or, in some cases, in-content input controls. They can be expandable and generally have a fixed width. CardView is implemented as a FrameLayout widget and can be used in association with a ListView or GridView to represent content elements.
ListView and ListBox (Windows Phone)
ListView and ListBox are the main collection visualization controls on the Windows Phone platform. ListView is a more specialized implementation of ListBox, and it is primarily used for displaying text-based content. Its counterpart ListBox is highly customizable and can be adopted to display content composed of
multiple data types.
Both of these containers can be used for item-level context actions. However, ListBox, similar to CardViews on the Android platform, is used to create interactive content elements that might include actions and input controls. Two-way data binding is available for both of these controls and items can be styled and customized using behaviors, item templates, and/or control styles. Orientation is vertical by default for both controls, but this can be set to horizontal if the content items are desired to be displayed on a horizontal line. In case there is the need for more customization on the template level and how the items are laid out, developers can also use the ItemsControl, which is the base implementation for most of the collection views on the Windows Phone platform. In order to customize how the items are displayed on a ListView, we would first need to create the DataTemplate that will be the template used for ListViewItems. A sample DataTemplate declaration could look like the following:
Once our template is ready, we can assign the template to our ListView together with the collection data source, which is a list of simple SampleItem objects with the properties described in the DataTemplate. The code is as follows:
Now, the content items are displayed in the ListView in a two-column style with an image, title, and description text.
GridView (Windows Phone)
GridView is another implementation of the ItemsControl on the Windows Phone
platform, which allows the developers to create collection views in a flow layout. GridView should generally be preferred over ListBox or ListView when dealing with media elements.
ListView versus GridView
[ 889 ]
View Elements
Similar to the previously defined elements, GridView supports two-way data binding and can be customized using standard methodologies.
Virtualizing panels (Windows Phone)
It is important to realize the fact that mobile platforms are not as performant as desktop or tablet devices. Especially when dealing with big sets of data, even though applications can perform well visually on a desktop workstation, memory resources might cause the UI to flicker, lag, or even block on a mobile device. In order to decrease the memory usage and improve performance by means of loading only the needed data, Windows Runtime provides the virtualizing panel controls (for example, VirtualizingStackPanel). ItemsControl, which is the base for most of the collection view controls described here, supports both data and UI virtualizations.
UI Virtualization on Windows Runtime
UI virtualization deals with the controls being rendered on the application viewport. The application list view bound to a large number of items, in this case, does not need to render and keep the controls in the runtime memory but only deal with the ones that are in the viewport. In this paradigm, controls that are removed from the screen with a scrolling action need to be destroyed and redrawn if the user scrolls back. Data virtualization deals with paged data sources. For instance, with a "virtualizable" data source (a collection that implements ISupportIncrementalLoading), only the data needed for the current viewport is loaded into the application and additional batches are requested from the data source when the UI control needs to display additional items. Random access virtualization lets developers retrieve a subset of data on any random ordinal. For this type of data virtualization, the data source needs to implement INotifyCollectionChanged and IObservableVector. [ 890 ]
Chapter 7
Modal views
Modal views are temporary view components that can provide an interactive interface to get the user's input on a certain task or decide on the execution path of a workflow. It is also common to use alert dialogs to inform the user about critical information that is crucial for the execution of the application.
Popover and alerts (iOS)
The iOS platform provides various modal dialogs to display, edit, and manipulate data in different scenarios. Each of these dialog types look different but the common denominator is the fact that they always get the focus and are displayed on the highest layer on the screen, while the content under the dialog is hidden with a translucent overlay layer. Action sheets are one of the most-frequently used modal dialogs. This dialog type is generally used to give the user an option before starting a task or cancelling the task. It is generally displayed as a list of buttons; the last of which is generally the "cancel" button, at the bottom of the screen.
Action sheet display on iOS
Action sheets can be initialized using a UIAlertController and specifying the UIAlertControllerStyleActionSheet. If the screen size permits (on a horizontally regular environment), action sheets are displayed as a popover.
[ 891 ]
View Elements
Alert dialogs are another type of modal dialogs on iOS. Alerts are generally used to inform or ask consent from the user about an issue that affects the execution of the application. Unlike action sheets, alert dialogs can contain descriptive text, a title, and even a text input field.
Alert dialog with input field and with only description and title
Alert dialogs can be invoked with UIAlertController, using the UIAlertControllerStyleAlert. Alert dialogs should avoid any kind of redundant, informal, and negative content. If the title provides enough information for the user to continue with the execution, description text could be omitted. Popovers are another type of temporary context views on the iOS platform. However, popovers only are displayed on a horizontally regular environment (in both portrait and landscape in iPad, and only in landscape rotation in iPhone 6 Plus). In horizontally compact environments, they are displayed as full screen modal dialogs. In order to initialize a popover, UIPopoverPresentationController can be used. Modal dialogs are another type of temporary view display used on iOS. Modal dialogs can be used in scenarios where a self-contained and compact view is needed to execute a very particular task or workflow.
Modal dialog with page sheet style
[ 892 ]
Chapter 7
Modal dialogs can be created using the UIPresentationController with various modal presentation styles (full screen, page sheet, form sheet, and current context). However, the presentation styles associated with modal dialogs behave almost the same on horizontally compact environments (all iPhone models except iPhone 6 Plus in landscape orientation).
Flyout, popups, and menus (Windows Phone)
Flyouts are the main modal dialogs on the Windows Phone platform. They can be used in various scenarios, including showing a context menu, showing additional details of an item, or getting consent from the user. The common behavior of different types of flyouts is that they are always displayed with the highest z-index on screen and the elements underneath are disabled with a translucent overlay. Flyouts have, by default, a light-dismiss mechanism. In other words, they can be dismissed if the user taps anywhere outside the flyout control's borders. Flyouts are generally associated with another control on the current view either by using the attached properties or using the ShowAt function of the Flyout class. The Content property of the Flyout class is used to assign a UIElement to display on screen. var flyout = new Flyout(); var stackPanel = new StackPanel { Orientation = Orientation. Vertical, Margin = new Thickness(5)}; var textBlock = new TextBlock { Text = "Flyout Text Content", FontSize = 20 }; var textInput = new TextBox { PlaceholderText = "Input Value", FontSize = 18 }; var button = new Button { Content = "Apply", FontSize = 18 }; stackPanel.Children.Add(textBlock); stackPanel.Children.Add(textInput); stackPanel.Children.Add(button); flyout.Content = stackPanel; flyout.ShowAt(TextBlock);
[ 893 ]
View Elements
The preceding sample code would create a flyout which has text content, an input field, and a button as its content:
Simple flyout menu on Windows Phone
In spite of the fact that flyouts are always attached to a UIElement (either using XAML or through code) and the dialog should be displayed in the vicinity of the associated element, on Windows Phone, flyouts behave like message dialogs displaying on the top of the screen.
In Windows Runtime, it is possible to use the derived types of flyouts for specific scenarios. MenuFlyout, TimePickerFlyout, and DateTimePickerFlyout are examples for these implementations.
Menu Flyout Usage
[ 894 ]
Chapter 7
Other than flyouts, popup control can also be used to display a temporary view or details of a content item. Popups are generally stand-alone controls and can directly be included in the view XAML. They can optionally use light-dismiss and can be shown or hidden using the IsOpen property. For alert dialogs or critical input requirements, the MessageDialog class provides developers a familiar implementation tool. MessageDialog is a simple dialog used to display text content and numerous UI commands. The UICommand class represents a button and the associated action (if any) and is used to display actions on the dialog and provide a result to the dialog once selected by the user. The following implementation creates a message dialog with a text field and two commands: MessageDialog dialog = new MessageDialog("You are about to delete an important item", "Important Deletion"); dialog.Commands.Add(new UICommand("Sure")); dialog.Commands.Add(new UICommand("Not Really")); dialog.ShowAsync();
This would be shown on the UI similar to how flyouts are visualized:
MessageDialog example on Windows Phone
Dialogs (Android)
Dialogs on Android can be implemented as simple as an alert dialog or a full screen dialog that retrieves the required form data to continue the current task. Dialogs behave the same way as modal dialog implementations on other platforms; they interrupt the current task and are displayed on top of the underlying layer. The content underneath is hidden with a translucent grey overlay layer.
[ 895 ]
View Elements
Simple alert dialogs, like their parallel implementations on other platforms, consist of a title, a descriptive content, and confirmation actions. They are invoked on critical scenarios where the user's input is crucial to continue with the execution.
Android Alert Dialog
It is important to be careful to avoid any ambiguity in the descriptive content and the action button contents. Another popular dialog used in Android applications are the context menu dialogs. This type of dialog does not require any confirmation once the item from the list is selected. They also have the light dismiss behavior. If the dialogs have additional information about the selection item and maybe additional actions, they are referred to as simple dialogs. The selection on these dialogs do not require confirmation either.
Android Dialogs
[ 896 ]
Chapter 7
If the dialog implementation requires the user to explicitly confirm the choice made, these dialogs are generally referred to as confirmation dialogs. It is common to have a "cancel" button at the bottom of the dialog screen so the previous selected option can be kept.
Text views
On all three platforms, with the emergence of the minimalist design inclinations, typography and text content items became the focus of UX design. Each platform has well-defined guidelines on font sizes and typefaces for different scenarios. More importantly, each of these platforms has specialized ways to display and edit rich text formats. • Windows: On the Windows Phone platform, Run elements are used to define specific sections of text that have a certain formatting applied to. Run elements can then be included in TextBlock elements or RichTextBlock controls. In addition to the Runs, RichTextBlocks can be used in conjunction with HTML-like styling elements (for example, bold, span, italics, and so on). Using the RichTextBlocks and RichTextBlockOverflow as a container, any shape and style text displays can be supported in Windows Phone applications. • Android: On the Android platform, text formatting is achieved using the so-called spans. There are numerous pre-styled span implementations such as RelativeSizeSpan, ForegroundColorSpan, and ClickableSpan. These span implementations are used to set certain sections of a SpannableString with the described styles. There is a SpannableStringBuilder class that can be used to create the styled paragraphs/text content. Once the SpannableString is complete, it can be used as content for the TextView control. • iOS: On the iOS platform, text-related features and controls are introduced by the Core Text library. The UITextView control is the visualization element in this library. Text formatting is achieved by using the NSMutableAttributedText class. For attributed text content, different text ranges can be set to use certain attributes such as NSUnderlineStyleAttribute, NSBackgroundColorAttribute, and so on. When displaying attributed text blocks a NSTextContainer can be used to describe a shape as line fragments in which the text should be displayed.
[ 897 ]
View Elements
Web views
Web view controls are used to display rich HTML content on Xamarin target platforms. These web view controls build their own navigation stack independent from the application runtime. On Android and Windows phone, it is also possible to inject JavaScript into the HTML content that is being displayed on the control. On all the platforms, it is possible to load not only remote, but also local web applications from the application resources.
Feedback
One of the pillars of modern application design is keeping the user informed at all times about the actions being executed by the application and the progress of these tasks. Even if the application is dealing with a blocking call (the execution cannot continue before finishing the task), displaying a progress ring creates the illusion that the application is still responsive. Progress indicators can be categorized into two groups: indeterminate and determinate.
Indeterminate progress
Indeterminate tasks and the associated progress indicators are related to the operations where the application cannot provide neither an estimated completion time nor progress information. These operations might depend on completion of multiple sub-procedures and might be related to the whole application or only a single UI element. With indeterminate processes, we first need to decide on how crucial the process is for the application. If the application cannot continue without completing the current process, this would be an application-level blocking call. In cases of blocking calls (involving single step or multiple steps), it is a good idea to use a progress ring on the main content area. A good example for this scenario would be a main client trying to retrieve e-mail messages from the server without knowing how many items there are on the server. If there are multiple steps involved in this process, you can additionally show an information text near or over the progress ring.
[ 898 ]
Chapter 7
This implementation on Android can be achieved with the ProgressDialog class. Instantiating this control provides a modal dialog with the possibility to include a descriptive text. It is important to set the indeterminate flag to true before displaying it on the UI.
Progress rings on Android, iOS and Windows Phone
On iOS, the same visualization is achieved with the UIActivityIndicatorView. You can modify the behavior to animate and change the color. On Windows Phone, the ProgressRing class provides the same type of functionality. In indeterminate scenarios where the process being executed does not stop the user from continuing with application interaction, it is better to give a more subtle indication about the process and the controls involved in the execution. This can be achieved by using a progress ring or a bar in the vicinity of or over the control where the process started. On iOS, the only distinction between the progress bar and the ring is the process being determinate or indeterminate. However, on Android and Windows Phone, a progress bar can also act as an indeterminate task indicator. On the Windows Phone platform, it is also general practice to display an indeterminate progress bar on top of the screen if the process is an application level task, but the interaction with the application can continue without waiting for the result of this process.
Determinate progress
Determinate tasks and associated indicators are related to processes where the application can provide a current state information to the user. A determinate progress indicator of choice on Xamarin target platforms is the progress bar. Progress bars, while providing a visual indication of the current completion state of the process, can also include a label giving a text description of the current state of the task.
[ 899 ]
View Elements
It is important to also provide a cancellation method (for example, a cancel button near the progress bar) if the process is relatively long.
Android determinate progress bar displays
On the Android platform, in addition to the progress indication, a buffering percentage can also be displayed on the progress bar.
User interaction
Another important element in cross-platform development projects is the set of user interaction patterns for the application. Users already using the application on other platforms would want to find the same interaction patterns on clients running on another platform. This decision process gets even more complicated with platform specific interaction patterns, since the application should provide a familiar interface for platform users. It is important to achieve a balanced compromise between platform nativity and application identity in such scenarios and find the optimum solution. A good example for branding by means of using an interaction pattern, would be the "pull-to-refresh" interactive pattern used in iOS applications. Most application providers dealing with information feeds (for example, Facebook, Twitter, and so on) used this implementation in their iOS applications. Even though this is not a native interaction pattern on Android and Windows Phone, a similar approach quickly became popular on these platforms; hence, most developers and users are now adopting this use-case on various platforms.
[ 900 ]
Chapter 7
Interactive controls
In most cases, applications built for Xamarin target platforms would require input and other interactive controls to collect necessary information from the user. By interactive, we are referring to almost all the UI controls that can be used in a Xamarin application. In this case, even a simple filter dropdown control to select a different view perspective would be an interaction control, requesting information from the user display appropriate data or perspective.
Text input
Text input fields are one of the most used type of input fields. Text fields can be implemented as a single line of text or as a multiline. An important aspect of text fields is the fact that as soon as a text input field gets selected on a touch-enabled device, the virtual keyboard appears on the screen. It is generally a good idea to keep this in mind while designing the user interface and implementing it later on. On iOS, while the UITextField provides an input mechanism for single line of text requirements, UITextView can be used to create editable rich text content. Both of these controls provide options such as capitalization and correction.
UITextView Edit and Read-only Views
[ 901 ]
View Elements
Additionally, UITextView provides detectors that can transform Internet addresses to links, addresses to map links, phone numbers into deep-links to make a phone call, and date/time values to calendar event items. Android text input fields are similar to the ones on iOS platform. The key difference is that on Android, instead of two different controls, only the EditText control exists for multiline and single line text inputs. This is achieved by settings the InputType property of the control (or inputType attribute in AXML). Other input scopes, besides the text format, can be set such as postal address, capitalized words, autocorrect, and capitalized sentence beginnings. Note that these scope parameters are bit-wise combinations. Another specialized control that provides auto suggestions is the AutoCompleteTextView, to which developers can assign an ArrayAdapter as a source for suggestions. On Windows Phone, TextBox is the most commonly used text input control. It can be highly customized to meet the previously mentioned requirements. Moreover, the input scope field lets developers control the virtual keyboard displayed for entering the value. For instance, setting the scope to be a telephone number would display a keyboard with only digits. AutoSuggestBox, PasswordBox, and RichEditBox are other controls that can be used for more specialized scenarios.
Dropdown selection
Dropdown elements can be used, on each platform, utilizing the specialized controls. While the UIPickerView is used on iOS, the same implementation is achieved on Android by so-called spinners. Spinners, very much like other content-driven controls, are populated with a SpinnerAdapter.
Dropdown controls on iOS, Android and Windows Phone
[ 902 ]
Chapter 7
In addition to the spinner control, simple menu dialogs can also be used for users' input. Windows Runtime provides additional specialized controls, the ComboBox and ListView, for different selection use cases.
Option selection
Similar to the radio or check boxes on HTML forms, each platform provides options related UI elements. On Android, specialized controls for this scenario are Checkbox, RadioButton, and ToggleButton. Starting with Android 4.0 (API 14), Switch control can also be used. Other than the visual difference between these controls, the behavior is the same. On iOS, the main toggle control for Boolean data types is the Switch. Similar to Android, Windows Phone offers checkbox, radio button, and toggle switch control with option selections and Boolean types. There are many other controls on each platform, and each provides a specific use case for different UI interaction scenarios. UX guides for Windows Runtime and Material Design are great resources for the respective platforms. Even though the Apple human interface design documents do not provide extensive UX guidelines as the other platforms, they are great resources to learn about user control use cases.
Gestures
When developing for Xamarin target platforms, you should always keep in mind that the devices that are going to run the application will most probably have a touchscreen.
[ 903 ]
View Elements
Touchscreen devices, apart from the classic pointer-like gestures (for example, tap, double tap, scroll, and so on), also provide various interaction gestures that help developers create an interface that can interact with the user in a more natural way.
Tap
In most scenarios, the tap gesture is analogous to a single click with a pointer device. It is primarily used to select a control.
Long Press
Long press or tap and hold is used to access a context menu on Windows Phone. It is used for item selection on Android.
Double-Tap
Double tap is generally used for scaling up / zooming-in on a control.
Swipe Down
Swipe down or pan down is used on vertical scroll scenarios. Also, list controls support swipe down for selection on Windows. It is also common to be used with "Pull to Refresh" implementations.
Swipe Right
Similar to swipe down, swipe right is used on vertical scroll scenarios and sibling navigation scenarios. It is called "flick" if the gesture is fast.
Swipe Left
This is same as other pan gestures. It can also be used to delete a list item on iOS and Windows Phone 10.
Swipe Up
This is another panning gesture. It can additionally be used to reveal a bottom sheet on Android applications.
Tap & Drag
This is generally used as an active gesture to interact with draggable components.
Pinch Out
This is used in active canvas application patterns. It is used to zoom in on a view. On Windows, semantic zoom control makes use of this.
Pinch In
This is similar to the Pinch-Out gesture and is used to zoom out of an active content area of application screen (for example, zoom in on a photo).
Rotate
This is another gesture used on active canvas applications (for example, a map client). It is used to rotate the current view-port. Common Gestures
[ 904 ]
Chapter 7
While some of these gestures are already implemented by out-of-box controls on Xamarin platforms, there might be scenarios where you need to use them to create a new interaction use case in your application. For these type of requirements, specialized implementations can be found on respective frameworks. On the iOS platform, the starting point for gesture recognizer implementation is the abstract class UIGestureRecognizer. There are numerous implementations of gesture recognizer in the UIKit and they can be combined and used with delegate implementations. On Android, the GestureDetector class and the IOnGestureListener interface can be used to provide implementations for various gesture events and user actions. Classic interaction events such as pan gestures and tap actions can already be accessed through the OnTouchEvent method of any Activity implementation. On the Windows Phone platform, most of the default controls provide interaction with pointer or touch events for classic manipulation scenarios. However, for more complicated gestures, the GestureRecognizer class available in the Windows. UI.Input namespace can be used.
Summary
This chapter presented an overview of the design philosophy of, and patterns on, Xamarin target platforms. The design elements section outlined the main controls and layouts that are at the disposal of designers and developers while providing various content display strategies. There were additional sections about interactive and modern user interface design. Even though each platform provides its own UI design patterns and guidelines, the main focus of the design effort in a cross-platform application is to find an optimal compromise between native look-and-feel and application brand design. In the next chapter, we will discuss the Xamarin.Forms framework and make use of the correlation between the design elements that are outlined here.
[ 905 ]
Xamarin.Forms Xamarin.Forms is an extension module to Xamarin compiler technologies; an abstraction layer on top of the native UI components on target platforms. This chapter will focus on the various features and extensibility options of Xamarin.Forms that help developers create cross-platform application user interfaces that can then be compiled into Xamarin projects, increasing the code-sharing quality markers, and making cross-platform application development projects more manageable and unified. This chapter is divided into the following sections: • Under the hood • Components • Extending forms • Patterns and best practices
Under the hood
As previously mentioned, Xamarin, being a cross-platform development framework, provides developers the toolset to create applications that depend on and use the same code base. The shared amount of code is directly proportional to the manageability in these types of implementation. Xamarin.Forms adds an abstraction layer on top of the mono runtime on Android and the pre-compiler .NET stack on iOS platforms. This abstraction layer's sole responsibility is to provide the Xamarin compilers with the necessary instructions to normalize the code or markup for GUI elements to render native controls in Xamarin apps. Since the platform language for Xamarin is C#, Extensible Application Markup Language (XAML) is the design markup language of choice. Xamarin.Forms provides the same abstraction as a runtime library for Windows Store applications.
[ 907 ]
Xamarin.Forms
The abstraction layer provided by Xamarin.Forms makes use of the similar UI elements and layout patterns which were illustrated in the previous chapter (see Chapter 7, View Elements). In this context, Xamarin.Forms only provides controls and views that are common to all three platforms and omits platform-specific UI elements. It is important to understand that Xamarin.Forms is not a replacement for a native user interface implementation, but more of a foundation to build upon while creating cross-platform applications.
Figure 1: Xamarin.Forms abstraction layer
Xamarin.Forms not only provides a uniform native UI development framework, but also additional features that are generally associated with loosely-coupled UI development, such as data binding, dependency injection, and messenger infrastructure. To a certain extent, these features render third-party MVVM libraries used in various mobile application projects obsolete.
Anatomy of Xamarin.Forms
Xamarin.Forms libraries are distributed through NuGet packages and can be freely included in cross-platform development projects.
[ 908 ]
Chapter 8
Whilst the NuGet package for iOS does not present any dependencies, the Android and Windows Phone versions depend on several support libraries (that is, WPToolKit for Windows Phone; and several design and compatibility packages for Android). The Xamarin.Forms.Core library contains the UI elements and the necessary XAML declarations together with additional features related to data binding and similar operations. This assembly can be included in portable class library projects that provide the view implementation to platform-specific projects. Native client projects, in return, should reference Xamarin.Forms.Core and the platform-specific assemblies of Xamarin.Forms (for example, Xamarin.Forms.Platform.iOS). Xamarin. Forms platform libraries contain the so-called renderer implementations that are responsible for rendering Xamarin.Form elements using native controls. In other words, these platform assemblies provide the mapping between native elements and their Xamarin. Forms counterparts.
Project structure
In order to create a Xamarin.Forms application project targeting iOS, Android, and/ or Windows Phone 8, it is sufficient to use one of the project templates located in the Cross-Platform section. While the portable library project template makes use of a PCL to create the Xamarin.Forms application boilerplate, the shared project template creates a shared project with file references linked to the native client app projects.
Figure 2: Xamarin.Forms project templates
[ 909 ]
Xamarin.Forms
Project templates can be found in the Mobile Apps section in older versions of Xamarin.
Once the project is initialized, by selecting the Blank App (Xamarin.Forms Portable) project template, the created solution will include four projects, one project carrying the same name as the entered project name and three platform-specific projects with the platform suffixes.
Figure 3: Xamarin.Forms solution main view and project scopes
One caveat of using this project template for Xamarin.Forms is the fact that other platforms that are actually supported by this framework (for example, Windows Phone 8.1 and Windows 10) are not included in this multi-project template. These projects can be created manually, and the NuGet package for Xamarin.Forms can be added using the NuGet package manager. It is also important to mention that the NuGet package referenced in the project template might not be the latest version of Xamarin.Forms and therefore can be updated using the NuGet package manager.
[ 910 ]
Chapter 8
Figure 4: The latest NuGet package for Xamarin.Forms
If you take a look at the generated code in the portable library, App.cs, and the platform-specific projects, the implementation pattern immediately becomes apparent.
[ 911 ]
Xamarin.Forms
The Xamarin.Forms implementation contains the application class implementation as the root node. This application is initialized and invoked by the generated code in the app delegates in platform-specific projects (similar to the following code excerpt from the Xamarin.Forms iOS application sample): [Register("AppDelegate")] public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS. FormsApplicationDelegate { public override bool FinishedLaunching(UIApplication app, NSDictionary options) { global::Xamarin.Forms.Forms.Init(); LoadApplication(new App()); return base.FinishedLaunching(app, options); } }
The initialization code for the app in the template boilerplate creates a content page with a single label in a StackLayout element and designates this view as the main page: public App() { // The root page of your application MainPage = new ContentPage { Content = new StackLayout { VerticalOptions = LayoutOptions.Center, Children = { new Label { XAlign = TextAlignment.Center, Text = "Welcome to Xamarin Forms!" } } } }; }
As you can see, the Xamarin.Forms application structure is made up of controls wrapped in different layout configurations that are presented through various page types.
[ 912 ]
Chapter 8
Components
Xamarin.Forms components can be categorized into three main groups according to their position in the view hierarchy and their usage.
Pages
Conceptually, pages are navigational elements. They provide a general hierarchical organization of the view elements whilst also acting as a container for the layouts. There are various page types that can be inherited and implemented or designed using XAML markups.
Tabbed page
When discussing the top-level navigation pages in the previous chapter, we mentioned several controls that can provide horizontal navigation throughout top-level pages. Using Xamarin.Forms, TabbedPage allows developers to create these horizontal navigational view elements. TabbedPage generates a tabbed action bar and associated activities on Android. On Windows Phone, the generated view contains a pivot control. Finally on iOS, generated view contains a tab bar and associated views. TabbedPage contains the navigation pages as its children (that is, the Children
property accepts different page implementations), and the page titles of the child elements are used as navigation links. Implementing the tabbed view example from the previous chapter for our TravelTrace application would look similar to the following snippet: var tabbedPage = new TabbedPage(); tabbedPage.Children.Add(new ContentPage { Title = "Recent", Content = new StackLayout { VerticalOptions = LayoutOptions.Center, Children = { new Label { HorizontalTextAlignment = TextAlignment.Center,
[ 913 ]
Xamarin.Forms Text = "Recent uploads page" } } } }); // ... // TODO: Add the other tab nav items MainPage = tabbedPage;
The same implementation can be done using XAML and creating a TabbedPage implementation:
[ 914 ]
Chapter 8
Assigning the newly created MainTabView class instance to MainPage in App.cs would result in the same view as the code implementation:
Figure 5: TabbedPage view
It is important here to mention that the Icon property provided for individual peers in a TabbedPage implementation only applies to the iOS platform. Icons in tab and pivot views are not supported by Xamarin and it is not an accepted design approach for Android and Windows Phone.
The MasterDetail page
The example with the tabbed view satisfies the horizontal navigation requirements of our design, but we also need a navigation drawer and associated main menu navigation items for our Android applications. MasterDetailPage provides a structure in which the master page selection menu
can initiate a navigation request on the detail page. Moreover, if the content of the Detail page is encapsulated in a NavigationPage, the generated view is added to the navigation stack so that the previously displayed pages can easily be pulled into the master view using the event methods. In order to include an additional layer of navigation and a global menu, we can now use the MasterDetailPage class to create the desired navigation structure.
[ 915 ]
Xamarin.Forms
The first step of the implementation is to create our master view. The master view in this case will include a simple list view with menu and a profile display as the list header. When the list view content items are selected, we can either bubble up the event to the MasterDetailPage or pass the parent page as a parameter to the menu page we are implementing. public NavigationMenuView(Page root) { Icon = "toggle.png"; InitializeComponent(); ListViewMenu.ItemsSource = m_MenuItems = new List { if(ListViewMenu.SelectedItem == null) return; // TODO: Implement the navigation strategy Debug.WriteLine("Item selected {0}", ((Tuple)e.SelectedItem).Item2); }; }
In this implementation, we are using a Tuple with three parameters for the label, tag, and icon of the menu item. It would, of course, be better to implement a class to contain these data values. Now we can construct our MasterDetailPage by setting the Master and Detail properties: var masterDetailPage = new MasterDetailPage(); // Can select any of the behaviors: // Default, Popover, Split, SplitOnLandscape, SplitOnPortrait
[ 916 ]
Chapter 8 masterDetailPage.MasterBehavior = MasterBehavior.Popover; masterDetailPage.Master = new NavigationMenuView(masterDetailPage); masterDetailPage.Detail = new NavigationPage(new ContentPage { Title = "Detail Page", Content = new StackLayout { VerticalOptions = LayoutOptions.Center, Children = { new Label { HorizontalTextAlignment = TextAlignment.Center, Text = "Here is the Detail" } } } }); MainPage = masterDetailPage;
MasterBehavior can be adjusted according to the platform. In this example, we will
be using the popover behavior, which displays a flyout and a toggle button in the main app bar on Android and creates a navigation command icon to open the flyout on other platforms.
Figure 6: Navigation flyout on Android and Windows Phone
[ 917 ]
Xamarin.Forms
When using MasterDetailPage, it is important to anticipate the outcome of the design decisions made in Xamarin.Forms markups so that final applications for the target platforms still follow the design guidelines.
NavigationPage
NavigationPage is the most abstract implementation of the Page class. The main
purpose of using NavigationPage is to create a navigational stack in the application context. This navigational context is supported natively on Windows Phone. However, other platforms do not create a stack for previously viewed pages. Using NavigationPage, one can utilize the items in the navigational history and manipulate the stack using push and pop methods.
CarouselPage
CarouselPage is another horizontal navigation implementation that the user can use to navigate through the peer pages using swipe or flick gestures. CarouselPage
is very similar to the panorama view and pivot controls from the Windows Phone 7 platform, except for the fact that CarouselPage has strict snap points (that is, when the free scrolling view snaps to the borders of a control or a page) and it does not have an endless loop of items, in contrast with pivot control, but instead has more linear navigation. Behaviorally, it resembles and uses a similar navigation strategy as the FlipView control from Windows Runtime.
In order to initiate a carousel-type navigation structure, either XAML or code-behind can be used. A simple carousel view with three content page implementations would look as follows:
[ 918 ]
Chapter 8
The resulting view would be a container for touch-initiated horizontal navigation between peers.
Figure 7: Carousel view
ContentPage
ContentPage is a simple page implementation used generally in cooperation with
previously described page structures. It can be described as the actual content presenter. Child views in other navigation implementations are generally made up of ContentPage implementations.
In order to set the content to be visualized on the user interface, you can use the Content property, which accepts a list of view objects. Layout elements are generally used as the direct children of ContentPage and other user controls are appended to this visual tree.
[ 919 ]
Xamarin.Forms
Layouts
Layouts are structural design elements that allow developers to organize the UI controls using various strategies. We can classify layouts into two groups according to their class inheritance hierarchy: single view and multiple view.
Figure 8: Layout classes
Single view layouts are direct descendants of the base layout implementation and they are capable of displaying only a single view item (they can also be a branch of a visual tree). Examples of this category are ContentView, Frame, and ScrollView. ContentView and Frame elements are rarely used and can be helpful while dealing with fewer content elements and/or an application with an active screen pattern (for example, a drawing application would use a single canvas implementation with absolute positioning; drawn geometry items would be the children of the canvas). ScrollView, on the other hand, is one of the most popular controls and can be used together with another layout element, such as StackLayout. When used with StackLayout, if the calculated height of StackLayout is greater than the client area, the parent control, ScrollView, makes it possible to change the viewport of the child control. Even though it is not very common, ScrollView can still be used with simple controls such as Label or Image.
[ 920 ]
Chapter 8
For instance, if we were to implement the primary content of the TabbedPage created in the previous section, we can use a ScrollView to display the StackLayout that is displaying the recently uploaded items from the TravelTrace server. The markup for this implementation would look similar to the following snippet:
… …
ItemTemplate defines how the content elements are to be rendered in the collection view. If ItemTemplate is not defined, the list renderer will try to convert the content elements to a string and display them as TextCells. Re-using the grid implementation from the previous example(s), we can define DataTemplate for the ItemTemplate property of ListView:
[ 927 ]
Xamarin.Forms
This implementation will be displayed in a scroll-enabled list container similar to the following screenshots:
Figure 11: ListView with item source
In order to implement context-related functions, the item data template, view cell, can be edited to include context menu elements. It is also possible to modify view cell in the code-behind file. The following XAML snippet can be used to create two context menu actions: Favourite and Remove:
[ 928 ]
Chapter 8
Notice that the Remove command is marked as destructive. The IsDestructive flag is used to create the slide-to-delete behavior on iOS. On other platforms, destructive actions are rendered similar to other commands.
Figure 12: Context menu actions
ListView also has a flag called IsPullToRefreshEnabled. This property can be used to support the pull-to-refresh behavior. RefreshCommand can be used to bind
the action required to refresh the list.
Extending forms
Even though the Xamarin.Forms framework provides an extensive set of customizable UI elements, in certain scenarios you might want to change how a certain control looks or behaves. Moreover, at times, providing an application-wide customization scheme can provide consistency and decrease redundancy. XAML markup infrastructure used in Xamarin.Forms provides various custom implementation scenarios.
[ 929 ]
Xamarin.Forms
Styles
When implementing certain UI patterns, view elements have to be declared independent of each other, and yet they have to carry the same design attributes, such as typography, layout properties, colors, and so on. Styles can be used in this situation to organize and re-use the element attributes. Using ListView, the only view container defined would be the item data template, and the content items loaded from the data source will be rendered using the same template. However, if the view requirement is to use Grid, StackLayout, or TableView, each view item would have to be defined separately. For instance, it might become quite cumbersome to create a settings view for Xamarin.Forms applications using the TableView control. In this implementation, if we cannot use the standard cell views, such as EntryCell or SwitchCell, because of requirements, the markup becomes even more redundant with each control having to declare similar fonts and colors that make up the theme of the application.
Figure 13: TableView used for a settings view
Custom cell views in this implementation were used to create a description element for each setting. If we look at the markup file, you can see the repeating styles for each text element:
[ 930 ]
Chapter 8
[ 931 ]
Xamarin.Forms
In this example, each label is defining at least TextColor, FontSize, VerticalTextAlignment, and HorizontalOptions. There is one pattern for setting labels and another one for description elements. Vertical and horizontal alignment options, however, apply to all text elements. Initially, we can simplify the markup by creating an implicit style that will apply to all Label elements. Implicit styles do not define a resource key, hence they apply to all targeted controls, such as TargetType:
We can now create additional styles to set item labels and descriptions:
However, this does not work as we expected it to. The outcome demonstrates that the implicit styles were overridden by more specific style descriptions. It is important to realize that there is no implicit cascading between the styles defined for the same target controls. XAML is not HTML/CSS.
Figure 14: Implicit style is overridden with assigned styles
[ 932 ]
Chapter 8
In order to create a cascading scheme, we need to base the SettingLabel and SettingDescription styles on the initial implicit style. For this purpose, we need to define a key for our base style and reference this base in the derived style declarations:
Notice that the SettingDescription style uses the BasedOn declaration (similar to the WPF implementation), while SettingLabel uses the BaseResourceKey property. Both of these references can be used in Xamarin.Forms implementations.
Triggers and behaviors
At times, implementation requires style-related or behavioral changes of controls in accordance with changes of the same or any other control's properties or data, as well as certain events (for example, disabling a certain control according to the data input value changes). Under normal circumstances, implementations utilize data bindings where the data change event is routed to the presenter and the presenter changes the view, providing a trivial solution. However, if the UI event should trigger another UI change, the cost of data binding would be an overhead. Instead, the Xamarin.Forms markup offers triggers and behaviors that add complexity to intrinsic controls.
[ 933 ]
Xamarin.Forms
For instance, the settings view that we previously created for our application requires certain business rules. The first setting value, UserLocation, is a dependency of the UseGeofences setting. In other words, technically it is not possible to create geofences without using location services. For this specific scenario, we could create a data binding from the IsToggled value:
The preceding implementation works as expected since the IsToggled and IsEnabled values are both using Boolean as the value type. If we were to change any other property of the target UI element, we would have to implement a value converter. Moreover, multiple property changes would require multiple bindings. Triggers provide an easy solution for this type of scenario. There are four types of trigger that can be used to initiate either a setter action or a custom implementation of a trigger action. Property triggers are used to create a visual state on a user control according to the value of a property of the same control. Data triggers are used in a similar fashion but in this case, the cause for the trigger is defined by data binding. Event triggers are bound to user control events and multi triggers can encompass and invoke an action that is dependent on multiple conditions. The same scenario from the previous example can, in this case, be implemented with a DataTrigger. Iterating on the scenario, the implementation can set the enabled and text color properties on the associated description label:
[ 934 ]
Chapter 8
Let us also implement a notification when the main control is disabled, warning the user about other settings being disabled. For this implementation, we will need an event trigger and a trigger action implementation. A trigger action implementation consists of implementing the TriggerAction class and the virtual Invoke method: (see the Dependency injection section for the implementation of INotificationService) public class WarningTriggerAction : TriggerAction { public string Message { get; set; } protected override void Invoke(Switch sender) { if(!sender.IsToggled) DependencyService.Get() .Notify(Message); } }
Then, we will need to declare the namespace containing the implementation in the root node of the page's markup: xmlns:components="clr-namespace:Xamarin.Master.TravelTrace .Components;assembly=Xamarin.Master.TravelTrace"
And finally, we can add the event trigger to the main setting control:
[ 935 ]
Xamarin.Forms
Figure 15: Notification triggered using EventTrigger
If we want this trigger to be applied to multiple controls (for example, the notification settings section in the example), we can create a new style for the main setting values and add the trigger to the style declaration:
[ 936 ]
Chapter 8
The same type of result could have been achieved with a behavior implementation for the Switch control. Behaviors are a more generic type of extension mechanism that allow developers to extend existing user controls without having to create derivatives of these controls. For instance, if we were to use the same scenario (that is, when the switch control is toggled off, a notification window should be shown to the user), we would need to implement the base class, Behavior, with a type argument for Switch view: public class SectionSwitchAlertBehavior : Behavior { public string Message { get; set; } protected override void OnAttachedTo(Switch control) { control.Toggled += ControlOnToggled; base.OnAttachedTo(control); } protected override void OnDetachingFrom(Switch control) { control.Toggled -= ControlOnToggled; base.OnDetachingFrom(control); } private void ControlOnToggled(object sender, ToggledEventArgs toggledEventArgs) { if (!toggledEventArgs.Value && !string.IsNullOrWhiteSpace(Message)) { DependencyService.Get() .Notify(Message); } } }
In a custom behavior implementation class, an OnAttachedTo method is used as the initialization function where the control can be customized. Similarly, OnDetachingFrom is used to clean up the customizations and any existing event handlers that might have been attached to the control. Even though it's technically possible, it is not advisable to modify the binding context using behaviors.
[ 937 ]
Xamarin.Forms
The custom behavior can be included either in styles targeting the same type of control or with in-place markup elements added to the specific control:
Custom renderers
Xamarin.Forms provides the developers with a uniform markup and implementation framework to create native UI views for all Xamarin target platforms. The abstractions of provided UI elements are then used by the framework to render native controls. Similar to the Xamarin.Forms solution anatomy, each view/control in the Xamarin.Forms platform is a composite implementation. While the behaviors for the abstracted control logic are implemented and can be derived in portable class libraries, the renderers associated with each control for various platforms are implemented by platform-specific libraries.
Figure 16: Custom renderer implementation
In order to customize a control, one must first create a derived class for the abstracted control. After this implementation, the custom control can be referenced with a clr-namespace declaration (similar to TriggerAction and Behaviors) and can be used in the view markup.
[ 938 ]
Chapter 8
At this stage, the custom implementation of the control would use the default renderer for the base class. In order to change the way that native controls are rendered on a specific platform, we would need to provide a custom renderer implementation and register it using the ExportRenderer attribute on the same platform. Custom renderers provide a powerful way to customize how the common view implementations with Xamarin.Forms should look on platform-specific views.
Patterns and best practices
In this section, we will discuss several implementation patterns and tools that developers generally resort to while developing Xamarin.Forms applications. Messaging and dependency injection features will be discussed further in Chapter 9, Reusable UI Patterns.
Messaging infrastructure
In an ideal implementation of the Model-View-ViewModel (MVVM) or ModelView-Presenter (MVP) pattern, each screen is self-contained; the screen modules for the view, model, and the mitigation components communicate with each other using various communication channels. However, in complex applications, there is sometimes the need for a communication channel between these self-contained elements, since the result of an action on one of the screens should be propagated to other unrelated section(s) of the application with a shared interest in the result of this very action. As a solution to this problem, in MVVM frameworks such as MVVMCross, Prism, or MVVM Light, it is common to see an implementation of the Event Aggregator pattern providing a loosely coupled, multicast-enabled publisher/subscriber messaging infrastructure. Event Aggregator can be described as the eventing hub, which receives multiple types of strongly typed messages and delivers these messages to multiple subscribers. In Xamarin.Forms, the Event Aggregator is called the MessagingCenter. It exposes three groups of methods: Subscribe, Unsubscribe, and Send. The Subscribe and Unsubscribe methods are used by the event observers, and the Send method is used by the publisher.
[ 939 ]
Xamarin.Forms
In this paradigm, the subscriber is responsible for providing the instance and/or the type of the sender together with the expected type of the message (that is, a simple text parameter defining the message). The message type or name is an identifier for the message and together with the message signature (the sender type and the arguments type), it makes up the decision criteria for the subscribers. Finally, the last provided parameter is the callback delegate, which can have the sender, and possibly the event arguments, as parameters: MessagingCenter.Subscribe(this, "MyMessageName", (sender, data) => { // TODO: Use the provided data and the sender }); // or //MessagingCenter.Subscribe(this, "MyMessageName", (sender) => { }, myViewModelInstance);
The publisher is responsible for providing the message with the same message name and signature. On the publisher's side, the message signature is made up of the message name and the message argument parameter: MessagingCenter.Send(this, "MyMessageName", new MyMessageContract { // TODO: Pass on the required data. });
MessagingCenter can prove to be very utile, providing simple solutions/ workarounds for architectural problems (especially scenarios where a Separation of Concerns is in question) in Xamarin.Forms applications, and creating a decoupled communication channel between components.
Dependency injection
As previously mentioned, one of the biggest drawbacks of using Portable Class Libraries (PCLs) to implement common cross-platform libraries is the fact that the platform-specific features cannot be accessed directly since the platform-dependent modules cannot be referenced by these libraries.
[ 940 ]
Chapter 8
One of the most effective and elegant solutions to this problem is using dependency injection (aka IoC - Inversion of Control). Using dependency injection, platform-specific functionality should be abstracted into segregated interfaces, and these interfaces can later be used to access the implementation modules injected with the provided dependency containers. DependencyService in Xamarin.Forms allows applications to use platform-specific
implementation through the abstraction interfaces.
In a common scenario, the first step would be to define the abstraction (in the common portable forms library) that is going to be used by the common application layer. For a demonstration, let us implement a module that uses the native messaging methods to display a notification for the user: public interface INotificationService { void Notify(string message); }
Now we can implement this interface in platform-specific projects. In the Xamarin. Android project, we can implement this using a toast notification: [assembly:Xamarin.Forms.Dependency(typeof(NotificationService))] namespace Xamarin.Master.TravelTrace.Droid.Modules { public class NotificationService : INotificationService { public void Notify(string message) { var toast = Toast.MakeText(Application.Context, message, ToastLength.Long); toast.Show(); } } }
[ 941 ]
Xamarin.Forms
For the iOS platform, we can create a local notification message and present it using the shared application infrastructure. However, local notifications for foreground applications are automatically dismissed (only at the UI level can one still implement an event delegate for a notification received event and display an alert instead). Hence, we will use the UIAlertController class and present it using the current window: [assembly: Xamarin.Forms.Dependency(typeof(NotificationService))] namespace Xamarin.Master.TravelTrace.iOS.Modules { public class NotificationService : INotificationService { public void Notify(string message) { // // This will not fire for the foreground application //UILocalNotification localNotification = new // UILocalNotification(); // localNotification.FireDate = // NSDate.Now.AddSeconds(2); //localNotification.AlertBody = message; //localNotification.TimeZone = // NSTimeZone.SystemTimeZone; // UIApplication.SharedApplication // .PresentLocalNotificationNow(localNotification); // UIApplication.SharedApplication // .ScheduleLocalNotification(localNotification); //Create Alert var okAlertController = UIAlertController.Create ("Notification", message, UIAlertControllerStyle.Alert); //Add Action okAlertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null)); if (UIApplication.SharedApplication.KeyWindow != null) UIApplication.SharedApplication.KeyWindow .RootViewController.PresentViewController( okAlertController, true, null); } } } [ 942 ]
Chapter 8
And finally, for the Windows Phone platform, we can only use the local toast notifications with the currently running applications on Windows Phone 8.1 and Windows 10 mobile. For other versions, similar to the iOS scenario, local toast notifications are not allowed for foreground applications. For this reason, we can implement a simpler notification dialog using the MessageBox class: [assembly: Xamarin.Forms.Dependency(typeof(NotificationService))] namespace Xamarin.Master.TravelTrace.WinPhone.Modules { public class NotificationService : INotificationService { public void Notify(string message) { MessageBox.Show(message); } } }
In order to use the INotificationService interface in the portable class library that implements the Xamarin.Forms application, we need to resolve the interface to create an instance of one of the platform-appropriate implementations: DependencyService.Get().Notify("Hello Xamarin. Forms!");
It is important to note that in this sample implementation, the Dependency assembly attribute was used to register the platform-dependent implementation classes. It is also possible to use the Register method of DependencyService to create dependency containers: Xamarin.Forms.DependencyService.Register();
The Register method has to be invoked after the initialization of Xamarin.Forms (that is, the Forms.Init method) and before any dependent module is loaded.
Shared project versus portable project
Xamarin.Forms extensions introduce two types of multi-project solution templates. Each template contains platform-specific projects as well as a common project to implement platform-agnostic components for these native applications.
[ 943 ]
Xamarin.Forms
In the previous examples we were using the PCL project template, which creates three platform-specific projects, each referencing a cross-platform portable class library. Platform-specific projects delegate the application initialization to the portable class library that initializes Xamarin.Forms and renders the pages implemented using Xamarin.Forms. The second project template creates a shared project that is included and compiled into the platform-specific projects. In this scenario, since we are technically not dealing with a platform-agnostic implementation (that is, implementations in the shared project are directly compiled into the referencing projects), developers are free to use platform-specific features, given that the compilation conditions are used for appropriate platforms. The easiest way to demonstrate the difference between the two approaches would be to re-implement the notification service from the previous section without dependency injection. In the previous example, we needed to create an abstraction of the notification feature to be used in common views and inject the implementation from platform-specific projects in the runtime. In the case of a shared project, we can implement the same feature using conditional compilation: public class NotificationService { public void Notify(string message) { if (!string.IsNullOrWhiteSpace(message)) #if __IOS__ var okAlertController = UIAlertController.Create("Notification", message, UIAlertControllerStyle.Alert); okAlertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null)); if (UIApplication.SharedApplication.KeyWindow != null) UIApplication.SharedApplication.KeyWindow.RootViewController .PresentViewController(okAlertController, true, null); #elif __ANDROID__ var toast = Toast.MakeText(Application.Context, message, ToastLength.Long); toast.Show(); #elif WINDOWS_PHONE MessageBox.Show(message); #endif } }
[ 944 ]
Chapter 8
In this case, each platform compilation uses a specific section of the function. We can also use other types of abstraction and partial classes or methods to create elegant implementations according to the requirements of the scenario.
Platform-specific fine-tuning
In spite of, or even because of, the fact that Xamarin.Forms tries to provide a uniform implementation layer and then translates this layer into native controls, at times developers are faced with the challenge of implementing retouches for specific platforms. These modifications vary from small changes, such as font size (because of device- and platform-dependent pixel measures) or background color, to more systematic problems, such as not having the auto-layout implementation for TableViews on the iOS platform. There are various ways to deal with this type of situation, and the Device class is generally the access point to these solutions. When dealing with common typographic controls, such as a Label or an Entry field, the simplest way to comply with the design or accessibility requirements of a specific device is to use the built-in styles available in the Device.Styles class. There are several style elements, such as BodyStyle, SubtitleStyle, and CaptionStyle, that can be used to solve common implementation problems. The style elements in this class are calculated for the current platform/device in the runtime, hence they have to be referenced by a DynamicResource XAML markup extension when dealing with markup rather than code. A simple label using the TitleStyle can be implemented as follows: var mylabel = new Label { Text = "Text for my Title", Style = Device.Styles.TitleStyle };
It can also be declared in the markup file as follows:
[ 945 ]
Xamarin.Forms
Another useful platform-specific typography-related utility is the NamedSize enumeration. The NamedSize enumeration can be used with the Device. GetNamedSize method to choose the most suitable font size in the target platform for a text field. The enumeration provides four built-in options for different scenarios: var mylabel = new Label {Text = "Text for my Title"}; // A Large font size, for titles or other important text elements mylabel.FontSize = Device.GetNamedSize(NamedSize.Large, typeof (Label));
A built-in converter can also be used to include the font size in XAML markup:
For more general implementation requirements, Device.Idiom and Device.OS provide valuable target platform information related to the type of device (desktop, phone, tablet, and so on) and the operating system of the device (Android, iOS, Windows, or Windows Phone) respectively. Currently, Windows Phone 8.1 and Windows Phone Silverlight versions cannot be differentiated using the Device.OS property. Conditional compilation can be used as a replacement for this distinction.
Additionally, the Device.OnPlatform function and its XAML extension counterpart can help developers implement platform-specific styles. The OnPlatform function uses three values for each platform and returns the appropriate value according to the Device.OS property. Visualizing a label using the OnPlatform function would look similar to the following snippet: var mylabel = new Label {Text = "Text for my Title"}; mylabel.FontSize = Device.OnPlatform( Android: 24, WinPhone: 24, iOS: 18);
Or, using the XAML markup extension, it would look like this:
[ 946 ]
Chapter 8
The Device.OnPlatform function has another overload that can be used to execute certain actions according to current operating system.
Summary
Briefly, Xamarin.Forms provides the toolset to increase code-sharing between platform-specific projects and provide developers with a uniform experience when developing UI components for these projects. The Xamarin.Forms framework, in general, proves to be indispensable, especially for cross-platform implementation where platform-dependent feature requirements are minimal. This uniform abstraction layer is responsible for rendering the platform-specific UI controls and creating native experience for the users. This layer can also be extended using various features and patterns, some of which were discussed in this chapter. We will be focusing on more re-usable view elements and implementation patterns in the next chapter. Xamarin.Forms will again be referenced in this context.
[ 947 ]
Reusable UI Patterns In this chapter, we will discuss strategies and patterns for reusing visual assets (that is, text and media resources) in cross platform projects. Furthermore, reusable assets will be iteratively explained from the localization perspective. Finally, some advanced software architectural topics about Model-View-Controller and Model-View-ViewModel patterns will be analyzed and demonstrated. This chapter is divided into the following sections: • Visual assets • Localization • Architectural patterns
Visual assets
We can classify any resource included in the project at compile time and used by the user interface as a visual asset. Visual assets can vary from simple text elements to media items (for example images, animations, videos, and so on) to be used for creating the visual elements of the user interface. Each Xamarin target platform provides different mechanisms to store and dispatch these assets. On Android and iOS, resources and their localized representations are kept in the designated Resources folder and substructures. On Windows Phone (both Silverlight and Windows Runtime), resources can be managed by using embedded resource files (that is, resw or resx).
[ 949 ]
Reusable UI Patterns
Text resources
Each Xamarin target platform uses various strategies to filter out static text resources, such as the content of a message dialog or a label, from the View implementation. Doing this helps developers separate human readable resources from code base, creating a project structure in line with the separation of concerns principle.
Xamarin.Android
On the Android platform, text resources can be stored in the strings.xml file and retrieved through code or used in declarative markups (that is, AXML files). The XML file containing the string resources can be found or created in the Resources\ values directory. There is no relevance between the filenames and how the resources are retrieved later on. The resource XML file has a simple format, where each string is defined as an XML node with an associated name as an attribute:
Fibonnaci Calculator Single Calculation Range Calculation GC Collect
The string values can later be used in markup, and also in Android declarative attributes, using the @string/ notation:
[ 950 ]
Chapter 9
In order to add an activity label for a view, the ApplicationName string can be included directly with the @string notation: [Activity(Label = "@string/ApplicationName")]
On top of the classic string resources, a collection of string resources and quantity strings can also be included in the resource XML file(s). Quantity strings are resource strings with a definition for different countable references for various scenarios with the correct pluralization rule. For instance, for an application with English as the default language, the plural quantity strings would look similar to the following (for example, a singular word for one, a plural form for others):
%d calculation was completed. %d calculations were completed.
Whereas for the Turkish language, it would look similar to the following (the same rule applies to all countable words):
%d islem tamamlandi.
Examples of this usage can be extended to Slavic languages (for example Russian, Polish, and Czech), where languages have different use cases for a small number of items or for numbers ending with certain digits. Possible switch values for quantities are zero, one, two, few, many, and other. The application of these switches follows the rules defined for language plurals in the unicode common locale data repository (see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/ language_plural_rules.html for more information). For instance, English does not require a specific handling for few items or zero items, so any rule defined for these cases will be ignored by the runtime.
[ 951 ]
Reusable UI Patterns
Once the resource XML file(s) are modified, you can see that the Resource. Designer.cs file is (re)generated with each compilation. This file contains the associated ID values for different types of resources and can be used for retrieving the resource items with the Resources utility class.
Figure 1: Generated resource constants
Using the Resources utility, text resources can be retrieved in the following ways: // Getting a single text value var singleStringValue = Resources.GetText(Resource.String.ApplicationName); // Getting a string array var stringArrayValue = Resources.GetTextArray(Resource.Array.MyStringArray); // Getting a pluralized version for 2 items var quantity = Resources.GetQuantityString (Resource.Plurals.CalculationsCompleted, 2, 2);
Additionally, other primitive data types (for example, integers, Booleans, and so on), as well as units or structs used in style definitions (for example, dimension and color) can be included in resource XML files. [ 952 ]
Chapter 9
Xamarin.iOS
On the iOS platform, the simplest way to separate the text resources from the rest of the project would be to create .strings files (for example, Localizable.strings), which follow a simple JSON-like pattern with key/value pairs: "GCCollect" = "GC Collect"; "RangeCalculation" = "Range Calculation"; "SingleCalculation"= "Single Calculation";
These string values, compiled into bundle resources, can, later on, be accessed using the NSBundle.MainBundle.LocalizedString method: var localizedString = NSBundle.MainBundle.LocalizedString ("RangeCalculation", "");
Localized string values can be used as labels for UI controls, creating a loosely-coupled relationship between the static text content and the actual runtime components. This process is referred to as internationalization in the iOS ecosystem. Internationalized controls and elements can easily be localized for different languages. Strings files can be created in the Resources folder or can be placed in the Base.lproj folder inside the Resources directory, which constitutes the base localization project folder for iOS projects (the default/fallback resources). For storyboards, the internationalization process can be a little more complicated. Each UI element in a storyboard is assigned a unique identifier called the Object ID in Xcode, while it is referred to as the Localization ID in Xamarin Storyboard Designer. In order to assign text content to a specific item on the storyboard, developers are required to create string files for each storyboard (for example, for a storyboard called Main.storyboard, you will need to create a Main.strings file) and use the localization ID of the specific control and the name of the text attribute: /* Class = "UIViewController"; title = "Single Calculation"; ObjectID = "138"; */ "138.title" = "Single Calculation"; /* Class = "UILabel"; text = "Ordinal"; ObjectID = "153"; */ "153.text" = "Ordinal"; /* Class = "UIButton"; normalTitle = "Calculate"; ObjectID = "156"; */ "156.normalTitle" = "Calculate";
As one can see, the attribute names and casings are clearly different from the actual type properties of UI controls (for example, text for UILabel, normalTitle for UIButton). The iOS internationalization guidelines can provide details on the storyboard attributes.
[ 953 ]
Reusable UI Patterns
Another way to create the base internationalization file for a storyboard is to use Xcode to generate the string file. In order to modify the Xamarin.iOS project with Xcode, the Open With context menu item can be used to select Xcode Interface Builder for a storyboard and the main project window to access the project properties.
Figure 2: Xcode Interface Builder
In the Xcode interface, the localization settings are located on the project settings page. If the base localization folder was created beforehand, the Base Localization option will already be checked in the project settings localization section.
Figure 3: Xcode project configuration
[ 954 ]
Chapter 9
Any additional language selection generates a language specific .lproj folder and the .strings file for the targeted storyboards and strings files. Once the Xcode window is closed, these changes will be reflected in the Xamarin.iOS project structure.
Windows Phone
In Windows Phone (Silverlight) projects, resources are managed through traditional resx files (a legacy of the .NET framework). The default language resources are generated with the project template and stored in the AppResources.resx file, located under the Resources folder.
Figure 4: Windows Phone resources
Additional types of content that can be embedded in the resources file are images, icons, audio, and other types of files. These files can be accessed through code and also in markup, using the generated AppResources class. Another generated class, LocalizedStrings, provides access to the resources stored in the embedded resource file(s):
In Windows Phone 8.1 (that is, Windows Runtime) and Windows 10, the applications use a resw file (called PRIResource, referring to the compilation method). Even though the format of resx and resw files is identical, resw files can only contain primitive values (that is, string values or values that can be expressed as strings). Using resw files, developers can assign style or other attribute values directly to user controls using the Uid value of the controls, similar to the internationalization of storyboards on iOS.
Figure 5: Windows Runtime PRI resources
In addition to the targeted resources, developers are still free to use simple resource strings. These resources can be accessed using the ResourceLoader class and the GetString method.
Image resources
Mobile application projects can contain media assets from external sources as well as the application bundle. In each target platform, media assets can be included in different ways.
[ 956 ]
Chapter 9
While iOS and Windows Phone do not dictate a certain location in the project tree for media assets, in Android projects, developers are obliged to include image documents in the drawable folder of the Resources directory.
Figure 6: Project structures
Similar to the text resource structure on the iOS platform, it is advisable to place language-neutral image elements (for the default language) in the Base.lproj location if you are planning to localize them in later iterations. Also, asset catalogs can be employed to simplify the management of images and their pixel-perfect alternatives for different resolutions (see the Adaptive visual assets section).
Adaptive visual assets
Adaptive UI patterns for applications targeting Xamarin platforms force developers, at times, to include variations of media assets for different resolutions and pixel densities. Even though the image resources are scaled according to the aforementioned adaptive UI metrics, the scaled images do not always result in visually pleasing displays (for example, an image resized to double the original size, to have the same physical screen dimensions on different devices, does not appear as it should).
[ 957 ]
Reusable UI Patterns
The Android platform uses the device compatibility configuration qualifiers for both image and text resource folders (that is, drawables and values), as well as other types of resources, such as layouts. In such projects, compatibility qualifiers are concatenated to the resource folder as a suffix (for example, the drawables-xhdpi folder can be used to provide images specific to extra high density device displays of approximately 320 dpi) and various default resources are added to this folder. Compatibility configuration not only deals with pixel density, but also provides selectors for mobile network related switches (that is, MCC (mobile country code) and MNC (mobile network code)), language, and region (see the Localization section), layout direction (that is, left to right or right to left), various screen size-related options, screen orientation, UI mode (related to the platform displaying the application—a car, desk, television, appliance, or watch), night mode (that is, day or night), input type-related configurations, and finally the platform API level/version. On the iOS platform, image assets can be individually suffixed to provide different versions of the same image for different resolutions and device idioms (that is, iPhone, iPod, and iPad). Device idiom values (that is, device modifiers) are used with the tilde (~) character and can identify resources for iPhone and iPod using the ~iphone suffix and resources for iPad using the ~ipad suffix. The @2x suffix, which should appear before the device modifier, is used to identify high resolution image variants. Before the introduction of Windows Phone 8.1, the Windows Phone operating system only supported four variations: WVGA (480 x 800, only used by WP 7.1), WXGA (768 x 1280), 720p (720 x 1280), and 1080p (1080 x 1920). The only way to differentiate between these resolutions was to use the App.Current.Host.ScaleFactor device configuration property (for example, a scale factor of 100 refers to WVGA and 150 refers to HD). Windows Store apps (including Windows Phone 8.1) provide an automated scaling mechanism similar to that of iOS and Android. On the Windows Phone 8.1 platform, each resource file and/or folder can be suffixed with various qualifiers to support multiple display scales, languages and regions, contrasts, and similar, to tailor a customized look and feel for different device configurations. If the qualifiers are applied to a specific file, each qualifier/value pair should be separated by an underscore and added between the filename and the extension (that is, filename. qualifiername-value_otherqualifier-value.fileextension). If the qualifiers are applied to complete folders, for each qualifier/value, a subfolder should be created (that is, resourcefolder/qualifier-value/otherqualifier-value/).
[ 958 ]
Chapter 9
For instance, see the following project path: Images/en-US/config-designer/myImage.scale-140_layoutdir-LTR.png
This can be accessed with the Images/myImage.png resource path.
Reusable assets
Managing media assets in cross platform projects, especially if you are providing variations for different device configurations, can become quite a hurdle. In order to reuse these assets for multiple platforms, linked file references can be utilized (Add | Existing Item | Add as Link).
Figure 7: Add resource as link
Using this strategy, image documents can be included in a common location for all platform-specific projects (for example, the common portable library), and only linked file references can be added to platform-specific projects.
[ 959 ]
Reusable UI Patterns
This way, image documents are not copied to multiple locations, but only compiled into different platform-specific projects.
Figure 8: Linked resources for normal and high definition in Windows Phone and Android projects
Text resources in a cross-platform project do not differ greatly between platforms, especially if the resources in question are simple string values, rather than targeted attributes for UI controls (for example, text content specified for a label or a button on a storyboard). Another observation is that most of the text resource values are handled as key/value pairs in XML format (for Windows Phone and Android) or with simple JavaScript-like notation (in iOS). Elaborating on these assumptions, we can create an automated process that evaluates a common resource file and creates/generates the resource strings for the target platforms. Considering the fact that we will use either a shared project library or a portable class library that will contain the shared code for the platform specific projects, this common project would be the most appropriate location to store the common resource strings. We can use this project to create the common resource package in the resx format.
[ 960 ]
Chapter 9
These embedded resource files, as previously mentioned, are simple XML files in which the string resources pairs are stored in nodes with the name attribute as the key and the text node as the value (the rest of the file contains the XSD schema and metadata values for code generation).
Figure 9: Resx file XML structure
Android string resources have a similar structure with less complexity and different node names (that is, resource values are represented with text nodes with the attribute name). Conversion between the two XML files is fairly simple with an XSL transformation in Visual Studio. XSL is an abbreviation for Extensible Stylesheet Language and is used for transforming XML documents from one format to another. XSLT files may utilize templates, XPath queries, and other XSL functions to process XML document content. More information can be found at http://www.w3schools.com/xsl/default.asp.
[ 961 ]
Reusable UI Patterns
To transform the resource file into the Android format, we will create an XSLT file in the same folder as the AppResources.resx file in the common project. In order to create the Android XML resource file, we need to select each element from the node and create nodes with appropriate text content and attributes inside the root node:
Now, after this step, we can use the XML menu to debug the XSLT file using the resx file:
Figure 10: XSL Transformation debug session in Visual Studio
[ 962 ]
Chapter 9
After confirming that the transformation works as expected, we can now automate this process to regenerate the strings file every time the common project is rebuilt. For this automation, we can use a third-party XML transformation command line application and add the console command as a pre-build event command line argument using the project settings. Another option would be to use the out-of-box MSBuild task XslTransformation to add a BeforeBuild target. In order to add new build targets, the csproj file needs to be modified in Visual Studio. For this purpose, the common project first needs to be unloaded using Unload Project from the project context menu, and the project file can be edited using the Edit option from the same context menu.
The XslTransformation task is a simple build task with three basic parameters for the XML file that needs to be transformed (that is, XmlInputPath), the XSL file to be used for the transformation (that is, XslInputPath), and finally the output path (that is, OutputPaths):
With this modification, every time the common project is built (with a default setup, the common project should be built before the Android project), the strings.xml file will be generated and placed into the values folder in the Android project. The same transformation approach is applicable to the iOS localized strings files. In an iOS-specific transformation, the output should be set to text and the transformation style sheet should create the key/value pairs. In order to create the lines of text for each data element in the embedded resource file, the concat function can be utilized:
[ 963 ]
Reusable UI Patterns
In this stylesheet, it is important to note that text elements (symbols), such as double quotes and carriage return (that is, line feed and end of line), are HTML encoded. Once the transformation result is confirmed, we can add another XslTransformation task to the project file as a BeforeBuild target to create the localized strings file:
Using the same implementation, the translation values containing the resx files can be transformed and used to generate localized resources for the target platforms. In addition to XSL transformations, T4 templates can also be used to generate the text resource files. Since certain build tasks (including XslTransformation) are not yet supported by xBuild and Xamarin Studio, T4 templates can provide an alternative if your main development environment is Mac OS and main development IDE is Xamarin Studio. With T4 templates, it is also possible to iterate through each file in the common resources and generate matching localization files in platform-specific projects. The next section will summarize the localization strategies on Xamarin target platforms.
[ 964 ]
Chapter 9
Localization
Localization and globalization are the two fundamental concepts of mobile applications. In the previous sections, we discussed different ways of separating visual content from the rest of the application. This process, in essence, prepares the mobile application to be localized and is generally a part of the globalization phase. Globalized applications should function the same way, independent of the culture or locale they are being executed on. During localization, developers are supposed to create language-specific resources and integrate them into the globalized applications.
Locale and culture
Locale can be defined as the umbrella term that includes all regional configurations on a specific device (or a specific application in some cases). The locale not only represents the user interface language, but also the formats used to display dates, times, numbers, and currency values. As part of the globalization effort, in Xamarin target platforms, developers first need to identify which languages are going to be supported as part of the localization effort. A mobile application, after it is published and installed by the user, should manifest the supported languages so that the user interface can be rendered either with the locale that is dictated by the operating system (if supported) or the default/fallback language of the application. The supported languages manifest is a calculated value according to the resources provided (Android) or a pre-declared manifest or project entry (iOS and Windows Phone).
[ 965 ]
Reusable UI Patterns
Windows Phone
In Windows Phone Silverlight application projects, resources for different languages can be provided using resource packages according to the naming conventions. The provided packages should then be referenced in the WMAppManifest.xml file. The easiest way to include additional language support for a Windows Phone application is to use the project properties to identify the supported cultures.
Figure 11: Project Properties for Windows Phone Silverlight application
Once the project configuration modifications are saved, Visual Studio automatically creates the associated resx files (for example, AppResources.bs.resx for Bosnian, AppResources.tr-TR.resx for Turkish) and updates the application manifest. The default language can be modified from the package manifest (that is, package. appxmanifest) or the application manifest (WMAppManifest.xml) designers.
[ 966 ]
Chapter 9
Windows Store applications (that is, Windows Phone 8.1) are globalized using folders named after the supported languages containing the resw resource files. For instance, in order to create an application that targets the same cultures as the previous example, we would need to create a folder structure and culture-specific resource files similar to the following:
Figure 12: Windows Store apps supported cultures and app bundle
Once the application package is created, you will notice that instead of a single application package, an application bundle is created and each supported culture has an associated store app package in the bundle. Application bundles are used in Windows Store applications to reduce the size of the application packages that users are going to download for specific CPU architecture (ARM, x86, or x64), display hardware (image and other media assets, optimized for different resolutions), or locale. The packaging strategy can be selected while creating application packages, but if bundling is declined, developers are required to create a different upload package for each CPU architecture they are planning to support with their applications.
[ 967 ]
Reusable UI Patterns
Xamarin.iOS
As previously explained, for Xamarin.iOS, once the additional languages are selected for the project in the Xcode development environment, generated localization folders and files are automatically added to the Xamarin.iOS project. The generated storyboard string files initially contain the possible localizable fields and the assigned values from the storyboard. Other string bundle resource files are copied with the same values from the Base.lproj folder.
Figure 13: Localized Xamarin.iOS project
When using text resource files for localization, the LocalizedString function for the MainBundle property either returns the value that matches the current user language selection or the default value defined in the Base.lproj directory. When using Visual Studio for creating and editing the strings files, it is a good idea to map the strings extension to JavaScript editor using the Options dialog and the Text Editor | File Extension section.
[ 968 ]
Chapter 9
In order to load a language-specific resource that does not match the current preferred language(s) configuration, you will need to use the localization bundle path and retrieve the localized resources using the same function on this bundle: var path = NSBundle.MainBundle.PathForResource("tr", "lproj"); NSBundle languageBundle = NSBundle.FromPath(path); var localizedString = languageBundle.LocalizedString ("RangeCalculation", "");
The native development language directory (that is, Base.lproj), as well as the language-specific folders, can also be used to store other types of bundle resources, such as image resources, storyboards, XIB files, or even language-specific Info.plist files. (The InfoPlist.strings file in a language directory can be used to override values from the application's Info.plist file, such as the application name.) It is crucial to add the supported languages to the info manifest. For localization, there are two relevant keys. The first relevant item is the Localization native development region (that is, CFBundleDevelopmentRegion) and the second key is the Localizations (that is, CFBundleLocalizations). While the native development region defines the language associated with the Base.lproj location, the localizations entries provide information about the other supported localizations.
Xamarin.Android
Localization in Xamarin.Android projects, similar to the folder structure of Windows Phone 8.1 projects, is achieved using a specific folder structure with the language code suffixed into the localized resource items (for example, drawable-tr or values-en).
Figure 14: Android localization folder structure
[ 969 ]
Reusable UI Patterns
An appropriate resource is selected in the runtime using a simple elimination algorithm that selects the correct resource file according to the locale, display density, display size, touch support, and other criteria.
Xamarin.Forms
The Xamarin.Forms portable class library project template provides the ideal environment for text resource sharing. In this setup, with a process similar to Windows Phone Silverlight projects, resx files can be used to create resource bundles that can be used to localize the cross-platform views created with Xamarin.Forms framework.
Figure 15: Localized Xamarin.Forms resources
Once the embedded resource files and their translation counterparts are added to the common PCL project, the resource entries can be accessed using the generated static class. In order for the generated class to be accessible from the platform specific implementations, the Custom Tool property of the resource file must be set to PublicResXFileCodeGenerator and the Build Action property to Embedded Resource. With Xamarin Studio or Visual Studio, the file properties window can be used to set to the correct access modifier for the resource accessors. In Visual Studio, the resource editor can also be used to correct the access modifier of resource items (that is, select Access Modifier | Public using the resource designer).
[ 970 ]
Chapter 9
In the Windows Phone runtime, the correct resource files are loaded according to the current thread culture, so the preceding implementation would automatically choose the appropriate embedded resource. However, supported languages should still be configured using the application manifest. In Xamarin.iOS, the correct resources are loaded according to the users' language preferences (not the current UI language) and supported languages should be included in the Info.plist file using the CFLocalizations entry. For the Android platform, UI language selection is taken as the identifier for the resources. The following implementation would localize the tabbed page implementation from the previous chapter: var tabbedPage = new TabbedPage(); tabbedPage.Children.Add(new ContentPage { Title = TextResources.TabItemRecent, Content = new StackLayout { // Omitted for clarity }, Icon = "social.png" });
In the preceding example, the highlighted line of code sets the accessor properties to specific resource elements. When using XAML for the same implementation, we can resort to statically bound properties using the TextResources generated class:
It is important to include the CLR namespace containing the generated resource accessor.
[ 971 ]
Reusable UI Patterns
Architectural patterns
The user interface of an application can be described as the packaging over the sum of all the moving parts underneath. As applications get more complex, the responsibilities of the user interface increase and it gets harder to package the product underneath. Leaving aside the static parts of the UI (that is, assets described in the previous sections of this chapter), it is the most volatile part of an application. In order to counteract the entropy that builds up throughout the application's lifetime, solve recurring problem patterns, and re-use modules, developers often utilize certain design patterns in their development efforts. Especially in cross-platform projects, the importance of these architectural design patterns have been proven to be irrefutable. For demonstration purposes, let's use a simple form-submit scenario. In this implementation, the users will be greeted with a form they will have to fill in. Once all the required text fields are populated by the user, he/she will submit the content using the submit button. The data is then validated and stored. The user should then be informed about the submission with a read-only screen where he/she can see the submitted and stored data.
Figure 16: The classic n-tier scenario
In an n-tier implementation, the presentation tier would be responsible for visualizing the data and would hold an instance of the API façade. The API façade would implement the business logic to validate the information and submit it to the data tier instance. The data tier would be solely responsible for communicating with the persistence store (possibly through the service layer).
[ 972 ]
Chapter 9
The event subscriptions (that is, text field changed or submit button clicked) would be implemented in the presentation layer. On successful submission, the presentation layer would pass on the current API object to a new presentation container and visualize the submitted data. Even though this approach provides a clear separation between the tiers, there are still strong ties between the layers in the hierarchy (that is, the presentation layer holds a strongly typed reference of the API and the API either reuses or creates a new instance of the data model). The application tier also creates an unnecessarily large and complex structure that should provide the required methods for all the containers and related scenarios in the presentation layer. The presentation layer still has the most responsibility in terms of event-driven implementation. If we transpose this implementation onto a Xamarin cross-platform project, we would be able to reuse the complete application tier and the data tier across platforms. However, it would still require quite a bit of re-implementation for the presentation layer for other platform projects, as this layer is responsible for using the API. Another downside of this pattern is the fact that, other than the façade, it isn't easy to unit test the implementation (that is, there are multiple event subscriptions on the presentation layer). MVP (model-view-presenter) and MVVM (model-view-viewmodel), both derivatives of MVC (model-view-controller), try to answer some of these issues for classic n-tier implementations. Both of these patterns inherently use a passive presentation layer and delegate the main responsibility to the supervising or mediating component; the main reason for this is the fact that unit testing the view is generally impractical, hence it should be devoid of logic as much as possible. The presenter communicates with the data tier actively and is responsible for how the view should be visualized. In this paradigm, the only way the view communicates with the mediator is through routed events (separation of concerns). It is also important to note that in these architectural implementations, the application is divided into self-sufficient triads (that is, model, view, and presenter) which make up different use-cases and views in the application. Façades are generally used only in the model component.
MVC
The MVC pattern was initially introduced into Smalltalk, and later on popularized with its (excessive) use in web applications and frameworks. In classic MVC implementation, the Model not only provides access to a data store but also implements any required business logic. The Model can be described as the core implementation of the problem domain, independent of the user interface.
[ 973 ]
Reusable UI Patterns
The Controller generally represents the logic stripped out of the View; it can send commands to the Model as well as the View, and receive the routed events from the View. Changes in state (that is, in the Model), are reflected on the View with or without the intervention of the Controller (classic MVC allows active or implicit interactions between the Model and the View).
iOS app architecture
In iOS applications, the main development language hitherto has been Objective-C. The Cocoa and Cocoa Touch frameworks used for Mac OS and iOS application development, respectively, were also developed mainly in Objective-C. Considering the strong ties between Objective-C and SmallTalk, it is no surprise the main development pattern adopted and enforced by the iOS development kit is MVC. In the Cocoa version of MVC implementation, direct communication between the View and Model is completely abandoned (and prohibited) because of the technical requirements of the mobile application development environment, and in order to increase the reusability of model and view components. In this pattern, the Controller (also called the mediator at times) is given the main responsibility to control the flow of data between the View and the Model. From this aspect, Cocoa's implementation of MVC undeniably resembles the MVP pattern:
Figure 17: Cocoa MVC
In this implementation schema, developers are encouraged to decouple the components of the triad from each other and implement the communication between them only through the defined abstractions.
[ 974 ]
Chapter 9
The separation between the View and the Controller is generally achieved with commands, outlets, and bindings. Commands provide actionable composites that can be passed from one layer to another, and outlets are extensions of certain UI elements so that the controller can subscribe to events and control how the UI is presented according to state. When view elements are designed using XIBs or storyboards (that is, storyboards are used to generate the XIBs at compile time), the outlets are defined as access points for the View-Controller. View-Controllers do not have a direct dependency on the View, nor does the View have any knowledge of the Controller. This setup complies with the separation of concerns principle and provides a loosely coupled structure as advised. If we were to implement the scenario from the previous example, we would be exposing two outlets for the text input fields in the submit form and an outlet for the submit button. These outlets would, in return, be used by the Controller assigned to the View for subscribing to certain events, to validate and submit the data. The View-Controller (that is, UIController) is also responsible for changing how the controls are displayed (for example, validation can change the color of the text input field) and communicating user actions such as the submission of data to the Model. Navigating to another view is also the responsibility of the Controller in this case.
Figure 18: MVC demonstration on an iOS form
[ 975 ]
Reusable UI Patterns
Segue navigation between views is another possible navigation strategy when the Controller for the new View exists in the calling Controller, or the same UI controller is used for both of the views (that is, the same Controller could have been used for both submit and read-only views in the preceding example).
MVVM
MVVM (Model-View-ViewModel), a derivative of the MVP pattern, provides wellestablished boundaries between the UI, business logic, and data. After its emergence, it almost immediately became the main implementation pattern for WPF (Windows Presentation Foundation) applications. The data binding features provided by the WPF framework make up the foundation of this mediation pattern. Data binding is the terminology used to describe the mechanism which connects data visualization elements from the UI layer (that is, the controls) to other controls or data objects from other tiers. The synchronization between the two actors of the binding is maintained through various events (for example, the INotifyPropertyChanged interface is used to propagate value change events).
In this pattern, the ViewModel is the main actor, whose responsibility is to control the data flow between the View and the Model. The outlets, in this architecture, are exposed by the ViewModel and used by the View implementation (as opposed to the iOS MVC architecture). The ViewModel provides these outlets in the form of data elements that can be associated to attributes or states of the UI controls, and also as generic commands that can be used by the View controls to respond to user input.
Windows Runtime
Windows Phone applications, as well as Windows Store applications, natively support data binding for UI controls. This feature makes Windows Phone applications ideal candidates for this architecture. However, architectural elements should still be implemented by developers according to the requirements of specific projects. There are multiple (open-source or commercial) libraries that can be included as NuGet packages in development projects, including Prism (a cross-platform MVVM library, which was initially a pet project of the Microsoft patterns and practices team, but is now being maintained by the community) and MVVMCross (a cross-platform open source MVVM framework).
[ 976 ]
Chapter 9
At the core of the MVVM pattern and data binding, we can locate the implementation of a bindable base class. A bindable base provides the implementation of the INotifyPropertyChanged interface and makes it easier to identify and implement the data elements that will take part in data binding. This interface is used to route value changed events from data items and their properties to UI elements. A simple bindable base implementation would look similar to: public abstract class BindableBase : INotifyPropertyChanged { protected virtual void SetProperty(ref T property, T value, [CallerMemberName] string propertyName = null) { if (Equals(property, value)) return; property = value; OnPropertyChanged(propertyName); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } }
The implementation of this class can be used with model data items so that any change can be reflected on the UI: public class ModelData : BindableBase { private string m_Property1BackingField = string.Empty; public string Property1 { get {
[ 977 ]
Reusable UI Patterns return m_Property1BackingField; } set { SetProperty(ref m_Property1BackingField, value); } } }
Now the ModelData class can already be used as the ViewModel and its bindings provided to Property1: public MainPage() { this.InitializeComponent(); this.DataContext = new ModelData {Property1 = "Hello MVVM"}; }
The data binding to an input control on the main page would look similar to this:
In this binding scenario, we set the binding mode to TwoWay. This binding type means that any change on this property value, either on the ViewModel or on the user interface (that is, on user input), would be propagated to the UI element, or vice versa. Data bindings can be maintained using different modes. The OneTime binding is used to update the target property using the source property when the data source changes. This type of binding is generally used by read-only controls. The OneWay binding is used only to update the target property when the source property value changes, whereas the TwoWay mode is for duplex synchronization. Finally, OneWayToSource is used only to update the source property if there are any changes on the target property.
Data bindings are not limited to values from and to ViewModel properties. Bindable properties of user controls can also be bridged in this pattern. Moreover, bindable properties of user controls include behavioral and style attributes (for example, the IsEnabled property of a TextBox user controls). Additional bindable properties can be provided to intrinsic or derived user controls using attached and/or dependency properties.
[ 978 ]
Chapter 9
Command binding is another concept which provides a decoupled way to associate user action controls (for example, Button) with executable elements on the data context (that is, the ViewModel). In order for a user control to be bound to a command, the user control should implement a bindable command attribute and the ViewModel should provide an ICommand implementation of a specific action. The ICommand interface is a simple interface containing a CanExecute property, an associated CanExecuteChanged event (which is generally bound to the IsEnabled property of the user control), and the Execute method. A simple command implementation that will validate the data model from the previous example and then execute would look similar to the following implementation (note that MVVM frameworks generally provide a generic Command class, which accepts delegates and/or lambdas for Execute and CanExecute methods): public class SubmitCommand : ICommand { private readonly ModelData m_DataContext; public SubmitCommand(ModelData dataContext) { m_DataContext = dataContext; m_DataContext.PropertyChanged += (sender, args) => { if(args.PropertyName == "Property1" && CanExecuteChanged !=null) CanExecuteChanged(this, null); }; } public bool CanExecute(object parameter) { return m_DataContext.Property1.Length > 5; } public void Execute(object parameter) { // TODO: } public event EventHandler CanExecuteChanged; }
[ 979 ]
Reusable UI Patterns
With this implementation (either public or a nested class of the data model defined previously), we can initialize and expose the command when a new ModelData class is initialized: public ModelData() { Submit = new SubmitCommand(this); } public ICommand Submit { get; set; }
Finally, the binding for this command in XAML markup would look similar to:
If we were to use the MVVM pattern to implement the previous form submission scenario, we can observe the implementation of both data and command bindings. We could implement a ViewModel class that is responsible for loading and submitting a bindable data item. The view would have the bindings to the ViewModel properties and commands, as well as bindings to the data item itself.
Figure 19: MVVM implementation of the form submission scenario
[ 980 ]
Chapter 9
In this design, SubmitCommand is used both to submit the user input to the Model and to validate the form itself (using the CanExecute method). The IsReadOnly property of the ViewModel is bound to the IsReadOnly properties of the text fields and the Visibility property of the submit button (in read-only mode, instead of the submit button, the submitted label should be displayed), possibly with an IValueConverter (an interface used in two-way conversions between bound properties in data-binding scenarios). Value converters implement the IValueConverter interface to apply custom logic to the binding process. They are generally used as adapters for the CLR type of the target property and the source property (for example, if the data model property type was a string defining a certain color, we would need to convert/parse this value to SolidColorBrush or similar to assign it to visual elements' properties).
Besides the loose coupling and modularity achieved by using MVVM, the pseudo-finite automaton provided by the ViewModel allows developers to easily recreate different data states used by the view and implement unit tests without much hassle.
MVVM on Xamarin.iOS and Xamarin.Android
In Xamarin projects, in order to create a uniform structure between the applications for different platforms and maximize code sharing, it is a widely accepted implementation principle to use the MVVM pattern solution-wide. Since data bindings and commanding pattern implementations are not natively supported on iOS and Android, using an MVVM framework that supports cross-platform development with Xamarin can be a solution. It is important to mention that iOS and Cocoa have the concept of key-value observing, and a binding-like implementation can be applied to some extent.
On Xamarin.iOS and Xamarin.Android, bindings are generally provided through the extensions to UIViewController (on iOS) and Activities (on Android). In iOS, this implementation strategy transforms the View and Controller from MVC architecture into mere View implementations, while the ViewModel, conceptually, replaces the Model implementation. Bindings to the ViewModel are initialized in the application lifecycle events of the UIViewControllers and Activities.
[ 981 ]
Reusable UI Patterns
MVVM with Xamarin.Forms
The data binding feature of Xamarin.Forms is an implementation/port of the WPF data bindings, so XAML bindings are supported for both data and commands. The main difference between Xamarin.Forms and Windows Runtime is that in Windows Store applications, binding context for a user control or a container is configured using the DataContext property, whereas in Xamarin.Forms, the BindingContext property is used for the same purpose. Xamarin.Forms additionally provides generic command implementation classes (namely, Command and Command) which allow developers to expose commands without having to implement the ICommand interface in nested classes for the ViewModels.
Summary
In cross-platform projects, with or without Xamarin.Forms, it is advisable to maintain the View elements as thin and devoid of static and/or sharable elements as possible. As discussed in this chapter, each Xamarin target platform supports resource and asset management in particular ways. These methodologies can be expanded to share static resources between the platform-specific projects by using linked resources and/or using special build techniques. Architectural patterns, imposed by the platform or otherwise, can also be employed either at the beginning of the project or as the project matures through iterations. MVC and MVVM, as well as MVP, patterns help reduce the sharable logic components on the View, creating a more loosely-coupled project structure (see quality identifiers in Chapter 1, Developing with Xamarin). After having covered different aspects of the Xamarin framework and UI-related concepts, in the next part of the module, we will discuss Application Lifecycle Management (ALM)-related topics to create an efficient development pipeline for individuals or teams dealing with Xamarin projects.
[ 982 ]
ALM – Developers and QA This chapter provides an introduction to Application Lifecycle Management (ALM) and continuous integration methodologies on Xamarin cross-platform applications. As the part of the ALM process that is most relevant for developers, unit test strategies will be discussed and demonstrated, as well as automated UI testing. This chapter is divided into the following sections: • Development pipeline • Troubleshooting • Unit testing • UI testing
Development pipeline
The development pipeline can be described as the virtual production line that steers a project from a mere bundle of business requirements to the consumers. Stakeholders that are part of this pipeline include, but are not limited to, business proxies, developers, the QA team, the release and configuration team, and finally the consumers themselves. Each stakeholder in this production line assumes different responsibilities, and they should all function in harmony. Hence, having an efficient, healthy, and preferably automated pipeline that is going to provide the communication and transfer of deliverables between units is vital for the success of a project.
[ 983 ]
ALM – Developers and QA
In the Agile project management framework, the development pipeline is cyclical rather than a linear delivery queue. In the application life cycle, requirements are inserted continuously into a backlog. The backlog leads to a planning and development phase, which is followed by testing and QA. Once the production-ready application is released, consumers can be made part of this cycle using live application telemetry instrumentation.
Figure 1: Application life cycle management
In Xamarin cross-platform application projects, development teams are blessed with various tools and frameworks that can ease the execution of ALM strategies. From sketching and mock-up tools available for early prototyping and design to source control and project management tools that make up the backbone of ALM, Xamarin projects can utilize various tools to automate and systematically analyze project timeline. The following sections of this chapter concentrate mainly on the lines of defense that protect the health and stability of a Xamarin cross-platform project in the timeline between the assignment of tasks to developers to the point at which the task or bug is completed/resolved and checked into a source control repository.
[ 984 ]
Chapter 10
Troubleshooting and diagnostics
SDKs associated with Xamarin target platforms and development IDEs are equipped with comprehensive analytic tools. Utilizing these tools, developers can identify issues causing app freezes, crashes, slow response time, and other resource-related problems (for example, excessive battery usage). Xamarin.iOS applications are analyzed using the XCode Instruments toolset. In this toolset, there are a number of profiling templates, each used to analyze a certain perspective of application execution (such as the allocations template that was used in Chapter 2, Memory Management, for memory profiling). Instrument templates can be executed on an application running on the iOS simulator or on an actual device.
Figure 2: XCode Instruments
Similarly, Android applications can be analyzed using the device monitor provided by the Android SDK. Using Android Monitor, memory profile, CPU/GPU utilization, and network usage can also be analyzed, and application-provided diagnostic information can be gathered. Android Debug Bridge (ADB) is a command-line tool that allows various manual or automated device-related operations.
[ 985 ]
ALM – Developers and QA
For Windows Phone applications, Visual Studio provides a number of analysis tools for profiling CPU usage, energy consumption, memory usage, and XAML UI responsiveness. XAML diagnostic sessions in particular can provide valuable information on problematic sections of view implementation and pinpoint possible visual and performance issues:
Figure 3: Visual Studio XAML analyses
Finally, Xamarin Profiler, as a maturing application (currently in preview release), can help analyze memory allocations and execution time. Xamarin Profiler can be used with iOS and Android applications.
[ 986 ]
Chapter 10
Unit testing
The test-driven development (TDD) pattern dictates that the business requirements and the granular use-cases defined by these requirements should be initially reflected on unit test fixtures. This allows a mobile application to grow/evolve within the defined borders of these assertive unit test models. Whether following a TDD strategy or implementing tests to ensure the stability of the development pipeline, unit tests are fundamental components of a development project.
Figure 4: Unit test project templates
Xamarin Studio and Visual Studio both provide a number of test project templates targeting different areas of a cross-platform project. In Xamarin cross-platform projects, unit tests can be categorized into two groups: platform-agnostic and platform-specific testing.
[ 987 ]
ALM – Developers and QA
Platform-agnostic unit tests
Platform-agnostic components, such as portable class libraries containing shared logic for Xamarin applications, can be tested using the common unit test projects targeting the .NET framework. Visual Studio Test Tools or the NUnit test framework can be used according to the development environment of choice. It is also important to note that shared projects used to create shared logic containers for Xamarin projects cannot be tested with .NET unit test fixtures. For shared projects and the referencing platform-specific projects, platform-specific unit test fixtures should be prepared. When following an MVVM pattern, view models are the focus of unit test fixtures since, as previously explained, view models can be perceived as a finite state machine where the bindable properties are used to create a certain state on which the commands are executed, simulating a specific use-case to be tested. This approach is the most convenient way to test the UI behavior of a Xamarin application without having to implement and configure automated UI tests. While implementing unit tests for such projects, a mocking framework is generally used to replace the platform-dependent sections of the business logic. Loosely coupling these dependent components (see Chapter 8, Xamarin.Forms) makes it easier for developers to inject mocked interface implementations and increases the testability of these modules. The most popular mocking frameworks for unit testing are Moq and RhinoMocks. Both Moq and RhinoMocks utilize reflection and, more specifically, the Reflection.Emit namespace, which is used to generate types, methods, events, and other artifacts in the runtime. Aforementioned iOS restrictions on code generation make these libraries inapplicable for platform-specific testing, but they can still be included in unit test fixtures targeting the .NET framework. For platform-specific implementation, the True Fakes library provides compile time code generation and mocking features.
Depending on the implementation specifics (such as namespaces used, network communication, multithreading, and so on), in some scenarios it is imperative to test the common logic implementation on specific platforms as well. For instance, some multithreading and parallel task implementations give different results on Windows Runtime, Xamarin.Android, and Xamarin.iOS. These variations generally occur because of the underlying platform's mechanism or slight differences between the .NET and Mono implementation logic. In order to ensure the integrity of these components, common unit test fixtures can be added as linked/referenced files to platform-specific test projects and executed on the test harness. [ 988 ]
Chapter 10
Platform-specific unit tests
In a Xamarin project, platform-dependent features cannot be unit tested using the conventional unit test runners available in Visual Studio Test Suite and NUnit frameworks. Platform-dependent tests are executed on empty platform-specific projects that serve as a harness for unit tests for that specific platform. Windows Runtime application projects can be tested using the Visual Studio Test Suite. However, for Android and iOS, the NUnit testing framework should be used, since Visual Studio Test Tools are not available for the Xamarin.Android and Xamarin.iOS platforms.
.
. Figure 5: Test harnesses
The unit test runner for Windows Phone (Silverlight) and Windows Phone 8.1 applications uses a test harness integrated with the Visual Studio test explorer. The unit tests can be executed and debugged from within Visual Studio. Xamarin.Android and Xamarin.iOS test project templates use NUnitLite implementation for the respective platforms. In order to run these tests, the test application should be deployed on the simulator (or the testing device) and the application has to be manually executed.
[ 989 ]
ALM – Developers and QA
It is possible to automate the unit tests on Android and iOS platforms through instrumentation; however, these methods will be discussed in the next chapter.
In each Xamarin target platform, the initial application lifetime event is used to add the necessary unit tests: [Activity(Label = "Xamarin.Master.Fibonacci.Android.Tests", MainLauncher = true, Icon = "@drawable/icon")] public class MainActivity : TestSuiteActivity { protected override void OnCreate(Bundle bundle) { // tests can be inside the main assembly //AddTest(Assembly.GetExecutingAssembly()); // or in any reference assemblies AddTest(typeof(Fibonacci.Android.Tests.TestsSample).Assembly); // Once you called base.OnCreate(), you cannot add more assemblies. base.OnCreate(bundle); } }
In the Xamarin.Android implementation, the MainActivity class derives from the TestSuiteActivity, which implements the necessary infrastructure to run the unit tests and the UI elements to visualize the test results. On the Xamarin.iOS platform, the test application uses the default UIApplicationDelegate, and generally, the FinishedLaunching event delegate is used to create the ViewController for the unit test run fixture: public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) { // Override point for customization after application launch. // If not required for your application you can safely delete this method var window = new UIWindow(UIScreen.MainScreen.Bounds); var touchRunner = new TouchRunner(window); touchRunner.Add(System.Reflection.Assembly. GetExecutingAssembly());
[ 990 ]
Chapter 10 window.RootViewController = new UINavigationController(touchRunn er.GetViewController()); window.MakeKeyAndVisible(); return true; }
The main shortcoming of executing unit tests this way is the fact that it is not easy to generate a code coverage report and archive the test results. Neither of these testing methods provide the ability to test the UI layer. They are simply used to test platform-dependent implementations. In order to test the interactive layer, platform-specific or cross-platform (Xamarin.Forms) coded UI tests need to be implemented.
UI testing
In general terms, the code coverage of the unit tests directly correlates with the amount of shared code which amounts to, at the very least, 70-80 percent of the code base in a mundane Xamarin project. As explained in the previous chapters, one of the main driving factors of architectural patterns was to decrease the amount of logic and code in the view layer so that the testability of the project utilizing conventional unit tests reaches a satisfactory level. Coded UI (or automated UI acceptance) tests are used to test the uppermost layer of the cross-platform solution: the views.
Xamarin.UITests and Xamarin Test Cloud
The main UI testing framework used for Xamarin projects is the Xamarin.UITests testing framework. This testing component can be used on various platformspecific projects, varying from native mobile applications to Xamarin.Forms implementations, except for the Windows Phone platform and applications. Xamarin.UITests is an implementation based on the Calabash framework, which is an automated UI acceptance testing framework targeting mobile applications. Xamarin.UITests is introduced to the Xamarin.iOS or Xamarin.Android applications using the publicly available NuGet packages. The included framework components are used to provide an entry point to the native applications. The entry point is the Xamarin Test Cloud Agent, which is embedded into the native application during the compilation. The cloud agent is similar to a local server that allows either the Xamarin Test Cloud or the test runner to communicate with the app infrastructure and simulate user interaction with the application.
[ 991 ]
ALM – Developers and QA
Xamarin Test Cloud is a subscription-based service allowing Xamarin applications to be tested on real mobile devices using UI tests implemented via Xamarin.UITests. Xamarin Test Cloud not only provides a powerful testing infrastructure for Xamarin.iOS and Xamarin.Android applications with an abundant amount of mobile devices but can also be integrated into Continuous Integration workflows.
After installing the appropriate NuGet package, the UI tests can be initialized for a specific application on a specific device. In order to initialize the interaction adapter for the application, the app package and the device should be configured. On Android, the APK package path and the device serial can be used for the initialization: IApp app = ConfigureApp.Android.ApkFile("/MyApplication. apk") .DeviceSerial("") .StartApp();
For an iOS application, the procedure is similar: IApp app = ConfigureApp.iOS.AppBundle("/ MyApplication.app") .DeviceIdentifier("