Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Flavours (AppInnovation)

0.00/5 (No votes)
8 Nov 2012 1  
Starting with Recipe creation and sharing, to a full fledged Kitchen Assistant

This article is an entry in our AppInnovation Contest. Articles in this sub-section are not required to be full articles so care should be taken when voting.

Introduction

At the core, this is a cookbook application. In addition to maintaining a list of recipes, it allows the user to discover, create or share recipes. It also assists the user through the process of planning what to cook, shopping for the ingredients and the actual process of cooking.

Background

There are countless recipes available in the form of videos, blogs, embedded in apps and many other places. This is the process that started off the idea to create an app that is more than just being a cookbook.

  1. It's Friday, these are the thoughts going through my head, I would like to have something spicy, filling and greasy (tired of the controlled diet I have been (hypothetically Wink | <img src= ) following week long).
  2. I decide on Chicken Biryani Wink | <img src= . I have the option of either take-away or going to a restaurant. But having been there too many times (I know it conflicts with the controlled diet Wink | <img src= ). I would like to prepare that myself. A search online gives me the required instructions.
  3. So I embark on to prepare Chicken Biryani at home.
  4. (Potential Problem) There are too many cookbooks out there with the same or slightly varying information. Which one to follow?
  5. After reading about preparing Chicken Biryani from various sources. I pick one to follow.
  6. (Potential Problem) I read through the ingredients list and search in the Kitchen to make sure I have all the required ones.
  7. (Potential Problem) In case I am out of stock of an ingredient, I need to find an alternative or know enough to skip it safely Smile | <img src=.
  8. (Potential Problem) If I am missing a few main ones (like Chicken Smile | <img src=), then I need to go shopping.
  9. (Potential Problem) I need to prepare a list either in my head, paper or a shopping list app.
  10. (Potential Problem) Once I am back home, I start cooking. I need to check through the steps a few times to make sure that I am following the said procedure.

We do have the technology and the devices to help us through the above said process. There are applications that do help us solve each of the identified problems, but the fact that each one is a different application and the integration between them doesn't exist is a major problem.

Please note that a few more identified problems have been omitted as they haven't been implemented yet.

Flavours tries to solve all the above identified problems.

What Can Flavours Do?

  1. Contains collection of recipes
  2. Suggests recipes based on your interests (includes a Learning algorithm to guess what you may like)
  3. Allows you to create new recipes and publish them for others to use
  4. Allows you to rate and give feedback on recipes
  5. Helps create a cooking plan which contains what to cook and when
  6. Helps create shopping lists based on ingredients from the recipes selected to cook
  7. Keeps track of your groceries, so that common ingredients like salt are not added to your shopping list.
  8. Reminders when you leave from work or home
  9. Alerts when you are in the vicinity of your shopping center based on your groceries list
  10. Walks you through the cooking process, step by step, with timers for cooking and plays your favorite music while you are at it
  11. Take pictures of your creation, while you are cooking and after a recipe is done and upload to Facebook

Architecture

Flavours is available in two flavours as a Windows 8 store application and an Intel AppUp application. The services are developed using the ASP.NET Web API framework, with a SQL Server as the Database.

Windows Store application will be developed using XAML and C#. The Intel AppUp application is a WPF application.

Flavours uses Sensors

  1. Multi Touch - Gesture driven activities are used to take user input. Creating new recipes relies on gestures and other sensor’s input rather than the traditional keyboard and mouse. Pinch an object anywhere in the application to pick-it-up.
  2. GPS - Shopping reminders. User can choose to get alerts when in the vicinity of a shop. Users can also setup reminders to be triggered when they leave from work or home.
  3. Ambient Light Sensor - Adjusts the contrast of the application based on the ambient light so that the application is pleasant to look under different light conditions.
  4. Inclinometer - Tilt the right edge to navigate to the next step and tilt the left edge to navigate to the previous step.
  5. Gyro and Accelerometer - On the recipe creation page. Shaking the device is taken as a step to perform shake on the ingredients. Rotating the device is understood as mixing the ingredients and fast rotation is taken as mixing thoroughly.

Flavours Explained

Flavours combines features of a cookbook, shopping list with reminders, kitchen timers and provides a fun way of creating and sharing new recipes.

