AppCenter Extensions for Xamarin.Forms

For the past 3 years or so I have been AppCenter for Crash Reporting and Analytics in Xamarin based apps. During this time, I have mostly built enterprise focused apps using Xamarin.Forms and as a developer I always think about code reuse which usually comes in the form of a library. Early this year, I decided to create and open source a set of convenience classes and extension methods to simplify Crash Reporting and Analytics using AppCenter and called it AppCenterExtensions.

The core features of the project are the following:

  • Simplified user interaction reporting using ICommand implementations
  • Automatic page tracking in Xamarin.Forms including time spent on screen
  • Extension methods for crash reporting
  • Anonymous user information configuration

This library is distributed as 2 NuGet packages

Getting Started

This library is configured almost the same way as the AppCenter SDK. You provide the AppCenter secrets, and specify whether to anonymize the user information. Both Crash Reporting and Analytics are always enabled when using AppCenterSetup.

AppCenterSetup.Instance.Start(
    "[iOS AppCenter secret]",
    "[Android AppCenter secret]",
    anonymizeAppCenterUser: true);

or

await AppCenterSetup.Instance.StartAsync(
    "[iOS AppCenter secret]",
    "[Android AppCenter secret]",
    anonymizeAppCenterUser: true);

The reason for the async API here is because anonymizeAppCenterUser internally relies on an async API. The synchronous API’s for starting AppCenter are non-blocking methods that do a fire-and-forget call to StartAsync(string,bool).

Anonymous User Information

The component AppCenterSetup exposes a method called UseAnonymousUserIdAsync() which sets the UserId in AppCenter to the first 8 characters a GUID that is unique per app installation. This can be used as a support key for uniquely identifying application users for instrumentation and troubleshooting. The support key can be attached to all HTTP calls by using the DiagnosticDelegatingHandler

AppCenter Crash Report

Error Reporting
The library exposes extension methods to the Exception class for conveniently reporting Exceptions to AppCenter

Example:

try
{
    // Something that blows up
    explosives.Detonate();
}
catch (Exception e)
{
    // Safely handle error then report
    e.Report();
}

HTTP Error Logging

The library provides a HttpMessageHandler implementation that logs non-successfuly HTTP results to AppCenter Analytics. This component will also attach HTTP headers describing the AppCenter SDK Version, Install ID, and a support key to all HTTP requests. The logged failed responses will contain the Endpoint URL (including the HTTP verb), Response status code, how the duration of the HTTP call. This will be logged under the event name HTTP Error
You will in most (if not all) cases would want to keep a singleton instance of the HttpClient. The DiagnosticDelegatingHandler is designed with unit testing in mind and accepts an IAnalytics and IAppCenterSetup interface, it also accepts an inner HttpMessageHandler if you wish to chain multiple delegating handlers.

Example:

var httpClient = new HttpClient(new DiagnosticDelegatingHandler());
await httpClient.GetAsync("https://entbpr4b9bdpo.x.pipedream.net/");

In the example above we made an HTTP GET call to the RequestBin endpoint https://entbpr4b9bdpo.x.pipedream.net. This will result in the following we inspected in RequestBin

AppCenter Crash Report

ITrackingCommand

This library provides 3 convenience implementations of ICommand that will report the action to AppCenter Analytics after successfully invoking the execute callback method

  • TrackingCommand – This implementation accepts an Action as the Execute callback and a Func<bool> as the CanExecute callback
  • TrackingCommand – This implementation accepts an Action<T> as the Execute callback and a Func<T, bool> as the CanExecute callback
  • AsyncTrackingCommand – This implementation accepts a Func<Task> as the execute callback and a Func<bool> as the CanExecute callback. This also exposes a CompletionTask property that the consumer can await if desired. The Execute(object parameter) method here is a non-blocking call

Example:

