Dependency injection in Android - 5. Sử dụng Dagger trong ứng dụng nhiều modules
Bài đăng này đã không được cập nhật trong 4 năm
1. Tổng quan
Một project có nhiều module Gradle được gọi là mutil-module project. Trong một mutil-module project, vận chuyển file APK không có dynamic feature, thông thường có một app module có thể phụ thuộc hầu hết các module khác và một base module hoặc core module mà phần còn lại của các module thường phụ thuộc vào. Module App thường chứa các lớp ứng dụng của chúng ta, trong khi Base Module chứa các lớp chung được chia sẻ trong dự án
Module App là nơi thích hợp để khai báo các thành phần ứng dụng (ví dụ như ApplicationComponent như hình ảnh bên dướ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. Ví dụ như các lớp OkHttpClient, JSON parser, trình truy cập Database, hoặc SharedPreferences có thể được xác định trong Core Module, sẽ được cung cấp bởi ApplicationComponent được xác định trong Module App.
Trong module app ta có thể có các components với sự sống ngắn hơn. Một ví dụ đó là UserComponents với cấu hình đặc biệt cho Người dùng (như UserSession) sau khi đăng nhập
Trong các module khác nhau của dự án, ta có thể định nghĩa ít nhất một thành phần con có logic cụ thể cho module đó như hình
Ví dụ: Trong module login ta có thể có một LoginComponent có phạm vi với anotation @ModuleScope, nó cung cấp các đối tượng phổ biến cho tính năng đó, chẳn hạn như LoginRepository. Bên trong module đó ta cũng có thể có các thành phần khác phụ thuộc vào LoginComponent với phạm vi tùy chỉnh khác. Ví dụ @FeatureScope cho LoginActivityComponent hoặc một TermsAndConditionsComponent nơi ta có thể giới hạn nhiều logic đặc trưng hơn như đối tượng ViewModel
Đối với các module khác ví dụ như Registration, ta sẽ có mộ thiết lập tương tự.
Một quy tắc chung cho một project đa module đó là các module cùng cấp không nên phụ thuộc lẫn nhau. Nếu làm như vậy hãy xem xét liệu logic chia sẻ (phần phụ thuộc) có nên là một phần của parent module hay không. Nếu không hãy tạo một module mới kế thừa parent module và 2 module ban đầu kế thừa module mới này.
Một cách thực hiện tốt nhất, ta thường sẽ tạo một thành phần trong module ở các trường hợp sau:
- Ta cần thực hiện field injection, như với LoginActivityComponent
- Ta cần phải giới hạn các đối tượng, như với LoginComponent
Nếu cả hai trường hợp này bạn không sử dụng và ta cần cho Dagger biết cách cung cấp các đối tượng từ module đó, tạo và hiển thị moduel Dagger bằng các phương thức @Provider hoặc @Binds nếu không thể thực hiện construction injection cho các lớp đó.
2. Implement các thành phần con của Dagger
Ví dụ nếu ta nghĩ về một luồng Login thông thường, và code ta có ở bài trước sẽ không biên dịch được nữa
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 để module login không biết về MyApplication hoặc appComponent. Để làm cho nó hoạt động, bạn cần xác định một interface trong feature module, cái mà cung cấp một FeatureComponent mà MyApplication cần triển khai.
Trong ví dụ sau, ta có thể xác định interface LoginComponentProvider cung cấp LoginComponent trong login module cho luồng Login
public interface LoginComponentProvider {
public LoginComponent provideLoginComponent();
}
Bây giờ LoginActivity sẽ sử dụng interface đó thay vì đoạn code ở trên
public class LoginActivity extends Activity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
loginComponent = ((LoginComponentProvider) getApplicationContext())
.provideLoginComponent();
loginComponent.inject(this);
...
}
}
Bây giờ MyApplication cần triển khai interface đó và thực hiện các phương thức cần thiết
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 ta có thể sử dụng các thành phần con của Dagger trong dự án đa module. Với dynamic feature module, giải pháp khác nhau tùy thuộc vào cách các module phụ thuộc lẫn nhau.
3. Phụ thuộc thành phần với các dynamic feature module
Với dynamic feature module, cách các module thường vụ thuộc vào nhau được đảo ngược. Thay vì app module, bao gồm cả feature modules, dynamic feature modules phụ thuộc vào app module Trong Dagger, các component cần biết về các subcomponent của chúng. Thông tin này bao gồm một Dagger module được add vào một parent component
Thật không may với sự đảo ngược phụ thuộc giữa app module và dynamic feature module, subcomponent không thể nhìn thấy từ app module bởi vì nó không năm trong build path. Ví dụ LoginComponent được xác định trong dynamic feature module không thể là subcomponent của ApplicationComponent define trong module app
Dagger có một cơ chế gọi là component dependecies mà ta có thể sử dụng để giải quyết vấn đề này. Thay vì child component là subcomponent của parent component thì child phụ thuộc vào parent component. Cùng với đó không có mối quan hệ parent - child; bây giờ các components phụ thuộc từ nơi khác để có được sự phụ thuộc nhất định.
Phía dưới là các định nghĩa cho các class và AppComponent là một phần của app Gradle module.
// 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 { ... }
Nếu login gradle module bao gồm app module, ta có một LoginActivity cần một thể hiện LoginViewmodel để được inject.
// 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ụ thuộc vào UserRepository và nằm trong phạm vi AppComponent. Hãy tạo một LoginComponent phụ thuộc vào AppComponent để inject LoginActivity.
// 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ụ thuộc vào vào AppComponent bằng cách add nó vào tham số của component anotation. Bởi vì LoginActivity sẽ được inject bởi Dagger, add thêm phương thức inject() vào interface.
Khi tạo một LoginComponent một phiên bản của AppComponent cần được truyền vào. Sử dụng component factory để thực hiện
@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);
}
Bây giờ LoginActivity có thể tạo một thể hiện của LoginComponent và gọi phương thức inject()
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 nó từ AppComponent, AppComponent cần phải lộ trong interface.
@Singleton
@Component
public interface AppComponent {
UserRepository userRepository();
}
Các quy tắc về phạm vi với các component phụ thuộc hoạt động theo cùng một cách với các thành phần con. Bởi vì LoginComponent sử dụng một phiên bản của AppComponent, chúng không thể sử dụng cùng một scope anotation
Nếu bạn muốn scope LoginViewModel thành LoginComponent. Bạn sẽ làm điều đó như trước đây bằng cách sử dụng custom @ActivityScope anotation
@ActivityScope
@Component(dependencies = AppComponent.class)
public interface LoginComponent { ... }
@ActivityScope
public class LoginViewModel {
private final UserRepository userRepository;
@Inject
public LoginViewModel(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
4. Best practices
- ApplicationComponent phải luôn nằm trong module app
- Tạo các thành phần Dagger trong các module nếu ta cần thực hiện field inject trong module đó hoặc bạn cần giới hạn đối tượng cho một luồng cụ thể trong ứng dụng
- Đối với các Gradle module là các utilities hoặc helpers và không cần xây dựng một graph (đó là lý do tại sao cần một thành phần Dagger), tạo và hiện thị các module Dagger với @Providers và @Bind của các lớp không hỗ trợ constructor injection
- Để sử dụng Dagger trong Android với dynamic feature modules, sử dụng các component dependencies để truy cập các phụ thuộc được cung cấp bởi ApplicationComponent được xác định trong app module
5. Tổng kết
Qua bài viết này mình đã giới thiệu cơ bản cách sử dụng Dagger trong một ứng dụng có nhiều modules. Chắc chắn bài viết mang tính hàn lâm và còn khá khó hiểu. Các bạn cố gắng thực hành và làm nhiều thì sẽ rõ hơn thôi. Cám ơn đã theo dõi bài viết. Để thực hiện được bài viết này mình đã tham khảo tài liệu của Google Android
All rights reserved