Starting the app for the first time presents the user with four different groups (Featured Recipes, Cooking Plan, Shopping List and All Recipes). New sections will be added based on user's activity, Favourites section will be added.

The first time, the Featured Recipes are selected randomly and will update based on user’s activity. Cooking Plan contains recipes picked to be prepared later. These are selected recipes that the user plans to prepare at a selected time. Shopping List is populated based on the ingredients in the cooking plan.

Preparing a Recipe Step-by-Step

On viewing a single recipe, the user can choose 'Start Preparing', and the application switches into step by step directions mode. Inclinometer is used for navigating while cooking to avoid grease marks on the screen or keyboard. The user is presented with the following screen with instructions on using tilt for navigation. The images animate to show the direction of tilt required for moving forward and moving backward.

Content removed from these screenshots. The tilt process is a storyboard that is set to run forever, i.e., restarting on completed. These instructions are shown only when Inclinometer is identified.

Using Inclinometer data in our application requires the following steps:

  1. Add a using statement for Windows.Devices.Sensors
  2. Identify if Inclinometer is present and get an instance of the Inclinometer object. - Inclinometer.GetDefault() returns an inclinometer instance. If the result is null, it means that the inclinometer is not available. Based on the presence of the inclinometer, instructions for tilt can be toggled. When the inclinometer is not available, show a message saying ‘Flavours can be fun with Sensors. Unfortunately the required sensor has not been identified.’ which disappears in a few seconds without user interaction.
  3. Set the ReportInterval on the in milliseconds. This also drives the sensitivity (Refer to MSDN for detail on the sensitivity). While we can set the interval, it cannot be guaranteed, as the device driver has the final say.
  4. Subscribe to the ReadingChanged event to get a notification when the inclination has changed.
  5. In the event handler, the argument is of type InclinometerReadingChangedEventArgs and contains information regarding the inclination in three degrees.
  6. When leaving the page or to stop reading, unsubscribe from the event and set the report interval to 0 and discard the inclinometer object.
async void _inclinometer_ReadingChanged(Inclinometer sender, InclinometerReadingChangedEventArgs args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        InclinometerReading reading = args.Reading;
        _currentRoll = reading.RollDegrees;
    });
} 

The Roll value of the tilt is saved in another variable (_prevRoll), when there is a difference beyond a threshold between the _prevRoll and _currentRoll, it is identified to be a tilt. Positive and negative values are important as they define the direction of the tilt. We get a positive value if tilted by lifting the left edge of the Ultrabook. (Based on documentation, needs to be tested).

Shopping List

On the main page, clicking/tapping on the shopping list header takes the user to a shopping list group page. Here, the user can pick a favorite shopping center and choose to be notified when around 5miles of the area. The user can add more markers like Work, Home, etc. and set up reminders for shopping when going to them or leaving them.

Note: The above screenshot is not a completed product. It hasn't been worked on with a designer's hat yet.

The user can delete shopping list items by swiping across the item. If the user chooses to add new item to the shopping list, he can do so too. This makes the app usable just as a shopping list application. When the user taps/clicks ‘add new item’ button in the app bar, a list of all available ingredients for cooking along with their images are shown, user can then pinch one of the item he/she wants to add to the shopping list and tap on the list to add it to the list. Hint text says ‘PINCH to pick-up, TAP to drop’.

Create a New Recipe

User can add recipes to the collection and share them with others or post on Facebook. From the main page, Clicking/Tapping on ‘add new recipe’ button in the app bar brings the user to this page.

