Share via


Picking a Photo from the Picture Library

This article walks through the creation of an application that allows the user to pick a photo from the phone's picture library. Because Xamarin.Forms does not include this functionality, it is necessary to use DependencyService to access native APIs on each platform.

Creating the interface

First, create an interface in shared code that expresses the desired functionality. In the case of a photo-picking application, just one method is required. This is defined in the IPhotoPickerService interface in the .NET Standard library of the sample code:

namespace DependencyServiceDemos
{
    public interface IPhotoPickerService
    {
        Task<Stream> GetImageStreamAsync();
    }
}

The GetImageStreamAsync method is defined as asynchronous because the method must return quickly, but it can't return a Stream object for the selected photo until the user has browsed the picture library and selected one.

This interface is implemented in all the platforms using platform-specific code.

iOS implementation

The iOS implementation of the IPhotoPickerService interface uses the UIImagePickerController as described in the Choose a Photo from the Gallery recipe and sample code.

The iOS implementation is contained in the PhotoPickerService class in the iOS project of the sample code. To make this class visible to the DependencyService manager, the class must be identified with an [assembly] attribute of type Dependency, and the class must be public and explicitly implement the IPhotoPickerService interface:

[assembly: Dependency (typeof (PhotoPickerService))]
namespace DependencyServiceDemos.iOS
{
    public class PhotoPickerService : IPhotoPickerService
    {
        TaskCompletionSource<Stream> taskCompletionSource;
        UIImagePickerController imagePicker;

        public Task<Stream> GetImageStreamAsync()
        {
            // Create and define UIImagePickerController
            imagePicker = new UIImagePickerController
            {
                SourceType = UIImagePickerControllerSourceType.PhotoLibrary,
                MediaTypes = UIImagePickerController.AvailableMediaTypes(UIImagePickerControllerSourceType.PhotoLibrary)
            };

            // Set event handlers
            imagePicker.FinishedPickingMedia += OnImagePickerFinishedPickingMedia;
            imagePicker.Canceled += OnImagePickerCancelled;

            // Present UIImagePickerController;
            UIWindow window = UIApplication.SharedApplication.KeyWindow;
            var viewController = window.RootViewController;
            viewController.PresentViewController(imagePicker, true, null);

            // Return Task object
            taskCompletionSource = new TaskCompletionSource<Stream>();
            return taskCompletionSource.Task;
        }
        ...
    }
}

The GetImageStreamAsync method creates a UIImagePickerController and initializes it to select images from the photo library. Two event handlers are required: One for when the user selects a photo and the other for when the user cancels the display of the photo library. The PresentViewController method then displays the photo library to the user.

At this point, the GetImageStreamAsync method must return a Task<Stream> object to the code that's calling it. This task is completed only when the user has finished interacting with the photo library and one of the event handlers is called. For situations like this, the TaskCompletionSource class is essential. The class provides a Task object of the proper generic type to return from the GetImageStreamAsync method, and the class can later be signaled when the task is completed.

The FinishedPickingMedia event handler is called when the user has selected a picture. However, the handler provides a UIImage object and the Task must return a .NET Stream object. This is done in two steps: The UIImage object is first converted to an in memory PNG or JPEG file stored in an NSData object, and then the NSData object is converted to a .NET Stream object. A call to the SetResult method of the TaskCompletionSource object completes the task by providing the Stream object:

namespace DependencyServiceDemos.iOS
{
    public class PhotoPickerService : IPhotoPickerService
    {
        TaskCompletionSource<Stream> taskCompletionSource;
        UIImagePickerController imagePicker;
        ...
        void OnImagePickerFinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs args)
        {
            UIImage image = args.EditedImage ?? args.OriginalImage;

            if (image != null)
            {
                // Convert UIImage to .NET Stream object
                NSData data;
                if (args.ReferenceUrl.PathExtension.Equals("PNG") || args.ReferenceUrl.PathExtension.Equals("png"))
                {
                    data = image.AsPNG();
                }
                else
                {
                    data = image.AsJPEG(1);
                }
                Stream stream = data.AsStream();

                UnregisterEventHandlers();

                // Set the Stream as the completion of the Task
                taskCompletionSource.SetResult(stream);
            }
            else
            {
                UnregisterEventHandlers();
                taskCompletionSource.SetResult(null);
            }
            imagePicker.DismissModalViewController(true);
        }

        void OnImagePickerCancelled(object sender, EventArgs args)
        {
            UnregisterEventHandlers();
            taskCompletionSource.SetResult(null);
            imagePicker.DismissModalViewController(true);
        }

        void UnregisterEventHandlers()
        {
            imagePicker.FinishedPickingMedia -= OnImagePickerFinishedPickingMedia;
            imagePicker.Canceled -= OnImagePickerCancelled;
        }
    }
}

An iOS application requires permission from the user to access the phone's photo library. Add the following to the dict section of the Info.plist file:

<key>NSPhotoLibraryUsageDescription</key>
<string>Picture Picker uses photo library</string>

