FZ2Cl: Xamarin + MVVMCross 5 Tutorial: Episode 2: TipCalc: Part 6: iOS

Now finally we'll start with iOS. So, let's get started.

Modify HomeView

Just like UWP and Android, we're going to add a button at HomeView first.

Open HomeView.storyboard, drag a Button onto the canvas. Give it the name "ClickHereButton" and change the text to "Click Here".

Alright, by now you'd realize there's no way to bind anything on the storyboard like how we did with UWP and Android. And yes, you're correct.

Hence, we're going to bind properties and events by code instead.

So open your HomeView.cs. And add these lines inside ViewDidLoad (after base.ViewDidLoad()) :

   var set = this.CreateBindingSet<HomeView, HomeViewModel>();
            set.Bind(ClickHereButton).For(x => x.BindTouchUpInside()).To(vm => vm.ClickHereButton_Click);
            set.Apply();

So what happened here is:

  1. We created a binding set that binds HomeView to HomeViewModel
  2. We bound ClickHereButton's TouchUpInside event to the IMvxAsyncCommand ClickHereButton_Click we defined in HomeViewModel.

If we don't feel like it, we can simply write the set.Bind() line like this:

set.Bind(ClickHereButton).To(vm => vm.ClickHereButton_Click);

...which means it binds the default property/event of UIButton to the ClickHereButton_Click ICommand, which is incidentally TouchUpInside() as well.

A list of default and other bindable events are available at the Fundamentals - Data Binding page at MVVMCross.

Alright, fire up your iOS app. Of course it's going to throw an error though, because we haven't created the TipCalcView, so let's go!

Add TipCalcView

As per tradition, I'm not going to walk you through this process. Just add a TipCalcView with storyboard.

local:MvxBind to Multiple Properties / Events

Let's say you have

<Button
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Click Here"
local:MvxBind="Click ClickHereButton_Click" />

But you want to bind a text property you set on the view model to the button as well.

You can do it like this:

<Button
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Click Here"
local:MvxBind="Click ClickHereButton_Click; Text PropertyInViewModel" />

Notice the space between ; and Text. The space must be there.

FZ2Cl: Xamarin + MVVMCross 5 Tutorial: Episode 2: TipCalc: Part 5: Android

Well, now we reach the fun part. Android!

Add a Splash Screen

Now, before we do alllll of that, let's add a SplashScreen first. If you remember what we did in HelloWorld, we have set our HomeView as our MainLauncher by applying the ActivityAttribute on the View.

What we're going to do now is to define a SplashScreen as our MainLauncher, and from there, it's going to call the HomeViewModel or any default ViewModel we defined inside our App.cs, basically centralizing everything.

First of all, remove all MainLauncher=true in all of your ActivityAttributes. In our HelloWorld, only the HomeView have the MainLauncher=true parameter, so remove that parameter.

    [Activity]
    public class HomeView : MvxActivity
    {
        protected override void OnViewModelSet()
        {
            SetContentView(Resource.Layout.home_view);
        }
    }

Now, let's create the splashscreen axml layout. Just a view with a 'loading...' text should be fine, so let's do that.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="loading..." />
  
</LinearLayout>

Next is we'll add the SplashScreenActivity. MVVMCross already has a built-in MvxSplashScreenActivity though, so let's just create a SplashScreenActivity class and then inherit from that.

using Android.App;
using MvvmCross.Droid.Views;

namespace HelloWorld.UI.Android.Views
{
    [Activity(Label = "Hello World", MainLauncher = true, NoHistory = true, Icon = "@drawable/icon")]
    public class SplashScreenActivity : MvxSplashScreenActivity
    {
        public SplashScreenActivity() : base(Resource.Layout.splashscreen_view)
        {
        }
    }
}

Now, a little explanation for the Activity attribute

  • Label = "Hello World" is what you will see at the ActionBar and the Icon inside the AppDrawer in your Android phone
  • MainLauncher = true sets the splash screen as the main launcher of your app. This will also list inside the AppDrawer in Android phone
  • NoHistory = true means it wouldn't be added to the back stack. So an user wouldn't reach this page if he clicks back.
  • Icon = "@drawable/icon" sets the icon of the activity inside the AppDrawer. @drawable/icon means Xamarin.Android will look for the icon at /Resources/drawable/icon.png . The png is not required to be written in the string @drawable/icon.

Alright, now that you're done, try running it on your phone. The splashscreen will show before you reach HomeView. Yay!

Modify HomeView

Alright, now we're done with that, let's modify our HomeView. Just like UWP, let's start with adding a button to our HomeView.

Open your home_view.axml.

First we'll need to define a new XML namespace because on top of Android's built-in namespace, we'll be referring to MVVMCross too. 

So inside <LinearLayout> parameters, add this parameter

xmlns:local="http://schemas.android.com/apk/res-auto"