The User provides Title and Description of the recipe which are the only two fields populated from on screen or a physical keyboard. The user is presented with a list of possible ingredients with images on the right. User can pick an ingredient using Pinch gesture and drop on the worktop area by Tapping on it. The following are generic steps in a recipe, and each step is identified by the user’s interaction with the sensors.

  1. Chopping (Fine, Coarse, Vertical and Horizontal) - Say, the user picked a Lettuce from the list and drops on the work area (designated area on the screen). Info cards are used to represent that the items are placed on the work area. Using gestures, the user can draw the direction of the cut. Once completed, the user picks up (Pinch) the info-card from the worktop and places it aside (this is another designated area for the recipe steps), by tapping. This action notifies that a step needs to be added to the recipe procedure and an ingredient needs to be added to the required ingredients section of the recipe.
  2. Mixing - User can pick existing items from the steps and drop on the work area. For example, if the user has already chopped a lettuce, cucumber and a tomato, they appear in the list of steps, the user can then pick them from the list of steps and place on the work area. A wobbling motion of the Ultrabook is recognized as a mixing step. When the user rotates it faster than a threshold, the step becomes ‘mix them thoroughly’.
  3. Frying - User can pick a step from the procedure or pick new ingredients (that is either frying chopped or whole ingredients). Then gently tilting the Ultrabook forward while holding the area beside the trackpad, denotes frying the selected items. To explain it better, assume that the Ultrabook is a pan and the motion of tossing objects in the pan that is required to register it as a Frying operation.
  4. Deep-frying - Action to be performed is similar to frying but the recognition threshold is higher, Requiring the user to move the Ultrabook faster.

Similar actions can be designated to other activities in the preparation process. Other identified actions include but may not be limited to Stirring, Cooking, Boiling, Grating, Grinding, Thaw, Grill, Cleaning, Microwave, Blending, Whisking, Oven, etc. For the steps in the procedure that cannot be directly interpreted from a gesture or sensor input, are provided with an icon which the user can drag on to the work area to perform. All actions that require sensors are confirmed before proceeding, giving the user a chance to cancel it if required.

Sharing Data

Recipe, ingredients and shopping lists data can be shared with other applications formatted as strings, JSON or XML. This will allow the user to continue using his/her favourite shopping list application while using the functionality of Flavours.

Sharing with other users can be done by publishing the recipe online. The app has a service (ASP.NET WEB API), that caters to the data needs of Flavours. Users can share recipes on Facebook as well.

Identifying User’s Interests Over Time

User’s activities such as selecting a recipe or going through the process of cooking a recipe are taken as inputs. Logistic classification algorithm is used to give each recipe a ‘may like rating’. The ingredients and preparation steps are used as parameters of the algorithm. User’s favorite recipes and prepared recipes form the training set. Each time the user prepares a recipe or marks one as favorite, the theta vector (constants in the algorithm that define the performance) are re-calculated with the new set of information.

The preparation process is given health rating and a factor for health is included into the suggestion process. For example, the user prefers deep fried food, but it is not a healthy choice when compared with salads, in that case both options may be presented so that the user can make a decision based on the calories and the health benefits.

Please note that more code snippets which explain sensor data calculations, will be added once the section for creating a recipe is implemented and tested. Rest of the application is normal code and hence omitted.

Which Color for Flavours?

The metro colors look good and I have been having a tough time picking one single color for the application. Here are a few icons, let me know which one you would like to see. The whole app uses the same color.

Blue - Currently set to this color.

Red - Stands out from the crowd among the tiles.

