Resolução de dependência em Xamarin.Forms

Baixar exemplo Baixar o exemplo

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.