Now that we have defined 'local' as our new namespace, we can refer to MVVMCross's parameters in our AXML file.

Next, add this line inside your linearlayout below your TextView:

  <Button
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:text="Click Here"
    local:MvxBind="Click ClickHereButton_Click" />

Do you see the local:MvxBind part? It's to tell MVVMCross to bind the Click event of the Button to the ClickHereButton_Click ICommand/IMvxAsyncCommand we defined in HomeViewModel.

Note: local:MvxBind binds to the public properties and events exposed by Xamarin.Android for that View/Widget (You can see the properties and events by looking up Android.Widget.Button and use intellisense to view its properties and events). So let's say you want to bind to the LongClick event of a button, just use local:MvxBind = "LongClick WhatEverICommand".

So your home_view.axml will look like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  
  <TextView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="Hello World!" />

  <Button
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:text="Click Here"
    local:MvxBind="Click ClickHereButton_Click" />

</LinearLayout>

Of course, if you click it now you will get an error because we don't have any TipCalcView defined. So let's gooooo.

Add TipCalcView

Let's add a tipcalc_view.axml file first.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</LinearLayout>

Now let's add the TipCalcView class. Likewise, I'm not going to walk you through it, but your end result should look like this:

using MvvmCross.Droid.Views;

namespace HelloWorld.UI.Android.Views
{
    public class TipCalcView : MvxActivity
    {
        protected override void OnViewModelSet()
        {
            SetContentView(Resource.Layout.tipcalc_view);
        }
       
    }
}

Now, run your Android app again. Now on the button click, you should move to TipCalcView. Congrats!

Modify TipCalcView

Okay, now let's modify TipCalcView.

Remember, update your namespace by adding xmlns:local="http://schemas.android.com/apk/res-auto" to your layout first.

Then we can add adding stuffs to our tipcalc_view.axml.

Your whole tipcalc_view.axml should look like this.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:local="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

  <TextView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="SubTotal" />
  <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        local:MvxBind="Text SubTotal" />
  <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Generosity" />
  <SeekBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        local:MvxBind="Progress Generosity" />
  <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#ffff00" />
  <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Tip to leave" />
  <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        local:MvxBind="Text Tip" />
  
</LinearLayout>

Now, Run it.

Wait, we're done? Yep, we're done. All of the bindings have been taken care of with the local:MvxBind attribute.

Just deploy and run the app.

Congratulations!

Now let's play with iOS.

Notes: What if I want to bind multiple properties and events?

You can. The syntax is

local:MvxBind="Text Tip; Click ClickHereButton_Click"

Notice the space between ; and Click. The space must be there.

 

 

 

 

FZ2Cl: Xamarin + MVVMCross 5 Tutorial: Episode 2: TipCalc: Part 4: Universal Windows Platform

Let's go with Universal Windows Platform.

Modify HomeView

Now open your HelloWorld.UI.Uwp and open HomeView.xaml.

We are now going to add a Button and bind the click event to ClickHereButton_Click we defined in HomeViewModel.

So open Toolbox, drag a Button onto the page. Change the text to "Click Here".

Now add the attribute Command="{Binding ClickHereButton_Click}" to the button, like so:

<Button Content="Button" HorizontalAlignment="Left" Margin="186,295,0,0" VerticalAlignment="Top"
                Command="{Binding ClickHereButton_Click, Mode=TwoWay}"
                />

What happened here is that we bind the Button's Command that is fired during a Click event to the ClickHereButton_Click ICommand we defined in our HomeViewModel. Mode=TwoWay means we set the binding as TwoWay, as the default mode in XAML is one-way.

So your whole HomeView should look like this:

<views:MvxWindowsPage
    xmlns:views="using:MvvmCross.Uwp.Views"
    x:Class="HelloWorld.UI.Uwp.Views.HomeView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloWorld.UI.Uwp.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock HorizontalAlignment="Left" Margin="107,190,0,0" TextWrapping="Wrap" Text="Hello World" VerticalAlignment="Top"/>
        <Button Content="Button" HorizontalAlignment="Left" Margin="186,295,0,0" VerticalAlignment="Top"
                Command="{Binding ClickHereButton_Click, Mode=TwoWay}"
                />

    </Grid>
</views:MvxWindowsPage>

You can try running it now and click the button. You'll get an error though because we haven't define the TipCalc view, so let's do that next!

Create TipCalcView

Now create a TipCalcView.

Remember that MVVMCross's convention is that if the ViewModel name is NameViewModel, your views should be named as NameView.

Now modify it just like what you did with the HomeView. I'm not going to walk you through it, but your end result should look like this.

TipCalcView.xaml

<views:MvxWindowsPage
    xmlns:views="using:MvvmCross.Uwp.Views"
    x:Class="HelloWorld.UI.Uwp.Views.TipCalcView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloWorld.UI.Uwp.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock x:Name="TextMiew" HorizontalAlignment="Left" Margin="110,296,0,0" TextWrapping="Wrap" Text="TipCalc" VerticalAlignment="Top"/>
    </Grid>
