Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Xamarin Blueprints
Xamarin Blueprints

Xamarin Blueprints: Leverage the power of Xamarin to create stunning cross-platform and native apps

eBook
€8.99 €36.99
Paperback
€45.99
Subscription
Free Trial
Renews at €18.99p/m

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Table of content icon View table of contents Preview book icon Preview Book

Xamarin Blueprints

Chapter 1.  Building a Gallery Application

This chapter will walkthrough native development with Xamarin by building an iOS and Android application that will read from your local gallery files, and display them in a UITableView and ListView. The following topics will be covered in this chapter:

Expected knowledge:

  • Creating iOS provision certificates
  • iOS development
  • Objective-C
  • Creating keystores
  • Android development
  • Java

In this chapter you will learn the following:

  • Creating an iOS project
  • Creating a UIViewController and UITableView
  • Customizing a cell's appearance
  • Creating an Android project
  • Creating an XML interface and ListView
  • Shared projects
  • Custom row appearance
  • Bitmap functions
  • The ALAssetLibrary
  • Adding the iOS photo screen
  • Adding the Android photo screen

Create an iOS project

Let's begin our Xamarin journey; we will start by setting up our iOS project in Xamarin Studio:

  1. Start by opening Xamarin Studio and creating a new iOS project. To do so, we simply select File | New | Solution and select an iOS Single View App; we must also give it a name and add the bundle ID you want in order to run your application.

    Note

    It is recommended that for each project, a new bundle ID is created, along with a developer provisioning profile for each project.

  2. Now that we have created the iOS project, you will be taken to the following screen:
Create an iOS project

Doesn't this look familiar? Yes, it is our AppDelegate file; notice the .cs on the end; because we are using C#, all our code files will have this extension (no more .h or .m files).

Tip

Before we go any further, spend a few minutes moving around the IDE, expanding the folders, and exploring the project structure; it is very similar to an iOS project created in XCode.

Creating a UIViewController and UITableView

Now that we have our new iOS project, we are going to start by creating a UIViewController. Right-click on the project file, select Add | New File, and select ViewController from the iOS menu selection in the left-hand box:

Creating a UIViewController and UITableView

You will notice three files generated, a .xib, a .cs, and a .designer.cs file. We don't need to worry about the third file; this is automatically generated based upon the other two files.

Tip

Right-click on the project item and select Reveal in Finder,

Creating a UIViewController and UITableView

This will bring up the finder where you will double-click on the GalleryCell.xib file; this will bring up the user interface designer in XCode. You should see automated text inserted into the document to help you get started.

Firstly, we must set our namespace accordingly, and import our libraries with using statements. In order to use the iOS user interface elements, we must import the UIKit and CoreGraphics libraries. Our class will inherit the UIViewController class in which we will override the ViewDidLoad function:

namespace Gallery.iOS  
{ 
    using System; 
    using System.Collections.Generic; 
 
    using CoreGraphics; 
    using UIKit; 
 
    public partial class MainController : UIViewController 
    { 
        private UITableView _tableView; 
 
        private TableSource _source; 
 
        private ImageHandler _imageHandler; 
 
        public MainController () : base ("MainController", null) 
        { 
            _source = new TableSource (); 
 
            _imageHandler = new ImageHandler (); 
            _imageHandler.AssetsLoaded += handleAssetsLoaded; 
        } 
 
        private void handleAssetsLoaded (object sender, EventArgs e) 
        { 
            _source.UpdateGalleryItems (_imageHandler.CreateGalleryItems()); 
            _tableView.ReloadData (); 
        } 
 
        public override void ViewDidLoad () 
        { 
            base.ViewDidLoad (); 
 
            var width = View.Bounds.Width; 
            var height = View.Bounds.Height; 
 
            tableView = new UITableView(new CGRect(0, 0, width, height)); 
            tableView.AutoresizingMask = UIViewAutoresizing.All; 
            tableView.Source = _source; 
 
            Add (_tableView); 
        } 
    } 
} 

Our first UI element created is UITableView. This will be used to insert into the UIView of the UIViewController, and we also retrieve width and height values of the UIView to stretch the UITableView to fit the entire bounds of the UIViewController. We must also call Add to insert the UITableView into the UIView. In order to fill the list with data, we need to create a UITableSource to contain the list of items to be displayed in the list. We will also need an object called GalleryModel; this will be the model of data to be displayed in each cell.

Follow the previous process for adding two new .cs files; one will be used to create our UITableSource class and the other for the GalleryModel class. In TableSource.cs, first we must import the Foundation library with the using statement:

using Foundation; 

Now for the rest of our class. Remember, we have to override specific functions for our UITableSource to describe its behavior. It must also include a list for containing the item view-models that will be used for the data displayed in each cell:

public class TableSource : UITableViewSource  
    { 
        protected List<GalleryItem> galleryItems; 
        protected string cellIdentifier = "GalleryCell"; 
 
        public TableSource (string[] items) 
        { 
            galleryItems = new List<GalleryItem> (); 
        } 
    } 

We must override the NumberOfSections function; in our case, it will always be one because we are not having list sections:

        public override nint NumberOfSections (UITableView tableView) 
        { 
            return 1; 
        } 

