החדרה ידנית של תלות או שירות שירותי האיתור באפליקציה ל-Android עלולים להיות בעייתיים, בהתאם לגודל פרויקט. אפשר להגביל את מורכבות הפרויקט ככל שהוא יתרחב באמצעות Dagger לניהול יחסי התלות.
Dagger יוצר באופן אוטומטי קוד שמחקה את הקוד שאחרת בכתב יד. מאחר שהקוד נוצר בזמן הידור (compile), ניתן לעקוב אחריו. והם בעלי ביצועים טובים יותר מאשר פתרונות אחרים שמבוססים על השתקפות, Guice.
יתרונות השימוש ב-Dagger
Dagger מאפשר לכם לכתוב קוד מסובך ומעורפל יותר מפני שגיאות על ידי:
יצירת הקוד של
AppContainer
(תרשים האפליקציות) שיצרת באופן ידני הטמענו בקטע ה-DI הידני.יצירת מפעלים עבור הכיתות הזמינות בתרשים האפליקציה. הזה הוא האופן שבו יחסי התלות מתקיימים באופן פנימי.
להחליט אם להשתמש שוב בתלות או ליצור מכונה חדשה באמצעות שימוש בהיקפים.
יצירת קונטיינרים לתהליכים ספציפיים כפי שיצרתם בתהליך ההתחברות את הקטע הקודם באמצעות רכיבי משנה של Dagger. זה משפר את הביצועים של האפליקציה על ידי שחרור אובייקטים בזיכרון כשאין בהם יותר צורך.
Dagger עושה את כל זה באופן אוטומטי בזמן ה-build, כל עוד להצהיר על יחסי תלות של מחלקה ולציין איך לספק אותם באמצעות הערות. Dagger יוצר קוד שדומה למה שהיית כותב במצב ה-GRU באופן פנימי, Dagger יוצר גרף של אובייקטים שהוא יכול להפנות אליהם כדי למצוא את הדרך לספק מופע של כיתה. לגבי כל כיתה בתרשים, Dagger יוצר מחלקה של סוג מפעל שבה הוא משתמש באופן פנימי כדי לקבל מופעים מהסוג הזה.
בזמן ה-build, Dagger עובר דרך הקוד ו:
יצירה ואימות של תרשימי תלות, כדי לוודא ש:
- יחסי התלות של כל אובייקט יכולים להתקיים, כך שאין זמן ריצה חריגים.
- לא קיימים מחזורי תלות, ולכן אין לולאות אינסופיות.
יוצרת את המחלקות שישמשו בסביבת זמן הריצה כדי ליצור את האובייקטים עצמם ואת יחסי התלות שלהם.
תרחיש לדוגמה פשוט ב-Dagger: יצירת מפעל
כדי להדגים איך אפשר לעבוד עם Dagger, בואו ניצור
מפעל לכיתה UserRepository
שמוצג
את התרשים הבא:
מגדירים את UserRepository
באופן הבא:
Kotlin
class UserRepository( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } ... }
צריך להוסיף הערה @Inject
ל-constructor של UserRepository
כדי ש-Dagger ידע
איך יוצרים UserRepository
:
Kotlin
// @Inject lets Dagger know how to create instances of this object class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; // @Inject lets Dagger know how to create instances of this object @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } }
בקטע הקוד שלמעלה אתם אומרים ל-Dagger:
איך יוצרים מכונה של
UserRepository
עם הערות ב@Inject
constructor.מהם יחסי התלות שלו:
UserLocalDataSource
ו-UserRemoteDataSource
.
עכשיו Dagger יודע איך ליצור מופע של UserRepository
, אבל הוא לא
לדעת איך ליצור את יחסי התלות שלו. אם מוסיפים הערות גם לכיתות האחרות,
Dagger יודע איך ליצור אותם:
Kotlin
// @Inject lets Dagger know how to create instances of these objects class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor() { ... }
Java
public class UserLocalDataSource { @Inject public UserLocalDataSource() { } } public class UserRemoteDataSource { @Inject public UserRemoteDataSource() { } }
רכיבי צלבון
צלבון יכול ליצור גרף של יחסי התלות בפרויקט,
כדי להבין מהיכן הוא צריך לקבל את יחסי התלות האלה כשהם נחוצים.
כדי לגרום ל-Dagger לעשות את זה, צריך ליצור ממשק ולהוסיף לו הערות
@Component
Dagger יוצר קונטיינר כמו שהיית עושה ידנית
החדרת תלות.
בתוך הממשק של @Component
אפשר להגדיר פונקציות שמחזירות
של הכיתות הנדרשות (למשל UserRepository
). @Component
אומרת
צלבון שמייצר קונטיינר עם כל יחסי התלות שדרושים כדי לעמוד
הסוגים שהיא חושפת. רכיב זה נקרא רכיב Dagger. הוא מכיל
תרשים שמורכב מהאובייקטים שפגבון יודע איך
מספקים ואת יחסי התלות שלהם.
Kotlin
// @Component makes Dagger create a graph of dependencies @Component interface ApplicationGraph { // The return type of functions inside the component interface is // what can be provided from the container fun repository(): UserRepository }
Java
// @Component makes Dagger create a graph of dependencies @Component public interface ApplicationGraph { // The return type of functions inside the component interface is // what can be consumed from the graph UserRepository userRepository(); }
כשיוצרים את הפרויקט, Dagger יוצר הטמעה של
ממשק ApplicationGraph
בשבילך: DaggerApplicationGraph
. עם
שמעבד הערות, Dagger יוצר תרשים תלות
ואת הקשרים בין שלושת הסוגים (UserRepository
,
UserLocalDatasource
ו-UserRemoteDataSource
) עם נקודת כניסה אחת בלבד:
מופיע מופע של UserRepository
. אפשר להשתמש בו באופן הבא:
Kotlin
// Create an instance of the application graph val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() // Grab an instance of UserRepository from the application graph val userRepository: UserRepository = applicationGraph.repository()
Java
// Create an instance of the application graph ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); // Grab an instance of UserRepository from the application graph UserRepository userRepository = applicationGraph.userRepository();
Dagger יוצר מופע חדש של UserRepository
בכל פעם שנשלח בקשה.
Kotlin
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() val userRepository: UserRepository = applicationGraph.repository() val userRepository2: UserRepository = applicationGraph.repository() assert(userRepository != userRepository2)
Java
ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); UserRepository userRepository = applicationGraph.userRepository(); UserRepository userRepository2 = applicationGraph.userRepository(); assert(userRepository != userRepository2)
לפעמים צריך להיות מופע ייחודי של תלות בקונטיינר. יכולות להיות לכך כמה סיבות:
רוצים שסוגים אחרים עם תלות מהסוג הזה יחלקו את אותו הדבר למשל, כמה אובייקטים של
ViewModel
בתהליך ההתחברות שמשתמשים באותםLoginUserData
.כדי ליצור אובייקט יקר ולא רוצים ליצור אובייקט חדש בכל פעם שהוצהר שהוא תלוי (לדוגמה, מנתח JSON).
בדוגמה, יכול להיות שתרצו לכלול מופע ייחודי של UserRepository
.
זמינות בתרשים, כך שבכל פעם שמבקשים UserRepository
,
תמיד מקבלים את אותו מופע. זה שימושי בדוגמה שלך, כי
של החיים האמיתיים באמצעות תרשים יישומים מורכב יותר,
מספר אובייקטים של ViewModel
, בהתאם ל-UserRepository
שיוחרגו מהמשתמשים
כדי ליצור מופעים חדשים של UserLocalDataSource
ו-UserRemoteDataSource
בכל פעם שצריך לספק את הערך UserRepository
.
בהזרקת תלות ידנית, אפשר לעשות זאת על ידי העברת
מופע של UserRepository
ל-constructor של המחלקות ViewModel; אבל
ב-Dagger, בגלל שאתם לא כותבים את הקוד באופן ידני,
Dagger יודע שאתם רוצים להשתמש באותו מכונה. אפשר לעשות זאת בעזרת היקף ההרשאות
בהערות.
קביעת היקף עם צלבון
אפשר להשתמש בהערות היקף כדי להגביל את משך החיים של אובייקט ל'משך החיים' של הרכיב שלו. המשמעות היא שנעשה שימוש באותו מופע של תלות בכל פעם שצריך לספק את הסוג הזה.
לקבל מופע ייחודי של UserRepository
כשמבקשים את המאגר
ב-ApplicationGraph
, להשתמש באותה הערה של היקף עבור @Component
ו-UserRepository
. אפשר להשתמש בהערה @Singleton
כבר מגיע עם החבילה javax.inject
שבה משתמש Dagger:
Kotlin
// Scope annotations on a @Component interface informs Dagger that classes annotated // with this annotation (i.e. @Singleton) are bound to the life of the graph and so // the same instance of that type is provided every time the type is requested. @Singleton @Component interface ApplicationGraph { fun repository(): UserRepository } // Scope this class to a component using @Singleton scope (i.e. ApplicationGraph) @Singleton class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
Java
// Scope annotations on a @Component interface informs Dagger that classes annotated // with this annotation (i.e. @Singleton) are scoped to the graph and the same // instance of that type is provided every time the type is requested. @Singleton @Component public interface ApplicationGraph { UserRepository userRepository(); } // Scope this class to a component using @Singleton scope (i.e. ApplicationGraph) @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; } }
לחלופין, אפשר ליצור הערה בהיקף מותאם אישית ולהשתמש בה. תוכלו ליצור הערה להיקף באופן הבא:
Kotlin
// Creates MyCustomScope @Scope @MustBeDocumented @Retention(value = AnnotationRetention.RUNTIME) annotation class MyCustomScope
Java
// Creates MyCustomScope @Scope @Retention(RetentionPolicy.RUNTIME) public @interface MyCustomScope {}
לאחר מכן תוכלו להשתמש בו כמו קודם:
Kotlin
@MyCustomScope @Component interface ApplicationGraph { fun repository(): UserRepository } @MyCustomScope class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val service: UserService ) { ... }
Java
@MyCustomScope @Component public interface ApplicationGraph { UserRepository userRepository(); } @MyCustomScope public class UserRepository { private final UserLocalDataSource userLocalDataSource; private final UserRemoteDataSource userRemoteDataSource; @Inject public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) { this.userLocalDataSource = userLocalDataSource; this.userRemoteDataSource = userRemoteDataSource; } }
בשני המקרים, האובייקט מסופק עם אותו היקף המשמש להערות
ממשק @Component
. לכן, בכל פעם שמתקשרים
applicationGraph.repository()
, מקבלים את אותו מופע של
UserRepository
.
Kotlin
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() val userRepository: UserRepository = applicationGraph.repository() val userRepository2: UserRepository = applicationGraph.repository() assert(userRepository == userRepository2)
Java
ApplicationGraph applicationGraph = DaggerApplicationGraph.create(); UserRepository userRepository = applicationGraph.userRepository(); UserRepository userRepository2 = applicationGraph.userRepository(); assert(userRepository == userRepository2)
סיכום
חשוב להיות מודע ליתרונות של Dagger וליסודות האופן שבו הוא פועל לפני שתוכלו להשתמש בה בתרחישים מורכבים יותר.
בדף הבא נסביר איך להוסיף את Dagger לאפליקציה ל-Android.