פרויקט עם כמה מודולים של Gradle נקרא 'פרויקט עם מודולים מרובים'.
בפרויקט עם מודולים מרובים שנשלח כ-APK יחיד ללא תכונה
לעיתים קרובות יש מודול app שיכול להיות תלוי
של הפרויקט ומודול base או core,
הם תלויים בדרך כלל. המודול app מכיל בדרך כלל את
הכיתה Application, ואילו base
מכיל את כל המחלקות המשותפות שמשותפות בכל המודולים בפרויקט.
המודול app הוא מקום טוב להצהיר עליו על רכיב האפליקציה (עבור
למשל, ApplicationComponent בתמונה שלמטה) שיכול לספק אובייקטים
שרכיבים אחרים עשויים להזדקק להם, וכן יחידות הסינגלונים של האפליקציה. בתור
למשל, מחלקות כמו OkHttpClient, כלי ניתוח JSON, כלי גישה למסד הנתונים שלך,
או SharedPreferences אובייקטים שעשויים להיות מוגדרים במודול core,
יסופק על ידי ה-ApplicationComponent שהוגדר במודול app.
במודול app יכולים להיות גם רכיבים אחרים עם תוחלת חיים קצרה יותר.
לדוגמה: UserComponent עם הגדרות ספציפיות למשתמש
(כמו UserSession) אחרי התחברות.
במודולים השונים של הפרויקט, אפשר להגדיר לפחות אחד רכיב משנה שיש לו לוגיקה ספציפית למודול הזה, כפי שמוצג באיור 1.
איור 1. דוגמה לתרשים של צלבון פרויקט עם מודולים מרובים
לדוגמה, במודול login, יכול להיות שיש LoginComponent
בהיקף עם הערה מותאמת אישית מסוג @ModuleScope שיכולה לספק אובייקטים משותפים
לאותה תכונה, כמו LoginRepository. בתוך המודול הזה, תוכלו גם
יש רכיבים אחרים שתלויים ב-LoginComponent עם ערך מותאם אישית אחר
לדוגמה, @FeatureScope עבור LoginActivityComponent או
TermsAndConditionsComponent, עם אפשרות להיקף לוגיקה יותר ספציפית לתכונות
כמו ViewModel אובייקטים.
במודולים אחרים, כמו Registration, תראו הגדרה דומה.
ככלל בפרויקט עם מודולים מרובים, המודולים של אותה רמה לא צריכים להיות תלויים זה בזה. אם כן, חשבו אם הלוגיקה המשותפת (יחסי התלות שביניהם) צריכים להיות חלק ממודול ההורה. אם כן, לשנות את הקוד (Refactoring) כדי להעביר את הכיתות למודול ההורה. אם לא, צרו מודול חדש שמרחיב את מודול ההורה וכולל את שני המודולים המקוריים מודול חדש.
באופן כללי, מומלץ ליצור רכיב במקרים הבאים:
צריך לבצע החדרת שדה, כמו ב-
LoginActivityComponent.צריך להגדיר את היקף האובייקטים, כמו ב-
LoginComponent.
אם אף אחת מהמארזים האלה לא רלוונטית ואתם צריכים לומר ל-Dagger איך לספק
לאובייקטים מסוימים מהמודול הזה, ליצור מודול Dagger ולחשוף אותו באמצעות @Provides או
@Binds שיטות אם לא ניתן לבצע החדרת בנייה למחלקות האלה.
הטמעה באמצעות רכיבי המשנה של Dagger
בדף המסמך שימוש ב-Dagger באפליקציות ל-Android מוסבר איך ליצור ולהשתמש
של רכיבי המשנה. עם זאת, לא ניתן להשתמש באותו קוד,
המודולים של התכונות לא מכירים את המודול 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. כדי שהיא תפעל, עליך להגדיר ממשק בתכונה
המודול שמספק FeatureComponent שנדרש ל-MyApplication
ליישם.
בדוגמה הבאה אפשר להגדיר ממשק של LoginComponentProvider.
שמספק LoginComponent במודול login לתהליך ההתחברות:
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
לייצוג של המבנה של המודולים.
איור 2. דוגמה לתרשים של צלבון פרויקט עם מודולים של מאפיינים
ב-Dagger, הרכיבים צריכים לדעת על רכיבי המשנה שלהם. המידע הזה
נכלל במודול Dagger שנוסף לרכיב ההורה (למשל
מודול SubcomponentsModule בשימוש ב-Dagger באפליקציות ל-Android).
לצערי, המשמעות של היפוך יחסי בין האפליקציה
מודול המאפיין, רכיב המשנה לא גלוי מהמודול app כי
הוא לא בנתיב ה-build. לדוגמה, LoginComponent מוגדר
מודול התכונה login לא יכול להיות רכיב משנה של
השדה ApplicationComponent הוגדר במודול app.
ל-Dagger יש מנגנון שנקרא יחסי תלות של רכיבים, שניתן להשתמש בו כדי כדי לפתור את הבעיה. במקום שרכיב הצאצא יהיה רכיב משנה של רכיב ההורה, רכיב הצאצא תלוי ברכיב ההורה. ב- כי אין יחסי הורה-ילד; הרכיבים תלויים בגורמים אחרים כדי לקבל יחסי תלות מסוימים. הרכיבים צריכים לחשוף סוגים מהתרשים לרכיבים תלויים כדי לצרוך אותם.
לדוגמה: מודול של תכונה בשם login רוצה ליצור
LoginComponent שתלויה ב-AppComponent שזמינים
app מודול Gradle.
בהמשך מוצגות ההגדרות של הכיתות ושל AppComponent שהן חלק מ-
מודול 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 { ... }
במודול login (GRid) שכולל את מודול המסלול app, יש
LoginActivity שנדרשת כדי להחדיר מופע 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 יש תלות ב-UserRepository הזמינה ו
בהיקף של AppComponent. בואו ניצור LoginComponent שמבוסס על
AppComponent כדי להחדיר 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 מציין תלות ב-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 של המחלקות האלה לא תומכים בהזרקה של constructor.
כדי להשתמש ב-Dagger באפליקציה ל-Android עם מודולים של תכונות, צריך להשתמש ברכיב של יחסי התלות שהם מסוגלים לגשת ליחסי התלות שמסופקים
ApplicationComponentמוגדר במודולapp.