Awaiting Google Play services tasks

April 16th 2021 Xamarin Android Async C#

Thanks to Xamarin, C# developers can use a lot of their existing knowledge even when interacting with native Android (and iOS) APIs. However, some APIs still break the idioms we're used to in .NET.

For example, we're used to the async and await keywords when writing asynchronous code:

var text = await File.ReadAllTextAsync(filepath);

When I recently had to integrate Firebase Cloud Messaging into a Xamarin application, I encountered an asynchronous call that didn't allow that. The method even returned a Task. It just wasn't the right Task.

The official sample code showed that I should add a listener (i.e. a handler) to be called when the task completes. Unfortunately, the listener has to implement an interface, which makes it inconvenient in terms of C# code.

I decided to hide the necessary plumbing in an extension method to make the actual application code more similar to idiomatic C#. My final goal was o await the Android Task just like I'm used to with the .NET Task.

The key to my solution was the TaskCompletionSource class, which is also a great tool for converting older .NET asynchronous patterns to tasks. I embedded it in the private listener helper class:

private class TaskCompleteListener : Java.Lang.Object, IOnCompleteListener
{
    private readonly TaskCompletionSource<Java.Lang.Object> taskCompletionSource;

    public TaskCompleteListener(TaskCompletionSource<Java.Lang.Object> tcs)
    {
        this.taskCompletionSource = tcs;
    }

    public void OnComplete(Android.Gms.Tasks.Task task)
    {
        if (task.IsCanceled)
        {
            this.taskCompletionSource.SetCanceled();
        }
        else if (task.IsSuccessful)
        {
            this.taskCompletionSource.SetResult(task.Result);
        }
        else
        {
            this.taskCompletionSource.SetException(task.Exception);
        }
    }
}

In the extension method, I could then simply instantiate this helper class and pass it as the listener to the Android Task class:

public static Task<Java.Lang.Object> ToAwaitableTask(this Android.Gms.Tasks.Task task)
{
    var taskCompletionSource = new TaskCompletionSource<Java.Lang.Object>();
    var taskCompleteListener = new TaskCompleteListener(taskCompletionSource);
    task.AddOnCompleteListener(taskCompleteListener);

    return taskCompletionSource.Task;
}

That was it. After using this extension method, the final code looked very .NET-like:

public async Task<string> GetToken()
{
    var result = await FirebaseMessaging.Instance.GetToken().ToAwaitableTask();
    return result.ToString();
}

You can find a working sample with full code in my GitHub repository.

The Task class from the Google Play services library that's used in Firebase APIs isn't awaitable. I created an extension method that converts it to a regular .NET Task class that can be awaited in an asynchronous method.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License