Xamarin
Table of Contents
C#
General
Namespace
The namespace keyword is used to declare a scope that contains a set of related objects.
To use/import a namespace we use the keyword using <namespace>
. This will import everything
in that namespace which can then be referenced as it was written inside that document, like
import * from numpy
or whatever in Python.
namespace SampleNamespace { class SampleClass {} interface SampleInterface {} struct SampleStruct {} enum SampleEnum {} delegate void SampleDelegate(int i); namespace SampleNamespace.Nested { class SampleClass2 {} } }
And to use a namespace
using SampleNamespace; class UseSampleClass { SampleClass sampleClass = new SampleClass(); }
Modifiers
Modifier | Purpose |
Access modifiers | Specifies the declared accessibility of types and type members |
abstract |
Class is intended only to be a base class of other classes |
async |
Method, lambda expression, or anonymous function is asynchronous |
const |
Value or field cannot be modified |
event |
Declares an event |
extern |
Method is implemented externally |
override |
Provides a new implemetion of a virtual member inherited from a base class |
partial |
Defines partial classes, structs and methods throughout the same assembly |
readonly |
Declares a field that can only be assigned values as a part of the declaration or in the constructor of the class |
sealed |
Specifies that a class cannot be inherited |
virtual |
Method or accessor whose implementation can be changed by overriding a member in a derived class |
volatile |
Field can be modified in the program by something such as the operating system, hardware, or a concurrently executing thread |
static |
Declares a member that belongs to the type itself, instead of an instance of the typeclass. Can also have static class which means that the class /cannot be instantiaed. |
Access modifiers
Public
Method or field is accesible by everything.
Private
Method or field is only accesible within the body of the class or struct in which they are declared.
Protected
Method or field is only accessible within the its class and by derived class instances.
More indept modifiers
partial
partial
makes it possible to split the definition of a class
or a struct
,
an interface
or a method over multiple source files. Each source file
contains a section of the type or method definition, and all parts are
combined when the application is compiled.
Why?
- Working on large projects, spreading a class over seperate files allows multiple programmers to work on the same class simultaneously.
- When working with automatically generated source code, this allows us to add code to the class without having to recreate the source file.
There are several restrictions when working with partial
classes.
class Container { partial class Nested { void Test() {} } partial class Nesten { void Test2() {} } }
readonly
The readonly
keyword is a modifier that you can use on fields.
When a field declaration includes a readonly
modifier, assignments
to the fields introducted by the declaration can only occur as part
of the declaration or in a constructor of the same class.
class Age { readonly int _year; Age(int year) { _year = year; } void changeYear() { // _year = 1967; --> Compile error } }
Exception handling
Throw
throw
is used to signal the occurence of an anomalous sitation (exception)
during the program execution.
Remark: A thrown execption is an object whose class is derived from System.Exception.
public class ThrowTest { static int GetNumber(int index) { int[] nums = { 300, 600, 900 }; if (index > nums.Length) { throw new IndexOutOfRangeException(); } return nums[index]; } static void Main() { int result = GetNumber(3); } } /* Output: The System.IndexOutOfRangeException exception occurs. */
Try-catch-finally
This works as you would expect. You use try { ... }
to try
something, then you
catch potenial exceptions using the catch (Exception e) { ... }
, and finally
you do whatever needs to done finally { ... }
.
Debugging
Writing to the debugging console can be done by simply using Debug.WriteLine(string)
where Debug
can be found in System.Diagnostics
.
Getters and setters
In C#
we can write getters and setters easily.
public string Name {get; set; }
The compiler will then auto-generate the equivalent simple implementation:
private string _name; public string Name { get { return _name; } set { _name = value; } }
.NET Framework
System
EventArgs
This class serves as the base class for all classes that represent event data. Source.
Assembly
"Assemblies are the building blocks of .NET Framework applications; they form the fundamental unit of deployment, version control, reuse, activation scoping, and security permissions. An assembly is a collection of types and resources that are built to work together and form a logical unit of functionality. An assembly provides the common language runtime with the information it needs to be aware of type implementations. To the runtime, a type does not exist outside the context of an assembly." Source: MSDN
An assembly is a file that is automatically generated by the compiler upon successful compilation of ever .NET application. It can either be a Dynamic Link Library (DLL) or an executable file. It is generated only once for an application and upon each subsequent compilation the assembly gets updated.
An Assembly contains Microsoft Intermediate Language (MSIL) code, which is similar to Java byte code. In the .NET language, it consists of metadata. Metadata enumerates the features of every "type" inside the assembly or the binary.
An assembly performs the following functions (in short):
- Contains the common language runtime executes. MSIL code in a portable
executable (PE) file will not be executed if it does not have an
associated assembly manifest. Note that each assembly can have only
one entry point (that is,
DllMain
,WinMain
, orMain
) - Forms a security boundary.
- Forms a reference scope boundary
- Forms a version boundary
- Forms a deployment unit
Attributes
You can read a ton about attributes here.
Generally
Attributes are used within .NET for declarative programming. We can use attributes to define both design-level information (such as help file, URL for documentation) and run-time information (such as associating XML field with class field).
From what I understand, they're an attempt to improve upon the compiler directives from C. Remember the macro stuff? It allowed us to do something like this 1:
#if DEBUG doSomethingOnlyInDebugMode(); #else doSomethingInReleaseMode(); #endif
Instead, .NET allows us to do the following:
[Conditional("DEBUG")] private void doSomethingOnlyInDebugMode(); private void doSomethingInReleaseMode(); public ClassName() { doSomethingInReleaseMode(); doSomethingOnlyInDebugMode(); }
And the doSomethingOnlyInDebugMode()
will only be executed in,
you guessed it, debug mode!
Or stuff like this:
using System; public class AnyClass { [Obsolete("Don't use Old method, use New method", true)] static void Old( ) { } static void New( ) { } public static void Main( ) { Old( ); } }
Which will throw the following error when we compile
AnyClass.Old()' is obsolete: 'Don't use Old method, use New method'
Custom attributes
You can also develop your own attributes.
A class that derives from the abstract class System.Attribute
class,
whether directly or indirectly, is an attribute class. The declaration
of an attribute class defines a new kind of attribute that can be placed
on a declaration. Thus, all we have to do is inherit from System.Attribute
and we're done!
using System; public class HelpAttribute : Attribute { } [Help()] public class SomeClass { }
Note: it's convetion to use Attribute as a suffix in attribute class names.
Attribute Identifiers
Suppose we want to place a Help
attribute on an entire assembly.
This can be done using the attribute identifier assembly
, like this
[assembly: Help("this is a do-nothing assembly")]
The assembly
identifier before the Help
attribute explicitly tells
the compiler that this attribute is attached to the entire assembly.
Possible identifiers are:
- assembly
- module
- type
- method
- property
- event
- field
- param
- return
Asynchronized programming with Async and Await
By using async
and await
you can use resources in the .NET Framework or the Windows Runtime to
create an asynchronous method almost as easily as you can create a synchronous method.
async Task<int> AccessTheWebAsync() { // You need to add a refecernece to System.Net.Http to declare client. HttpClient client = new HttpClient(); // GetStringAsync returns a Task<string> which means that when you 'await' // the task you'll get a string (urlContents) Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com"); // ... string urlContents = await getStringTask; return urlContents.Length; }
There are three things worth noting in the signature of AccessTheWebAsync
:
- The method has an
async
modifier - The return type is
Task
orTask<T>
where the genericT
defines the return type of the task - The method name ends in "Async"
GetStringAsync
returns a Task<string>
, ergo when we await
we get a string
representing the
content of the url, which we later store in urlContents
.
Now, await
suspends the function, which means that AccessTheWebSync
can't continue until GetStringAsync
is complete. But control is returned to the caller of AccessTheWebSync
while we wait. Control
is then brought back to AccessWebSync
when getStringTask
is complete. The string
result is then
retrieved from getStringTask
and function keeps going.
Methods from .NET Framework 4.5 and the Windows Runtime contain methods that support async programming:
Application area | Supporting APIs that contain async methods |
---|---|
Web access | HttpClient, SyndicationClient |
Working with files | StorageFile, StreamWriter, StreamReader, XmlReader |
WCF Programming | Synchronous and Asychronous Opertaions |
async
Use the async
modifier to specify that a method, lambda expression or anonymous method is asynchronous.
Using this modifier -> we have a async method.
public async Task<int> ExampleMethodAsyc() { // ... }
Appendix
Syllabus
WCF = Windows Communication Foundation DLL = Dynamic-Link Library IL = Intermediate Language MSIL = Microsoft Intermediate Language PE = Portable Executable
Xamarin.Forms
Concepts
DependencyService
Overview
DependencyService
allows you to call into platform-specific functionality
from shared code. It's a dependency resolver. In practice, an interface is
defined and the DependecyService
finds the correct implementation of that
interface from the various platforms.
How it works
Xamarin.Forms apps need three components to use DependencyService
:
- Interface The required functionality is defined by an interface in shared code.
- Implementation for each platform Classes that implement the interface must be added to each platform object.
- Registration of implementation
Each implementing class must be registered with
DependencyService
via a metadata attribute. Registration enablesDependencyService
to find the implementing class and supply it in place of the interface at run time. - Call to DependencyService
Shared code needs to explicitly call
DependencyService
to ask for implementations of the interface.
Platform projects without implementations will fail at runtime.
Example
public interface ITextToSpeech { void Speak (string text); // interface members are public by default }
[assembly: Xamarin.Forms.Dependency(typeof(TextToSpeechImplementation))] namespace TextToSpeach.WinPhone { public class TextToSpeechImplementation : ITextToSpeech { public TextToSpeechImplementation( ) { }; public async void Speak(string text) { SpeechSynthesizer synth = new SpeechSynthesizer(); await synth.SpeakTextAsync(text); } } }
Note: the dependency registration is performed at the namespace level.
Now that we've implemented the interface ITextToSpeech
and registered
its Windows implementation TextToSpeechImplementation
, we can do
DependencyService.Get<ITextToSpeech>().Speak("Hello, World!");
Where DependencyService<T>
will find the correct implementation of
interface <T>
.
Note: You must provide an implementation for every platform project.
If no interface implementation is registered, then the
DependencyService
will be unable to resolve the Get<T>
method at runtime.
ServiceLocator
BindableProperties
Overview
In Xamarin.Forms, the functionality of common language runtime (CLR)
properties is extended by bindable properties.
A BindableProperty
is a special type of property, where the property's
value is tracked by the Xamarin.Forms property system.
The purpose is to provide a property system that supports data binding, styles, templates, and values set through parent-child relationships.
In addition, BindableProperty
can provide default values, validation
of property values, and callbacks that monitor property changes.
Properties should be implemented as BindableProperty
in order to support one or more of the following:
- Acting as a valid target property for data binding
- Setting the property through a
Style
- Providing a default property value that's different from the default for the type of the property
- Validating the value of the property
- Monitoring property changes
Creating and Consuming a Bindable Property
The process for creating a bindable property as follows:
- Create a
BindableProperty
instance with one of theBindableProperty.Create
method overloads. - Define property accessors for the
BindableProperty
instance.
Important: In order to create a BindableProperty
instance, the
containing class must derive from the BindableObject
class.
At a minimum, an identifier must be specified when creating a
BindableProperty
, along with the following parameters:
- The name of the
BindableProperty
- The type of the property
- The type of the owning object
- The default value of the property. The default value will be restored
when
ClearValue
method is called on theBindableProperty
.
public static readonly BindableProperty EventNameProperty = BindableProperty.Create("EventName", typeof(string), typeof(EventToCommandBehavior), null);
This creates a BindableProperty
instance named EventName
, of type
string
. The property is owned by the EventToCommanderBehavior
class,
and has a default value of null
.
Detecting Property Changes
public static readonly BindableProperty EventNameProperty = BindableProperty.Create ( "EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged); ... static void OnEventNameChanged (BindableObject bindable, object oldValue, object newValue) { // Property changed implementation goes here }
Data binding
Data binding is the process of connecting the UI and the business logic.
Data bindings connect properties of two objects, called the source and the target. In code two steps are required:
- Set the
BindingContext
property of the target to the source object. - Set the
Binding
by callingSetBinding
on the target object to bind a property of that object to a property of the source object.
Note: target property must be a bindable property, which means that
the target object must derive from BindableObject
.
All of the visual elements in the Xamarin.Forms namespace inherit from
BindableObject class, which provide the methods SetBinding
and
BindingContext
.
Note: BindingContex
is set to null
by default. So if we say
wanted to refer to a field on the page itself, we would have to
set BindingContext = this
and refer in the XAML by {Binding FieldName}
.
C# + XAML (Preffered)
This is a really good example from the Master Detail Page Xamarin Example.
C#
Inheritance from BindableObject
exposes the methods SetBinding
and
BindingContext
.
With C#
we simply do the following:
var label = new Label(); label.SetBinding(Label.TextProperty, "Name"); label.BindingContext = new {Name = "John Doe", Company = "Xamarin"};
XAML
Here the Binding
markup extension takes the place of SetBinding
call
and the Binding
class.
We can set the binding using a StaticResource
or x:Static
markup extension,
and even as the content of BindingContext
property-element tags.
View-to-View
We can actually defined a view-to-view binding, that is link two views on the same page, as we do here.
<Slider x:Name="slider" /> <Label Text="ROTATION" BindingContext="{x:Reference Name=slider}" Rotation="{Binding Path=Value}" /> <Label BindingContenxt="{x:Reference slider}" Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}" />
Note: We can refer to the slider
element using both Name
slider= and just slider
.
Collections
ListView
defines an ItemsSource
property of type IEnumerable
, and
displays the items in that collection.
These items can be of any type.
By default ListView
uses the ToString
method of each item to display
that item. But we can display the items in the ListView
collection
in any way we want through the use ofa template, which involves a class
that derives from Cell
. Very often, we use the ViewCell
class.
Say we have declared the local
namespace to point to our project
and we have a field in our namespace called NamedColor.All
which
returns all our NamedColor
objects. These objects have a FriendlyName
field which is simply the name of the color. We can then display
the FriendlyName
in a ListView
as follows:
<ListView ItemsSource="{x:Static local:NamedColor.All}"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <ViewCell.View> <Label Text="{Binding FriendlyName}"> </ViewCell.View> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView>
Events
We use the x:Name
to create a reference to the XAML element, which is
available in our View code. This item has different fields corresponding
to the different events. These fields are Array
s of EventHandler
s.
To create our own EventHandler
, we simply append (using the +=
operator)
our own function to this list of EventHandler
s!
public partial class MainView : ContentPage { public MainView () { InitializeComponent (); // listView is a XAML ListView element with x:Name="listView" listView.ItemSelected += OnSelection; } void OnSelection(object sender, SelectedItemChangedEventArgs e) { if (e.SelectedItem == null) { return; } DisplayAlert("Item selected", e.SelectedItem.ToString(), "Ok"); } }
We can also specify event handling in XAML
<Button Text="Click me breh" Clicked="OnClick">
where OnClick
references a method on the class specifed by x:Class
on the
Page
.
Custom Rendering
Within the ViewRenderer
we can access the TView
and TNativeView
:
- The
TView
can be referenced byElement
- The
TNativeView
can be referenced byControl
There constraints for the second generic parameter to ViewRenderer
:
Platform | TNativeView |
---|---|
iOS | UIKit.UIView |
Android | Android.View.Views |
Windows | Windows.UI.Xaml.FrameworkElement |
XAML
Definitions
Pages
Xamarin.Forms uses the word Pages to refer to cross-platform mobile app screens.
The Page class Page is a visual element that occupies most or all of the screen and contains a single child. On specific OSes we have
OS | Page represents |
---|---|
iOS | View Controller |
Android | A page takes up the screen like an Activity |
Windows Phone | Page |
Layouts
Xamarin.Forms Layout are used to compose user interface controls into logical structures.
The Layout class is a specialized subtype of View
, which acts as
a container for other Layout
s or View
s. It typically contains
logic to set the position and size of child elements.
Views
Cells
Xamarin.Forms cells can be added to ListView
s and TableView
s.
A Cell
is a specialized element used for items in a table and
describes how each item in a list should be drawn. Cell
derives
from Element
.
See here for more info regarding Cell
s.
Namespace declarations
Namespaces are declared by xmlns
. We have the standard ones in
Xamarin.Forms:
One for accessing Xamarin.Forms classes
xlmns="http://xamarin.com/schemas/2014/forms"
And another for the namespace for referencing tags and attributes intrinsic to XAML
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
We can also reference our own code or the System
namespace
xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples" xmlns:sys="clr-namespace:System;assembly=mscorlib"
where mscorlib
used to stand for "Microsoft Common Object Runtime Library",
but now stands for "Multilanguage Standard Common Object Runtime Library".
Markup extensions
Sometimes properties must reference values defined elsewhere, or which might require processing by code at runtime. For these purposes, we use XAML markup extensions.
We call these extensions because they're backed by code in classes
that implement IMarkupExtension
.
One such method is to store values or objects in a No description for this link.
Another method is to use x:Static
, which is what I prefer, to
access a public static field, static property, or constant defined
by a class or structure, or an enumeration member.
ResourceDictionary
To use a resource dictionary on a page, include a pair of Resources
property-element tags.
Each item requires a x:Key
, which specifies the dictionary key that
we can use to access this resource. We then use the resource by
Attribute
"{StaticResource key}"= on an element. See 1.
StaticResource
returns an object from a resource dictionary.
XAML also supports DynamicResource
, which is a resource which may
change during runtime.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="XamlSamples.SharedResourcesPage" Title="Shared Resources Page"> <ContentPage.Resources> <ResourceDictionary> <LayoutUptions x:Key="horzOptions" Alignment="Center" /> <LayoutOptions x:Key="vertOptions" Alignment="Center" Expands="True" /> </ResourceDictionary> </ContentPage.Resources> <Button Text="Hi" HorizontalOptions="{StaticResource horzOptions}" VerticalOptions="{StaticResource vertOptions}"> </ContentPage>
x:Static
I'd say this is my preferred way of doing things.
Here we explicitly reference static fields and enumeration members.
<Label Text="Hello, XAML!" VerticalOptions="{x:Static LayoutOptions.Start}" HorizontalTextAlignment="{x:Static TextAlignment.Center}" TextColor="{x:Static Color.Aqua}" />
And here we reference a static field from our own code.
To do this though, we need to reference the namespace in our
XAML file first. Say we have the class AppConstants
, then
xmlns:local="clr-namespace:XamlSamples;assembl=XamlSamples"
And define our namespace
in C#
.
using System; using Xamarin.Forms; namespace XamlSamples { static class AppConstants { public static readonly Thickness PagePadding = new Thickness(5, Device.OnPlatform(20, 0, 0), 5, 0); public static readonly Font TitleFont = Font.SystemFontOfSize(Device.OnPlatform(35, 40, 50), FontAttributes.Bold); public static readonly Color BackgroundColor = Device.OnPlatform(Color.White, Color.Black, Color.Black); public static readonly Color ForegroundColor = Device.OnPlatform(Color.Black, Color.White, Color.White); } }
Now, we simply reference our code using {x:Static local:AppConstants.fieldName}
.
<Label Text="Hi" TextColor="{x:Static local:AppConstants.TextColor}" BackgroundColor="{x:Static local:AppConstants.BackgroundColor}" Font="{x:Static local:AppConstants.TitleFont}" />
Property-element
These are properties that we create inside their own tags. Instead of doing this
<Button Background="Blue" ... />
we can do
<Button> <Button.Background> "Blue" </Button.Background> </Button>
which is what a property-element is!
Tutorials
Phoneword
Main Page
Xamarin.Forms uses XAML (XML-like language) to define the actual layout of the page.
The MainPage
class in MainPage.xaml defines the layout of the main
page of the application.
We then use the MainPage
class in MainPage.xaml.cs to define the
business logic that is executed when the user interacts with the page.
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Phoneword.MainPage"> <ContentPage.Padding> <OnPlatform x:TypeArguments="Thickness" iOS="20, 40, 20, 20" Android="20, 20, 20, 20" WinPhone="20, 20, 20, 20" /> </ContentPage.Padding> <ContentPage.Content> <StackLayout VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" Orientation="Vertical" Spacing="15"> <Label Text="Enter a Phoneword:" /> <Entry x:Name="phoneNumberText" Text="1-855-XAMARING" /> <Button x:Name="translateButton" Text="Translate" Clicked="OnTranslate" /> <Button x:Name="callButton" Text="Call" isEnabled="false" Clicked="OnCall" /> </StackLayout> </ContentPage.Content> </ContentPage>
using System; using System.Threading.Tasks; using Xamarin.Forms; namespace Phoneword { public partial class MainPage : ContentPage{ string translatedNumber; public MainPage() { InitializeComponent(); } void OnTranslate(object sender, EventArgs e) { translatedNumber = Core.PhonewordTranslator.ToNumber (phoneNumberText.Text); if (!string.IsNullOrWhiteSpace (translatedNumber)) { callButton.IsEnabled = true; callButton.Text = "Call " + translatedNumber; } else { callButton.IsEnabled = false; callButton.Text = "Call"; } } async void OnCall (object sender, EventArgs e) { if (await this.DisplayAlert ("Dial a Number", "Would you like to call " + translatedNumber + "?", "Yes", "No")) { var dialer = DependencyService.Get<IDialer> (); if (dialer != null) { dialer.Dial(translatedNumber); } } } } }
Master Detail Page
A Xamarin.forms MasterDetailPage is a page that has a master page for presentinglisting items, and a /detail page which is responsible for showing information about selected item. See here.
MasterPage.xaml
<?xml version="1.0" encoding="UTF-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MasterDetailPageNavigation.MasterPage"> <ContentPage.Content> <StackLayout> <ListView x:Name="listView"> <ListView.ItemTemplate> <DataTemplate> <ImageCell Text="{Binding Title}" ImageSource="{Binding IconSource}" /> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage.Content> </ContentPage>
MasterPageItem.cs
// MasterPageItem.cs using System; namespace MasterDetailPageNavigation { public class MasterPageItem { public string Title { get; set; } public Type TargetType { get; set; } } }
MasterPage.xaml.cs
// MasterPage.xaml.cs using System.Collections.Generic; using Xamarin.Forms; namespace MasterDetailPageNavigation { public partial class MasterPage : ContentPage { public ListView ListView {get { return listView; }} public MasterPage () { InitializeComponent(); // First we instatiate and populate the list that will be our ItemsSource var masterPageItems = new List<MasterPageItem> (); masterPageItems.Add(new MasterPageItem { Title = "Contacts", TargeType = typeof(ContactsPage) }); masterPageItems.Add(new MasterPageItem { Title = "About" TargetType = typeof(AboutPage) }); // Set the ItemsSource of the ListView element, which we reference using listView listView.ItemsSource = masterPageItems; } } }
Then for the Xamarin.Forms MasterDetailPage stuff.
We set the master-part of the MasterDetailPage
by using the Master
property
on the MasterDetailPage
as seen below. In the code-behind of MasterPage
,
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:MasterDetailPageNavigation;assembly=MasterDetailPageNavigation" x:Class="MasterDetailPageNavigation.MainPage"> <MasterDetailPage.Master> <local:MasterPage x:Name="masterPage" /> </MasterDetailPage.Master> <MasterDetailPage.Detail> <NavigationPage> <x:Arguments> <local:ContactsPage /> </x:Arguments> </NavigationPage> </MasterDetailPage.Detail> </MasterDetailPage>
Looking at this we might be a bit wierded out by the <NavigationPage><x:Arguments>...
lines, but what it is actually doing is instatiating a NavigationPage
object, which
may either take no arguments or one argument of type Page
. As we said, arguments, not
properties! The XAML format allows us to pass arguments to the parent using the <Arguments>
element,
but we also need to reference the namespace of XAML, thus <x:Arguments>
.
public class MainPage : MasterDetailPage { public MainPage() { InitializeComponents(); } }
Model-View-ViewModel (MVVM)
Old way - Singleton pattern
The Singleton pattern is convenient way to create and expose an instance of a class. Example 1 we show a class exposing a property following the Singleton pattern.
// Singleton Pattern implementation public class DataService { private static DataService _instance; public static DataService Instance { get { return _instance ?? (_instance = new DataService()); } } }
Few thing to note about the code:
- Constructor of class is
private
-> only way to create the instance is by using theInstance
static property. - Instance is created on demand, which is generally a good thing, but sometimes we want the instance to be ready as soon as the application starts.
- No way to delete the instance
- Instances other than the main
Instance
property can be created, but each would require different accessors, either properties or methods.
A cleaner approach is to remove this infrastructure code from each class we implement and instead use an external object that acts like a cache for the instance we need in various parts of our application.
Inversion of Control and Dependency Injection
This is were Inversion of Control (IOC) comes in. The term means that the act of creating and keeping the instance is not the responsibility of the consuming class anymore, but is instead delegated to an external container.
The cached instances are often injected into the consumer class's constructor or made available through a property of the consumer, but this is not required. This is why we talk about Dependency Injection (DI).
DI is not necessary for IoC containers to work, but it is a convenient way to decouple the consumer from the cached instances and from the cache itself.
In Example 2 we decided to provide two implementations of the service, one for run time and one for test purposes.
// Classic compostion of consumer and service public class Consumer { private IDataService _service; public Consumer() { if (App.IsTestMode) { _service = new TestDataService(); } else { _service = new DataService(); } } } public interface IDataService { Task<DataItem> GetData(); } public class DataService : IDataService { public async Task<DataItem> GetData() { // TODO: Provide a runtime implementation of the GetData method. } } public class TestDataService : IDataService { public async Task<DataItem> GetData () { // TODO: Provide a test implementation of the GetData method. } }
Consumer
with dependecy injection, the code becomes much cleaner, as shown in Example 3.
// Dependecy Injection public class ConsumerWithInjection { private IDataService _service; public ConsumerWithInjection(IDataService service) { _service = service; } }
Then we need to take of creating the service outside of the
Consumer
and injecting it, this is where the IoC container
becomes useful.
MVVM Light's SimpleIoC
SimpleIoC
is, as the name indicates, a simple IoC container,
which allows registering and getting instances from the cache
in an uncomplicated manner.
It also allows composing objects with dependency injection in the constructor.
With SimpleIoC
, we can register the IDataService
, the
implementing class or classes and the Consumer
class, as
shown in Example 4.
// Registering the IDataService and the Consumer if (App.IsTestMode) { SimpleIoc.Default.Register<IDataService, TestDataService>(); } else { SimpleIoc.Default.Register<IDataService, DataService>(); } SimpleIoc.Default.Register<ConsumerWithInjection>();
Basically what we do in Example 4 is the following: if the
application is in the test mode, every time anyone needs an
IDataService
, pass the cached instace of TestDataService
;
otherwise, use the cached instance of DataService
.
Note: the action of registering does not create any instance yet - the instatiation is on demand, and only executed when the objects are actually needed.
Next we create the ConsumerWithInjection
instance as shown in
Example 5]].
// Getting the ConsumerWithInjection Instance public ConsumerWithInjection ConsumerInstance { get { return SimpleIoc.Default.GetInstance<ConsumerWithInjection>(); } }
When the getter in Example 5 is called, the SimpleIoc
container will
run the following steps:
- Check whether the instance of
ConsumerWithInjection
is already existing in the cache. If yes, the instance is returned. - If the instance doesn't exist yet, inspect the
ConsumerWithInjection
's constructor. It requires the instance ofIDataService
. - Check whether an instance of
IDataService
is already available in the cache. If yes, pass it to theConsumerWithInjection
's constructor. If not, create an instance of
IDataService
, cache it and pass it to theConsumerWithInjection
's constructor.It is not strictly necessary to use the constructor injection shown earlier in Example 3, although it is an elegant manner in which to compose objects and to decouple the dependencies between objects.
Keyed instances
The GetInstance
method can also return keyed instances. This
means that IoC container can create multiple instances of the same
class, keeping them indexed with a key.
When GetInstance
is called with a key, the IoC container checks
whether an instace of that class is already saved with that key.
If it is not, the instace is created before it is returned. It
is then saved in the cache for later reuse.
It is also opssible to get all the instances of a given class, as shown in Example 6.
// Getting Keyed instances, and getting all the instances // Default instance var defaultInstance = SimpleIoc.Default.GetInstance<ConsumerWithInjection>(); // Keyed instances var keyed1 = SimpleIoc.Default.GetInstance<ConsumerWithInjection>("key1"); var keyed2 = SimpleIoc.Default.GetInstance<ConsumerWithInjection>("key2"); var keyed3 = SimpleIoc.Default.GetInstance<ConsumerWithInjection>("key3"); // Get all the instances var allInstances = SimpleIoc.Default.GeAllInstances<ConsumerWithInjection>();
Various ways to register a class
Each IoC container has certain features that make it unique in the way that classes are registered. Some of the use code-only configuration, while others can read external XML files, allowing for great flexibility in the way that classes are instatiated by the container. Others allow for powerful factories to be used.
Some, like MVVM Light's SimpleIoc
, are more simple and straightforward.
Registration can occur in a central location (often called the
Service Locator
), where important decisions can be taken, such
as when to use the test implementation for all the services.
In some MVVM applications, a class named ViewModelLocator
is used
to create and expose some of the application's ViewModel
s.
This is a convient location to register most of the services and
service consumers. In fact, some ViewModel
s can also be registered
with the IoC container.
In most cases, only the ViewModel
s that are long-lived are
registered in the ViewModelLocator
class. Others may be created
ad hoc. In navigation apps, these instances may be passed when
the navigation occurs. In some cases, SimpleIoc
may be used as
a cache for keyed instances in order to make this step easier.
To make the IoC container easier to swap with another, many such
containers use the Common Service Locator implementation. This
relies on a common interface ( IServiceLocator
) and the ServiceLocator
class, which is used to abstract the IoC container's implementation.
Because SimpleIoc
implements IServiceLocator
, we have the
code shown in Example 7 execute in the same manner as the code
in Example 6.
// Registering the ServiceLocator ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); // Default instance var defaultInstace = ServiceLocator.Current.GetInstance<ConsumerWithInjection>(); // Keyed instances var keyed1 = ServiceLocator.Current.GetInstance<ConsumerWithInjection>("key1"); var keyed2 = ServiceLocator.Current.GetInstance<ConsumerWithInjection>("key2"); var keyed3 = ServiceLocator.Current.GetInstance<ConsumerWithInjection>("key3"); // Get all instances var allInstances = ServiceLocator.Current.GetAllInstances<ConsumerWithInjection>();
Instead of registering a class and delegating the instance creation to the IoC container, it is also possible to register a factory expression. This delegate returns an instance, and is often expressed using a lambda expression.
public async void InitiateRegistriation() { // Registering at 0:00:00 SimpleIoc.Default.Register(() => new DataItem(DateTime.Now())); await Task.Delay(5000); // Getting at 0:00:05 var item = ServiceLocator.Current.GetInstance<DataItem>(); away Task.Delay(5000); // Getting at 0:00:10. Creation time is still the same item = ServiceLocator.Current.GetInstance<DataItem>(); }
MVVM Light's ViewModelLocator
View Services
Servies are classes that provide the ViewModel
s with data and
functionality. Sometimes, however, the ViewModel
also needs
another kind of service in order to use functionality in the View
.
In this case, we talk about View Services.
Two typical View Services are the NavigationService
and the
DialogService
.
Each Page
performs navigation by using a built-in NavigationService
property. Since a ViewModel
is a plain object it does not have such
a built-in property, and here the IoC container comes in handy: the
NavigationService
is registered in the ViewModelLocator
, cached
in the IoC container and can be injected into each ViewModel
as needed.
This operation is shown in Example 10.
public class ViewModelLocator { static ViewModelLocator() { ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); if (ViewModelBase.IsInDesignModeStatic) { SimpleIoc.Default.Register<IRssService, Design.DesignRssService>(); } else { SimpleIoc.Default.Register<IRssService, RssService>(); } SimpleIoc.Default.Register<INavigationService, NavigationService>(); SimpleIoc.Default.Register<MainViewModel>(); } public MainViewModel Main { get { return ServiceLocator.Current.GetInstance<MainViewModel>(); } } } public class MainViewModel : ViewModelBase { private readonly IRssService _rssService; private readonly INavigationService _navigationService; public ObservableCollection<RssItem> Items { get; private set; } public MainViewModel(IRssService rssService, INavigationService navigationService) { _rssService = rssService; _navigationService = navigationService; Items = new ObservableCollection<RssItem>(); } }