using System.Threading.Tasks;
using System.Windows.Input;
using ChristianHelle.DeveloperTools.AppCenterExtensions.Commands;
using ChristianHelle.DeveloperTools.AppCenterExtensions.Extensions;
using Microsoft.AppCenter.Crashes;
using Xamarin.Essentials;

namespace SampleApp.ViewModels
{
    public class AboutViewModel : BaseViewModel
    {
        public AboutViewModel()
        {
            AsyncButtonTappedCommand = new AsyncTrackingCommand(
                OnAsyncButtonTapped,
                nameof(AsyncButtonTappedCommand).ToTrackingEventName(),
                nameof(AboutViewModel).ToTrackingEventName());

            ButtonOneTappedCommand = new TrackingCommand(
                OnButtonOneTapped,
                nameof(ButtonOneTappedCommand).ToTrackingEventName(),
                nameof(AboutViewModel).ToTrackingEventName());

            ButtonTwoTappedCommand = new TrackingCommand<string>(
                OnButtonTapped,
                nameof(ButtonTwoTappedCommand).ToTrackingEventName(),
                nameof(AboutViewModel).ToTrackingEventName());
        }

        public ICommand AsyncButtonTappedCommand { get; }
        public ICommand ButtonOneTappedCommand { get; }
        public ICommand ButtonTwoTappedCommand { get; }

        private Task OnAsyncButtonTapped()
            => Browser.OpenAsync("https://xamarin.com");

        private void OnButtonOneTapped() { }

        private void OnButtonTwoTapped(string obj) { }
    }
}

Specifying the screenName argument in the constructor is optional and when this is not provided manually then it will use the declaring Type name from the method that instantiated the ITrackingCommand instance and convert it to a more analytics friendly event name using the ToTrackingEventName() extension method. In the example above, if the nameof(AboutViewModel).ToTrackingEventName() parameter is not provided then the owner declaring Type is AboutViewModel and the ScreenName will be set to "About"

Automatic Page Tracking

Automatic page tracking is enabled by replacing the base class of the ContentPage to classes to use TrackingContentPage class. By doing so the library will send page tracking information to AppCenter after leaving every page. Currently, the library will send the page Type, Title, and the duration spent on the screen. The library is rather opinionated on how to log information, and this will only change if I get a request to do so. Duration spent on screen is calculated using a Stopwatch that is started upon Page OnAppearing and is reported to Analytics upon OnDisappearing. The event name is based on the Type name of the Page and is split into multiple words based on pascal case rules and afterwards removes words like PageViewModelAsync. For example: UserSettingsPage or UserSettingsView becomes User Settings

XAML Example:

<?xml version="1.0" encoding="utf-8"?>
<ext:TrackingContentPage 
    xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    xmlns:d="http://xamarin.com/schemas/2014/forms/design" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:ext="clr-namespace:ChristianHelle.DeveloperTools.AppCenterExtensions.XamarinForms;assembly=AppCenterExtensions.XamarinForms"
    mc:Ignorable="d" 
    x:Class="SampleApp.Views.ItemDetailPage" 
    Title="{Binding Title}">

    <StackLayout Spacing="20" Padding="15">
        <Label Text="Text:" FontSize="Medium" />
        <Label Text="{Binding Item.Text}" d:Text="Item name" FontSize="Small" />
        <Label Text="Description:" FontSize="Medium" />
        <Label Text="{Binding Item.Description}" d:Text="Item description" FontSize="Small" />
    </StackLayout>

</ext:TrackingContentPage>

Custom Trace Listener

This library includes a trace listener implementation that reports to AppCenter. The reason for this is to cater to those who have implemented error handling or reporting using Trace Listeners, these types of users can just swap out (or add on) the AppCenterTraceListener

This implementation implements the following methods:

  • Write(object obj)
  • Write(object obj, string category)
  • WriteLine(object obj)
  • WriteLine(object obj, string category)

If the object provided is an Exception then this is reported to AppCenter Crash Reporting. If the object provided is an instance of AnalyticsEvent then this is sent to AppCenter Analytics

