Xamarin.Native Tutorial: InvoiceJe: iOS

We are now going to make an iOS version of InvoiceJe.

If you haven't setup your Mac yet, you'll need to set it up now or you won't be able to follow the tutorial.

If you've done setting up, let's go!

Open Main.storyboard

We're going to use storyboards in our app. So, for now let's take a look at our Main.storyboard and ViewController.cs.

You'll see something like this:

Several things you need to know:

What you're looking at is referred to as a "Scene".

Click on the "View Controller" bar at the bottom. Now look at the Properties.

The Class property refers to the Class that will be the code-behind for this particular View Controller. So in this case, the class ViewController.cs is the code-behind. You can change to any class you want.

Now, Click on the "Hello World, Click Me" button.

The Name property refers to the ID/Name of the Button when you refer to it in your ViewController.cs. In this case, the name of this button is "Button", so inside ViewController.cs, you can refer to it as Button, as per the sample code inside ViewController.cs:

using System;

using UIKit;

namespace InvoiceJe.iOS
{
	public partial class ViewController : UIViewController
	{
		int count = 1;

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

		public override void ViewDidLoad ()
		{
			base.ViewDidLoad ();
			// Perform any additional setup after loading the view, typically from a nib.
			Button.AccessibilityIdentifier = "myButton";
			Button.TouchUpInside += delegate {
				var title = string.Format ("{0} clicks!", count++);
				Button.SetTitle (title, UIControlState.Normal);
			};
		}

		public override void DidReceiveMemoryWarning ()
		{
			base.DidReceiveMemoryWarning ();
			// Release any cached data, images, etc that aren't in use.
		}
	}
}

The "Hello World, Click Me" button also have been centered using AutoLayout.

By default, when you drag elements into the Scene, you are giving the absolute coordinates of the element. This works well if all iPhones are the same size. However, obviously iPhones have different sizes, so using absolute coordinates will make your layout looks nice on iPhone 6, but will look like crazy in iPad.

Xamarin have a tutorial on using AutoLayout here that you should totally read, later. However, for now, we'll simply drag and drop stuffs onto the View.

Alright, let's take a look inside ViewController.cs.

Remember, just like Android, in iOS you also need to define the events inside the code-behind, in this case the ViewController.cs.

The line:

Button.TouchUpInside += delegate {
				var title = string.Format ("{0} clicks!", count++);
				Button.SetTitle (title, UIControlState.Normal);
			};

...will be fired when the TouchUpInside event is fired on Button. TouchUpInside is just like an OnClick event.

Alright, try running it again and make sure you understand what's going on.

Tabs, InvoicesViewController, ConfigurationViewController

Alright, done looking at the HelloWorld sample? Alright, we don't need all that, so let's delete all of them!

In Main.storyboard, delete the Scene by clicking on the "View Controller" text and hit delete. You're going to get an error later in ViewController.cs (because we referred to Button) so be sure to comment out all of ViewController.cs. We don't need it.

Remember, we're trying to make something like this:

Good thing is, iOS already have a built-in element for Tabs in place and Xamarin have translated them to Visual Studio well.

So, open Toolbox. Drag the Tab Bar controller to the Storyboard. Just arrange them however you want.

You'll realize there's a flag symbol. That flag signifies the Initial View Controller to be shown to the user. If it's somewhere else, drag the Flag symbol to the TabBarController, making it the initial ViewController.

And another thing to remember, the arrow thing that signifies the app's flow is referred to as Segue.

Alright, now you'll see that the TabBar only have 'Item 1' and 'Item 2' as the items. So let's change that to something better. So in one of the child ViewController click on the TabBarItem and change the BarItem Title to 'Invoices'. Change the other child ViewController to 'Configuration'.

Your storyboard should look like this:

This is a good time to run your iOS app. So try running it and see if things work as intended.

Using NavigationController and Segues

Alright, our next part is to make the InvoicesCreate view, which can navigated to by clicking on a Button in Invoices view.

Now, iOS also have a built-in ViewController that handles Forward-and-Back relationships for us automatically. So let's use NavigationViewController for our Invoices page instead rather than an empty ViewController.

So, remove the InvoicesViewController. Then, open Toolbar. Drag the NavigationController to the Storyboard. You'll get a NavigationController and a TableViewController.

The "Root View Controller" looks pretty jarring. So let's rename the TableViewController to Invoices. Click on "Root View Controller" and change the Title.

Now Ctrl+drag from the TabBar controller to the new NavigationController to set it as one of the tabs.

But, how do I reorder the tab items in the TabBarController? You can't. So if the Invoices and Configuration have been mis-ordered, simply delete the Segues and add them back in your desired order.

Now, we're going to add 2 other views. The InvoicesCreate viewcontroller and the InvoicesEdit viewcontroller.

What's going to happen is that:

  1. When user click on the Create button, it navigates to InvoicesCreate
  2. When user clicks on any invoices in the table, it navigates to InvoicesEdit of that invoice.

Let's fulfill number 1 first.

Open Toolbox. Drag a Button and put it at top-right part of the NavigationBar (the top bar) inside the Invoices ViewController. It will automatically snap itself. Now change the label to 'Create'.

Alright, now we've done that, let's add another ViewController and make it the InvoicesCreateViewController. 

You know the drill. Drag a ViewController from the Toolbox. Rename it to InvoicesCreateViewController.

Now Ctrl+drag from the 'Create' button to the InvoicesCreateViewController. In the Action Segue options, set it to 'Show'. What's 'Show Detail' anyway? It's for UISplitViewController. We're not using it right now, but feel free to play around with it later.

Now, by using NavigationController, it will automatically handle the back stacks and the back button for us. Unlike Android, iOS will handle everything for us. Both good and bad.