To determine the number of list items, we return the count of the list:

        public override nint RowsInSection (UITableView tableview, nint section) 
        { 
            return galleryItems.Count; 
        } 

Then we must add the GetCell function; this will be used to get the UITableViewCell to render for a particular row. But before we do this, we need to create a custom UITableViewCell.

Customizing a cell's appearance

We are now going to design our cells that will appear for every model found in the TableSource class. Add a new .cs file for our custom UITableViewCell.

Note

We are not going to use a .xib and simply build the user interface directly in code using a single .cs file.

Now for the implementation:

public class GalleryCell: UITableViewCell   
    { 
        private UIImageView _imageView; 
 
        private UILabel _titleLabel; 
 
        private UILabel _dateLabel; 
 
        public GalleryCell (string cellId) : base (UITableViewCellStyle.Default, cellId) 
        { 
            SelectionStyle = UITableViewCellSelectionStyle.Gray; 
 
            _imageView = new UIImageView() 
            { 
                TranslatesAutoresizingMaskIntoConstraints = false, 
            }; 
 
            _titleLabel = new UILabel ()  
            { 
                TranslatesAutoresizingMaskIntoConstraints = false, 
            }; 
 
            _dateLabel = new UILabel ()  
            { 
                TranslatesAutoresizingMaskIntoConstraints = false, 
            }; 
 
            ContentView.Add (imageView); 
            ContentView.Add (titleLabel); 
            ContentView.Add (dateLabel); 
        } 
    } 

Our constructor must call the base constructor, as we need to initialize each cell with a cell style and cell identifier. We then add a UIImageView and two UILabels for each cell, one for the filename and one for the date. Finally, we add all three elements to the main content view of the cell.

When we have our initializer, we add the following:

public void UpdateCell (GalleryItem gallery) 
        { 
            _imageView.Image = UIImage.LoadFromData (NSData.FromArray (gallery.ImageData)); 
            _titleLabel.Text = gallery.Title; 
            _dateLabel.Text = gallery.Date; 
        } 
 
        public override void LayoutSubviews () 
        { 
            base.LayoutSubviews (); 
 
            ContentView.TranslatesAutoresizingMaskIntoConstraints = false; 
 
            // set layout constraints for main view 
            AddConstraints (NSLayoutConstraint.FromVisualFormat("V:|[imageView(100)]|", NSLayoutFormatOptions.DirectionLeftToRight, null, new NSDictionary("imageView", imageView))); 
            AddConstraints (NSLayoutConstraint.FromVisualFormat("V:|[titleLabel]|", NSLayoutFormatOptions.DirectionLeftToRight, null, new NSDictionary("titleLabel", titleLabel))); 
            AddConstraints (NSLayoutConstraint.FromVisualFormat("H:|-10-[imageView(100)]-10-[titleLabel]-10-|", NSLayoutFormatOptions.AlignAllTop, null, new NSDictionary ("imageView", imageView, "titleLabel", titleLabel))); 
            AddConstraints (NSLayoutConstraint.FromVisualFormat("H:|-10-[imageView(100)]-10-[dateLabel]-10-|", NSLayoutFormatOptions.AlignAllTop, null, new NSDictionary ("imageView", imageView, "dateLabel", dateLabel))); 
        } 

Our first function, UpdateCell, simply adds the model data to the view, and our second function overrides the LayoutSubViews method of the UITableViewCell class (equivalent to the ViewDidLoad function of a UIViewController).

Now that we have our cell design, let's create the properties required for the view-model. We only want to store data in our GalleryItem model, meaning we want to store images as byte arrays. Let's create a property for the item model:

namespace Gallery.iOS 
{ 
    using System; 
 
    public class GalleryItem 
    { 
        public byte[] ImageData; 
 
        public string ImageUri; 
 
        public string Title; 
 
        public string Date; 
 
        public GalleryItem () 
        { 
        } 
    } 
} 

Now back to our TableSource class. The next step is to implement the GetCell function:

public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) 
        { 
            var cell = (GalleryCell)tableView.DequeueReusableCell (CellIdentifier); 
            var galleryItem = galleryItems[indexPath.Row]; 
 
            if (cell == null) 
            {  
                // we create a new cell if this row has not been created yet 
                cell = new GalleryCell (CellIdentifier);  
            } 
 
            cell.UpdateCell (galleryItem); 
 
            return cell; 
        } 

Notice the cell reuse on the if statement; you should be familiar with this type of approach, it is a common pattern for reusing cell views and is the same as the Objective-C implementation (this is a very basic cell reuse implementation). We also call the UpdateCell method to pass in the required GalleryItem data to show in the cell. Let's also set a constant height for all cells. Add the following to your TableSource class:

public override nfloat GetHeightForRow (UITableView tableView, NSIndexPath indexPath) 
        { 
            return 100; 
        } 

So what is next?

public override void ViewDidLoad () 
{ 
.. 
table.Source = new TableSource(); 
.. 
} 

Let's stop development and have a look at what we have achieved so far. We have created our first UIViewController, UITableView, UITableViewSource, and UITableViewCell, and bound them all together. Fantastic!

We now need to access the local storage of the phone to pull out the required gallery items. But before we do this, we are going to create an Android project and replicate what we have done with iOS.

Creating an Android project

Our first step is to create new general Android app:

Creating an Android project