</views:MvxWindowsPage>

I purposely added a textblock with Tipcalc text so that I can see that we actually have changed views.

TipCalcView.xaml.cs

using MvvmCross.Uwp.Views;

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

namespace HelloWorld.UI.Uwp.Views
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class TipCalcView : MvxWindowsPage
    {
        public TipCalcView()
        {
            this.InitializeComponent();
        }
    }
}

Now try running the app. Once you click on the Click Here button, you should be moved to the other TipCalcView screen.

Modify TipCalcView

Now we're going to modify TipCalcView.xaml to show the Textbox, TextBlocks and Sliders for our TipCalc screen. To do that, we'll define a StackPanel (so that our elements will be, uhh, stacked on top of each other without us having to manually define the coordinates) and put everything inside there.

Your whole TipCalcView should now look like this:

<views:MvxWindowsPage
    xmlns:views="using:MvvmCross.Uwp.Views"
    x:Class="HelloWorld.UI.Uwp.Views.TipCalcView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HelloWorld.UI.Uwp.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Margin="12,0,12,0">
            <TextBlock Text="SubTotal" />
            <TextBox Text="{Binding SubTotal, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="Generosity" />
            <Slider Value="{Binding Generosity,Mode=TwoWay}" 
                SmallChange="1" 
                LargeChange="10" 
                Minimum="0" 
                Maximum="100" />
            <TextBlock Text="Tip" />
            <TextBlock Text="{Binding Tip}" />
        </StackPanel>
    </Grid>
</views:MvxWindowsPage>

Notice that we used UpdateSourceTrigger=PropertyChanged  on the SubTotal textbox. This means that the setter we set for SubTotal will be triggered while we type in the textbox, rather than when the textbox loses focus (which was the default behaviour).

Now, run it.

Congratulations! Now let's move on to Android.

 

 

FZ2Cl: Xamarin + MVVMCross 5 Tutorial: Episode 2: TipCalc: Part 3: Planning and Moving Between ViewModels

Now that we got dependency injection out of the way, let's return to our ViewModels and plan out our stuffs.

Referring to the picture again:

What each viewmodel will have is basically:

  • HomeViewModel
    • event ButtonClickHere_Clicked
  • TipCalcViewModel
    • double SubTotal
    • int Generosity
    • double Tip. And this should be updated automatically each time SubTotal and Generosity is changed.

So let's go!

TipCalcViewModel

We'll start with TipCalcViewModel first.

Create a TipCalcViewModel class inside ViewModels folder and inherit from MvxViewModel.

namespace HelloWorld.Core.ViewModels
{
    public class TipCalcViewModel : MvxViewModel
    {
       
    }
}

We'll be using the ICalculationService, so let's get it via the constructor

        private ICalculationService _calculationService;

        public TipCalcViewModel(ICalculationService calculationService)
        {
            _calculationService = calculationService;
        }

Okay, so we'll have the SubTotal, Generosity and Tip inside our ViewModel, so let's do that.

        private double _subTotal;
        public double SubTotal
        {
            get
            {
                return _subTotal;
            }
            set
            {
                _subTotal = value;
                RaisePropertyChanged(() => SubTotal);
            }
        }

        private int _generosity;
        public int Generosity
        {
            get
            {
                return _generosity;
            }
            set
            {
                _generosity = value;
                RaisePropertyChanged(() => Generosity);
            }
        }

        private double _tip;
        public double Tip
        {
            get
            {
                return _tip;
            }
            set
            {
                _tip = value;
                RaisePropertyChanged(() => Tip);
            }
        }

Wait, what does RaisePropertyChanged(() => Tip); means?

It's a mechanism for us to notify the base MvxViewModel whenever the data, in this case value of SubTotal, Generosity and Tip, changes.

Cool.

Now we want the Tip property to automatically change each time SubTotal and Generosity changes.

To do that, let's create a function that calculates the Tip amount based on Generosity and SubTotal.

        private void Recalculate()
        {
            Tip = _calculationService.TipAmount(SubTotal, Generosity);
        } 

Now each time SubTotal and Generosity changes value (the setter is executed), then it should update the Tip value.

So let's update the setter of SubTotal and Generosity to call Recalculate() as well.

        private double _subTotal;
        public double SubTotal
        {
            get
            {
                return _subTotal;
            }
            set
            {
                _subTotal = value;
                RaisePropertyChanged(() => SubTotal);
                Recalculate();
            }
        }

        private int _generosity;
        public int Generosity
        {
            get
            {
                return _generosity;
            }
            set
            {
                _generosity = value;
                RaisePropertyChanged(() => Generosity);
                Recalculate();
            }
        }