Your storyboard should look like this:

This is a good time to 'Play'. So try playing it and see if it works as intended.

Configuration Page & AutoLayout

For now we still won't go into the code-behind yet. But we'll start by designing a nice, simple Configuration page for our app.

We'll make something like this:

Now remember the ConfigurationViewController that we made? We made it using ViewController, right? Well, we'll be using a TableViewController.

Let's do this:

  1. Delete the ConfigurationViewController
  2. Drag a TableViewController into the Storyboard.
  3. Ctrl+drag from TabViewController to the new TableViewController to create a segue
  4. Rename the TabItem's Title to Configuration

Alright, now we have the scene set up, let's set up the TableView.

(Note: usually you need to drag a TableView into the ViewController by yourself, but since we dragged a TableViewController already, so it's already been set up for you.)

Let's do this:

  1. Click on the TableView. Look at Properties. Under TableView section, look at Content. Change the value to Static Cells. (By default, you need to populate the TableView dynamically, from database for example. However, in this case, we'll be populating it manually, so Static Cells it is).
  2. Now, change the Style to Grouped. Now, rather than a bland-looking TableView full of rows, we can group several rows nicely.
  3. Let's change the number of Sections to 2, since we'll only have 2 sections in our ConfigurationViewController.
  4. Now, inside the Scene, click on each TableViewSection. Then look at Properties. Change the Rows to 1 for both TableViewSections. Alternatively, you can simply click and delete the rows you don't want.

Now, look at the screenshot back. We're going to make the Purchase Now button first.

  1. Click on the lower TableViewSection first. Look at Properties. Change the Header text to "Upgrade". The Footer text to "Upgrade to unlock more features!"
  2. Now click on the row (TableViewCell). Change the Style to 'Basic'. The editor will then add a Label to the row automatically. Change the Label text to 'Purchase Now'.
  3. Let's add an Accessory to indicate to the user that it is clickable. Click on the row again, look at Accessory and change it to Detail Disclosure.
  4. Now you have a nice looking Upgrade section.

Now let's do the General section.

  1. Select the upper TableViewSection. Change the Header to 'General' and Footer to 'General settings'.
  2. Click on the Row. This time, set the Style to Custom. We're be doing it ourselves.
  3. Open Toolbox and drag a Label onto the Row. Align it nicely on the left, snap it in the center vertically. Change the Title to 'Local Only'. 
  4. Next, drag a Switch from the Toolbox. Align it on the right, snap it center vertically.

Yay! Now try running your app.

It should be running nicely, but chances are even though you've aligned the Labels and Switches correctly, it still looks... not correct in your emulator, or it might even not be there.

AutoLayout and Constraints

So this is where AutoLayout and Constraints come in.

You see, if you've been using the default settings, you've been looking at the Storyboard in Generic view. You can change it to iPhone 4, 5, iPad, etc. view by changing the View As: dialog at the top toolbar.

Try changing your View Mode to iPhone, iPad etc and you'll see that your Switch will look horribly misaligned. 

This is simply because by simply dragging and dropping the controls onto our Scenes, we've only set the Explicit coordinates of the control. So basically since iPhone 4 is smaller than the Generic scene we have here, the Switch will be outside of the screen, hence not seen by the user. On the other hand, iPad is bigger than the Generic view, so our Switch will be in the center of the screen instead.

So, iOS have AutoLayout. And to tell iOS how to align the Controls no matter the screen size, you introduce Constraints to each Controls.

Alright, so let's do it. Let's set the Constraints on our 'Local Only' Label first.

Click on your Label. You'll see this:

Now click on your Label again. You'll see this:

So this is the Layout view.

Now click on the middle square icon and drag to the middle snap line. The snap line will change from green to blue if you're doing it correctly. Now we've set the constraint "No matter what size the row is, this Label should be aligned vertically like this based on the center snap line".

Next, let's set the left-side constraint. Now pull the sideways T icon on the left and drag it to the Left edge of the screen. Now it's "No matter what size the screen is, the Label needs to be THIS far from the left edge".

Now, the I icon is the height constraint. So we can constraint the height of the Label based on the height of another element. But for now, we're not going to do that.

Let's do the Switches next. Double-click on the Switches. Now pull the right-side sideways T icon to the right edge of the screen. Awesome.

Now pull the center square to the middle vertical snap line.

Now change it to any other view from Generic and see if it's aligned correctly.

If it's okay, try running your App and look at it again. Yay!

Basic Segues

Alright, you've been looking Segues as something that signifies the flow of your app.

You can also do something like "The User will move to this screen after clicking on a button" easily with Segues.

Alright, so what we're going to do is, whenever the User clicks on the Purchase Now row, the user will be sent to another screen.

Open Toolbox and drag a ViewController to the Storyboard.

Now, simply Ctrl+Drag from the Purchase Now TableViewRow (NOT THE LABEL) to the new ViewController.

Save and run your iOS app. Now each time someone clicks on Purchase Now, they'll be sent to the new screen.

As usual, the sample is available at Github: https://github.com/purrmiaw/InvoiceJeNoDatabase

Congratulations! Next we will mess with the database and more complex segues.

 

 

Xamarin.Native Tutorial: InvoiceJe: Android

Alright, so let's start with our Android implementation.

Setup

But first, we need to do a bunch of setup to ensure our life in Xamarin.Native goes well.

Android is notorious for throwing exceptions when we least expect it, which usually is because we're not using the correct theme, not using its appcompat versions or we simply didn't install the correct library required (it's okay if you don't understand).

So, follow these steps to make your life easier:

Install Android SDK 7.1.1

Usually Visual Studio would have these installed by default, however it may not be so for you. So you'll need to install the Android SDKs we're going to use first.

First, go to Tools > Android > Android SDK Manager

Tick the checkbox beside Android 7.1.1 > SDK Platform, like so:

...Then, click Install at the bottom right of the screen.

Feel free to install all of the other stuffs as well, but this tutorial will use Android 7.1.1.

Change Target Framework to Android 7.1

Now, in the solution explorer, expand InvoiceJe.Android and double-click on Properties.

Make sure to change the Target Framework to Android 7.1 (if it's not already).

Install Xamarin.Android.Support.Design

Open Package Manager Console. Set the default project to InvoiceJe.Android. Then, run

install-package Xamarin.Android.Support.Design
install-package Xamarin.Android.Support.v4

Nearly every single Android app we create will rely on these packages. So you'll need install them.

If you get error here and if you had to install the 7.1 SDK, then try creating a new project instead. The project may be targeting the older SDKs and didn't switch over when we installed new SDKs.

Setup Resources Folder

Now, Xamarin actually didn't create all the standard folder or files required in the Resources folder.

(this is so that the developer can mix and choose though, but is very disorienting when you're a noob and trying to follow a tutorial and you don't have those folders, so yeah so not noob-friendly).

So, we'll have to do it ourselves.

To make things simple, I've made a zip file for you that contains all the required files and folders. So simply download it, unzip, then paste them inside the Resources folder.

Resources.zip (42.54 kb)

Note: You'll get an error in MainActivity after you copy this.

Change the line:

SetContentView(Resource.Layout.Main) to SetContentView(Resource.Layout.main_activity)

[Activity (Label = "InvoiceJe.Android", MainLauncher = true, Icon = "@drawable/icon")] to [Activity (Label = "InvoiceJe.Android", MainLauncher = true, Icon = "@mipmap/icon")]

I will explain the role of each folder.

  • drawable, drawable-hdpi, drawable-mdpi, drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi:
    • These are folders where you put your image files. hdpi, mdpi, etc refers to the size of the device.
    • The drawable folder hosts the default images if no images are found on other folders (you need this folder to support Android <1.6)
  • layout
    • This hosts our layouts
  • menu
    • this will host our menu xmls
  • mipmap
    • this will host our app launcher icons.
  • values
    • This folder have all xml values that we use inside our app.
    • arrays.xml
      • Hosts our xml arrays
    • colors.xml
      • Where we put our app's colours
    • dimens.xml
      • Where we keep the dimensions/margins/paddings of elements in our views
    • strings.xml
      • Where we keep our strings and texts.
    • styles.xml
      • Where we keep our styles.
      • I've made a MasterTheme here, so you can simply use this in your app and you'll have no problems with errors regarding themes.

MainActivity

Alright, now we're done setting up, let's take a look at MainActivity and modify it to suit our needs.

Let's open MainActivity.cs and main_activity.axml. You will see these code:

[Activity (Label = "InvoiceJe.Android", MainLauncher = true, Icon = "@mipmap/icon")]
	public class MainActivity : Activity
	{
		int count = 1;

		protected override void OnCreate (Bundle bundle)
		{
			base.OnCreate (bundle);

			// Set our view from the "main" layout resource
			SetContentView (Resource.Layout.main_activity);

			// Get our button from the layout resource,
			// and attach an event to it
			Button button = FindViewById<Button> (Resource.Id.myButton);
			
			button.Click += delegate {
				button.Text = string.Format ("{0} clicks!", count++);
			};
		}
	}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
  <Button
      android:id="@+id/myButton"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="@string/hello" />
</LinearLayout>

 

The line

[Activity (Label = "InvoiceJe.Android", MainLauncher = true, Icon = "@mipmap/icon")]

..is the attribute. It means the label is "InvoiceJe.Android" (which will be used as the AppLauncher text or Toolbar title), MainLauncher means it will be the main launcher and the app launcher will launch this activity. Icon means the icon will be taken from mipmap/icon.png.

// Set our view from the "main" layout resource
SetContentView (Resource.Layout.main_activity);

This line sets the Layout of this activity. So in our case, it's main_activity.axml, hence you put SetContentView(Resource.Layout.main_activity) here.

// Get our button from the layout resource,
			// and attach an event to it
			Button button = FindViewById<Button> (Resource.Id.myButton);
			
			button.Click += delegate {
				button.Text = string.Format ("{0} clicks!", count++);
			};

These lines are basically as per the comment.

If you see inside the main_activity.axml file, you'll see this:

<Button
      android:id="@+id/myButton"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="@string/hello" />

This means, the button have the id "myButton". So to refer to this button from MainActivity.cs you use the line FindViewById<Button>(Resource.id.myButton).

Usually in Android development the events aren't wired by you. You'll need to wire it yourself. So, the line:

button.Click += delegate {
				button.Text = string.Format ("{0} clicks!", count++);
			};

...means on button.Click, change the button text to the string {0} clicks!

Of course there are many other events which you can wire. But basically this is the cycle in Android development.

Add an element in axml -> Refer to it in Activity/Fragment -> Wire events to it

MainActivity -> CoordinatorLayout and BottomNavigationView

Now, remember, we're aiming to make something like this:

So our current axml file aren't gonna cut it. So let's do some more awesome things.

So open your main_activity.axml and replace 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>
    <FrameLayout
        android:id="@+id/main_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_above="@+id/bottom_navigation"
        android:layout_alignParentTop="true" />
    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/white"
        android:foregroundTint="@color/accent"
        android:elevation="8dp"
        app:elevation="8dp"
        app:layout_anchor="@+id/main_container"
        app:layout_anchorGravity="bottom"
        app:layout_behavior="com.miaw.invoiceje.BottomNavigationViewBehavior"
        app:menu="@menu/mainactivity_bottomnavigationview" />
  
    <android.support.design.widget.FloatingActionButton
        android:id="@+id/floatingactionbutton_navigatetoinvoicescreateviewmodel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_marginBottom="64dp"
        android:layout_marginRight="16dp"
        android:src="@drawable/ic_add_white_18dp"
        android:elevation="6dp"
        app:fabSize="normal"
        app:rippleColor="@color/accent" />
</android.support.design.widget.CoordinatorLayout>

<!--app:backgroundTint="@color/accent"-->

...And to control the behavior of the BottomNavigationView, create a folder named Extensions, create a class named BottomNavigationBehavior and put this inside (it's okay if you don't understand this, just do it):

using System.Collections.Generic;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Support.Design.Widget;
using Android.Support.V4.View;
using Android.Util;

namespace InvoiceJe.Droid.Extensions
{

    [Register("com.miaw.invoiceje.BottomNavigationViewBehavior")]
    public class BottomNavigationViewBehavior :
        CoordinatorLayout.Behavior
    {
        private int height;

        public BottomNavigationViewBehavior(Context context, IAttributeSet attrs) : base()
        {

        }

        public override bool LayoutDependsOn(CoordinatorLayout parent, Java.Lang.Object child, View dependency)
        {
            return dependency is FloatingActionButton || base.LayoutDependsOn(parent, child, dependency);
        }

        public override bool OnLayoutChild(CoordinatorLayout parent, Java.Lang.Object child, int layoutDirection)
        {
            BottomNavigationView childView = child.JavaCast<BottomNavigationView>();
            this.height = childView.Height;
            return base.OnLayoutChild(parent, child, layoutDirection);

        }

        public override bool OnStartNestedScroll(CoordinatorLayout coordinatorLayout, Java.Lang.Object child, View directTargetChild, View target, int nestedScrollAxes)
        {
            return nestedScrollAxes == ViewCompat.ScrollAxisVertical
                    || base.OnStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
        }

        public override void OnNestedScroll(CoordinatorLayout coordinatorLayout, Java.Lang.Object child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
        {
            // dyconsumed: user initiates movement on the y-axis and it happens on screen
            // dyunconsumed: user initiates movement on the y-axis but it doesn't happen on screen, for example, downwards movement at the bottom end of the list.

            // dy > 0: user swiping up, so screen in scrolling down
            // dy < 0: user swiping down, so screen is scrolling up

            base.OnNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);

            BottomNavigationView childView = child.JavaCast<BottomNavigationView>();

            if (dyUnconsumed > 0 || dyConsumed > 0)
            {
                this.SlideDown(childView);
            }
            else if (dyUnconsumed < 0 || dyConsumed < 0)
            {
                this.SlideUp(childView);
            }

            // define our own behavior
            IList<View> dependencies = coordinatorLayout.GetDependencies(childView);

            foreach (View view in dependencies)
            {
                if (view is FloatingActionButton)
                {
                    FloatingActionButton floatingActionButtonView = view.JavaCast<FloatingActionButton>();

                    if (dyUnconsumed > 0 || dyConsumed > 0)
                    {
                        floatingActionButtonView.Hide();
                    }
                    else if (dyUnconsumed < 0 || dyConsumed < 0)
                    {
                        floatingActionButtonView.Show();
                    }

                }
            }

        }

        private void SlideUp(BottomNavigationView child)
        {
            child.ClearAnimation();
            child.Animate().TranslationY(0).SetDuration(200);
        }

        private void SlideDown(BottomNavigationView child)
        {
            child.ClearAnimation();
            child.Animate().TranslationY(height).SetDuration(200);
        }
    }
}

...and finally, update MainActivity.cs to this:

using Android.App;
using Android.OS;
using Android.Support.V7.App;
using Android.Support.Design.Widget;
using Android.Support.V7.Widget;
using Android.Content;
//using InvoiceJe.Droid.Fragments;
//using InvoiceJe.Droid.Activities;
using Android.Views;

namespace InvoiceJe.Droid
{
	[Activity (Label = "InvoiceJe.Android", MainLauncher = true, Icon = "@mipmap/icon", Theme = "@style/MasterTheme")]
	public class MainActivity : AppCompatActivity
	{
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.main_activity);
          
            // Setup toolbar
            Toolbar toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);
            toolbar.SetTitle(Resource.String.applicationname); // Set toolbar title here

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

            //// Setup FloatingActionButton click
            //FloatingActionButton navigateToCreateInvoicesActivityFloatingActionButton = FindViewById<FloatingActionButton>(Resource.Id.floatingactionbutton_navigatetoinvoicescreateviewmodel);
            //navigateToCreateInvoicesActivityFloatingActionButton.Click +=
            //    delegate
            //    {
            //        Intent intent = new Intent(this, typeof(InvoicesCreateActivity));
            //        StartActivity(intent);
            //    };

            //// Setup BottomNavigationView clicks 
            //BottomNavigationView bottomNavigationView = FindViewById<BottomNavigationView>(Resource.Id.bottom_navigation);
            //bottomNavigationView.NavigationItemSelected += (sender, e) =>
            //{
            //    Android.Support.V4.App.FragmentTransaction fragmentTransaction =
            //        this.SupportFragmentManager.BeginTransaction();
            //    fragmentTransaction.SetCustomAnimations(Resource.Animation.abc_fade_in, Resource.Animation.abc_fade_out);

            //    switch (e.Item.ItemId)
            //    {
            //        case Resource.Id.bottomnavigationview_invoices:
            //            InvoicesFragment invoicesFragment = new InvoicesFragment();
            //            fragmentTransaction.Replace(Resource.Id.main_container, invoicesFragment);
            //            fragmentTransaction.Commit();

            //            // show the floating action button
            //            navigateToCreateInvoicesActivityFloatingActionButton.Show();
            //            break;

            //        case Resource.Id.bottomnavigationview_configuration:
            //            ConfigurationFragment configurationFragment = new ConfigurationFragment();
            //            fragmentTransaction.Replace(Resource.Id.main_container, configurationFragment);
            //            fragmentTransaction.Commit();

            //            // hide the floating action button
            //            navigateToCreateInvoicesActivityFloatingActionButton.Hide();

            //            break;

            //    }
            //};

            //// OnCreate only run once when this activity is created. So let's set the default
            //// fragment to show by 'clicking' on the bottomnavigationview_invoices button.
            //View bottomNavigationViewInvoicesButton = bottomNavigationView.FindViewById(Resource.Id.bottomnavigationview_invoices);
            //bottomNavigationViewInvoicesButton.PerformClick();

        }

    }
}