The AnalyticsEvent exposes 2 properties:

  • string EventName { get; } – self explanatory
  • IDictionary<string,string> Properties { get; } – Additional properties to attach to the Analytics event

To set it up you simply add an instance of AppCenterTraceListener to your existing Trace listeners:

Trace.Listeners.Add(new AppCenterTraceListener());

Here’s an example of how to use System.Diagnostics.Trace to report errors

try
{
    // Something that blows up
    explosives.Detonate();
}
catch (Exception e)
{
    // Safely handle error then report
    Trace.Write(e);

    // or
    Trace.Write(e, "Error");

    // or
    Trace.WriteLine(e);

    // or
    Trace.WriteLine(e, "Error");
}

and here’s an example of to use System.Diagnostics.Trace to send analytics data

public partial class App : Application
{
    private const string StateKey = "State";

    public App()
    {
        // Some initialization code ...

        Trace.Listeners.Add(new AppCenterTraceListener());
    }

    protected override void OnStart()
        => Trace.Write(
            new AnalyticsEvent(
                nameof(Application),
                new Dictionary<string, string>
                {
                    { StateKey, nameof(OnStart) }
                }));

    protected override void OnSleep()
        => Trace.Write(
            new AnalyticsEvent(
                nameof(Application),
                new Dictionary<string, string>
                {
                    { StateKey, nameof(OnSleep) }
                }));

    protected override void OnResume()
        => Trace.Write(
            new AnalyticsEvent(
                nameof(Application),
                new Dictionary<string, string>
                {
                    { StateKey, nameof(OnResume) }
                }));
}

Task Extensions

This library includes a few Task extension methods with AppCenter error reporting in mind. Possible exceptions that occur in the async operation are swallowed and reported to AppCenter. These extension methods will internally wrap the Task in a try/catch and await the Task using ConfigureAwait(false).

Here are usage some examples

Fire and Forget on a Task (Note: Forget() returns void)

var task = someClass.SomethingAsync()
task.Forget()

Awaitable Task (also available for Task<T>)

var task = someClass.SomethingAsync()
await task.WhenErrorReportAsync();

Generate iOS InfoPlist.strings Translations from Google Sheets

In my previous article Generating ResX translations from Google Sheets, I wrote about using Google Sheets as a translation tool by using the GOOGLETRANSLATE built in function to generate translation files for a Xamarin.Forms solution. For this post, I will demonstrate something very similar, but instead of ResX files I’ll generate InfoPlist.strings files in iOS for localizing the permission request prompts for accessing things like Camera, Location, Photo Gallery, etc. For the sake of this article I created this sample Google Sheets

For a quick recap, we will use a tool called csvtrans written by my colleague and good friend, Ricky Kaare Engelharth. The tool is built with .NET Core and can be installed using this command

dotnet tool install -g csvtrans

Using the tool is also straight forward and it also comes with some quick start instructions

USAGE: csvtrans [--help] [--sheet <document id> <sheet name>]
                [--csv <url or path>] [--format <apple|android|resx>]
                [--outputdir <directory path>] [--name <string>]
                [--convert-placeholders <regex pattern>]

OPTIONS:

    --sheet, -s <document id> <sheet name>
                          specify a Google Sheet as input.
    --csv, -c <url or path>
                          specify a online or local cvs file as input.
    --format, -f <apple|android|resx>
                          specify the output format.
    --outputdir, -o <directory path>
                          specify the output directory.
    --name, -n <string>   specify an optional name for the output.
    --convert-placeholders, -p <regex pattern>
                          convert placeholders to match the output format.
    --help                display this list of options.

Here’s an example usage of the tool

csvtrans --sheet 125id155PUq-6Odwg8Nf9fmkgBsKahTGbJYaYBD2rpSg iOS --format apple --outputdir .\Resources --name InfoPlist

