CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true bug

https://social.msdn.microsoft.com/Forums/en-US/21215c3a-cc0f-46c7-b9e0-3469559e900a/14393-extendviewintotitlebar-can-never-return-to-false-again-even-reinstall?forum=wpdevelop

CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar will not return to false again even after reinstall.

This is because the value is saved inside the registry. 

"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ApplicationFrame\TitleBar\{YourAppIdentifier}"

Setting it to true will save it inside the registry. Setting it to false, however, does not.

 

Xamarin.Native Tutorial: InvoiceJe: Universal Windows Platform

Alright, now let's mess with the Universal Windows Platform.

Developing for UWP is actually pretty fun and it looks friggin' awesome. Too bad not many people use Windows phone :( (Alright Microsoft, pay me that pandering money!)

We'll go with the basics first.

Just like in the Android and iOS platforms, the application will be a tabbed application.

Setup

We'll setup a little thing first.

Open App.xaml.

<Application
    x:Class="InvoiceJe.UWP.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:InvoiceJe.UWP">
    <!--RequestedTheme="Light"-->
    
</Application>

Now, by default UWP sets the RequestedTheme as "Light". We'll remove it and let the app use the default theme of the user's device.

MainPage.xaml

Now open MainPage.xaml. And copy paste this xml:

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

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

    <Grid x:Name="ContentGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>

        <Pivot>
            <PivotItem Header="Invoices" Margin="0">
                <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                    <ListView Margin="0,0,0,48">
                        <TextBlock Text="Invoice #1" />
                        <TextBlock Text="Invoice #2" />
                        <TextBlock Text="Invoice #3" />
                        <TextBlock Text="Invoice #4" />
                        <TextBlock Text="Invoice #5" />
                        <TextBlock Text="Invoice #6" />
                        <TextBlock Text="Invoice #7" />
                        <TextBlock Text="Invoice #8" />
                        <TextBlock Text="Invoice #9" />
                        <TextBlock Text="Invoice #10" />
                        <TextBlock Text="Invoice #11" />
                        <TextBlock Text="Invoice #12" />
                        <TextBlock Text="Invoice #13" />
                        <TextBlock Text="Invoice #14" />
                        <TextBlock Text="Invoice #15" />
                        <TextBlock Text="Invoice #16" />
                        <TextBlock Text="Invoice #17" />
                        <TextBlock Text="Invoice #18" />
                    </ListView>

                    <CommandBar HorizontalAlignment="Stretch" VerticalAlignment="Bottom">
                        <CommandBar.Content>
                            <Grid/>
                        </CommandBar.Content>
                        <AppBarButton Icon="Add" Label="Add Invoice" Click="AppBarButton_Click"/>
                    </CommandBar>
                </Grid>

            </PivotItem>
            <PivotItem Header="Configuration">
                <ScrollViewer>
                    <StackPanel>
                        <TextBlock TextWrapping="Wrap" FontSize="24" Margin="0 0 0 3" Text="General Settings"/>
                        <ToggleSwitch Header="Local Only" Margin="0 0 0 12" />
                        <TextBlock TextWrapping="Wrap" FontSize="24" Margin="0 0 0 3" Text="Purchase"/>
                        <TextBlock TextWrapping="Wrap" Text="Click here to purchase now" />
                    </StackPanel>
                </ScrollViewer>
            </PivotItem>
        </Pivot>

    </Grid>

</Page>

 ...and replace the MainPage.xaml.cs to this:

using InvoiceJe.UWP.Views;
using Windows.ApplicationModel.Core;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace InvoiceJe.UWP
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

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

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

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


        }

        //private void AppBarButton_Click(object sender, RoutedEventArgs e)
        //{
        //    Frame.Navigate(typeof(InvoicesCreatePage));
        //}
    }
}

 

You will end up with these screens.

There, we're done!

Nah, just kidding. But let me explain first.

Let's take a look at the XAML structure first.

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


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

    <Grid x:Name="ContentGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" >

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>

        <Pivot>
            ....
        </Pivot>
    </Grid>

</Page>

The parent is the Page element. So this defines a screen.

There's the <Page.Transitions> element (or actually, one of the Page properties as well). This defines the transitions for the page. You can read all about it here.

Then, you have 1 Grid. This defines the whole page layout. Just like Android.