Try deploying and running the app. If all goes well, you're going to get this:

In main_activity.axml, we have CoordinatorLayout as our root element. CoordinatorLayout is a layout that provides us a simpler method handle animations in our activity.

Toolbar will be the top toolbar you always see in Android apps.

BottomNavigationView contains our bottom tabs.

BottomNavigationViewBehavior.cs is where we define the animations of the activity in relation to BottomNavigationView. We won't delve into it, but feel free to read about it here later.

Let's take a look at MainActivity.cs

First and foremost. See that we are inheriting from AppCompatActivity. This is required so that we can use all those AppCompat goodies. If you don't inherit from AppCompatActivity, you'll only get confusing errors.

The lines:

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

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

...Sets the toolbar to act as an ActionBar. The line SetSupportActionBar(toolbar) is required to support older Androids. Now, all ActionBars have a HomeButton. The commented lines .SetDisplayHomeAsUpEnabled(true) means switching the HomeButton with a Back Button. SetHomeButtonEnabled(false) means to remove the Home Button entirely.

Alright, now our MainActivity looks awesome, though it's still empty.

Add InvoicesFragment, ConfigurationFragment, InvoicesCreateActivity

 

Now let's add the InvoicesFragment and ConfigurationFragment.

Alright, so in Android you have Activity and Fragment. You can say that a Fragment is a mini-activity that you can put inside any Activity.

