Xamarin.Forms での依存関係の解決

この記事では、アプリケーションの依存関係挿入コンテナーがカスタム レンダラー、効果、DependencyService 実装の作成と有効期間を制御できるように、依存関係解決メソッドを Xamarin.Forms に挿入する方法について説明します。

Model-View-ViewModel (MVVM) パターンを使用する Xamarin.Forms アプリケーションのコンテキストでは、依存関係挿入コンテナーを使用して、ビュー モデルの登録と解決、およびサービスの登録とビュー モデルへの挿入を行うことができます。 ビュー モデルの作成時に、コンテナーは必要な依存関係を挿入します。 これらの依存関係が作成されていない場合、コンテナーは最初に依存関係を作成して解決します。 依存関係の挿入の詳細 (ビュー モデルへの依存関係の挿入の例など) については、「依存関係の挿入」を参照してください。

プラットフォーム プロジェクトでの型の作成と有効期間の制御は、従来、Xamarin.Forms が Activator.CreateInstance メソッドを使用してカスタム レンダラー、効果、DependencyService の実装のインスタンスを作成することにより実行されます。 残念ながら、これにより、開発者によるこれらの型の作成と有効期間、およびそれらに依存関係を挿入する機能が制限されます。 この動作を変更するには、アプリケーションの依存関係挿入コンテナーまたは Xamarin.Forms のいずれかによって、型の作成方法を制御する依存関係解決メソッドを Xamarin.Forms に挿入します。 ただし、依存関係解決メソッドを Xamarin.Forms に挿入する必要がないことに注意してください。 Xamarin.Forms は、依存関係解決メソッドが挿入されていない場合、プラットフォーム プロジェクトの型の有効期間を作成および管理し続けます。

Note

この記事では、依存関係挿入コンテナーを使用して登録済み型を解決する依存関係解決メソッドを Xamarin.Forms に挿入することに重点を置いていますが、ファクトリ メソッドを使用して登録済み型を解決する依存関係解決メソッドを挿入することもできます。

依存関係解決メソッドの挿入

DependencyResolverクラスは、ResolveUsing メソッドを使用して依存関係解決メソッドを Xamarin.Forms に挿入する機能を提供します。 その後、Xamarin.Forms が特定の型のインスタンスを必要とする場合、インスタンスを提供する機会が依存関係解決メソッドに与えられます。 要求された型に対して依存関係解決メソッドが null を返す場合、Xamarin.Forms はフォールバックして、Activator.CreateInstance メソッドを使用して型インスタンス自体を作成しようとします。

次の例は、ResolveUsing メソッドを使用して依存関係解決メソッドを設定する方法を示しています。

using Autofac;
using Xamarin.Forms.Internals;
...

public partial class App : Application
{
    // IContainer and ContainerBuilder are provided by Autofac
    static IContainer container;
    static readonly ContainerBuilder builder = new ContainerBuilder();

    public App()
    {
        ...
        DependencyResolver.ResolveUsing(type => container.IsRegistered(type) ? container.Resolve(type) : null);
        ...
    }
    ...
}

この例では、依存関係解決メソッドは、Autofac 依存関係挿入コンテナーを使用して、コンテナーに登録されている型を解決するラムダ式に設定されています。 それ以外の場合は null が返され、その結果、Xamarin.Forms が型の解決を試みます。

Note

依存関係挿入コンテナーによって使用される API は、コンテナーに固有です。 この記事のコード例では、依存関係挿入コンテナーとして Autofac を使用します。これは IContainer 型と ContainerBuilder 型を提供します。 別の依存関係挿入コンテナーも同様に使用できますが、ここに示されている API とは異なる API を使用します。

アプリケーションの起動時に依存関係解決メソッドを設定しなくてもよいことに注意してください。 設定はいつでもできます。 唯一の制約は、アプリケーションが依存関係挿入コンテナーに格納されている型の使用を試みるまでに、Xamarin.Forms が依存関係解決メソッドについて知る必要があることです。 したがって、依存関係挿入コンテナーに、起動時にアプリケーションが必要とするサービスがある場合、依存関係解決メソッドを、アプリケーションのライフサイクルの早い段階で設定する必要があります。 同様に、依存関係挿入コンテナーが特定の Effect の作成と有効期間を管理する場合、Xamarin.Forms はその Effect を使用するビューの作成を試みる前に、依存関係解決メソッドについて知る必要があります。

警告

依存関係挿入コンテナーを使用した型の登録と解決にはパフォーマンス コストがかかります。これは、特に、アプリケーション内の各ページ ナビゲーションに対して依存関係が再構築されている場合など、コンテナーが各型の作成にリフレクションを使用するためです。 依存関係が多いか深い場合、作成コストが大幅に増加する可能性があります。

型の登録

