Time Tortoise: Add, Save, and List

Add, Save, List

This is one in a series of articles about Time Tortoise, a Universal Windows Platform app for planning and tracking your work schedule. For more on the development of this app and the ideas behind it, see my Time Tortoise category page.

Incremental Build Model

Last week I made my first commit to the GitHub repository for Time Tortoise, the app that I’m working on this year.

I publish a blog post every Wednesday, so I have adopted that schedule for this project as well. In general, each blog post is associated with a GitHub commit. In the first few weeks of the year, I used example UWP projects to experiment with a few ideas. Starting last week, my commits have been adding functionality to the Time Tortoise app itself.

This approach is an example of the incremental build model of software development. The idea is to start with a small working program and build it over time through a series of small (incremental) improvements. After each increment, the program remains usable, and it has slightly more functionality.

In agile terms, I’m building Time Tortoise using a series of one-week sprints, with a release at the end of each sprint. Since the project team consists of one very part-time developer (me), the scope of each sprint is small. But thanks to the incremental build model, it will add up to something useful over time.

The scope for this week: create, update, and list activity names.

Create, Update, and List Activity Names

In Time Tortoise, an activity is defined as follows:

Activity: a type of work whose duration you want to measure.

For example, at this moment I’m tracking an activity that I could call blog post writing. Earlier this week I was tracking an activity called Time Tortoise development. I want to know how many hours I have spent on these and other activities today, this week, last week, and so on. Eventually, Time Tortoise will be able to answer these questions.

A basic Time Tortoise use case is to select an activity from a list and start tracking it. So the system needs a way to manage a list of activities. This week, I worked on a few basic activity management features:

  • Add a new activity to the list.
  • Update an existing activity.
  • Display the current list of activities.

An activity will eventually be associated with a list of time segments. For now, I’m storing just the activity name. The database also assigns a unique ID to each activity, but the user doesn’t have to know about that.

MVVM Design

The Time Tortoise design is based on the Model-View-ViewModel pattern. As explained last week, I have separate .NET projects for the view, view model, model, data access layer, and unit tests. In last week’s commit, I just exercised the view model with test code and a mock repository. This week, I have an end-to-end scenario that includes a XAML UI and a SQLite database.

View

The view now has four controls:

ListView

A XAML ListView “displays data items in a vertical stack.” In this case, the data items are activity names.

One of the benefits of XAML is its data binding functionality. So the goal is to send the list of activity names to the UI through binding.

Currently the app has one view, MainPage.xaml. The codebehind for a view is a convenient place to reference the corresponding view model, like this:

public MainViewModel Main { get; set; }

Then in the XAML, we can bind to properties on the view model, using the Main identifier. For example:

ItemsSource="{x:Bind Main.Activities, Mode=OneWay}"

This tells the ListView that it can get its list of activities from a collection called Main.Activities, and that it should update itself when this collection changes.

To operate on an activity, the user first selects it from the list. We can use binding to keep track of which activity the user selects:

SelectedIndex="{x:Bind Main.SelectedIndex, Mode=TwoWay}"

If we only needed activity names, then Activities could be a collection of strings. But I’m planning to include more information (e.g., time totals) in the list. Formatting a row with multiple columns is possible using an ItemTemplate. That approach allows each column to be bound to a separate text block, like this for the Name column:

<TextBlock Text="{x:Bind Name, Mode=OneWay}" />

TextBox

To add and update an activity name, we need a TextBox. Two-way binding ensures that the name displayed in the textbox stays in sync with the name selected in the list:

<TextBox Text="{x:Bind Main.SelectedActivity.Name, Mode=TwoWay}"/>

Button

Two buttons allow the user to add and save activities. The click event for each button is bound to a method in the view model, which I’ll discuss next:

<Button Click="{x:Bind Main.Add}" Content="Add" />
<Button Click="{x:Bind Main.Save}" Content="Save" />

