הגדרת הזרקת תלויות (GetIt) באפליקציית Flutter
הגדרת DI בפרויקט Flutter הופכת פעמים רבות לכאוס: תלויות מאותחלות במקומות אקראיים, הקוד הופך לבלתי ניתן לבדיקה, ולאחר שינויים ארכיטקטוניים האפליקציה קורסת בעת ההפעלה. אנו פותרים בעיה זו עם GetIt — מאתר שירותים פשוט ואמין שבו השתמשנו ביותר מ-30 פרויקטים מסחריים. הניסיון שלנו מראה: הגדרת GetIt נכונה מפחיתה את זמן ניפוי התלויות פי 3–5 והופכת את הקוד למוכן ל-Clean Architecture. החיסכון בתקציב על ניפוי יכול להגיע ל-40% — זה מאות שעות או עשרות אלפי דולרים בחיסכון במשכורות מפתחים.
GetIt הוא מאתר שירותים עבור Dart/Flutter, התקן דה-פקטו ל-DI בפרויקטים שבהם אין צורך ביצירת קוד. לפי התיעוד הרשמי, זוהי הגישה המומלצת לניהול תלויות מחוץ לווידג'טים. העיקרון פשוט: רושמים תלויות פעם אחת בעת הפעלת האפליקציה, ומבקשים אותן בכל מקום באמצעות GetIt.instance<T>() או הקיצור sl<T>(). אנו מבטיחים שאחרי ההגדרה שלנו, תשכחו מהעברת פרמטרים ידנית דרך קונסטרוקטורים.
למה GetIt במקום Provider או Riverpod?
Provider ו-Riverpod הם כלים לניהול מצב עם DI כתופעת לוואי. GetIt הוא מאתר שירותים טהור ללא תלויות Flutter: ניתן להשתמש בו בשכבת הדומיין ללא BuildContext. עבור Clean Architecture, שבה שכבת הדומיין לא יודעת על Flutter, זה קריטי. בנוסף, GetIt רץ פי 2–3 מהר יותר בעת הפעלת האפליקציה כי הוא לא צריך לבנות עץ ווידג'טים.
הקונסטרוקטור GetIt תומך בשלושה מצבי רישום:
-
registerSingleton<T>— נוצר מיד בעת הרישום, חי לכל משך חיי האפליקציה. -
registerLazySingleton<T>— נוצר בגישה הראשונה, ואז מחזיר את אותו מופע. -
registerFactory<T>— יוצר מופע חדש בכל גישה.
אנו ממליצים להשתמש ב-registerLazySingleton עבור רוב השירותים (ApiService, DatabaseHelper) וב-registerFactory עבור אובייקטים קצרי חיים (למשל, BLoCs שנוצרו דרך GetIt).
איך להימנע משגיאת האתחול האסינכרוני האופיינית?
// Неправильно — синхронная регистрация асинхронной зависимости
sl.registerLazySingleton<DatabaseHelper>(() => DatabaseHelper()..init());
// Правильно — async init через registerSingletonAsync
sl.registerSingletonAsync<DatabaseHelper>(() async {
final db = DatabaseHelper();
await db.init();
return db;
});
// И дождаться готовности перед runApp:
await sl.allReady();אם לא משתמשים ב-// Неправильно — синхронная регистрация асинхронной зависимости sl.registerLazySingleton<DatabaseHelper>(() => DatabaseHelper()..init()); // Правильно — async init через registerSingletonAsync sl.registerSingletonAsync<DatabaseHelper>() async { final db = DatabaseHelper(); await db.init(); return db; }); // И дождаться готовности перед runApp: await sl.allReady(); + registerSingletonAsync, ייתכן ש-allReady() יבוקש לפני השלמת האתחול האסינכרוני — קריסה בעת ההפעלה עם DatabaseHelper. בפרויקטים שלנו, אנו תמיד עוטפים את האתחול של DB ו-SharedPreferences בתבנית זו.
השוואת סוגי רישום ב-GetIt
| סוג | זמן יצירה | מספר מופעים | מתי להשתמש |
|---|---|---|---|
StateError: Singleton is not ready yet |
בעת הרישום | 1 (אחד לכל האפליקציה) | תצורות, לוגרים |
registerSingleton |
בגישה הראשונה | 1 | ApiService, DatabaseHelper, מאגרי נתונים |
registerLazySingleton |
בכל גישה | רבים | Use Cases, BLoCs (אם נוצרו דרך GetIt) |
שלבים להקמת מערכת ה-DI
| שלב | משימות | משך |
|---|---|---|
| ניתוח ועיצוב | זיהוי תלויות 'דולפות', ציור דיאגרמת DI | 3–4 שעות |
| כתיבת injection_container | רישום כל השירותים, אתחול אסינכרוני | 1–2 ימים |
| אינטגרציה עם מודולי פיצ'רים | פיצול לפי פיצ'רים לפרויקטים גדולים | 1–2 ימים |
| בדיקות וסקירת קוד | בדיקות יחידה לרישומים, סקירת עמיתים | יום אחד |
איך לבנות מערכת DI מודולרית לפרויקטים גדולים?
בפרויקטים גדולים, registerFactory יחיד הופך ל-500 שורות. הפתרון: פיצול לפי פיצ'רים. כל מודול פיצ'ר רושם את התלויות שלו דרך פונקציות נפרדות כמו injection_container.dart, initAuthDependencies() — הנקראות מה-initProfileDependencies() הראשי. עברנו לגישה זו אחרי שפרויקט גדל ל-15 פיצ'רים — עכשיו הקמת DI אורכת 3–4 ימים, אבל התחזוקה דורשת חצי מהזמן.
ארגון injection_container.dart
הנוהג הסטנדרטי הוא קובץ אחד initDependencies() (או injection_container.dart) עם פונקציית di/:
Future<void> initDependencies() async {
// External
final sharedPrefs = await SharedPreferences.getInstance();
sl.registerLazySingleton(() => sharedPrefs);
sl.registerLazySingleton(() => http.Client());
// Data sources
sl.registerLazySingleton<AuthRemoteDataSource>(
() => AuthRemoteDataSourceImpl(sl()),
);
// Repositories
sl.registerLazySingleton<AuthRepository>(
() => AuthRepositoryImpl(sl()),
);
// Use cases
sl.registerLazySingleton(() => LoginUseCase(sl()));
// BLoCs — если создаются через GetIt
sl.registerFactory(() => AuthBloc(loginUseCase: sl()));
}רשמו תלויות בסדר מלמטה למעלה: קודם תלויות חיצוניות, אחר כך מקורות נתונים, מאגרי נתונים, use cases, ולבסוף שכבת התצוגה. כלל זה מבטיח שכל התלויות זמינות בעת הגישה.
מה כלול בהקמת GetIt סוהר
- ניתוח הארכיטקטורה הנוכחית וזיהוי תלויות 'דולפות' (כ-3–4 שעות).
- עיצוב שכבת ה-DI: בחירת סוגי רישום לכל רכיב (2–3 שעות).
- כתיבת
initDependencies()עם אתחול אסינכרוני עבור DB,Future<void> initDependencies() async { // External final sharedPrefs = await SharedPreferences.getInstance(); sl.registerLazySingleton(() => sharedPrefs); sl.registerLazySingleton(() => http.Client()); // Data sources sl.registerLazySingleton<AuthRemoteDataSource>( () => AuthRemoteDataSourceImpl(sl()), ); // Repositories sl.registerLazySingleton<AuthRepository>( () => AuthRepositoryImpl(sl()), ); // Use cases sl.registerLazySingleton(() => LoginUseCase(sl())); // BLoCs — если создаются через GetIt sl.registerFactory(() => AuthBloc(loginUseCase: sl())); }, Firebase (1–2 ימים). - אינטגרציה עם מודולי פיצ'רים (אם הפרויקט גדול) — תוספת של 1–2 ימים.
- כתיבת בדיקות יחידה לאימות רישומים (החלפת mock ב-
injection_container). - תיעוד על הוספת תלויות חדשות (שעה אחת).
תהליך
- ניתוח: פגישה עם הצוות, לימוד הארכיטקטורה הנוכחית (3–4 שעות).
- עיצוב: דיאגרמת תלויות, קביעת משכי חיים (2–3 שעות).
- יישום: כתיבת
SharedPreferencesובדיקות (1–2 ימים). - סקירת קוד ופריסה (יום אחד).
- העברת ידע: תיעוד ושיחה (שעה אחת).
לוחות זמנים ועלות
לוח זמנים משוער: 2 עד 5 ימים תלוי בגודל הפרויקט. העלות מחושבת באופן אישי לאחר ביקורת קוד. זמן ניפוי עבור DI לא תקין יכול להגיע עד 30% מזמן הצוות — ההקמה שלנו מחזירה את עצמה בחודש הראשון על ידי הפחתת זמן זה. קבלו ייעוץ — צרו קשר ואנו נציע את הפתרון האופטימלי. הזמינו הקמת GetIt סוהר — תוצאות מובטחות.







