Xamarin.Native Tutorial: InvoiceJe: Entity Framework: UWP

Alright, for Universal Windows Platform, we're going to simply update our InvoicesEditPage and InvoicesCreatePage.

InvoicesCreatePage

First, open InvoicesCreatePage.xaml.

Now, simply give names to the following TextBoxes inside our layout file:

  • Reference Number -> ReferenceNumber
  • Bill To -> BillTo
  • Total -> Total

Your StackPanel should look like this:

<StackPanel Margin="12 12 12 0">
            <TextBlock TextWrapping="Wrap" Text="Reference" Height="20" FontSize="15" />
            <TextBox Name="ReferenceNumber" TextWrapping="Wrap" Height="32" Margin="0 4 0 0" />
            <TextBlock TextWrapping="Wrap" Text="Date Issued" Height="20" FontSize="15" Margin="0 24 0 0" />
            <DatePicker Height="34" Margin="0,4,0,0" HorizontalAlignment="Stretch" />
            <TextBlock TextWrapping="Wrap" Text="Bill To" Height="20" FontSize="15" Margin="0 24 0 0" />
            <TextBox Name="BillTo" TextWrapping="Wrap" Height="32" Margin="0 0 0 0" />
            <TextBlock TextWrapping="Wrap" Text="Email To" Height="20" FontSize="15" Margin="0 24 0 0" />
            <TextBox TextWrapping="Wrap" Height="32" Margin="0 0 0 0" />
            <TextBlock TextWrapping="Wrap" Text="Total" Height="20" FontSize="15" Margin="0 24 0 0" />
            <TextBox Name="Total" TextWrapping="Wrap" Height="32" Margin="0 0 0 0" />
        </StackPanel>

Then, let's update our AppBarButton so that it will fire a function (which is where we will implement save) when clicked.

 At the <AppBarButton> element, add a property named Click, then you'll see some Intellisense magic here:

Simply click on <New Event Handler>. It will automatically add a function named "AppBarButton_Click".

Now, open InvoicesCreatePage.xaml.cs file again. You will see the function AppBarButton_Click already created automatically for you.

Now, simply paste this code inside the function:

Invoice invoice = new Invoice();
            invoice.ReferenceNumber = ReferenceNumber.Text;
            invoice.Amount = Decimal.Parse(Total.Text);
            invoice.BillTo = BillTo.Text;

            var repository = new Repository(FileAccessHelper.GetLocalDatabasePath());
            await repository.CreateAsync(invoice);

            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame == null)
                return;

            rootFrame.GoBack();

...It will then tell you to make the scope Async since we used the Await keyword inside our function. Simply click on the lightbulb and let Visual Studio update the function for you.

Your AppBarButton_Click will be renamed to AppBarButton_ClickAsync. Your function should now look like this:

        private async void AppBarButton_ClickAsync(object sender, RoutedEventArgs e)
        {
            Invoice invoice = new Invoice();
            invoice.ReferenceNumber = ReferenceNumber.Text;
            invoice.Amount = Decimal.Parse(Total.Text);
            invoice.BillTo = BillTo.Text;

            var repository = new Repository(FileAccessHelper.GetLocalDatabasePath());
            await repository.CreateAsync(invoice);

            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame == null)
                return;

            rootFrame.GoBack();
        }

"...but wait! Didn't we name it AppBarButton_Click in our xaml file? What happens then?"

It's okay, because Visual Studio is intelligent enough to rename our functions inside the xaml file for us accordingly.

InvoicesEditPage

InvoicesEditPage will be the same.

Xamarin.Native Tutorial: InvoiceJe: Entity Framework + SQLite Database: Setup

Alright, now we're going to implement Entity Framework + SQLite database inside our app (finally!).

What will happen is that we will update our Repository class to initialize with a path to the database file. Now, since each platform saves their database in different places, we will have to define the database path for each platform.

Then, we will simply implement the CRUD operations to the Edit page and the Create page.

Yay, let's go!

We will be referencing these articles: 

Update to .NET Standard 1.3

First, to use EntityFramework inside our mobile apps, we'll need to update our PCL to .NET Standard 1.3.

Note: Don't try any later versions, it just doesn't work well with this project for some reason.

So, go to Solution Explorer > PCL > Properties:

Click on the Target .NET Platform Standard link.

Your project will reload. Then, open Properties again. This time, set the target to .NET Standard 1.3.

Install EntityFramework and SQLite

Next, install Microsoft.EntityFrameworkCore (1.1.2) and Microsoft.EntityFrameworkCore.Sqlite (1.1.2) in each project.

Do NOT install 2.0.0. We want version 1.1.2.

Alright, now we're talking!

Create DataContext

Now, I will assume you guys are comfortable with Entity Framework. So, I'll skip explaining what's Entity Framework is.

Let's add a DataContext class that inherits from DbContext.

Inside the PCL Project > Data, create a class named DataContext and replace it with the following code:

using InvoiceJe.Models;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;

namespace InvoiceJe.Data
{
    public class DataContext : DbContext
    {

        private string _databasePath = "";

        public DataContext() : base() 
        {

        }

        public DataContext(string databasePath)
        {

            _databasePath = databasePath;
            Database.Migrate();
            //Database.EnsureCreated();

            //// Android
            //var dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "invoiceje.db");

            //// UWP
            //var dbPath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "exrin.db");

            //// iOS
            //var dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "..", "Library", "exrin.db");

        }

        public DbSet<Invoice> Invoices { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            base.OnConfiguring(optionsBuilder);

            string connectionStringBuilder = new SqliteConnectionStringBuilder()
            {
                DataSource = _databasePath
            }
            .ToString();

            var connection = new SqliteConnection(connectionStringBuilder);
            optionsBuilder.UseSqlite(connection);
        }
    }
}

Update Repository.cs

Now, we're going to update our Repository class to use EntityFramework and query from database rather than acting as a dummy all this while.

So, open Repository.cs inside the PCL and replace it with this:

using InvoiceJe.Models;
using System.Collections.Generic;
using System.Linq;

namespace InvoiceJe.Data
{
    public class Repository
    {

        private DataContext _db;

        public Repository(string databasePath)
        {
            _db = new DataContext(databasePath);
        }

        public async System.Threading.Tasks.Task CreateAsync(Invoice invoice)
        {
            _db.Invoices.Add(invoice);
            await _db.SaveChangesAsync();
        }

        public async System.Threading.Tasks.Task UpdateAsync(Invoice invoice)
        {
            _db.Entry(invoice).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
            await _db.SaveChangesAsync();
        }

        public IEnumerable<Invoice> GetInvoices()
        {
            return _db.Invoices.ToList();
        }
    }
}

(We purposely didn't change GetInvoices() to Async so that other usage of GetInvoices() wouldn't break. However, feel free to change this later)

Create FileAccessHelper in Each Platform

Next thing we're going to do is that we're going to make a FileAccessHelper class in each platform. These classes will help us define the path to save our database in each platform. We need to define in each class because each platform saves data in different places.

Inside the Android project, under the folder Extensions, add this class:

namespace InvoiceJe.Droid.Extensions
{
    public class FileAccessHelper
    {
        public static string GetLocalFilePath(string filename)
        {
            // Use the SpecialFolder enum to get the Personal folder on the Android file system.
            // Storing the database here is a best practice.
            string path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
            return System.IO.Path.Combine(path, filename);
        }

        public static string GetLocalDatabasePath()
        {
            return GetLocalFilePath("invoiceje.db");
        }

    }
}

Next, inside the iOS project, add a folder named Extensions and add this class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Foundation;
using UIKit;

namespace InvoiceJe.iOS.Extensions
{
    public class FileAccessHelper
    {
        public static string GetLocalFilePath(string filename)
        {
            // Use the SpecialFolder enum to get the Personal folder on the iOS file system.
            // Then get or create the Library folder within this personal folder.
            // Storing the database here is a best practice.
            var docFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
            var libFolder = System.IO.Path.Combine(docFolder, "..", "Library");

            if (!System.IO.Directory.Exists(libFolder))
            {
                System.IO.Directory.CreateDirectory(libFolder);
            }

            return System.IO.Path.Combine(libFolder, filename);
        }

        public static string GetLocalDatabasePath()
        {
            //var dbPath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "exrin.db");
            var dbPath = GetLocalFilePath("exrin.db");
            return dbPath;
        }
    }
}

..And similarly in the UWP project, add a folder named Extensions and add this class:

using System.IO;

namespace InvoiceJe.UWP.Extensions
{
    public class FileAccessHelper
    {
        public static string GetLocalFilePath(string filename)
        {
            // For UWP, we store the database file in our application data's local folder.
            var path = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
            return Path.Combine(path, filename);
        }