Well, it'd be boring if the user sees 0 when they view the TipCalcViewModel screen. Let's set the initial value too.

        public override void Start()
        {
            this.SubTotal = 100;
            this.Generosity = 20;
            base.Start();
        }

 

Now, your whole TipCalcViewModel should look like this

using HelloWorld.Core.Services;
using MvvmCross.Core.ViewModels;

namespace HelloWorld.Core.ViewModels
{
    public class TipCalcViewModel : MvxViewModel
    {
        private ICalculationService _calculationService;

        public TipCalcViewModel(ICalculationService calculationService)
        {
            _calculationService = calculationService;
        }

        public override void Start()
        {
            this.SubTotal = 100;
            this.Generosity = 20;
            base.Start();
        }

        private double _subTotal;
        public double SubTotal
        {
            get
            {
                return _subTotal;
            }
            set
            {
                _subTotal = value;
                RaisePropertyChanged(() => SubTotal);
                Recalculate();
            }
        }

        private int _generosity;
        public int Generosity
        {
            get
            {
                return _generosity;
            }
            set
            {
                _generosity = value;
                RaisePropertyChanged(() => Generosity);
                Recalculate();
            }
        }

        private double _tip;
        public double Tip
        {
            get
            {
                return _tip;
            }
            set
            {
                _tip = value;
                RaisePropertyChanged(() => Tip);
            }
        }

        private void Recalculate()
        {
            Tip = _calculationService.TipAmount(SubTotal, Generosity);
        } 
    }
}

Phew, that was easy!

Move From HomeViewModel to TipCalcViewModel

Now let's take a look again at our HomeViewModel.

What we want is, whenever an user click on the ClickHere button, the screen will change from HomeViewModel to TipCalcViewModel.

In MVVMCross 5's navigation, navigation is handled by IMvxNavigationService. So let's get that first inside our HomeViewModel:

ICalculationService _calculationService;
        IMvxNavigationService _mvxNavigationService;

        // MVVMCross will pass CalculationService here in the constructor
        public HomeViewModel(ICalculationService calculationService, IMvxNavigationService mvxNavigationService)
        {
            _calculationService = calculationService;
            _mvxNavigationService = mvxNavigationService;
        }

 

Now we'll need to add an IMvxAsyncCommand inside HomeViewModel so that our Button in UWP, Android and iOS can refer to later when the ClickHere button in each respective platform is clicked. Let's do that.

        public IMvxAsyncCommand ClickHereButton_Click
        {
            get
            {
                IMvxAsyncCommand navigationCommand = new MvxAsyncCommand(() => _mvxNavigationService.Navigate<TipCalcViewModel>());
                return navigationCommand;
            }
        }

 

_mvxNavigationService.Navigate<TipCalcViewModel>()

... tells MVVMCross to show TipCalcViewModel.

Your HomeViewModel should now look like this:

using HelloWorld.Core.Services;
using MvvmCross.Core.Navigation;
using MvvmCross.Core.ViewModels;

namespace HelloWorld.Core.ViewModels
{
    public class HomeViewModel : MvxViewModel
    {
        ICalculationService _calculationService;
        IMvxNavigationService _mvxNavigationService;

        // MVVMCross will pass CalculationService and IMvxNavigationService here in the constructor
        public HomeViewModel(ICalculationService calculationService, IMvxNavigationService mvxNavigationService)
        {
            _calculationService = calculationService;
            _mvxNavigationService = mvxNavigationService;
        }

        public IMvxAsyncCommand ClickHereButton_Click
        {
            get
            {
                IMvxAsyncCommand navigationCommand = new MvxAsyncCommand(() => _mvxNavigationService.Navigate<TipCalcViewModel>());
                return navigationCommand;
            }
        }

        private double AnyFunction()
        {
            // We are now free to refer _calculationService anywhere inside the viewmodel
            return _calculationService.TipAmount(10, 2);
        }

    }
}

Your folder structure should look like this:

Further recommended reading about ViewModel navigation: https://www.mvvmcross.com/documentation/tipcalc-tutorial/the-tip-calc-navigation?scroll=300

 Cool! Now we're done with our Core class. Now let's move on with UWP.

Recommended Reading:

https://www.mvvmcross.com/documentation/fundamentals/navigation

 

 

 

 

 

FZ2Cl: Xamarin + MVVMCross 5 Tutorial: Episode 2: TipCalc: Part 2: Dependency Injection

In this part, we will be writing the CalculationService first.

CalculationService will help us calculate the amount of tips we will give based on the amount of food we bought.

This was skipped in the HelloWorld tutorial, but it should be noted that MVVMCross has a built-in support for Dependency Injection, which allows for Inversion of Control and as a side effect makes it possible/easier to do unit testing on each modules or functions.

Honestly, the whole thing deserves a book of its own. Jefferey Pallermo had written a good article about Onion Architecture (long ago in 2008), which is a good light read on how to architect your software and apps.

