Mobile Zone is brought to you in partnership with:

Den is a DZone Zone Leader and has posted 460 posts at DZone. You can read more from them at their website. View Full User Profile

Weather alerts on your Windows Phone 7

09.15.2010
| 7895 views |
  • submit to reddit

Weather is not only about getting the current conditions and a possible forecast for the next couple of days, but also about getting alerts about critical weather conditions. You probably want to know about current flash flood warnings or tornado watches, but in most cases, weather APIs (like Google Weather API) don’t directly offer any information regarding weather warnings.

There are however ATOM feeds that show the current warnings for all 50 states and related territories. Those feeds are provided by National Oceanic and Atmospheric Administration's National Weather Service. Although the service is still in beta-stage, it works pretty well from what I’ve tested.

However, since those feeds are not in their stable version, the feed URL can possibly change. Therefore, it is not quite reasonable to hard-code it or bundle directly with the application. I’ve compiled the current list of available locations and the bound URL in a single XML file and hosted it here. In case the URLs are updated, I can easily fix one single file that is accessed by multiple instances of my application.

To start, I created a simple application UI that is composed of a main grid, divided in two rows. The first one is for the location picker (defined by a ComboBox – although it is not Metro-style, it works pretty well for now). The second one – for the list of warnings (defined by a ListBox control with a custom ItemTemplate).
The XAML for the entire page is below:

<phone:PhoneApplicationPage
    x:Class="WP7_WAlerts.MainPage" x:Name="AlertPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    shell:SystemTray.IsVisible="True" Loaded="PhoneApplicationPage_Loaded">

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <ComboBox SelectionChanged="locationList_SelectionChanged" ScrollViewer.VerticalScrollBarVisibility="Auto" ItemsSource="{Binding ElementName=AlertPage, Path=Locations}" Grid.Row="0" Height="50" Margin="10" Name="locationList">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock FontSize="30" Foreground="Black" Text="{Binding Path=Location}"></TextBlock>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>

        <ListBox ItemsSource="{Binding ElementName=AlertPage,Path=Alerts}" Grid.Row="2" Height="900">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>

                        <TextBlock Text="{Binding Path=Title}" TextWrapping="Wrap" Grid.Row="0" Foreground="Red" FontWeight="ExtraBlack" ></TextBlock>
                        <TextBlock Text="{Binding Path=Content}" TextWrapping="Wrap"  Grid.Row="1" ></TextBlock>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</phone:PhoneApplicationPage>

Notice that my controls are already bound to specific properties and have event handlers assigned (where required).

The functional part of the application is built around the main page and two additional classes – LocationAlert (for separate alerts assigned to a specific location) and LocationUnit (for storing existing locations that are registered in the XML file).

The LocationUnit contains two properties – Location and URL – since the data available through the XML file is the name of the state/territory and the URL that points to the ATOM feed that provides the alerts.

public class LocationUnit
{
    public string Location { get; set; }
    public string URL { get; set; }
}

NOTE: All classes that I am using in this project are embedded in a single namespace.

The LocationAlert instance also holds two properties, although of a different nature. Each provided feed item has a title and a summary. Therefore, I need to show the end-user the title of the alert, as well as the associated details.

public class LocationAlert
{
    public string Title { get; set; }
    public string Content { get; set; }
}

When the application loads, I need to get the data about the existing locations first – the list of territories and the associated URL. This collection will be bound to the ComboBox control, present on the main page, but only the Location property will be displayed. The actual data set is represented by an ObservableCollection<LocationUnit> instance (for easier binding) and a DependencyProperty, that will allow me to link the collection and the control.

public static readonly DependencyProperty _locations = DependencyProperty.Register("Locations", typeof(ObservableCollection<LocationUnit>), typeof(PhoneApplicationPage), new PropertyMetadata(null));
public ObservableCollection<LocationUnit> Locations
{
    get { return (ObservableCollection<LocationUnit>)GetValue(_locations); }
    set { SetValue(_locations, value); }
}