Dark Green (colors names for our identification only Smile | <img src=

Light Green

Orange

Code Usage

The following segment of code explains how Pinch gesture has been used for selection of an ingredient. This code is from Flavours User Experience Test project which was used to test the theory (that gestures can be used as events and the pinch feels okay to select) and ascertain that the gesture can be used for selection.

Generally a Tap / Click is used to select an item on the page. We have used Pinch to give the user a feeling of picking an ingredient and placing on the worktop area. Pinch gesture feels like picking grains off a surface.

To handle gestures in the application, we need to handle the Manipulation events and that is all that is required.

XAML Page

The XAML text in bold below shows the event handlers for Manipulation events.

<common:LayoutAwarePage x:Name="pageRoot"

                        x:Class="Flavours.SensorsUsagePrototype.MultiTouchPickAndRelease"

                        DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"

                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                        xmlns:data="using:Flavours.SensorsUsagePrototype.Data"

                        xmlns:common="using:Flavours.SensorsUsagePrototype.Common"

                        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

                        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

                        mc:Ignorable="d">
    <Page.Resources>
        <!-- Collection of items displayed by this page -->
        <CollectionViewSource x:Name="itemsViewSource"

                              Source="{Binding Items}"

                              d:Source="{Binding AllItems, 
                              Source={d:DesignInstance Type=data:SampleDataSource, 
                              IsDesignTimeCreatable=True}}" />
    </Page.Resources>
    <Grid Background="#FF00BCF2">
        <Grid.RowDefinitions>
            <RowDefinition Height="140" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="57*" />
                <RowDefinition Height="257*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="419*" />
                <ColumnDefinition Width="426*" />
                <ColumnDefinition Width="521*" />
            </Grid.ColumnDefinitions>
            <TextBlock HorizontalAlignment="Left"

                       Height="71"

                       Margin="46,10,0,0"

                       TextWrapping="Wrap"

                       Text="Pinch an item to Select or Pickup"

                       VerticalAlignment="Top"

                       Width="242"

                       FontSize="26"

                       TextAlignment="Center" />
            <TextBlock Grid.Column="2"

                       HorizontalAlignment="Center"

                       Height="71"

                       TextWrapping="Wrap"

                       Text="Tap below to drop item on the Worktop Area"

                       VerticalAlignment="Top"

                       Width="297"

                       FontSize="26"

                       TextAlignment="Center" />
            <TextBlock Grid.Column="1"

                       HorizontalAlignment="Center"

                       Height="71"

                       TextWrapping="Wrap"

                       Text="Currently Selected Item"

                       VerticalAlignment="Top"

                       Width="280"

                       FontSize="26"

                       Margin="77,0,69,0" />
            <ListView Grid.Row="1"

                      SelectionMode="None"

                      x:Name="ItemsList"

                      ItemsSource="{Binding Source={StaticResource itemsViewSource}}"

                      Background="{x:Null}"

                      HorizontalContentAlignment="Stretch"

                      VerticalContentAlignment="Stretch"

                      Margin="30,0,0,0">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <!--Attach event handlers for Manipulation to the Grid in the DataTemplate-->
                        <Grid ManipulationMode="Scale"

                              ManipulationDelta="Grid_ManipulationDelta_1"

                              ManipulationStarting="Grid_ManipulationStarting_1">
                            <StackPanel Orientation="Horizontal">
                                <Image Height="128"

                                       Source="{Binding Image}" />
                                <TextBlock TextWrapping="Wrap"

                                           Text="{Binding Title}"

                                           VerticalAlignment="Center"

                                           FontSize="28"

                                           Style="{StaticResource BasicTextStyle}"

                                           Margin="10,0,0,0" />
                            </StackPanel>
                        </Grid>
                    </DataTemplate>
                </ListView.ItemTemplate>
                <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem">
                        <Setter Property="Margin"

                                Value="0,10,18,2" />
                    </Style>
                </ListView.ItemContainerStyle>
            </ListView>
            <Grid Grid.Row="1"

                  Grid.Column="1"

                  DataContext="{Binding PickedUpItem}"

                  VerticalAlignment="Top"

                  Height="90"

                  HorizontalAlignment="Center"

                  Margin="0,100,0,0">
                <StackPanel Orientation="Horizontal">
                    <Image Height="90"

                           Source="{Binding Image}" />
                    <TextBlock TextWrapping="Wrap"

                               Text="{Binding Title}"

                               VerticalAlignment="Center"

                               FontSize="21.333"

                               Style="{StaticResource BasicTextStyle}"

                               Margin="4,0,0,0" />
                </StackPanel>
            </Grid>
            <Button Content="Clear Currently Selected"

                    Grid.Column="1"

                    HorizontalAlignment="Left"

                    Height="63"

                    Margin="42,267,0,0"

                    Grid.Row="1"

                    VerticalAlignment="Top"

                    Width="296"

                    FontSize="23"

                    Click="Button_Click_1" />
            <Rectangle Grid.Column="2"

                       x:Name="targetRect"

                       Fill="#7FC5C5C5"

                       Height="434"

                       Stroke="Black"

                       Width="501"

                       Grid.Row="1"

                       Tapped="targetRect_Tapped"

                       Grid.ColumnSpan="2"

                       VerticalAlignment="Top" />
            <Button Content="Clear Currently Dropped"

                    Grid.Column="2"

                    HorizontalAlignment="Left"

                    Height="63"

                    Margin="138,373,0,0"

                    Grid.Row="1"

                    VerticalAlignment="Top"

                    Width="296"

                    FontSize="23"

                    Click="Button_Click_2"

                    Grid.ColumnSpan="2" />
            <Grid Grid.Row="1"

                  Grid.Column="2"

                  DataContext="{Binding DroppedItem}"

                  VerticalAlignment="Top"

                  Margin="0,100,0,0"

                  HorizontalAlignment="Center">
                <StackPanel Orientation="Horizontal">
                    <Image Height="90"

                           Source="{Binding Image}" />
                    <TextBlock TextWrapping="Wrap"

                               Text="{Binding Title}"

                               VerticalAlignment="Center"

                               FontSize="21.333"

                               Style="{StaticResource BasicTextStyle}"

                               Margin="4,0,0,0" />
                </StackPanel>
            </Grid>
        </Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="120" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <TextBlock x:Name="pageTitle"

                       Text="{StaticResource AppName}"

                       Grid.Column="1"

                       IsHitTestVisible="false"

                       Style="{StaticResource PageHeaderTextStyle}" />
                       <Button VerticalAlignment="Center"

                    Click="Back_Button_Click_1"

                    Style="{StaticResource BackButtonStyle}"

                    Margin="36,50,36,36" />
        </Grid>
    </Grid>
