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
and add Property Changed implementation:
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:
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.
="1.0"="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:
="1.0"="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.
<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:
public string ErrorMessage { get; set; }
private Weather weather = new Weather();
change the GetWeather function to be private and add a second parameter;
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:
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:
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:
<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:
public MainPage()
{
InitializeComponent();
}
In MainPage.xaml bind to the click event in the XAML:
<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:
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
="1.0"="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
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
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)
{
string key = "f3748390cfea7374d3fb0580af0cf4ae";
string queryString = "http://api.openweathermap.org/data/2.5/weather?zip="
+ pZipCode + ",us&appid=" + key + "&units=imperial";
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).