End of Cineworld app for #Windows and issues facing indie dev #uwpdev

Back in the summer of 2016, I sat waiting for a movie to start at Cineworld. Wanted to check something, I was forced to use the mobile website. At that point I wondered if they had an API and within 2 days I had a version of the app ready for Windows Phone store.

A few months down the line, I ported it to Windows 8 and then ended up using Azure to generate dataset for the apps.  For the last 2 years or so I have been paying for the service.. it wasn’t significant but still odd 200 a month and having looked at IAP purchase in the last 2 months I decided that it was time to call it a day.

While Microsoft might say mobile didn’t work for them, I’d beg to differ. Windows Mobile version of the app had nearly 4k downloads (3976) vs desktop app with 256 downloads. YES a full 256 downloads.

Mobile app – Windows Phone app (WP8x and Windows 10 Mobile)

Cineworld-Mobile

Desktop app – Windows 8 app (Windows 8x and Windows 10 Desktop)

Cineworld-Desktop

That tells you all you need to know about consumption of certain apps on desktop vs mobile – it might not be global but it definitely is so in UK.

Now lets look at IAP.

IAP for Mobile app

Cineworld-IAP-Mobile

IAP for Desktop app

Cineworld-IAP-Desktop

Above are for the last 2 months – minus governmental and store taxation, I could have paid for the Azure service for a month.

Having decided that it wasn’t economical, the fact that Microsoft officially acknowledged end of the road for mobile was sufficient to determine that the app will never ever pay for itself.

The source for the Apps and Azure services is available on GitHub

Most code was before I adopted MVVM for Windows Development so please except my apologies for whatever you find in there.

Happy coding

Advertisements

Finding system fonts for use with XAML UWP app #uwpdev

Back in 2011 in my early days of Windows Phone 7 development, I created Alarm Clock app. Over a period of time, I upgraded it to SL8x and made it available on Windows 8x devices. It has seen many iterations and it made sense to rework it for UWP so I don’t have 2 distinct apps any longer – just one that scales across devices.

What was unique about this app ? It has some custom font and alarm sounds.. weird and wonderful – lets just say unusual.

This time around I thought that maybe just maybe I should also allow user to select system fonts!. So how does one go about it ? Well SO #FTW 😛 Filip Skakun replied on http://Loading list of available fonts using c# in winrt mentioned DirectWrite and linked to a git repos

https://github.com/christophwille/winrt-snippets/blob/master/EnumerateFonts/EnumerateFonts/InstalledFont.cs
and
https://github.com/sharpdx/SharpDX-Samples/blob/master/Desktop/DirectWrite/FontEnumeration/Program.cs

For the sake of showing time, I wasn’t interested in symbol fonts otherwise the source is identical.

private void LoadInstalledFonts()
{
    var factory = new SharpDX.DirectWrite.Factory();
    var fontCollection = factory.GetSystemFontCollection(false);
    var familyCount = fontCollection.FontFamilyCount;
            
    for (int i = 0; i < familyCount; i++)
    {
        var fontFamily = fontCollection.GetFontFamily(i);
                
        var familyNames = fontFamily.FamilyNames;
        int index;

        if (!familyNames.FindLocaleName(CultureInfo.CurrentCulture.Name, out index))
            familyNames.FindLocaleName("en-us", out index);

        string name = familyNames.GetString(index);

        using (var font = fontFamily.GetFont(index))
        {
            if (font.IsSymbolFont)
            {
                continue;
            }
        }

        this.AvailableFonts.Add(new FontFamily(name));
    }
}

The DirectWrite API is exposed by SharpDX and you need to add a reference to SharpDX.Direct2D1 using Nuget Package Manager

That is all that is required. Now the system fonts are available for use however you please. I myself allow user to chose what font they want to use.

Happy Coding

Operating system analysis of a small #uwp app user base #uwpdev

In 2015, I released Windows Phone 8.1 and Windows 10 version of the Daily Mail Online app. The apps uses tracking to monitor usage etc. In the last set of builds for the first iteration of my work there, I set the OS to be Windows Phone 8.1, Windows 10 and Windows 10 Mobile. Whilst it was sufficient back then, it meant that I did not have sufficient data to set the Min Target SDK the 2nd time around.

Let me start by saying that my data is mostly UK centric.

Such data hasn’t so far been released by Microsoft and to ensure that I targeted as many users as possible I settled for a min target SDK to be 10240 (the first version of Windows 10).

I still have users on the early version of the app and those will never upgrade. There are significant users on Windows Phone 8.1 (dropping at a steady rate)

I have been monitoring this numbers every day and I finally decided to modify the Min SDK to 14939. This is why.

