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
を介してアプリケーションの依存関係挿入コンテナーから型を解決するときに使用する必要があります。