paint-brush
Injeção de dependência com Dagger 2: o que é, conceitos-chave e muito maispor@dilip2882
404 leituras
404 leituras

Injeção de dependência com Dagger 2: o que é, conceitos-chave e muito mais

por Dilip Patel21m2024/08/28
Read on Terminal Reader

Muito longo; Para ler

Dependency Injection (DI) é um padrão de design usado para implementar Inversion of Control (IoC). É uma técnica onde a responsabilidade de criar objetos é transferida para outras partes do código. Isso promove acoplamento frouxo, tornando o código mais modular e fácil de gerenciar.
featured image - Injeção de dependência com Dagger 2: o que é, conceitos-chave e muito mais
Dilip Patel HackerNoon profile picture
0-item


Introdução à injeção de dependência

Dependency Injection (DI) é um padrão de design usado para implementar Inversion of Control (IoC) onde o controle de criação e gerenciamento de dependências é transferido do aplicativo para uma entidade externa. Isso ajuda a criar um código mais modular, testável e sustentável. É uma técnica onde a responsabilidade de criar objetos é transferida para outras partes do código. Isso promove acoplamento frouxo, tornando o código mais modular e mais fácil de gerenciar.

As classes geralmente precisam de referências a outras classes para funcionar corretamente. Por exemplo, considere uma classe Library que requer uma classe Book . Essas classes necessárias são conhecidas como dependências. A classe Library depende de ter uma instância da classe Book para operar.

hiperskill.org

Há três maneiras principais para uma classe obter os objetos de que necessita:

  1. Autoconstrução : A classe cria e inicializa suas próprias dependências. Por exemplo, a classe Library criaria e inicializaria sua própria instância da classe Book .
  2. Recuperação externa : A classe recupera dependências de uma fonte externa. Algumas APIs do Android, como Context getters e getSystemService() , funcionam dessa forma.
  3. Injeção de dependência : Dependências são fornecidas à classe, seja quando ela é construída ou por meio de métodos que as requerem. Por exemplo, o construtor Library receberia uma instância Book como parâmetro.

A terceira opção é injeção de dependência! Com DI, você fornece as dependências de uma classe em vez de fazer com que a instância da classe as obtenha ela mesma.

Exemplo sem injeção de dependência

Sem DI, uma Library que cria sua própria dependência Book pode se parecer com isto:

 class Library { private Book book = new Book(); void open() { book.read(); } } public class Main { public static void main(String[] args) { Library library = new Library(); library.open(); } }

Este não é um exemplo de DI porque a classe Library constrói seu próprio Book . Isso pode ser problemático porque:

  • Acoplamento forte : Library e Book são acoplados forte. Uma instância de Library usa um tipo de Book , dificultando o uso de subclasses ou implementações alternativas.
  • Dificuldades de teste : A dependência hard em Book torna o teste mais desafiador. Library usa uma instância real de Book , impedindo o uso de dublês de teste para modificar Book para diferentes casos de teste.

Exemplo com injeção de dependência

