Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Push Notifications for Windows Phone 7 using SQL Azure and Cloud services - Part 2/3

DZone's Guide to

Push Notifications for Windows Phone 7 using SQL Azure and Cloud services - Part 2/3

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

In the first part of this article series we created a SQL Azure database and a WCF service. In this part we will create the Windows Phone 7 application that is consuming the WCF service and receiving toast notifications.

Creating the Windows Phone 7 app

I will now create a simple WP7 app where the user can subscribe/unsubscribe to notifications for a given category. The application will consume the WCF service created in part 1 and toast notifications will be displayed.

Since this application is only created as an example for this article I keep it very simple. When the application starts it will query the WCF service for all categories and display these in a list. I will also query the service for my subscriptions so that I can display in the list which categories you are currently subscribing to. When you select a category in the list you can chose to subscribe/unsubscribe to that category. At start up a unique device ID is created and stored in the isolated storage. A HttpNotificationChannel is also created for the app which contains a unique channel URI that will be used to push notifications to this application on the specific device.

I assume that you know how to create a WP7 project in Visual Studio. I have created a Windows Phone project and given it the name NewsReader.

Adding a reference to the WCF service

Before you do this make sure that the WCF service created earlier is running. In the solution explorer right click “References” and select “Add Service Reference”. In the address field you enter the url from the browser window you got when starting the service (same URL as you used when adding the service to WCF Test Client) , enter a name in the “Namespace” field (f.ex. NewsReaderService) and click “GO”. . Click “OK” to add the service reference.

In your solution explorer you will now see the service reference that you added.

WP7 application code

The GUI for this application is located in MainPage.xaml and you can see my xaml code below

<phone:PhoneApplicationPage 
x:Class="NewsReader.MainMenuPage"
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"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">

<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="News Reader (Push Notification Example)" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="Categories" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>

<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Margin="0,0,0,113" Name="mainMenuList">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Width="460" Height="120" Click="MenuButtonClicked">
<Button.Content>
<StackPanel Orientation="Vertical" Height="80">
<StackPanel Orientation="Horizontal" Height="40">
<TextBlock Width="300" Height="40" Text="{Binding Name}" FontSize="22"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Height="40">
<TextBlock Width="200" Height="40" FontSize="22" Text="Subscribing:"/>
<TextBlock Width="100" Height="40" FontSize="22" Text="{Binding SubscribeText}"/>
</StackPanel>
</StackPanel>
</Button.Content>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Content="Unsubscribe" Height="85" HorizontalAlignment="Left" Margin="0,516,0,0" Name="unsubscribeButton" VerticalAlignment="Top" Width="233" IsEnabled="False" Click="UnsubscribeButtonClick" />
<Button Content="Subscribe" Height="85" HorizontalAlignment="Left" Margin="223,516,0,0" Name="subscribeButton" VerticalAlignment="Top" Width="233" IsEnabled="False" Click="SubscribeButtonClick" />
</Grid>
</Grid>

</phone:PhoneApplicationPage>

Next I create a class called CategoryItem.cs that I use as a place holder for the CategoryItem. It implements INotifyPropertyChanged to raise PropertyChanged events used by data binding. You can see the CategoryItem.cs code below.

using System.ComponentModel;

namespace NewsReader
{
public class CategoryItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

private string _name;
private int _categoryId;
private bool _subscribe;

public string Name
{
get { return _name; }
set { _name = value; RaisePropertyChanged("Name"); }
}

public int CateogoryId
{
get { return _categoryId; }
set { _categoryId = value; RaisePropertyChanged("CategoryId"); }
}

public bool Subscribe
{
get { return _subscribe; }
set { _subscribe = value; RaisePropertyChanged("SubscribeText"); }
}
public string SubscribeText
{
get {
return Subscribe ? "Yes" : "No";
}
}

public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}

The class MainMenuViewModel.cs contains data used by the MainPage. In this case it is an ObservebalCollection of CategoryItem that is binded to the ListBox used to display the category list.

using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;