However, to shorten the whole story, what happens is:

  1. Rather than writing a class that holds the functions we require and reference it directly..
  2. We write an Interface that holds the function names only
  3. And we only refer to the Interface when we use it elsewhere in our application (Typically in a controller in ASP.NET MVC, or in case of MVVMCross, probably inside our ViewModels)
  4. We will then write another Class that implements the Interface
  5. And then during Application Start, we assign the Interface to the Class that we have written, which in turn will be passed to the Controllers or ViewModels via their constructor objects during runtime.
  6. Ta daa! Now you can simply switch the Class to anything you want without affecting the other codes, as long as it implements the interface.
  7. This allows you to do Unit Tests on your controllers and viewmodels by changing the Class to a MockClass, for example. This also allows you to change implementations just by changing one line, which is very useful if, let's say, the service depends on an external source (database, web service, etc).

Well, doing it will make things clearer. So let's go!

In HelloWorld.Core, add a folder named Services. Add an interface named ICalculationService.

namespace HelloWorld.Core.Services
{
    public interface ICalculationService
    {
        double TipAmount(double subTotal, int generosity);
    }
}

Now inside the Services folder, add a folder named Impl. Add a class named CalculationService and implement ICalculationService.

namespace HelloWorld.Core.Services.Impl
{
    public class CalculationService : ICalculationService
    {
        public double TipAmount(double subTotal, int generosity)
        {
            return subTotal * ((double) generosity) / 100.0;
        }
    }
}

Okay, now we have defined the Interface and created a Class implementation of it. Now, we need to tell MVVMCross that "If anybody use ICalculationService, that means they're looking for CalculationService".

To do that, open our App.cs inside HelloWorld.Core and add this line inside the constructor:

            Mvx.RegisterType<ICalculationService, CalculationService>();

So now your whole App.cs will look like this:

using HelloWorld.Core.Services;
using HelloWorld.Core.Services.Impl;
using HelloWorld.Core.ViewModels;
using MvvmCross.Core.ViewModels;
using MvvmCross.Platform;

namespace HelloWorld.Core
{
    public class App : MvxApplication
    {
        public App()
        {
            Mvx.RegisterType<ICalculationService, CalculationService>();
            Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<HomeViewModel>());
        }
    }
}

Now that we're done telling MVVMCross what to do if they see an ICalculationService, we can use it inside our ViewModels now. If you ever define another service, simply map the interface and class implementation here as well.

We'll be using ICalculationService inside TipCalcViewModel later, but let's say we want to reference ICalculationService inside the HomeViewModel, we will need to:

  1. Put a ICalculationService parameter inside the ViewModel constructor
  2. Create a private variable and store the ICalculationService passed to HomeViewModel inside the Constructor.

Here's an example code:

using HelloWorld.Core.Services;
using MvvmCross.Core.ViewModels;

namespace HelloWorld.Core.ViewModels
{
    public class HomeViewModel : MvxViewModel
    {
        ICalculationService _calculationService;

        // MVVMCross will pass CalculationService here in the constructor
        public HomeViewModel(ICalculationService calculationService)
        {
            _calculationService = calculationService;
        }

        private double AnyFunction()
        {
            // We are now free to refer _calculationService anywhere inside the viewmodel
            return _calculationService.TipAmount(10, 2);
        }

    }
}

Here's how your folder structure should look like:

Alright, let's move on!

 

 

FZ2Cl: Xamarin + MVVMCross 5 Tutorial: Episode 2: TipCalc: Part 1: Overview

Alright, now that you have managed to make a Hello World app, now let's make something a little bit cooler which shows off why you'd want to use a Model-View-ViewModel structure in the first place.

This time, will be implementing the TipCalc thingy available on MVVMCross official website, but rather than starting from scratch, we'll extend the HelloWorld thingy we did in Episode 1: Hello World and add our own small twist.

If you feel like taking up a small challenge, just visit the MVVMCross tutorial and try following the TipCalc tutorial there (though there are subtle differences here). The HelloWorld before is just made to address all the niggles you may encounter (not necessarily due to MVVMCross) that isn't addressed in the tutorial.

Overview

Basically, this is what we're going to make.

Excuse my paint skills. And the pic on the right is from MVVMCross TipCalc tutorial.

What will happen is:

HomeViewModel

  1. The HelloWorld text
  2. When you click on the Click Here button, the screen will change to the TipCalcViewModel screen.

TipCalcViewModel

  1. You can fill in the SubTotal with any amount
  2. You can change the Generosity Slider to any value
  3. The tip will change based on the SubTotal amount and the Generosity Slider's value

What You Should Learn From This Episode

  1. Dependency Injection in MVVMCross
  2. How to move between ViewModels
  3. DataBinding ViewModel properties and events with UI Views/Controls

Gotchas

  1. You need to have done the HelloWorld tutorial first
  2. Some explanations are simplified/skipped. This is to ensure the readers can focus on completing the exercise. Notes are written at the end of each part for parts that require explanations. However, we will go a bit more in-depth than we did in HelloWorld.
  3. We'll be jumping between the Core, UWP, Android and iOS projects, so be ready.