Com DI, em vez de cada instância de Library construir seu próprio objeto Book , ela recebe um objeto Book como parâmetro em seu construtor:

 class Library { private Book book; Library(Book book) { this.book = book; } void open() { book.read(); } } public class Main { public static void main(String[] args) { Book book = new Book(); Library library = new Library(book); library.open(); }

A função principal usa Library . Como Library depende de Book , o aplicativo cria uma instância de Book e a usa para construir uma instância de Library . Os benefícios dessa abordagem baseada em DI são:

  • Reusabilidade de Library : Você pode passar diferentes implementações de Book para Library . Por exemplo, você pode definir uma nova subclasse de Book chamada EBook que você quer que Library use. Com DI, você simplesmente passa uma instância de EBook para Library , e funciona sem nenhuma outra alteração.
  • Teste fácil da Library : você pode passar testes duplos para testar diferentes cenários.

Outro exemplo de DI

Considere um cenário em que uma classe NotificationService depende de uma classe Notification . Sem DI, o NotificationService cria diretamente uma instância de Notification , dificultando o uso de diferentes tipos de notificações ou o teste do serviço com várias implementações de notificação.

Para ilustrar DI, vamos refatorar este exemplo:

 interface Notification { void send(); } class EmailNotification implements Notification { @Override public void send() { // Send email notification } } class SMSNotification implements Notification { @Override public void send() { // Send SMS notification } } class NotificationService { void sendNotification(Notification notification) { notification.send(); } }

Agora, NotificationService depende da interface Notification em vez de uma classe específica. Isso permite que diferentes implementações de Notification sejam usadas de forma intercambiável. Você pode definir a implementação que deseja usar por meio do método sendNotification :

 NotificationService service = new NotificationService(); service.sendNotification(new EmailNotification()); service.sendNotification(new SMSNotification());

Métodos de injeção de dependência no Android

Existem três tipos principais de DI:

  1. Injeção de Método (Interface) : Dependências são passadas por métodos que a classe pode acessar por meio de uma interface ou outra classe. O exemplo anterior demonstra injeção de método.
  2. Injeção de Construtor : Dependências são passadas para a classe através de seu construtor.
 class NotificationService { private final Notification notification; public NotificationService(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(new EmailNotification()); service.sendNotification(); } }

3. Field Injection (ou Setter Injection) : Certas classes do framework Android, como activities e fragments, são instanciadas pelo sistema, então a injeção de construtor não é possível. Com a injeção de campo, as dependências são instanciadas após a classe ser criada.

 class NotificationService { private Notification notification; public Notification getNotification() { return notification; } public void setNotification(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(); service.setNotification(new EmailNotification()); service.sendNotification(); } }

4. Injeção de método : as dependências são fornecidas por meio de métodos, geralmente usando a anotação @Inject .

Vantagens da injeção de dependência

  • As classes se tornam mais reutilizáveis e menos dependentes de implementações específicas. Isso se deve à inversão de controle, onde as classes não gerenciam mais suas dependências, mas trabalham com qualquer configuração fornecida.
  • Dependências fazem parte da superfície da API e podem ser verificadas na criação do objeto ou no momento da compilação, facilitando a refatoração.
  • Como uma classe não gerencia suas dependências, diferentes implementações podem ser passadas durante os testes para cobrir vários cenários.

Injeção de Dependência Automatizada

No exemplo anterior, você criou, forneceu e gerenciou manualmente as dependências de diferentes classes sem usar uma biblioteca. Essa abordagem é conhecida como injeção manual de dependência. Embora funcione para casos simples, torna-se trabalhoso à medida que o número de dependências e classes aumenta. A injeção manual de dependência tem várias desvantagens:

  • Boilerplate Code : Para grandes aplicativos, gerenciar todas as dependências e conectá-las corretamente pode resultar em muito código repetitivo. Em uma arquitetura multicamadas, criar um objeto para uma camada superior requer fornecer todas as dependências para as camadas abaixo dela. Por exemplo, para construir um computador, você precisa de uma CPU, uma placa-mãe, RAM e outros componentes; e uma CPU pode precisar de transistores e capacitores.
  • Gerenciamento de dependências complexas : quando não é possível construir dependências antecipadamente — como com inicializações lentas ou escopo de objetos para fluxos específicos no seu aplicativo — você precisa escrever e manter um contêiner personalizado (ou gráfico de dependências) para gerenciar o tempo de vida das suas dependências na memória.

As bibliotecas podem automatizar esse processo criando e fornecendo dependências para você. Essas bibliotecas se dividem em duas categorias:

  1. Soluções baseadas em reflexão : conectam dependências em tempo de execução.
  2. Soluções estáticas : geram código para conectar dependências em tempo de compilação.

Dagger é uma biblioteca popular de injeção de dependência para Java, Kotlin e Android, mantida pelo Google. Dagger simplifica DI em seu aplicativo criando e gerenciando o gráfico de dependência para você. Ele fornece dependências totalmente estáticas e em tempo de compilação, abordando muitos dos problemas de desenvolvimento e desempenho associados a soluções baseadas em reflexão como Guice.

Soluções baseadas em reflexão

Essas estruturas conectam dependências em tempo de execução:

  1. Toothpick : Um framework de DI de tempo de execução que usa reflexão para conectar dependências. Ele foi projetado para ser leve e rápido, tornando-o adequado para aplicativos Android.

Soluções Estáticas

Essas estruturas geram código para conectar dependências em tempo de compilação:

  1. Hilt : Construído sobre o Dagger, o Hilt fornece uma maneira padrão de incorporar injeção de dependência do Dagger em um aplicativo Android. Ele simplifica a configuração e o uso do Dagger ao fornecer componentes e escopos predefinidos .
  2. Koin : Um framework DI leve e simples para Kotlin. Koin usa um DSL para definir dependências e é fácil de configurar e usar.
  3. Kodein : Um framework DI baseado em Kotlin que é fácil de usar e entender. Ele fornece uma API simples e flexível para gerenciar dependências.

Alternativas à injeção de dependência

Uma alternativa à injeção de dependência é o padrão service locator. Esse padrão de design também ajuda a desacoplar classes de suas dependências concretas. Você cria uma classe conhecida como service locator que cria e armazena dependências, fornecendo-as sob demanda.

 object ServiceLocator { fun getProcessor(): Processor = Processor() } class Computer { private val processor = ServiceLocator.getProcessor() fun start() { processor.run() } } fun main(args: Array<String>) { val computer = Computer() computer.start() }

O padrão do localizador de serviço difere da injeção de dependência em como as dependências são consumidas. Com o padrão do localizador de serviço, as classes solicitam as dependências de que precisam; com a injeção de dependência, o aplicativo fornece proativamente os objetos necessários.

O que é Dagger 2?

Dagger 2 é um framework DI popular para Android. Ele usa geração de código em tempo de compilação e é conhecido por seu alto desempenho. O Dagger 2 simplifica o processo de injeção de dependência gerando o código necessário para lidar com dependências, reduzindo boilerplate e melhorando a eficiência.

Dagger 2 é uma biblioteca baseada em anotação para injeção de dependência no Android. Aqui estão as principais anotações e seus propósitos:

  • @Module : Usado para definir classes que fornecem dependências. Por exemplo, um módulo pode fornecer um ApiClient para Retrofit.
  • @Provides : Anota métodos em um módulo para especificar como criar e retornar dependências.
  • @Inject : Usado para solicitar dependências. Pode ser aplicado a campos, construtores e métodos.
  • @Component : Uma interface que faz a ponte entre @Module e @Inject . Ela contém todos os módulos e fornece o construtor para o aplicativo.
  • @Singleton : Garante que uma única instância de uma dependência seja criada.
  • @Binds : Usado em classes abstratas para fornecer dependências, semelhante a @Provides mas mais conciso.

Componentes de Adaga

O Dagger pode gerar um gráfico de dependências para seu projeto, permitindo que ele determine onde obter dependências quando necessário. Para habilitar isso, você precisa criar uma interface e anotá-la com @Component .

Dentro da interface @Component , você define métodos que retornam instâncias das classes que você precisa (por exemplo, BookRepository ). A anotação @Component instrui o Dagger a gerar um contêiner com todas as dependências necessárias para satisfazer os tipos que ele expõe. Esse contêiner é conhecido como um componente Dagger e contém um gráfico de objetos que o Dagger sabe como fornecer junto com suas dependências.


Exemplo

Vamos considerar um exemplo envolvendo um LibraryRepository :

  1. Anotar o construtor : adicione uma anotação @Inject ao construtor LibraryRepository para que o Dagger saiba como criar uma instância de LibraryRepository .
 public class LibraryRepository { private final LocalLibraryDataSource localDataSource; private final RemoteLibraryDataSource remoteDataSource; @Inject public LibraryRepository(LocalLibraryDataSource localDataSource, RemoteLibraryDataSource remoteDataSource) { this.localDataSource = localDataSource; this.remoteDataSource = remoteDataSource; } }

2. Anotar dependências : Da mesma forma, anote os construtores das dependências ( LocalLibraryDataSource e RemoteLibraryDataSource ) para que o Dagger saiba como criá-las.

 public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } }

3. Defina o componente : crie uma interface anotada com @Component para definir o gráfico de dependência.

 @Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); }

