Xamarin Forms Activity To Awaitable Android iOS Xamarin

Dec 8th, 2016 - written by Kimserey with .

Today I want to share a neat trick which I have been using a lot - transform an asynchronous operation (triggered with StartActivityFromResult) from an Android Activity to an awaitable task - which then can be used with async await keywords in a Xamarin.Forms PCL project.

  1. Scenario
  2. CustomPickActivity
  3. TaskCompletionSource in service

##1. Scenario

I needed to pick a file from my documents and execute an action using the selected file.

##2. CustomPickerActivity

To pick a file from our Xamarin.Forms project, we use a service which starts a picker activity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class FilePicked : EventArgs
{
    public string AbsolutePath { get; set; }
}

[Activity]
public class PickActivity: Activity
{
    static event EventHandler<FilePicked> OnFilePicked;

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        var intent = new Intent();
        intent.SetType("image/*");
        intent.SetAction(Intent.ActionPick);
        StartActivityForResult(intent, 0);
    }

    protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
    {
        base.OnActivityResult(requestCode, resultCode, data);

        if (requestCode == 0 && resultCode == Result.Ok)
        {
            if (OnFilePicked != null)
            {
                OnFilePicked(this, new FilePicked { AbsolutePath = data.Data.Path });
            }
        }
        
        Finish(); 
    }
}

When the activity is created, another activity is started with a ActionPick intent. Once picked, the result is given back in the OnActivityResult. I also added an event static event EventHandler<FilePicked> OnFilePicked and triggered it from within the ActivityResult OnFilePicked(this, new FilePicked { AbsolutePath = data.Data.Path }). This is important for the next step.

##3. TaskCompletionSource in service

The next step is to build the service which will be called from the Xamarin.Forms PCL. The trick here is to use a TaskCompletionSource to create a task which starts immediately and gets completed only when the event in the activity is triggered.

Also, we use interlock methods to ensure that at any point only one task is created. If not we directly return with an error.

This is important because the event is static, concurrent call to the service will yield unexpected behaviours.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class PickFileService: IPickFileService
{
    TaskCompletionSource<string> tcs;

    public Task<string> PickFile()
    {
        var uniqueId = Guid.NewGuid();
        var next = new TaskCompletionSource<string>(uniqueId); 

        // Interlocked.CompareExchange(ref object location1, object value, object comparand)
        // Compare location1 with comparand.
        // If equal replace location1 by value.
        // Returns the original value of location1.
        // ---
        // In this context, tcs is compared to null, if equal tcs is replaced by next,
        // and original tcs is returned.
        // We then compare original tcs with null, if not null it means that a task was 
        // already started.
        if (Interlocked.CompareExchange(ref tcs, next, null) != null)
        {
            return Task.FromResult<string>(null);
        }

        EventHandler<FilePicked> handler = null;

        handler = (sender, e) => {
            
            // Interlocaked.Exchange(ref object location1, object value)
            // Sets an object to a specified value and returns a reference to the original object.
            // ---
            // In this context, sets tcs to null and returns it.
            var task = Interlocked.Exchange(ref tcs, null);

            PickActivity.OnFilePicked -= handler;

            if (!String.IsNullOrWhiteSpace(e.AbsolutePath))
            {
                task.SetResult(e.AbsolutePath);
            }
            else
            {
                task.SetCanceled();
            }
        };

        PickActivity.OnFilePicked += handler;
        var pickIntent = new Intent(Forms.Context, typeof(PickActivity));
        pickIntent.SetFlags(ActivityFlags.NewTask);
        Forms.Context.StartActivity(pickIntent);

        return tcs.Task;
    }
}

By doing this, we now have a way to return a task which only completes when a file is picked or the activity cancelled.

This then allow us to await the pick intent when calling the service from our Xamarin.Forms project.

1
2
3
4
5
6
7
var buttonPickFile = new Button { Text = "Pick file" };

buttonPickFile.Clicked += async (sender, e) =>
{
    var file = await DependencyService.Get<IPickFileService>().PickFile();
    await page.DisplayAlert("File picked", file, "OK");
};

Thanks to this trick, we can now await and execute something after the file is picked like here, we display an alert. But I am sure you will find something more important to do than display an alert!

Full source code available here

Conclusion

Today we saw how we can transform a pick activity to an awaitable task which can be used from a service in Xamarin.Forms projects. Using TaskCompletionSource together with event to transform event/callback codes to Task is a very nice trick to know. If you have any question, leave it here or hit on Twitter @Kimserey_Lam. See you next time!

Support me

Support me by downloading my app BASKEE. Thank you!

baskee

Other posts you will like

Designed, built and maintained by Kimserey Lam.