Okay, let's go!

 

 

 

Implementing BottomNavigationView with a custom Behavior (Xamarin)

So, I'll be writing about how to implement a BottomNavigationView using a CoordinatorLayout, complete with its own custom behavior (hides itself when you slide down, and shows itself when you slide up), which is defined in Material Design guidelines but not implemented by default.

Here's the layout file (.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>

  <FrameLayout
      android:id="@+id/main_container"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_above="@+id/bottom_navigation"
      app:layout_behavior="@string/appbar_scrolling_view_behavior"
      android:layout_alignParentTop="true">

    <TextView
        android:text="@string/version_number"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@android:id/text1"
        android:padding="@dimen/small_margin"
        android:ellipsize="end"
        android:maxLines="4"
        style="?android:textAppearanceSmall" />

  </FrameLayout>

  <android.support.design.widget.BottomNavigationView
      android:id="@+id/bottom_navigation"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="@color/primaryDark"
      android:foregroundTint="@color/accent"
      app:itemIconTint="@android:color/white"
      app:itemTextColor="@android:color/white"
      app:layout_anchor="@+id/main_container"
      app:layout_anchorGravity="bottom"
      app:layout_behavior="com.companyname.xamarinnative.BottomNavigationViewBehavior"
      app:menu="@menu/bottom_navigation_main" />

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

Here's the menu file (save it as /Resources/menu/bottom_navigation_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
  <item
      android:id="@+id/action_favorites"
      android:enabled="true"
      android:icon="@android:drawable/ic_menu_save"
      android:title="Top"
      app:showAsAction="ifRoom" />
  <item
      android:id="@+id/action_schedules"
      android:enabled="true"
      android:icon="@android:drawable/ic_menu_save"
      android:title="Near"
      app:showAsAction="ifRoom" />
  <item
      android:id="@+id/action_music"
      android:enabled="true"
      android:icon="@android:drawable/ic_menu_save"
      android:title="My"
      app:showAsAction="ifRoom" />
</menu>

Here's the HomeActivity.cs

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

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Support.Design.Widget;

namespace XamarinNative.Droid.Activities
{
    //[Activity(Label = "HomeActivity")]
    [Activity(Label = "HomeActivity")]
    public class HomeActivity : BaseActivity
    {

        BottomNavigationView bottomNavigationView;

        /// <summary>
        /// Sets the layout file
        /// </summary>
        protected override int LayoutResource => Resource.Layout.activity_home;

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

            // Create your application here

            // Setup the top toolbar
            SupportActionBar.SetDisplayHomeAsUpEnabled(false);
            SupportActionBar.SetHomeButtonEnabled(false);

            // Setup BottomNavigationView clicks 
            bottomNavigationView = FindViewById<BottomNavigationView>(Resource.Id.bottom_navigation);

            bottomNavigationView.NavigationItemSelected += (sender, e) =>
            {
                switch (e.Item.ItemId)
                {
                    case Resource.Id.action_favorites:
                        Toast.MakeText(this, "meow", ToastLength.Short).Show();

                        Android.Support.V4.App.FragmentTransaction fragmentTransaction = this.SupportFragmentManager.BeginTransaction();
                        BrowseFragment browseFragment = new BrowseFragment();
                        fragmentTransaction.Replace(Resource.Id.main_container, browseFragment);
                        fragmentTransaction.Commit();

                        break;

                    case Resource.Id.action_schedules:

                        Android.Support.V4.App.FragmentTransaction fragmentTransaction2 = this.SupportFragmentManager.BeginTransaction();
                        AboutFragment aboutFragment = new AboutFragment();
                        fragmentTransaction2.Replace(Resource.Id.main_container, aboutFragment);
                        fragmentTransaction2.Commit();

                        break;
                }
            };


        }
    }
}

...and here's the BottomNavigationViewBehavior

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

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Support.Design.Widget;
using Android.Util;
using Java.Lang;
using Android.Support.V4.View;