Quando você cria o projeto, o Dagger gera uma implementação da interface ApplicationComponent para você, normalmente chamada DaggerApplicationComponent .

Uso

Agora você pode usar o componente gerado para obter instâncias de suas classes com suas dependências injetadas automaticamente:

 public class MainApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.create(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }

Na sua atividade ou fragmento, você pode recuperar a instância LibraryRepository :

 public class MainActivity extends AppCompatActivity { @Inject LibraryRepository libraryRepository; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MainApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected libraryRepository } }

Conceitos-chave em Dagger 2

1. Módulos
∘ Conceitos-chave dos módulos
∘ Incluindo Módulos em Componentes
2. Escopos
3. Componentes
4. Dependências de componentes
5. Ligações de tempo de execução

1. Módulos

Módulos no Dagger 2 são classes anotadas com @Module que fornecem dependências para os componentes. Eles contêm métodos anotados com @Provides ou @Binds para especificar como criar e fornecer dependências. Módulos são essenciais para organizar e gerenciar a criação de objetos que seu aplicativo precisa.

Conceitos-chave dos módulos

  1. @Anotação de módulo: Esta anotação é usada para definir uma classe como um módulo Dagger. Uma classe de módulo contém métodos que fornecem dependências.
  2. @Provides Annotation: Esta anotação é usada em métodos dentro de um módulo para indicar que o método fornece uma certa dependência. Esses métodos são responsáveis por criar e retornar instâncias das dependências.
  3. Anotação @Binds: Esta anotação é usada em classes abstratas para vincular uma implementação a uma interface. Ela é mais concisa que @Provides e é usada quando o módulo é uma classe abstrata.