The first screen you will land on is MainActivity. This is our starting activity, which will inflate the first user interface; take notice of the configuration attributes:

[Activity (Label = "Gallery.Droid", MainLauncher = true, Icon = "@mipmap/icon")] 

The MainLauncher flag indicates the starting activity; one activity must have this flag set to true so the application knows what activity to load first. The icon property is used to set the application icon, and the Label property is used to set the text of the application, which appears in the top left of the navigation bar:

namespace Gallery.Droid 
{ 
    using Android.App; 
    using Android.Widget; 
    using Android.OS; 
 
    [Activity (Label = "Gallery.Droid", MainLauncher = true, Icon = "@mipmap/icon")] 
    public class MainActivity : Activity 
    { 
        int count = 1; 
 
        protected override void OnCreate (Bundle savedInstanceState) 
        { 
            base.OnCreate (savedInstanceState); 
 
            // Set our view from the "main" layout resource 
            SetContentView (Resource.Layout.Main); 
        } 
    } 
} 

The formula for our activities is the same as Java; we must override the OnCreate method for each activity where we will inflate the first XML interface Main.xml.

Creating an XML interface and ListView

Our starting point is the main.xml sheet; this is where we will be creating the ListView:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent"> 
    <ListView 
        android:id="@+id/listView" 
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent" 
        android:layout_marginBottom="10dp" 
        android:layout_marginTop="5dp" 
        android:background="@android:color/transparent" 
        android:cacheColorHint="@android:color/transparent" 
        android:divider="#CCCCCC" 
        android:dividerHeight="1dp" 
        android:paddingLeft="2dp" /> 
</LinearLayout> 

Tip

The main.xml file should already be in the resource | layout directory, so simply copy and paste the previous code into this file.

Excellent! We now have our starting activity and interface, so now we have to create a ListAdapter for our ListView. An adapter works very much like a UITableSource, where we must override functions to determine cell data, row design, and the number of items in the list.

Note

Xamarin Studio also has an Android GUI designer.

Right-click on the Android project and add a new empty class file for our adapter class. Our class must inherit the BaseAdapter class, and we are going to override the following functions:

public override long GetItemId(int position); 
 
public override View GetView(int position, View convertView, ViewGroup parent); 

Before we go any further, we need to create a model for the objects used to contain the data to be presented in each row. In our iOS project, we created a GalleryItem to hold the byte array of image data used to create each UIImage. We have two approaches here: we could create another object to do the same as the GalleryItem, or even better, why don't we reuse this object using a shared project?

Shared projects

We are going to delve into our first technique for sharing code between different platforms. This is what Xamarin wants us to achieve, and reuse as much code as possible. The biggest disadvantage when developing natively is two different language, and we can't reuse anything.

Let's create our first shared project:

Shared projects

Our shared project will be used to contain the GalleryItem model, so whatever code we include in this shared project can be accessed by both the iOS and Android projects:

Shared projects

In the preceding screenshot, have a look at the Solution explorer, and notice how the shared project doesn't contain anything more than .cs code sheets. Shared projects do not have any references or components, just code that is shared by all platform projects. When our native projects reference these shared projects, any libraries being referenced via using statements come from the native projects.

Now we must have the iOS and Android projects reference the shared project; right-click on the References folder and select Edit References:

Shared projects

Select the shared project you just created and we can now reference the GalleryItem object from both projects.

Custom row appearance

Let's get back to the ListAdapter implementation and design our ListView row appearance. Open the Resources | Layout folder, create a new .xml file for the cell appearance, call it CustomCell.xml, and copy in the following XML code:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="horizontal" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:weightSum="4"> 
    <LinearLayout 
        android:orientation="vertical" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" 
        android:layout_weight="1"> 
        <ImageView 
            android:id="@+id/image" 
            android:layout_width="100dp" 
            android:layout_height="100dp" 
            android:adjustViewBounds="true" /> 
    </LinearLayout> 
    <LinearLayout 
        android:orientation="vertical" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" 
        android:layout_weight="3" 
        android:weightSum="2"> 
        <TextView 
            android:id="@+id/title" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:layout_weight="1" /> 
        <TextView 
            android:id="@+id/date" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:layout_weight="1" /> 
    </LinearLayout> 
</LinearLayout> 

We are creating the same layout as the custom cell made for iOS, but in Android we will use the ImageView and TextView objects. Now that we have our custom cell, we can implement the the GetView function. The GetView function is exactly like the GetCell function in the preceding UITableSource implementation. Open up the ListAdapter.cs file and continue with the list adapter implementation:

public class ListAdapter : BaseAdapter 
    { 
        private List<GalleryItem> _items; 
        private Activity _context; 
 
        public ListAdapter(Activity context) : base() 
        { 
            _context = context; 
            _items = new List<GalleryItem>(); 
        } 
 
        public override Java.Lang.Object GetItem (int position) 
        { 
            return null; 
        } 
 
        public override long GetItemId(int position) 
        { 
            return position; 
        } 
 
        public override int Count 
        { 
            get 
            { 
                return items.Count;  
            } 
        } 
} 

We override the Count property and functions GetItemId and GetItem, to return the number of gallery items in our list. These override functions are exactly the same as the overrides in Java for any BaseAdapter inherited class. Now for the GetView function:

public override View GetView(int position, View convertView, ViewGroup parent) 
        { 
            View view = convertView; // re-use an existing view, if one is available 
 
            if (view == null) 
            { 
                // otherwise create a new one 
                view = context.LayoutInflater.Inflate(Resource.Layout.CustomCell, null); 
            } 
 
            // set image 
            var imageView = view.FindViewById<ImageView> (Resource.Id.image); 
            BitmapHelpers.CreateBitmap (imageView, _items [position].ImageData); 
 
            // set labels 
            var titleTextView = view.FindViewById<TextView> (Resource.Id.title); 
            titleTextView.Text = _items[position].Title; 
            var dateTextView = view.FindViewById<TextView> (Resource.Id.date); 
            dateTextView.Text = _items[position].Date; 
 
            return view; 
        } 
 
        private async void createBitmap(ImageView imageView, byte[] imageData) 
        { 
            try 
            { 
                if (imageData != null)  
                { 
                    var bm = await BitmapFactory.DecodeByteArrayAsync(imageData, 0, imageData.Length); 
                    if (bm != null)  
                    { 
                        imageView.SetImageBitmap(bm); 
                    } 
                } 
            } 
            catch (Exception e)  
            { 
                Console.WriteLine ("Bitmap creation failed: " + e); 
            } 
        } 

Notice in the GetView function we are using the CustomCell layout for each row; we also have a private method for creating our bitmaps from each model's byte array.

If we have a look at the current implementation, what do we notice here?

We are creating a bitmap every time the cell requires this data again for the view; is this efficient? No, we should be reusing bitmaps and memory as much as possible.

This tends to be a common issue with Android ListView.

What is the most memory efficient way to reuse bitmaps across hundreds of items in a ListView while scrolling and staying smooth as we move down the list at various speeds? How can we tackle this problem? Let's have a look at how we can approach this problem.

Firstly, we need to implement an object called ImageHandler. This will contain the logic for retrieving byte arrays from all gallery images on an Android device. Create a new file, name it ImageHandler, and start importing these namespaces:

namespace Gallery.Droid 
{ 
    using System; 
    using System.Collections.Generic; 
 
    using Android.Database; 
    using Android.Content; 
    using Android.Provider; 
 
    using Gallery.Shared; 
 
    public static class ImageHandler 
    { 
    } 
} 

This class will include a function, GetFiles, which will create gallery items based upon the items pulled from any device's gallery using the ContentResolver interface:

public static IEnumerable<GalleryItem> GetFiles(Context context) 
        { 
            ContentResolver cr = context.ContentResolver; 
 
            string[] columns = new string[]  
            { 
                MediaStore.Images.ImageColumns.Id, 
                MediaStore.Images.ImageColumns.Title, 
                MediaStore.Images.ImageColumns.Data, 
                MediaStore.Images.ImageColumns.DateAdded, 
                MediaStore.Images.ImageColumns.MimeType, 
                MediaStore.Images.ImageColumns.Size, 
            }; 
             
            var cursor = cr.Query(MediaStore.Images.Media.ExternalContentUri, columns, null, null, null); 
 
            int columnIndex = cursor.GetColumnIndex(columns[2]); 
 
            int index = 0; 
 
            // create max 100 items 
            while (cursor.MoveToNext () && index < 100)  
            { 
                index++; 
 
                var url = cursor.GetString(columnIndex); 
 
                var imageData = createCompressedImageDataFromBitmap (url); 
 
                yield return new GalleryItem ()  
                { 
                    Title = cursor.GetString(1), 
                    Date = cursor.GetString(3), 
                    ImageData = imageData, 
                    ImageUri = url, 
                }; 
            } 
        } 

Using ContentResolver (used to access the content model), we resolve URIs to specific content providers. A content provider provides queries to content, in our case image files. We simply create an access query off the main context's ContentResolver instance, and we provide an array of columns for the query to retrieve (for example, file titles, file data, file size, and so on). The first parameter is as follows:

"MediaStore.Images.Media.ExternalContentUri" 

This is used for retrieving the URI to each piece of content returned from the query. Finally, we now have a cursor to iterate through, exactly like an Enumerable, which will loop to the end until there are no more items, and for each iteration we pull the data and URI columns and create a new GalleryItem. You will notice a little trick here with the yield keyword: if we call this function, it will actually return the entire Enumerable from start to finish. Calling the function starts for each-ing over the object; the function is called again until it yields. In the return from calling this function, we get an Enumerable of all the items retrieved from the query as gallery items with image information and local URI.

Bitmap functions

What about the byte data? First, let's implement our BitmapHelpers; these will include two global functions to help with bitmap processing:

public static int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) 
        { 
            // Raw height and width of image 
            float height = options.OutHeight; 
            float width = options.OutWidth; 
            double inSampleSize = 1D; 
 
            if (height > reqHeight || width > reqWidth) 
            { 
                int halfHeight = (int)(height / 2); 
                int halfWidth = (int)(width / 2); 
 
                // Calculate a inSampleSize that is a power of 2 - the decoder will use a value that is a power of two anyway. 
                while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) 
                { 
                    inSampleSize *= 2; 
                } 
            } 
 
            return (int)inSampleSize; 
        } 
 
        public static async void CreateBitmap(ImageView imageView, byte[] imageData) 
        { 
            try 
            { 
                if (imageData != null)  
                { 
                    var bm = await BitmapFactory.DecodeByteArrayAsync(imageData, 0, imageData.Length); 
                    if (bm != null)  
                    { 
                        imageView.SetImageBitmap(bm); 
                    } 
                } 
            } 
            catch (Exception e)  
            { 
                Console.WriteLine ("Bitmap creation failed: " + e); 
            } 
        } 

