Getting-started

Fundamentals

Advanced

Platforms

Plugins

Samples

Tutorials

Upgrading

Contributing

Customizing App and Setup

Edit

In each deployed MvvmCross application there are two key classes which control how your app starts:

  • The App class in the core project - which provides the initialization for your core business logic and your viewmodels.
  • The Setup class in the native UI project - this Setup is a bootstrapper for the MvvmCross system and for your app.

App.cs

Typically App.cs provides only initialization of:

  • Simple rule-based IoC registration - e.g.:
CreatableTypes()
    .EndingWith("Service")
    .AsInterfaces()
    .RegisterAsLazySingleton();
  • The IMvxAppStart - which ViewModel or ViewModels are shown when the application is first started

Setup.cs

Internally the Setup bootstrapper performs many steps.

You can see most of them in the MvxSetup.cs class source which includes a sequence like this:

    // IoC
    InitializeIoC();
     
    // Core components
    InitializeFirstChance();
    InitializeDebugServices();
    InitializePlatformServices();
    InitializeSettings();
    InitializeSingletonCache();

    // Second components
    PerformBootstrapActions();
    InitializeStringToTypeParser();
    InitializeViewModelFramework();
    var pluginManager = InitializePluginFramework();
    InitializeApp(pluginManager);
    InitialiseViewModelTypeFinder();
    InitializeViewsContainer();
    InitiaiseViewDispatcher();
    InitializeViewLookup();
    InitialiseCommandCollectionBuilder();
    InitializeNavigationSerializer();
    InitializeInpcInterception();
    InitializeLastChance();

Most of these steps are virtual - so they allow customization. Also most of these steps are implemented using virtual Create steps - which again should make customization easier:

protected virtual void InitialiseFoo()
{
    var foo = CreateFoo();
    Mvx.IoCProvider.RegisterSingleton<Foo>();
}
        
protected virtual IFoo CreateFoo()
{
    return new Foo();
}

Added to these base class steps, each platform adds a small number of platform specific steps - eg Android adds some additional methods and properties for initialization of the Android UI and especially of the data-binding framework:

    string ExecutableNamespace { get; }
    Assembly ExecutableAssembly { get; }
    IMvxAndroidViewPresenter CreateViewPresenter()
    InitializeSavedStateConverter()
    InitialiseBindingBuilder()
    MvxAndroidBindingBuilder CreateBindingBuilder()
    RegisterBindingBuilderCallbacks()
    FillBindingNames(IMvxBindingNameRegistry registry)
    FillAxmlViewTypeResolver(IMvxAxmlNameViewTypeResolver viewTypeResolver)
    FillNamespaceListViewTypeResolver(IMvxNamespaceListViewTypeResolver viewTypeResolver)
    FillValueConverters(IMvxValueConverterRegistry registry)
    FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
    IList<string> ViewNamespaces { get; }
    IDictionary<string, string> ViewNamespaceAbbreviations { get; }
    List<Assembly> ValueConverterAssemblies { get; }
    List<Type> ValueConverterHolders { get; }
    IList<Assembly> AndroidViewAssemblies { get; }

This long list of virtual methods does provide a lot of opportunities for overriding default MvvmCross behaviour.

However, most applications actually override only very few of these methods. Indeed, many applications only override only the one required method: IMvxApplication CreateApp()

If you do want to override some of the Setup, then the rest of this document describes some of the methods and properties that you may find useful.

Individual customizations

Providing application specific initialization

There are three key methods where application specific initialization might be added

  1. App.Initialize - this is the place where all cross-platform app initialization should occur. In general it is the first choice for all app-specific initialization. Only use the Setup-based methods if you need platform-specific code injected

  2. Setup.InitializeFirstChance - a “first blood” placeholder for any steps you want to take before any of the later steps happen

  3. Setup.InitializeLastChance - a “last ditch” placeholder for any steps you want to take after all of earlier steps have happened. Note that the iOS and Android base classes configure binding within this method so you must call base.InitializeLastChance if you implement this method.