型は、依存関係解決メソッドを使用して解決する前に、依存関係挿入コンテナーに登録する必要があります。 次のコード例は、サンプル アプリケーションが Autofac コンテナーの App クラスで公開する登録メソッドを示しています。

using Autofac;
using Autofac.Core;
...

public partial class App : Application
{
    static IContainer container;
    static readonly ContainerBuilder builder = new ContainerBuilder();
    ...

    public static void RegisterType<T>() where T : class
    {
        builder.RegisterType<T>();
    }

    public static void RegisterType<TInterface, T>() where TInterface : class where T : class, TInterface
    {
        builder.RegisterType<T>().As<TInterface>();
    }

    public static void RegisterTypeWithParameters<T>(Type param1Type, object param1Value, Type param2Type, string param2Name) where T : class
    {
        builder.RegisterType<T>()
               .WithParameters(new List<Parameter>()
        {
            new TypedParameter(param1Type, param1Value),
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
                (pi, ctx) => ctx.Resolve(param2Type))
        });
    }

    public static void RegisterTypeWithParameters<TInterface, T>(Type param1Type, object param1Value, Type param2Type, string param2Name) where TInterface : class where T : class, TInterface
    {
        builder.RegisterType<T>()
               .WithParameters(new List<Parameter>()
        {
            new TypedParameter(param1Type, param1Value),
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
                (pi, ctx) => ctx.Resolve(param2Type))
        }).As<TInterface>();
    }

    public static void BuildContainer()
    {
        container = builder.Build();
    }
    ...
}

アプリケーションで依存関係解決メソッドを使用してコンテナーから型を解決する場合、型の登録は通常、プラットフォーム プロジェクトから実行されます。 これにより、プラットフォーム プロジェクトでカスタム レンダラー、効果、DependencyService の実装の型を登録できます。

プラットフォーム プロジェクトからの型登録に従って、IContainer オブジェクトをビルドする必要があります。これは、BuildContainer メソッドを呼び出すことによって実現されます。 このメソッドは、ContainerBuilder インスタンスで Autofac の Build メソッドを呼び出します。これにより、作成された登録を含む新しい依存関係挿入コンテナーがビルドされます。

次のセクションでは、ILogger インターフェイスを実装する Logger クラスがクラス コンストラクターに挿入されます。 Logger クラスは Debug.WriteLine メソッドを使用して単純なログ機能を実装し、カスタム レンダラー、効果、DependencyService の実装にサービスを挿入する方法を示すために使用されます。

カスタム レンダラーの登録

サンプル アプリケーションには、Web ビデオを再生するページが含まれています。その XAML ソースは次の例に示されています。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:video="clr-namespace:FormsVideoLibrary"
             ...>
    <video:VideoPlayer Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />
</ContentPage>

VideoPlayer ビューは、動画再生機能を提供する VideoPlayerRenderer クラスによって各プラットフォームに実装されます。 これらのカスタム レンダラー クラスの詳細については、「ビデオ プレーヤーの実装」を参照してください。

iOS および ユニバーサル Windows プラットフォーム (UWP) では、VideoPlayerRenderer クラスには次のコンストラクターがあり、これには ILogger 引数が必要です。

public VideoPlayerRenderer(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

すべてのプラットフォームで、依存関係挿入コンテナーへの型登録は RegisterTypes メソッドによって実行されます。これは、プラットフォームが LoadApplication(new App()) メソッドを使用してアプリケーションを読み込む前に呼び出されます。 次の例は、iOS プラットフォーム上の RegisterTypes メソッドを示します。

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterType<FormsVideoLibrary.iOS.VideoPlayerRenderer>();
    App.BuildContainer();
}

この例では、Logger 具象型はインターフェイス型に対するマッピングを介して登録され、VideoPlayerRenderer 型はインターフェイス マッピングなしで直接登録されます。 ユーザーが VideoPlayer ビューを含むページに移動すると、依存関係解決メソッドが呼び出され、依存関係挿入コンテナーから VideoPlayerRenderer 型が解決されます。これによって、Logger 型も解決され、VideoPlayerRenderer コンストラクターに挿入されます。

Android プラットフォーム上の VideoPlayerRenderer コンストラクターは、ILogger 引数に加えて Context 引数を必要とするため、少し複雑になります。

public VideoPlayerRenderer(Context context, ILogger logger) : base(context)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

次の例は、Android プラットフォーム上の RegisterTypes メソッドを示します。

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterTypeWithParameters<FormsVideoLibrary.Droid.VideoPlayerRenderer>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
    App.BuildContainer();
}

この例では、App.RegisterTypeWithParameters メソッドが VideoPlayerRenderer を依存関係挿入コンテナーに登録します。 登録メソッドは、MainActivity インスタンスが Context 引数として挿入され、Logger 型が ILogger 引数として挿入されることを保証します。

効果の登録