</common:LayoutAwarePage>
Code Behind Page
using Flavours.SensorsUsagePrototype.Common;
using Flavours.SensorsUsagePrototype.Data;
using System;
using System.Collections.Generic;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
namespace Flavours.SensorsUsagePrototype
{
    public sealed partial class MultiTouchPickAndRelease : LayoutAwarePage
    {
        #region Fields
        private int stretchCount = 0;
        private int pinchCount = 0;
        #endregion
        #region Constructor
        public MultiTouchPickAndRelease()
        {
            this.InitializeComponent();
        }
        #endregion
        #region Overrides
        protected override void LoadState
        (Object navigationParameter, Dictionary<String, Object> pageState)
        {
            this.DefaultViewModel["Items"] = SampleDataSource.GetItemsForMultiTouch();
        }
        #endregion
        #region Events
        #region Manipulation Events
        private void Grid_ManipulationStarting_1(object sender, ManipulationStartingRoutedEventArgs e)
        {
            // Reset the counts.
            pinchCount = 0;
            stretchCount = 0;
        }
        private void Grid_ManipulationDelta_1(object sender, ManipulationDeltaRoutedEventArgs e)
        {
            // We use the Scale value to determine whether it is Pinch or Stretch operation.
            if (e.Delta.Scale > 1) // This means it was a Stretch operation. 
                                   // Or the fingers moved apart.
                stretchCount++;
            else if (e.Delta.Scale <= 1) // this means it was a Pinch operation.
                //The fingers moved towards each other. 
                pinchCount++;
            // To be sure that the operation performed is definitely a pinch, 
            // increasing the threshold by 5 here.
            // When it is a pinch operation Pick the object
            if (pinchCount > stretchCount + 5)
                this.DefaultViewModel["PickedUpItem"] = (sender as Grid).DataContext;
        }
        #endregion
        private async void targetRect_Tapped(object sender, TappedRoutedEventArgs e)
        {
            if (this.DefaultViewModel["PickedUpItem"] == null)
            {
                var md = new MessageDialog("Select an item first and then try tapping again.");
                await md.ShowAsync();
            }
            else
                this.DefaultViewModel["DroppedItem"] = this.DefaultViewModel["PickedUpItem"];
        }
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            // Clears the currently selected/ picked item.
            this.DefaultViewModel["PickedUpItem"] = null;
        }
        private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            // Clears the dropped item from the worktop area.
            this.DefaultViewModel["DroppedItem"] = null;
        }
        private void Back_Button_Click_1(object sender, RoutedEventArgs e)
        {
            // takes the user back to the main page.
            this.Frame.Navigate(typeof(MainPage));
        }
        #endregion
    }
} 

ManipulationDelta gives the relative values for that particular delta. Which means that we won't be able to decide whether it is a Pinch or a Stretch by just a single Delta's values. Sum of individual deltas is used to decide if it is a Pinch or Stretch. Cumulative property can be used, but using a cumulative in the ManipulationDelta event can fire false positives when a single finger is used as well. If the Cumulative is used from ManipulationCompleted event then the UI is not responding immediately to the user's actions. UI responds after the fingers leave the surface, i.e., when the ManipulationCompleted event is fired.