Of the Windows 10 users, majority were on 14393. 0.7% users are still on 10240, around 1.9% are on 10586.

I found 16 different builds of 10240 in use amongst those 0.7%

21 different builds for 10586

With number of Insiders matching those on older generations, its time move forward. Min target for next update is 14393 and a total of 33 Windows 10 builds to support.

Using Composition Animations in your #uwp app

As we speak a new version of Daily Mail Online is being published to store. I am not going to discuss the content rather stick with code.

I have been testing staged rollout for a week now and that gave me valuable insights. I got errors I could not have reproduced myself. However all this time I was using UWPCommunityToolkit animations – Fade and Offset. Testing out nightly builds post 1.2 release I noticed that the app start time would significantly spiral with initial load taking over 20 seconds. The toolkit animations whilst providing a simple API on the surface are very extensive underneath and talking to folks I decided to create a custom set.

I have in snippet below two border controls

Border b = item.FindDescendantByName("ChannelBackground") as Border;
Border separator = item.FindDescendantByName("ChannelSeparator") as Border;

using toolkit extensions I did

b.Offset(offsetY: -65, duration: animationduration).Start();
separator.Fade(1, animationduration).Start();

so after a big round of search and copy pasting code from around the net, I finally had my own set. To make the change easier, I decided to use different method names so I could test individual bits without affecting it all.

b.AnimateOffsetY(-65, animationduration);
separator.AnimateOpacity(1, animationduration);

One thing that caught me off guard. To not show separator when XAML loads, I set Opacity to 0. With opacity to set, Composition API could not change its opacity any longer. As a fallback I use Storyboard and they seem to work just fine. Took me a while to figure out so now you will notice that in animation extensions, I check UIElement opacity and set it to 1 if it is zero.

Here is the animation extensions.

public static class AnimationExtensions
{
    public static bool UseCompositionAnimations => ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 2); // SDK >= 10586

    public static void AnimateOpacity(this UIElement element, float value, double duration = 150)
    {
        if (UseCompositionAnimations)
        {
            if (element.Opacity == 0) // composition animations doesn't have any effect if it is zero
            {
                element.Opacity = 1;
            }
                
            var xamlVisual = ElementCompositionPreview.GetElementVisual(element);

            if (duration <= 0)
            {
                xamlVisual.Opacity = value;
                return;
            }

            var compositor = xamlVisual.Compositor;

            var animation = compositor.CreateScalarKeyFrameAnimation();
                
            animation.Duration = TimeSpan.FromMilliseconds(duration);
            animation.DelayTime = TimeSpan.Zero;
            animation.InsertKeyFrame(1f, value);

            xamlVisual.StartAnimation("Opacity", animation);
        }
        else
        {
            if (duration <= 0)
            {
                element.Opacity = value;
                return;
            }

            AnimateDoubleProperty(element, "Opacity", value, duration);
        }
    }

    public static void AnimateOffsetY(this UIElement element, float value, double duration = 150)
    {
        if (UseCompositionAnimations)
        {
            var xamlVisual = ElementCompositionPreview.GetElementVisual(element);

            var offsetVector = new Vector3(0, value, 0);

            if (duration <= 0)
            {
                xamlVisual.Offset = offsetVector;
                return;
            }

            var compositor = xamlVisual.Compositor;

            var animation = compositor.CreateVector3KeyFrameAnimation();

            animation.Duration = TimeSpan.FromMilliseconds(duration);
            animation.DelayTime = TimeSpan.Zero;
            animation.InsertKeyFrame(1f, offsetVector);

            xamlVisual.StartAnimation("Offset", animation);
        }
        else
        {
            var transform = GetAttachedCompositeTransform(element);

            if (duration <= 0)
            {
                transform.TranslateY = value;
                return;
            }

            string path = GetAnimationPath(transform, element, "TranslateY");

            AnimateDoubleProperty(element, path, value, duration);
        }
    }

    private static string GetAnimationPath(CompositeTransform transform, UIElement element, string property)
    {
        if (element.RenderTransform == transform)
        {
            return $"(UIElement.RenderTransform).(CompositeTransform.{property})";
        }

        var group = element.RenderTransform as TransformGroup;

        if (group == null)
        {
            return string.Empty;
        }

        for (var index = 0; index < group.Children.Count; index++)
        {
            if (group.Children[index] == transform)
            {
                return $"(UIElement.RenderTransform).(TransformGroup.Children)[{index}].(CompositeTransform.{property})";
            }
        }

        return string.Empty;
    }

    private static CompositeTransform GetAttachedCompositeTransform(UIElement element)
    {
        CompositeTransform compositeTransform = null;

        if (element.RenderTransform != null)
        {
            compositeTransform = element.RenderTransform as CompositeTransform;
        }

        if (compositeTransform == null)
        {
            compositeTransform = new CompositeTransform();
            element.RenderTransform = compositeTransform;
        }

        return compositeTransform;
    }

    private static Storyboard AnimateDoubleProperty(
        this DependencyObject target,
        string property,
        double to,
        double duration = 250,
        EasingFunctionBase easingFunction = null)
    {

        var storyboard = new Storyboard();
        var animation = new DoubleAnimation
        {
            To = to,
            Duration = TimeSpan.FromMilliseconds(duration),
            EasingFunction = easingFunction ?? new SineEase(),
            FillBehavior = FillBehavior.HoldEnd,
            EnableDependentAnimation = true
        };

        Storyboard.SetTarget(animation, target);
        Storyboard.SetTargetProperty(animation, property);

        storyboard.Children.Add(animation);
        storyboard.FillBehavior = FillBehavior.HoldEnd;
        storyboard.Begin();

        return storyboard;
    }
}