So let's go. First add a folder named Fragments and another folder named Activities.

Add these files

Resources/Layout/invoices_fragment.axml

(Basically, just a NestedScrollView and a bunch of dummy TextViews to simulate scroll. We'll add database functionality later)

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
   xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="?android:dividerHorizontal"
        android:showDividers="middle"
  >
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/bottomnavigationview_invoices" />
  </LinearLayout>


</android.support.v4.widget.NestedScrollView>

 

Fragments/InvoicesFragment.cs:

using Android.OS;
using Android.Support.V4.App;
using Android.Views;

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);
            return view;
        }
    }
}

 

Resources/Layout/configuration_fragment.axml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
  >
  <LinearLayout
      android:orientation="vertical"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:divider="?android:dividerHorizontal"
      android:showDividers="middle">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/configurationview_localonly" />
    <!-- 26sp kalau kecik-->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_marginTop="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="@string/configurationview_purchase" />
    <!-- 26sp kalau kecik-->
    <!--<View
    android:layout_width="match_parent"
    android:layout_height="1dp"
    android:background="@android:color/darker_gray"/>-->
  </LinearLayout>
</android.support.v4.widget.NestedScrollView>

Fragments/ConfigurationFragment.cs:

using Android.OS;
using Android.Views;
using Android.Support.V4.App;

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

