Một dự án có nhiều mô-đun Gradle được gọi là một dự án đa mô-đun.
Trong một dự án nhiều mô-đun được gửi dưới dạng một tệp APK duy nhất không có mô-đun tính năng, thông thường sẽ có một mô-đun app
có thể phụ thuộc vào hầu hết các mô-đun của dự án và một mô-đun base
hoặc core
mà phần còn lại của các mô-đun thường phụ thuộc vào. Mô-đun app
thường chứa lớp Application
, trong khi mô-đun base
chứa tất cả các lớp chung được chia sẻ trên tất cả các mô-đun trong dự án của bạn.
Mô-đun app
là vị trí phù hợp để khai báo thành phần ứng dụng của bạn (ví dụ như ApplicationComponent
trong hình bên dưới), nơi có thể cung cấp các đối tượng mà các thành phần khác có thể cần cũng như các singleton của ứng dụng. Chẳng hạn như các lớp OkHttpClient
, trình phân tích cú pháp JSON, trình truy cập cho cơ sở dữ liệu của bạn, hoặc các đối tượng SharedPreferences
có thể được xác định trong mô-đun core
, sẽ được cung cấp bởi ApplicationComponent
đã xác định trong mô-đun app
.
Trong mô-đun app
, bạn cũng có thể sở hữu các thành phần khác có thời gian tồn tại ngắn hơn.
Ví dụ như UserComponent
với cấu hình dành riêng cho người dùng (như UserSession
) sau khi đăng nhập.
Trong các mô-đun khác nhau của dự án, bạn có thể xác định ít nhất một thành phần phụ có logic dành riêng cho mô-đun đó như minh họa trong hình 1.
Ví dụ: trong mô-đun login
, bạn có thể có phạm vi LoginComponent
với chú thích @ModuleScope
tuỳ chỉnh có thể cung cấp các đối tượng chung cho tính năng đó, chẳng hạn như LoginRepository
. Bên trong mô-đun đó, bạn cũng có thể có các thành phần khác phụ thuộc vào LoginComponent
với một phạm vi tuỳ chỉnh khác, ví dụ: @FeatureScope
cho LoginActivityComponent
, hoặc TermsAndConditionsComponent
nơi bạn có thể đặt phạm vi logic cụ thể hơn cho tính năng, chẳng hạn như các đối tượng ViewModel
.
Bạn cũng cần thiết lập tương tự đối với các mô-đun khác như Registration
.
Quy tắc chung cho một dự án nhiều mô-đun là các mô-đun cùng cấp không được phụ thuộc lẫn nhau. Nếu có, hãy cân nhắc xem logic dùng chung (các phần phụ thuộc giữa chúng) có phải là một phần của mô-đun mẹ hay không. Nếu có, hãy tái cấu trúc để di chuyển các lớp vào mô-đun mẹ; nếu không, hãy tạo một mô-đun mới mở rộng mô-đun mẹ và để cả hai mô-đun ban đầu mở rộng mô-đun mới.
Cách tốt nhất là bạn nên tạo một thành phần trong một mô-đun ở các trường hợp sau:
Bạn cần thực hiện chèn trường, như với
LoginActivityComponent
.Bạn cần đặt phạm vi cho các đối tượng, như với
LoginComponent
.
Nếu cả hai trường hợp này đều không áp dụng được và bạn cần cho Dagger biết cách cung cấp các đối tượng từ mô-đun đó, hãy tạo và hiển thị một mô-đun Dagger bằng các phương thức @Provides
hoặc @Binds
nếu không thể chèn nội dung xây dựng cho các lớp đó.
Triển khai bằng các thành phần phụ của Dagger
Trang tài liệu Sử dụng Dagger trong các ứng dụng Android trình bày cách tạo và sử dụng các thành phần phụ. Tuy nhiên, bạn không thể sử dụng cùng một mã vì
các mô-đun tính năng không biết về mô-đun app
. Chẳng hạn như nếu bạn nghĩ về một luồng Đăng nhập thông thường và mã chúng tôi có ở trang trước, thì quy trình này sẽ không biên dịch nữa:
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); ... } }
Lý do là mô-đun login
không biết về MyApplication
và
appComponent
. Để làm cho nó hoạt động, bạn cần xác định một giao diện trong mô-đun tính năng cung cấp FeatureComponent
mà MyApplication
cần triển khai.
Ở ví dụ sau, bạn có thể xác định giao diện LoginComponentProvider
cung cấp LoginComponent
trong mô-đun login
của luồng Đăng nhập:
Kotlin
interface LoginComponentProvider { fun provideLoginComponent(): LoginComponent }
Java
public interface LoginComponentProvider { public LoginComponent provideLoginComponent(); }
Giờ thì LoginActivity
sẽ sử dụng giao diện đó thay vì đoạn mã được xác định ở trên:
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); ... } }
Hiện tại, MyApplication
cần triển khai giao diện đó cùng với các phương thức bắt buộc:
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(); } }
Đây là cách bạn có thể sử dụng các thành phần phụ của Dagger trong một dự án nhiều mô-đun. Giải pháp này khác với các mô-đun tính năng do cách các mô-đun phụ thuộc lẫn nhau.
Các phần phụ thuộc của thành phần với mô-đun tính năng
Với mô-đun tính năng, cách các mô-đun thường phụ thuộc vào nhau bị đảo ngược. Thay vì mô-đun app
bao gồm các mô-đun tính năng, các mô-đun tính năng lại phụ thuộc vào mô-đun app
. Xem hình 2 để biết cách cấu trúc các mô-đun.
Trong Dagger, các thành phần cần biết về các thành phần phụ của chúng. Thông tin này có trong mô-đun Dagger được thêm vào thành phần mẹ (như mô-đun SubcomponentsModule
ở bài viết Sử dụng Dagger trong các ứng dụng Android).
Thật không may, với phần phụ thuộc bị đảo ngược giữa ứng dụng và mô-đun tính năng, thành phần phụ không thể hiển thị ở mô-đun app
vì nó không nằm trong đường dẫn của bản dựng. Ví dụ như LoginComponent
đã xác định trong mô-đun tính năng login
không thể là thành phần phụ của ApplicationComponent
được xác định trong mô-đun app
.
Dagger có một cơ chế được gọi là phần phụ thuộc của thành phần mà bạn có thể dùng để giải quyết vấn đề này. Thay vì thành phần con là một thành phần phụ của thành phần mẹ, thì thành phần con phụ thuộc vào thành phần mẹ. Theo đó, không có mối quan hệ mẹ con; hiện tại các thành phần phụ thuộc vào thành phần khác để có được phần phụ thuộc nhất định. Các thành phần cần hiển thị các loại có trong biểu đồ để các thành phần phụ thuộc sử dụng chúng.
Ví dụ như một mô-đun tính năng có tên là login
muốn tạo một LoginComponent
phụ thuộc vào AppComponent
có sẵn trong mô-đun Gradle app
.
Dưới đây là định nghĩa cho các lớp và AppComponent
thuộc
mô-đun 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 { ... }
Trong mô-đun gradle login
bao gồm mô-đun gradle app
, bạn có một
LoginActivity
cần chèn vào bản sao của 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
có một phần phụ thuộc trên UserRepository
có sẵn và
trong phạm vi AppComponent
. Hãy tạo một LoginComponent
phụ thuộc vào AppComponent
để chèn LoginActivity
vào:
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
chỉ định một phần phụ thuộc trên AppComponent
bằng cách thêm phần phụ thuộc đó vào tham số phần phụ thuộc của chú thích thành phần. Vì LoginActivity
sẽ được Dagger chèn vào, hãy thêm phương thức inject()
vào giao diện.
Khi tạo LoginComponent
, một bản sao của AppComponent
cần phải được truyền vào. Sử dụng nhà máy thành phần để thực hiện việc này:
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
hiện có thể tạo một bản sao của LoginComponent
và gọi phương thức 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
phụ thuộc vào UserRepository
; và để LoginComponent
có thể truy cập vào từ AppComponent
, AppComponent
cần hiển thị trong giao diện của nó:
Kotlin
@Singleton @Component interface AppComponent { fun userRepository(): UserRepository }
Java
@Singleton @Component public interface AppComponent { UserRepository userRepository(); }
Quy tắc xác định phạm vi với các thành phần phụ thuộc hoạt động theo cách tương tự như với các thành phần phụ. Vì LoginComponent
sử dụng bản sao của AppComponent
, nên chúng không thể sử dụng cùng một chú thích phạm vi.
Nếu muốn chuyển phạm vi LoginViewModel
sang LoginComponent
, bạn cần thực hiện như trước đây bằng cách sử dụng chú thích @ActivityScope
tuỳ chỉnh.
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; } }
Các phương pháp hay nhất
ApplicationComponent
phải luôn nằm trong mô-đunapp
.Tạo thành phần Dagger trong các mô-đun nếu bạn cần thực hiện thao tác chèn trường trong mô-đun đó, hoặc bạn cần xác định phạm vi các đối tượng cho một luồng cụ thể của ứng dụng.
Đối với các mô-đun Gradle là tiện ích hoặc trình trợ giúp và không cần tạo biểu đồ (đó là lý do bạn cần một thành phần Dagger), hãy tạo và hiển thị các mô-đun Dagger công khai với phương thức @Provides và @Binds của những lớp không hỗ trợ chèn hàm khởi tạo.
Để sử dụng Dagger trong ứng dụng Android với các mô-đun tính năng, hãy sử dụng các phần phụ thuộc của thành phần để có thể truy cập vào các phần phụ thuộc do
ApplicationComponent
cung cấp đã xác định trong mô-đunapp
.