Un progetto con più moduli Gradle è noto come progetto multimodulo.
In un progetto multimodulo che viene fornito come singolo APK senza funzionalità
di moduli, è comune avere un modulo app
che può dipendere dalla maggior parte
moduli del tuo progetto e un modulo base
o core
che il resto del
da cui dipendono generalmente i moduli. Il modulo app
in genere contiene i tuoi
Application
, mentre base
contiene tutti i corsi comuni condivisi tra tutti i moduli del tuo progetto.
Il modulo app
è ideale per dichiarare il componente dell'applicazione (ad
(ad esempio ApplicationComponent
nell'immagine sotto) che possono fornire oggetti
di cui potrebbero avere bisogno gli altri componenti,
così come i singleton della tua app. Come
ad esempio classi come OkHttpClient
, parser JSON, funzioni di accesso per il database,
o SharedPreferences
oggetti che possono essere definiti nel modulo core
,
verrà fornito dal ApplicationComponent
definito nel modulo app
.
Nel modulo app
potresti anche avere altri componenti con una durata minore.
Un esempio potrebbe essere un UserComponent
con una configurazione specifica per l'utente
(come UserSession
) dopo un accesso.
Nei diversi moduli del progetto, puoi definire almeno uno con una logica specifica per il modulo, come si vede nella Figura 1.
Ad esempio, in un modulo login
, potresti avere un LoginComponent
con ambito con un'annotazione @ModuleScope
personalizzata che può fornire oggetti comuni
a quella funzionalità, ad esempio LoginRepository
. All'interno di questo modulo,
avere altri componenti che dipendono da un LoginComponent
con un'impostazione
ambito, ad esempio @FeatureScope
per LoginActivityComponent
o
TermsAndConditionsComponent
in cui puoi definire l'ambito di una logica più specifica per le funzionalità
ad esempio ViewModel
oggetti.
Per gli altri moduli, ad esempio Registration
, viene utilizzata una configurazione simile.
Una regola generale per un progetto multimodulo è che moduli dello stesso livello non devono dipendere l'uno dall'altro. In tal caso, valuta se la logica condivisa (le dipendenze tra loro) devono fare parte del modulo padre. Se sì, eseguire il refactoring per spostare le classi nel modulo padre; In caso contrario, crea un nuovo modulo che estende il modulo principale e fa sì che entrambi i moduli originali estendono nuovo modulo.
Come best practice, in genere si crea un componente in una nei seguenti casi:
Devi eseguire l'inserimento del campo, come per
LoginActivityComponent
.Devi definire l'ambito degli oggetti, come per
LoginComponent
.
Se nessuna di queste condizioni è applicabile e devi dire a Dagger come fornire
oggetti da quel modulo, crea ed esponi un modulo Dagger con @Provides
o
@Binds
metodi se l'inserimento dei lavori non è possibile per queste classi.
Implementazione con i sottocomponenti di Dagger
La pagina del documento Utilizzare Dagger nelle app Android spiega come creare e utilizzare
i componenti secondari. Non puoi, però, utilizzare lo stesso codice
i moduli delle funzionalità non conoscono il modulo app
. Ad esempio, se ritieni
su un tipico flusso di accesso e sul codice che abbiamo nella pagina precedente,
compila ancora:
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); ... } }
Il motivo è che il modulo login
non sa di MyApplication
né
appComponent
. Affinché funzioni, devi definire un'interfaccia nella caratteristica
che fornisce un FeatureComponent
di cui MyApplication
ha bisogno
da implementare.
Nell'esempio seguente, puoi definire un'interfaccia LoginComponentProvider
che fornisce un LoginComponent
nel modulo login
per il flusso di accesso:
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
Ora l'LoginActivity
utilizzerà questa interfaccia al posto dello snippet di codice
definita sopra:
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); ... } }
A questo punto, MyApplication
deve implementare l'interfaccia e implementare
metodi richiesti:
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(); } }
Ecco come puoi utilizzare i sottocomponenti di Dagger in un progetto multimodulo. Con i moduli delle funzionalità, la soluzione è diversa per via del fatto che i moduli dipendono l'uno dall'altro.
Dipendenze dei componenti con moduli delle funzionalità
Con i moduli delle caratteristiche, il modo in cui i moduli dipendono solitamente
invertiti l'uno con l'altro. Al posto del modulo app
include la funzionalità
, i moduli delle funzionalità dipendono dal modulo app
. Vedi figura 2
per una rappresentazione di come sono strutturati i moduli.
In Dagger, i componenti devono conoscere i relativi sottocomponenti. Queste informazioni
è incluso in un modulo Dagger aggiunto al componente padre (come
modulo SubcomponentsModule
in Utilizzo di Dagger nelle app Android).
Purtroppo, con la dipendenza invertita tra l'app e
modulo delle funzionalità, il sottocomponente non è visibile nel modulo app
perché
ma non nel percorso di build. Ad esempio, un valore LoginComponent
definito in
Il modulo delle funzionalità login
non può essere un sottocomponente di
ApplicationComponent
definita nel modulo app
.
Dagger ha un meccanismo chiamato dipendenze dei componenti che puoi utilizzare per per risolvere il problema. Anziché essere un componente secondario del il componente principale, il componente figlio dipende da quest'ultimo. Con che non esista una relazione genitore-figlio; Ora i componenti dipendono da altri per ottenere determinate dipendenze. I componenti devono esporre i tipi del grafico per consentire ai componenti dipendenti di utilizzarli.
Ad esempio: un modulo di funzionalità chiamato login
vuole creare una
LoginComponent
che dipende dai AppComponent
disponibili in
app
modulo Gradle.
Di seguito sono riportate le definizioni delle classi e dei AppComponent
che fanno parte di
il modulo 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 { ... }
Nel modulo di laurea login
che include il modulo di livello app
, hai un
LoginActivity
che richiede l'inserimento di un'istanza 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
ha una dipendenza da UserRepository
disponibile e
con ambito AppComponent
. Creiamo un LoginComponent
che dipende da
AppComponent
per inserire 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
specifica una dipendenza su AppComponent
aggiungendola al
delle dipendenze dell'annotazione del componente. Poiché LoginActivity
inserito da Dagger, aggiungi il metodo inject()
all'interfaccia.
Quando crei un LoginComponent
, un'istanza di AppComponent
deve essere
passato. Per farlo, utilizza il data di fabbrica del componente:
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); }
Ora LoginActivity
può creare un'istanza di LoginComponent
e chiamare il metodo
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
dipende da UserRepository
; e per LoginComponent
possa accedervi da AppComponent
, AppComponent
deve esporlo in
nell'interfaccia:
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
Le regole di definizione dell'ambito con componenti dipendenti funzionano come quelle
i componenti secondari. Poiché LoginComponent
utilizza un'istanza di AppComponent
,
non possono utilizzare
la stessa annotazione di ambito.
Se volessi applicare l'ambito LoginViewModel
a LoginComponent
, devi utilizzare
che hai fatto in precedenza utilizzando l'annotazione @ActivityScope
personalizzata.
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; } }
Best practice
ApplicationComponent
deve essere sempre presente nel moduloapp
.Crea componenti Dagger nei moduli se devi eseguire l'inserimento dei campi in quel modulo o devi definire l'ambito degli oggetti per un flusso specifico la tua applicazione.
Per i moduli Gradle che sono pensati come utilità o helper e che non richiedono per creare un grafico (per questo ti serve un componente Dagger), creare ed esporre di moduli Dagger pubblici con metodi @Provides e @Binds di quelle classi che non supportano l'inserimento del costruttore.
Per utilizzare Dagger in un'app Android con moduli di funzionalità, usa il componente alle dipendenze per poter accedere alle dipendenze fornite
ApplicationComponent
definita nel moduloapp
.