Exemplo de um módulo

 @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } @Provides @Singleton OkHttpClient provideOkHttpClient() { return new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build(); } }

Neste exemplo, NetworkModule é uma classe anotada com @Module . Ela contém dois métodos anotados com @Provides que criam e retornam instâncias de Retrofit e OkHttpClient .

Usando @Binds

Quando você tem uma interface e sua implementação, você pode usar @Binds para vincular a implementação à interface. Isso é mais conciso do que usar @Provides .

 public interface ApiService { void fetchData(); } public class ApiServiceImpl implements ApiService { @Override public void fetchData() { // Implementation } } @Module public abstract class ApiModule { @Binds abstract ApiService bindApiService(ApiServiceImpl apiServiceImpl); }

Neste exemplo, ApiModule é uma classe abstrata anotada com @Module . O método bindApiService é anotado com @Binds para vincular ApiServiceImpl a ApiService .

Os módulos podem ser organizados com base na funcionalidade que eles fornecem. Por exemplo, você pode ter módulos separados para operações de rede, operações de banco de dados e dependências relacionadas à UI.

Exemplo:

  • NetworkModule : fornece dependências relacionadas à rede, como Retrofit e OkHttpClient .
  • DatabaseModule : fornece dependências relacionadas ao banco de dados, como RoomDatabase .
  • UIModule : fornece dependências relacionadas à interface do usuário, como ViewModel e Presenter .

Incluindo módulos em componentes

Os módulos são incluídos em componentes para fornecer dependências às classes que precisam deles. Veja como você pode configurá-lo:

ApplicationComponent.java:

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Neste exemplo, ApplicationComponent inclui NetworkModule e DatabaseModule para fornecer dependências ao aplicativo.

2. Escopos

Os escopos no Dagger 2 são anotações que definem o ciclo de vida das dependências. Eles garantem que uma única instância de uma dependência seja criada e compartilhada dentro de um escopo especificado. Isso ajuda a gerenciar a memória de forma eficiente e a garantir que as dependências sejam reutilizadas quando apropriado.

  • Escopo Singleton : garante uma única instância de uma dependência durante todo o ciclo de vida do aplicativo.
  • Escopo da atividade : garante uma única instância de uma dependência dentro do ciclo de vida de uma atividade.
  • Escopo do fragmento : garante uma única instância de uma dependência dentro do ciclo de vida de um fragmento.

1. Escopo Singleton

Definição : O escopo @Singleton garante que uma única instância de uma dependência seja criada e compartilhada durante todo o ciclo de vida do aplicativo.

Esse escopo normalmente é usado para dependências que precisam ser compartilhadas por todo o aplicativo, como clientes de rede, instâncias de banco de dados ou preferências compartilhadas.

Exemplo:

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Neste exemplo, a anotação @Singleton garante que as instâncias Retrofit e Database fornecidas pelo NetworkModule e DatabaseModule sejam singletons e compartilhadas por todo o aplicativo.

2. Âmbito da atividade

Definição : O @ActivityScope (um escopo personalizado) garante que uma única instância de uma dependência seja criada e compartilhada dentro do ciclo de vida de uma atividade.

Esse escopo é útil para dependências específicas de uma atividade e devem ser recriadas sempre que a atividade for recriada, como apresentadores ou modelos de exibição.

Exemplo :

 @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

Neste exemplo, a anotação @ActivityScope garante que as dependências fornecidas pelo ActivityModule sejam delimitadas ao ciclo de vida da atividade.

3. Escopo do fragmento

Definição : O @FragmentScope (outro escopo personalizado) garante que uma única instância de uma dependência seja criada e compartilhada dentro do ciclo de vida de um fragmento.

Caso de uso: esse escopo é útil para dependências específicas de um fragmento e devem ser recriadas sempre que o fragmento for recriado, como apresentadores específicos de fragmento ou modelos de exibição.

Exemplo :

 @Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope { } @FragmentScope @Component(dependencies = ActivityComponent.class, modules = FragmentModule.class) public interface FragmentComponent { void inject(MyFragment myFragment); }

Neste exemplo, a anotação @FragmentScope garante que as dependências fornecidas pelo FragmentModule sejam delimitadas ao ciclo de vida do fragmento.

3. Componentes

Dependências de componentes permitem que um componente dependa de outro, permitindo a reutilização de dependências. Existem dois tipos principais de dependências de componentes:

  • Componente do aplicativo : fornece dependências necessárias em todo o aplicativo.
  • Componente de atividade : fornece dependências necessárias dentro de uma atividade específica.

1. Componente de Aplicação

Definição : O Application Component fornece dependências que são necessárias em todo o aplicativo. Ele é tipicamente delimitado com @Singleton para garantir que as dependências sejam compartilhadas em todo o aplicativo.

Este componente é usado para dependências que precisam estar disponíveis globalmente, como clientes de rede, instâncias de banco de dados ou preferências compartilhadas.

Exemplo :

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Neste exemplo, o ApplicationComponent é responsável por fornecer instâncias de Retrofit e Database , que são compartilhadas por todo o aplicativo.

2. Componente de atividade

Definição : O Activity Component fornece dependências que são necessárias dentro de uma atividade específica. Ele é tipicamente delimitado com um escopo personalizado, como @ActivityScope , para garantir que as dependências sejam recriadas sempre que a atividade for recriada.

Este componente é usado para dependências específicas de uma atividade, como apresentadores ou modelos de exibição.

Exemplo :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

Neste exemplo, o ActivityComponent depende do ApplicationComponent e fornece dependências específicas para o MainActivity .

4. Dependências de componentes

Dependências de componentes permitem que um componente dependa de outro, permitindo a reutilização de dependências. Existem dois tipos principais de dependências de componentes:

  • Subcomponentes : Um subcomponente é filho de outro componente e pode acessar as dependências de seu pai.
  • Atributo de dependência : permite que um componente dependa de outro componente sem ser um subcomponente.

1. Subcomponentes:

Um subcomponente é filho de outro componente e pode acessar as dependências de seu pai. Subcomponentes são definidos dentro do componente pai e podem herdar seu escopo.

Exemplo :

 @ActivityScope @Subcomponent(modules = ActivityModule.class) public interface ActivitySubcomponent { void inject(MainActivity mainActivity); }

Neste exemplo, ActivitySubcomponent é um subcomponente do componente pai e pode acessar suas dependências.

2. Atributo de dependência

Isso permite que um componente dependa de outro componente sem ser um subcomponente. O componente dependente pode acessar as dependências fornecidas pelo componente pai.

Exemplo :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

Neste exemplo, ActivityComponent depende de ApplicationComponent e pode acessar suas dependências.

5. Ligações de tempo de execução

As vinculações de tempo de execução no Dagger 2 referem-se ao fornecimento de dependências que são criadas e gerenciadas em tempo de execução, com base no contexto em que são necessárias.

  • Contexto do aplicativo : usado para dependências que precisam durar tanto quanto o aplicativo.
  • Contexto da atividade : usado para dependências que precisam durar tanto quanto uma atividade.

1. Contexto da aplicação

Definição : O contexto do aplicativo é um contexto que está vinculado ao ciclo de vida de todo o aplicativo. Ele é usado para dependências que precisam viver tanto quanto o próprio aplicativo.

Dependências que são compartilhadas por todo o aplicativo e não precisam ser recriadas para cada atividade ou fragmento. Exemplos incluem clientes de rede, instâncias de banco de dados e preferências compartilhadas.

Exemplo :

 @Module public class AppModule { private final Application application; public AppModule(Application application) { this.application = application; } @Provides @Singleton Application provideApplication() { return application; } @Provides @Singleton Context provideApplicationContext() { return application.getApplicationContext(); } }

Neste exemplo, AppModule fornece o contexto do aplicativo como uma dependência singleton. O método provideApplicationContext garante que o contexto fornecido esteja vinculado ao ciclo de vida do aplicativo.

2. Contexto da atividade

Definição : O contexto de atividade é um contexto que está vinculado ao ciclo de vida de uma atividade específica. Ele é usado para dependências que precisam viver tanto quanto a atividade em si.

Dependências que são específicas de uma atividade e devem ser recriadas sempre que a atividade for recriada. Exemplos incluem modelos de visualização, apresentadores e dependências relacionadas à IU.

Exemplo :

 @Module public class ActivityModule { private final Activity activity; public ActivityModule(Activity activity) { this.activity = activity; } @Provides @ActivityScope Activity provideActivity() { return activity; } @Provides @ActivityScope Context provideActivityContext() { return activity; } }

Neste exemplo, ActivityModule fornece o contexto da atividade como uma dependência com escopo. O método provideActivityContext garante que o contexto fornecido esteja vinculado ao ciclo de vida da atividade.

Usando ligações de tempo de execução em componentes

Para usar essas vinculações de tempo de execução, você precisa incluir os módulos correspondentes em seus componentes:

Componente do aplicativo :

 @Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); Context getApplicationContext(); }