サンプル アプリケーションには、タッチ追跡効果を使用してページの周囲に BoxView インスタンスをドラッグするページが含まれています。 Effect は、次のコードを使用して BoxView に追加されます。

var boxView = new BoxView { ... };
var touchEffect = new TouchEffect();
boxView.Effects.Add(touchEffect);

TouchEffect クラスは、PlatformEffect である TouchEffect クラスによって各プラットフォームに実装される RoutingEffect です。 プラットフォーム TouchEffect クラスは、ページの周囲に BoxView をドラッグする機能を提供します。 これらの効果クラスの詳細については、「エフェクトからのイベントの呼び出し」を参照してください。

すべてのプラットフォームで、TouchEffect クラスには次のコンストラクターがあり、これには ILogger 引数が必要です。

public TouchEffect(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

すべてのプラットフォームで、依存関係挿入コンテナーへの型登録は RegisterTypes メソッドによって実行されます。これは、プラットフォームが LoadApplication(new App()) メソッドを使用してアプリケーションを読み込む前に呼び出されます。 次の例は、Android プラットフォーム上の RegisterTypes メソッドを示します。

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterType<TouchTracking.Droid.TouchEffect>();
    App.BuildContainer();
}

この例では、Logger 具象型はインターフェイス型に対するマッピングを介して登録され、TouchEffect 型はインターフェイス マッピングなしで直接登録されます。 ユーザーが、TouchEffect がアタッチされた BoxView インスタンスを含むページに移動すると、依存関係解決メソッドが呼び出され、依存関係挿入コンテナーからプラットフォーム TouchEffect 型が解決されます。これによって、Logger 型も解決され、TouchEffect コンストラクターに挿入されます。

DependencyService 実装の登録

サンプル アプリケーションには、各プラットフォームの DependencyService 実装を利用して、ユーザーがデバイスの画像ライブラリから写真を選択できるようにするページが含まれています。 IPhotoPicker インターフェイスは、DependencyService の実装によって実装される機能を定義します。これを次の例に示します。

public interface IPhotoPicker
{
    Task<Stream> GetImageStreamAsync();
}

各プラットフォーム プロジェクトでは、PhotoPicker クラスは プラットフォーム API を使用して IPhotoPicker インターフェイスを実装します。 これらの依存関係サービスの詳細については、「画像ライブラリから写真を選択する」を参照してください。

iOS および UWP では、PhotoPicker クラスには次のコンストラクターがあり、これには ILogger 引数が必要です。

public PhotoPicker(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

すべてのプラットフォームで、依存関係挿入コンテナーへの型登録は RegisterTypes メソッドによって実行されます。これは、プラットフォームが LoadApplication(new App()) メソッドを使用してアプリケーションを読み込む前に呼び出されます。 次の例は、UWP 上の RegisterTypes メソッドを示しています。

void RegisterTypes()
{
    DIContainerDemo.App.RegisterType<ILogger, Logger>();
    DIContainerDemo.App.RegisterType<IPhotoPicker, Services.UWP.PhotoPicker>();
    DIContainerDemo.App.BuildContainer();
}

この例では、Logger 具象型はインターフェイス型に対するマッピングを介して登録され、PhotoPicker 型もインターフェイス マッピングを介して登録されます。

Android プラットフォーム上の PhotoPicker コンストラクターは、ILogger 引数に加えて Context 引数を必要とするため、少し複雑になります。

public PhotoPicker(Context context, ILogger logger)
{
    _context = context ?? throw new ArgumentNullException(nameof(context));
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

次の例は、Android プラットフォーム上の RegisterTypes メソッドを示します。

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterTypeWithParameters<IPhotoPicker, Services.Droid.PhotoPicker>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
    App.BuildContainer();
}

この例では、App.RegisterTypeWithParameters メソッドが PhotoPicker を依存関係挿入コンテナーに登録します。 登録メソッドは、MainActivity インスタンスが Context 引数として挿入され、Logger 型が ILogger 引数として挿入されることを保証します。

ユーザーが写真の選択ページに移動し、写真選択を選択すると、OnSelectPhotoButtonClicked ハンドラーが実行されます。

async void OnSelectPhotoButtonClicked(object sender, EventArgs e)
{
    ...
    var photoPickerService = DependencyService.Resolve<IPhotoPicker>();
    var stream = await photoPickerService.GetImageStreamAsync();
    if (stream != null)
    {
        image.Source = ImageSource.FromStream(() => stream);
    }
    ...
}

DependencyService.Resolve<T> メソッドが呼び出されると、依存関係解決メソッドが呼び出され、依存関係挿入コンテナーから PhotoPicker 型が解決されます。これによって、Logger 型も解決され、PhotoPicker コンストラクターに挿入されます。

Note

Resolve<T> メソッドは、DependencyService を介してアプリケーションの依存関係挿入コンテナーから型を解決するときに使用する必要があります。