Our first function will determine the best sample size by the requested width and height. This is a very good technique for reducing the resources required to load an image into memory. Our next function is used to create a bitmap for the ImageView that is passed in from the byte data.

The next step is to create this image data using the private method createCompressedImageDataFromBitmap:

private static byte[] createCompressedImageDataFromBitmap(string url) 
        { 
            BitmapFactory.Options options = new BitmapFactory.Options (); 
            options.InJustDecodeBounds = true; 
            BitmapFactory.DecodeFile (url, options); 
            options.InSampleSize = BitmapHelpers.CalculateInSampleSize (options, 1600, 1200); 
            options.InJustDecodeBounds = false; 
 
            Bitmap bm = BitmapFactory.DecodeFile (url, options); 
 
            var stream = new MemoryStream (); 
            bm.Compress (Bitmap.CompressFormat.Jpeg, 80, stream); 
            return stream.ToArray (); 
        } 

This method will take the image URI and decode the bitmap options in order to sample the smallest possible size for the dimensions provided.

We have to make sure that we flag InJustDecodeBounds so this bitmap is not loaded into memory while we are retrieving the options information. This approach is very useful for reducing images to the size we require, thus saving memory. We then compress the image by 80% into a JPEG and convert the stream into a byte array for our GalleryItem model.

Now let's go back to the adapter class and add this method to fill in the items of our ListAdapter:

public ListAdapter(Activity context) : base() 
        { 
            _context = context; 
            _items = new List<GalleryItem>(); 
 
            foreach (var galleryitem in ImageHandler.GetFiles (_context)) 
            { 
                _items.Add (galleryitem); 
            } 
        } 

Note

Remember we must have a reference in our list adapter to the main context.

Now for the final piece of the puzzle, connecting the adapter to our list view. Open up the MainActivity.cs file and update the code list like so:

public class MainActivity : Activity 
    { 
        private ListAdapter _adapter; 
 
        protected override void OnCreate (Bundle savedInstanceState) 
        { 
            base.OnCreate (savedInstanceState); 
 
            SetContentView (Resource.Layout.Main); 
 
            _adapter = new ListAdapter (this); 
 
            var listView = FindViewById<ListView> (Resource.Id.listView); 
            listView.Adapter = adapter; 
        } 
    } 

And voila! Try running the application and watching the ListView update with the images in your device's Gallery folder. Congratulations! You have just developed your first Xamarin.Android application. Now we must replicate this approach for the iOS version.

Note

Notice the challenge with context switching when jumping back and forth between Android and iOS; it can get confusing. Luckily, with Xamarin we keep to just one programming language, which helps reduce the complexity.

The ALAssetLibrary

Jumping back into our iOS, we are going to use the ALAssetsLibrary class and call the Enumerate function by passing in the group type ALAssetsGroupType.SavedPhoto, the enumeration result delegate GroupEnumerator, and the error action that will be performed if an exception occurs.

Start by adding in a new .cs file for our iOS image handler:

Note

We are not going to use a static class with this object.

namespace Gallery.iOS 
{ 
    using System; 
    using System.Threading; 
 
    using UIKit; 
    using AssetsLibrary; 
    using Foundation; 
 
    /// <summary> 
    /// Image handler. 
    /// </summary> 
    public class ImageHandler 
    { 
        /// <summary> 
        /// The asset library. 
        /// </summary> 
        ALAssetsLibrary _assetLibrary; 
 
        /// <summary> 
        /// Initializes a new instance of the <see cref="Gallery.iOS.ImageHandler"/> class. 
        /// </summary> 
        public ImageHandler () 
        { 
            _assetLibrary = new ALAssetsLibrary(); 
            _assetLibrary.Enumerate(ALAssetsGroupType.SavedPhotos, GroupEnumerator, Console.WriteLine); 
        }  
    } 
} 

In our constructor, we create the new instance of the ALAssetsLibrary and call the Enumerate function; now let's add the GroupEnumerator delegate:

private void GroupEnumerator(ALAssetsGroup assetGroup, ref bool shouldStop) 
        { 
            if (assetGroup == null) 
            { 
                shouldStop = true; 
                NotifyAssetsLoaded (); 
 
                return; 
            } 
 
            if (!shouldStop) 
            { 
                assetGroup.Enumerate(AssetEnumerator); 
                shouldStop = false; 
            } 
        } 
 
        private void AssetEnumerator(ALAsset asset, nint index, ref bool shouldStop) 
        { 
            if (asset == null) 
            { 
                shouldStop = true; 
                return; 
            } 
 
            if (!shouldStop) 
            { 
                // add asset name to list 
                _assets.Add (asset.ToString()); 
                shouldStop = false; 
            } 
        } 
 
   private void NotifyAssetsLoaded() 
        { 
            if (AssetsLoaded != null) 
            { 
                AssetsLoaded (this, EventArgs.Empty); 
            } 
        } 