namespace XamarinNative.Droid.Extensions
{
    [Register("com.companyname.xamarinnative.BottomNavigationViewBehavior")]
    public class BottomNavigationViewBehavior :
        CoordinatorLayout.Behavior
    {
        private int height;

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

        }

        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;
            //return 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)
        {

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

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

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

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

 

 

 

 

 

 

No installed provisioning profiles match the installed iOS signing identities

No installed provisioning profiles match the installed iOS signing identities

If you get this error message while trying to deploy your application on your iPhone/iPad, then it simply means as it said, you have not deployed any provisioning profile on your Mac.

Basically, what we need to do is to install a provisioning profile inside your Mac using XCode.

The whole process for free provisioning can be found here:

https://developer.xamarin.com/guides/ios/getting_started/installation/device_provisioning/free-provisioning/

..however, I'm going to walk through it here as well because the screenshots over there seems outdated.

First, you'll need an AppleId. If you don't have one already, sign up for one here: appleid.apple.com.

Next, open Xcode. Open Spotlight (Command + Space), Xcode.

Before everything, you'll need to add your AppleID first. So at the bar at the top-left of the monitor, click XCode and click Preferences. Click the Accounts tab. Click the + button at the bottom left and add your AppleID account.

Once you have added your AppleID, click on your AppleID and click on Manage Certificates at the bottom right. Click the + button and choose iOS Development.

Now once it's finished, close the preference window.

Plug in your iPad or iPhone if you haven't into your Mac.

Click Create a new XCode Project, choose Single View Application. 

Now you'll see the project properties page. Change the bundle identifier to match the one you have in Xamarin.iOS.

Under the Signing section, select the profile you added before. Now if you have plugged in your device, XCode will automatically register a provisioning profile for the device for you.

Now you're done. Try deploying from your Visual Studio and start the app.

 

From Zero to Can-lah: Xamarin + MVVMCross 5 Tutorial: Part 5: iOS

Yay! Now we're finally making an iOS project. Let's go!

Add New Project

Well, you know the drill. Right click on your Solution and Add New Project.

Search for Single View App (iOS) and name it HelloWorld.UI.Ios. Click OK.

Install MVVMCross

Go to Package Manager Console. Change the default project to HelloWorld.UI.Ios. Execute

Install-Package MVVMCross

Add Reference to Core Project

Same.. Add reference to HelloWorld.Core Project

Add Setup Class

Now, add a Setup.cs at the root iOS project. It'll need to inherit MvxIosSetup, as per the following code.

using MvvmCross.iOS.Platform;
using MvvmCross.iOS.Views.Presenters;
using MvvmCross.Core.ViewModels;
using HelloWorld.Core;

namespace HelloWorld.UI.Ios
{
    public class Setup : MvxIosSetup
    {
        public Setup(MvxApplicationDelegate appDelegate, IMvxIosViewPresenter presenter)
        : base(appDelegate, presenter)
        {
        }

        protected override IMvxApplication CreateApp()
        {
            return new App();
        }
    }
}

Modify AppDelegate

Now, we need to tell iOS to use our Setup class. We do that by modifying the AppDelegate class.

We need to change the AppDelegate to inherit from MvxApplicationDelegate, like so:

public partial class AppDelegate : MvxApplicationDelegate
And to tell it to use MvvmCross setup and presenter upon finished loading, like so:
var presenter = new MvxIosViewPresenter(this, Window);

var setup = new Setup(this, presenter);
setup.Initialize();

var startup = Mvx.Resolve<IMvxAppStart>();
startup.Start();
Your AppDelegate.cs should look like this:
using Foundation;
using MvvmCross.Core.ViewModels;
using MvvmCross.iOS.Platform;
using MvvmCross.iOS.Views.Presenters;
using MvvmCross.Platform;
using UIKit;

namespace HelloWorld.UI.Ios
{
    // The UIApplicationDelegate for the application. This class is responsible for launching the 
    // User Interface of the application, as well as listening (and optionally responding) to 
    // application events from iOS.
    [Register("AppDelegate")]
    public class AppDelegate : MvxApplicationDelegate
    {
        // class-level declarations

        public override UIWindow Window
        {
            get;
            set;
        }

        public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
        {
            // Override point for customization after application launch.
            // If not required for your application you can safely delete this method

            Window = new UIWindow(UIScreen.MainScreen.Bounds);

            var presenter = new MvxIosViewPresenter(this, Window);

            var setup = new Setup(this, presenter);
            setup.Initialize();

            var startup = Mvx.Resolve<IMvxAppStart>();
            startup.Start();

            Window.MakeKeyAndVisible();

            return true;
        }

        public override void OnResignActivation(UIApplication application)
        {
            // Invoked when the application is about to move from active to inactive state.
            // This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) 
            // or when the user quits the application and it begins the transition to the background state.
            // Games should use this method to pause the game.
        }

        public override void DidEnterBackground(UIApplication application)
        {
            // Use this method to release shared resources, save user data, invalidate timers and store the application state.
            // If your application supports background exection this method is called instead of WillTerminate when the user quits.
        }

        public override void WillEnterForeground(UIApplication application)
        {
            // Called as part of the transiton from background to active state.
            // Here you can undo many of the changes made on entering the background.
        }

        public override void OnActivated(UIApplication application)
        {
            // Restart any tasks that were paused (or not yet started) while the application was inactive. 
            // If the application was previously in the background, optionally refresh the user interface.
        }

        public override void WillTerminate(UIApplication application)
        {
            // Called when the application is about to terminate. Save data, if needed. See also DidEnterBackground.
        }
    }
}

Setup Your Mac

Now, we're going to Add a View. Now if this is your first time 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 develop the view.

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.

Add View

Hey, we're not done yet. Finally, we're going to add a view. We're going to use a Storyboard as our view.

Create a Views folder. And inside the Views folder, Right Click > Add New Item > StoryboardViewController. Name it HomeView.cs.

Now, you're going to have 2 things added to your project, which are HomeView.cs and HomeView.storyboard.

Modify HomeView.cs

Let's modify HomeView.cs first.

Inside HomeView.cs, we'll need to inherit HomeView from MvxViewController, and decorate it with the [MvxFromStoryboard] attribute, to tell MVVMCross to look for the storyboard as the view.

Your HomeView.cs class will look like this

using System;
using System.Drawing;

using Foundation;
using UIKit;
using MvvmCross.iOS.Views;
using HelloWorld.Core.ViewModels;

namespace HelloWorld.UI.Ios.Views
{
    [MvxFromStoryboard]
    public partial class HomeView : MvxViewController<HomeViewModel>
    {
        public HomeView(IntPtr handle) : base(handle)
        {
        }

        public override void DidReceiveMemoryWarning()
        {
            // Releases the view if it doesn't have a superview.
            base.DidReceiveMemoryWarning();

            // Release any cached data, images, etc that aren't in use.
        }

        #region View lifecycle

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

            // Perform any additional setup after loading the view, typically from a nib.
        }

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

        public override void ViewDidAppear(bool animated)
        {
            base.ViewDidAppear(animated);
        }

        public override void ViewWillDisappear(bool animated)
        {
            base.ViewWillDisappear(animated);
        }

        public override void ViewDidDisappear(bool animated)
        {
            base.ViewDidDisappear(animated);
        }

        #endregion
    }
}

 

Modify HomeView.storyboard

Yay, now we're going to modify our storyboard file. Open the storyboard file and you should see this screen.

If you couldn't load the Storyboard, it might be because your Mac is in sleep mode. Turn it on again and make sure you've logged in again.

Now open the Toolbox and drag a ViewController to the screen. It'll be like this: 

Now we need to tell that we want to use HomeView.cs as the ViewController of this screen. So first click on the View Controller. Then open the Properties window on the right. You'll need to do 2 things:

