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 que requer uma classe . Essas classes necessárias são conhecidas como dependências. A classe depende de ter uma instância da classe para operar. Library Book Library Book Há três maneiras principais para uma classe obter os objetos de que necessita: : A classe cria e inicializa suas próprias dependências. Por exemplo, a classe criaria e inicializaria sua própria instância da classe . Autoconstrução Library Book : A classe recupera dependências de uma fonte externa. Algumas APIs do Android, como getters e , funcionam dessa forma. Recuperação externa Context getSystemService() : Dependências são fornecidas à classe, seja quando ela é construída ou por meio de métodos que as requerem. Por exemplo, o construtor receberia uma instância como parâmetro. Injeção de dependência Library Book 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 que cria sua própria dependência pode se parecer com isto: Library Book 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 constrói seu próprio . Isso pode ser problemático porque: Library Book : e são acoplados forte. Uma instância de usa um tipo de , dificultando o uso de subclasses ou implementações alternativas. Acoplamento forte Library Book Library Book : A dependência hard em torna o teste mais desafiador. usa uma instância real de , impedindo o uso de dublês de teste para modificar para diferentes casos de teste. Dificuldades de teste Book Library Book Book Exemplo com injeção de dependência Com DI, em vez de cada instância de construir seu próprio objeto , ela recebe um objeto como parâmetro em seu construtor: Library Book Book 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 . Como depende de , o aplicativo cria uma instância de e a usa para construir uma instância de . Os benefícios dessa abordagem baseada em DI são: Library Library Book Book Library : Você pode passar diferentes implementações de para . Por exemplo, você pode definir uma nova subclasse de chamada que você quer que use. Com DI, você simplesmente passa uma instância de para , e funciona sem nenhuma outra alteração. Reusabilidade de Library Book Library Book EBook Library EBook Library : você pode passar testes duplos para testar diferentes cenários. Teste fácil da Library Outro exemplo de DI Considere um cenário em que uma classe depende de uma classe . Sem DI, o cria diretamente uma instância de , dificultando o uso de diferentes tipos de notificações ou o teste do serviço com várias implementações de notificação. NotificationService Notification NotificationService Notification 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, depende da interface em vez de uma classe específica. Isso permite que diferentes implementações de sejam usadas de forma intercambiável. Você pode definir a implementação que deseja usar por meio do método : NotificationService Notification Notification 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: : 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. Injeção de Método (Interface) : Dependências são passadas para a classe através de seu construtor. Injeção de 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(); } } : 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. 3. Field Injection (ou Setter Injection) 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(); } } : as dependências são fornecidas por meio de métodos, geralmente usando a anotação . 4. Injeção de método @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: : 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. Boilerplate Code : 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. Gerenciamento de dependências complexas As bibliotecas podem automatizar esse processo criando e fornecendo dependências para você. Essas bibliotecas se dividem em duas categorias: : conectam dependências em tempo de execução. Soluções baseadas em reflexão : geram código para conectar dependências em tempo de compilação. Soluções estáticas 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: : 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. Toothpick Soluções Estáticas Essas estruturas geram código para conectar dependências em tempo de compilação: : 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. . Hilt Ele simplifica a configuração e o uso do Dagger ao fornecer componentes e escopos predefinidos : Um framework DI leve e simples para Kotlin. Koin usa um DSL para definir dependências e é fácil de configurar e usar. Koin : 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. Kodein 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: : Usado para definir classes que fornecem dependências. Por exemplo, um módulo pode fornecer um para Retrofit. @Module ApiClient : Anota métodos em um módulo para especificar como criar e retornar dependências. @Provides : Usado para solicitar dependências. Pode ser aplicado a campos, construtores e métodos. @Inject : Uma interface que faz a ponte entre e . Ela contém todos os módulos e fornece o construtor para o aplicativo. @Component @Module @Inject : Garante que uma única instância de uma dependência seja criada. @Singleton : Usado em classes abstratas para fornecer dependências, semelhante a mas mais conciso. @Binds @Provides 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 , você define métodos que retornam instâncias das classes que você precisa (por exemplo, ). A anotação 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. @Component BookRepository @Component Exemplo Vamos considerar um exemplo envolvendo um : LibraryRepository : adicione uma anotação ao construtor para que o Dagger saiba como criar uma instância de . Anotar o construtor @Inject LibraryRepository 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; } } : Da mesma forma, anote os construtores das dependências ( e ) para que o Dagger saiba como criá-las. 2. Anotar dependências LocalLibraryDataSource RemoteLibraryDataSource public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } } : crie uma interface anotada com para definir o gráfico de dependência. 3. Defina o componente @Component @Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); } Quando você cria o projeto, o Dagger gera uma implementação da interface para você, normalmente chamada . ApplicationComponent 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 que fornecem dependências para os componentes. Eles contêm métodos anotados com ou 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. @Module @Provides @Binds Conceitos-chave dos módulos @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. @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. Anotação @Binds: Esta anotação é usada em classes abstratas para vincular uma implementação a uma interface. Ela é mais concisa que e é usada quando o módulo é uma classe abstrata. @Provides 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, é uma classe anotada com . Ela contém dois métodos anotados com que criam e retornam instâncias de e . NetworkModule @Module @Provides Retrofit OkHttpClient Usando @Binds Quando você tem uma interface e sua implementação, você pode usar para vincular a implementação à interface. Isso é mais conciso do que usar . @Binds @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, é uma classe abstrata anotada com . O método é anotado com para vincular a . ApiModule @Module bindApiService @Binds ApiServiceImpl 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: : fornece dependências relacionadas à rede, como e . NetworkModule Retrofit OkHttpClient : fornece dependências relacionadas ao banco de dados, como . DatabaseModule RoomDatabase : fornece dependências relacionadas à interface do usuário, como e . UIModule ViewModel 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, inclui e para fornecer dependências ao aplicativo. ApplicationComponent NetworkModule DatabaseModule 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. : garante uma única instância de uma dependência durante todo o ciclo de vida do aplicativo. Escopo Singleton : garante uma única instância de uma dependência dentro do ciclo de vida de uma atividade. Escopo da atividade : garante uma única instância de uma dependência dentro do ciclo de vida de um fragmento. Escopo do fragmento 1. Escopo Singleton : O escopo garante que uma única instância de uma dependência seja criada e compartilhada durante todo o ciclo de vida do aplicativo. Definição @Singleton 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 garante que as instâncias e fornecidas pelo e sejam singletons e compartilhadas por todo o aplicativo. @Singleton Retrofit Database NetworkModule DatabaseModule 2. Âmbito da atividade : O (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. Definição @ActivityScope 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 garante que as dependências fornecidas pelo sejam delimitadas ao ciclo de vida da atividade. @ActivityScope ActivityModule 3. Escopo do fragmento : O (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. Definição @FragmentScope 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 garante que as dependências fornecidas pelo sejam delimitadas ao ciclo de vida do fragmento. @FragmentScope FragmentModule 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: : fornece dependências necessárias em todo o aplicativo. Componente do aplicativo : fornece dependências necessárias dentro de uma atividade específica. Componente de atividade 1. Componente de Aplicação : O Application Component fornece dependências que são necessárias em todo o aplicativo. Ele é tipicamente delimitado com para garantir que as dependências sejam compartilhadas em todo o aplicativo. Definição @Singleton 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 é responsável por fornecer instâncias de e , que são compartilhadas por todo o aplicativo. ApplicationComponent Retrofit Database 2. Componente de atividade : 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 , para garantir que as dependências sejam recriadas sempre que a atividade for recriada. Definição @ActivityScope 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 depende do e fornece dependências específicas para o . ActivityComponent ApplicationComponent 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: : Um subcomponente é filho de outro componente e pode acessar as dependências de seu pai. Subcomponentes : permite que um componente dependa de outro componente sem ser um subcomponente. Atributo de dependência 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, é um subcomponente do componente pai e pode acessar suas dependências. ActivitySubcomponent 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, depende de e pode acessar suas dependências. ActivityComponent ApplicationComponent 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. : usado para dependências que precisam durar tanto quanto o aplicativo. Contexto do aplicativo : usado para dependências que precisam durar tanto quanto uma atividade. Contexto da atividade 1. Contexto da aplicaçã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. Definição 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, fornece o contexto do aplicativo como uma dependência singleton. O método garante que o contexto fornecido esteja vinculado ao ciclo de vida do aplicativo. AppModule provideApplicationContext 2. Contexto da atividade : 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. Definição 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, fornece o contexto da atividade como uma dependência com escopo. O método garante que o contexto fornecido esteja vinculado ao ciclo de vida da atividade. ActivityModule provideActivityContext 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, 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. MainActivity 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 pela versão mais recente do Dagger 2. 2.x Etapa 1: Definir um módulo Crie um módulo para fornecer dependências. Por exemplo, um para fornecer uma instância : NetworkModule 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.