Part 26 - Exercise: The Daily Rituals App

Sign in to queue

Description

Download the source code for this lesson at http://absolutebeginner.codeplex.com/

In this exercise we’ll build a project that combines many of the things we talked about previously into a single example.  The Daily Rituals project is essentially it's a goal-tracking app.  The idea is, for 30 days, you'll track your progress against all the goals you've created. 

clip_image002

For example, I'll create a new ritual or goal for myself.  I’ll click the + Add icon in the Command Bar …

clip_image004

  Which allows me to add a new Ritual.  I’ll create dummy data for this demo, the Goal Name “Ritual 1” and the Goal Description: “Description of my new ritual”, and I will click the Add button …

clip_image006

I’ll return to the main screen where I can see a list of rituals.  (I’ve added a second one in the screen shot, below):

clip_image008

I can click the “I Did This Today” button which will keep track of the days I completed this goal and it will show progress on a Progress Control below the Ritual.  The progress bar will fill up over 30 days of establishing these daily rituals.

I’ll begin by (1) creating a new Blank App project (2) called Daily Rituals.  (3) I’ll click the OK button on the New Project dialog to create the project.

clip_image010

First, I’ll create a new folder called DataSource:

clip_image012

Then right-click to Add > New Item … in that folder.

In the Add New Item dialog, (1) I’ll add a Class (2) named DataSource.cs then (3) click the Add button:

clip_image014

First, I’ll create a simple Ritual class using the prop code snippet to create the auto-implemented properties.  I’ll also add the proper using statement for the ObservableCollection<T> using the Control + [dot] keyboard shortcut.

clip_image016

Second, I’ll flesh out the DataSource class adding a private field of type ObservableCollection<Ritual> called _rituals.  I’ll create a new instance of ObservableCollection<Ritual> in the constructor.  As you may anticipate, the rest of the DataSource class will define methods that operate on this private field.

clip_image018

We’ll follow the pattern we’ve seen several times before now, creating a helper method called ensureDataLoaded() that will determine whether or not the _rituals collection contains instances of Ritual.  If not, then we’ll call the (yet to be implemented) getRitualDataAsync() method.

clip_image020

We delegate the responsibility of retrieving data from a serialized file back into an object graph stored in the _rituals collection to the getRitualDataAsync() method.  We’ve seen this code before when working with serialized types that we need to “re-hydrate” back into an object graph.  Most of the heavy lifting is performed by the DataContractJsonSerializer and the ApplicationData.Current.LocalFolder.OpenStreamForReadAsync() method.

clip_image022

There are several things we’ll have to fix with this code, including the creation of a constant for the fileName which I’ll define near the top of the DataSource class:

clip_image024

… and I’ll also need to add various using statements to satisfy all of the class references:

clip_image026

Next, I’ll implement the corollary to the getRitualDataAsync(), the saveRitualDataAsync() method which will be delegated the responsibility of persisting / “de-hydrating” the object graph stored in the _rituals field to disk.  Here again, most of the heavy lifting is performed by the DataContractJsonSerializer and the Application.Current.LocalFolder.OpenStreamForWriteAsync() method.

clip_image028

We will want to allow one of our pages to add new Rituals to the _rituals collection.  Therefore, we’ll implement a public method that will do that:

clip_image030

Furthermore, we’ll want to retrieve the _rituals collection:

clip_image032

We have enough of the data model in place.  Next we can focus on the AddRitual.xaml page.  I’ll right-click the project name and select Add > New Item … to display the Add New Item dialog.  I’ll (1) select a Blank Page, (2) name it AddRitual.xaml, and (3) click the Add button:

clip_image034

Before we begin work on the new page, we’ll need to be able to reference the DataSource class from the entire project.  To do so, (1) in the App.xaml.cs, (2) I’ll create a public static field called DataModel (3) which will necessitate the need to add a using statement to the DataModel namespace.

clip_image036

In the App() class’ constructor, I’ll create a new instance of DataSource().

clip_image038

Now, that I have a way to get at the DataModel, I’ll wire up the data context for the MainPage.xaml.  Up to now, we’ve done this by creating an instance of the DataSource class and holding a reference to it in a public read only property that we bind to declaratively.  However, this time I decided to do something different.  Here I’m calling App.DataModel.GetRituals() to get an ObservableCollection<Ritual> and set the DataContext property of the page.  Using this technique, there’s no additional work to be done in XAML.

clip_image040

In the MainPage.xaml, I add RowDefinitions, then the title TextBlocks:

clip_image042

