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

 

Xamarin.Native Tutorial: InvoiceJe: DataBinding: Setup

Alright, now let's go with DataBinding. We still won't make a database though.

We will pick up from where we left earlier.

We will use https://github.com/purrmiaw/InvoiceJeNoDatabase as our starting point (you should be here if you've followed the previous tutorial).

What we are going to do here is databinding. Basically, we will touch on how:

  • To display data from code behind to the screen
  • To bind enumerable data to enumerable views/controls such as ListView (Android, UWP) / TableView (iOS).
  • How to send parameters between activities (Android) / screens (iOS) / pages (UWP)

Okay, let's go!

Setup Dummy Repository

For now, we still won't go into database yet. However, we will setup a dummy repository that returns data to us.

So, in the Portable class, create a folder named Models. Inside it create a class named Invoice. Replace the code with this:

namespace InvoiceJe.Models
{
    public class Invoice
    {
        public int Id { get; set; }
        public string ReferenceNumber { get; set; }
        public string BillTo { get; set; }
        public decimal Amount { get; set; }
    }
}

Now let's create the repository class. Create a folder named Data. Inside it, create a class named Repository. Replace the code with this:

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

namespace InvoiceJe.Data
{
    public class Repository
    {
       
        public IEnumerable<Invoice> GetInvoices()
        {
            var invoices = new List<Invoice>();
            invoices.Add(new Invoice { Id = 1, ReferenceNumber = "001", Amount = 20.12M, BillTo="ABC Sdn Bhd" });
            invoices.Add(new Invoice { Id = 2, ReferenceNumber = "002", Amount = 32.56M, BillTo = "Ahmad and Friends" });
            invoices.Add(new Invoice { Id = 3, ReferenceNumber = "003", Amount = 45.12M, BillTo = "Reddit pvt ltd" });
            invoices.Add(new Invoice { Id = 4, ReferenceNumber = "004", Amount = 27.00M, BillTo = "Google ltd" });
            invoices.Add(new Invoice { Id = 5, ReferenceNumber = "005", Amount = 16.00M, BillTo = "Microsoft ltd" });
            invoices.Add(new Invoice { Id = 6, ReferenceNumber = "006", Amount = 87.00M, BillTo = "Dave Games" });
            invoices.Add(new Invoice { Id = 7, ReferenceNumber = "007", Amount = 4.32M, BillTo = "Authentic Venture Sdn Bhd" });
            invoices.Add(new Invoice { Id = 8, ReferenceNumber = "008", Amount = 20.12M, BillTo = "Nancy" });
            invoices.Add(new Invoice { Id = 9, ReferenceNumber = "009", Amount = 17.00M, BillTo = "Archer" });
            invoices.Add(new Invoice { Id = 10, ReferenceNumber = "010", Amount = 46.85M, BillTo = "Hatsune Miku" });
            invoices.Add(new Invoice { Id = 11, ReferenceNumber = "011", Amount = 95.45M, BillTo = "Game of Thrones pvt ltd" });
            invoices.Add(new Invoice { Id = 12, ReferenceNumber = "012", Amount = 100.2M, BillTo = "Sunsuria Apts ltd" });
            invoices.Add(new Invoice { Id = 13, ReferenceNumber = "013", Amount = 520.7M, BillTo = "Miaw.xyz" });
            invoices.Add(new Invoice { Id = 14, ReferenceNumber = "014", Amount = 1.12M, BillTo = "Authentic Venture Sdn Bhd" });
            invoices.Add(new Invoice { Id = 15, ReferenceNumber = "015", Amount = 50.00M, BillTo = "Acer Sdn Bhd" });
            invoices.Add(new Invoice { Id = 16, ReferenceNumber = "016", Amount = 100.0M, BillTo = "Oppo Sdn Bhd" });
            invoices.Add(new Invoice { Id = 17, ReferenceNumber = "017", Amount = 145.1M, BillTo = "FL Studio" });
            invoices.Add(new Invoice { Id = 18, ReferenceNumber = "018", Amount = 205.8M, BillTo = "Unity3D" });
            invoices.Add(new Invoice { Id = 19, ReferenceNumber = "019", Amount = 785.6M, BillTo = "Goal" });
            invoices.Add(new Invoice { Id = 20, ReferenceNumber = "020", Amount = 15.03M, BillTo = "Google ltd" });

            return invoices;
        }
    }
}

...Basically, we're just returning dummy data first.

Your folder structure should now look like this:

Alright, that's all we need to do in the Portable Class Library. Now let's move on to each platform.

 

How to use Acrylic Accent

You will need to target Creator Update for this.

    <RelativePanel Grid.Column="0" Grid.ColumnSpan="2" MinWidth="40" x:Name="MainGrid" SizeChanged="Page_SizeChanged">
        <RelativePanel Grid.Column="0" Width="{Binding ElementName=MainGrid,Path=Width}" Background="#28000000">
            <Grid>
                <!--Having content here, for example textblock and so on-->
            </Grid>
        </RelativePanel>
    </RelativePanel>

 

public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            applyAcrylicAccent(MainGrid);
        }

        Compositor _compositor;
        SpriteVisual _hostSprite;

        private void applyAcrylicAccent(Panel panel)
        {
            _compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
            _hostSprite = _compositor.CreateSpriteVisual();
            _hostSprite.Size = new Vector2((float)panel.ActualWidth, (float)panel.ActualHeight);

            ElementCompositionPreview.SetElementChildVisual(panel, _hostSprite);
            _hostSprite.Brush = _compositor.CreateHostBackdropBrush();
        }

        private void Page_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (_hostSprite != null)
                _hostSprite.Size = e.NewSize.ToVector2();
        }

    }