Walkthrough for Xamarin in VS2017 - Part Two
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).
using System.ComponentModel;
...
public class Core : INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string _ZipCode;
public string ZipCode
{ get
{ return _ZipCode; }
set
{ _ZipCode = value; }
}
<?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">
<?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 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>
public string ErrorMessage { get; set; }
private Weather weather = new Weather();
private static async Task<Weather> GetWeather(string pZipCode, string pErrorMessage)
public async void GetWeather()
{
weather = await 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");
}
}
<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>
public MainPage()
{
InitializeComponent();
//btnGetWeather.Clicked += btnGetWeather_Click;
}
<Button Text="Get Weather" x:Name="btnGetWeather" VerticalOptions="Start" Clicked="BtnGetWeather_Click"/>
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();
}
<?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>
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();
}
}
}
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).