含有多個 Gradle 模組的專案稱為多模組專案。如果是以單一 APK 形式 (不含任何功能模組) 的多模組,通常為可依附於專案大部分模組的 app
模組及其餘模組經常依附的 base
或 core
。app
模組通常包含您的 Application
類別,而 base
模組則包含專案中所有模組共用的所有通用類別。
您可以使用 app
模組宣告應用程式元件 (例如下圖中的 ApplicationComponent
),藉此提供其他元件可能需要的物件和應用程式的單例模式。舉例來說,OkHttpClient
模組中定義的 ApplicationComponent
會提供 OkHttpClient
等類別、JSON 剖析器、資料庫的存取子或可能定義的 SharedPreferences
物件。app
core
在 app
模組中,您還可以有壽命較短的其他元件。例如,在登入後,具有使用者專屬設定的 UserComponent
(例如 UserSession
)。
在專案的不同模組中,您可以定義至少一項具有該模組特定邏輯的子元件,如圖 1 所示。
舉例來說,在 login
模組中,您可以將 LoginComponent
範圍指定為自訂 @ModuleScope
註解,以提供該功能通用的物件 (例如 LoginRepository
)。在該模組中,您也可以有其他依附於 LoginComponent
且包含不同的自訂範圍的元件,舉例來說 @FeatureScope
(適用於 LoginActivityComponent
) 或 TermsAndConditionsComponent
(可以在這裡設定更多特定功能的邏輯的範圍,例如 ViewModel
物件)。
至於其他模組 (例如 Registration
),則需要進行類似的設定。
多模組專案的一般規則是,相同層級的模組不應該彼此相依。如果可以,請考慮該共用邏輯 (兩者之間的依附元件) 是否應屬於父項模組。如果是這種情況,重構以將類別移至父項模組;否則,請建立擴充父項模組的新模組,並同時讓兩個原始模組擴充新的模組。
最佳做法為在以下例子的模組中建立元件:
您必須執行欄位插入,就像
LoginActivityComponent
一樣。請務必設定物件範圍,與
LoginComponent
相同。
如果上述兩者均不適用,您需要告知 Dagger 如何提供該模組的物件、建立和顯示 Dagger 模組 @Provides
或 @Binds
方法 (如果建構插入功能不適用於這些類別)。
Dagger 子元件實作
「在 Android 應用程式中使用 Dagger」文件頁面說明如何建立及使用子元件。不過,您無法使用相同的程式碼,因為功能模組並不知道 app
模組。舉例來說,如果您考慮一般的登入流程和我們在前一頁中提到的程式碼,就不會再編譯:
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); ... } }
原因在於 login
模組不知道 MyApplication
和 appComponent
。為了讓功能順利運作,您必須在功能模組中定義介面,並提供 MyApplication
需要實作的 FeatureComponent
。
如以下範例中,您可以定義 LoginComponentProvider
介面,為登入流程的 login
模組中提供 LoginComponent
:
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
現在,LoginActivity
會使用該介面,而非上述定義的程式碼片段:
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
必須實作該介面,並實作必要方法:
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(); } }
如何在多模組專案中使用 Dagger 子元件。使用功能模組時,解決方案會因模組彼此的依賴方式而異。
功能模組的元件依附元件
使用功能模組時,模組間依附通常會依循反轉方式。功能模組不會使用包含功能模組的 app
模組,而是依附 app
模組。請參閱圖 2 以表示模組結構。
在 Dagger 中,元件需要瞭解其子元件。這些資訊包含在已新增至父項元件的 Dagger 模組中 (例如在 Android 應用程式中使用 Dagger中的 SubcomponentsModule
模組)。
遺憾的是,由於應用程式和功能模組之間存在反向依附元件,app
模組不會顯示該子元件,因為子元件不在建構路徑中。舉例來說,在 login
功能模組中定義的 LoginComponent
不能是 app
模組中定義的 ApplicationComponent
子元件。
Dagger 有個機制稱為元件依附元件,您可以使用這個機制來解決這個問題。子項元件不是父項元件的子元件,而是取決於父項元件。因此,沒有上下層關係,現在,元件取決於其他元件,以取得特定依附元件。元件必須從圖表中呈現類型,相依元件才能取用。
例如,名為 login
的功能模組想要建構 LoginComponent
,取決於 app
Gradle 模組提供的 AppComponent
。
以下是類別與 app
Gradle 模組的 AppComponent
的定義:
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 { ... }
在包含 app
Gradle 模組的 login
Gradle 模組中,您有一個需要插入 LoginViewModel
執行個體的 LoginActivity
:
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
有依附於 UserRepository
且範圍限制於 AppComponent
的依附元件。讓我們建立依附於 AppComponent
來插入 LoginActivity
的 LoginComponent
:
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
指定 AppComponent
的依附元件,透過將其新增至元件註解的依附元件參數。由於 LoginActivity
將由 Dagger 插入,因此請將 inject()
方法新增至介面。
建立 LoginComponent
時,必須傳入 AppComponent
的執行個體。請使用元件工廠進行這項操作:
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
可以建立 LoginComponent
執行個體,並呼叫 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
取決於 UserRepository
;為了讓 LoginComponent
能夠從 AppComponent
存取,AppComponent
必須在介面中公開:
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
依附元件的限定範圍運作方式與子元件相同。由於 LoginComponent
使用 AppComponent
的執行個體,因此不能使用相同的範圍註解。
如要將 LoginViewModel
範圍限制為 LoginComponent
,方法與先前使用自訂 @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; } }
最佳做法
ApplicationComponent
應一律位於app
模組中。如果您需要在該模組中執行欄位插入,或需要針對應用程式的特定流程設定物件範圍,請在模組中建立 Dagger 元件。
針對設計為公用程式或輔助程式且不需要建構圖形的 Gradle 模組 (這就是您需要 Dagger 元件的原因),請在不支援建構函式插入的類別中使用 @Provides 和 @Binds 建立並公開公開的 Dagger 模組。
如要在具有功能模組的 Android 應用程式中使用 Dagger,請使用元件依附元件,以存取
app
模組中定義的ApplicationComponent
提供的依附元件。