Xamarin Guide 3: Create the SessionsView

This Xamarin Workshop Guide was created for the The Portuguese National Meeting of IT Students (ENEI) by Sara Silva of which the original content is available here. To extend it to the global community, it was published in a new project called Xam Community Workshop that the main goal is for any developer or user group to customize it to their events.

Before reading this article you must read:

Guide 3: Create the SessionsView

In this step, you will create the user interface to the SessionsView (which is the main page) to show the data defined in the last step.

3.1. Create the XAML page

In Xamarin Studio, select the ENEI.SessionsApp project and create a folder called “Views”, as described in Figure 1 and Figure 2.


Figure 1: Creating new folder


Figure 2: The Views folder in the project


Then select the folder “Views” then double-click the mouse to open the context and the click on “Add” > “New File...” as shown in Figure 3:


Figure 3: Add new file

Select “Forms ContentPage XAML”, as in the following:


Figure 4: Adding a XAML page

The result will be something as in the following:


Figure 5: The XAML page code

The SessionsView XAML is defined by:
  • The XAML file that defines the user interface
  • The cs file that defines the code behind

This approach is really useful when designers and developers work together and using the MVVM pattern allows you to have a UI separated from the behavior of the page.

See more about Xamarin.Forms XAML Basics.  

Now let's change the App.cs in ENEI.SessionsApp to use the SessionsView:
  1. public class App : Application  
  2. {  
  3.       public App()  
  4.       {  
  5.           // The root page of your application  
  6.           MainPage = new NavigationPage(new SessionsView())  
  7.           {  
  8.                 BarBackgroundColor = Color.White,  
  9.                 BarTextColor = Color.Black  
  10.           };  
  11.       }  

In this case, you defined the MainPage with a NavigationPage that the content is defined by SessionView. The NavigationPage is required when the application requires navigation between a page and the application that should have only one NavigationPage.

At this moment, if you run the application you will not have content and you should have something such as described in Figure 6.


Figure 6: The XAML page code

Now let's define the UI.

3.2. Create the Header

The first thing you should define is the Title and the Icon used in the header of the page, something such as in the following:


Figure 7: The header

That code can be:
  1. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
  2.     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
  3.     x:Class="ENEI.SessionsApp.Views.SessionsView"  
  4.     Title="1010 ENEI || Sessões"  
  5.     BackgroundColor="White"  
  6.     Icon="ic_action_users.png"> 
This will be showed in Android and in iOS but for Windows Phone 8.0 we need to create a workaround for it, as we will see below.

In Android, more specific in the MainActivity is required (in this case) to set the Icon in ActionBar as in the following: ActionBar.SetIcon(Resource.Drawable.ic_action_users);

The SessionsView is a ContentPage that is a simple page provided by Xamarin Forms API (see more in Xamarin Forms Gallery). To define its content, you should use controls layouts like StackLayout or Grid, for example.


Figure 8: Control Layouts

This way, let's choose the Grid as control layout to define two rows and one columns, where:
  • The first row has the header for Windows Phone (hidden to the others platforms)
  • The second row has the ListView to show the sessions

The code for it will be something such as in the following:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
  4.              x:Class="ENEI.SessionsApp.Views.SessionsView"  
  5.              Title="1010 ENEI || Sessões"  
  6.              BackgroundColor="White"  
  7.              Icon="ic_action_users.png">  
  8.   <Grid BackgroundColor="White">  
  9.     <Grid.RowDefinitions>  
  10.       <RowDefinition Height="Auto" />  
  11.       <RowDefinition Height="*" />  
  12.     </Grid.RowDefinitions>  
  13.       
  14.     <!-- Title - Only for WP-->  
  15.     <StackLayout Grid.Row="0" Orientation="Horizontal" Padding="20,10,0,0">  
  16.       <StackLayout.IsVisible>  
  17.         <OnPlatform Android="false"  
  18.                     WinPhone="true"  
  19.                     iOS="false"  
  20.                     x:TypeArguments="x:Boolean" />  
  21.       </StackLayout.IsVisible>  
  22.       <Image WidthRequest="48"   
  23.              HeightRequest="38"   
  24.              Source="Images/ic_action_users.png"/>  
  25.       <Label FontSize="Large" FontAttributes="Bold"  
  26.            TextColor="Black">  
  27.   
  28.         <OnPlatform Android=""  
  29.                     WinPhone="1010 ENEI || Sessões"  
  30.                     iOS=""  
  31.                     x:TypeArguments="x:String" />  
  32.       </Label>  
  33.     </StackLayout>  
  34.     <!-- ListView will be defined here -->  
  35.   </Grid>  
  36. </ContentPage> 
3.3 Defining the Images for each platform

In the last step you used the image “ic_action_users.png” that must be added to each project (ENEI.SessionApp.Android, ENEI.SessionApp.iOS and ENEI.SessionApp.WinPhone). This way, each app will have its own images that should be defined following the platform specifications, in other words, each image should provide the right resolution by platform.

Get the images and assets by platform here and see more about this subject in this article.

At this moment, you should have:


Figure 9: The Windows Phone, iOS and Android applications

3.4 Creating the Listview


The next step is to define the list of the sessions from the 1010 ENEI that were defined in the topic “The Data Source”.

To show the list of the sessions you will use a ListView that must have:
  • ItemsSource defined with the list of sessions
  • ItemTemplate defined with the template for each row

Let's define the first version of the ListView as in the following:

  1. <!-- ListView will be defined here -->  
  2.     <ListView x:Name="SessionsList"  
  3.                  Grid.Row="1"  
  4.                  ItemSelected="SessionsList_OnItemSelected"  
  5.                  ItemsSource="{Binding Sessions}"  
  6.                  SeparatorColor="#0094FF">  
  7.       <!--  
  8.                 Setting the HasUnevenRows property tells the list view to render  
  9.                 each cell with a different height.  
  10.             -->  
  11.       <ListView.RowHeight>  
  12.         <OnPlatform Android="150"  
  13.                     WinPhone="180"  
  14.                     iOS="150"  
  15.                     x:TypeArguments="x:Int32" />  
  16.       </ListView.RowHeight>  
  17.     </ListView> 
The ListView has the name “SessionsList” that is defined in the second row of the grid that defines the root content of the page, it has the “ItemSelected” subscribed to ignore the “SelectItem”, the “SeparatorColor" is defined as Blue and the “ItemsSource” is binding with the “Sessions” property (that is a property from the object defined in “BindingContext”).

Data bindings allow properties of two objects to be linked so that a change in one causes a change in the other. See more about it in these article Data Binding Basics and From Data Bindings to MVVM

Each row from the ListView can be defined with a static or dynamic size. In this case a static size was defined for each platform (related with screen resolution). A developer that needs to have a different row's size depending on the data shown is recommended to use the property HasUnevenRows.

To complete, in the code behind, you need to define the Sessions that should be an “ObservableCollection” of Session (this kind of list is allowed to notify the UI each time an object is removed or added in the list). You need to get the sessions from the “SessionsDataSource” and you need to define the “BindingContext”. The data should be loaded in the “OnAppearing” method and is not recommended to load it in the constructor of the page, because it will increase the time required to create the page and that can create issues.
  1. public partial class SessionsView : ContentPage  
  2.     {  
  3.         public SessionsView()  
  4.         {  
  5.             InitializeComponent();  
  6.             Sessions = new ObservableCollection<Session>();  
  7.             BindingContext = this;  
  8.   
  9.         }  
  10.   
  11.         public ObservableCollection<Session> Sessions { getset; }  
  12.   
  13.         protected override void OnAppearing()  
  14.         {  
  15.             base.OnAppearing();  
  16.             if (Sessions.Count == 0)  
  17.             {  
  18.                 var sessions = SessionsDataSource.GetSessions();  
  19.                 foreach (var session in sessions)  
  20.                 {  
  21.                     Sessions.Add(session);  
  22.                 }  
  23.             }  
  24.         }  
  25.         private void SessionsList_OnItemSelected(object sender, SelectedItemChangedEventArgs e)  
  26.         {  
  27.             if (SessionsList.SelectedItem == null)  
  28.             {  
  29.                 return;  
  30.             }  
  31.   
  32.             SessionsList.SelectedItem = null;  
  33.         }  

In this case, you will not use the MVVM pattern that is extremely recommended in real projects. To keep it simple the code behind will have the behavior used by the UI that in a MVVM pattern is defined in the ViewModel.
 
3.5 Creating the ItemTemplate

The ListView has the “ItemTemplate” property that allows defining a “DataTemplate” for each row. In this case you will define a template as described in Figure 10, that code will be:
  1. <ListView.ItemTemplate>  
  2.          <DataTemplate>  
  3.                     <ViewCell>  
  4.                         <ViewCell.View>  
  5.                             <Grid BackgroundColor="Transparent" Padding="20,0,20,0">  
  6.                                 <Grid.ColumnDefinitions>  
  7.                                     <ColumnDefinition Width="Auto" />  
  8.                                     <ColumnDefinition Width="*" />  
  9.                                 </Grid.ColumnDefinitions>  
  10.                                 <Grid.RowDefinitions>  
  11.                                     <RowDefinition Height="5" />  
  12.                                     <RowDefinition Height="Auto" />  
  13.                                     <RowDefinition Height="48" />  
  14.                                     <RowDefinition Height="5" />  
  15.                                 </Grid.RowDefinitions>  
  16.                                 <!-- Define the Image from Speaker -->  
  17.                                 <Image Grid.Row="1"  
  18.                                        HorizontalOptions="StartAndExpand"  
  19.                                        Source="{Binding Speaker.ImageUrl}"  
  20.                                        VerticalOptions="Start">  
  21.                                     <Image.WidthRequest>  
  22.                                         <OnPlatform Android="50"  
  23.                                                     WinPhone="100"  iOS="50"  
  24.                                                     x:TypeArguments="x:Double" />  
  25.                                     </Image.WidthRequest>  
  26.                                     <Image.HeightRequest>  
  27.                                         <OnPlatform Android="50"  
  28.                                                     WinPhone="100" iOS="50"  
  29.                                                     x:TypeArguments="x:Double" />  
  30.                                     </Image.HeightRequest>  
  31.                                 </Image>  
  32.                                 <!-- Define the Image from Speaker -->  
  33.                                 <StackLayout Grid.Row="1"  
  34.                                              Grid.Column="1"  
  35.                                              HorizontalOptions="FillAndExpand"  
  36.                                              Padding="10,0,0,0">  
  37.                                     <Label Font="Medium"  
  38.                                            FontAttributes="Bold"  
  39.                                            Text="{Binding Name}"  
  40.                                            TextColor="Black" />  
  41.                                     <Label Font="Medium"  
  42.                                            LineBreakMode="TailTruncation"  
  43.                                            Text="{Binding Speaker.Name}"  
  44.                                            TextColor="Black" />  
  45.                                     <Label Font="Small"  
  46.                                            LineBreakMode="TailTruncation"  
  47.                                            TextColor="Black" Text="{Binding Details}"/>  
  48.                                 </StackLayout>                 
  49.                                 <!-- Define the menu for each session -->    
  50.                            </Grid>  
  51.                        </ViewCell.View>  
  52.                     </ViewCell>  
  53.                 </DataTemplate>  
  54.       </ListView.ItemTemplate> 

Figure 10: A row in ListView

Running the application you will have the following:


Figure 11: The Windows Phone, iOS and Android application showing sessions from 1010 ENEI

3.6. Create the menu for each Session

To create the menu for each session as described in Figure 12, you need to change the ItemTemplate defined above:


Figure 12: The session's menu
 
  1. <Grid Grid.Row="2" Grid.Column="0"  Grid.ColumnSpan="2" Padding="0,5,0,0">  
  2.                   <Grid.ColumnDefinitions>  
  3.                     <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" />  
  4.                     <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /><ColumnDefinition Width="*" />  
  5.                     <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /><ColumnDefinition Width="Auto" />  
  6.                     <ColumnDefinition Width="*" />  
  7.                   </Grid.ColumnDefinitions>  
  8.                   <StackLayout Grid.Column="1" Orientation="Horizontal">  
  9.                     <Image>  
  10.                       <Image.WidthRequest>  
  11.                         <OnPlatform Android="48" WinPhone="48" iOS="30"  x:TypeArguments="x:Double" />  
  12.                       </Image.WidthRequest>  
  13.                       <Image.HeightRequest>  
  14.                         <OnPlatform Android="48"  WinPhone="48" iOS="30" x:TypeArguments="x:Double" />  
  15.                       </Image.HeightRequest>  
  16.                       <Image.Source>  
  17.                         <OnPlatform x:TypeArguments="ImageSource">  
  18.                           <OnPlatform.iOS>  <FileImageSource File="ic_action_like.png" />  
  19.                           </OnPlatform.iOS>  
  20.                           <OnPlatform.Android> <FileImageSource File="ic_action_like.png" />  
  21.                           </OnPlatform.Android>  
  22.                           <OnPlatform.WinPhone><FileImageSource File="Images/ic_action_like.png" />  
  23.                           </OnPlatform.WinPhone>  
  24.                         </OnPlatform>  
  25.                       </Image.Source>  
  26.                     </Image>  
  27.                     <Label Font="Small" Text="{Binding NumLikes}" TextColor="#0094FF" VerticalOptions="Center" />  
  28.                   </StackLayout>  
  29.                   <Image Grid.Column="3" Source="{Binding IsFavorite, Converter={StaticResource FavoriteImageConverter}}">  
  30.                     <Image.WidthRequest>  
  31.                       <OnPlatform Android="48" WinPhone="48" iOS="30"  x:TypeArguments="x:Double" />  
  32.                     </Image.WidthRequest>  
  33.                     <Image.HeightRequest>  
  34.                       <OnPlatform Android="48"  WinPhone="48"    iOS="30"  x:TypeArguments="x:Double" />  
  35.                     </Image.HeightRequest>  
  36.                   </Image>  
  37.                   <Image Grid.Column="5">  
  38.                     <Image.WidthRequest>  
  39.                       <OnPlatform Android="30"  WinPhone="48" iOS="30" x:TypeArguments="x:Double" />  
  40.                     </Image.WidthRequest>  
  41.                     <Image.HeightRequest>  
  42.                       <OnPlatform Android="30"  WinPhone="48" iOS="30" x:TypeArguments="x:Double" />  
  43.                     </Image.HeightRequest>  
  44.                     <Image.Source>  
  45.                       <OnPlatform x:TypeArguments="ImageSource">  
  46.                         <OnPlatform.iOS><FileImageSource File="ic_action_share_2.png" /></OnPlatform.iOS>  
  47.                         <OnPlatform.Android><FileImageSource File="ic_action_share_2.png" /></OnPlatform.Android>  
  48.                         <OnPlatform.WinPhone>  
  49. <FileImageSource File="Images/ic_action_share_2.png" /></OnPlatform.WinPhone>  
  50.                       </OnPlatform> </Image.Source>  </Image>  
  51.                   <Image Grid.Column="7">  
  52.                     <Image.WidthRequest>  
  53.                       <OnPlatform Android="30" WinPhone="48" iOS="30"   x:TypeArguments="x:Double" />  
  54.                     </Image.WidthRequest>  
  55.                     <Image.HeightRequest>  
  56.                       <OnPlatform Android="30"   WinPhone="48"  iOS="30" x:TypeArguments="x:Double" />  
  57.                     </Image.HeightRequest>  
  58.                     <Image.Source>  
  59.                       <OnPlatform x:TypeArguments="ImageSource">  
  60.                         <OnPlatform.iOS>   <FileImageSource File="ic_action_list.png" />  
  61.                         </OnPlatform.iOS>  
  62.                         <OnPlatform.Android><FileImageSource File="ic_action_list.png" />  
  63.                         </OnPlatform.Android>  
  64.                         <OnPlatform.WinPhone> <FileImageSource File="Images/ic_action_list.png" />  
  65.                         </OnPlatform.WinPhone>  
  66.                       </OnPlatform>  
  67.                     </Image.Source>  
  68.                   </Image>  
  69.                 </Grid> 
The SessionDetailsView will have a similar implementation for the header as defined in SessionsView, the difference it is the title value that will be defined by the Session's name.
 
The code above contains a grid with the four images such as Favorites, Shares and Details. And for each one we need to subscribe to the Tap event using the “GestureRecognizers” from the Image, this way you need to define for each image its TapGesture as in the following:
  1. <Image.GestureRecognizers>  
  2.      <TapGestureRecognizer x:Name="FavoriteGesture"  
  3. CommandParameter="{Binding}" Tapped="FavoriteGestureRecognizer_OnTapped" />  
  4. </Image.GestureRecognizers> 
In an implementation based in the MVVM pattern we use the Command instead of the Tapped event.
 
Where the event's handler will be defined as in the following:
  1. private void FavoriteGestureRecognizer_OnTapped(object sender, EventArgs e)  
  2. {  
  3.      var tappedEventArg = e as TappedEventArgs;  
  4.      if (tappedEventArg != null)  
  5.      {  
  6.          var session = tappedEventArg.Parameter as Session;  
  7.          if (session != null)  
  8.          {  
  9.              session.IsFavorite = !session.IsFavorite;  
  10.          }  
  11.      }  

Do the same for “Like” and to Share and Details we will see it in the next procedure.

The Favorite option uses a FavoriteImageConverter that allows the showiing of the right image based on if the user selected it as a favorite or not, this way if the favorite option is Red then that means the user selected the session as a favorite, the Blue color is defined by default.

The implementation of the FavoriteImageConverter will be something as in the following:
  1. public class FavoriteImageConverter:IValueConverter  
  2.     {  
  3.         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)  
  4.         {  
  5.             if (value == null)  
  6.             {  
  7.                 return Device.OS == TargetPlatform.WinPhone ? "Images/ic_action_heart.png" : "ic_action_heart.png";  
  8.             }  
  9.             var isFavorite = (bool) value;  
  10.   
  11.             if (isFavorite)  
  12.             {  
  13.                 return Device.OS == TargetPlatform.WinPhone ? "Images/ic_action_heart_red.png" : "ic_action_heart_red.png";  
  14.             }  
  15.             return Device.OS == TargetPlatform.WinPhone ? "Images/ic_action_heart.png" : "ic_action_heart.png";  
  16.         }  
  17.   
  18.         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)  
  19.         {  
  20.             throw new NotImplementedException();  
  21.         }  
  22.     } 
3.7 Running the application

At this moment you should have the UI from the 1010 ENEI Sessions App as described in Figure 13:


Figure 13: The Windows Phone, iOS and Android applications