Componente de atividade :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); Context getActivityContext(); }

Injetando Contextos

Depois de configurar seus componentes e módulos, você pode injetar os contextos em suas classes conforme necessário.

Exemplo em uma atividade :

 public class MainActivity extends AppCompatActivity { @Inject Context activityContext; @Inject Context applicationContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApplicationComponent appComponent = ((MyApplication) getApplication()).getApplicationComponent(); ActivityComponent activityComponent = DaggerActivityComponent.builder() .applicationComponent(appComponent) .activityModule(new ActivityModule(this)) .build(); activityComponent.inject(this); // Use the injected contexts Log.d("MainActivity", "Activity Context: " + activityContext); Log.d("MainActivity", "Application Context: " + applicationContext); } }

Neste exemplo, MainActivity recebe tanto o contexto da atividade quanto o contexto do aplicativo por meio de injeção de dependência. Isso permite que a atividade use o contexto apropriado com base nas necessidades específicas das dependências.

Exemplo: Usando Dagger 2 em um aplicativo Android

Configurando Dagger 2

Para usar o Dagger 2 em seu projeto, você precisa adicionar as seguintes dependências ao seu arquivo build.gradle :

 dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' }

Substitua 2.x pela versão mais recente do Dagger 2.

Etapa 1: Definir um módulo

Crie um módulo para fornecer dependências. Por exemplo, um NetworkModule para fornecer uma instância Retrofit :

 @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } }

