פרויקט עם כמה מודולים של Gradle נקרא 'פרויקט עם מודולים מרובים'.
בפרויקט עם מודולים מרובים שנשלח כ-APK יחיד ללא תכונה
לעיתים קרובות יש מודול app
שיכול להיות תלוי
של הפרויקט ומודול base
או core
,
הם תלויים בדרך כלל. המודול app
מכיל בדרך כלל את
הכיתה Application
, ואילו base
מכיל את כל המחלקות המשותפות שמשותפות בכל המודולים בפרויקט.
המודול app
הוא מקום טוב להצהיר עליו על רכיב האפליקציה (עבור
למשל, ApplicationComponent
בתמונה שלמטה) שיכול לספק אובייקטים
שרכיבים אחרים עשויים להזדקק להם, וכן יחידות הסינגלונים של האפליקציה. בתור
למשל, מחלקות כמו OkHttpClient
, כלי ניתוח JSON, כלי גישה למסד הנתונים שלך,
או SharedPreferences
אובייקטים שעשויים להיות מוגדרים במודול core
,
יסופק על ידי ה-ApplicationComponent
שהוגדר במודול app
.
במודול app
יכולים להיות גם רכיבים אחרים עם תוחלת חיים קצרה יותר.
לדוגמה: UserComponent
עם הגדרות ספציפיות למשתמש
(כמו UserSession
) אחרי התחברות.
במודולים השונים של הפרויקט, אפשר להגדיר לפחות אחד רכיב משנה שיש לו לוגיקה ספציפית למודול הזה, כפי שמוצג באיור 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
לייצוג של המבנה של המודולים.
ב-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
.