        public static string GetLocalDatabasePath()
        {
            //var dbPath = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "exrin.db");
            var dbPath = GetLocalFilePath("invoiceje.db");
            return dbPath;
        }
    }
}

...Alright.

Update Each Line Where We Called Repository

Now, unfortunately a vanilla Xamarin.Native doesn't have a built-in dependency injection (nor does this tutorial will cover it), so we'll have to do this the manual way.

Since we have changed our Repository construction to have a string parameter, we'll need to change every line in each of our project that references Repository. So, open Error List and you'll see all instance where Repository is used inside our projects. 

Change all of the lines...

var repository = new Repository();

... to this:

var repository = new Repository(FileAccessHelper.GetLocalDatabasePath());

To make things simple, I'll list down the files you'll need to change:

Android: InvoicesFragment, InvoicesEditActivity

iOS: InvoicesTableViewController, InvoicesEditTableViewController

UWP: MainPage.xaml.cs, InvoicesEditPage.xaml.cs

"Really, purr?"

"Ya rly"

"Isn't there a way to make this, not horrible? What if later I want to change the database name?"

 Yup, the smarties amongst y'all would probably have thought of some ways to do that. We ain't gonna go through that in this step, for clarity. But later we'll be writing on some of my opinions on how to centralize these codes. K?

Alright, now that we're done setting up our repository, we only have 2 major things to care about.

  1. EntityFramework Migrations
  2. iOS Xamarin Linker File (IMPORTANT! Or else your app will throw an error when it runs on iOS devices)

EntityFramework Migrations

Now, we're going to implement migrations for our SQLite database using Entity Framework.

Unfortunately, unlike ASP.NET, as of the time of writing, there's no built-in tool where you can simply run the command 'Enable-Migration' or 'Add-Migration xxx' inside the Package Manager Console.

We'll need to use the dotnet command line tool instead to achieve this. However, we can't use it in our PCL library. So, we'll need a workaround.

What's going to happen is that:

  1. We're going to create a new .NET Core console application
  2. Install Microsoft.EntityFramework tools and library in the console app
  3. We'll copy over our DataContext class to the .NET Core console application
  4. We'll run the dotnet Add Migration command to add the migration files
  5. Then, we copy over the migration files to our PCL
  6. Done!

We'll be referring the Xamarin blog heavily: https://blog.xamarin.com/building-android-apps-with-entity-framework/

So let's go.

InvoiceJeMigrationsBait .NET Core Console App

In Solution Explorer, Right click on "Solution 'InvoiceJe'" and Add New Project.

Select .NET Core > Console App. Name it InvoiceJeMigrationsBait.

Next, install Microsoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.Sqlite and Microsoft.EntityFrameworkCore.Design

...inside InvoiceJeMigrationsBait via NuGET.

REMEMBER. Do NOT install 2.0.0. We want version 1.1.2.

Install EntityFramework Tools and Library

Next, we need to install Microsoft.EntityFrameworkCore.Tools.DotNet.

However, if we try to install this package via NuGET we'll get an error "Package 'Microsoft.EntityFrameworkCore.Tools.DotNet 1.0.0' has a package type 'DotnetCliTool' that is not supported by project", which is discussed in this EntityFramework issue on Github.

To overcome this, we can edit the InvoiceJeMigrationsBait .csproj file. To edit it, simply right-click on InvoiceJeMigrationsBait project. Unload project. And then right-click again and click on the edit InvoiceJeMigrationsBait .csproj. 

Then, add this tag:

<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />

...inside the <ItemGroup> tag.

Afterwards, Nuget will install the DotNetCliTool for you. Let Nuget restore the packages and you should be good to go. 

Your .csproj file should look like this once you have installed everything:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Data.SQLite" Version="1.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.2" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.2" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.2" />
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
  </ItemGroup>

</Project>

Copy DataContext class to InvoiceJeMigrationsBait

Now, simply copy the DataContext class and the Models (in our case, Invoice.cs) to InvoiceJeMigrationsBait project. What I did was I simply copied the whole Data folder and Models folder.

Add Migration Using dotnet Tool

Now we're going to add our first initial migration for our project. We're going to use the dotnet command line interface to achieve this, since we can't use our Package Manager Console.

Now, open Command Prompt, navigate to the InvoiceJeMigrationsBait folder. Then, run this command: 

dotnet ef migrations add Initial

This is equivalent to "Add-Migrations Initial" if we use Package Manager Console.

If things go right, the dotnet CLI will generate the Migrations folder and classes for you. Your InvoiceJeMigrationsBait project should look like this:

Copy over the migration files to our PCL

Now, simply copy over the Migrations folder to our PCL. Remember, our whole exercise here is to generate the Migrations file for EntityFramework in our PCL.

Alright, now we're done with setting up EF Migrations.

Add Xamarin Linker File for iOS Project

Now, according to Github issue #7158, even though now using EntityFramework with iOS emulators work, there are still problems when running it on iOS physical devices, which is due to the fact that Xamarin linker removes certain types and methods that is not directly referenced in our project.

The answer is to create our own Xamarin linker file and manually tell Xamarin to include those methods. (Thanks to cwrea!)

So, in the root of our iOS project, add an XML file named Linker.xml. Then, replace the contents with this:

<?xml version="1.0" encoding="utf-8" ?>
<linker>
  <!--
  
        LinkDescription.xml
        
        Prevents runtime errors when reflection is used for certain types that are not otherwise referenced
        directly in the project, and that would be removed by the Xamarin linker.
        
        These kinds of runtime errors do occur in particular when using Entity Framework Core on iOS. EF Core's
        query parser does reference certain .NET methods _only_ via reflection, and it is those reflection-only
        instances that we need to guard against by ensuring the linker includes them. For the curious, search
        for mentions of "GetRuntimeMethod" at https://github.com/aspnet/EntityFramework. Use of EF Core more
        advanced than this sample may require additional types/methods added to those below.
        
        Include the following in the project build settings under "Additional mtouch arguments":
          [hyphen][hyphen]xml=${ProjectDir}/LinkDescription.xml
          
        There is supposed to be a "LinkDescription" build action for this linker config file so the step above
        shouldn't be necessary, but at time of writing Visual Studio 2017 for PC doesn't show that build action
        in iOS projects, even though it is an option within iOS projects on Visual Studio 2017 for Mac.
        
  -->
  <assembly fullname="mscorlib">
    <type fullname="System.String">
      <method name="Compare"></method>
      <method name="CompareTo"></method>
      <method name="ToUpper"></method>
      <method name="ToLower"></method>
    </type>
  </assembly>
  <assembly fullname="System.Core">
    <type fullname="System.Linq.Expressions.Expression`1"></type>
    <type fullname="System.Linq.Queryable"></type>
  </assembly>
</linker>

...Finally...

...Alright, now that we've set up everything, try running the app on each platform.

Yeah, currently it'll be empty simply because we didn't add anything inside our database yet. So, let's do it.

 

Xamarin.Native Tutorial: InvoiceJe: DataBinding: Universal Windows Platform

Alright, let's do Universal Windows Platform next.

We're going to build upon on top of our previous tutorials.

So, we had ListView in Android and TableViewController in iOS. The counterpart in UWP is ListView as well.

We'll refer to this StackOverflow question: https://stackoverflow.com/questions/33339255/how-to-use-listview-in-universal-windows-platformwindows-10-app

First, open MainPage.axml since this is where we made our pages.

So, here's what's going to happen:

  1. We're going to name our ListView "InvoicesListView" and remove the TextBlocks inside it.
  2. In the code-behind, we're going to set the ItemsSource of our ListView to an IEnumerable<Invoice>
  3. We're going to set the DataTemplate of our ListView to tell ListView how to show our Invoices

Alright, so let's go!

First, go to the ListView inside the "Invoice" Pivot. Change the name to "InvoicesListView" and remove all of the TextBlocks inside it. The ListView should now look like this.

                    <ListView x:Name="InvoicesListView" Margin="0,0,0,48">

                    </ListView>

Next, we will set the ItemsSource of our InvoicesListView to our Invoices.

So, open MainPage.xaml.cs. Replace the constructor with this:

public MainPage()
        {
            this.InitializeComponent();

            // To disable ExtendViewIntoTitleBar and others when using Acryllic Accent
            CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = false;
            ApplicationViewTitleBar titleBar = ApplicationView.GetForCurrentView().TitleBar;
            titleBar.ButtonBackgroundColor = null; // by setting this to null, it gets the default value
            titleBar.ButtonInactiveBackgroundColor = null; // by setting this to null, it gets the default value

            // repository
            var repository = new Repository();
            InvoicesListView.ItemsSource = repository.GetInvoices().ToList();

        }

Awesome!

Now try Deploying and Running it. Unfortunately, you're going to get this:

What happened is that since we did not define any ItemTemplate, UWP will default to getting the .ToString() of an Invoice.

Now, let's do some "make-up" to our ListView.

Define a ItemTemplate inside our ListView so that it looks like this:

<ListView x:Name="InvoicesListView" Margin="0,0,0,48">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                 <TextBlock Text="{Binding ReferenceNumber}" />
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>

What happened here is that we overrode the DataTemplate inside the ItemTemplate of our InvoicesListView. So, basically whatever we put inside the <DataTemplate> will be what is shown inside each of the row of the ListView.

{Binding ReferenceNumber} is how you bind the data from the ItemsSource to the xaml. Remember that each Invoice have a Public Property named ReferenceNumber, right? So here simply set the Text of the TextBlock to the value of ReferenceNumber of each Invoice inside Invoices we set at the ItemsSource.

Yes, we can also modify the template of the ListView itself, but we won't touch it here. You can read about ItemTemplate at the Windows documentation

Click ListView -> Goes to Edit Invoices Page

Alright, now what we're going to do is,

  1. Create an InvoicesEditPage.xaml (We'll only create a blank one, for now)
  2. Update MainPage to get the Click events on our ListView and navigate the user to InvoicesEditPage

Alright, so let's make an Edit Invoices page.

Create a new blank page inside the Views folder and name it InvoicesEditPage.xaml.

Okay, that's it. Now open MainPage.axml.cs.

Add a function to handle a click in a ListViewItem:

        private void InvoicesListView_Click(object sender, ItemClickEventArgs e)
        {
            Invoice invoice = (Invoice)e.ClickedItem;
            Frame.Navigate(typeof(InvoicesEditPage), invoice.Id);
        }

Now, open MainPage.axml. Add these properties to the <ListView> element so that it looks like this:

<ListView x:Name="InvoicesListView" Margin="0,0,0,48" IsItemClickEnabled="True" ItemClick="InvoicesListView_Click">

Explanation

Now, to enable a ListView to handle click events, we must first set IsItemClickEnabled="True".

Then, we need to set the function that will handle the ItemClick event. In this case, we have set it to InvoicesListView_Click().

Inside InvoicesListView_Click(), we can get the clicked item by accessing e.ClickedItem. Since we have populated the ListView with Invoice, e.ClickedItem contains the Invoice of that specific row.

The line:

Frame.Navigate(typeof(InvoicesEditPage), invoice.Id);

...Means we will Navigate to the InvoicesEditPage and send a parameter of Invoice.Id, so that InvoicesEditPage knows which Invoice should it show to the user.

We will see how a Page can retrieve a parameter when we actually modify the InvoicesEditPage.

InvoicesEditPage

Now we have allowed the user to navigate from MainPage to InvoicesEditPage, let's make it so that the EditPage actually shows the data of the selected Invoice.

We will basically make a copy of the InvoicesCreatePage.xaml but we will introduce property binding in it.

 Open InvoicesEditPage.xaml.cs, and replace the contents with this:

using InvoiceJe.Data;
using InvoiceJe.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

namespace InvoiceJe.UWP.Views
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class InvoicesEditPage : Page
    {

        public Invoice Invoice;

        public InvoicesEditPage()
        {
            this.InitializeComponent();

            // Register a handler for BackRequested events and set the
            // visibility of the Back button
            Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested +=
    App_BackRequested;

        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            // Back Navigation
            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame.CanGoBack)
            {
                // Show UI in title bar if opted-in and in-app backstack is not empty.
                SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
                    AppViewBackButtonVisibility.Visible;
            }

            // Retrieve paratemer
            int invoiceId = (int)e.Parameter;

            // Populate Invoice property
            var repository = new Repository();
            Invoice = repository.GetInvoices().Where(x => x.Id == invoiceId).FirstOrDefault();

        }

        private void App_BackRequested(object sender, Windows.UI.Core.BackRequestedEventArgs e)
        {
            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame == null)
                return;

            // Navigate back if possible, and if the event has not 
            // already been handled .
            if (rootFrame.CanGoBack && e.Handled == false)
            {
                e.Handled = true;
                rootFrame.GoBack();
            }
        }

    }
}

Alright. Now open InvoicesEditPage.xaml and replace the contents with this:

<Page
    x:Class="InvoiceJe.UWP.Views.InvoicesEditPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:InvoiceJe.UWP.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.Transitions>
        <TransitionCollection>
            <EntranceThemeTransition />
        </TransitionCollection>
    </Page.Transitions>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Margin="12 12 12 0">
            <TextBlock TextWrapping="Wrap" Text="Reference" Height="20" FontSize="15" />
            <TextBox Text="{x:Bind Invoice.ReferenceNumber, Mode=TwoWay}" TextWrapping="Wrap" Height="32" Margin="0 4 0 0" />
            <TextBlock TextWrapping="Wrap" Text="Date Issued" Height="20" FontSize="15" Margin="0 24 0 0" />
            <DatePicker Height="34" Margin="0,4,0,0" HorizontalAlignment="Stretch" />
            <TextBlock TextWrapping="Wrap" Text="Bill To" Height="20" FontSize="15" Margin="0 24 0 0" />
            <TextBox Text="{x:Bind Invoice.BillTo, Mode=TwoWay}" TextWrapping="Wrap" Height="32" Margin="0 0 0 0" />
            <TextBlock TextWrapping="Wrap" Text="Email To" Height="20" FontSize="15" Margin="0 24 0 0" />
            <TextBox TextWrapping="Wrap" Height="32" Margin="0 0 0 0" />
            <TextBlock TextWrapping="Wrap" Text="Total" Height="20" FontSize="15" Margin="0 24 0 0" />
            <TextBox Text="{x:Bind Invoice.Amount, Mode=TwoWay}" TextWrapping="Wrap" Height="32" Margin="0 0 0 0" />
        </StackPanel>
    </Grid>

    <Page.BottomAppBar>
        <CommandBar HorizontalAlignment="Stretch">
            <CommandBar.PrimaryCommands>
                <AppBarButton Icon="Save" Label="Save" />
            </CommandBar.PrimaryCommands>
        </CommandBar>
    </Page.BottomAppBar>
</Page>

Explanation

Inside InvoicesEditPage.xaml.cs, you will see these lines:

        public Invoice Invoice;

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            // Back Navigation
            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame.CanGoBack)
            {
                // Show UI in title bar if opted-in and in-app backstack is not empty.
                SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
                    AppViewBackButtonVisibility.Visible;
            }

            // Retrieve paratemer
            int invoiceId = (int)e.Parameter;

            // Populate Invoice property
            var repository = new Repository();
            Invoice = repository.GetInvoices().Where(x => x.Id == invoiceId).FirstOrDefault();
        }

What happened is:

We created a Public property for Invoice. This is so that we can bind Invoice to our xaml.

The lines:

            // Retrieve paratemer
            int invoiceId = (int)e.Parameter;

...is how we retrieve the Parameter we sent from the previous page.

Remember that we sent invoiceId from the MainPage? We can access the invoiceId by overriding OnNavigateTo and get it inside e.Parameter (where e is a NavigationEventArgs).

            // Populate Invoice property
            var repository = new Repository();
            Invoice = repository.GetInvoices().Where(x => x.Id == invoiceId).FirstOrDefault();

...These lines simply gets the correct Invoice and update the Invoice property.

Now let's take a look at InvoicesEditPage.xaml.

Now mostly everything is the same as InvoicesCreatePage.xaml. However, you will see a couple of lines like this:

<TextBox Text="{x:Bind Invoice.ReferenceNumber, Mode=TwoWay}" TextWrapping="Wrap" Height="32" Margin="0 4 0 0" />

...So what happened here is that for this TextBox, we x:Bind the value of Invoice.ReferenceNumber to the Text property of the TextBox.

By default, x:Bind is OneWay, ie the TextBox will only get the value of Invoice.ReferenceNumber, but if the TextBox itself changes its value, it won't update Invoice.ReferenceNumber. Hence, we've the Mode=TwoWay to tell UWP that if the TextBox is updated, update the Invoice property too.

Feel free to look at the other elements too.

Okay, now Deploy and Run the UWP app.

Congrats! You can now do DataBinding in UWP. Finally, we're going to introduce database inside our application.

Sample at Github: https://github.com/purrmiaw/InvoiceJeBindingNoDatabase

 

iOS Best Practice

https://github.com/futurice/ios-good-practices

 

Folder structure:

CoreData : Contains DataModel and Entity Classes.

Extension : Contain One class(default apple class extensions+project class extensions.)

Helper: Contain Third Party classes/Frameworks (eg. SWRevealController) + Bridging classes (eg. Obj C class in Swift based project)

Model : Make a singleton class (eg.AppModel - NSArray,NSDictionary, String etc.) for saving data. The Web Service Response parsing and storing data is also done here.

