Resolução de dependência em Xamarin.Forms
Este artigo explica como injetar um método de resolução de dependência para Xamarin.Forms que o contêiner de injeção de dependência de um aplicativo tenha controle sobre a criação e o tempo de vida de renderizadores personalizados, efeitos e implementações dependencyService. Os exemplos de código neste artigo são obtidos do exemplo Resolução de Dependência usando Contêineres .
No contexto de um Xamarin.Forms aplicativo que usa o padrão MVVM (Model-View-ViewModel), um contêiner de injeção de dependência pode ser usado para registrar e resolver modelos de exibição e para registrar serviços e injetá-los em modelos de exibição. Durante a criação do modelo de exibição, o contêiner injeta todas as dependências necessárias. Se essas dependências não tiverem sido criadas, o contêiner criará e resolverá as dependências primeiro. Para obter mais informações sobre injeção de dependência, incluindo exemplos de injeção de dependências em modelos de exibição, consulte Injeção de dependência.
O controle sobre a criação e o tempo de vida dos tipos em projetos de plataforma é tradicionalmente executado pelo Xamarin.Forms, que usa o Activator.CreateInstance
método para criar instâncias de renderizadores, efeitos e DependencyService
implementações personalizados. Infelizmente, isso limita o controle do desenvolvedor sobre a criação e o tempo de vida desses tipos e a capacidade de injetar dependências neles. Esse comportamento pode ser alterado injetando um método de resolução de dependência em Xamarin.Forms que controla como os tipos serão criados , seja pelo contêiner de injeção de dependência do aplicativo ou por Xamarin.Forms. No entanto, observe que não há nenhum requisito para injetar um método de resolução de dependência em Xamarin.Forms. Xamarin.Forms continuará a criar e gerenciar o tempo de vida dos tipos em projetos de plataforma se um método de resolução de dependência não for injetado.
Observação
Embora este artigo se concentre em injetar um método de resolução de dependência no Xamarin.Forms que resolve tipos registrados usando um contêiner de injeção de dependência, também é possível injetar um método de resolução de dependência que usa métodos de fábrica para resolve tipos registrados. Para obter mais informações, consulte o exemplo resolução de dependência usando métodos de fábrica .
Injetar um método de resolução de dependência
A DependencyResolver
classe fornece a capacidade de injetar um método de resolução de dependência em Xamarin.Forms, usando o ResolveUsing
método . Em seguida, quando Xamarin.Forms precisar de uma instância de um tipo específico, o método de resolução de dependência terá a oportunidade de fornecer a instância. Se o método de resolução de dependência retornar null
para um tipo solicitado, Xamarin.Forms retornará à tentativa de criar a própria instância de tipo usando o Activator.CreateInstance
método .
O exemplo a seguir mostra como definir o método de resolução de dependência com o ResolveUsing
método :
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);
...
}
...
}
Neste exemplo, o método de resolução de dependência é definido como uma expressão lambda que usa o contêiner de injeção de dependência do Autofac para resolve todos os tipos que foram registrados com o contêiner. Caso contrário, null
será retornado, o que resultará na Xamarin.Forms tentativa de resolve o tipo.
Observação
A API usada por um contêiner de injeção de dependência é específica para o contêiner. Os exemplos de código neste artigo usam o Autofac como um contêiner de injeção de dependência, que fornece os IContainer
tipos e ContainerBuilder
. Contêineres alternativos de injeção de dependência poderiam ser usados igualmente, mas usariam APIs diferentes das apresentadas aqui.
Observe que não há nenhum requisito para definir o método de resolução de dependência durante a inicialização do aplicativo. Ele pode ser definido a qualquer momento. A única restrição é que Xamarin.Forms precisa saber sobre o método de resolução de dependência no momento em que o aplicativo tenta consumir tipos armazenados no contêiner de injeção de dependência. Portanto, se houver serviços no contêiner de injeção de dependência que o aplicativo exigirá durante a inicialização, o método de resolução de dependência precisará ser definido no início do ciclo de vida do aplicativo. Da mesma forma, se o contêiner de injeção de dependência gerenciar a criação e o tempo de vida de um determinado Effect
, Xamarin.Forms precisará saber sobre o método de resolução de dependência antes de tentar criar uma exibição que use esse Effect
.
Aviso
Registrar e resolver tipos com um contêiner de injeção de dependência tem um custo de desempenho devido ao uso de reflexão do contêiner para criar cada tipo, especialmente se as dependências estiverem sendo reconstruídas para cada navegação de página no aplicativo. Se houver muitas dependências ou se elas forem profundas, o custo da criação poderá aumentar significativamente.
Registrando tipos
Os tipos devem ser registrados com o contêiner de injeção de dependência antes que ele possa resolve-los por meio do método de resolução de dependência. O exemplo de código a seguir mostra os métodos de registro que o aplicativo de exemplo expõe na App
classe para o contêiner Autofac:
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();
}
...
}
Quando um aplicativo usa um método de resolução de dependência para resolve tipos de um contêiner, registros de tipo normalmente são executados de projetos de plataforma. Isso permite que projetos de plataforma registrem tipos para renderizadores, efeitos e DependencyService
implementações personalizados.
Após o registro de tipo de um projeto de plataforma, o IContainer
objeto deve ser criado, o que é feito chamando o BuildContainer
método . Esse método invoca o ContainerBuilder
método autofac Build
na instância , que cria um novo contêiner de injeção de dependência que contém os registros que foram feitos.
Nas seções a seguir, uma Logger
classe que implementa a ILogger
interface é injetada em construtores de classe. A Logger
classe implementa a funcionalidade de registro em log simples usando o Debug.WriteLine
método e é usada para demonstrar como os serviços podem ser injetados em renderizadores, efeitos e DependencyService
implementações personalizados.
Registrando renderizadores personalizados
O aplicativo de exemplo inclui uma página que reproduz vídeos da Web, cuja origem XAML é mostrada no exemplo a seguir:
<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>
A VideoPlayer
exibição é implementada em cada plataforma por uma VideoPlayerRenderer
classe, que fornece a funcionalidade para reproduzir o vídeo. Para obter mais informações sobre essas classes de renderizador personalizadas, consulte Implementando um player de vídeo.
No iOS e na UWP (Plataforma Universal do Windows), as VideoPlayerRenderer
classes têm o seguinte construtor, que requer um ILogger
argumento:
public VideoPlayerRenderer(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Em todas as plataformas, o registro de tipo com o contêiner de injeção de dependência é executado pelo RegisterTypes
método , que é invocado antes da plataforma carregar o aplicativo com o LoadApplication(new App())
método . O exemplo a seguir mostra o RegisterTypes
método na plataforma iOS:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterType<FormsVideoLibrary.iOS.VideoPlayerRenderer>();
App.BuildContainer();
}
Neste exemplo, o Logger
tipo concreto é registrado por meio de um mapeamento em relação ao seu tipo de interface e o VideoPlayerRenderer
tipo é registrado diretamente sem um mapeamento de interface. Quando o usuário navegar até a página que contém a VideoPlayer
exibição, o método de resolução de dependência será invocado para resolve o VideoPlayerRenderer
tipo do contêiner de injeção de dependência, que também resolve e injetará o Logger
tipo no VideoPlayerRenderer
construtor.
O VideoPlayerRenderer
construtor na plataforma Android é um pouco mais complicado, pois requer um Context
argumento além do ILogger
argumento :
public VideoPlayerRenderer(Context context, ILogger logger) : base(context)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
O exemplo a seguir mostra o RegisterTypes
método na plataforma Android:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterTypeWithParameters<FormsVideoLibrary.Droid.VideoPlayerRenderer>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
App.BuildContainer();
}
Neste exemplo, o App.RegisterTypeWithParameters
método registra o VideoPlayerRenderer
com o contêiner de injeção de dependência. O método de registro garante que a MainActivity
instância será injetada como o Context
argumento e que o Logger
tipo será injetado como o ILogger
argumento.
Registrando efeitos
O aplicativo de exemplo inclui uma página que usa um efeito de rastreamento de toque para arrastar BoxView
instâncias ao redor da página. O Effect
é adicionado ao BoxView
usando o seguinte código:
var boxView = new BoxView { ... };
var touchEffect = new TouchEffect();
boxView.Effects.Add(touchEffect);
A TouchEffect
classe é uma RoutingEffect
implementada em cada plataforma por uma TouchEffect
classe que é um PlatformEffect
. A classe de plataforma TouchEffect
fornece a funcionalidade para arrastar o BoxView
ao redor da página. Para obter mais informações sobre essas classes de efeito, consulte Invocando eventos de efeitos.
Em todas as plataformas, a TouchEffect
classe tem o seguinte construtor, que requer um ILogger
argumento:
public TouchEffect(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Em todas as plataformas, o registro de tipo com o contêiner de injeção de dependência é executado pelo RegisterTypes
método , que é invocado antes da plataforma carregar o aplicativo com o LoadApplication(new App())
método . O exemplo a seguir mostra o RegisterTypes
método na plataforma Android:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterType<TouchTracking.Droid.TouchEffect>();
App.BuildContainer();
}
Neste exemplo, o Logger
tipo concreto é registrado por meio de um mapeamento em relação ao seu tipo de interface e o TouchEffect
tipo é registrado diretamente sem um mapeamento de interface. Quando o usuário navegar até a página que contém uma BoxView
instância que tem o TouchEffect
anexado a ela, o método de resolução de dependência será invocado para resolve o tipo de plataforma TouchEffect
do contêiner de injeção de dependência, que também resolve e injetará o Logger
tipo no TouchEffect
construtor.
Registrando implementações de DependencyService
O aplicativo de exemplo inclui uma página que usa implementações DependencyService
em cada plataforma para permitir que o usuário escolha uma foto da biblioteca de imagens do dispositivo. A IPhotoPicker
interface define a funcionalidade implementada pelas DependencyService
implementações e é mostrada no exemplo a seguir:
public interface IPhotoPicker
{
Task<Stream> GetImageStreamAsync();
}
Em cada projeto de plataforma, a PhotoPicker
classe implementa a IPhotoPicker
interface usando APIs de plataforma. Para obter mais informações sobre esses serviços de dependência, consulte Escolhendo uma foto da biblioteca de imagens.
No iOS e na UWP, as PhotoPicker
classes têm o seguinte construtor, que requer um ILogger
argumento:
public PhotoPicker(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Em todas as plataformas, o registro de tipo com o contêiner de injeção de dependência é executado pelo RegisterTypes
método , que é invocado antes da plataforma carregar o aplicativo com o LoadApplication(new App())
método . O exemplo a seguir mostra o RegisterTypes
método na UWP:
void RegisterTypes()
{
DIContainerDemo.App.RegisterType<ILogger, Logger>();
DIContainerDemo.App.RegisterType<IPhotoPicker, Services.UWP.PhotoPicker>();
DIContainerDemo.App.BuildContainer();
}
Neste exemplo, o Logger
tipo concreto é registrado por meio de um mapeamento em relação ao seu tipo de interface e o PhotoPicker
tipo também é registrado por meio de um mapeamento de interface.
O PhotoPicker
construtor na plataforma Android é um pouco mais complicado, pois requer um Context
argumento além do ILogger
argumento :
public PhotoPicker(Context context, ILogger logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
O exemplo a seguir mostra o RegisterTypes
método na plataforma Android:
void RegisterTypes()
{
App.RegisterType<ILogger, Logger>();
App.RegisterTypeWithParameters<IPhotoPicker, Services.Droid.PhotoPicker>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
App.BuildContainer();
}
Neste exemplo, o App.RegisterTypeWithParameters
método registra o PhotoPicker
com o contêiner de injeção de dependência. O método de registro garante que a MainActivity
instância será injetada como o Context
argumento e que o Logger
tipo será injetado como o ILogger
argumento.
Quando o usuário navega até a página de seleção de fotos e opta por selecionar uma foto, o OnSelectPhotoButtonClicked
manipulador é executado:
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);
}
...
}
Quando o DependencyService.Resolve<T>
método é invocado, o método de resolução de dependência será invocado para resolve o PhotoPicker
tipo do contêiner de injeção de dependência, que também resolve e injetará o Logger
tipo no PhotoPicker
construtor.
Observação
O Resolve<T>
método deve ser usado ao resolver um tipo do contêiner de injeção de dependência do aplicativo por meio do DependencyService
.