Etapa 2: Definir um componente

Crie um componente para conectar o módulo e as classes que precisam das dependências:

 @Singleton @Component(modules = {NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Etapa 3: Injetar dependências

Use o componente para injetar dependências em suas classes. Por exemplo, em sua classe Application :

 public class MyApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder() .networkModule(new NetworkModule()) .build(); applicationComponent.inject(this); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }

Etapa 4: Use dependências injetadas

Agora você pode usar as dependências injetadas em suas classes. Por exemplo, em uma Activity :

 public class MainActivity extends AppCompatActivity { @Inject Retrofit retrofit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MyApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected Retrofit instance // ... } }

Conclusão

Vamos resumir este tópico:

  • O principal objetivo do DI é afrouxar o acoplamento, facilitando o gerenciamento de dependências.
  • Ao usar DI, você pode aumentar a flexibilidade do código e simplificar o processo de teste.
  • DI é um tópico complexo com diferentes implementações baseadas no cenário.
  • A DI em diferentes idiomas tem peculiaridades que podem afetar a maneira como você trabalha com ela.
  • O Dagger 2 automatiza o processo de criação e fornecimento de dependências, reduzindo o código clichê e melhorando a manutenibilidade.
  • O Dagger 2 oferece segurança em tempo de compilação, garantindo que todas as dependências sejam satisfeitas antes da execução do aplicativo.
  • Ao gerar código em tempo de compilação, o Dagger 2 evita a sobrecarga de desempenho associada a soluções baseadas em reflexão.