The first argument –-sheet is the Google Sheet document ID followed by the Sheet Name, the next argument –-format specifies the output file format, the argument –-outputdir specifies the output folder, and the last argument --name specifies the output filename.

You can get the Document ID from the URL of the Google Sheet

Here’s an example output

Now I can just bring these files into my project and use them directly. Well, almost! There’s one little problem, and that is that by default the Xamarin.iOS csproj tooling explicitly adds each InfoPlist.strings file as a BundleResource. Oddly enough, the csproj format allows to specify wild card folders, so if we want to enable dynamic generation of InfoPlist.strings translations then we need to manually edit the csproj.

This is actually very easy to do. We just need to replace the lines like

with

This opens up for dynamic translations at build time using your CI/CD build tools of choice

Generate Resx Translations from Google Sheets

In my career, I have tried multiple translation tools for handling localization. This usually ends up in a spreadsheet sent back and forth that gets imported/exported with the actual translation tool. I have also tried giving my translators and customers direct access to the translation tool but that never really worked as they tend to blindly translate everything they see and usually miss out on the fact that some strings contain important placeholders that executable code expects. Anyway, at the end of the sending a spreadsheet back and forth seems to always work.

In a recent project, I built an Android and iOS app with Xamarin.Forms that used Resx files for handling cross platform translations, and InfoPlist.strings files in iOS for localizing OS requirement prompts for using things like Camera, Localization, Photos, etc. For this project we thought about playing around with Google Sheets as a translation tool. Google Sheets has built in Google Translate support so you can do something like =GOOGLETRANSLATE($B2,$B$1,C$1) where $B2 describes the text to translate, $B1 describes the source language, in this case English is the default, and $C1 describes the language to translate to. With this approach, I can very easily, blindly, add new translations to my app, like in this sample Google Sheets document, where I added Danish, German, Filipino, Simplified Chinese, Japanese, and Koreanusing the Google Translate tool. Of course, this needs to be proof-read by a translation professional who mastered the language, but this approach is very convenient for checking out how the app looks like in different languages.

Now here’s the awesome part. My colleague and good friend, Ricky Kaare Engelharth, created a translation tool called csvtrans that can produce Resx, iOS, and Android translation files from a publicly available Google Sheets document. The tool is written in .NET Core and is publicly available from nuget.org as a tool.

The tool can be installed using this command

dotnet tool install -g csvtrans

Using the tool is also straight forward and it also comes with some quick start instructions

USAGE: csvtrans [--help] [--sheet <document id> <sheet name>]
                [--csv <url or path>] [--format <apple|android|resx>]
                [--outputdir <directory path>] [--name <string>]
                [--convert-placeholders <regex pattern>]

OPTIONS:

    --sheet, -s <document id> <sheet name>
                          specify a Google Sheet as input.
    --csv, -c <url or path>
                          specify a online or local cvs file as input.
    --format, -f <apple|android|resx>
                          specify the output format.
    --outputdir, -o <directory path>
                          specify the output directory.
    --name, -n <string>   specify an optional name for the output.
    --convert-placeholders, -p <regex pattern>
                          convert placeholders to match the output format.
    --help                display this list of options.

Here’s an example usage of the tool

csvtrans --sheet 1icJ0a48MIIRkbHSIbPyLNXsbTZcPKI_U80QwdX5pWf8 Resx --format resx --outputdir .\Resources

The first argument –-sheet is the Google Sheet document ID followed by the Sheet Name, the next argument –-format specifies the output file format, and the last argument –-outputdir specifies the output folder.

You can get the Document ID from the URL of the Google Sheet

Here’s an example output

Now I can just bring these files into my project and use them directly. With the modern csproj format I don’t even need to do any changes to include these translation files, as long as the resx files are in the project folder they will be automagically included into the output. This opens up for dynamic translations at build time using your CI/CD build tools of choice

Scrollable UISegmentedControl for Xamarin.iOS