Geolocation and Bing Maps Usage

Geolocation is used in Flavours to set up reminders based on the geographic location. When the user has a few items in the shopping list, Flavours automatically reminds the user when leaving the house or from work. Optionally other types of reminders can be set up, like remind me when I am within 5miles of the groceries shop.

This is how to use geolocation data from the device and use the positions in Bing Maps. Please note that the key provided is a test key generated for this app and may not be active when you use it.

XAML Page
<common:LayoutAwarePage x:Name="pageRoot"

                        x:Class="Flavours.SensorsUsagePrototype.GeoLocationTest"

                        DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"

                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

                        xmlns:data="using:Flavours.SensorsUsagePrototype.Data"

                        xmlns:common="using:Flavours.SensorsUsagePrototype.Common"

                        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

                        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

                        xmlns:bm="using:Bing.Maps"

                        mc:Ignorable="d">
    <Grid Background="#FF00BCF2">
        <Grid.RowDefinitions>
            <RowDefinition Height="140" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="644*" />
                <ColumnDefinition Width="723*" />
            </Grid.ColumnDefinitions>
            <bm:Map Credentials="YOUR BING API KEY HERE"

                    x:Name="bingMap"

                    Height="600"

                    Width="600"

                    CopyrightPosition="BottomLeft"

                    MapType="Road"

                    ScaleBarPosition="TopLeft"

                    ViewRestriction="MapOnly"

                    ZoomLevel="10">
                <bm:Pushpin x:Name="InitialPushpin" />
                <bm:Pushpin x:Name="CurrentPushpin" />
            </bm:Map>
            <TextBlock HorizontalAlignment="Left"

                       TextWrapping="Wrap"

                       Text="{Binding Longitude}"

                       VerticalAlignment="Top"

                       FontFamily="Segoe UI"

                       FontSize="22"

                       Margin="230,90,0,0"

                       Grid.Column="1" />
            <TextBlock HorizontalAlignment="Left"

                       TextWrapping="Wrap"

                       Text="{Binding Latitude}"

                       VerticalAlignment="Top"

                       FontFamily="Segoe UI"

                       FontSize="22"

                       Margin="230,150,0,0"

                       Grid.Column="1" />
            <TextBlock HorizontalAlignment="Left"

                       TextWrapping="Wrap"

                       Text="{Binding Accuracy}"

                       VerticalAlignment="Top"

                       FontFamily="Segoe UI"

                       FontSize="22"

                       Margin="230,210,0,0"

                       Grid.Column="1" />
            <TextBlock HorizontalAlignment="Left"

                       TextWrapping="Wrap"

                       Text="{Binding Status}"

                       VerticalAlignment="Top"

                       FontFamily="Segoe UI"

                       FontSize="22"

                       Margin="230,270,0,0"

                       Grid.Column="1" />
        </Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="120" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <TextBlock x:Name="pageTitle"

                       Text="{StaticResource AppName}"

                       Grid.Column="1"

                       IsHitTestVisible="false"

                       Style="{StaticResource PageHeaderTextStyle}" /> 
           <Button VerticalAlignment="Center"

                    Click="Back_Button_Click_1"

                    Style="{StaticResource BackButtonStyle}"

                    Margin="36,50,36,36" />
        </Grid>
    </Grid>