Services : Contain Web Service processes (eg. Login Verification, HTTP Request/Response)

View : Contain storyboard, LaunchScreen.XIB and View Classes. Make a sub folder Cells - contain UITableViewCell, UICollectionViewCell etc.

Controller: Contain Logic or Code related to UIElements (eg. UIButton’s reference+ clicked action)

ref: https://stackoverflow.com/questions/39945727/best-practice-for-an-xcode-project-groups-structure

 

Xamarin.Native Tutorial: InvoiceJe: DataBinding: iOS

Now let's go with iOS.

Our tutorial will heavily refer https://developer.xamarin.com/guides/ios/user_interface/controls/tables/

Open Main.storyboard.

Open the Invoices TableViewController.

If you've followed from the previous tutorial, there wouldn't be any UITableView inside it yet. So drag a UITableView onto the Invoices TableViewController.

Now, click on a row inside the UITableView. Change the Style to Basic (This one doesn't really matter, but just do it). Change the Accessory to Disclosure Indicator. Change the Identifier to TaskCell.

If you haven't, rebuild the iOS project.

Now click on the Invoices TableViewController. Click Properties. Change the Identity>Class to InvoicesTableViewController. This will automatically create a InvoicesTableViewController.cs class in the root of the iOS project.

Now, we've set InvoicesTableViewController as the controller. Now we can freely modify things inside it.

 Now, open InvoicesTableViewController.cs and replace it with this:

using Foundation;
using InvoiceJe.Data;
using InvoiceJe.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using UIKit;

namespace InvoiceJe.iOS
{
    public partial class InvoicesTableViewController : UITableViewController
    {
        public InvoicesTableViewController (IntPtr handle) : base (handle)
        {
        }

        public override void ViewWillAppear(bool animated)
        {
            base.ViewWillAppear(animated);

            var repository = new Repository();
            TableView.Source = new InvoicesTableSource(repository.GetInvoices());
        }
    }


    public class InvoicesTableSource : UITableViewSource
    {

        private IEnumerable<Invoice> _invoices;
        private string _cellIdentifier = "TaskCell";

        public InvoicesTableSource(IEnumerable<Invoice> invoices)
        {
            _invoices = invoices;
        }

        public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
        {
            UITableViewCell cell = tableView.DequeueReusableCell(_cellIdentifier);

            // If there are no cells to reuse, create a new one
            if (cell == null)
            {
                cell = new UITableViewCell(UITableViewCellStyle.Default, _cellIdentifier);
            }

            Invoice invoice = _invoices.ElementAt(indexPath.Row);
            cell.TextLabel.Text = invoice.ReferenceNumber;

            return cell;
        }

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

}

...Now try running the app. Your table should already be populated by the repository.

Explanation

Now, to populate a TableView with dynamic data, what you need is to have a UIViewController that implements IUITableViewDataSource (to handle data population), IUITableViewDelegate (to handle events).

Fortunately, we have a UITableViewController that handles both of that for us. So, we can simply inherit InvoicesTableViewController from UITableViewController.

Next is, we need to tell our TableView what is its DataSource. To do that, we create a new class, names InvoicesDataSource, that inherits from UITableViewSource.

Let's take a look at the private properties and constructor:

        private IEnumerable<Invoice> _invoices;
        private string _cellIdentifier = "TaskCell";

        public InvoicesTableSource(IEnumerable<Invoice> invoices)
        {
            _invoices = invoices;
        }

..We simply send the Invoices into our InvoicesTableSource and defined the _cellIdentifier.

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

...Here, we tell iOS how many rows are there inside a section inside our table. Since we only have 1 section, we can simply return the total number of invoices via _invoices.Count().

Next will be the GetCell() implementation:

        public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
        {
            UITableViewCell cell = tableView.DequeueReusableCell(_cellIdentifier);

            // If there are no cells to reuse, create a new one
            if (cell == null)
            {
                cell = new UITableViewCell(UITableViewCellStyle.Default, _cellIdentifier);
            }

            Invoice invoice = _invoices.ElementAt(indexPath.Row);
            cell.TextLabel.Text = invoice.ReferenceNumber;
            return cell;
        }

The lines..

            UITableViewCell cell = tableView.DequeueReusableCell(_cellIdentifier);

            // If there are no cells to reuse, create a new one
            if (cell == null)
            {
                cell = new UITableViewCell(UITableViewCellStyle.Default, _cellIdentifier);
            }

...is required for caching. Basically what happens behind the scenes is that, if you have a table with 1000 rows, rather than iOS rendering 1000s of rows offscreen, it simply reuse the cells you see onscreen by changing the data inside each row based on how far you scroll. The _cellIdentifier allows iOS to identify whether it's re-using the correct cell template, which in our case, defined as "TaskCell". More information can be gleaned from this stackoverflow question.

And also, we've set the UITableViewCell to use the Default style. There are plenty other styles you can experiment with, or you can even define a custom one yourself.

            Invoice invoice = _invoices.ElementAt(indexPath.Row);
            cell.TextLabel.Text = invoice.ReferenceNumber;
            return cell;

...and lastly, these lines simply gets the Invoice, replace the Text with the ReferenceNumber and then returns the cell. Easy-peasy, right?

Here's how your app should look like:

Cool.

Edit Invoices

Now, let's create an Edit Invoices page.

At Main.storyboard, open Toolbox and drag a TableViewController onto the storyboard. Let's name it InvoicesEditViewController.

Ctrl+drag from the TableView inside InvoicesTableViewController to the InvoicesEditViewController to create a segue between them. This means each time a row in TableView is clicked, the screen will navigate to InvoicesEditViewController.

Now let's make a sweet-looking form. Now, there are many ways to design a form, but I will show you one way to make a nice looking form using TableView.

You should make it look like this:

Refer to the previous tutorial to make a TableView like this, but I'll list down the steps here:

Click on the TableView. Change the content to Static Cells and Section 1. Style is Grouped. Separator is Single Line. 

Open Toolbox. Drag a TextField into one of the row.

Resize it to fill the row vertically, and have a margin left of 16 and margin right of 16 (If you resize with mouse, the iOS will automatically snap it for you).

Set the constraints so that the height is equal to the superview (in this case, it's the row). Double-click on the TextField to change into the Constraint mode, drag the "I" on the right-most side to the right-edge of the row to tell iOS "This TextField needs to be the same size as this row".

Set the constraints for the left and right margins too. This time, drag the left 'T' to the left edge of the row, and then drag the right 'T' to the right side of the row.

Click on Properties, change to the Layout tab and look at the Constraints section. It should look like this:

...Lastly, click on the TextField. Change the Border Style to empty. Remove the Text property and set the Name to "ReferenceNumber" (this will be the ID when we refer to this TextField from our code-behind) and Placeholder to "Reference Number".

If there are other rows in the TableView, delete them.

Then, select the row with your shining new TextField and copy. And then paste 2 times and VS will add 2 new rows for you.

Change the PlaceholderTexts for the remaining rows' Name and Placeholders to "BillTo" and "Bill To" and "Amount" and "Amount" respectively.

Done!

Now we're going to add a Save button at the top-right of the screen.

However, if you drag a Button to the top bar, it will only be added to the TableView.

You can't, because technically there's no UINavigationItem inside the InvoicesEditTableViewController.

So, open Toolbox and drag UINavigationItem to the top of the scene. Now drag a button to the Top Bar. Change the Title to 'Save'. Awesome!

Sending From the Table to the Correct Invoice and also Actually Populating the InvoicesEdit screen

Yeah, I'm totally a fan of long titles.

Btw, here's what we're going to do:

  1. Send the InvoiceId from the InvoicesViewController to InvoicesEditViewController
  2. Actually populating the InvoicesEdit screen with the correct Invoice

Let's go!

Before that, let's create the InvoicesEditTableViewController first.

So, open Main.storyboard. Click on the InvoicesEditTableViewController we've made. Change the Identity -> Class to InvoicesEditTableViewController. VS will automatically create a InvoicesEditTableViewController class in the root of the iOS project.

Now this is where it's a bit different than Android.

Rather than creating an Intent and filling it with parameters for the next Activity to consume then simply start the activity, we override the function PrepareForSegue inside the source ViewController (in this case, InvoicesTableViewController). Inside the PrepareForSegue function, we can directly manipulate the Public Properties of the destination ViewController (in this case, InvoicesEditViewController).

Firstly, open InvoicesEditTableViewController.cs.

  1. Since we need to receive InvoiceId from the previous ViewControoler, we're going to add a Public property Int InvoiceId.
  2. Next, we're going to update the TextFields with information from our Invoice

Hence, this is how InvoicesEditTableViewController.cs should look like (simply copy paste):

using Foundation;
using InvoiceJe.Data;
using System;
using System.Linq;
using UIKit;

namespace InvoiceJe.iOS
{
    public partial class InvoicesEditTableViewController : UITableViewController
    {

        public int InvoiceId { get; set; }

        public InvoicesEditTableViewController (IntPtr handle) : base (handle)
        {
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            var repository = new Repository();
            var invoice = repository.GetInvoices().Where(x => x.Id == InvoiceId).FirstOrDefault();

            ReferenceNumber.Text = invoice.ReferenceNumber;
            BillTo.Text = invoice.BillTo;
            Amount.Text = invoice.Amount.ToString();

        }
    }
}

Alright, now let's open Main.storyboard. Locate the Segue that goes from InvoicesTableViewController to InvoicesEditViewController. Click on it and open Properties. Now set the Identifier property to "ShowInvoicesEditViewController".

Now, open InvoicesTableViewController.cs. What's going to happen is:

  1. We override the PrepareForSegue() method
  2. This is funny, but iOS couldn't automatically differentiate between segues if there are 2 or more segues that originate from a ViewController. This is where the Identifier comes in. We need to tell iOS that "if Segue's Identifier is 'xxx', THEN do something like this, if not then do something else".
  3. Lastly, we simply set the Public Properties of the target ViewController, which is InvoicesEditViewController.

So, simply copy paste the following code:

using Foundation;
using InvoiceJe.Data;
using InvoiceJe.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using UIKit;

namespace InvoiceJe.iOS
{
    public partial class InvoicesTableViewController : UITableViewController
    {

        private IEnumerable<Invoice> _invoices;

        public InvoicesTableViewController (IntPtr handle) : base (handle)
        {
            var repository = new Repository();
            _invoices = repository.GetInvoices();
        }

        /// <summary>
        /// Happens every time a view loads
        /// </summary>
        /// <param name="animated"></param>
        public override void ViewWillAppear(bool animated)
        {
            base.ViewWillAppear(animated);
            TableView.Source = new InvoicesTableSource(_invoices);
        }

        public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
        {            
            base.PrepareForSegue(segue, sender);

            if (segue.Identifier == "ShowInvoicesEditViewController")
            {
                var editViewController = segue.DestinationViewController as InvoicesEditTableViewController;
                var row = TableView.IndexPathForSelectedRow;
                var invoice = _invoices.ElementAt(row.Row);
                editViewController.InvoiceId = invoice.Id;
            }
        }
    }


    public class InvoicesTableSource : UITableViewSource
    {

        private IEnumerable<Invoice> _invoices;
        private string _cellIdentifier = "TaskCell";

        public InvoicesTableSource(IEnumerable<Invoice> invoices)
        {
            _invoices = invoices;
        }

        public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
        {
            UITableViewCell cell = tableView.DequeueReusableCell(_cellIdentifier);

            // If there are no cells to reuse, create a new one
            if (cell == null)
            {
                cell = new UITableViewCell(UITableViewCellStyle.Default, _cellIdentifier);
            }

            Invoice invoice = _invoices.ElementAt(indexPath.Row);
            cell.TextLabel.Text = invoice.ReferenceNumber;
            return cell;
        }

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

    }

}

...Now try playing it. Your iOS app should now look pretty awesome!

Explanation:

            if (segue.Identifier == "ShowInvoicesEditViewController")
            {
                var editViewController = segue.DestinationViewController as InvoicesEditTableViewController;
                var row = TableView.IndexPathForSelectedRow;
                var invoice = _invoices.ElementAt(row.Row);
                editViewController.InvoiceId = invoice.Id;
            }

The segue.Identifier line is so that we only run the code if the specific "ShowInvoicesEditViewController" is triggered.

We can directly modify the destination ViewController by accessing segue.DestinationViewController.

We can get the selected row inside the TableView by accessing TableView.IndexPathForSelectedRow.

Then, from the number of selected row, we get the exact invoice.

Lastly, we simply set the InvoiceId property of the InvoicesEditViewController.

Alright, now we've did it, remember the InvoicesCreateViewController that we never got around before? Let's do it.

You know what, I'm not gonna hand-hold you through it. Basically you just create the same thing you did for the EditInvoices we just did, okay? ;)

You've done that? AWESOME! Now let's actually connect with a database!

 Sample at Github: https://github.com/purrmiaw/InvoiceJeBindingNoDatabase

 

Xamarin.Native Tutorial: InvoiceJe: DataBinding: Android

Now, we're going to mess around with databinding in Android.

Remember the InvoicesFragment where we simply populated it with a bunch of TextViews inside a LinearLayout? We're going to change it so the page dynamically gets its data from InvoicesFragment.cs. AND we're going to make it clickable so that it goes to another Activity that shows the Invoice information.

In Android, usually we use a RecyclerView to achieve this. Well, at least at the time of writing, Android may still change things around in the future.

What will happen is:

  • We define a RecyclerView inside invoices_fragment.axml
  • In the code-behind (InvoicesFragment.cs), we will populate the RecyclerView with our data by:
    • Create an recyclerview_invoice.axml layout file as the template for each Invoice
    • Create an InvoiceAdapter that extends RecyclerView.Adapter
    • Create an InvoiceViewHolder that extends RecyclerView.ViewHolder
    • Initialize the RecyclerView inside OnCreate
  • We will then create an Activity (InvoicesEditActivity) that is populated by one invoice based on the parameter we send to it
  • And inside InvoicesFragment, we make it so that wheneve an user presses on a specific Invoice, it goes to the Edit page.
  • Done!

So, let's go!

Setup

If you have followed this tutorial from previous steps, then you can simply ignore this. HOWEVER, if you haven't, then make sure you install the Xamarin.Android.Support.v7.RecyclerView package.

install-package xamarin.android.support.v7.recyclerview

What's a RecyclerView?

Now, a good tutorial of recyclerview is available at Xamarin. This tutorial will heavily refer to that.

Basically, RecyclerView is what we use when we want to show a dynamic repeating data. For example, we're getting a list of Invoices from our database and we want to show it.

Well, you can show dynamic data by programmatically instantiating TextViews and lining them up with LinearLayouts and ScrollViews, but clearly this will be a nightmare, so RecyclerView to the rescue!

Let's go!

Replace invoices_fragment.axml Contents

Alright, open invoices_fragment.axml. Replace everything, the NestedScrollView and stuffs, with this:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/recyclerView"
  android:scrollbars="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent" />

Gotchas: You need to the full name <android.support.v7.widget.RecyclerView> or you'll get a nasty error.

Wait? Where's the NestedScrollView? Don't we need to scroll? Don't worry, because RecyclerView is itself an expanded version of NestedScrollView. So there's no need to put it inside another ScrollView.

Okay, now we only have the RecyclerView inside our InvoicesFragment.

Populate the RecyclerView

So your question now will be "How do I actually put my data inside RecyclerView?"

Now, this will be a bit confusing, but don't worry.

First, you need to have an axml item template. This is where you design how each of your item/row inside RecyclerView should look like using axml.

Next, to populate the RecyclerView with data, we use a RecyclerView.Adapter. The Adapter is the one that fills the RecyclerView with our data.

Then you have the RecylerView.ViewHolder. This is what connects the Adapter with our Layout file. The Adapter will say, "ViewHolder, this data for this row needs to go this view" and the ViewHolder will say, "Okay bro, this is the View you want".

A more technical explanation is there in Xamarin. Haha.

Alright, got it. So let's design a template for each of our item. Now create a layout named recyclerview_invoice.axml and fill it with this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="16dp"
    android:paddingTop="20dp"
    android:paddingBottom="20dp"
    android:paddingRight="16dp"
    android:orientation="horizontal">
  <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:id="@+id/invoiceReferenceNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textColor="?android:textColorPrimary" />
    <TextView
        android:id="@+id/billTo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:textColor="?android:textColorSecondary" />
  </LinearLayout>
  <View
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        />
  <TextView
    android:id="@+id/invoiceAmount"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
     android:layout_gravity="right"
    android:textSize="14sp"
    android:textColor="?android:textColorTertiary" />

</LinearLayout>

Okay, now we have a nice view. Now let's create the adapter next.

Create a folder named Adapters in the root, create a class named InvoicesRecyclerViewAdapter and fill it with this:

using System.Collections.Generic;
using System.Linq;
using Android.Views;
using Android.Widget;
using Android.Support.V7.Widget;
using InvoiceJe.Models;
using System;

namespace InvoiceJe.Droid.Adapters
{
    public class InvoicesRecyclerViewAdapter : RecyclerView.Adapter
    {

        private IEnumerable<Invoice> _invoices;

        public InvoicesRecyclerViewAdapter(IEnumerable<Invoice> invoices)
        {
            _invoices = invoices;
        }

        public override int ItemCount => _invoices.Count();

        public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
        {
            InvoiceRecyclerViewViewHolder invoiceViewHolder = holder as InvoiceRecyclerViewViewHolder;

            var currentInvoice = _invoices.OrderBy(x => x.ReferenceNumber).ElementAt(position);
            invoiceViewHolder.ReferenceNumber.Text = "Invoice #" + currentInvoice.ReferenceNumber;
            invoiceViewHolder.BillTo.Text = currentInvoice.BillTo.ToString();
            invoiceViewHolder.InvoiceAmount.Text = "RM " + currentInvoice.Amount.ToString();
            invoiceViewHolder.InvoiceId = currentInvoice.Id;
        }

        public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
        {

            View itemView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.recyclerview_invoice, parent, false);

            InvoiceRecyclerViewViewHolder invoiceViewHolder = new InvoiceRecyclerViewViewHolder(itemView); // No clicks
            //InvoiceRecyclerViewViewHolder invoiceViewHolder = new InvoiceRecyclerViewViewHolder(itemView, OnClick); // With clicks

            return invoiceViewHolder;
        }

        //public event EventHandler<int> ItemClick;

        //void OnClick(int invoiceId)
        //{
        //    if (ItemClick != null)
        //    {
        //        ItemClick(this, invoiceId);
        //    }
        //}

    }

    public class InvoiceRecyclerViewViewHolder : RecyclerView.ViewHolder
    {
        public TextView ReferenceNumber { get; private set; }
        public TextView InvoiceAmount { get; private set; }
        public TextView BillTo { get; private set; }
        public int InvoiceId { get; set; }

        // A Constructor with No clicks
        public InvoiceRecyclerViewViewHolder(View itemView) : base(itemView)
        {
            ReferenceNumber = itemView.FindViewById<TextView>(Resource.Id.invoiceReferenceNumber);
            BillTo = itemView.FindViewById<TextView>(Resource.Id.billTo);
            InvoiceAmount = itemView.FindViewById<TextView>(Resource.Id.invoiceAmount);
        }

        ////A constructor with clicks
        //public InvoiceRecyclerViewViewHolder(View itemView, Action<int> listener) : base(itemView)
        //{
        //    ReferenceNumber = itemView.FindViewById<TextView>(Resource.Id.invoiceReferenceNumber);
        //    BillTo = itemView.FindViewById<TextView>(Resource.Id.billTo);
        //    InvoiceAmount = itemView.FindViewById<TextView>(Resource.Id.invoiceAmount);

        //    ItemView.Click += (sender, e) => listener(InvoiceId);
        //}
    }

}

Well, I've defined the Adapter and the ViewHolder inside the same file. Feel free to divide them differently though.

Don't worry about the commented lines, we'll be using it later.

Now open your InvoicesFragment.cs, and replace it with this:

using System;
using Android.OS;
using Android.Support.V4.App;
using Android.Support.V7.Widget;
using Android.Views;
using Android.Widget;
using InvoiceJe.Models;
using System.Collections.Generic;
using System.Linq;
using InvoiceJe.Data;
using InvoiceJe.Droid.Adapters;
using Android.Content;
using InvoiceJe.Droid.Activities;

namespace InvoiceJe.Droid.Fragments
{
    public class InvoicesFragment : Fragment
    {
        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            var view = inflater.Inflate(Resource.Layout.invoices_fragment, container, false);

            var repository = new Repository();
            var invoices = repository.GetInvoices();

            RecyclerView recyclerView = view.FindViewById<RecyclerView>(Resource.Id.recyclerView);
            LinearLayoutManager layoutManager = new LinearLayoutManager(view.Context);
            recyclerView.SetLayoutManager(layoutManager);
            InvoicesRecyclerViewAdapter adapter = new InvoicesRecyclerViewAdapter(invoices);
            //adapter.ItemClick += OnItemClick; // register onItemClick
            recyclerView.SetAdapter(adapter);

            // divider
            // ref: https://stackoverflow.com/questions/24618829/how-to-add-dividers-and-spaces-between-items-in-recyclerview
            recyclerView.AddItemDecoration(new DividerItemDecoration(view.Context, DividerItemDecoration.Vertical));

            return view;
        }

        //void OnItemClick(object sender, int invoiceId)
        //{
        //    Toast.MakeText(this.Context, "This is invoice " + invoiceId.ToString(), ToastLength.Short).Show();

        //    //Intent intent = new Intent(this.Context, typeof(InvoicesEditActivity));
        //    //intent.PutExtra("InvoiceId", position);
        //    //StartActivity(intent);
        //}
    }
}

Alright, we did it! Try running and see how it works.

Explanation

Now let's take a look at InvoicesFragment.cs first, okay?

var repository = new Repository();
var invoices = repository.GetInvoices();

...Here we simply get the list of invoices from our repository.

RecyclerView recyclerView = view.FindViewById<RecyclerView>(Resource.Id.recyclerView);

...Here we refer to the RecyclerView we defined in our invoices_fragment.axml.

LinearLayoutManager layoutManager = new LinearLayoutManager(view.Context);
            recyclerView.SetLayoutManager(layoutManager);

This is where we define the LayoutManager. It basically determines how our items inside RecyclerView will be laid out. There are 3 predefined: LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager (great for pictures). You can also define a custom one yourself if you want.

For our purposes, we will use the LinearLayoutManager.

            InvoicesRecyclerViewAdapter adapter = new InvoicesRecyclerViewAdapter(invoices);
            recyclerView.SetAdapter(adapter);

...This is where we set the Adapter that will fill the RecyclerView with data.

Let's take a look at InvoiceRecyclerViewViewHolder:

    public class InvoiceRecyclerViewViewHolder : RecyclerView.ViewHolder
    {
        public TextView ReferenceNumber { get; private set; }
        public TextView InvoiceAmount { get; private set; }
        public TextView BillTo { get; private set; }
        public int InvoiceId { get; set; }

        // A Constructor with No clicks
        public InvoiceRecyclerViewViewHolder(View itemView) : base(itemView)
        {
            ReferenceNumber = itemView.FindViewById<TextView>(Resource.Id.invoiceReferenceNumber);
            BillTo = itemView.FindViewById<TextView>(Resource.Id.billTo);
            InvoiceAmount = itemView.FindViewById<TextView>(Resource.Id.invoiceAmount);
        }

        ////A constructor with clicks
        //public InvoiceRecyclerViewViewHolder(View itemView, Action<int> listener) : base(itemView)
        //{
        //    ReferenceNumber = itemView.FindViewById<TextView>(Resource.Id.invoiceReferenceNumber);
        //    BillTo = itemView.FindViewById<TextView>(Resource.Id.billTo);
        //    InvoiceAmount = itemView.FindViewById<TextView>(Resource.Id.invoiceAmount);

        //    ItemView.Click += (sender, e) => listener(InvoiceId);
        //}
    }

...What happened is basically, we defined public TextViews and inside the constructor we tell it each Property refers to which TextView inside our item template.

Now let's take a look at InvoicesRecyclerViewAdapter.

private IEnumerable<Invoice> _invoices;

        public InvoicesRecyclerViewAdapter(IEnumerable<Invoice> invoices)
        {
            _invoices = invoices;
        }

...Inside the constructor, we simply receive data from our Fragment/Activity.

public override int ItemCount => _invoices.Count();

We tell the adapter how to calculate the number of items/rows. In our case, it'd simply be _invoices.Count().

        public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
        {

            View itemView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.recyclerview_invoice, parent, false);

            InvoiceRecyclerViewViewHolder invoiceViewHolder = new InvoiceRecyclerViewViewHolder(itemView); // No clicks
            //InvoiceRecyclerViewViewHolder invoiceViewHolder = new InvoiceRecyclerViewViewHolder(itemView, OnClick); // With clicks

            return invoiceViewHolder;
        }