Resources/Layout/invoicescreate_activity.axml

<?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: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="text"
                android:hint="Reference"/>

      </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="date"
                android:hint="Date Issued"
                />

      </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="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: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="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>

Activities/InvoicesCreateActivity.cs

using Android.App;
using Android.OS;
using Android.Support.V7.App;
using Android.Support.V7.Widget;
using Android.Views;

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

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

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

        }

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

Gotchas: You need to have the [Activity] attribute for Xamarin to recognize it as an Activity.

...and now, return to MainActivity.cs and uncomment these lines:

            // Setup FloatingActionButton click
            FloatingActionButton navigateToCreateInvoicesActivityFloatingActionButton = FindViewById<FloatingActionButton>(Resource.Id.floatingactionbutton_navigatetoinvoicescreateviewmodel);
            navigateToCreateInvoicesActivityFloatingActionButton.Click +=
                delegate
                {
                    Intent intent = new Intent(this, typeof(InvoicesCreateActivity));
                    StartActivity(intent);
                };

            // Setup BottomNavigationView clicks 
            BottomNavigationView bottomNavigationView = FindViewById<BottomNavigationView>(Resource.Id.bottom_navigation);
            bottomNavigationView.NavigationItemSelected += (sender, e) =>
            {
                Android.Support.V4.App.FragmentTransaction fragmentTransaction =
                    this.SupportFragmentManager.BeginTransaction();
                fragmentTransaction.SetCustomAnimations(Resource.Animation.abc_fade_in, Resource.Animation.abc_fade_out);

                switch (e.Item.ItemId)
                {
                    case Resource.Id.bottomnavigationview_invoices:
                        InvoicesFragment invoicesFragment = new InvoicesFragment();
                        fragmentTransaction.Replace(Resource.Id.main_container, invoicesFragment);
                        fragmentTransaction.Commit();

                        // show the floating action button
                        navigateToCreateInvoicesActivityFloatingActionButton.Show();
                        break;

                    case Resource.Id.bottomnavigationview_configuration:
                        ConfigurationFragment configurationFragment = new ConfigurationFragment();
                        fragmentTransaction.Replace(Resource.Id.main_container, configurationFragment);
                        fragmentTransaction.Commit();

                        // hide the floating action button
                        navigateToCreateInvoicesActivityFloatingActionButton.Hide();

                        break;

                }
            };

            // OnCreate only run once when this activity is created. So let's set the default
            // fragment to show by 'clicking' on the bottomnavigationview_invoices button.
            View bottomNavigationViewInvoicesButton = bottomNavigationView.FindViewById(Resource.Id.bottomnavigationview_invoices);
            bottomNavigationViewInvoicesButton.PerformClick();

 

