Click here to Skip to main content
15,860,844 members
Articles / Mobile Apps / Xamarin

Walkthrough for Xamarin in VS2017 - Part Two

Rate me:
Please Sign up or sign in to vote.
4.60/5 (3 votes)
1 Aug 2017CPOL2 min read 15.9K   10   2
Part two of taking the Microsoft Xamarin Weather tutorial to the next level.

Introduction

In Part One we created a basic App to retrieve Weather for a given Zip (postal) code - see https://www.codeproject.com/Articles/1191947/Walkthrough-of-Xamarin-in-VS2017-Part-One

....

Where to next?  Currently the application uses code behind (C#) to take the ZipCode from the entry/edit box and performs assignment of the results to the text boxes.  However in in modern apps we would expect to Bind these text properties to our data class with XAML and let the system update the screen when the data is changed.

We will update the App to use design pattern akin to MVVM to use XAML binding.

First we need to update Core.cs to link the View (the screen XAML) to the ViewModel (core.cs). 

C#
using System.ComponentModel;

...


public class Core : INotifyPropertyChanged
and add Property Changed implementation:
C#
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
    var changed = PropertyChanged;
    if (changed != null)
    {
       PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
=> this declartion and code allows the framework to update our screen when the underlying data is changed.
 
and add a variable to hold the ZipCode and properties to access the variable:
C#
private string _ZipCode;
public string ZipCode
{ get
    { return _ZipCode; }
  set
    { _ZipCode = value; }
}
Now we update the (MainPage) XAML to populate the ZipCode.  First add a reference to the ViewModel named vm.
XML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:WeatherApp"
             xmlns:vm="clr-namespace:WeatherApp"
             x:Class="WeatherApp.MainPage">
and add a BindingContext to the Core.cs class:
XML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:WeatherApp"
             xmlns:vm="clr-namespace:WeatherApp"
             x:Class="WeatherApp.MainPage">

    <ContentPage.BindingContext>
        <vm:Core/>
    </ContentPage.BindingContext>
finally update the Text property of the edit box to be bound to the ZipCode property within the Core class.
XML
<StackLayout  Orientation="Horizontal" VerticalOptions="Start">
    <Entry WidthRequest="100" x:Name="edtZipCode"  VerticalOptions="Start" Text="{Binding ZipCode, Mode=TwoWay}"/>
    <Button Text="Get Weather" x:Name="btnGetWeather"  VerticalOptions="Start"/>
</StackLayout>
At this point the app isn't using the bound data, however you can check it is working by setting a breakpoint on the get/set methods of the ZipCode property and running.
 
Add a new variable in Core.cs to hold any error state and an instance of the Weather class:
C#
public string ErrorMessage { get; set; }
private Weather weather = new Weather();
change the GetWeather function to be private and add a second parameter;
C#
private static async Task<Weather> GetWeather(string pZipCode, string pErrorMessage)
and add a new procedure to call the original function.  As we aren't setting the individual parameters, we need to call the OnPropertyChanged events to update the screen:
C#
public async void GetWeather()
{
      weather = await GetWeather(ZipCode, ErrorMesage);

      OnPropertyChanged("Title");
      OnPropertyChanged("Temperature");
      OnPropertyChanged("Wind");
      OnPropertyChanged("Humidity");
      OnPropertyChanged("Visibility");
      OnPropertyChanged("Sunrise");
      OnPropertyChanged("Sunset");
}
 
Add properties for each of the values that we are going to display:
C#
        public string Title
        {
            get
            {
                return weather.Title;
            }
            set
            {
                weather.Title = value;
                OnPropertyChanged("Title");
            }
        }
        public string Temperature
        {
            get
            {
                return weather.Temperature;
            }
            set
            {
                weather.Temperature = value;
                OnPropertyChanged("Temperature");
            }
        }
        public string Wind
        {
            get
            {
                return weather.Wind;
            }
            set
            {
                weather.Wind = value;
                OnPropertyChanged("Wind");
            }
        }
        public string Humidity
        {
            get
            {
                return weather.Humidity;
            }
            set
            {
                weather.Humidity = value;
                OnPropertyChanged("Humidity");
            }
        }
        public string Visibility
        {
            get
            {
                return weather.Visibility;
            }
            set
            {
                weather.Visibility = value;
                OnPropertyChanged("Visibility");
            }
        }
        public string Sunrise
        {
            get
            {
                return weather.Sunrise;
            }
            set
            {
                weather.Sunrise = value;
                OnPropertyChanged("Sunrise");
            }
        }
        public string Sunset
        {
            get
            {
                return weather.Sunset;
            }
            set
            {
                weather.Sunset = value;
                OnPropertyChanged("Sunset");
            }
        }
Bind the XAML fields that display the results to these new properties:
 
XML
<StackLayout VerticalOptions="StartAndExpand">
     <Label Text ="Location" TextColor="#FFA8A8A8" FontSize="14"/>
     <Label Text ="{Binding Title}" Margin="10,0,0,10" x:Name="txtLocation"/>
     <Label Text ="Temperature" TextColor="#FFA8A8A8" FontSize="14"/>
     <Label Text ="{Binding Temperature}" Margin="10,0,0,10" x:Name="txtTemperature"/>
     <Label Text ="Wind Speed" TextColor="#FFA8A8A8" FontSize="14"/>
     <Label Text ="{Binding Wind}" Margin="10,0,0,10" x:Name="txtWind"/>
     <Label Text ="Humidity" TextColor="#FFA8A8A8" FontSize="14"/>
     <Label Text ="{Binding Humidity}" Margin="10,0,0,10" x:Name="txtHumidity"/>
     <Label Text ="Visibility" TextColor="#FFA8A8A8" FontSize="14"/>
     <Label Text ="{Binding Visibility}" Margin="10,0,0,10" x:Name="txtVisibility"/>
     <Label Text ="Sunrise" TextColor="#FFA8A8A8" FontSize="14"/>
     <Label Text ="{Binding Sunrise}" Margin="10,0,0,10" x:Name="txtSunrise"/>
     <Label Text ="Sunset" TextColor="#FFA8A8A8" FontSize="14"/>
     <Label Text ="{Binding Sunset}" Margin="10,0,0,10" x:Name="txtSunset"/>
 </StackLayout>
In MainPage.xaml.cs remove the Code Behind linking of the Button Click event:
C#
public MainPage()
{
   InitializeComponent();
   //btnGetWeather.Clicked += btnGetWeather_Click;
}
In MainPage.xaml bind to the click event in the XAML:
XML
<Button Text="Get Weather" x:Name="btnGetWeather"  VerticalOptions="Start" Clicked="BtnGetWeather_Click"/>
 
Finally, update the button clicked event to remove the code behind populating the screen fields and call the new GetWeather procedure:
 
XML
private async void btnGetWeather_Click(object sender, EventArgs e)
{

        //if (!String.IsNullOrEmpty(edtZipCode.Text))
        //{
        //    Weather weather = await Core.GetWeather(edtZipCode.Text);

        //    if (weather != null)
        //    {
        //        txtLocation.Text = weather.Title;
        //        txtTemperature.Text = weather.Temperature;
        //        txtWind.Text = weather.Wind;
        //        txtVisibility.Text = weather.Visibility;
        //        txtHumidity.Text = weather.Humidity;
        //        txtSunrise.Text = weather.Sunrise;
        //        txtSunset.Text = weather.Sunset;

        //        btnGetWeather.Text = "Search Again";
        //    }
        //}

        if (!String.IsNullOrEmpty(edtZipCode.Text))
           ((Core)BindingContext).GetWeather();
}
 
There have been a lot of changes applied, so here is a listing of the App as we are currently up to:
 
MainPage.xaml
XML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:WeatherApp"
             xmlns:vm="clr-namespace:WeatherApp"
             x:Class="WeatherApp.MainPage">

    <ContentPage.BindingContext>
        <vm:Core/>
    </ContentPage.BindingContext>
   
    <StackLayout>
        <StackLayout Margin="10,0,0,0" VerticalOptions="Start" HorizontalOptions="Start" WidthRequest="400" BackgroundColor="#545454">
            <Label Text="Weather App" x:Name="lblTitle"/>

            <StackLayout HorizontalOptions="Start" Margin="10,10,0,0" VerticalOptions="Start"  WidthRequest="400">
                <Label Text="Search by Zip Code" FontAttributes="Bold" TextColor="White" Margin="10" x:Name="lblSearchCriteria" VerticalOptions="Start"/>
                <Label Text="Zip Code" TextColor="White" Margin="10" x:Name="lblZipCode"/>
                <StackLayout  Orientation="Horizontal" VerticalOptions="Start">
                    <Entry WidthRequest="100" x:Name="edtZipCode"  VerticalOptions="Start" Text="{Binding ZipCode, Mode=TwoWay}"/>
                    <Button Text="Get Weather" x:Name="btnGetWeather"  VerticalOptions="Start" Clicked="btnGetWeather_Click"/>
                </StackLayout>
            </StackLayout>
        </StackLayout>
       
        <StackLayout VerticalOptions="StartAndExpand">
            <Label Text ="Location" TextColor="#FFA8A8A8" FontSize="14"/>
            <Label Text ="{Binding Title}" Margin="10,0,0,10" x:Name="txtLocation"/>
            <Label Text ="Temperature" TextColor="#FFA8A8A8" FontSize="14"/>
            <Label Text ="{Binding Temperature}" Margin="10,0,0,10" x:Name="txtTemperature"/>
            <Label Text ="Wind Speed" TextColor="#FFA8A8A8" FontSize="14"/>
            <Label Text ="{Binding Wind}" Margin="10,0,0,10" x:Name="txtWind"/>
            <Label Text ="Humidity" TextColor="#FFA8A8A8" FontSize="14"/>
            <Label Text ="{Binding Humidity}" Margin="10,0,0,10" x:Name="txtHumidity"/>
            <Label Text ="Visibility" TextColor="#FFA8A8A8" FontSize="14"/>
            <Label Text ="{Binding Visibility}" Margin="10,0,0,10" x:Name="txtVisibility"/>
            <Label Text ="Sunrise" TextColor="#FFA8A8A8" FontSize="14"/>
            <Label Text ="{Binding Sunrise}" Margin="10,0,0,10" x:Name="txtSunrise"/>
            <Label Text ="Sunset" TextColor="#FFA8A8A8" FontSize="14"/>
            <Label Text ="{Binding Sunset}" Margin="10,0,0,10" x:Name="txtSunset"/>
        </StackLayout>
    </StackLayout>
</ContentPage>
 
MainPage.xaml.cs
C#
using System;
using Xamarin.Forms;

namespace WeatherApp
{
    public partial class MainPage : ContentPage
    {

        public MainPage()
        {
            InitializeComponent();
        }

        private void BtnGetWeather_Click(object sender, EventArgs e)
        {
            if (!String.IsNullOrEmpty(edtZipCode.Text))
              ((Core)BindingContext).GetWeather();
        }
    }
}
 
Core.cs
C#
using System;
using System.ComponentModel;
using System.Threading.Tasks;

namespace WeatherApp
{
    public class Core: INotifyPropertyChanged
    {

        private string _ZipCode;
        public string ZipCode
        { get
            { return _ZipCode; }
          set
            { _ZipCode = value; }
        }
        public string ErrorMesage { get; set; }

        private Weather weather = new Weather();

        public async void GetWeather()
        {
            weather = await Core.GetWeather(ZipCode, ErrorMesage);

            OnPropertyChanged("Title");
            OnPropertyChanged("Temperature");
            OnPropertyChanged("Wind");
            OnPropertyChanged("Humidity");
            OnPropertyChanged("Visibility");
            OnPropertyChanged("Sunrise");
            OnPropertyChanged("Sunset");
        }

        public string Title
        {
            get
            {
                return weather.Title;
            }
            set
            {
                weather.Title = value;
                OnPropertyChanged("Title");
            }
        }
        public string Temperature
        {
            get
            {
                return weather.Temperature;
            }
            set
            {
                weather.Temperature = value;
                OnPropertyChanged("Temperature");
            }
        }
        public string Wind
        {
            get
            {
                return weather.Wind;
            }
            set
            {
                weather.Wind = value;
                OnPropertyChanged("Wind");
            }
        }
        public string Humidity
        {
            get
            {
                return weather.Humidity;
            }
            set
            {
                weather.Humidity = value;
                OnPropertyChanged("Humidity");
            }
        }
        public string Visibility
        {
            get
            {
                return weather.Visibility;
            }
            set
            {
                weather.Visibility = value;
                OnPropertyChanged("Visibility");
            }
        }
        public string Sunrise
        {
            get
            {
                return weather.Sunrise;
            }
            set
            {
                weather.Sunrise = value;
                OnPropertyChanged("Sunrise");
            }
        }
        public string Sunset
        {
            get
            {
                return weather.Sunset;
            }
            set
            {
                weather.Sunset = value;
                OnPropertyChanged("Sunset");
            }
        }

        public static async Task<Weather> GetWeather(string pZipCode, string pErrorMessage)
        {
            //Sign up for a free API key at http://openweathermap.org/appid 
            string key = "f3748390cfea7374d3fb0580af0cf4ae";
            string queryString = "http://api.openweathermap.org/data/2.5/weather?zip="
                + pZipCode + ",us&appid=" + key + "&units=imperial";
                       
            //Make sure developers running this sample replaced the API key
            if (key != "f3748390cfea7374d3fb0580af0cf4ae")
            {
                pErrorMessage = "You must obtain an API key from openweathermap.org/appid and save it in the 'key' variable.";
                return null;
            }

            try
            {
                dynamic results = await DataService.getDataFromService(queryString).ConfigureAwait(false);

                if (results["weather"] != null)
                {
                    Weather weather = new Weather();
                    weather.Title = (string)results["name"];
                    weather.Temperature = (string)results["main"]["temp"] + " F";
                    weather.Wind = (string)results["wind"]["speed"] + " mph";
                    weather.Humidity = (string)results["main"]["humidity"] + " %";
                    weather.Visibility = (string)results["weather"][0]["main"];

                    DateTime time = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);
                    DateTime sunrise = time.AddSeconds((double)results["sys"]["sunrise"]);
                    DateTime sunset = time.AddSeconds((double)results["sys"]["sunset"]);
                    weather.Sunrise = sunrise.ToString() + " UTC";
                    weather.Sunset = sunset.ToString() + " UTC";
                    return weather;
                }
                else
                {
                    pErrorMessage = (string)results["message"];
                    return null;
                }
            }
            catch (Exception ex)
            {

                pErrorMessage = ex.Message;
                return null;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            var changed = PropertyChanged;
            if (changed != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

 

There you have it to the End of Part Two. Functionally the App does not work any better/different than at the End of Part One - but our code is now using the modern techniques expected in an a Xamarin App.

Let's continue improving the App in Part Three (https://www.codeproject.com/Articles/1192813/Walkthrough-for-Xamarin-in-VS2017-Part-Three).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionPart 3 Pin
Calle M2-Sep-17 4:00
Calle M2-Sep-17 4:00 
AnswerRe: Part 3 Pin
Rodimus742-Sep-17 21:37
Rodimus742-Sep-17 21:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.