This is where we tell the Adapter to use which viewholder. Here, we told the adapter to use InvoiceRecyclerViewViewHolder.

And lastly, the OnBindViewHolder:

        public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
        {
            InvoiceRecyclerViewViewHolder invoiceViewHolder = holder as InvoiceRecyclerViewViewHolder;

            var currentInvoice = _invoices.OrderBy(x => x.ReferenceNumber).ElementAt(position);
            invoiceViewHolder.ReferenceNumber.Text = "Invoice #" + currentInvoice.ReferenceNumber;
            invoiceViewHolder.BillTo.Text = currentInvoice.BillTo.ToString();
            invoiceViewHolder.InvoiceAmount.Text = "RM " + currentInvoice.Amount.ToString();
            invoiceViewHolder.InvoiceId = currentInvoice.Id;
        }

...Basically OnBindViewHolder is where the databinding takes place at each row of data. This where we tell the Adapter which data goes to which element inside ViewHolder.

InvoiceRecyclerViewViewHolder invoiceViewHolder = holder as InvoiceRecyclerViewViewHolder;

...holder is the ViewHolder we created in OnCreateViewHolder. We simply casted it to InvoiceRecyclerViewViewHolder.

var currentInvoice = _invoices.OrderBy(x => x.ReferenceNumber).ElementAt(position);

