iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
📖

Introduction to ViewModelLocator in Prism for Xamarin.Forms

に公開

Now, starting with this post, I will begin to delve into and explain each specific essence one by one.

The glorious? first topic is the ViewModelLocator.

It might seem trivial, but it is an important starting point for developing applications using Prism.

In this entry, in addition to the standard behavior and usage of the ViewModelLocator, I would like to introduce a detailed explanation and several scenarios for using it with customizations.

This entry is part of the series "Introduction to Prism for Xamarin.Forms."

There is a table of contents below, so I hope you will also take a look at the other entries.

【Xamarin】Introduction to Prism.Forms Table of Contents - nuits.jp blog

Quickstart

The role of the ViewModelLocator is to inject a ViewModel into a View.

It automatically identifies the ViewModel and binds it to the View's BindingContext.

I explained the importance of this in my previous entry, so if you are interested, please take a look.

http://www.nuits.jp/embed/2016/08/12/165215

To use the ViewModelLocator, you only need to add the following two lines to the View's XAML file.

xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"

Specifically, you add it to the top of the XAML like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="HelloPrism.Views.MainPage"
             Title="MainPage">
  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
    <Label Text="{Binding Title}" />
  </StackLayout>
</ContentPage>

By the way, the ViewModelLocator is created using a technology called Attached Properties.

It is a technology used not only in Xamarin but in XAML architectures in general, so I recommend looking it up if you are interested.

By default, the ViewModelLocator determines the ViewModel for a View according to several rules:

  • The View and ViewModel must be in the same assembly.
  • If the View's namespace is Xx.Views, the ViewModel's namespace must be Xx.ViewModels.
  • The ViewModel class name should be "View class name + ViewModel".
    However, if the View name ends in ~View, it should be "~ViewModel".
    Example 1) FirstPage > FirstPageViewModel
    Example 2) SecondView > SecondViewModel

Also, although I feel like this should technically be written in the Navigation section, it is important, so I will write it here as well.

Instances of the ViewModel are created every time a screen transition occurs. Please be aware that they are not cached in the DI container.

I think it would be easy to understand if you actually create a project using the Prism Template Pack and peek at the contents.

I also explained how to introduce the Prism Template Pack and the details of what is created from it in the following entry, so please take a look.

http://www.nuits.jp/embed/2016/08/11/195415

Deep Dive

Now, I would like to delve a little deeper into how the ViewModelLocator works.

As mentioned earlier, the role of the ViewModelLocator is to inject a ViewModel into a View.

And the actual creation of the ViewModel being injected is handled by the ViewModelLocationProvider.

The relationship is roughly as follows:

Therefore, if you want to customize the ViewModel creation logic, you need to change the ViewModelLocationProvider settings.

Changes to ViewModelLocationProvider settings are usually made from the App class, which I will describe later.

By the way, the ViewModelLocator itself is almost empty, and the actual substance exists almost entirely on the ViewModelLocationProvider side.

At first glance, it seems like integrating the ViewModelLocator and ViewModelLocationProvider might make things clearer, but I imagine the classes are separated for the following two reasons:

  1. The ViewModelLocationProvider is intended to be shared as a Core library with platforms like WPF.
  2. The point of injecting a ViewModel into a View cannot be shared for the following two reasons:
    1. The implementation of Attached Properties differs between Xamarin and others.
    2. The target for injecting the ViewModel is the BindableContext in Xamarin, whereas it is the DataContext in WPF, etc.

Cross-platform development is tough. The more I learn about Prism and Xamarin, the more I feel they are divine.

Let's get back to the topic.

The ViewModelLocationProvider generates a ViewModel from View information based on several rules.

The general process flows as follows:

  1. Determine the ViewModel type from the View type.
  2. Generate an instance of the ViewModel.

A mechanism is provided to freely customize both 1 and 2.

In addition to this framework, you can exceptionally register a dedicated ViewModel factory for a specific View.

If a dedicated factory is registered, it will be used with priority.

Determining the ViewModel Type from the View Type

There are two elements that determine the ViewModel type:

Element Description
TypeFactory Assigns a ViewModel type individually to a View type
ViewTypeToViewModelTypeResolver Determines the ViewModel type from the View type based on rules

In the default state, the ViewTypeToViewModelTypeResolver is set, which determines the ViewModel according to the naming conventions mentioned earlier.

TypeFactory is not set by default.

If you want to change the rules for determining the ViewModel type within your application, you can change it to your own custom resolver using SetDefaultViewTypeToViewModelTypeResolver.

Specific code samples will be provided later.