A few years ago, I had a full time job as a device developer in the Music Streaming industry. The applications we produced at the time targeted consumers and had a huge focus on UX and UI. One of the requirements our designers had was to have scrollable tabs. This was 5 years ago and before Xamarin.Forms existed so we built the iOS app and Android app separately. We used MvvmCross and the shared a lot of core code but the UI components were done per OS. When we started, the company did a recent switch to go full on with .NET on everything, so not only did we share code between apps, but we shared code across all systems within the entire organization.

Scrollable tabs come for free in Android using the built-in control TabLayout but on iOS we needed to re-create the UISegmentedControl and add scroll/pan/swipe functionality to it. On native code, you had a few options to choose from, so the first task was to find the best native implementation of it and port it to C#. A few Google searches later I found the HMSegmentedControl written by Hesham Abd-Elmegid. The component at the time was written in a single file and was a drop in replacement for the UISegmentedControl. It was functional, elegent, and directly portable to C#. It was perfect!

A few hours of focused coding later I managed to port the entire thing to C# and created a Github project for it. I originally called it HMSegmentedControl as a tribute to the author (I also sent him a thank you email at the time) but later changed it to ScrollableSegmentedControl as it was a better and more descriptive name that states exactly what it does. Recently, I re-visited this project to clean up, modernize the code, and structure of the repository. I added a README file with a useful description, screenshots, and code examples. I also published a NuGet package called ScrollableSegmentedControl to make it easier for others to use while keeping the responsibility of maintaining it.

So using the component is quite trivial. Here’s all you need to do:

Add the ScrollableSegmentedControl NuGet package

then you create an instance of ScrollableSegmentedControl and you add it to a View

using System;
using ChristianHelle.Controls.iOS;
using CoreGraphics;
using UIKit;

namespace ScrollableSegmentedControlSample
{
    public partial class ViewController : UIViewController
    {
        public ViewController(IntPtr handle) : base(handle)
        {
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            CreateScrollableSegmentedControl();
        }

        private void CreateScrollableSegmentedControl()
        {
            var sectionTitles = new[] { "One", "Two", "Three", "Four", "Five", "Six" };
            View.AddSubview(new ScrollableSegmentedControl(sectionTitles)
            {
                Font = UIFont.FromName("STHeitiSC-Light", 18.0f),
                Frame = new CGRect(0, 60, View.Frame.Width, 40),
                SegmentEdgeInset = new UIEdgeInsets(0, 10, 0, 10),
                SelectionStyle = ScrollableSegmentedControlSelectionStyle.FullWidthStripe,
                SelectionIndicatorLocation = ScrollableSegmentedControlIndicatorLocation.Down
            });
        }
    }
}

This would result in a segmented control that looks like this:

and can look like one of these examples depending on the SelectionStyle and SelectionIndicatorLocation

This post is probably 5 years too late but since I just only recently made it publicly available as a NuGet package I thought I should write a short article about it. I hope you find it useful!

New Challenges in the Cloud

I have been building mobile solutions for nearly 2 decades, I have seen things come and go, and I had loads of fun working in this space. A great part of my mobile development career involved consulting and for most of my work, I build the entire solution, including the web services and the data store and I am extremely proud of the things I have built. For the past 4 years I have been working in the Music Streaming industry. Using the tools from Xamarin, we built software for Android and iOS with a very large shared code base. The entire organization shares components through different platforms, it was great. Our products have a very high number of daily active users and has great ratings on the Apple AppStore and Google Play.

During these 4 years, a lot of things has happened. Microsoft acquired HockeyApp then later on Xamarin a few years back, and unfortunately, things went down hill. This is of course almost to be expected as Microsoft has bigger plans for these products and they will be all well integrated into a larger suite of products. I think things are starting to look better now, but for a very long while, it has been much worse. For instrumentation we had to switch from Xamarin Insights to RayGun. At that time when we did the switch, RayGun didn’t have the same feature set as Xamarin Insights so it felt like a down grade, with a higher price tag. RayGun eventually caught up and also improved the pricing plan.