Below that, I add an inner Grid containing an ItemsControl.  This will give the Grid the binding behavior of a ListView.  Notice that the ItemTemplate has a blue-squiggly line beneath it … that’s because we haven’t created the ItemTemplate yet:

clip_image044

Next, we’ll add the ItemTemplate called dataTemplate.  We implement it as a child to the Grid in the Grid’s Resources property.  It consists of a StackPanel that arranged two TextBlocks vertically.  The TextBlocks are bound to the Name and Description of a given Ritual object:

clip_image046

We’ll add a Button control to the DataTemplate.  We’ll utilize the Button later, but for now it’s just for layout purposes:

clip_image048

In order to add a new Ritual, we’ll need a CommandBar with a Primary Command Button.  To begin, put your mouse cursor in the Page element (top or bottom) and in the Properties window, select the New button next to BottomAppBar:

clip_image050

This will create a CommandBar object as a child to the Page’s BottomAppBar property:

clip_image052

Put your mouse cursor in the CommandBar element and in the Properties pane click the ellipsis button next to the PrimaryCommands property:

clip_image054

This will display an editor allowing you to add app bar buttons.  Click the Add button to create your first app bar button:

clip_image056

The Properties pane on the right allows you to modify properties of the selected app bar button on the left.  Here we’ll change the label to: Add Ritual …

clip_image058

… and the Icon to (1) an Icon (2) set to the + Add symbol:

clip_image060

To close the editor dialog, click the OK button at the bottom.  This will have created an AppBarButton element.  We’ll add a Name and Click attribute like so:

<AppBarButton Icon="Add" Label="Add Ritual" Name="AddRitual" Click="AddRitual_Click"/>

Put your mouse cursor in the AddRitual_Click event handler name and select the F12 keyboard key to create a method stub.  When a user clicks the Add button in the command bar, we want to navigate to our new AddRitual.xaml page, so we’ll add the following code:

        private void AddRitual_Click(object sender, RoutedEventArgs e)
        {
          Frame.Navigate(typeof(AddRitual));
        }

Next, in the AddRitual.xaml page, I’ll paste in the following XAML inside of the Grid element:

        <TextBlock HorizontalAlignment="Left"
                   Margin="34,161,0,0"
                   TextWrapping="Wrap"
                   Text="Goal Description:"
                   VerticalAlignment="Top"/>
       
        <TextBox x:Name="goalDescriptionTextBox"
                 HorizontalAlignment="Left"
                 Margin="34,170,0,0"
                 TextWrapping="Wrap"
                 VerticalAlignment="Top"
                 Height="125"
                 Width="332"/>
       
        <TextBox x:Name="goalNameTextBox"
                 HorizontalAlignment="Left"
                 Margin="34,98,0,0"
                 TextWrapping="Wrap"
                 VerticalAlignment="Top"
                 Width="332"/>
       
        <TextBlock HorizontalAlignment="Left"
                   Margin="34,89,0,0"
                   TextWrapping="Wrap"
                   Text="Goal Name:"
                   VerticalAlignment="Top"/>
       
        <Button x:Name="addButton"
                Content="Add"
                HorizontalAlignment="Left"
                Margin="34,322,0,0"
                VerticalAlignment="Top"
                Click="addButton_Click" />

Which creates the following preview:

clip_image062

Next, we’ll need to write code to handle the click event of the Add button.  We’ll want to (a) add the new Ritual to the collection of rituals in memory, and save it to the Phone’s storage, then (b) navigate back to the MainPage.xaml.  Put your mouse cursor on the addButton_Click event handler name and press the F12 key on your keyboard to create the method stub.  I’ll add the following two lines of code to the method:

clip_image064

Now when I run the app, I can click the Add button in the command bar:

clip_image066

… I can enter a Goal Name and Goal Description, then click the Add button:

clip_image068

And when I return to the MainPage.xaml, I should be able to see the ritual I just added.

clip_image070

To ensure that we’re saving the data to the Phone’s local storage, I’ll go to Visual Studio while the app is running in the emulator and select the drop down list of Lifecycle Events and choose “Suspend and shutdown”:

clip_image072

When I re-start the app, the new ritual should still be present.

Next, we’ll want to wire up  the “I Did This Today” button so that, when a user clicks that button, it will record today’s date for the associated Ritual.

To begin, I’ll add a new Command folder:

clip_image074

Then I’ll add a new class.  In the Add New Item dialog, (1) select Class, (2) rename to CompletedButtonClick, then (3) select the Add button:

clip_image076

In the new CompletedButtonClick class, you’ll make it implement the ICommand interface like we learned in a previous lesson.  To accomplish this, first you’ll need to add a using statement using the Control + [dot] technique, then selecting the first option from the Intellisense menu:

clip_image078

Then you’ll need to use the Control + [dot] technique a second time to display the Intellisense option to “Implement interface ‘ICommand’”.

clip_image080

Once you’ve selected this, the CanExecuteChanged event, as well as the CanExecute() and Execute() methods will be stubbed out for you:

clip_image082

Before we develop our own implementation of the Execute() method, we’ll need to create a method that our Execute() method will call in our DataSource.  To that end, I want to create a method that will accept an instance of Ritual, add today’s date to the Dates property (an ObservableCollection<Date>) and then save that change to the Phone’s storage.  So, I implement the CompleteRitualToday() method as follows:

clip_image084

We’ll need to implement the AddDate() method for a given Ritual.  I’ll add the AddDate() method to the Ritual class definition:

clip_image086

Now, back in the CompletedButtonClick class, I can (1) comment out the stubbed out code and simply return true always when asked whether this command can be executed, and (2) call the CompleteRitualToday() method I just implemented a moment ago in the DataSource class, passing in the input parameter and casting it to a Ritual.  (3) I will have to make sure that I add a using statement for the DataModel namespace by using the Control + [dot] keyboard shortcut to display the Intellisense menu option:

clip_image088

Now that we’ve implemented the CompletedButtonClick command, we’ll need to add it as a property to our Ritual class.  Therefore, any user interface property that binds to Ritual can invoke the command.  We’ll call our property CompletedCommand which is of type ICommand.  Naturally, we’ll need to add the using statement for System.Windows.Input using the Control + [dot] keyboard shortcut to display the Intellisense menu option:

clip_image090

In the Ritual class’ constructor, we’ll need to create a new instance of CompletedbuttonClick and set it to our new CompletedCommand property.  In this way, we wire up the property called by the user interface to the CompletedButtonClick class we created.  Again, we’ll need to add a using statement to our DailyRitual.Commands namespace using the technique Control + [dot] keyboard shortcut.

clip_image092

Finally, in the constructor I’ll need to create a new instance of ObservableCollection<DateTime> and set it equal to Dates to properly initialize that property as well:

clip_image094

Now that I have everything in my data model wired up correctly for the new Command, I can use it in my XAML.  I’ll update the MainPage’s CompletedButton by binding the Command to the CompletedCommand of the Ritual and binding the CommandParameter to the Ritual itself so that it gets properly sent into the Command and we know which Ritual the invoked command belongs to:

clip_image096

However, when we run the application, we’ll get an error clicking the “I Did This Today” button for a given Ritual.  Why?  Because the DataContractJsonSerializer does not know what to do with our new CompletedCommand property … it tried to persist it to JSON format, but it could not:

clip_image098

Therefore, we’ll need to adorn the CompletedCommand property with an attribute that will tell the DataContractJsonSerializer to ignore this property when serializing an instance of Ritual to JSON.  We’ll add the [IgnoreDataMember] attribute above the CompletedCommand property.  This will require we add the proper using statement (using the technique you should now be familiar with):

clip_image100

Important: Once you make this change, you will need to reset the data stored for the app because it corrupted.  To do this, simply shut down the emulator completely.  The next time you run your application in debug mode, the emulator will be refreshed and your old data will be removed.  Your app should now work correctly.

Next, we’ll want to disable the “I Did This Today” button once it has been clicked.  Furthermore, it should be disabled when we re-run the application a second and subsequent time on the same day.  Therefore, we’ll need to (a) implement INotifyPropertyChanged on the Ritual class to notify the user interface when the Dates property, an ObservableCollection of DateTime instances, contains today’s date, and (b) we’ll need to create a value converter that will be used to tell the user interface whether or not to enable the button based on whether or not the Dates property contains today’s date.

We’ll start by implementing the INotifyPropertyChanged interface on the Ritual class:

clip_image102

And I’ll choose the keyboard shortcut to display Intellisense and implement the INotifyPropertyChanged interface:

clip_image104

Once implemented, we’ll want to also add our helper method NotifyPropertyChanged() like so:

clip_image106

Finally, we’ll want to fire the event by calling NotifyPropertyChanged() from the AddDate() method.  We’ll pass in the name of the property that caused the event to fire (i.e., “Dates”):

clip_image108

To prepare for the value converter, I’ll add the IsEnabled=”False” attribute / value setting.  Soon, we’ll bind the value to a new Value Converter that will determine whether this should be true or false, depending on whether today’s date is in the Dates ObservableCollection<Date>:

clip_image110

I’ll create a new folder called ValueConverters:

clip_image112