In this section, we’ll only introduce some of the possibilities for creating and registering objects during initialize and setup. For a more in-depth introduction to MvvmCross IoC, see Service Location and Inversion of Control.

Registering cross-platform business objects in App.Initialize

It’s very common for App.Initialize to include IoC registration. Indeed, the default nuget App.cs template includes

CreatableTypes()
    .EndingWith("Service")
    .AsInterfaces()
    .RegisterAsLazySingleton();

This logic says:

  • within the Assembly containing the App
  • find all creatable Types - meaning:
  • with public constuctors
  • not marked abstract
  • find the interfaces they implement
  • lazily register these types as singletons - meaning:
  • that no instance will be created until one is requested
  • that once the first instance has been created, then that same instance will be returned for all subsequent requests

Many other registration techniques are available - including registering objects from other assemblies, using instance-per-request registration and using individual rather than reflection-based registrations. For more on these, see Service Location and Inversion of Control.

Registering platform-specific business objects in Setup.InitializeFirstChance and Setup.InitializeLastChance

These two placeholders provide key places for you to create and register services which are specific to your platform.

For example, if you wanted to implement an EncryptionService which would provide native data-encryption for your application, then you could do this during Setup.InitializeFirstChance using:

 Mvx.IoCProvider.RegisterType<IEncryption, MyEncryption>();

This would then allow all of your App code - including code executed during App.Initialize() to use calls like:

var encryption = Mvx.IoCProvider.Resolve<IEncryption>();
var safe = encryption.Encode(raw);

Alternatively, if you wanted to implement a DialogService which would be used during normal UI flow, then you might choose to register this during Setup.InitializeLastChance as:

Mvx.IoCProvider.RegisterSingleton<IDialogService>(new MyDialogService());

For many objects the choice of when to initialize - first or last - doesn’t matter. For others, the key choice is whether the service needs to be available before or after the App is created and initialized.

Changing the IoC container that MvvmCross uses

IoC is the first thing that MvvmCross setup starts.

This is done within the method InitializeIoC

To override MvvmCross’ IoC, you can:

  • first find your alternative IoC implementation - e.g. something like Funq or TinyIoC
  • then create an Adapter which maps the implementation behind an IMvxIoCProvider interface and which inherits from MvxSingleton in order to provide a Singleton
    • the majority of the adaption should be relatively straight-forwards - see MvxIoCContainer for how the default IoC container is provided.
    • The only unusual methods in the MvvmCross IoC interface are the CallbackWhenRegistered hooks - these provide callbacks when new object types are registered and may require a little custom code in the RegisterXXX methods within your adapter.
  • finally, you can override IMvxIoCProvider CreateIocProvider() in your Setup class to return your IoC provider

Providing additional View and ViewModel Assemblies

The default MvvmCross ViewModel Locator looks for:

  • Views in the Assembly containing Setup.cs
  • ViewModels in the Assembly containing App.cs If you wish to add additional View and ViewModel assemblies, then you can do this using overrides in Setup:
protected override Assembly[] GetViewAssemblies()
{
    var list = new List<Assembly>();
    list.AddRange(base.GetViewAssemblies());
    list.Add(typeof(SomeTypeFromAdditionalViewAssembly1).Assembly);
    list.Add(typeof(SomeTypeFromAdditionalViewAssembly2).Assembly);
    list.Add(typeof(SomeTypeFromAdditionalViewAssembly3).Assembly);
    // ...
    return list.ToArray();
}

protected override Assembly[] GetViewModelAssemblies()
{
    var list = new List<Assembly>();
    list.AddRange(base.GetViewModelAssemblies());
    list.Add(typeof(SomeTypeFromAdditionalViewModelAssembly1).Assembly);
    list.Add(typeof(SomeTypeFromAdditionalViewModelAssembly2).Assembly);
    list.Add(typeof(SomeTypeFromAdditionalViewModelAssembly3).Assembly);
        // ...
    return list.ToArray();
}