This is where we get the invoice at the current row. If it's row 1, it's this invoice. If it's row 2, then it's a different invoice.

            invoiceViewHolder.ReferenceNumber.Text = "Invoice #" + currentInvoice.ReferenceNumber;
            invoiceViewHolder.BillTo.Text = currentInvoice.BillTo.ToString();
            invoiceViewHolder.InvoiceAmount.Text = "RM " + currentInvoice.Amount.ToString();
            invoiceViewHolder.InvoiceId = currentInvoice.Id;

...And this is where we assign the data to the TextViews through the ViewHolder.

Yay, we've got it! Try running it. You should get a nice RecyclerView that is filled with data from Repository.cs.

RecyclerView with Clicks

Okay, now a RecyclerView that you can only scroll is pretty boring. So let's implement clicks for fun.

So, basically what we'll do is that when the user clicks on one Invoice, it'll say: "This is invoice #xxx").

What needs to happen is:

  1. We define what happens when an item inside RecyclerView is clicked, inside our Fragment using the function OnItemClick().
  2. Then, we tell the ViewHolder to listen to the ItemView.Click event.
  3. Afterwards, we tell the Adapter that when ItemView.Click event is fired inside ViewHolder, send it to the Fragment to fire the OnItemClick() function.

Let's go!

First, open InvoicesFragment and uncomment these lines:

        void OnItemClick(object sender, int invoiceId)
        {
            Toast.MakeText(this.Context, "This is invoice " + invoiceId.ToString(), ToastLength.Short).Show();

            //Intent intent = new Intent(this.Context, typeof(InvoicesEditActivity));
            //intent.PutExtra("InvoiceId", position);
            //StartActivity(intent);
        }