Great job! Now let's try running it again.

Yeah.. Now your app looks awesome! Give yourself a pat on the back ;).

Now let's do some explanation. Let's take a look at MainActivity.cs first.

            // Setup FloatingActionButton click
            FloatingActionButton navigateToCreateInvoicesActivityFloatingActionButton = FindViewById<FloatingActionButton>(Resource.Id.floatingactionbutton_navigatetoinvoicescreateviewmodel);
            navigateToCreateInvoicesActivityFloatingActionButton.Click +=
                delegate
                {
                    Intent intent = new Intent(this, typeof(InvoicesCreateActivity));
                    StartActivity(intent);
                };

This part here tells Android to Start the InvoicesCreateActivity when FloatingActionButton is clicked.

// Setup BottomNavigationView clicks 
            BottomNavigationView bottomNavigationView = FindViewById<BottomNavigationView>(Resource.Id.bottom_navigation);
            bottomNavigationView.NavigationItemSelected += (sender, e) =>
            {
                Android.Support.V4.App.FragmentTransaction fragmentTransaction =
                    this.SupportFragmentManager.BeginTransaction();
                fragmentTransaction.SetCustomAnimations(Resource.Animation.abc_fade_in, Resource.Animation.abc_fade_out);

                switch (e.Item.ItemId)
                {
                    case Resource.Id.bottomnavigationview_invoices:
                        InvoicesFragment invoicesFragment = new InvoicesFragment();
                        fragmentTransaction.Replace(Resource.Id.main_container, invoicesFragment);
                        fragmentTransaction.Commit();

                        // show the floating action button
                        navigateToCreateInvoicesActivityFloatingActionButton.Show();
                        break;

                    case Resource.Id.bottomnavigationview_configuration:
                        ConfigurationFragment configurationFragment = new ConfigurationFragment();
                        fragmentTransaction.Replace(Resource.Id.main_container, configurationFragment);
                        fragmentTransaction.Commit();

                        // hide the floating action button
                        navigateToCreateInvoicesActivityFloatingActionButton.Hide();

                        break;

                }
            };

This where we handle the BottomNavigationView.

Now this is going to be a bit hairy, but if you want to swap Fragments inside an Activity, you need to use FragmentTransaction.

Here's in a simpler form:

 Android.Support.V4.App.FragmentTransaction fragmentTransaction =
                    this.SupportFragmentManager.BeginTransaction();
                fragmentTransaction.SetCustomAnimations(Resource.Animation.abc_fade_in, Resource.Animation.abc_fade_out);
InvoicesFragment invoicesFragment = new InvoicesFragment();
                        fragmentTransaction.Replace(Resource.Id.main_container, invoicesFragment);
                        fragmentTransaction.Commit();
  1. First we BeginTransaction().
  2. Then we set Animations (or anything else).
  3. Then we replace one of our Views in the current Activity with the Fragment we want. We usually use FrameLayout (as you can see, we're replacing main_container, which is a FrameLayout in main_activity.axml) as our placeholder for Fragment.
  4. Then Commit(). Here, all of our setup will be executed by Android.

Next, the lines
navigateToCreateInvoicesActivityFloatingActionButton.Show() and navigateToCreateInvoicesActivityFloatingActionButton.Hide() simply means we'll show the FloatingActionButton if the current Fragment is InvoicesFragment, whereas hide it if the current Fragment is ConfigurationFragment.

The last 2 lines:

// OnCreate only run once when this activity is created. So let's set the default
            // fragment to show by 'clicking' on the bottomnavigationview_invoices button.
            View bottomNavigationViewInvoicesButton = bottomNavigationView.FindViewById(Resource.Id.bottomnavigationview_invoices);
            bottomNavigationViewInvoicesButton.PerformClick();

...is to tell Android to Perform a Click on the bottomnavigationview_invoices button. If we don't do this, the first time the user opens MainActivity, he/she'll be greeted by the MainActivity but none of the InvoicesFragment/ConfigurationFragment is active. So, the first time the user opens this app, we click it for them.

In Android Lifecycle, the OnCreate() is only run in the first time this Activity is create, so it's fine to put it here.

Alright, let's take a look at InvoicesFragment:

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

This is simple. Basically we're just telling the Fragment to inflate invoices_fragment.axml. The only difference with Activity is that we're overriding OnCreateView.

How about invoices_fragment.axml?

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="?android:dividerHorizontal"
        android:showDividers="middle">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:layout_marginTop="16dp"
            android:layout_marginLeft="16dp"
            android:layout_marginBottom="16dp"
            android:text="@string/bottomnavigationview_invoices" />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:layout_marginTop="16dp"
            android:layout_marginLeft="16dp"
            android:layout_marginBottom="16dp"
            android:text="@string/bottomnavigationview_invoices" />

      <!-- .....omitted cause it's too long -->

    </LinearLayout>
</android.support.v4.widget.NestedScrollView>

So here we have a NestedScrollView and a LinearLayout inside it. 

Now, if you only put LinearLayout, then the screen wouldn't scroll even if there are too many elements that it doesn't fit on screen. That's why you put your Layout inside a ScrollView. However, this time we use a NestedScrollView because our CoordinatorLayout already have a ScrollView of its own. And we can't put a ScrollView inside a ScrollView, that'd make Android turn on its metaphorical head, because it doesn't know which one should scroll when people touch both of them. But we can put NestedScrollView inside a ScrollView, which allows Android to know how to handle both of them. So yeah that's why we end up with this.

(It's k if you don't understand these)

Same thing with ConfigurationFragment.cs and configuration_fragment.axml.

Now let's take a look at InvoicesCreateActivity and invoicescreate_activity.axml.

protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.invoicescreate_activity);

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

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