Like anything else, if you work on the same suite of products for 4 years, no matter how fun it is, it becomes less and less exciting. Lately I have taken a keen interest in the recent developments in the Microsoft Azure space and early this year a very interesting opportunity presented itself. So after weeks of consideration, I decided to take a break from full time mobile development. For the past 2 months I have been a full time Cloud developer. Although I still do side projects and most of them will most likely still be mobile, I will be working on Cloud based solutions in my day job.

Using the Windows Phone Custom Contact Store

In previous versions of Windows Phone, you could always query the contact store to retrieve contact or calendar items. During that time I always wondered why I couldn’t just create my own contacts that can be shared with other applications and accessed through the People Hub. I guess more people had this problem and in Windows Phone 8 this has been addressed. Windows Phone 8 introduced the custom contact store in which apps can create contacts that are accessible from the People Hub and from other apps. Items in the custom contact store may only be modified by app that created them

How to create contacts in Windows Phone 8

In this section I would like to demonstrate how to use the custom contact store API. In order to do so we’ll create a UI that accepts the display name, email address, and mobile phone number. To make it a bit more fancy, we’ll add a feature that accepts a photo which can be loaded either from the camera or the media library

So here’s the code…

XAML
<phone:PhoneApplicationPage x:Class="CustomContactStore.MainPage"
                           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"
                           FontFamily="{StaticResource PhoneFontFamilyNormal}"
                           FontSize="{StaticResource PhoneFontSizeNormal}"
                           Foreground="{StaticResource PhoneForegroundBrush}"
                           SupportedOrientations="Portrait"
                           Orientation="Portrait"
                           shell:SystemTray.IsVisible="True">

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

        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="CUSTOM CONTACT STORE" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0" />
            <TextBlock Text="Sample" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}" />
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                        <RowDefinition />
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <TextBlock Text="Display Name" VerticalAlignment="Center" />
                    <TextBox Grid.Column="1" Name="displayName" />
                    <TextBlock Grid.Row="1" Text="Email" VerticalAlignment="Center" />
                    <TextBox Grid.Row="1" Grid.Column="1" Name="email" />
                    <TextBlock Grid.Row="2" VerticalAlignment="Center" Text="Mobile" />
                    <TextBox Grid.Row="2" Grid.Column="1" Name="mobile" />
                </Grid>
                <Button Content="Attach New Photo" Click="AttachNewPhotoClicked" />
                <Button Content="Attach Existing Photo" Click="AttachExistingPhotoClicked" />
                <Button Content="Save Contact" Click="AddClicked" />
            </StackPanel>
        </Grid>
    </Grid>

</phone:PhoneApplicationPage>