namespace NewsReader
{
public class MainMenuViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
//Collection of categories used in the category list
private ObservableCollection<CategoryItem> _categoryItems;

public MainMenuViewModel()
{
//Create an empty collection
_categoryItems = new ObservableCollection<CategoryItem>();
//Hook up collection changed event
CategoryItems.CollectionChanged += CategoryItemsCollectionChanged;
}

public ObservableCollection<CategoryItem> CategoryItems
{
get { return _categoryItems; }

}

public void CategoryItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
foreach (CategoryItem item in e.NewItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
break;
case NotifyCollectionChangedAction.Add:
foreach (CategoryItem item in e.NewItems)
{
//Added items
item.PropertyChanged += EntityViewModelPropertyChanged;
}
break;
}
}

public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
//This will get called when the property of an object inside the collection changes
RaisePropertyChanged("CategoryItems");
}

public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}

MainPage.xaml.cs is the class that is responsible for all logic; using the NewsReaderService, populate the category list, generating device ID, registering notification channel and display toast notification while application is running. You can see the MainPage.xaml.cs code below. I have added comments to the code explaining each method instead of writing it here.

using System;
using System.Collections.ObjectModel;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Notification;

namespace NewsReader
{
public partial class MainMenuPage : PhoneApplicationPage
{
//Guid for the device
private Guid _deviceId;
//URI for this app's notification channel.
private HttpNotificationChannel _devicePushChannel;
//The referenced NewsReaderSerivce
private readonly NewsReaderService.NewsReaderServiceClient _newsReaderService = new NewsReaderService.NewsReaderServiceClient();
//View Model keeping the data for this view
private MainMenuViewModel _viewModel;
//The selected category in the category list
private CategoryItem _selectedCategory;

public MainMenuPage()
{
InitializeComponent();
//Create or reuse unique device ID
GetDeviceId();
//Create or reuse HttpNotificationChannel
GetDevicePushChannel();
//Create and set View model as DataContext
_viewModel = new MainMenuViewModel();
DataContext = _viewModel;
//Hook up loaded event
Loaded += MainMenuPageLoaded;
}

//Called when page is loaded
private void MainMenuPageLoaded(object sender, EventArgs args)
{
//Get categories from service
_newsReaderService.GetCategoriesCompleted += CategoriesReturned;
_newsReaderService.GetCategoriesAsync();
}


//Called when categories are returned from service
private void CategoriesReturned(object sender, NewsReaderService.GetCategoriesCompletedEventArgs e)
{
//Add all categories to the CategoryItem collection
foreach (var category in e.Result)
{
var item = new CategoryItem {CateogoryId = category.CategoryId, Name = category.Name};
_viewModel.CategoryItems.Add(item);
}
//When all categories are retrieved we want to get a list with categoryID
//for categories that we subscribe to
_newsReaderService.GetSubscriptionsCompleted += SubscriptionsReturned;
_newsReaderService.GetSubscriptionsAsync(_deviceId);
}

//Called when subscribed categories are returned from service
private void SubscriptionsReturned(object sender, NewsReaderService.GetSubscriptionsCompletedEventArgs e)
{
//Add subscribe flag to the CategoryItems that we subscribe to
foreach (var subscriptionId in e.Result)
{
int id = subscriptionId;
foreach (var categoryItem in
_viewModel.CategoryItems.Where(categoryItem => categoryItem.CateogoryId == id))
{
categoryItem.Subscribe = true;
}
}
}

private void GetDeviceId()
{
//First, generate a GUID on this phone and save it; reuse if it exists to keep consistent with service's data.
if (IsolatedStorageSettings.ApplicationSettings.Contains("DeviceId"))
{
//load existing
_deviceId = (Guid) IsolatedStorageSettings.ApplicationSettings["DeviceId"];
}
else
{
//Create new GUID and save it
_deviceId = Guid.NewGuid();
IsolatedStorageSettings.ApplicationSettings["DeviceId"] = _deviceId;
}
}

private void GetDevicePushChannel()
{
//Create or reuse a notification channel which will have its own URI that the web service will need to know.
_devicePushChannel = HttpNotificationChannel.Find("DevicePushChannel");

//check to see if this channel has already been created; if so, reuse
if (_devicePushChannel == null)
{
//This channel needs to be created. ChannelUriUpdated will fire upon completion.
_devicePushChannel = new HttpNotificationChannel("DevicePushChannel");
AttachNotificationHandlers();
_devicePushChannel.Open();
}
else
{
//Channel exist, we only need to attach handlers
AttachNotificationHandlers();

}
}

private void AttachNotificationHandlers()
{
//ChannelUriUpdated fires when channel is first created or the channel URI changes
_devicePushChannel.ChannelUriUpdated += DevicePushChannelUriUpdated;
//ShellToastNotificationReceived fired when Toast notification is received
//while app is running
_devicePushChannel.ShellToastNotificationReceived += ToastReceived;
}

private void DevicePushChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
{
//Make sure phone's shell knows this channel is authorized
//to receive Toast messages when app is not running
if (_devicePushChannel.IsShellToastBound == false)
{
_devicePushChannel.BindToShellToast();
}
}

private void ToastReceived(object sender, NotificationEventArgs e)
{
//Toast notification is received, invoke GUI thread and display a messagebox
Dispatcher.BeginInvoke((()=> MessageBox.Show(e.Collection["wp:Text2"], e.Collection["wp:Text1"], MessageBoxButton.OK)));
}

//Fires when a CategoryItem in the list is clicked
private void MenuButtonClicked(object sender, RoutedEventArgs e)
{
//Get the selected category from list and keep it
var item = ((sender as Button).DataContext as CategoryItem);
_selectedCategory = item;

//Enable or disable Subscribe and Unsubscribe buttons
if (_selectedCategory.Subscribe)
{
subscribeButton.IsEnabled = false;
unsubscribeButton.IsEnabled = true;
}
else
{
subscribeButton.IsEnabled = true;
unsubscribeButton.IsEnabled = false;
}
}

//Fires when the Subscribe button is clicked
private void SubscribeButtonClick(object sender, RoutedEventArgs e)
{
//Subscribe to the selected category
_newsReaderService.SubscribeToNotificationAsync(_deviceId, _devicePushChannel.ChannelUri.ToString(), _selectedCategory.CateogoryId);
UpdateListItem(true);
subscribeButton.IsEnabled = false;
unsubscribeButton.IsEnabled = true;
}

//Fires when the Unsubscribe button is clicked
private void UnsubscribeButtonClick(object sender, RoutedEventArgs e)
{
//Remove subscription from to selected category
_newsReaderService.RemoveCategorySubscriptionAsync(_deviceId, _selectedCategory.CateogoryId);
UpdateListItem(false);
subscribeButton.IsEnabled = true;
unsubscribeButton.IsEnabled = false;
}

//Update list item GUI when subscription are changed
private void UpdateListItem(bool subscribe)
{
foreach (var categoryItem in _viewModel.CategoryItems)
{
if (categoryItem.CateogoryId == _selectedCategory.CateogoryId)
{
categoryItem.Subscribe = subscribe;
break;
}
}
}
}
}

Running the application

You can now run the application and test the push notification functionality. First add some categories (you can use WCF Test Client to invoke the AddCategory method). When you start the WP7 NewsReader app you will be presented a screen that looks like the images below.

 

Click on a category and then click the Subscribe button, you have now subscribed to the category and the info is stored in your SQL Azure database. To test push notification you can invoke the PushToastNotification from WCF Test Client, you need to enter title, message and the category ID for one of the categories that you subscribe to. A toast notification will then be push to your application. In the images below you can see that the toast notification is displayed both when application is running and when the application is not running.

Part 3

In part 3 of this article series I will write about notification subscription management, content matching and notification scheduling. Continue to part 3.

You can read my blog at www.breathingtech.com and follow me on twitter @PerOla

 

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}