Overriding ViewModel Location/Construction

By default, MvvmCross builds a new ViewModel every time one is requested and uses the CIRS sequence - Construction-Init-ReloadState-Start - to initialize that ViewModel.

If you want to override this behaviour for one or more ViewModel types, then you can do this in your App object by supplying your own IMvxViewModelLocator implementation.

For example, you could implement

public class MyViewModelLocator
  : MvxDefaultViewModelLocator
{
    private SpecialViewModel _special = new SpecialViewModel();

    public override bool TryLoad(Type viewModelType, IDictionary<string, string> parameterValueLookup,
	                             out IMvxViewModel model)
    {
        if (viewModelType == typeof(SpecialViewModel))
        {
        	model = _special;
        	return true;
        }
        else if (viewModelType == typeof(FooViewModel))
        {
        	model = new FooViewModel(_special);
        	return true;
        }
        
        return base.TryLoad(viewModelType, parameterValueLookup, out model);
    }
}

and could then return this in App.cs:

protected override IMvxViewModelLocator CreateDefaultViewModelLocator()
{
    return new MyViewModelLocator();
}

Custom Presenters

For ‘my very first MvvmCross application’ most people start with a ‘full page’ app in which each navigation causes a new full-screen View to be displayed.

There are many other possibilities for ViewModel -> ViewModel navigation, including:

  • Tabbed displays
  • Dialogs and flyouts
  • Hamburger menus
  • Splitviews, master-detail displays, screen ‘regions’ and other screen division/fragmentation

To provide these alternative UI possibilities, MvvmCross provides a default ViewPresenter per platform, but you can always use your own custom presenter.

For more on ViewPresenters, check out the official documentation.

Providing ValueConverters

For the platforms that use MvvmCross’ databinding platform, then ValueConverters:

are classes which implement IMvxValueConverter are registered by name with a singleton IMvxValueConverterRegistry There are several ways that individual ValueConverters can be registered. The most common are all in Setup.cs:

  1. registering all value converters inside an assembly. To do this: override ValueConverterAssemblies:
  2. registering value converters using a wrapper Converters class
  3. registering value converters one at a time These three techniques are shown as: ```c# // 1. register all value converters in the Assembly containing MyFirstValueConverter protected override List ValueConverterAssemblies { get { var toReturn = base.ValueConverterAssemblies; toReturn.Add(typeof(MyFirstValueConverter).Assembly); return toReturn; } }

// 2. register all value converters in the wrapper Converters public class Converters { // this converter will be registered with name “TheLength” public readonly LengthValueConverter TheLength = new LengthValueConverter(); }

protected override protected List ValueConverterHolders { get { var toReturn = base.ValueConverterHolders; toReturn.Add(typeof(Converters); return toReturn; } }

// 3. register value converters one at a time protected override void FillValueConverters(IMvxValueConverterRegistry registry) { base.FillValueConverters(registry);

registry.AddOrOverwrite("Foo", new SquareValueConverter()); } ```

Notes:

When using the Assembly based method then the registered name uses the rules:

  • remove any Mvx prefix
  • remove any ValueConverter or Converter suffix
  • because of this a value converter in a class MvxBeerValueConverter will be registered under the name Beer
  • When using any of the registration mechanisms then the value converters are always ultimately added using a call to registry.AddOrOverwrite - so in the case of a name clash, then the last added value converter always wins. One final technique used for registering value converters is used by some of the plugins to enable them to register value converters independently of the main setup flow.

This technique involves using the CallbackWhenRegistered IoC method on the IMvxValueConverterRegistry interface. This is used, for example, in the Visibility plugin as:

Mvx.IoCProvider.CallbackWhenRegistered<IMvxValueConverterRegistry>(RegisterValueConverters);
    
    // ...
    
private void RegisterValueConverters()
{
    var registry = Mvx.IoCProvider.Resolve<IMvxValueConverterRegistry>();
    registry.AddOrOverwriteFrom(GetType().Assembly);
}   