Code Behind (C#)
using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Tasks;
using Windows.Phone.PersonalInformation;

namespace CustomContactStore
{
    public partial class MainPage
    {
        private Stream photo;

        public MainPage()
        {
            InitializeComponent();
        }

        private async void AddClicked(object sender, RoutedEventArgs e)
        {
            var store = await ContactStore.CreateOrOpenAsync();
            var contact = new StoredContact(store)
                              {
                                  DisplayName = displayName.Text
                              };

            var props = await contact.GetPropertiesAsync();
            props.Add(KnownContactProperties.Email, email.Text);
            props.Add(KnownContactProperties.MobileTelephone, mobile.Text);

            if (photo != null)
                await contact.SetDisplayPictureAsync(photo.AsInputStream());

            await contact.SaveAsync();

            if (photo != null)
                photo.Dispose();
        }

        private void AttachNewPhotoClicked(object sender, RoutedEventArgs e)
        {
            var task = new CameraCaptureTask();
            task.Completed += OnTaskOnCompleted;
            task.Show();
        }

        private void OnTaskOnCompleted(object o, PhotoResult result)
        {
            photo = result.ChosenPhoto;
        }

        private void AttachExistingPhotoClicked(object sender, RoutedEventArgs e)
        {
            var task = new PhotoChooserTask();
            task.Completed += OnTaskOnCompleted;
            task.Show();
        }
    }
}

To create a custom contact we need to use the ContactStore API, we create an instance of this using the helper method CreateOrOpenAsync(). Now that we have an instance of the contact store, we create an instance of a StoredContact and set the DisplayName property to the value of the display name entered in the UI. The StoredContact object is very limited but we can add KnownContactProperties such as Email and MobileTelephone. This is done by using the GetPropertiesAsync() method of the StoredContact instance. The photos can be attached using the CameraCaptureTask or the PhotoChooserTask. We attach the photos by calling the SetDisplayPictureAsync() method of the StoredContact instance. The API’s for the custom contact store are pretty straight forward and easy to use.

Manifest

The custom contact store requires the ID_CAP_CONTACTS capability, we should enable that in the WMAppManifest.xml file. In order to that, in the Visual Studio Solution Explorer, expand the project properties folder and double click the WMAppManifest.xml file. This will open the new UI editor for the manifest file. Go to the Capabilities tab and enable the ID_CAP_CONTACTS

id_cap_contacts

Once the manifest file has been updated the app should be able to launch.

The user interface looks like this:

Custom Contact Store

Once the contact is created it will be available in the People Hub

People Hub

When the contact is viewed from the People Hub the owner of the contact will be displayed on top

Custom Contact

I hope you found this useful. You can check out the source code using the link below

The missing ResWFileCodeGenerator custom tool for Visual Studio 2012

I’ve been doing quite a lot of Windows Store Apps on Windows 8 lately. Ever since I started I always seemed to miss the ResXFileCodeGenerator custom tool. This is because in WinRT when you want to load a localized string in code then you will have to do something like this:

var resources = new ResourceLoader();
var localizedString = resources.GetString("SomeResourceName");

This of course can easily lead to a lot of errors. In my case I got a little to eager while refactoring and forgot to notice that I was also changing hard coded strings. This broke the code quite a lot. Because of this frustration I decided to implement my own custom tool since Microsoft didn’t provide one

The project is open source and is available at CodePlex. Here’s a preview of the description which I took directly from my CodePlex project site.

Project Description
A Visual Studio 2012 Custom Tool for generating a strongly typed helper class for accessing localized resources from a .ResW file.

Features
– C# code generator
– VB.NET code generator

Visual Studio 2012 Custom Tool (C#)

Visual Studio 2012 Custom Tool (VB)

Resource File Contents

C# Usage

private string test1, test2, test3;

private void LoadLocalizedStrings()
{
test1 = App1.LocalizedResources.Resources.Test1;
test2 = App1.LocalizedResources.Resources.Test2;
test3 = App1.LocalizedResources.Resources.Test3;
}

Visual Basic Usage

Dim test1, test2, test3

Private Sub LoadLocalizedStrings()
test1 = AppVb.LocalizedStrings.Resources.Test1
test2 = AppVb.LocalizedStrings.Resources.Test2
test3 = AppVb.LocalizedStrings.Resources.Test3
End Sub

Generated C# Code

//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.18010
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

// --------------------------------------------------------------------------------------------------
// <auto-generatedInfo>
// This code was generated by ResW File Code Generator (http://reswcodegen.codeplex.com)
// ResW File Code Generator was written by Christian Resma Helle
// and is under GNU General Public License version 2 (GPLv2)
//
// This code contains a helper class exposing property representations
// of the string resources defined in the specified .ResW file
//
// Generated: 11/08/2012 22:41:22
// </auto-generatedInfo>
// --------------------------------------------------------------------------------------------------
namespace App1.LocalizedResources
{
using Windows.ApplicationModel.Resources;


public partial class Resources
{

private static ResourceLoader resourceLoader = new ResourceLoader();

/// <summary>
/// Localized resource similar to "Test 1 value"
/// </summary>
public static string Test1
{
get
{
return resourceLoader.GetString("Test1");
}
}

/// <summary>
/// Localized resource similar to "Test 2 value"
/// </summary>
public static string Test2
{
get
{
return resourceLoader.GetString("Test2");
}
}

/// <summary>
/// Localized resource similar to "Test 3 value"
/// </summary>
public static string Test3
{
get
{
return resourceLoader.GetString("Test3");
}
}
}
}

Generated Visual Basic Code

'------------------------------------------------------------------------------
' <auto-generated>
' This code was generated by a tool.
' Runtime Version:4.0.30319.18010
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
' </auto-generated>
'------------------------------------------------------------------------------

Option Strict Off
Option Explicit On

Imports Windows.ApplicationModel.Resources

'--------------------------------------------------------------------------------------------------
'<auto-generatedInfo>
' This code was generated by ResW File Code Generator (http://reswcodegen.codeplex.com)
' ResW File Code Generator was written by Christian Resma Helle
' and is under GNU General Public License version 2 (GPLv2)
'
' This code contains a helper class exposing property representations
' of the string resources defined in the specified .ResW file
'
' Generated: 11/12/2012 21:30:52
'</auto-generatedInfo>
'--------------------------------------------------------------------------------------------------
Namespace AppVb.LocalizedStrings

Partial Public Class Resources

Private Shared resourceLoader As ResourceLoader = New ResourceLoader()

'''<summary>
'''Localized resource similar to "Test 1 value"
'''</summary>
Public Shared ReadOnly Property Test1() As String
Get
Return resourceLoader.GetString("Test1")
End Get
End Property

'''<summary>
'''Localized resource similar to "Test 2 value"
'''</summary>
Public Shared ReadOnly Property Test2() As String
Get
Return resourceLoader.GetString("Test2")
End Get
End Property

'''<summary>
'''Localized resource similar to "Test 3 value"
'''</summary>
Public Shared ReadOnly Property Test3() As String
Get
Return resourceLoader.GetString("Test3")
End Get
End Property
End Class
End Namespace

Parenthood

Once again I have taken a break from blogging. Parenting and my new family has been my absolute top priority and I’ve been having a hard time finding the time to write articles. I really enjoy spending time with my son and wife and I take advantage of every possible moment to do so.

I ended up at a schedule that starts around 5:30 AM in which my son wakes up, and wakes me up. I go and start preparing some breakfast for him. We then have a cozy morning playing around the house. I drop him off at day care as late possible (in my case 9:00 AM) and then drive to the office, or to a customer, or back home if I’m working from home. I pick my son up at between 3:00-4:00 PM and spend time with him and my wife until around 6:30 PM (this is his bed time and I’m very lucky that he has no trouble sleeping). From around 6:30 to 8:00-9:00 PM I spend some time with my wife discussing how our days went and do some planning for the following days. My wife goes to bed a bit early, and when that happens I go back to work. Since I drastically shortened by day, I need to do quite a bit of catching up at night. I usually work an extra 2-3 hours more at night, then I have an hour or 2 to give my mind a rest or work on a hobby project. My day usually ends between 12:30 AM and 2:00 AM. I need at least 4 hours of sleep to recharge for the next day, otherwise I won’t be able to last a full week on my schedule.

Hence the break from blogging 🙂

HTML5 and Windows Phone 7

Last week I did a talk on HTML5 and Windows Phone for the Danish Developer Conference 2012. The talk was quite successful and around 80% of said that they learned something new, which is the whole point of these conferences.

If you’re interested in my presentation and code examples then you can download it at http://dl.dropbox.com/u/18352048/Presentations/Windows%20Phone%20and%20HTML5/Windows%20Phone%20and%20HTML5.rar