Delay a callback

At times I come across the task to delay the callback of an event in time (and only call it once). The most popular scenarii are probably:

  • Execute an action if the user types in something, but wait for the execution until the user stops typing for eg. a second
  • Save a settings file after changes occur, but wait until the changing stops for a while

In both cases you also want the action be executed only ONCE. 

Googling around I found no real solution for it with one exception: Rx. This is of course the most powerful approach with the Throttle or Sample methods. But in some cases the project does not use Rx otherwise and might be forced to keep the number of used packages low.

So I came up with the following solution, which proved to work well in many situations, although its thread safety was not tested thoroughly. So use it without any guarantee ;-)

To better follow the scenario let's look at the following console app, which collects user input.

    static void Main(string[] args)
    {
        ConsoleKeyInfo input;
        StringBuilder result = new StringBuilder();
        while ((input = Console.ReadKey()).Key != ConsoleKey.Escape)
        {
            result.Append(input.KeyChar);
            Console.WriteLine(result.ToString());
        }
    }

This would result in a mess mixing the typed text into the entered text itself.

HH
eHe
lHel
lHell
oHello
 Hello
wHello w
oHello wo
rHello wor
lHello worl
dHello world

Let's imagine a class DelayCallback with a method Enqueue and put it into the code:

    static void Main(string[] args)
    {
        ConsoleKeyInfo input;
        StringBuilder result = new StringBuilder();
        while ((input = Console.ReadKey()).Key != ConsoleKey.Escape)
        {
            result.Append(input.KeyChar);
            DelayCallback.Enqueue("someid", ()=> Console.WriteLine(result.ToString()),
                TimeSpan.FromSeconds(1));
        }
    }

And the output would look like:

Hello WorldHello World

Here is the class:

    public class DelayCallback
    {
        DateTime m_LastPulse;
        TimeSpan m_Duration = TimeSpan.MaxValue;
        Action m_Work;
        string m_Id;
        int m_Resolution;

        private DelayCallback(string id, Action work, TimeSpan duration, int resolution)
        {
            this.m_Duration = duration;
            this.m_Work = work;
            this.m_Id = id;
            this.m_LastPulse = DateTime.Now;
            Task.Factory.StartNew(Worker);
            //Console.WriteLine("ctor");
            m_Resolution = resolution;
        }

        private void Worker()
        {
            bool stop = false;
            while (!stop)
            {
                Sleep(m_Resolution);
                stop = DateTime.Now - m_LastPulse > m_Duration;
                //Console.WriteLine("pulse");
            }
            Done(this);
        }

        ManualResetEvent m_Sleeper = new ManualResetEvent(false);

        private void Sleep(int resolution)
        {
            m_Sleeper.WaitOne(resolution);
        }

        private static void Done(DelayCallback throttle)
        {
            lock (throttles)
            {
                throttles.Remove(throttle.m_Id);
                //Console.WriteLine("done");
            }
            throttle.m_Work();
        }


        private static Dictionary<string, DelayCallback> throttles = new Dictionary<string, DelayCallback>();

        public static void Enqueue(string id, Action work, TimeSpan wait, int resolution = 50)
        {
            //Console.WriteLine("Enqueue");
            lock (throttles)
            {
                if (throttles.ContainsKey(id) == false)
                {
                    throttles[id] = new DelayCallback(id, work, wait, resolution);
                }
                else
                {
                    throttles[id].Replace(work, wait);
                }
            }
        }

        private void Replace(Action action, TimeSpan wait)
        {
            this.m_Work = action;
            this.m_Duration = wait;
            this.m_LastPulse = DateTime.Now;
            //Console.WriteLine("replace");
        }
    }

 

Changing the Navigation Icon in Xamarin.Forms (Android)

Did you ever wonder how to change the Icon of the Navigation page in a Xamarin.Forms app? I mean, that icon which shows up in the Android version only. In the iOS and Windows versions there is only the page title and eventually a back button. See the following screenshot for this:

So, it only makes sense to do this in Android. In my current project we are porting an Android app (Xamarin) to Xamarin.Forms on all 3 platforms. One task was to have a context dependent icon in the ActionBar. It turned out to be a bit tricky to do, but here is the approach I came up with:

Suppose you have a fresh Xamarin.Forms project. It shows a NavigationPage on every screen which contains content pages shown via PushAsync on the NavigationPage. All of them are bound to ViewModels derived from a PropertyChanged-implementing page.

Let's say, the MainViewModel has a title property set to its name, and a string property naming the icon, like so:

