Cross Platform C#
Simplifying Cross-Platform Mobile App Development with Xamarin.Forms
Lots of decisions go into creating cross-platform apps. Without Xamarin.Forms, the decision process is almost too unwieldy. Here's how it can simplify your mobile development.
Over the past several years, developers have been excited by the ability of Xamarin to target various mobile platforms and share code across these platforms. Over that time, Xamarin has increased the ability to share code by sharing code in Linked Files, supporting Portable Class Libraries, introducing the Xamarin Component Store, adding support for NuGet directly into Xamarin Studio, and a number of other enhancements to their product lines.
One feature that developers have been asking for over the years is the ability to share UI code between various platforms. I know I've asked for this feature for several years. With the late spring 2014 release of Xamarin 3 and Xamarin.Forms, Xamarin has produced a cross-platform UI application-programming interface for developers.
What Is Xamarin.Forms?
Xamarin.Forms is a cross-platform UI API that allows developers to program an application for iOS, Android and Windows Phone from one code base. One of the great features of Xamarin.Forms is that it uses the existing features and controls on the platform. For example, creating a button when the application runs on Android results in an Android button and results in a UIButton when running on the iPhone. Within this API, it provides the following:
Pages
Pages are the screens of a Xamarin.Forms application. Each page is an Android Activity, iOS View Controller or a Page in Windows Phone. Here's a list of the Pages included:
- Content Page: A content page is a fairly generic container of views and controls. It contains a single view, such as a stack layout or scroll view.
- Master Detail Page: A master detail page displays information from a parent child form. It works well as a slide-out menu.
- Navigation Page. The navigation page is used to manage navigation.
- Tabbed Page. The tabbed page provides navigation via a series of tabs.
- Carousel Page. A carousel page allows for swipe gestures between subpages. This is similar in concept to a gallery.
Layouts
Layouts are containers for other views and containers. The types of layouts include:
- Stack Layout: Positions elements in a single line that may be either vertical or horizontal.
- Absolute Layout: Positions elements at absolute positions.
- Relative Layout: Positions elements based on constraints.
- Grid Layout: Displays elements in a row- and column-style layout.
- Content View: Contains a single element of content. This view isn't used much on its own, however, and is used to serve as a base class for user-defined views.
- Scroll View: An element that allows a user to scroll through the contents.
- Frame: An element that contains a single child. The frame has several display options.
Views (or Controls)
Views are the UI elements users will interact with. These UI elements can be defined programmatically in code as well as within a XAML layout. Some of the more common controls are:
- Entry: A single-line form element for textual input.
- Button: Used to begin a command from a user.
- Image: Used to display an image to a user.
- ListView: A control that displays a set of data in cells.
There are 40-plus views currently in Xamarin.Forms and many more views and classes in third-party projects. In addition, Xamarin plans to add more views in future versions of Xamarin.Forms.
Along with these features, Xamarin.Forms allows developers to use different methodologies. Both Model-View-Controller (MVC) and Model-View-ViewModel (MVVM) methodologies are supported.
Why Use Xamarin.Forms?
Choosing a technology to build an application sounds like an easy prospect, but you'll be faced with more options as you make choices along the way. Here are some of the issues that developers and companies must discuss and think through before settling on a technology:
- What type of expertise do you currently have? A company that has invested heavily in C# and the Microsoft .NET Framework would have a significant burden taking on Objective-C for iOS and Java for Android.
- How much do you want to invest for the application you're developing? Web applications tend to be lower cost to get started. As customers ask for more features, the cost to continually add platform-specific features tends to cost more money than merely adding features to a platform-specific application. Will the application fit within a Web application forever, or will end users ask for features that will result in a dead-end Web interface? What happens when an application needs to use the image processing capabilities in iOS? Will the time, effort, and money spent building a Web application end up being a sunk cost?
- What's your end-customer expectation? End customers want apps that look like all of the other apps with which they work. Giving iOS users an application that looks like some generic platform (think jQuery Mobile default themes) results in some strange looks from those users. While they won't hate the application, they won't love the application as much as if they'd been presented with a platform-looking application.
- Increasing the productivity of end users tends to be much more valuable than increasing the productivity of developers. End users outnumber developers by many times. I have a client with approximately 3,000 end users using an application I've written. A simple 5 percent increase in end-user productivity would greatly offset a 50 percent increase in developer productivity gained by using a cross-platform framework.
Based on many of these issues, I've recommended that companies build native mobile applications as opposed to Web applications or common UI frameworks. The problem is that customers will eventually ask for features that exceed the abilities of a mobile Web application. Having to throw away an existing Web application never goes over well with clients or management.
Enter Xamarin.Forms to this decision tree. Now, a developer can build an application in Xamarin.Forms and if the application outgrows the ability of the Xamarin.Forms platform, all is not lost. A developer can place a platform-specific front-end (iOS or Android), continue to use the code that has already been developed, and customers will continue to get existing and new features. This is a win for everyone.
An Example: Binding Data
Let's look at an example that takes some data from a remote data source via an asynchronous request via the HttpClient. This example will use the JSON data source I used in this article that uses the Open Weather Map data API.
Figure 1 and Figure 2 show the output in iOS and Android. Notice they have a similar look. The images show the same data within the confines of what makes them platform-specific. With iOS, the data is shown in the UITableView. With Android, the data is shown in an Android ListView.
Listing 1 shows the code used to generate the application. Note, this code was created on Xamarin Studio for the Macintosh.
Listing 1: Code for Generating the UITableView
using System; using System.Collections.Generic; using System.ComponentModel; using System.Net.Http; using System.Threading.Tasks; using Xamarin.Forms; using Newtonsoft.Json; using System.Diagnostics; namespace XamarinForms { public class App { static NavigationPage np; static ListView lv; public static Page GetMainPage () { lv = new ListView(){ RowHeight = 40 }; var stack = new StackLayout(){ VerticalOptions = LayoutOptions.FillAndExpand, Children = { lv }, }; var contentPage = new ContentPage () { Content = stack, }; np = new NavigationPage(contentPage); np.Title = "Weather"; np.BarTextColor = Color.Black; GetData(); return np; } async static void GetData() { try{ np.IsBusy = true; var d = await GetData("Knoxville, TN"); var cellTemplate = new DataTemplate(typeof(ImageCell)); cellTemplate.SetBinding(ImageCell.ImageSourceProperty, "weather[0].ImageUrl"); cellTemplate.SetBinding(ImageCell.TextProperty, "weather[0].description"); cellTemplate.SetBinding(ImageCell.DetailProperty, "temp.display"); lv.ItemTemplate = cellTemplate; lv.ItemsSource = d.list; } finally{ np.IsBusy = false; } } async static private Task<WeatherReport> GetData(string location) { string contents; WeatherReport res = new WeatherReport(); try { string Url = String.Format("http://api.openweathermap.org/data/2.5/forecast/daily?q={0}&mode=json&units=imperial&cnt=14", location.Trim()); HttpClient hc = new HttpClient(); contents = await hc.GetStringAsync(Url); res = JsonConvert.DeserializeObject<WeatherReport>(contents); } catch (System.Exception sysExc) { // Do something to log this error. throw; } return res; } } public class WeatherReport { public WeatherReport() { list = new List<WeatherDay>(); } [JsonProperty(PropertyName = "cod")] public string cod { get; set; } [JsonProperty(PropertyName = "message")] public string message { get; set; } [JsonProperty(PropertyName = "city")] public WeatherCity city { get; set; } [JsonProperty(PropertyName = "cnt")] public int cnt { get; set; } [JsonProperty(PropertyName = "list")] public List<WeatherDay> list { get; set; } } public class WeatherCity { public WeatherCity() { } [JsonProperty(PropertyName = "id")] public string id { get; set; } [JsonProperty(PropertyName = "name")] public string name { get; set; } [JsonProperty(PropertyName = "coord")] public coord location { get; set; } [JsonProperty(PropertyName = "country")] public string country { get; set; } [JsonProperty(PropertyName = "population")] public int population { get; set; } } public class coord { [JsonProperty(PropertyName = "lat")] public float lat { get; set; } [JsonProperty(PropertyName = "lon")] public float lon { get; set; } } public class WeatherDay { public WeatherDay() { weather = new List<weatherinfo>(); } [JsonProperty(PropertyName = "dt")] public int dt { get; set; } [JsonProperty(PropertyName = "temp")] public temps temp { get; set; } [JsonProperty(PropertyName = "pressure")] public float pressure { get; set; } [JsonProperty(PropertyName = "humidity")] public int humidity { get; set; } [JsonProperty(PropertyName = "weather")] public List<weatherinfo> weather { get; set; } } public class temps { string _display; float _min, _max; [JsonProperty(PropertyName = "day")] public float day { get; set; } [JsonProperty(PropertyName = "night")] public float night { get; set; } [JsonProperty(PropertyName = "min")] public float min { get{ return _min; } set{ _min = value; _display = String.Format("Max: {0} Min: {1}", _max, _min); } } [JsonProperty(PropertyName = "max")] public float max { get{ return _max; } set{ _max = value; _display = String.Format("Max: {0} Min: {1}", _max, _min); } } [JsonProperty(PropertyName = "eve")] public float eve { get; set; } [JsonProperty(PropertyName = "morn")] public float morn { get; set; } public string display { get{ return _display; } set{ _display = value; } } } public partial class weatherinfo : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; string _icon, _ImageUrl; [JsonProperty(PropertyName = "id")] public int id { get; set; } [JsonProperty(PropertyName = "main")] public string main { get; set; } [JsonProperty(PropertyName = "description")] public string description { get; set; } [JsonProperty(PropertyName = "icon")] public string icon { get{ return _icon; } set{ _icon = value; _ImageUrl = String.Format("http://openweathermap.org/img/w/{0}.png", _icon); OnPropertyChanged("icon"); } } public byte[] Image { get; set; } // Create the OnPropertyChanged method to raise the event public string ImageUrl{ get { return _ImageUrl; } } protected void OnPropertyChanged(string name) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(name)); } } } }
There are several interesting items to note in this project :
- When a Xamarin.Forms solution is created, it creates multiple projects. On the Macintosh, this means that a Xamarin.Forms project is created along with a Xamarin.iOS and Xamarin.Android project.
- When compilation occurs, the Xamarin.Forms project is compiled. The output is then added to the Xamarin.iOS and Xamarin.Android project. The code from the Xamarin.Forms project is compiled with any necessary code from the iOS and Android projects and that creates an output executable for ISO and Android. Within these projects will be platform-specific code.
- The base Xamarin.Forms project is a PCL project. You can use that fact to add PCL components to the project. In this specific example, the HttpClient class and JSON.NET from NuGet have been added to the project.
- Your existing knowledge is usable. Note the use of the existing HttpClient methods, as well as the properties and methods within JSON.NET.
Wrapping It Up
With Xamarin.Forms, developers can build applications for multiple platforms. From one code base and project, it's possible to target Android, iOS and Windows Phone and still use native controls. This is a great feature for developers.
About the Author
Wallace (Wally) B. McClure has authored books on iPhone programming with Mono/Monotouch, Android programming with Mono for Android, application architecture, ADO.NET, SQL Server and AJAX. He's a Microsoft MVP, an ASPInsider and a partner at Scalable Development Inc. He maintains a blog, and can be followed on Twitter.