For more on creating ValueConverters, read the official documentation or read the ValueConverter sample in: https://github.com/MvvmCross/MvvmCross-Samples/tree/master/ValueConversion

Providing Custom Views (Android)

In Android, MvvmCross overrides the default Android xml inflation and instead provides its own mechanism.

In order to do this, you must supply the MvvmCross binding system with a list of Assembly’s in which it should look for Android View objects (note that these are Android View objects and have nothing to do with Mvvm Views at this point).

To do this, you can override the Setup property AndroidViewAssemblies:

protected override IList<Assembly> AndroidViewAssemblies {
    get {
        return new List<Assembly>(base.AndroidViewAssemblies) {
            typeof (CheeseBaron.ExcellentViews.Pages).Assembly,
            typeof (AnyAdditionalViewType).Assembly
        };
    }
}

Or as an expression body property in C# 6:

protected override IList<Assembly> AndroidViewAssemblies => new List<Assembly>(base.AndroidViewAssemblies) {
     typeof (CheeseBaron.ExcellentViews.Pages).Assembly,
    typeof (AnyAdditionalViewType).Assembly
};

For more on writing Custom Views, see the N=18 and N=19 steps in N+1 - todo-link

Registering Default Binding Names

In iOS Fluent Bindings you frequently see code like:

set.Bind(myLabel).To(vm => vm.FullName);

This code uses a developer shortcut - the default binding property of a UILabel. This is just a shortcut and it means the binding is actually performed as:

set.Bind(myLabel).For(label => label.Text).To(vm => vm.FullName);

At present the defined set of default properties or events includes:

UIButton TouchUpInside
UIBarButtonItem Clicked
UITextField Text
UITextView Text
UITextField Text
MvxCollectionViewSource ItemsSource
MvxTableViewSource ItemsSource
UIImageView	Image
UIDatePicker Date
UISlider Value
UISwitch On
UIDatePicker Date

If you would like register additional shortcuts in your application, or if you’d like to replace some of the existing shortcuts, then this can be done in FillBindingNames

protected override void FillBindingNames(IMvxBindingNameRegistry registry)
{
	registry.AddOrOverwrite(typeof (MyControl), "MyDefaultPropertyOrEvent");
}

Registering custom bindings

MvvmCross binding works by default against Android and iOS UI objects by trying to use Reflection on properties and events.

This generally works really well, especially for cases where properties and events are linked using the convention that the property called Foo is associated with the change event FooChanged

For situations where this default binding doesn’t work, custom bindings can be written and these can be registered using an override of the FillTargetFactories method in the Android and iOS Setup classes.

For more on this, see the N+1 video on custom binding - N=28.

Overriding View-ViewModel associations

By default, MvvmCross discovers the ViewModel that a View is associated with using the type of ViewModel discovered by name convention

This makes prototyping initial application generally very functionality straight-forward.

However, as applications grow in size and complexity, then sometimes developers like to override this lookup behaviour.

To do this they can instead:

  • provide a concrete type of the ViewModel where one is specified - e.g. as:
    public new DetailViewModel ViewModel
    {
      get { return base.ViewModel as DetailViewModel; }
      set { base.ViewModel = value; }
    }
    
  • provide an explicit type of the ViewModel specified using an MvxViewForAttribute

Further, in cases where every microsecond of startup time is essential, they can also help reduce the Reflection overhead by overriding the InitializeViewLookup method - e.g.

protected override void InitializeViewLookup()
{
    var viewModelViewLookup = new Dictionary<Type, Type>()
    {
        { typeof (FirstViewModel), typeof(FirstView) },
        { typeof (SecondViewModel), typeof(SecondView) },
        	//
        { typeof (UmpteenthViewModel), typeof(UmpteenthView) },
    };

    var container = Mvx.IoCProvider.Resolve<IMvxViewsContainer>();
    container.AddAll(viewModelViewLookup);
}