(Leave the 3 commented out lines alone, we'll touch them later)

Now, basically we defined that when an item is clicked, show a Toast "This is invoice xxx".

Now let's see how to wire this function to the RecyclerView.

Next, take a look at InvoiceRecyclerViewViewHolder. And replace it with this:

    public class InvoiceRecyclerViewViewHolder : RecyclerView.ViewHolder
    {
        public TextView ReferenceNumber { get; private set; }
        public TextView InvoiceAmount { get; private set; }
        public TextView BillTo { get; private set; }
        public int InvoiceId { get; set; }

        //// A Constructor with No clicks
        //public InvoiceRecyclerViewViewHolder(View itemView) : base(itemView)
        //{
        //    ReferenceNumber = itemView.FindViewById<TextView>(Resource.Id.invoiceReferenceNumber);
        //    BillTo = itemView.FindViewById<TextView>(Resource.Id.billTo);
        //    InvoiceAmount = itemView.FindViewById<TextView>(Resource.Id.invoiceAmount);
        //}

        //A constructor with clicks
        public InvoiceRecyclerViewViewHolder(View itemView, Action<int> listener) : base(itemView)
        {
            ReferenceNumber = itemView.FindViewById<TextView>(Resource.Id.invoiceReferenceNumber);
            BillTo = itemView.FindViewById<TextView>(Resource.Id.billTo);
            InvoiceAmount = itemView.FindViewById<TextView>(Resource.Id.invoiceAmount);

            ItemView.Click += (sender, e) => listener(InvoiceId);
        }
    }

Basically, we've changed the constructor so that it can receive a listener. And it assigns the event ItemView.Click to listener(). So, each time ItemView.Click is fired, listener() is triggered.

In our case, we want the listener() to be the OnItemClick() in the fragment. We can't directly talk with the Fragment from the ViewHolder though. Hence, we need to pass listener() through the Adapter. So let's do that.

Go to InvoicesRecyclerViewAdapter. Replace it with this:

public class InvoicesRecyclerViewAdapter : RecyclerView.Adapter
    {

        private IEnumerable<Invoice> _invoices;

        public InvoicesRecyclerViewAdapter(IEnumerable<Invoice> invoices)
        {
            _invoices = invoices;
        }

        public override int ItemCount => _invoices.Count();

        public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
        {
            InvoiceRecyclerViewViewHolder invoiceViewHolder = holder as InvoiceRecyclerViewViewHolder;

            var currentInvoice = _invoices.OrderBy(x => x.ReferenceNumber).ElementAt(position);
            invoiceViewHolder.ReferenceNumber.Text = "Invoice #" + currentInvoice.ReferenceNumber;
            invoiceViewHolder.BillTo.Text = currentInvoice.BillTo.ToString();
            invoiceViewHolder.InvoiceAmount.Text = "RM " + currentInvoice.Amount.ToString();
            invoiceViewHolder.InvoiceId = currentInvoice.Id;
        }

        public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
        {

            View itemView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.recyclerview_invoice, parent, false);

            //InvoiceRecyclerViewViewHolder invoiceViewHolder = new InvoiceRecyclerViewViewHolder(itemView); // No clicks
            InvoiceRecyclerViewViewHolder invoiceViewHolder = new InvoiceRecyclerViewViewHolder(itemView, OnClick); // With clicks

            return invoiceViewHolder;
        }

        public event EventHandler<int> ItemClick;

        void OnClick(int invoiceId)
        {
            if (ItemClick != null)
            {
                ItemClick(this, invoiceId);
            }
        }

    }

What happened is that we defined an EventHandler<int> named ItemClick and a function named OnClick() that will trigger ItemClick() whenever it's called.

The line:

InvoiceRecyclerViewViewHolder invoiceViewHolder = new InvoiceRecyclerViewViewHolder(itemView, OnClick); // With clicks

...means we tell the ViewHolder that our Listener is OnClick().

Alright, we're done making the bridge. So the only thing left is to assign ItemClick EventHandler to the OnItemClick() function in our Fragment. Hence, each time OnClick() inside our Adapter is triggered, it will trigger OnItemClick() inside our Fragment.

So go to InvoicesFragment again and uncomment this line:

            adapter.ItemClick += OnItemClick; // register onItemClick

Okay, now we're done wiring all those stuffs. Try running the damn app. The Toast will show each time you show the data.

Note that we've sent InvoiceId throughout the Events. Obviously you can send other stuffs as well.

Yay!

InvoicesFragment -> EditInvoicesActivity

Wouldn't it be cool if someone clicked on an Invoice in InvoicesFragment and it will immediately open EditActivity with the correct Invoice? Yeah, that'd be pretty cool.

So what we need to do is:

  1. Make an EditInvoicesActivity
  2. Change the OnItemClick in InvoicesFragment to Start EditInvoicesActivity.

Let's do it bruh.

EditInvoicesActivity

Alright, so now make an EditInvoicesActivity.

Basically we want to make an Activity that allows us to edit an Invoice.