ViewModel

The Time Tortoise solution currently has two view model classes.

ActivityViewModel exposes data from the Activity model class. Although this view model doesn’t currently do any special formatting or other processing, it’s still desirable to encapsulate Activity rather than use it as-is. The reason: we don’t want to directly reference the Model from the View. That would break MVVM information hiding rules, whereby the View Model knows about both the Model and the View, but the Model and the View don’t know about each other.

MainViewModel supports MainPage, primarily by exposing these binding sources and targets:

  • Activities is a property of type ObservableCollection, a special collection type that provides notifications that allow UI elements (like the ListView) to stay in sync with changes to the collection.
  • SelectedIndex contains the integer index for the currently selected activity.
  • SelectedActivity contains the corresponding activity (ActivityViewModel) that is selected. It gets updated when the selected index changes.
  • Save calls the repository to save the currently selected activity.
  • Add adds a new activity to the collection. Binding then causes it to show up in the list.

The MainViewModel constructor calls LoadActivities, which calls the repository to initialize the Activities collection.

Binding

Here’s a review of how binding works:

  • Each view model class inherits from NotificationBase, which implements INotifyPropertyChanged, the .NET interface that provides notifications about changed properties.
  • Property getters in view model classes are implemented like normal getters. They just return the value of a private variable.
  • Property setters don’t just update the private variable. Instead, they make a call like SetProperty(ref _selectedActivity, value). In this case, _selectedActivity is the private variable, value is the new value provided by the caller, and SetProperty is implemented in NotificationBase.
  • Because SetProperty accepts _selectedActivity by reference, it can update the value directly. But in addition to updating the value, it also raises a PropertyChanged event. This notifies bound clients that a new activity has been selected. For example, the text box on the view, which is bound to SelectedActivity.Name, then knows to update its text value.

Model

The only change in the model since last week is to add an integer Id field to the Activity class. This is an Entity Framework convention to identify the primary key for a database table.

Data Access Layer

Last week’s data access code consisted only of a mock repository that didn’t make any actual database calls. So the data access layer is almost entirely new for this week. Here’s the current state of the data access layer:

  • Repository now has two methods: LoadActivities returns the Activities collection from the database context. SaveActivity calls the database context to persist a single Activity object (new or previously saved).
  • The database context is now a hierarchy of classes: The base class is DbContext, which is provided by Entity Framework. Context inherits from DbContext and contains only properties of type DbSet, which are used to persist and retrieve C# objects. Finally, SqliteContext inherits from Context, and contains SQLite-specific details like the database connection string. Since Repository accepts an instance of type Context in its constructor, this allows a client such as an integration test to pass in a type that uses an in-memory connection string instead of pointing to the standard database file on disk.
  • The Migrations folder, as always, contains code to build the database schema.

Tracking an entity

Here’s an error message that I got while debugging the data access layer:

The instance of entity type ‘Activity’ cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.

This message was associated with a System.InvalidOperationException exception that was thrown when I called DbContext.SaveChanges to save an updated Activity.

As the message suggests, the problem was due to having two instances of the same activity record. When I load the current set of activities from the database in MainViewModel.LoadActivities, I turn each one into an ActivityViewModel so it can be used by the View. At one point, I was doing this by assigning each field individually in the ActivityViewModel constructor. Not only is this more work than necessary. It also results in the above error. The simpler, and correct, approach is the following constructor:

public ActivityViewModel(Activity activity) : base(activity)
{}

ActivityViewModel inherits from NotificationBase<Activity>, which encapsulates an instance of Activity in a protected field called This that is initialized in the constructor. By always letting NotificationBase keep track of the activity instance, we ensure that we have only one copy floating around, which keeps Entity Framework happy.

Tests

Since this week was mainly about research and experimentation to get the binding and data access code working, I didn’t write my tests first, as strict TDD rules would dictate. Getting to full test coverage will be a task for next week.