And inside the Grid, you have the Grid.ColumnDefinitions. This defines the number of columns/cells the grid have. Right now, we only have 1.

Now the other children is the Pivot. Pivot is equivalent to Tabs in Android. So you simply define a Pivot. And then, you define the PivotItem, which is the child tabs. Inside the PivotItems you just define the elements inside.

PivotItem "Invoice"

Now inside the PivotItem "Invoice", we defined another Grid to host our child elements inside.

See that in the attributes of PivotItem: 

<PivotItem Header="Invoices" Margin="0">

...We have set the Margin to "0".

Inside the Grid, we have 2 elements, the ListView and the CommandBar.

We have the ListView which houses a bunch of dummy TextBlocks.

The CommandBar is the bottom bar you see in UWP apps, and is usually used to show the actions available to the user for the current screen. To add buttons, simply define the AppBarButton inside it.

Since the CommandBar is inside the "Invoice" PivotItem, it will be hidden when the user swipes to the "Configuration" PivotItem.

 Now, swipe to the "Invoices" pivot and try stretching the CommandBar.

You'll see that the CommandBar will stretch as well.

This is because...

<CommandBar HorizontalAlignment="Stretch" VerticalAlignment="Bottom">

....we defined the property HorizontalAlignment="Stretch" inside our CommandBar. This means that the horizontal length of the element should be stretched according to the parent element. The VerticalAlignment="Bottom" property means the element will be placed at the bottom of the parent element (which is the Grid) and maintain its default height.

UWP's fluid layout will immediately understand that the CommandBar is meant to be at the bottom of the PivotItem and stretches as necessary. 

(Actually, "Stretch" is the default value for both HorizontalAlignment and VerticalAlignment properties. So for all elements that did not define their Alignment values, they'll default to "Stretch").

Now take a look at the ListView tag again...

<ListView Margin="0,0,0,48">

...Since we did not define the HorizontalAlignment and VerticalAlignment properties, it will default to "Stretch". But this means that the ListView will overlap with the CommandBar! Hence we defined the Margin="0,0,0,48", which means we set the bottom margin to 48px, which is the default height of the CommandBar.

"Configuration" PivotItem

<PivotItem Header="Configuration">
                <ScrollViewer>
                    <StackPanel>
                        <TextBlock TextWrapping="Wrap" FontSize="24" Margin="0 0 0 3" Text="General Settings"/>
                        <ToggleSwitch Header="Local Only" Margin="0 0 0 12" />
                        <TextBlock TextWrapping="Wrap" FontSize="24" Margin="0 0 0 3" Text="Purchase"/>
                        <TextBlock TextWrapping="Wrap" Text="Click here to purchase now" />
                    </StackPanel>
                </ScrollViewer>
            </PivotItem>

Now let's take a look at the Configuration PivotItem.

We've used a ScrollViewer here. ScrollViewer is just like ScrollView in Android. If we're putting too many things on our page until it doesn't fit, by putting it inside a ScrollViewer it will allow the user to scroll the elements that is put inside the ScrollViewer (We actually don't need it for our current Configuration page because we only have 2 elements inside it, but we'll just put it there for the future when the configuration page is big).

But what about the ListView before? We didn't put it inside a ScrollViewer, but I could scroll! It's because ListView have a built-in ScrollViewer itself, so we don't need to wrap it with a ScrollViewer.

And inside the ScrollViewer, we used a StackPanel. It's similar to the LinearLayout of Android, which means the elements defined inside it will 'stack' on top of each other.

The insides are pretty much self-explanatory. We have TextBlock as the text and the ToggleSwitch as the switch. However, here we defined the elements explicitly so that it looks nice (well, according to Microsoft Design Guidelines).

...Unfortunately, UWP doesn't have a built-in BottomNavigationView, so we're going with Pivot in our UWP app.

Of course, some of you will think "How about using the CommandBar as our BottomNavigationView?" Yes, you can, though we're not going to touch that in our tutorial. But with the knowledge from this tutorial it should be easy enough for you to change it to however you want.

...And for the .cs file:

public MainPage()
        {
            this.InitializeComponent();

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

...This one is probably not important to you, but basically we're explicitly setting the app so that it works just like the user's system default.

And this one:

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

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

        }

...is to handle the Back button whether to show to the users or not. Don't worry we're take a look at this later.

InvoicesCreate

Alright, now let's make an InvoicesCreatePage page.

Inside Solution Explorer, at the root of the UWP project, Right Click -> Add Folder and name it Views. Select XAML > Blank Page. Name it InvoicesCreatePage.xaml and click Add.

Your folder structure should look like this:

Now, inside InvoicesCreatePage.xaml, replace with this XAML:

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

    <Page.Transitions>
        <TransitionCollection>
            <EntranceThemeTransition />
        </TransitionCollection>
    </Page.Transitions>
    
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Margin="12 12 12 0">
            <TextBlock TextWrapping="Wrap" Text="Reference" Height="20" FontSize="15" />
            <TextBox TextWrapping="Wrap" Height="32" Margin="0 4 0 0" />
            <TextBlock TextWrapping="Wrap" Text="Date Issued" Height="20" FontSize="15" Margin="0 24 0 0" />
            <DatePicker Height="34" Margin="0,4,0,0" HorizontalAlignment="Stretch" />
            <TextBlock TextWrapping="Wrap" Text="Bill To" Height="20" FontSize="15" Margin="0 24 0 0" />
            <TextBox TextWrapping="Wrap" Height="32" Margin="0 0 0 0" />
            <TextBlock TextWrapping="Wrap" Text="Email To" Height="20" FontSize="15" Margin="0 24 0 0" />
            <TextBox TextWrapping="Wrap" Height="32" Margin="0 0 0 0" />
            <TextBlock TextWrapping="Wrap" Text="Total" Height="20" FontSize="15" Margin="0 24 0 0" />
            <TextBox TextWrapping="Wrap" Height="32" Margin="0 0 0 0" />
        </StackPanel>
    </Grid>

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

...and the code-behind:

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

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

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

            Windows.UI.Core.SystemNavigationManager.GetForCurrentView().BackRequested +=
                App_BackRequested;

        }

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

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

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

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

