Awaiting Google Play services tasks
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.