לעתים קרובות אנו נתקלים בפרויקטים שבהם Dagger 2 מתווסף באמצע הפיתוח. התוצאה היא גרף סבוך, דליפות זיכרון ובאגים שלא ניתן לשחזר מקומית. Dagger מייצר קוד בזמן קומפילציה — ללא reflection, אך הבטיחות הזו באה במחיר של משמעת ארכיטקטונית. הניסיון שלנו: הגדרה מאפס אורכת 3–5 ימים, ו-refactoring של יישום כאוטי אורך משבוע. עם למעלה מ-5 שנות עבודה, הגדרנו Dagger ביותר מ-30 פרויקטים בקני מידה שונים — מסטארטאפים ועד יישומים ארגוניים. אנו מבטיחים קוד DI נקי ללא הפתעות בזמן ריצה. קבלו ייעוץ לפרויקט שלכם: הזמינו ניתוח של הארכיטקטורה הנוכחית שלכם.
כיצד לתכנן נכון גרף רכיבים?
הסכימה האופיינית לאפליקציה גדולה: AppComponent (Singleton) → ActivityComponent (PerActivity) → FragmentComponent (PerFragment). כל רמה היא subcomponent או dependent component. הטעות הארכיטקטונית היא הצבת כל התלויות ב-AppComponent, מה שמגדיל את זמני הבנייה ומסבך את הבדיקות.
@Singleton
@Component(modules = [AppModule::class, NetworkModule::class, DatabaseModule::class])
interface AppComponent {
fun inject(app: App)
fun activityComponentBuilder(): ActivityComponent.Builder
}
@Module
class NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(AuthInterceptor())
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.API_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
} מדוע scopes הם גורם נפוץ לבאגים חמקמקים?
הטעות הנפוצה ביותר היא scopes שגויים. אם @Singleton @Component(modules = [AppModule::class, NetworkModule::class, DatabaseModule::class]) interface AppComponent { fun inject(app: App) fun activityComponentBuilder(): ActivityComponent.Builder } @Module class NetworkModule { @Provides @Singleton fun provideOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .addInterceptor(AuthInterceptor()) .connectTimeout(30, TimeUnit.SECONDS) .build() } @Provides @Singleton fun provideRetrofit(client: OkHttpClient): Retrofit { return Retrofit.Builder() .baseUrl(BuildConfig.API_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() } } מוגדר UserRepository ו-@Singleton בתוכו נשמר בזיכרון, אז לאחר התנתקות, ללא יצירת רכיב מחדש, ה-token הישן נשאר חי. זה מוביל לבקשות עם token מיושן — באג ייצור שמשתחזר רק בתרחיש ספציפי. פתרון: רכיבי AuthToken לא צריכים להכיל מצב משתנה שתלוי בסשן המשתמש. תלויות ברמת סשן צריכות לעבור ל-@Singleton:
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class UserScope
@UserScope
@Subcomponent(modules = [UserModule::class])
interface UserComponent {
@Subcomponent.Factory
interface Factory {
fun create(@BindsInstance userId: String): UserComponent
}
fun inject(profileFragment: ProfileFragment)
}@UserScope נוצר לאחר התחברות ונהרס לאחר התנתקות. כל התלויות הקשורות למשתמש חיות בדיוק כל עוד צריך. להלן טבלה של טעויות אופייניות:
| טעות אופיינית | השלכה | פתרון |
|---|---|---|
| מצב משתנה ב-@Singleton | דליפת נתונים בעת התנתקות | העברה ל-@UserScope |
| תלות @Singleton עם אתחול איטי | עיכוב בהפעלת האפליקציה | שימוש ב-@Lazy או Provider |
| חוסר ב-@BindsInstance | צורך ליצור רכיב ידנית | הוספת @BindsInstance לפרמטרים דינמיים |
בנוסף, שימוש לא נכון ב-scopes עלול לגרום לדליפות זיכרון עקב החזקה מקרית של Activity context ב-@Scope @Retention(AnnotationRetention.RUNTIME) annotation class UserScope @UserScope @Subcomponent(modules = [UserModule::class]) interface UserComponent { @Subcomponent.Factory interface Factory { fun create(@BindsInstance userId: String): UserComponent } fun inject(profileFragment: ProfileFragment) } . אנו תמיד בודקים את הגרף לתרחישים כאלה.
Multibindings וארכיטקטורת תוספים
UserComponent עם @Singleton הוא תבנית להזרקת ViewModels דרך @IntoMap. Dagger יוצר @ViewModelKey, והמפעל בוחר את המחלקה הנכונה. ללא תבנית זו, כל ViewModel חייב להיות מוגדר בנפרד ברכיב.
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(LoginViewModel::class)
abstract fun bindLoginViewModel(vm: LoginViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(ProfileViewModel::class)
abstract fun bindProfileViewModel(vm: ProfileViewModel): ViewModel
}אותה גישה חלה על ארכיטקטורת תוספים, שבה כל מודול רושם את התלויות שלו דרך ViewModelProvider.Factory או Map<Class<out ViewModel>, Provider<ViewModel>>. השתמשנו בזה בפרויקט עם 10+ מודולי תכונה — Dagger אוסף אוטומטית את כל היישומים שסופקו.
Kapt ו-KSP: מה לבחור לבנייה?
Dagger 2 עובד באופן מסורתי עם kapt. החל מגרסה 2.50, קיימת תמיכה ניסיונית ב-KSP, שמאיצה בניות מצטברות. בפרויקט עם כ-200 הערות Dagger, המעבר מ-kapt ל-KSP הפחית את זמן הבנייה הנקייה מ-4.5 ל-2.8 דקות — חיסכון משמעותי בזמן הפיתוח.
// build.gradle.kts
plugins {
id("com.google.devtools.ksp")
}
dependencies {
implementation("com.google.dagger:dagger:2.51")
ksp("com.google.dagger:dagger-compiler:2.51")
} | פרמטר | Kapt | KSP (ניסיוני) |
|---|---|---|
| זמן בנייה נקייה (200 הערות) | 4.5 דקות | 2.8 דקות |
| בנייה מצטברת | רגילה | מואצת |
| תמיכת Dagger | מלאה | מאז 2.50, לא כל התכונות |
כמו כן, אל תשכחו על obfuscation: ProGuard/R8 יכול להסיר מחלקות שנוצרו על ידי Dagger אם לא נוספו כללי keep. אנו כוללים זאת בתצורה.
כיצד לבדוק אפליקציה עם Dagger?
Dagger ובדיקות הם סיפור נפרד. הגישה הסטנדרטית: מודולי בדיקה מחליפים תלויות ייצור ב-fakes:
@Component(modules = [TestNetworkModule::class, DatabaseModule::class])
interface TestAppComponent : AppComponent
@Module
class TestNetworkModule {
@Provides
@Singleton
fun provideApiService(): ApiService = FakeApiService()
}בבדיקות Espresso, @Module abstract class ViewModelModule { @Binds @IntoMap @ViewModelKey(LoginViewModel::class) abstract fun bindLoginViewModel(vm: LoginViewModel): ViewModel @Binds @IntoMap @ViewModelKey(ProfileViewModel::class) abstract fun bindProfileViewModel(vm: ProfileViewModel): ViewModel } מוחלף ב-@IntoSet לפני שהבדיקה רצה. ללא החלפה זו, בדיקות אינטגרציה פוגעות בשרת האמיתי. אנו גם משתמשים ב-@IntoMap כדי לדמות עיכובים.
מתי לבחור Dagger 2 על פני Hilt
Hilt הוא wrapper מעל Dagger עם מבנה רכיבים מוגדר מראש. אם אתם צריכים scopes מותאמים אישית, גרפים רב-מודוליים עם רכיבים עצמאיים, או ש-Dagger כבר קיים בפרויקט — Dagger 2 נותן שליטה מלאה. Hilt מאפשר התחלה מהירה יותר, אך מגביל ארכיטקטורות מורכבות. בפרויקטים שלנו, אנו לעתים קרובות משלבים Dagger עם Hilt במודולים שונים.
מה כלול בהגדרת Dagger 2 במפתח
- ניתוח ארכיטקטורה ועיצוב גרף רכיבים
- יצירת מודולים לרשת, מסד נתונים, shared preferences
- הגדרת scopes (Singleton, PerActivity, UserScope)
- אינטגרציית ViewModel דרך multibinding
- הגדרת רכיבי בדיקה עם fakes
- הגירה מ-kapt ל-KSP במידת הצורך
- תיעוד לתחזוקת הגרף
העלות מחושבת באופן אישי. הגדרה מאפס — 3–5 ימים, refactoring — משבוע. קבלו ייעוץ: כתבו לנו, אנו נעריך את הפרויקט שלכם. רוצים ליישם Dagger 2 ללא כאבי ראש? צרו קשר, אנו נבצע ביקורת על קוד ה-DI שלכם.