So you still have the same lines as in MainActivity.cs to setup the toolbar. Now the only difference is this function:

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

..Try running the App again and did you realize you can click on the Back button at the top Toolbar and it'd close the Activity? Well, Android doesn't handle it on its own, it's up to us to handle that behaviour, and this is how we did it.

Remember that we're using AppCompatActivity and called SetSupportActionBar()? So basically what happened is that we're assigning the Toolbar as the ActionBar of this current Activity. So how do we tell Android what to do when somebody clicks on the toolbar buttons?

Yup, do that by overriding OnOptionsItemSelected(). Now, the default ItemId for the Home/Back button is global::Android.Resource.Id.Home, so we tell Android to do the OnBackPressed() function whenever people click Home/Back.

Alright. For now we'll stop here to avoid it being more complex. You can save this as base for any of your Xamarin.Android projects, okay? ;)

Next stop, we'll be implementing SQLite Database and use RecyclerView to show our data.

You can view the example code on Github: https://github.com/purrmiaw/InvoiceJeNoDatabase

 

 

 

 

Enable Developer Mode on Windows 10 Mobile

To debug on your UWP device, you'll need to enable Developer Mode.

Go to Settings > For Developers.

Enable the Developer Mode.

Done. Now your device should be listed in the Device list when you click on the Play Button in Visual Studio.

Setup Your Mac for Development with Xamarin

If you are building with Xamarin.iOS, you'll need to connect your Visual Studio to your Mac as the build server. If you don't connect your PC to your Mac, you can't even open the Storyboard file nor run your build on an emulator or iPhone.

Here are the steps

Buy/Steal a Mac if you don't have one.

Turn on your Mac.

Click on the Apple icon at the top left, click About This Mac. Make sure the version is 10.12 Sierra (this is the version this tutorial is written based on, you can try any newer builds later but I don't know if it'll work). If it's not, update it to 10.12 Sierra. You can update it by clicking on the Apple icon > App Store > Updates. If you don't do this, the Xamarin Mac Agent will not be able to connect to your Mac because reasons.

Version is good? Now there are 2 things you'll need to install and update.

Install XCode 8.3 or above. You can do that by clicking on the Apple > App Store > Search for XCode. Install it.

Install Visual Studio for Mac. You'll need to download it at https://www.visualstudio.com/ using your Mac. More detailed steps here: https://docs.microsoft.com/en-us/visualstudio/mac/installation

Done all that? Nice. Now you need your Mac's IP. Click on the Wifi icon somewhere on the top right. Right Click > Open Network Preferences. Take note of the IP Address.

Awesome. Now go back to your darling PC. Go to Tools > iOS > Xamarin Mac Agent.

You can follow the instructions on the screen. But I'll walk you through anyways.

On your mac, open Spotlight (press Cmd+Space), search for Remote Login, open the Sharing System Preferences.

Enable the "Remote Login" option.

Select Allow Access for Only These Users. Make sure your user is in the list.

Get your Mac's username and password. Note that what you always see on your Mac is the display name. If you don't remember the username, open spotlight (cmd + space), type terminal, open terminal. Use " ls /users " to get a list of users and guess your username from there.

Now back to your PC. Click 'Next'. You'll reach the Xamarin Mac Agent selection page.

Now this is important. Click the Add Mac... button and fill in your Mac's IP Address. Do NOT click on anything in the list. At the time of writing, there may be a bug where the Mac agent simply refuses to connect to Visual Studio when you create a Storyboard later, or maybe I'm just stupid, but anyways do not select from the list. Fill in your IP Address manually in the Add Mac... dialog. Click enter. And after it has finished refreshing, THEN click on your IP in the list.

Now it's going to ask for your Username and Password. Fill it in. Make sure your Mac screen is on and not in sleep mode.

Everything okay? Alright, give yourself a pat on the back. You've earned it.

Enable Developer Mode on Android

To enable developer mode on Android:

  1. Go to Settings > About Phone > Build Number
  2. Tap on the Build Number 7 times. You will get the message "You are now a developer!"
  3. Go back to Settings. Look for Developer Options. Turn it on and also turn on USB Debugging.

Now that you're done, if you're using Visual Studio, simply plug your Android in. You phone will now be available as a debug option.

Xamarin.Native Tutorial: InvoiceJe

Alright, so today we'll be doing a Xamarin.Native tutorial, of which we will be writing an application from scratch to being able to do SQLite database transactions.

This tutorial will be pretty detailed, in which we'll make an app which you can proudly upload to the app store ;). Or at least, it'll give you all you need to know from scratch until you can make a deployable app.

The final app will hopefully look like this:

Mockup made using MarvelApp. I highly recommend them!

Basic Requirements

Of course, to follow this tutorial, you'll need a few things:

The good thing is, that's it! Of course, if you don't have a Mac that might pose a problem.

New Project

First up, let's create a new Xamarin.Native project.

Boot up your Visual Studio 2017. Click File > New Project.