Alright, now we've made a basic layout to issue an invoice.

The XAML file is self-explanatory, however you'll realize that we have put the CommandBar inside the Property Page.BottomAppBar.

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

...So basically we have assigned this CommandBar as the BottomAppBar.

The reason we do this is because when the on-screen keyboard opens, usually the keyboard will hover on top of the CommandBar, hiding it from the user. By putting it inside <Page.BottomAppBar>, the CommandBar will be visible to the user, at the bottom of the page, even if the user opens the keyboard. 

Now for the .cs file.

The OnNavigatedTo is the same as the one in the MainPage. However, this time, it will modify the app title bar to show the Back button.

The App_BackRequested() is the one that handles the Back Button clicked event. Basically, once clicked, if CanGoBack, GoBack().

Linking to InvoicesCreate From MainPage

Let's navigate from MainPage to InvoicesCreate.

Now, what we want to do is that when the User clicks/presses on the '+' button in the AppBar inside the Invoices PivotItem, we want the User to navigate to the InvoicesCreatePage.

So, let's do that.

Open MainPage.xaml. 

Now simply double-click on the '+' button inside the AppBar.

This will automatically generate the code-behind function named AppBarButton_Click that handles the AppBarButton_Click event.

(If you realized, I've already put the commented out function inside the .cs file, so feel free to use it too)

We can navigate to a different page inside our App using Frame.Navigate, like so:

 private void AppBarButton_Click(object sender, RoutedEventArgs e)
        {
            Frame.Navigate(typeof(InvoicesCreatePage));
        }

Yay, we're done!

Explanation: Open MainPage.xaml again, click on the '+' button and look at the properties. But instead, click on the Events button to show the Events available for the button (the button is on the top right of the Properties pane).

You'll see that for the Click event, it's wired to AppBarButton_Click.

Now try taking a look at the XAML and find the AppBarButton. You'll see the Designer have added a new property:

<AppBarButton Icon="Add" Label="Add Invoice" Click="AppBarButton_Click"/>

That's how events are wired inside UWP. So basically, you can define the function inside the code-behind (in this case, MainPage.xaml.cs) and then simply wire it to the element inside the view by referring to it inside the XAML. Easy-peasy.

Okay, now Deploy and Play.

Congrats!

Example is available on Github:

https://github.com/purrmiaw/InvoiceJeNoDatabase

Now we're going to make the database connection.

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.