Creating a Xamarin Forms App Part 8 : Consuming a RESTful Web Service

  • Part 1 : Introduction
  • Part 2 : Getting Started
  • Part 3 : How to use Xamarin Forms with Visual Studio without the Business Edition
  • Part 4 : Application Resources
  • Part 5 : Dependency Injection
  • Part 6 : View Model First Navigation
  • Part 7 : Unit Testing
  • Part 8 : Consuming a RESTful Web Service
  • Part 9 : Working with Alerts and Dialogs
  • Part 10 : Designing and Developing the User Interface
  • Part 11 : Updating to Xamarin Forms 1.3
  • Part 12 : Extending the User Interface

My Mountain Weather App is now starting to take shape but one thing that is missing right now is some real data. In this post I will be consuming data from a RESTful web service and displaying data from it in my App.

There are many freely available web services currently available that we can consume and use in our applications. For my app I want to start with a mountain weather forecast data feed. There are quite a few out there but perhaps one of the best is the new Met Office Data Point weather service, which offers a comprehensive set of data, feeds via RESTful service calls. Best of all they also provide a mountain weather forecast feed for UK mountain areas. This is what I will be focusing on in this post.

The Met office Data Point service have done a very good job of providing documentation for all their products. The documentation for the mountain forecast service can be found here. Looking at this I can see that there are essentially three data feeds each of which provides data in either xml or json format. Too keep the data download to an absolute minimum I have chosen to consume the data as json.

There are two libraries that I will need in order to get started. I will be using the HttpClient to make the requests therefore I will need to add the Microsoft HTTP Client Libraries to my shared MountainWeather PCL via NuGet.

Screen Shot 2014-11-24 at 08.06.52

I will be using Json.NET to read the json in to my .Net classes so I also need to add this via NuGet.

Screen Shot 2014-11-24 at 08.03.17

The first thing I need to do is to create .net Model classes to represent the data structures for each of the data feeds. I could make these exactly match the structure of the json objects. However looking at the json I noticed it’s not really in an ideal format to achieve this without my class structure being a bit odd. The three data feeds are:

  1. Site List Feed – The mountain area site list
  2. Capabilities Feed – Provides a summary of which results are available
  3. Mountain Area Forecast Feed – The weather forecast report for a mountain area location.

Let’s start with the Mountain Area site list feed. Looking at the documentation the json looks like this:

{
    "Locations": {
        "Location": [{
            "@id": "100",
            "@name": "Brecon Beacons"
        }, {
            "@id": "101",
            "@name": "East Highland"
        }, {
            "@id": "102",
            "@name": "Lake District"
        }, {
            "@id": "103",
            "@name": "Peak District"
        }, {
            "@id": "104",
            "@name": "Snowdonia"
        }, {
            "@id": "105",
            "@name": "West Highland"
        }, {
            "@id": "106",
            "@name": "Yorkshire Dales"
        }]
    }
}

I currently already have the Location class in the Models folder.

namespace Silkweb.Mobile.MountainWeather.Models
{
    public class Location
    {
        public int Id { get; set; }

        public string Name { get; set; }
    }
}

I need to update my IMountainWeatherSerivce to return the list of Locations asynchronously. All I need to do for this is to wrap the result in Task<> so that we can use .Net 4.5 async await keywords. The service interface for this method now looks like this:

Task<IEnumerable<Location>> GetAreas();

Now lets update the implementation of the MountainWeatherSerivce. For this I need to update the method to return Task<IEnumerable<Location>> and also use the async keyword.

public async Task<IEnumerable<Location>>  GetAreas()

Each web service call shares the same BaseUrl and I also need to pass a DataPoint API Key, which I have acquired when I registered with the Data Point Service. I can define these as string constants within the service.

private const string BaseUrL = "http://datapoint.metoffice.gov.uk/public/data/";
private const string Key = "{obtain from DataPoint}";

I’m also going to create a simple helper method that will construct the full Url. This will take just the part specific Url for a service call and then prefix it with the baseUrl and add the API key as a parameter. This method uses the HttpClient to make the web service call using the GetAsync method.

        private async Task<string> Get(string uri)
        {
            var client = new System.Net.Http.HttpClient ();

            client.BaseAddress = new Uri(BaseUrL);

            var response = await client.GetAsync(string.Format("{0}?key={1}", uri, Key));

            response.EnsureSuccessStatusCode();

            return response.Content.ReadAsStringAsync().Result;
        }

Notice this returns Task<string> and is marked with async, and the GetAsync call uses await to form the asynchronous call. The returned string is the Json I will be consuming. Now lets use this in the GetAreas method.

     public async Task<IEnumerable<Location>>  GetAreas()
        {
            string  result = await Get("txt/wxfcs/mountainarea/json/sitelist");   

            Location[] locations = null;

            var locationToken = JObject.Parse(result)["Locations"]["Location"];

            locations = locationToken.Select(location => new Location()
                { 
                    Id = (int)location["@id"], 
                    Name = (string)location["@name"] 
                }).ToArray();


            return locations;
        }