Choose Visual C# > Cross-Platform > Cross Platform App (Xamarin), as per depicted in the screenshot below. Name the project InvoiceJe.

Click OK. Now you'll encounter this dialog:

For our purpose, choose Blank App, Native and Portable Class Library (PCL). Click OK.

You will get another dialog asking for the target and minimum platform versions for the Universal Windows Platform app.

Simply choose the default and click 'OK'.

Cool, now you're all set up. Your Solution tree will look like this.

 

Now, the Portable Class Library represents code that is shared across all platforms. The Android, iOS and UWP obviously represents code for each platform respectively.

Feel free to divide however you want, but usually we'll put all the business logic codes inside the portable class library and the UI ones inside each platform (though sometimes things get hairy inside the UI codes, but that's a problem for another day).

Try Running Each App

Alright, for now let's try running each project.

Universal Windows Platform

Let's try with Universal Windows Project. This is easy because we can simply run it on our machine. 

The methodology is: Deploy -> Run.

First, make sure you've selected InvoiceJe.UWP as the Startup Project and set it to deploy on the Local Machine at the top bar.

In the solution explorer, Right-Click on InvoiceJe.UWP and click Deploy. This will deploy the UWP on the local machine.

Next, click the Play button at the top bar. Your UWP app will now run.

You can also run your app on Windows phone by setting it to Developer Mode. Then simply plug it in and change from Local Machine to your phone.

Android

For Android, I prefer using my Android phone.

First you'll need to set it to Developer Mode.

Next, you'll need to install Android Debug Bridge (ADB) USB Driver if you're unlucky (and in Android development, you are always unlucky).

Download and install it here: https://software.intel.com/protected-download/385047/494732

(The full article about it is here, but just download the driver at the above link and install it)

Now, after installing the ADB driver, plug your Android in. Your Android will now be a choice.

Your phone will now be in the toolbar. Change the Play Button from deploying into the Emulator to your phone.

Now, Right-Click on InvoiceJe.Android and click Deploy. Then, click the Play button. Yay!

iOS

iOS will be a bit involved because you need to Setup your Mac for Development with Xamarin first.

Once you have Setup your Mac, at the Play button, change it to your desired emulator (iPhone/iPad) and then simply click the Play button. 

Alright, so did you manage all of them? Alright, so our next plaything will be Android. So, let's go!

Table of Contents

No Database

UWP: http://miaw.xyz/b/post/2017/07/19/xamarin-native-tutorial-invoiceje-universal-windows-platform

Android: http://miaw.xyz/b/post/2017/07/12/xamarin-native-tutorial-invoiceje-android

iOS: http://miaw.xyz/b/post/2017/07/14/xamarin-native-tutorial-invoiceje-ios

Databinding No Database

Databinding Setup (DO THIS FIRST BEFORE PROCEEDING FURTHER DOWN THE LIST)http://miaw.xyz/b/post/2017/07/25/xamarin-native-tutorial-invoiceje-databinding-setup

UWP: http://miaw.xyz/b/post/2017/08/02/xamarin-native-tutorial-invoiceje-databinding-universal-windows-platform

Android: http://miaw.xyz/b/post/2017/07/25/xamarin-native-tutorial-invoiceje-databinding-android

iOS: http://miaw.xyz/b/post/2017/07/26/xamarin-native-tutorial-invoiceje-databinding-ios

Databinding with Database

Setup EntityFramework and SQLite: http://miaw.xyz/b/post/2017/08/03/xamarin-native-tutorial-invoiceje-sqlite-database (DO THIS FIRST BEFORE PROCEEDING FURTHER DOWN THE LIST)

UWP: In progress

Android: In progress

iOS: In progress

(But I'm pretty sure you have already realized that you simply need to replace the methods inside RabbitService to call the database instead. And yeah you may need to refresh your screens)

Consuming RESTful Webservice + Animations

Setup: http://miaw.xyz/b/post/2017/09/20/xamarin-native-tutorial-consuming-webservice-setup (DO THIS FIRST BEFORE PROCEEDING FURTHER DOWN THE LIST)

 UWP: In Progress

Android: http://miaw.xyz/b/post/2017/09/20/xamarin-native-tutorial-consuming-webservice-android

 

iOS: In progress  

 

 

MVVMCross not calling Initialize() in ViewModel called from RegisterAppStart() in App.cs

MVVMCross 5.0.6 not calling Initialize() in ViewModel called from RegisterAppStart() in App.cs

https://github.com/MvvmCross/MvvmCross/issues/1972

The problem is that the underlying RegisterAppStart uses ShowViewModel() rather than  the new MvxNavigation.

The solution: Implement a custom AppStart that uses the NavigationService instead of ShowViewModel().

public class CustomAppStart : IMvxAppStart
    {
        private readonly IMvxNavigationService _navigationService;

        public CustomAppStart(IMvxNavigationService navigationService)
        {
            _navigationService = navigationService;
        }

        public void Start(object hint = null)
        {
            try
            {
                _navigationService.Navigate<MainViewModel>().GetAwaiter().GetResult();
            }
            catch
            {
            }
        }
    }

...And your Setup.cs should be edited to use the CustomAppStart, like this: 

 public class App : MvvmCross.Core.ViewModels.MvxApplication
    {
        public override void Initialize()
        {
            CreatableTypes()
                .EndingWith("Service")
                .AsInterfaces()
                .RegisterAsLazySingleton();

            // custom app start
            Mvx.ConstructAndRegisterSingleton<IMvxAppStart, CustomAppStart>();
            var customAppStart = Mvx.Resolve<IMvxAppStart>();

            RegisterAppStart(customAppStart);
        }
    }