Now that our mobile app can download the photos, we need to display them on our app.
A typical way to display such data is through a scrolling list, with the most recent items at the top. Xamarin.Forms provides a ListView
that we will use to display such a scrolling list.
ListView
s can be bound to a collection of objects, and each item in the collection is used to supply the data for an item in the list. The common way to expose such a list is using an ObservableCollection
. This collection type raises an event when the collection changes, such as having new items added. A ListView
bound to an ObservableCollection
will automatically incorporate the changes to the UI. We will put a ListView
on MainPage.xaml
and bind it to an ObservableCollection
in MainViewModel
.
What object should be in the observable collection? Ideally you want a model that represents each individual item, exposing the photo and metadata as properties.
Property | Type | Description |
---|---|---|
Caption |
string |
The caption for the photo generated by the Computer Vision service |
Tags |
string |
The tags for the photo generated by the Computer Vision service, concatenated into a single string |
Timestamp |
long |
A timestamp for when the document was created |
Photo |
ImageSource |
An image source for the photo, this will be created from the file name |
-
In the Visual Studio Solution Explorer, right-click HappyXamDevs > Models > Add > Class
- (Mac) On Visual Studio for Mac, right-click HappyXamDevs > Models > Add > New File
-
In the Add New Item window, name the file
PhotoModel.cs
-
In the PhotoModel.cs editor, enter the following code:
using System.Linq;
using Xamarin.Forms;
namespace HappyXamDevs.Models
{
public class PhotoModel
{
public PhotoModel(PhotoMetadataModel photoMetadata)
{
Caption = photoMetadata.Caption;
Timestamp = photoMetadata.Timestamp;
Tags = string.Join(" ", photoMetadata.Tags.Select(t => $"#{t}"));
Photo = ImageSource.FromFile(photoMetadata.FileName);
}
public string Caption { get; }
public ImageSource Photo { get; }
public string Tags { get; }
public long Timestamp { get; }
}
}
-
In the Visual Studio Solution Explorer, open HappyXamDevs > ViewModels > MainViewModel.cs
-
In the MainViewModel.cs editor, add the following using statement:
using System.Collections.ObjectModel;
using System.Linq;
using HappyXamDevs.Models;
- In the MainViewModel.cs editor, add the following field:
private bool isRefreshing;
- In the MainViewModel.cs editor, add the following property:
public bool IsRefreshing
{
get => isRefreshing;
set => Set(ref isRefreshing, value);
}
About the Code
IsRefreshing
will be used to display the spinning indicator when a pull-to-refresh is triggered
- In the MainViewModel.cs editor, add the following properties:
public ObservableCollection<PhotoModel> Photos { get; } = new ObservableCollection<PhotoModel>();
public ICommand RefreshCommand { get; }
About the Code
ObservableCollection<PhotoViewModel> Photos
this is an IEnumerable that will contain the photos displayed on the UI
ICommand RefreshCommand
this command will be triggered when the user initiates a pull-to-refresh on theListView
- In the MainViewModel.cs editor, add the following method:
private async Task Refresh()
{
var photos = await azureService.GetAllPhotoMetadata();
if (!Photos.Any())
{
foreach (var photo in photos.OrderByDescending(p => p.Timestamp))
{
Photos.Add(new PhotoModel(photo));
}
}
else
{
var latest = Photos[0].Timestamp;
foreach (var photo in photos.Where(p => p.Timestamp > latest).OrderBy(p => p.Timestamp))
{
Photos.Insert(0, new PhotoModel(photo));
}
}
IsRefreshing = false;
}
About the Code
azureService.GetAllPhotoMetadata();
retrieves the photo meta data from our Azure Function
if (!Photos.Any())
ifPhotos
is empty, add all of the photos to theObservableCollection
from newest to oldest
else
otherwise, only add the newest photos to theObservableCollection
- In the MainViewModel.cs editor, update the constructor using this code:
public MainViewModel()
{
TakePhotoCommand = new Command(async () => await TakePhoto());
SelectFromLibraryCommand = new Command(async () => await SelectFromLibrary());
RefreshCommand = new Command(async () => await Refresh());
azureService = DependencyService.Get<IAzureService>();
}
About the Code
RefreshCommand = new Command(async () => await Refresh());
initializesRefreshCommand
, instructing it to runRefresh()
when it is triggered
ListView
use a DataTemplate
to specify how to display the items in the list.
Xamarin.Forms
includes three templates for displaying text & images; we will use the built-in ImageCell
template to display the photos. This UI will be improved later in this workshop.
-
In the Visual Studio Solution Explorer, open HappyXamDevs > MainPage.xaml
-
In the MainPage.xaml editor, enter the following code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Title="Happy Developers"
xmlns:viewModels="clr-namespace:HappyXamDevs.ViewModels"
x:Class="HappyXamDevs.MainPage">
<ContentPage.BindingContext>
<viewModels:MainViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Order="Primary"
Icon="TakePhoto.png"
Priority="0"
Command="{Binding TakePhotoCommand}" />
<ToolbarItem Order="Primary"
Icon="SelectFromLibrary.png"
Priority="1"
Command="{Binding SelectFromLibraryCommand}" />
</ContentPage.ToolbarItems>
<ListView x:Name="PhotosListView"
ItemsSource="{Binding Photos}"
IsRefreshing="{Binding IsRefreshing}"
RefreshCommand="{Binding RefreshCommand}"
IsPullToRefreshEnabled="true"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell ImageSource="{Binding Photo}"
Text="{Binding Caption}"
Detail="{Binding Tags}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
About the Code
ItemsSource="{Binding Photos}"
binds theListView.ItemSource
to theObservableCollection
,MainViewModel.Photos
RefreshCommand="{Binding RefreshCommand}"
binds theListView.RefreshCommand
toICommand MainViewModel.Refresh
. This will trigger `ICommand
<DataTemplate> <ImageCell ...
uses Xamarin.Forms' baked-inImageCell
template to display eachPhotoModel
.ImageSource
is bound toPhotoModel.Photo
, displaying the image in bold text.Text
is bound toPhotoModel.Caption
, displaying each photo's caption next to the photo.Detail
is bound toPhotoModel.Tags
, displaying each photo's tags in a smaller font underneath its caption.
When the main page is launched, it checks to see if the user is logged in, and if not shows the login page. After the user logs in, MainPage
should automatically load all the photos.
-
In the Visual Studio Solution Explorer, open HappyXamDevs > MainPage.xaml.cs
-
In the MainPage.xaml.cs editor, enter the following code:
using System.Linq;
using HappyXamDevs.Services;
using Xamarin.Forms;
namespace HappyXamDevs
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override async void OnAppearing()
{
base.OnAppearing();
var azureService = DependencyService.Get<IAzureService>();
if (!azureService.IsLoggedIn())
{
if(!Navigation.ModalStack.Any())
await Navigation.PushModalAsync(new LoginPage(), false);
}
else
{
PhotosListView.BeginRefresh();
}
}
}
}
About the Code
else { PhotosListView.BeginRefresh(); }
WhenMainPage
appears on the screen, if the user is logged in, theListView
will automatically trigger pull-to-refresh
You should now have everything in place to view the photos that have been uploaded. Run the app on your platform of choice and you will see the photos with their captions and tags.
-
In Visual Studio, right-click on HappyXamDevs.Android > Set as Startup Project
-
(PC) In Visual Studio, select Debug > Start Debugging
- (Mac) In Visual Studio for Mac, select Run > Start Debugging
-
On the Android device, if the LoginPage complete the login flow
-
On the Android device, on the MainPage, tap the Camera icon
-
On the Android device, if prompted for permission, tap Allow
-
On the Android device, ensure the Camera appears
-
On the Android device, take a happy-looking selfie
-
On the Android device, trigger a pull-to-refresh
-
On the Android device, ensure the uploaded photo and its associated metadata appear
Note: Repeat the pull-to-refresh a few times until the image appears, because the Azure Functions may take a few seconds to complete. If the image still doesn't appear after a minute, there likely is a bug.
-
In Visual Studio, right-click on HappyXamDevs.iOS > Set as Startup Project
-
(PC) In Visual Studio, select Debug > Start Debugging
- (Mac) In Visual Studio for Mac, select Run > Start Debugging
-
On the iOS device, if the LoginPage complete the login flow
-
On the iOS device, on the MainPage, tap the Camera icon
-
On the iOS device, if prompted for permission, tap Allow
-
On the iOS device, ensure the Camera appears
-
On the iOS device, take a happy-looking selfie
-
On the iOS device, trigger a pull-to-refresh
-
On the iOS device, ensure the uploaded photo and its associated metadata appear
Note: Repeat the pull-to-refresh a few times until the image appears, because the Azure Functions may take a few seconds to complete. If the image still doesn't appear after a minute, there likely is a bug.
-
(PC) In Visual Studio, right-click on HappyXamDevs.UWP > Set as Startup Project
- (Mac) Skip this step
-
(PC) In Visual Studio, select Debug > Start Debugging
- (Mac) Skip this step
-
On the UWP device, if the LoginPage complete the login flow
-
On the UWP device, on the MainPage, tap the Camera icon
-
On the UWP device, if prompted for permission, tap Allow
-
On the UWP device, ensure the Camera appears
-
On the UWP device, take a happy-looking selfie
-
On the UWP device, trigger a pull-to-refresh
-
On the UWP device, ensure the uploaded photo and its associated metadata appear
Note: Repeat the pull-to-refresh a few times until the image appears, because the Azure Functions may take a few seconds to complete. If the image still doesn't appear after a minute, there likely is a bug.
Now that you have a basic UI to show photos, the next step is to improve the UI.