Android implementation

The Android implementation uses the technique described in the Select an Image recipe and the sample code. However, the method that is called when the user has selected an image from the picture library is an OnActivityResult override in a class that derives from Activity. For this reason, the normal MainActivity class in the Android project has been supplemented with a field, a property, and an override of the OnActivityResult method:

public class MainActivity : FormsAppCompatActivity
{
    internal static MainActivity Instance { get; private set; }  

    protected override void OnCreate(Bundle savedInstanceState)
    {
        // ...
        Instance = this;
    }
    // ...
    // Field, property, and method for Picture Picker
    public static readonly int PickImageId = 1000;

    public TaskCompletionSource<Stream> PickImageTaskCompletionSource { set; get; }

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

        if (requestCode == PickImageId)
        {
            if ((resultCode == Result.Ok) && (intent != null))
            {
                Android.Net.Uri uri = intent.Data;
                Stream stream = ContentResolver.OpenInputStream(uri);

                // Set the Stream as the completion of the Task
                PickImageTaskCompletionSource.SetResult(stream);
            }
            else
            {
                PickImageTaskCompletionSource.SetResult(null);
            }
        }
    }
}

The OnActivityResultoverride indicates the selected picture file with an Android Uri object, but this can be converted into a .NET Stream object by calling the OpenInputStream method of the ContentResolver object that was obtained from the activity's ContentResolver property.

Like the iOS implementation, the Android implementation uses a TaskCompletionSource to signal when the task has been completed. This TaskCompletionSource object is defined as a public property in the MainActivity class. This allows the property to be referenced in the PhotoPickerService class in the Android project. This is the class with the GetImageStreamAsync method:

[assembly: Dependency(typeof(PhotoPickerService))]
namespace DependencyServiceDemos.Droid
{
    public class PhotoPickerService : IPhotoPickerService
    {
        public Task<Stream> GetImageStreamAsync()
        {
            // Define the Intent for getting images
            Intent intent = new Intent();
            intent.SetType("image/*");
            intent.SetAction(Intent.ActionGetContent);

            // Start the picture-picker activity (resumes in MainActivity.cs)
            MainActivity.Instance.StartActivityForResult(
                Intent.CreateChooser(intent, "Select Picture"),
                MainActivity.PickImageId);

            // Save the TaskCompletionSource object as a MainActivity property
            MainActivity.Instance.PickImageTaskCompletionSource = new TaskCompletionSource<Stream>();

            // Return Task object
            return MainActivity.Instance.PickImageTaskCompletionSource.Task;
        }
    }
}

This method accesses the MainActivity class for several purposes: for the Instance property, for the PickImageId field, for the TaskCompletionSource property, and to call StartActivityForResult. This method is defined by the FormsAppCompatActivity class, which is the base class of MainActivity.

UWP implementation

Unlike the iOS and Android implementations, the implementation of the photo picker for the Universal Windows Platform does not require the TaskCompletionSource class. The PhotoPickerService class uses the FileOpenPicker class to get access to the photo library. Because the PickSingleFileAsync method of FileOpenPicker is itself asynchronous, the GetImageStreamAsync method can simply use await with that method (and other asynchronous methods) and return a Stream object:

[assembly: Dependency(typeof(PhotoPickerService))]
namespace DependencyServiceDemos.UWP
{
    public class PhotoPickerService : IPhotoPickerService
    {
        public async Task<Stream> GetImageStreamAsync()
        {
            // Create and initialize the FileOpenPicker
            FileOpenPicker openPicker = new FileOpenPicker
            {
                ViewMode = PickerViewMode.Thumbnail,
                SuggestedStartLocation = PickerLocationId.PicturesLibrary,
            };

            openPicker.FileTypeFilter.Add(".jpg");
            openPicker.FileTypeFilter.Add(".jpeg");
            openPicker.FileTypeFilter.Add(".png");

            // Get a file and return a Stream
            StorageFile storageFile = await openPicker.PickSingleFileAsync();

            if (storageFile == null)
            {
                return null;
            }

            IRandomAccessStreamWithContentType raStream = await storageFile.OpenReadAsync();
            return raStream.AsStreamForRead();
        }
    }
}

Implementing in shared code

Now that the interface has been implemented for each platform, the shared code in the .NET Standard library can take advantage of it.

The UI includes a Button that can be clicked to choose a photo:

<Button Text="Pick Photo"
        Clicked="OnPickPhotoButtonClicked" />

The Clicked event handler uses the DependencyService class to call GetImageStreamAsync. This results in a call to the platform project. If the method returns a Stream object, then the handler sets the Source property of the image object to the Stream data:

async void OnPickPhotoButtonClicked(object sender, EventArgs e)
{
    (sender as Button).IsEnabled = false;

    Stream stream = await DependencyService.Get<IPhotoPickerService>().GetImageStreamAsync();
    if (stream != null)
    {
        image.Source = ImageSource.FromStream(() => stream);
    }

    (sender as Button).IsEnabled = true;
}