(We won't do the editing feature yet, only the showing part)

You smarties would probably have figured out how to do this, but let's just go through with the flow.

First, create a Layout named invoicesedit_activity.axml and fill it with this:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <android.support.design.widget.AppBarLayout
      android:id="@+id/appbar"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        app:layout_scrollFlags="scroll|enterAlways|snap" />
  </android.support.design.widget.AppBarLayout>
  <android.support.v4.widget.NestedScrollView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layout_behavior="@string/appbar_scrolling_view_behavior">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

      <android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayoutInvoiceReferenceNumber"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
          android:layout_marginTop="16dp"
          android:layout_marginLeft="16dp">

        <android.support.design.widget.TextInputEditText
                android:id="@+id/invoiceReferenceNumber"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="20sp"
                android:inputType="text"
                android:hint="Reference Number"/>

      </android.support.design.widget.TextInputLayout>

      <android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayoutInvoiceDateIssued"        android:layout_width="match_parent"
        android:layout_height="wrap_content"
          android:layout_marginTop="16dp"
          android:layout_marginLeft="16dp">

        <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="20sp"
                android:inputType="date"
                android:hint="Date Issued"
                />

      </android.support.design.widget.TextInputLayout>

      <android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayoutInvoiceBillTo"        android:layout_width="match_parent"
        android:layout_height="wrap_content"
          android:layout_marginTop="16dp"
          android:layout_marginLeft="16dp">

        <android.support.design.widget.TextInputEditText
                android:id="@+id/invoiceBillTo"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="20sp"
                android:inputType="textPersonName"
                android:hint="Bill To"
                />

      </android.support.design.widget.TextInputLayout>

      <android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
          android:layout_marginTop="16dp"
          android:layout_marginLeft="16dp">

        <android.support.design.widget.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="20sp"
                android:inputType="textEmailAddress"
                android:hint="Email To"
                />

      </android.support.design.widget.TextInputLayout>

      <android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayoutInvoiceAmount"       
android:layout_width="match_parent"
        android:layout_height="wrap_content"
          android:layout_marginTop="16dp"
          android:layout_marginLeft="16dp">

        <android.support.design.widget.TextInputEditText
                android:id="@+id/invoiceAmount"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="20sp"
                android:inputType="numberDecimal"
                android:hint="Total"
                />

      </android.support.design.widget.TextInputLayout>

    </LinearLayout>
  </android.support.v4.widget.NestedScrollView>
  <android.support.design.widget.FloatingActionButton
      android:id="@+id/save_button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/ic_save_white_18dp"
      app:elevation="4dp"
      android:layout_gravity="bottom|right"
      android:layout_margin="@dimen/fab_margin"
      />
</android.support.design.widget.CoordinatorLayout>

(Yup, basically same as invoicescreate_activity.axml)

Now, inside the Activities folder, create an Activity named InvoicesEditActivity.cs and fill it with this:

using Android.App;
using Android.OS;
using Android.Support.Design.Widget;
using Android.Support.V7.App;
using Android.Support.V7.Widget;
using Android.Views;
using Android.Widget;
using InvoiceJe.Data;
using InvoiceJe.Models;
using System.Linq;

namespace InvoiceJe.Droid.Activities
{
    [Activity(Theme = "@style/MasterTheme")]
    public class InvoicesEditActivity : AppCompatActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.invoicesedit_activity);

            // Setup toolbar
            Android.Support.V7.Widget.Toolbar toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
            toolbar.SetTitle(Resource.String.applicationname); // Set toolbar title here

            if (toolbar != null)
            {
                SetSupportActionBar(toolbar);
                SupportActionBar.SetHomeButtonEnabled(true);
                SupportActionBar.SetDisplayHomeAsUpEnabled(true);
            }

            var repository = new Repository();
            int invoiceId = this.Intent.Extras.GetInt("InvoiceId");
            Invoice invoice = repository.GetInvoices().Where(x => x.Id == invoiceId).FirstOrDefault();
            
            // To handle animations. If we don't do this, the animation will stutter upon page load.
            TextInputLayout textInputLayoutInvoiceReferenceNumber = FindViewById<TextInputLayout>(Resource.Id.textInputLayoutInvoiceReferenceNumber);
            TextInputLayout textInputLayoutInvoiceBillTo = FindViewById<TextInputLayout>(Resource.Id.textInputLayoutInvoiceBillTo);
            TextInputLayout textInputLayoutInvoiceAmount = FindViewById<TextInputLayout>(Resource.Id.textInputLayoutInvoiceAmount);
            textInputLayoutInvoiceReferenceNumber.HintAnimationEnabled = false;
            textInputLayoutInvoiceBillTo.HintAnimationEnabled = false;
            textInputLayoutInvoiceAmount.HintAnimationEnabled = false;

            TextView referenceNumberTextView = FindViewById<TextView>(Resource.Id.invoiceReferenceNumber);
            TextView billToTextView = FindViewById<TextView>(Resource.Id.invoiceBillTo);
            TextView amountTextView = FindViewById<TextView>(Resource.Id.invoiceAmount);
            referenceNumberTextView.Text = invoice.ReferenceNumber;
            billToTextView.Text = invoice.BillTo;
            amountTextView.Text = invoice.Amount.ToString();

            // re-enable animations again
            textInputLayoutInvoiceReferenceNumber.HintAnimationEnabled = true;
            textInputLayoutInvoiceBillTo.HintAnimationEnabled = true;
            textInputLayoutInvoiceAmount.HintAnimationEnabled = true;

        }

        public override bool OnOptionsItemSelected(IMenuItem item)
        {
            switch (item.ItemId)
            {
                case global::Android.Resource.Id.Home:
                    OnBackPressed();
                    break;
            }
            return true;
        }
    }
}

Gotchas: Toolbar MUST BE Android.Support.V7.Widget.Toolbar or else your cat will be sacrificed to the Android gods.

(Now if you compare it with InvoicesCreateActivity, it's pretty much the same. Makes it more logical to make something like BaseActivity and inherit from there, right? Yup, you're correct! However, we'll not do that inside this tutorial for clarity, but feel free to do it yourself later!)

Okay, now we're done making the EditActivity (which doesn't show anything for now anyways).

Update OnItemClick()

Let's go back to InvoicesFragment and update the OnItemClick() function.

What we need to do is simply replace OnItemClick() with this:

        void OnItemClick(object sender, int invoiceId)
        {
            //Toast.MakeText(this.Context, "This is invoice " + invoiceId.ToString(), ToastLength.Short).Show();

            Intent intent = new Intent(this.Context, typeof(InvoicesEditActivity));
            intent.PutExtra("InvoiceId", invoiceId);
            StartActivity(intent);
        }

Now, try running it. Try clicking on any of the invoice.

Alright! Pat yourself on the back bruh!

Explanations

Inside OnItemClick(), you'll see these lines:

            Intent intent = new Intent(this.Context, typeof(InvoicesEditActivity));
            intent.PutExtra("InvoiceId", invoiceId);
            StartActivity(intent);

...Now, the intent.PutExtra() line is where we pass parameters to a different Activity.

So, inside InvoicesEditActivity, you'll see this line:

            int invoiceId = this.Intent.Extras.GetInt("InvoiceId");

...This is how we get a parameter sent from a different Activity.

The whole lines:

            var repository = new Repository();
            int invoiceId = this.Intent.Extras.GetInt("InvoiceId");
            Invoice invoice = repository.GetInvoices().Where(x => x.Id == invoiceId).FirstOrDefault();
            
            // To handle animations. If we don't do this, the animation will stutter upon page load.
            TextInputLayout textInputLayoutInvoiceReferenceNumber = FindViewById<TextInputLayout>(Resource.Id.textInputLayoutInvoiceReferenceNumber);
            TextInputLayout textInputLayoutInvoiceBillTo = FindViewById<TextInputLayout>(Resource.Id.textInputLayoutInvoiceBillTo);
            TextInputLayout textInputLayoutInvoiceAmount = FindViewById<TextInputLayout>(Resource.Id.textInputLayoutInvoiceAmount);
            textInputLayoutInvoiceReferenceNumber.HintAnimationEnabled = false;
            textInputLayoutInvoiceBillTo.HintAnimationEnabled = false;
            textInputLayoutInvoiceAmount.HintAnimationEnabled = false;

            TextView referenceNumberTextView = FindViewById<TextView>(Resource.Id.invoiceReferenceNumber);
            TextView billToTextView = FindViewById<TextView>(Resource.Id.invoiceBillTo);
            TextView amountTextView = FindViewById<TextView>(Resource.Id.invoiceAmount);
            referenceNumberTextView.Text = invoice.ReferenceNumber;
            billToTextView.Text = invoice.BillTo;
            amountTextView.Text = invoice.Amount.ToString();

            textInputLayoutInvoiceReferenceNumber.HintAnimationEnabled = true;
            textInputLayoutInvoiceBillTo.HintAnimationEnabled = true;
            textInputLayoutInvoiceAmount.HintAnimationEnabled = true;

...is simply to fill the data inside invoicesedit_activity.axml.

The lines:

            // To handle animations. If we don't do this, the animation will stutter upon page load.
            TextInputLayout textInputLayoutInvoiceReferenceNumber = FindViewById<TextInputLayout>(Resource.Id.textInputLayoutInvoiceReferenceNumber);
            TextInputLayout textInputLayoutInvoiceBillTo = FindViewById<TextInputLayout>(Resource.Id.textInputLayoutInvoiceBillTo);
            TextInputLayout textInputLayoutInvoiceAmount = FindViewById<TextInputLayout>(Resource.Id.textInputLayoutInvoiceAmount);
            textInputLayoutInvoiceReferenceNumber.HintAnimationEnabled = false;
            textInputLayoutInvoiceBillTo.HintAnimationEnabled = false;
            textInputLayoutInvoiceAmount.HintAnimationEnabled = false;

.
.
.


            // re-enable animations again
            textInputLayoutInvoiceReferenceNumber.HintAnimationEnabled = true;
            textInputLayoutInvoiceBillTo.HintAnimationEnabled = true;
            textInputLayoutInvoiceAmount.HintAnimationEnabled = true;

Are required so that the page won't stutter upon page load. We simply:

  1. Disable animations
  2. Change the Texts
  3. Then enable animations again

Wow, we're done! Our next step will be to finally connect with a database (specifically SQLite).

...And yeah, it's bs how much hoops we need to go through, but that's life.

 Sample at Github: https://github.com/purrmiaw/InvoiceJeBindingNoDatabase