public class MainViewModel : ViewModelBase
    {
        public ICommand ShowSecondCommand => new Command(
            () => (Application.Current.MainPage as NavigationPage).PushAsync(
                new ContentPage { Title = "No.2", Icon="Icon" }));

        public string Title => GetType().Name;
        public string Image => "Zeigl";
    }

The content page has a binding to the Title and to the icon, like that:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:v="clr-namespace:PlayWithBehaviors.Core.Views"
             x:Class="PlayWithBehaviors.Core.Views.MainPage" Padding="4, 10" 
             Icon="{Binding Image}"
             Title="{Binding Title}">

This will care for the titles being correctly shown. But what about the icons?

To get them displayed, a custom renderer is necessary. But don't worry, this is less effort than it seems. We only need it for the Android project, and we do not need to derive our own NavigationPage class. We just override the renderer, therefore it is enough to just add the new class to the Android project (and don't forget the ExportRenderer attribute):

[assembly: ExportRenderer(typeof(NavigationPage), typeof(CustomNavigationRenderer))]
namespace PlayWithBehaviors.Droid.Renderers
{
    public class CustomNavigationRenderer : NavigationRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
        {
            base.OnElementChanged(e);
            ChangeIcon();
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName == "CurrentPage")
                ChangeIcon();
        }

        private void ChangeIcon()
        {
            string iconName = Element?.CurrentPage?.Icon;
            if (string.IsNullOrEmpty(iconName)) return;
            var actionBar = ((Activity)Context).ActionBar;
            int iconId = (int)typeof(Resource.Drawable).InvokeMember(iconName, 
                BindingFlags.GetField | BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase, null, null, null);
            actionBar.SetIcon(iconId);
        }
    }
}

This should work, if the icons are in the Resources.Drawable part of the Android project (zeigl.png and Icon.png). The reasons why this works are the following:

public partial class Drawable
{

    // ...

    // aapt resource value: 0x7f020091
    public const int Icon = 2130837649;

    // aapt resource value: 0x7f0200a1
    public const int zeigl = 2130837665;

    // ...
}
  1. Android keeps the Resource.Drawable class in sync to the contents of the Resources folder and creates int Ids for these elements. So, the reflection code to get the Id in the ChangeIcon method will find the int value in the Resources.designer.cs file where the Drawable nested class lives
  2. The Icon property of the Xamarin.Forms page, although being of type FileImageSource, can directly be cast to (or interpreted as) a string, because the Xamarin.Forms framework has a conversion operator for that

So, in conjuction with the binding in the content page classes, this is a nice way to keep the code well-structured and the navigation image flexibly set. 

 

 

 

 

Using SignalR in Xamarin

Microsofts SignalR technology is great for connecting apps over the internet, so the idea is apparent to use it in Cross Platform projects. Unfortunately, it throws us some obstacles in the way and cannot be used like we are used to it (that is, nowadays, load a nuget package and off we go…).

There are a lot of questions on stackoverflow and other places concerning this problem. Microsoft even threw the Xamarin samples out of the SignalR project at Github. So, this means it is not usable any more? I’d say, it is, and here is, how.

In a recent training, we wanted to try SignalR in a UWP, Droid and iOS client and came across the same issues just mentioned. Against my first assumption we could not google a solution, so I delved into it and came up with the following solution that worked on all three platforms:

Short Story

  1. Use the SignalR package from within the Portable project. It will install there without problems
  2. The platform specific projects for UWP, Droid and iOS will throw exceptions, because it can’t locate NewtonSoft.Json 6.04 and Microsoft.Net.HttpExtensions, but we can handle this as follows.
  3. Just add the current Newtonsoft.Json Nuget package to the portable project and use it. It will then be deployed with the app (it worked for me with v2.2.1 of SignalR and v9.0.1 of Newtonsoft.Json).
  4. Add the Microsoft.Net.Http nuget package to the portable project. It will then deploy the Microsoft.Net.HttpExtensions.dll to the platform projects (which it did for me with v2.2.29).
  5. If you still see exceptions, get the corresponding Nuget packages from the server, save and unpack them locally. Then add the suitable dll from the lib subdirectory into the project. This will be "portable-net45+wp80+win8+wpa81" for Json and "monoandroid", "monotouch" and "win8" for Microsoft.Net.Http.

Long Story

Let’s create a demo project for that

PCL project

First, let’s just create a common Portable class library:

Let’s call it Signals.PCL and the solution Signals. This will target the right platforms and is compatible to most other packages out there.