These are very lightweight animations. If you encounter weird slow downs, maybe consider rolling out your own animation extensions like above. Happy coding

Beware oh beware of the Nullable type #uwp

Having done limited rollout yesterday I noticed a strange pattern. A huge number of crashes with “System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw

The most interesting bit was that the failures were only mostly on phones and not on desktop devices.

devices

What the heck!! looking inside didn’t help much. The crash group on HockeyApp also mentioned ‘invalidoperation_novalue‘ Okay this was strange.. In another instance I had a similar invalidoperation_novalue but somewhat different location ‘System.Nullable$1.get_Value

In a few places I was using nullable int. that translates to System.UInt32

int? pos = null;

// elsewhere read it
var item = list[pos.HasValue ? pos.Value : 0];

// in other places I encountered some exceptions - can't remember what and I used null check rather than HasValue
var item2 = list[pos != null ? pos.Value : 0]; 

//or in newer iteration of csharp
var item3 = list[pos?.Value??0];

Somehow I was directly using instance of nullable directly. A few posts helped. I was sure that was the problem. Thanks to Carsten Schuette for showing the use of GetValueOrDefault

InvalidOperationException in DeviceUtils.DiagonalSize
System.InvalidOperationException must contain value for ViewModel

So in the latest build, I have replaced above usage with

int? pos = null;

// elsewhere read it
var item = list[pos.GetValueOrDefault(0)];

Only hoping that I identified and fixed the one that was causing the issue. Stack trace with .NET Native can be painful.

Exception Type:  System.InvalidOperationException
Crashed Thread:  2

Application Specific Information:
InvalidOperation_NoValue. For more information, visit http://go.microsoft.com/fwlink/?LinkId=623485

Exception Stack:
SharedLibrary!<BaseAddress>+0x388719
MOL-Desktop!<BaseAddress>+0x121e122
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
SharedLibrary!<BaseAddress>+0x353fa2
SharedLibrary!<BaseAddress>+0x353a0e
SharedLibrary!<BaseAddress>+0x38387a
MOL-Desktop!<BaseAddress>+0x121d783
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
SharedLibrary!<BaseAddress>+0x3c2c09
SharedLibrary!<BaseAddress>+0x35369a
MOL-Desktop!<BaseAddress>+0xf08a62
MOL-Desktop!<BaseAddress>+0xf0b397
MOL-Desktop!<BaseAddress>+0xf0b2ef
Microsoft.HockeyApp.Extensibility.Windows.UnhandledExceptionTelemetryModule.CoreApplication_UnhandledErrorDetected(Object sender, ApplicationModel.Core.UnhandledErrorDetectedEventArgs e)

Thread 2 Crashed:
0   SharedLibrary                        0x0000000002908719 0x0000000002580000 + 3704601
1   MOL-Desktop                          0x0000000001e1e122 0x0000000000c00000 + 18997538
2   SharedLibrary                        0x00000000028d3fcc 0x0000000002580000 + 3489740
3   SharedLibrary                        0x00000000028d3fa2 0x0000000002580000 + 3489698
4   SharedLibrary                        0x00000000028d3a0e 0x0000000002580000 + 3488270
5   SharedLibrary                        0x000000000290387a 0x0000000002580000 + 3684474
6   MOL-Desktop                          0x0000000001e1d783 0x0000000000c00000 + 18995075
7   SharedLibrary                        0x00000000028d3fcc 0x0000000002580000 + 3489740
8   SharedLibrary                        0x0000000002942c09 0x0000000002580000 + 3943433
9   SharedLibrary                        0x00000000028d369a 0x0000000002580000 + 3487386
10  MOL-Desktop                          0x0000000001b08a62 0x0000000000c00000 + 15764066
11  MOL-Desktop                          0x0000000001b0b397 0x0000000000c00000 + 15774615
12  MOL-Desktop                          0x0000000001b0b2ef 0x0000000000c00000 + 15774447
13  MOL-Desktop                          0x0000000001dd44db 0x0000000000c00000 + 18695387