I am passing the Url part for the Locations feed to the Get method. Then I use Json.net Linq to project the Jason objects in to a list of location objects.

Now lets update the MountainAreasViewModel to consume the service asynchronously. I also need to add a property changed notification to the Areas property because it may get set after the page has been displayed; therefore it needs to call PropertyChanged to update the view.

namespace Silkweb.Mobile.MountainWeather.ViewModels
{
    public class MountainAreasViewModel : ViewModelBase
    {
        private IEnumerable<MountainAreaViewModel> _areas;
        private readonly IMountainWeatherService _mountainWeatherService;
        private readonly Func<Location, MountainAreaViewModel> _areaViewModelFactory;

        public MountainAreasViewModel(IMountainWeatherService mountainWeatherService, 
            Func<Location, MountainAreaViewModel> areaViewModelFactory)
        {
            _areaViewModelFactory = areaViewModelFactory;
            _mountainWeatherService = mountainWeatherService;
            Title = "Mountain Areas";
            SetAreas();
        }

        public IEnumerable<MountainAreaViewModel> Areas
        {
            get  { return _areas; }
            set  { SetProperty(ref _areas, value); }
        }

        private async void SetAreas()
        {
            var locations = await _mountainWeatherService.GetAreas();

            Areas = locations
                .Select(location =>  _areaViewModelFactory(location))
                .ToList();
        }
    }
}

Here I have created a SetAreas method that calls the service using async/await and then creates a list of MountainAreaViewModels using the areaViewModelFactory as before.

There’s no change required in the View, so let’s run the app and see what happens.

Screen Shot 2014-11-27 at 07.35.36

Hey! Great stuff, now I have a real list of Mountain Areas displayed from the web service. Cool!

Now I just need to do the same for the other two service calls. I create the model classes required which I place in the Models folder, which now looks like this:

Screen Shot 2014-11-27 at 19.39.01

Next I need to update the IMountainWeatherService interface for the other two service calls. The complete interface now looks like this.

namespace Silkweb.Mobile.MountainWeather.Services
{
    public interface IMountainWeatherService
    {
        Task<ForecastCapability[]> GetCapabilities();

        Task<IEnumerable<Location>> GetAreas();

        Task<ForecastReport> GetAreaForecast(int id);
    }
}

And then I update the implementation in the same way as I did for the Locations feed using the Get method.

Now let’s update the ForecatReportViewModel. At this stage I want to avoid exposing lots of properties on this view model for all the aspects of the ForecastReport. I just want to get things working and I’m only interested in displaying some basic information. So I’m just going to expose the ForecastReport as a property so I can bind to something. I will update this later when I come to looking at how I want to expose and lay this out. But for now the ForecastReportViewModel looks like this.

namespace Silkweb.Mobile.MountainWeather.ViewModels
{
    public class ForecastReportViewModel : ViewModelBase
    {
        private ForecastReport _forecastReport;

        public ForecastReport ForecastReport
        {
            get { return _forecastReport; }
            set { SetProperty(ref _forecastReport, value); }
        }
    }
}

I also need to update the ShowForecast method in the MountainAreaViewModel to set this property like this.

       private async void ShowForecast()
        {
            ForecastReport forecast = await _mountainWeatherService.GetAreaForecast(_location.Id);

            await _navigator.PushAsync<ForecastReportViewModel>(vm => 
                {
                    vm.Title = _location.Name;
                    vm.ForecastReport = forecast;
                }
            );
        }

Finally I need to update the ForecastReportView. For now I am still going to keep this very simple and just display the IssuedDate and Overview properties on the ForecastReport. This will show me clearly that it is working and displaying real data. The layout for this view I will address later.

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    x:Class="Silkweb.Mobile.MountainWeather.Views.ForecastReportView"
    xmlns:cv="clr-namespace:Silkweb.Mobile.Core.Views;assembly=Silkweb.Mobile.Core"
    Title="{Binding Title}">

    <StackLayout BindingContext="{Binding ForecastReport}" Padding="5">

        <Label Text="{Binding IssueDateTime, StringFormat='Issued at {0:ddd dd MMM yyyy} at {0:H:mm}'}" 
               Font="Bold, Micro" />
        <Label Text="{Binding Overview}" Font="Micro" />

    </StackLayout>

</ContentPage>

Notice here I am setting the BindingContext on the StackLayout to the ForecastReport. I am also using StringFormat to format the text for the Issue Date.

Now let’s run the app and see what happens when we navigate to one of the Mountain Areas.

Screen Shot 2014-11-27 at 20.00.33

Awesome!

Now I am consuming data from the DataPoint web service and displaying it in my App end to end. Cool.

But there’s one thing that I’m not entirely happy with. What if something goes wrong? What if the web service is not available? Clearly I need some exception handling around all this, and I should really inform the user if something is wrong or if the service is not available. Xamarin Forms provides the DisplayAlert method on the Page class to show dialog messages to the user. But I don’t want to work directly with the Page class in my service. What I need is a service that I can inject that will provide methods for showing alert messages. This will be the topic of my next post.