NOTE: The collections and methods described from here on are members of the main page class – due to the limited nature of this application I did not separate the helper methods in different classes.

This collection is populated by triggering the GetData method, that creates a WebClient instance that will download the state/territories list:

public void GetData()
{
    WebClient client = new WebClient();
    client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
    client.DownloadStringAsync(new Uri("http://dennisdel.com/content/alert_locations.xml"));
}

As you see, this method later on references the client_DownloadStringCompleted event handler, that is triggered when the XML file is downloaded. Once it is actually cached, I am able to read the nodes and populate the Locations collection:

void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    XDocument document = XDocument.Parse(e.Result);

    foreach (XElement element in (from c in document.Root.Elements("alert") select c))
    {
        LocationUnit unit = new LocationUnit()
        {
            Location = element.Attribute("loc").Value.ToString(),
            URL = element.Element("url").Attribute("data").Value.ToString()
        };

        Locations.Add(unit);
    }
}

A new LocationUnit instance is created for each registered location. These will be available in the ComboBox list (an ObservableCollection instance performs automatic re-binding on change).

Since the user now has a list of possible locations, I need to somehow implement capabilities to display alerts depending on the selection. Since the selection is managed by the ComboBox control, I am tying the process of updating the alerts to the locationList_SelectionChanged event handler, therefore triggered when a new item is selected.  But before I show the way I structured the event handler, I must mention that there is an actual ObservableCollection instance that stores the alerts, that is also tied to a DependencyProperty (since this will be bound to the ListBox control):

public static readonly DependencyProperty _alerts = DependencyProperty.Register("Alerts", typeof(ObservableCollection<LocationAlert>), typeof(PhoneApplicationPage), new PropertyMetadata(null));
public ObservableCollection<LocationAlert> Alerts
{
    get { return (ObservableCollection<LocationAlert>)GetValue(_alerts); }
    set { SetValue(_alerts, value); }
}

Here is what I do the selection in the ComboBox changes:

private void locationList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Alerts = new ObservableCollection<LocationAlert>();

    LocationUnit selectedLocation = (from c in Locations where c.Location== ((LocationUnit)locationList.SelectedItem).Location select c).First();

    WebClient client = new WebClient();
    client.DownloadStringCompleted +=new DownloadStringCompletedEventHandler(client_DownloadAlertsCompleted);
    client.DownloadStringAsync(new Uri(selectedLocation.URL));

}

Once again, I am using an instance of WebClient to download the string that will later on be parsed into a XML document. The client_DownloadAlertsCompleted event handler (referenced in the method above) transforms the received string in an XML document and reads the contents via an Atom10FeedFormatter, creating new LocationAlert instances for each feed item that is present.

NOTE: For this specific service, there will be at least one feed item present at all times.

void client_DownloadAlertsCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    XmlReader reader = XmlReader.Create(new StringReader(e.Result));
    SyndicationFeed feed = SyndicationFeed.Load(reader);

    Atom10FeedFormatter formatter = feed.GetAtom10Formatter();
    
    foreach (SyndicationItem item in formatter.Feed.Items)
    {
        LocationAlert alert = new LocationAlert();
        alert.Title = item.Title.Text;
        alert.Content = item.Summary.Text;

        Alerts.Add(alert);
    }
}

You might be a bit confused because by default you cannot use Atom10FeedFormatter (which is a member of System.ServiceModel.Syndication) since there is no available library reference. I’d recommend you reading this post that explains how to fix this – you are still able to use System.ServiceModel.Syndication in your Windows Phone 7 application.

Now, all I have to mention before you start the application is that the GetData method should be triggered on application startup. Also, the location collection should be initialized, since by default it is set to null:

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
    Locations = new ObservableCollection<LocationUnit>();

    GetData();
}

You’re now ready to go. Launch the application and see what you get. Pick a specific state, and you will get the available weather alerts:

You can download the project source code here.