The ViewTypeToViewModelTypeResolver indicates how the ViewModel is determined for the entire application, but there may be cases where you want to deviate from the rules and change it for an individual View.

In such cases, you can prioritize applying it by registering the ViewModel type by View in the TypeFactory.

Use the Register method to register with TypeFactory.

For a View where TypeFactory is set, the TypeFactory will be used with priority over the Resolver.

Generating the ViewModel Instance

ViewModel instances are generated according to the types determined by the sequence described above.

There are also two elements that instantiate the ViewModel:

Element Description
ViewModelFactory Generates an instance from the ViewModel type
ViewModelFactoryWithViewParameter Generates an instance from the ViewModel type using information from the View instance

By default, the ViewModelFactory is used, and the ViewModel instance is generated by the DI container.

While the ViewModelFactory can of course be changed to a custom implementation, caution is required because it is intertwined with the Page Navigation Service provided by Prism.

Now, there is another factory, ViewModelFactoryWithViewParameter.

While ViewModelFactory generates an instance using the ViewModel type as an argument, ViewModelFactoryWithViewParameter generates the ViewModel by taking the View instance as a parameter in addition to that.

This is used when you want to customize ViewModel generation based on some state of the View.

It is not configured by default. If you set ViewModelFactoryWithViewParameter, it will be prioritized, and the ViewModelFactory will not be used.

Overview of ViewModel Generation

The flow described so far is illustrated in the diagram below.

Case Study

Now, I would like to talk about some more practical aspects.

I will explain how to handle scenarios that might actually occur in real-world applications.

There is one point to note:

Customization of the ViewModelLocator (or rather, the ViewModelLocationProvider) is performed by overriding the ConfigureViewModelLocator method in the App class.

When doing this, if you are not changing the DefaultViewModelFactory, please do not forget to call base.ConfigureViewModelLocator().

Otherwise, DI will not be applied at all.

Let's get started.

01: I want to change the naming convention for "~Page" ViewModels from "~PageViewModel" to "~ViewModel".

This is likely the most common one.

You might want to just put up with it, but I totally understand the desire to change it.

To change the rule that identifies the ViewModel type, you implement it by replacing the ViewTypeToViewModelTypeResolver.

Specifically, add the following code to your App.xaml.cs class.

Implement this by overriding ConfigureViewModelLocator.

The code is based on Prism's default ViewTypeToViewModelTypeResolver.

As I have stressed, please don't forget to call base.ConfigureViewModelLocator().

02: I want to apply a specific ViewModel to only a specific View.

This might be useful when you want to use the same ViewModel for different Views, for example. I haven't personally encountered this yet, but it seems plausible.

In such cases, it is a matter of debate whether creating a common BaseViewModel is better, but I will describe how to handle it using the ViewModelLocationProvider.

For this case as well, override ConfigureViewModelLocator in your App class to implement it.

Register the ViewModel type with the ViewModelLocationProvider by specifying the View.

Specifically, the following two types of methods are provided:

  1. void Register<T, VM>()
  2. void Register(string viewTypeName, Type viewModelType)

Method 1 calls method 2 internally, so they are managed in the same way.

Example code is as follows:

In this example, I am specifying that HogeViewModel should be used for the MainPage class.

It is simple, isn't it?

03: I want to generate the ViewModel for a specific View manually.

In cases 01 and 02, we customized the determination of the ViewModel type for a View. This method is for when you want to generate the ViewModel for a specific View yourself.

Basically, I think it is better to leave ViewModel generation to the DI container, but perhaps you don't want to rely on the container for a screen where you want to prioritize performance?

For this case as well, override ConfigureViewModelLocator in your App class to implement it.

Register a Func<object> to generate the ViewModel with the ViewModelLocationProvider, specifying the View.

Specifically, the following two types of methods are provided:

  1. void Register<T>(Func<object> factory)
  2. void Register(string viewTypeName, Func<object> factory)

Here too, method 1 calls method 2 internally, so they are managed in the same way.

Example code is as follows:

It might be a trivial example, but here it is.

Although the assigned ViewModel hasn't changed from the standard, it might be slightly faster since it doesn't go through the container.

Summary

ViewModelLocator might seem minor, but it was actually quite a lot to cover properly.

To be honest, checking the ViewModelLocationProvider source code on GitHub might have been faster! (lol)

Also, I have omitted the replacement of ViewModelFactory in this article. Basically, I think it's better to use the DI container, and I didn't see a compelling case for taking the View instance as an argument.

If I have further thoughts, I might add to this later or write a separate entry.

That's all for today.

See you next time!

Discussion