… and I’ll add a new item into that folder, specifically (1) a Class (2) named IsCompleteToBoolean.cs, and then I’ll (3) click the OK button in the Add New Item dialog to complete this operation:

clip_image114

First, I’ll need to implement the IValueConverter interface for this new class.  I’ll use the usual technique to first add a using statement:

clip_image116

… and the same Control + [dot] technique to implement the interface:

clip_image118

… which should produce the following stubbed out implementation:

clip_image120

We’ll only implement the Convert() method.  Here we expect the Dates property to be passed in as value.  I’ll first need to cast the value parameter to an ObservableCollection<Date>.  Then, I’ll merely call the Contains() extension method to determine whether DateTime.Today is part of that collection.  If it is, return false (because we want to set the Button’s IsEnabled property to False, since we already clicked it once today) and if it is not present, then return true (because we want to set the Button’s IsEnabled property true since we have NOT clicked the button today).

clip_image122

Now, back in the MainPage.xaml, we’ll add a new prefix for the ValueConverters namespace we created when we added the ValueConverters folder, and we’ll add a Page.Resources section, and a new <valueconverter> instance, giving it a key of the same name.

<Page
    x:Class="DailyRituals.MainPage"
    . . .
    xmlns:valueconverter="using:DailyRituals.ValueConverters"
    . . .
    >
    <Page.Resources>
        <valueconverter:IsCompleteToBooleanConverter x:Key="IsCompleteToBooleanConverter" />
        <valueconverter:CompletedDatesToIntegerConverter x:Key="CompletedDatesToIntegerConverter" />
    </Page.Resources>

Below that, we’ll modify our Button like so:

<Button Name="CompletedButton"                       
        Content="I Did this Today!"
        Command="{Binding CompletedCommand}"
        CommandParameter="{Binding}"
        IsEnabled="{Binding Dates, Converter={StaticResource IsCompleteToBooleanConverter}}"
        />

The key here is the IsEnabled property which is bound to the Dates collection, but converted using our new Value Converter.

The final feature we’ll add is the progress bar.  We want to add a ProgressBar below the button.  We’ll hard wire the Value attribute to 1, but we’ll work on a Value Converter that will bind the Value to a count of Dates for a given Ritual which will allow us to see the progress against our Goal:

clip_image124

In the ValueConverter folder, we’ll Add a new Item.  In the Add New Item dialog, we’ll add (1) a new Class (2) named CompletedDatesToIntegerConverter.cs and (3) click the Add button:

clip_image126

We’ll use the techniques described earlier to implement the IValueConverter interface:

clip_image128

In the Convert() method, we’ll expect the value to be passed in as an ObservableCollection<Date>.  Then, we’ll simply return the number of items in that collection:

clip_image130

Once we’ve implemented our converter, we’ll add a new reference to it in the Page.Resources section of MainPage:

clip_image132

Then we’ll change the Value attribute to bind to the Dates collection converting it to an integer (the count of dates) using our new CompletedDatesToIntegerConverter:

clip_image134

Now when we run the app and click the “I Did This Today” button, we can see the button becomes disabled and the ProgressBar creeps up 1/30th of it’s width:

clip_image136

There are still some small improvements we could make to the layout and branding of the app, but we tackled the most challenging aspects from beginning to end.  The great thing about this example is that we used many different pieces to we’ve learned about in previous lessons and you can see how they contributed to the overall functionality we desired.

Embed

Download

Download this episode

The Discussion

  • User profile image
    Jacob

    Hi,
    in 25:05 you created public method in class Ritual for adding current date to collection.
    And in 25:19 you have a method that takes ritual, searches collection of rituals, finds ritual from parameter and then calls method AddDate on that found ritual.
    My question is: Why do you search for reference for ritual, when you already have that reference from parameter? You could just do "ritual.AddDate()" where "ritual" is from parameter of that method, couldn't you?

    Thanks

  • User profile image
    Robert Roeder

    Hi Bob,

    Question, several of the classes you have created in this series use async void. This in my opinion is a very bad practice, you should always return a task unless the async void is part of s sealed class or you have no choice. There are a couple wonderfull channel 9 series by Lucian Wischik shows why this should not be done.

    Robert

  • User profile image
    Phantompig

    Hi Bob

    I started working through your Windows Phone 8 series when this series was published and have now picked this series up.

    I just wanted to say that this video (albeit a little faster than the previous videos) has been very useful. Its brought together a lot of the concepts you introduced earlier and has been easy to work through as an absolute beginner like me.

    As an added bonus this has also been the funniest video by far!

Comments closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to send us feedback you can Contact Us.