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
asyncmodifier - The return type is
TaskorTask<T>where the genericTdefines 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
DependencyServicevia a metadata attribute. Registration enablesDependencyServiceto find the implementing class and supply it in place of the interface at run time. - Call to DependencyService
Shared code needs to explicitly call
DependencyServiceto 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
BindablePropertyinstance with one of theBindableProperty.Createmethod overloads. - Define property accessors for the
BindablePropertyinstance.
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
ClearValuemethod 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
BindingContextproperty of the target to the source object. - Set the
Bindingby callingSetBindingon 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 Nameslider= 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
TViewcan be referenced byElement - The
TNativeViewcan 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 theInstancestatic 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
Instanceproperty 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
ConsumerWithInjectionis 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
IDataServiceis 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>(); } }