Un projet comportant plusieurs modules Gradle est appelé "projet multimodule".
Dans un projet multimodule envoyé en tant qu'APK unique sans module de fonctionnalité, il est courant de disposer d'un module app
pouvant dépendre de la plupart des modules de votre projet, ainsi que d'un module base
ou core
dont dépendent les autres modules. Le module app
contient généralement votre classe Application
, tandis que le module base
contient toutes les classes communes à tous les modules de votre projet.
Le module app
est le bon endroit pour afficher votre composant d'application (par exemple, ApplicationComponent
dans l'image ci-dessous) qui peut fournir des objets dont d'autres composants pourraient avoir besoin, ainsi que les Singletons de votre application. Par exemple, les classes telles que OkHttpClient
, les analyseurs JSON, les accesseurs de votre base de données ou les objets SharedPreferences
qui peuvent être définis dans le module core
seront fournis par l'élément ApplicationComponent
défini dans le module app
.
Dans le module app
, d'autres composants peuvent également avoir une durée de vie plus courte.
Par exemple, une propriété UserComponent
avec une configuration spécifique à l'utilisateur (comme UserSession
) après une connexion.
Dans les différents modules de votre projet, vous pouvez définir au moins un sous-composant ayant une logique spécifique à ce module, comme l'illustre l'image 1.
Par exemple, dans un module login
, vous pouvez avoir un élément LoginComponent
limité par une annotation @ModuleScope
personnalisée pouvant fournir des objets communs à cette fonctionnalité, comme un élément LoginRepository
. Dans ce module, d'autres composants peuvent aussi dépendre d'un élément LoginComponent
avec un champ d'application personnalisé, par exemple @FeatureScope
pour LoginActivityComponent
, ou d'un élément TermsAndConditionsComponent
dans lequel vous pouvez appliquer une logique spécifique à une caractéristique, comme des objets ViewModel
.
Pour les autres modules tels que Registration
, la configuration est semblable.
En règle générale, pour un projet multimodule, les modules de même niveau ne doivent pas dépendre les uns des autres. Si c'est le cas, déterminez si cette logique partagée (les dépendances entre elles) doit faire partie du module parent. Procédez alors à une refactorisation pour déplacer les classes vers le module parent. Si ce n'est pas le cas, créez un nouveau module qui étend le module parent et demandez aux deux modules d'origine d'étendre le nouveau module.
Il est recommandé de créer un composant dans un module dans les cas suivants :
Vous devez injecter un champ, comme avec
LoginActivityComponent
.Vous devez définir le champ d'application des objets, comme avec
LoginComponent
.
Si aucun de ces cas ne s'applique et que vous devez indiquer à Dagger comment fournir des objets de ce module, créez et affichez un module Dagger avec des méthodes @Provides
ou @Binds
si l'injection de construction n'est pas possible pour ces classes.
Intégration de sous-composants Dagger
La page de documentation Utiliser Dagger dans les applications Android explique comment créer et utiliser des sous-composants. Toutefois, vous ne pouvez pas utiliser le même code, car les modules de fonctionnalité ne connaissent pas le module app
. Par exemple, si vous pensez à un flux de connexion type et au code de la page précédente, la compilation s'arrête :
Kotlin
class LoginActivity: Activity() { ... override fun onCreate(savedInstanceState: Bundle?) { // Creation of the login graph using the application graph loginComponent = (applicationContext as MyDaggerApplication) .appComponent.loginComponent().create() // Make Dagger instantiate @Inject fields in LoginActivity loginComponent.inject(this) ... } }
Java
public class LoginActivity extends Activity { ... @Override protected void onCreate(Bundle savedInstanceState) { // Creation of the login graph using the application graph loginComponent = ((MyApplication) getApplicationContext()) .appComponent.loginComponent().create(); // Make Dagger instantiate @Inject fields in LoginActivity loginComponent.inject(this); ... } }
La raison est que le module login
ne connaît ni MyApplication
, ni appComponent
. Pour y arriver, vous devez définir une interface dans le module de fonctionnalité qui fournit un élément FeatureComponent
que MyApplication
doit intégrer.
Dans l'exemple suivant, vous pouvez définir une interface LoginComponentProvider
qui fournit un élément LoginComponent
dans le module login
du flux de connexion :
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
À l'avenir, LoginActivity
utilisera cette interface au lieu de l'extrait de code défini ci-dessus :
Kotlin
class LoginActivity: Activity() { ... override fun onCreate(savedInstanceState: Bundle?) { loginComponent = (applicationContext as LoginComponentProvider) .provideLoginComponent() loginComponent.inject(this) ... } }
Java
public class LoginActivity extends Activity { ... @Override protected void onCreate(Bundle savedInstanceState) { loginComponent = ((LoginComponentProvider) getApplicationContext()) .provideLoginComponent(); loginComponent.inject(this); ... } }
MyApplication
doit maintenant intégrer cette interface et les méthodes requises :
Kotlin
class MyApplication: Application(), LoginComponentProvider { // Reference to the application graph that is used across the whole app val appComponent = DaggerApplicationComponent.create() override fun provideLoginComponent(): LoginComponent { return appComponent.loginComponent().create() } }
Java
public class MyApplication extends Application implements LoginComponentProvider { // Reference to the application graph that is used across the whole app ApplicationComponent appComponent = DaggerApplicationComponent.create(); @Override public LoginComponent provideLoginComponent() { return appComponent.loginComponent.create(); } }
Voici comment utiliser des sous-composants Dagger dans un projet multimodule. Avec les modules de fonctionnalité, la solution est différente, car les modules dépendent les uns des autres.
Dépendances de composants avec des modules de fonctionnalité
Avec des modules de fonctionnalité, la manière dont les modules dépendent généralement les uns des autres est inversée. Au lieu du module app
incluant des modules de fonctionnalité, ceux-ci dépendent du module app
. L'image 2 illustre la structure des modules.
Dans Dagger, les composants doivent connaître leurs sous-composants. Ces informations sont incluses dans un module Dagger ajouté au composant parent (comme le module SubcomponentsModule
dans la section Utiliser Dagger dans des applications Android).
Malheureusement, avec la dépendance inverse entre l'application et le module de fonctionnalité, le sous-composant n'est pas visible depuis le module app
, car il ne se trouve pas dans le chemin de compilation. Par exemple, un élément LoginComponent
défini dans un module de fonctionnalité login
ne peut pas être un sous-composant de l'élément ApplicationComponent
défini dans le module app
.
Dagger dispose d'un mécanisme appelé dépendances de composants qui permet de résoudre ce problème. Au lieu d'être un sous-composant du composant parent, le composant enfant dépend du composant parent. Il n'y a donc pas de relation parent-enfant : les composants dépendent d'autres éléments pour obtenir certaines dépendances. Les composants doivent exposer les types du graphique pour que les composants dépendants les consomment.
Par exemple, un module de fonctionnalité appelé login
souhaite créer un LoginComponent
qui dépend de l'élément AppComponent
disponible dans le module Gradle app
.
Vous trouverez ci-dessous les définitions des classes et l'élément AppComponent
qui font partie du module Gradle app
:
Kotlin
// UserRepository's dependencies class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor() { ... } // UserRepository is scoped to AppComponent @Singleton class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... } @Singleton @Component interface AppComponent { ... }
Java
// UserRepository's dependencies public class UserLocalDataSource { @Inject public UserLocalDataSource() {} } public class UserRemoteDataSource { @Inject public UserRemoteDataSource() { } } // UserRepository is scoped to AppComponent @Singleton public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } } @Singleton @Component public interface ApplicationComponent { ... }
Dans votre module Gradle login
, qui inclut le module app
, vous disposez d'un élément LoginActivity
nécessitant l'injection d'une instance LoginViewModel
:
Kotlin
// LoginViewModel depends on UserRepository that is scoped to AppComponent class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Java
// LoginViewModel depends on UserRepository that is scoped to AppComponent public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
LoginViewModel
dispose d'une dépendance vis-à-vis de UserRepository
, disponible et limitée à AppComponent
. Créons un élément LoginComponent
qui dépend de l'élément AppComponent
pour injecter LoginActivity
:
Kotlin
// Use the dependencies attribute in the Component annotation to specify the // dependencies of this Component @Component(dependencies = [AppComponent::class]) interface LoginComponent { fun inject(activity: LoginActivity) }
Java
// Use the dependencies attribute in the Component annotation to specify the // dependencies of this Component @Component(dependencies = AppComponent.class) public interface LoginComponent { void inject(LoginActivity loginActivity); }
LoginComponent
spécifie une dépendance sur AppComponent
en l'ajoutant au paramètre de dépendances de l'annotation du composant. Étant donné que LoginActivity
sera injecté par Dagger, ajoutez la méthode inject()
à l'interface.
Lors de la création d'un élément LoginComponent
, une instance de l'élément AppComponent
doit être transmise. Pour ce faire, utilisez la fabrique de composants :
Kotlin
@Component(dependencies = [AppComponent::class]) interface LoginComponent { @Component.Factory interface Factory { // Takes an instance of AppComponent when creating // an instance of LoginComponent fun create(appComponent: AppComponent): LoginComponent } fun inject(activity: LoginActivity) }
Java
@Component(dependencies = AppComponent.class) public interface LoginComponent { @Component.Factory interface Factory { // Takes an instance of AppComponent when creating // an instance of LoginComponent LoginComponent create(AppComponent appComponent); } void inject(LoginActivity loginActivity); }
LoginActivity
peut maintenant créer une instance de LoginComponent
et appeler la méthode inject()
.
Kotlin
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the Login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { // Gets appComponent from MyApplication available in the base Gradle module val appComponent = (applicationContext as MyApplication).appComponent // Creates a new instance of LoginComponent // Injects the component to populate the @Inject fields DaggerLoginComponent.factory().create(appComponent).inject(this) super.onCreate(savedInstanceState) // Now you can access loginViewModel } }
Java
public class LoginActivity extends Activity { // You want Dagger to provide an instance of LoginViewModel from the Login graph @Inject LoginViewModel loginViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Gets appComponent from MyApplication available in the base Gradle module AppComponent appComponent = ((MyApplication) getApplicationContext()).appComponent; // Creates a new instance of LoginComponent // Injects the component to populate the @Inject fields DaggerLoginComponent.factory().create(appComponent).inject(this); // Now you can access loginViewModel } }
LoginViewModel
dépend de UserRepository
. Pour que LoginComponent
puisse y accéder à partir de l'élément AppComponent
, AppComponent
doit l'exposer dans son interface :
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
Les règles de champ d'application avec des composants dépendants fonctionnent de la même manière qu'avec des sous-composants. Comme LoginComponent
utilise une instance de l'élément AppComponent
, il ne peut pas utiliser la même annotation de champ d'application.
Si vous souhaitez limiter LoginViewModel
à LoginComponent
, procédez comme auparavant avec l'annotation personnalisée @ActivityScope
.
Kotlin
@ActivityScope @Component(dependencies = [AppComponent::class]) interface LoginComponent { ... } @ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
Java
@ActivityScope @Component(dependencies = AppComponent.class) public interface LoginComponent { ... } @ActivityScope public class LoginViewModel { private final UserRepository userRepository; @Inject public LoginViewModel(UserRepository userRepository) { this.userRepository = userRepository; } }
Bonnes pratiques
ApplicationComponent
doit toujours se trouver dans le moduleapp
.Créez des composants Dagger dans des modules si vous devez effectuer une injection de champs dans ce module ou si vous devez limiter les objets à un flux spécifique de votre application.
Pour les modules Gradle destinés à être des utilitaires ou des assistants et qui n'ont pas besoin de créer de graphique (ce qui explique la nécessité d'avoir un composant Dagger), créez et exposez des modules Dagger publics avec les méthodes @Provides et @Binds de ces classes qui ne sont pas compatibles avec l'injection de constructeurs.
Pour utiliser Dagger dans une application Android avec des modules de fonctionnalité, utilisez des dépendances de composants afin d'accéder aux dépendances fournies par l'élément
ApplicationComponent
défini dans le moduleapp
.