  • Change the class to HomeView.
  • Change the Storyboard ID to HomeView (MVVMCross needs this for reference)

You've done setting up the Storyboard. Lastly, open Toolbox and select Label, drag it onto the screen. Change its name to Hello World.

Okay.. You're done!

Run It

For now, we'll run it on the emulator. In the top toolbar change the Startup project to HelloWorld.UI.Ios and the Play button to iPhone 7 (or anything that you want, like so). 

Click Play.

Now the iPhone 7 emulator should start on your Mac. Congratulations!

Run It on Your iPhone/iPhad

Now, to run it on your real iPhone, simply plug in your iphone to your Mac (NOT your PC) and change the Play button at the top toolbar to Device.

But noooo. If it's your first time, you're going to get this error:

"No installed provisioning profiles match the installed iOS signing identities".

What happens is that iOS requires you to install provisioning profiles for your app in your Mac before you can deploy it anywhere.

Basically, what we need to do is to install a provisioning profile inside your Mac using XCode.

The whole process for free provisioning can be found here:

https://developer.xamarin.com/guides/ios/getting_started/installation/device_provisioning/free-provisioning/

..however, I'm going to walk through it here as well because the screenshots over there seems outdated.

First, you'll need an AppleId. If you don't have one already, sign up for one here: appleid.apple.com.

Next, double check your iOS bundle identifier. On your HelloWorld.UI.Ios, double-click on Properties > iOS Application and look at the Identifier box.

Next, open Xcode. Open Spotlight (Command + Space), Xcode.

Before everything, we're going to add our AppleID first. So at the bar at the top-left of the monitor, click XCode and click Preferences. Click the Accounts tab. Click the + button at the bottom left and add your AppleID account.

Once you have added your AppleID, click on your AppleID and click on Manage Certificates at the bottom right. Click the + button and choose iOS Development.

Now once it's finished, close the preference window.

Plug in your iPad or iPhone if you haven't.

Click Create a new XCode Project, choose Single View Application. Name the application HelloWorld.UI.Ios. Notice the bundle identifier. It's okay because we can change it later. Save it at any path you like and click 'OK'.

Now you'll see the project properties page. Change the bundle identifier to match the one you have in Xamarin.iOS, which is com.companyname.HelloWorld.UI.Ios.

Under the Signing section, select the profile you added before. Now if you have plugged in your device, XCode will automatically register a provisioning profile for the device for you.

Once you have done everything, open your dear PC again.

Now at the play toolbar, change it to Device. Deploy your HelloWorld.UI.Ios. And then click 'Play'. Congratulations!

The whole example source code can be accessed here: 

https://github.com/purrmiaw/HelloWorld.Core/