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