Notice the call to notify our event handler. This signals we have reached the end of the asset library, and we have retrieved all ALAsset in our gallery. We can now pull out a list of the file names, so we need to add another function that will pull out the ALAsset object synchronously:

public ALAsset SynchronousGetAsset(string filename) 
        { 
            ManualResetEvent waiter = new ManualResetEvent(false); 
            NSError error = null; 
            ALAsset result = null; 
            Exception exception;  
 
            ThreadPool.QueueUserWorkItem ((object state) => assetLibrary.AssetForUrl (new NSUrl (filename), (ALAsset asset) =>  
                { 
                    result = asset; 
                    waiter.Set (); 
                },  
                e =>  
                { 
                    error = e; 
                    waiter.Set (); 
                })); 
 
 
            if(!waiter.WaitOne (TimeSpan.FromSeconds (10))) 
                throw  new Exception("Error Getting Asset : Timeout, Asset=" + filename); 
 
            if (error != null) 
                throw new Exception (error.Description); 
 
            return result; 
        } 

Finally, we need a public function that will pull all the byte arrays and NSURL into an Enumerable of gallery items that we will use to populate the UITableView.

Tip

As this is only a demo, we are only going to take the first 100 items. If you would like another challenge, remove Take(100), and see if you can adjust the code to load thousands of images more efficiently.

foreach (var file in _assets.Take(100))  
            { 
                using (var asset = SynchronousGetAsset (file)) 
                { 
                    if (asset != null)  
                    { 
                        var thumbnail = asset.Thumbnail; 
                        var image = UIImage.FromImage (thumbnail); 
                        var jpegData = image.AsJPEG ().ToArray (); 
 
                        yield return new GalleryItem ()  
                        { 
                            Title = file, 
                            Date = asset.Date.ToString(), 
                            ImageData = jpegData, 
                            ImageUri = asset.AssetUrl.ToString () 
                        }; 
                    } 
                } 
            } 
        } 

Let's look a bit more closely at this function. We use the asset library object to pull out all the filenames we have in our gallery, then for each filename we pull out the ALAsset object, and from this we create a GalleryItem object for each, which takes the image data as a byte array from the ALAsset and the NSURL of the asset. Now let's create an instance of the ImageHandler inside our TableSource:

        private ImageHandler _imageHandler; 
 
        public TableSource (string[] items) 
        { 
            _galleryItems = new List<GalleryItem> (); 
            _imageHandler = new ImageHandler (); 
 
            foreach (var galleryItem in imageHandler.GetFiles ())  
            { 
                _galleryItems.Add (galleryItem); 
            } 
        } 

Excellent! Now we have our gallery items ready to display inside the table.

For the final piece of the iOS project, let's go back to our AppDelegate.cs file. We still need to implement the FinishedLaunching method. Our root controller is going to be a UINavigationController, which will use the MainController as the starting UIViewController:

public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) 
        { 
            _window = new UIWindow (UIScreen.MainScreen.Bounds); 
 
            MainController mainController = new MainController(); 
 
            var rootNavigationController = new UINavigationController(); 
            rootNavigationController.PushViewController(mainController, false); 
 
            _window.RootViewController = rootNavigationController; 
            _window.MakeKeyAndVisible (); 
 
            return true; 
        } 

We also adjust the window bounds the main screen bounds and call the function on the window at the very end of MakeKeyAndVisible.

Adding the iOS photo screen

Now that we have our list page, we want to add another UIViewController for displaying selected photos. Let's add a new UIViewController and call it PhotoController. In PhotoController, we are going to build a screen that simply displays the same content in the PhotoCell, but a bit larger.

First, let's add the navigation flow from MainController to PhotoController. We are going to be pushing a new PhotoController whenever a row is selected. Open up TableSource.cs and add the following; at the top, we need to add an EventHandler:

public event EventHandler<GalleryItem> 
 ItemSelected; 

Whenever the row is selected we want to fire this event:

public override void RowSelected (UITableView tableView, NSIndexPath indexPath) 
        { 
            if (ItemSelected != null) 
            { 
                ItemSelected (this, galleryItems[indexPath.Row]); 
            } 
 
            tableView.DeselectRow (indexPath, true); 
        } 

Whenever the row is selected, we want to fire this event and pass the gallery item for the index path row. Now we need to handle this event in the MainController class to push a new PhotoController on the navigation stack, but before we do this we need to implement PhotoController:

public partial class PhotoController : UIViewController 
    { 
        /// <summary> 
        /// The image view. 
        /// </summary> 
        private UIImageView _imageView; 
 
        /// <summary> 
        /// The title label. 
        /// </summary> 
        private UILabel _titleLabel; 
 
        /// <summary> 
        /// The date label. 
        /// </summary> 
        private UILabel _dateLabel; 
 
        /// <summary> 
        /// Initializes a new instance of the <see cref="Gallery.iOS.PhotoController"/> class. 
        /// </summary> 
        public PhotoController (ALAsset asset) : base ("PhotoController", null) 
        { 
            _imageView = new UIImageView() 
            { 
                TranslatesAutoresizingMaskIntoConstraints = false, 
                ContentMode = UIViewContentMode.ScaleAspectFit 
            }; 
 
            _titleLabel = new UILabel ()  
            { 
                TranslatesAutoresizingMaskIntoConstraints = false, 
            }; 
 
            _dateLabel = new UILabel ()  
            { 
                TranslatesAutoresizingMaskIntoConstraints = false, 
            }; 
 
            _imageView.Image = new UIImage(asset.DefaultRepresentation.GetFullScreenImage ()); 
            _titleLabel.Text = asset.DefaultRepresentation.Filename; 
            _dateLabel.Text = asset.Date.ToString(); 
        } 

This is very similar to our GalleryCell presentation, but this controller will stack the elements vertically and force the image to scale to fit, keeping the image's correct ratio to avoid any warping. Now let's add ViewDidLoad to lay out the views:

public override void ViewDidLoad () 
        { 
            base.ViewDidLoad (); 
 
            View.Add (_imageView); 
            View.Add (_titleLabel); 
            View.Add (_dateLabel); 
 
            // set layout constraints for main view 
            View.AddConstraints (NSLayoutConstraint.FromVisualFormat("V:|[imageView]-10-[titleLabel(50)]-10-[dateLabel(50)]|", NSLayoutFormatOptions.DirectionLeftToRight, null, new NSDictionary("imageView", imageView, "titleLabel", titleLabel, "dateLabel", dateLabel))); 
 
            View.AddConstraints (NSLayoutConstraint.FromVisualFormat("H:|[imageView]|", NSLayoutFormatOptions.AlignAllTop, null, new NSDictionary ("imageView", imageView))); 
            View.AddConstraints (NSLayoutConstraint.FromVisualFormat("H:|[titleLabel]|", NSLayoutFormatOptions.AlignAllTop, null, new NSDictionary ("titleLabel", titleLabel))); 
            View.AddConstraints (NSLayoutConstraint.FromVisualFormat("H:|[dateLabel]|", NSLayoutFormatOptions.AlignAllTop, null, new NSDictionary ("dateLabel", dateLabel))); 
        } 

There's nothing new here; we are simply adding the three elements and setting our layout constraints accordingly. We stretch all elements to the entire width of the view and stack elements down the pages with the image view on top and a dynamic size based upon the aspect size of the image.

Finally, the last step is to add the event handler whenever a row is selected. We use ImageHandler to fetch ALAsset by the title (filename) in the gallery item, then pass this into the constructor of a new PhotoController and update the constructor of MainController:

        public MainController () : base ("MainController", null) 
        { 
            _source = new TableSource (); 
 
            _source.ItemSelected += (sender, e) =>  
            { 
                var asset = _imageHandler.SynchronousGetAsset (e.Title); 
                NavigationController.PushViewController (new PhotoController (asset), true); 
            }; 
 
            _imageHandler = new ImageHandler (); 
            _imageHandler.AssetsLoaded += handleAssetsLoaded; 
        } 

Excellent! Now run the application and try selecting a few items in the list; you will be navigated to a new PhotoController which will display the selected ALAsset image with its filename and date information.

Adding the Android photo screen

Implementing a photo view for cell selections is very similar, although with Android we will be using an intent to create a new activity, which in turn will inflate a new view to display the image and details. Let's start by adding a new XML called photo_view.xml, and paste in the following code:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:weightSum="4"> 
    <LinearLayout 
        android:orientation="vertical" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" 
        android:layout_weight="1"> 
        <ImageView 
            android:id="@+id/image_photo" 
            android:scaleType="centerCrop" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:adjustViewBounds="true" /> 
    </LinearLayout> 
    <LinearLayout 
        android:orientation="vertical" 
        android:layout_width="match_parent" 
        android:layout_height="match_parent" 
        android:layout_weight="3" 
        android:weightSum="2"> 
        <TextView 
            android:id="@+id/title_photo" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:layout_weight="1" /> 
        <TextView 
            android:id="@+id/date_photo" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:layout_weight="1" /> 
    </LinearLayout> 
</LinearLayout> 

The layout is very much the same as the custom_cell.xml sheet, although we are going to stack items vertically and set the following two properties to keep the correct image aspect ratio:

android:adjustViewBounds="true" 
android:scaleType="centerCrop" 

Note

Make sure XML sheets do not contain the same IDs as any other XML sheet.

Now that we have our user interface for the PhotoActivity, let's add the new activity:

[Activity (Label = "Gallery.Droid", Icon = "@mipmap/icon")] 
    public class PhotoActivity : Activity 
    { 
        /// <summary> 
        /// Raises the create event. 
        /// </summary> 
        /// <param name="savedInstanceState">Saved instance state.</param> 
        protected override void OnCreate (Bundle savedInstanceState) 
        { 
            base.OnCreate (savedInstanceState); 
 
            // Set our view from the "main" layout resource 
            SetContentView (Resource.Layout.Photo); 
 
            var imageData = Intent.GetByteArrayExtra ("ImageData"); 
            var title = Intent.GetStringExtra ("Title") ?? string.Empty; 
            var date = Intent.GetStringExtra ("Date") ?? string.Empty; 
 
            // set image 
            var imageView = FindViewById<ImageView> (Resource.Id.image_photo); 
            BitmapHelpers.CreateBitmap (imageView, imageData); 
 
            // set labels 
            var titleTextView = FindViewById<TextView> (Resource.Id.title_photo); 
            titleTextView.Text = title; 
            var dateTextView = FindViewById<TextView> (Resource.Id.date_photo); 
            dateTextView.Text = date; 
        } 
    } 

Looking at this new activity, what can we see? Notice the attributes at the top:

[Activity (Label = "Gallery.Droid", Icon = "@mipmap/icon")] 

There is no MainLauncher tag because this is not our starting activity. We then add the intent.GetExtras for the image data and strings required to display on our Photo interface.

Now we need to make one addition to the ListAdapter class:

public GalleryItem GetItemByPosition (int position) 
{ 
     return _items[position]; 
} 

When an item in the list is selected, we need to be able to access the selected GalleryItem. Our next step is to add the ItemClick delegate for the ListView. Open up the MainActivity class and add the following to the OnCreate function:

listView.ItemClick += (object sender, AdapterView.ItemClickEventArgs e) =>  
            { 
                var galleryItem = adapter.GetItemByPosition (e.Position); 
                var photoActivity = new Intent(this, typeof(PhotoActivity)); 
                photoActivity.PutExtra ("ImageData", galleryItem.ImageData); 
                photoActivity.PutExtra ("Title", galleryItem.Title); 
                photoActivity.PutExtra ("Date", galleryItem.Date); 
                StartActivity(photoActivity); 
            }; 

Place this after we set the list adapter. When an item is clicked, we simply pull out the gallery item from our adapter by the position passed from the ItemClickEventArgs. Once we have the gallery item, we create the new PhotoActivity intent and pass the extras.

That is all; run the application and play around selecting cells to display the PhotoActivity.

Summary

In this chapter, we built a gallery application on both iOS and Android using native development with Xamarin. We learnt how to setup projects in Xamarin Studio and code using the native frameworks in C#. In the next chapter, we will build a text to speech service using Xamarin.Forms.

Try improving on this code and make this function asynchronous; the more background processing we have at this stage, the better. These are the small improvements we should take time with, as combining all these small additions can create a real difference to the speed of your application.

As this is only a demo, we are only going to take the first 100 items. If you would like another challenge, remove Take(100), and see if you can adjust the code to load thousands of images more  efficiently.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Helps you get a clear practical understanding of creating professional-grade apps with Xamarin
  • Covers Xamarin.Forms, Xamarin Android, and Xamarin iOS
  • If you want to transform yourself from an amateur mobile developer into a professional app developer across multiple platforms, then this is the ideal book for you

Description

Do you want to create powerful, efficient, and independent apps from scratch that will leverage the Xamarin framework and code with C#? Well, look no further; you’ve come to the right place! This is a learn-as-you-build practical guide to building eight full-fledged applications using Xamarin.Forms, Xamarin Android, and Xamarin iOS. Each chapter includes a project, takes you through the process of building applications (such as a gallery Application, a text-to-speech service app, a GPS locator app, and a stock market app), and will show you how to deploy the application’s source code to a Google Cloud Source Repository. Other practical projects include a chat and a media-editing app, as well as other examples fit to adorn any developer’s utility belt. In the course of building applications, this book will teach you how to design and prototype professional-grade applications implementing performance and security considerations.

Who is this book for?

If you are a mobile developer looking to create interesting and fully featured apps for different platforms, then this book is the ideal solution for you. A basic knowledge of Xamarin and C# programming is assumed

What you will learn

  • Discover eight different ways to create your own Xamarin applications
  • Improve app performance by using SQLite for data-intensive applications
  • Set up a simple web service to feed JSON data into mobile applications
  • Store files locally with Xamarin.Forms using dependency services
  • Use Xamarin extension libraries to create effective applications with less coding

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Sep 30, 2016
Length: 516 pages
Edition : 1st
Language : English
ISBN-13 : 9781785889486
Vendor :
Microsoft
Category :
Languages :

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
OR
Modal Close icon
Payment Processing...
tick Completed

Billing Address

Product Details

Publication date : Sep 30, 2016
Length: 516 pages
Edition : 1st
Language : English
ISBN-13 : 9781785889486
Vendor :
Microsoft
Category :
Languages :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€18.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
€189.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
€264.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total 120.98
Xamarin: Cross-Platform Mobile Application Development
€74.99
Xamarin Blueprints
€45.99
Total 120.98 Stars icon
Banner background image

Table of Contents

8 Chapters
1. Building a Gallery Application Chevron down icon Chevron up icon
2. Building a SpeechTalk Application Chevron down icon Chevron up icon
3. Building a GPS Locator Application Chevron down icon Chevron up icon
4. Building an Audio Player Application Chevron down icon Chevron up icon
5. Building a Stocklist Application Chevron down icon Chevron up icon
6. Building a Chat Application Chevron down icon Chevron up icon
7. Building a File Storage Application Chevron down icon Chevron up icon
8. Building a Camera Application Chevron down icon Chevron up icon

Customer reviews

Rating distribution
Full star icon Full star icon Full star icon Full star icon Full star icon 5
(1 Ratings)
5 star 100%
4 star 0%
3 star 0%
2 star 0%
1 star 0%
Xavier Minaya Ruiz May 16, 2017
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Very good book. It combines Xamarin.Forms and native coding patterns. Recommended.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.