All new Daily Mail Online #UWP #Windows10 app now available in store

In November 2015, I joined MailOnline and created their first Windows Phone and Windows 10 app. It was a standard XAML app following MVVM pattern etc etc. The design was tried and tested first on iOS and then on Android. There were discussion on a clean new design and being the smallest platform, new design Windows 10 app were approved in March 2016. Fast forward 9 months and Windows 10 app rework has been pushed to store today with a limited rollout 5% users

I mentioned that the design is different – it follows the curated style on the website closely. Not only that I have followed a new style myself – I created this app as an SPA (Single Page Application). With WinRT API page caching is disabled by default and maintaining scroll positions etc is PITA. In past I have seen ListView behaving oddly when page caching is turned on. To get away from this, I created this app as an SPA. No forward backward navigation – its more like show / hide correct content. Its a big experiment on my side. Above and beyond all else two libraries played key role in creating this app

ReactiveUI My first go at using RX ever. It has made many things so simple. I still use event wiring in a few places but I will try to remove those over coming versions.
UWP Community Toolkit In previous version trying to tune the caching was a pain. In this version thanks to the toolkit I have multiple caches (image, video and json) and image ready for offline usage.

I should thank my colleagues George Medve, Jan Knotek and VK. I should also thank my ex-colleagues Zeno Foltin, Kristian Angyal and Rui Peres. They all helped out in many ways including happily getting trolled in the process.

Here are some of the screenshots.

screenshot-24
screenshot-29
screenshot-27
screenshot-25
screenshot-32

Its an adaptive app and adapts to mobile devices.
wp_ss_20170117_0001
wp_ss_20170117_0002
wp_ss_20170117_0004
wp_ss_20170117_0005
wp_ss_20170117_0006

There much more that needs to be done. This is only the beginning.

More on #UWPCommunityToolkit CacheBase #uwpdev

In the first version of UWP Community Toolkit, we only had ImageCache which had its origin in Windows App Studio. A few issues were raised to optimise it and one mentioned extensible cache that can be used to create any case.

FileCache, ImageCache, VideoCache, JsonCache.. you name it.. Yesterday I mentioned CacheBase. FileCache and ImageCache that ship with UWP Community Toolkit are implementations of CacheBase by giving it a specific type.

Today I had to implement ability to pull configuration settings from our server. I tried using current prod version of FileCache but my implementation was somewhat wrong there. It would try to create File from Stream and return null and then fail internally (fixed in current dev branch) however I needed something today. Enter ConfigCache.. well JsonCache really

public class ConfigCache : CacheBase<ConfigurationSetting>
{
    JsonSerializer jsonSerializer = new JsonSerializer();

    /// <summary>
    /// Private singleton field.
    /// </summary>
    private static ConfigCache _instance;

    /// <summary>
    /// Gets public singleton property.
    /// </summary>
    public static ConfigCache Instance => _instance ?? (_instance = new ConfigCache() { MaintainContext = false });

    protected override async Task<ConfigurationSetting> InitializeTypeAsync(StorageFile baseFile)
    {
        using (var stream = await baseFile.OpenStreamForReadAsync())
        {
            return InitializeTypeAsync(stream);
        }
    }

    protected override Task<ConfigurationSetting> InitializeTypeAsync(IRandomAccessStream stream)
    {
        var config = InitializeTypeAsync(stream.AsStream());
        return Task.FromResult<ConfigurationSetting>(config);
    }

    private ConfigurationSetting InitializeTypeAsync(Stream stream)
    {
        var reader = new StreamReader(stream);

        using (var jsonReader = new JsonTextReader(reader))
        {
            return jsonSerializer.Deserialize<ConfigurationSetting>(jsonReader);
        }
    }
}

In this case I am using a specific type to deserialise json to. How do I use it ?

ConfigCache.Instance.CacheDuration = TimeSpan.FromDays(1);

this.ConfigurationSettings = await ConfigCache.Instance.GetFromCacheAsync(new Uri(urlPath));

This would ensure that if configuration is older than a day, it will be downloaded again. Either way the caller will get deserialised data.