</common:LayoutAwarePage>
Code Behind Page
using Bing.Maps;
using Flavours.SensorsUsagePrototype.Common;
using System;
using System.Collections.Generic;
using Windows.Devices.Geolocation;
using Windows.UI.Core;
using Windows.UI.Xaml;
namespace Flavours.SensorsUsagePrototype
{
    public sealed partial class GeoLocationTest : LayoutAwarePage
    {
        #region Fields
        // 1. variable for Geolocator.
        private Geolocator _geolocator = null;
        #endregion
        #region Constructor
        public GeoLocationTest()
        {
            this.InitializeComponent();
            // 2. Instantiate the Geolocator.
            _geolocator = new Geolocator();
            this.Loaded += GeoLocationTest_Loaded;
        }
        void GeoLocationTest_Loaded(object sender, RoutedEventArgs e)
        {
            // Initializing the map to show the shop location.
            var shopLocation = new Location(51.749856, -1.299348);
            MapLayer.SetPosition(InitialPushpin, shopLocation);
            bingMap.SetView(shopLocation, 12);
        }
        #endregion
        #region Overrides
        protected override void LoadState
        (Object navigationParameter, Dictionary<String, Object> pageState)
        {
            // 3. Hook event handlers on to the position changed and status changed events.
            _geolocator.PositionChanged += _geolocator_PositionChanged;
            _geolocator.StatusChanged += _geolocator_StatusChanged;
        }
        protected override void SaveState(Dictionary<string, object> pageState)
        {
            // 6. Un-hook the event handlers. Else it may cause memory leaks.
            _geolocator.PositionChanged -= _geolocator_PositionChanged;
            _geolocator.StatusChanged -= _geolocator_StatusChanged;
        }
        #endregion
        #region Events
        #region Geo Location Events
        // 4. Implement the event handler for Status changed.
        // This is fired when the device's GPS sensor's status changes.
        private async void _geolocator_StatusChanged(Geolocator sender, StatusChangedEventArgs args)
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                switch (args.Status)
                {
                    case PositionStatus.Ready:
                        this.DefaultViewModel["Status"] = 
                        "Status: Data is available and sensor is Ready.";
                        break;
                    case PositionStatus.Initializing:
                        this.DefaultViewModel["Status"] = "Status: Initializing...";
                        break;
                    case PositionStatus.NoData:
                        this.DefaultViewModel["Status"] = "Status: Data not available";
                        break;
                    case PositionStatus.Disabled:
                        this.DefaultViewModel["Status"] = 
                        "Status: Location is denied by the user or disabled.";
                        break;
                    case PositionStatus.NotInitialized:
                        this.DefaultViewModel["Status"] = 
                        "Status: Location is not initialized.";
                        break;
                    case PositionStatus.NotAvailable:
                        this.DefaultViewModel["Status"] = 
                        "Status: Location is not available.";
                        break;
                    default:
                        this.DefaultViewModel["Status"] = "Status: unknown state.";
                        break;
                }
            });
        }
        // 5. Implement the event handler for Position changed.
        // This is fired when a location change is noticed by the GPS.
        private async void _geolocator_PositionChanged
                       (Geolocator sender, PositionChangedEventArgs args)
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                var pos = args.Position;
                this.DefaultViewModel["Latitude"] = 
                "Latitude: " + pos.Coordinate.Latitude.ToString();
                this.DefaultViewModel["Longitude"] = 
                "Longitude: " + pos.Coordinate.Longitude.ToString();
                this.DefaultViewModel["Accuracy"] = 
                "Accuracy: " + pos.Coordinate.Accuracy.ToString();
                var currentLocation = new Location
                          (pos.Coordinate.Latitude, pos.Coordinate.Longitude);
                // place a marker on the map to the current location.
                MapLayer.SetPosition(CurrentPushpin, currentLocation);
                bingMap.SetView(currentLocation, 12);
            });
        }
        #endregion
        private void Back_Button_Click_1(object sender, RoutedEventArgs e)
        {
            // takes the user back to the main page.
            this.Frame.Navigate(typeof(MainPage));
        }
        #endregion        
    }
}

When a position change is notified, we can check the distance from home, work or the shop and remind the user that he/she has items in their shopping list that need to be purchased.

Next

The project is currently being ported to WPF. I will post a follow up article about code sharing and porting the application to WPF.

Source Code

You can download the source code made available from the link at the top of the page. This is NOT the application's source code, while it is the code that has been used to test individual features as a proof of concept.

You can get a Bing Maps API key free of charge at Bing Maps Portal.

Note: Using Bing Maps SDK

The project uses Bing Maps SDK and it can be downloaded from here: Visual Studio Gallery -> Bing Maps SDK

In the project's build configuration, make sure that the Active Solution Platform is NOT 'Any CPU'. Any of the other configuration which defines the platform specifically works fine (that is ARM, x86 or x64).

History

  • 20th October, 2012 - First version
  • 22nd October, 2012 - Second revision
  • 23rd October, 2012 - Third revision - added sample code for download and code snippets

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here