نقوم حاليًا بتجربة Flutter أثناء تطوير مشروعنا الجانبي لمواجهة تحديات الخطوة مع الزملاء. يجب أيضًا اعتبار هذا المشروع الجانبي ملعبًا ، حيث يمكننا التحقق مما إذا كان بإمكاننا استخدام Flutter في مشاريع أكثر جدية. لهذا السبب نريد استخدام بعض الأساليب التي يمكن أن تبدو وكأنها هندسة مفرطة لمثل هذا المشروع الصغير.
لذلك كان أحد الأسئلة الأولى ما الذي يمكن أن نستخدمه لحقن التبعية. كشف البحث السريع في الإنترنت عن وجود مكتبتين لهما ملاحظات إيجابية: get_it و kiwi . عندما تحولت get_it
إلى "محدد موقع الخدمة" (وأنا لست من المعجبين بهذا النمط) ، كنت سألعب مع الكيوي ، والتي بدت أكثر واعدة ، ولكن بعد ذلك وجدت مكتبة أخرى: inject.dart . إنها مستوحاة بشدة من مكتبة Dagger ، وحيث أننا نستخدم أحدث واحد في مشاريعنا الأخرى التي تعمل بنظام Android ، فقد قررت البحث فيها.
تجدر الإشارة إلى أنه على الرغم من وجود هذه المكتبة في مستودع Google GitHub ، فهي ليست مكتبة رسمية من Google ولا يتوفر أي دعم حاليًا:
يتم تقديم هذه المكتبة حاليًا كما هي (معاينة مطور البرامج) نظرًا لأنها مفتوحة المصدر من مستودع داخلي داخل Google. على هذا النحو ، لا يمكننا التعامل مع الأخطاء أو طلبات الميزات في الوقت الحالي.
ومع ذلك ، يبدو أن المكتبة تفعل كل ما نحتاجه في الوقت الحالي ، لذلك أود أن أشارك بعض المعلومات حول كيفية استخدام هذه المكتبة في مشروعك.
تركيب
نظرًا لعدم وجود حزمة في المستودع الرسمي ، يتعين علينا تثبيتها يدويًا. أفضل أن أفعل ذلك كوحدة فرعية git ، لذلك أقوم بإنشاء vendor
مجلد في دليل مصدر المشروع الخاص بي وأقوم بتشغيل الأمر التالي من هذا الدليل:
git submodule add https://github.com/google/inject.dart
والآن يمكننا إعداده عن طريق إضافة الأسطر التالية إلى pubspec.yaml
:
dependencies: // other dependencies here inject: path: ./vendor/inject.dart/package/inject dev_dependencies: // other dev_dependencies here build_runner: ^1.0.0 inject_generator: path: ./vendor/inject.dart/package/inject_generator
استعمال
ما الوظيفة التي نتوقعها عادة من مكتبة DI؟ دعنا نذهب من خلال بعض حالات الاستخدام الشائعة:
حقن الطبقة ملموسة
يمكن أن يكون بهذه البساطة:
import 'package:inject/inject.dart'; @provide class StepService { // implementation }
يمكننا استخدامه على سبيل المثال مع عناصر واجهة المستخدم الرسومية مثل:
@provide class SomeWidget extends StatelessWidget { final StepService _service; SomeWidget(this._service); }
حقن واجهة
بادئ ذي بدء ، نحن بحاجة إلى تحديد فئة مجردة مع بعض فئة التنفيذ ، على سبيل المثال:
abstract class UserRepository { Future<List<User>> allUsers(); } class FirestoreUserRepository implements UserRepository { @override Future<List<User>> allUsers() { // implementation } }
والآن يمكننا توفير التبعيات في وحدتنا:
import 'package:inject/inject.dart'; @module class UsersServices { @provide UserRepository userRepository() => FirestoreUserRepository(); }
مقدمي
ماذا تفعل إذا لم نحتاج إلى حقن مثيل من فئة ما ، ولكن بدلاً من ذلك مزودًا ، سيعطينا مثيلًا جديدًا من هذه الفئة في كل مرة؟ أو إذا كنا بحاجة إلى حل التبعية بتكاسل بدلاً من الحصول على مثيل محدد في المنشئ؟ لم أجدها لا في الوثائق (جيدًا ، نظرًا لعدم وجود وثائق على الإطلاق) أو في الأمثلة المقدمة ، ولكنها تعمل في الواقع بهذه الطريقة يمكنك طلب دالة تُرجع المثيل المطلوب وسيتم حقنه بشكل صحيح.
يمكننا حتى تحديد نوع المساعد مثل هذا:
typedef Provider<T> = T Function();
واستخدامها في فصولنا:
@provide class SomeWidget extends StatelessWidget { final Provider<StepService> _service; SomeWidget(this._service); void _someFunction() { final service = _service(); // use service } }
حقن بمساعدة
لا توجد وظيفة مضمنة لحقن كائنات تتطلب وسائط معروفة في وقت التشغيل فقط ، حتى نتمكن من استخدام النمط الشائع مع المصانع في هذه الحالة: إنشاء فئة مصنع تأخذ جميع تبعيات وقت الترجمة في المُنشئ وحقنها ، وتوفر طريقة المصنع مع وسيطة وقت التشغيل والتي ستقوم بإنشاء مثيل مطلوب.
المفردة ، التصفيات والحقن غير المتزامن
نعم ، تدعم المكتبة كل هذا. يوجد بالفعل تفسير جيد في المثال الرسمي .
الأسلاك عنه
الخطوة الأخيرة لجعل كل شيء يعمل هو إنشاء حاقن (ويعرف أيضًا باسم مكون من خنجر) ، مثل:
import 'main.inject.dart' as g; @Injector(const [UsersServices, DateResultsServices]) abstract class Main { @provide MyApp get app; static Future<Main> create( UsersServices usersModule, DateResultsServices dateResultsModule, ) async { return await g.Main$Injector.create( usersModule, dateResultsModule, ); } }
هنا UserServices
و DateResultsServices
هي وحدات تم تعريفها مسبقًا ، MyApp
هي الأداة الأساسية main.inject.dart
، و main.inject.dart
هو ملف تم إنشاؤه تلقائيًا (المزيد حول هذا لاحقًا).
الآن يمكننا تحديد وظيفتنا الرئيسية مثل هذا:
void main() async { var container = await Main.create( UsersServices(), DateResultsServices(), ); runApp(container.app); }
تشغيل
بما أن inject
يعمل مع إنشاء الشفرة ، نحتاج إلى استخدام runner لإنشاء الكود المطلوب. يمكننا استخدام هذا الأمر:
flutter packages pub run build_runner build
أو watch
أجل الحفاظ على مزامنة شفرة المصدر تلقائيًا:
flutter packages pub run build_runner watch
ولكن هناك لحظة مهمة هنا: افتراضيًا ، سيتم إنشاء الشفرة في مجلد cache
ولا يدعم Flutter هذا في الوقت الحالي (على الرغم من أن هناك عملًا مستمرًا لحل هذه المشكلة). لذلك نحن بحاجة إلى إضافة الملف inject_generator.build.yaml
بالمحتوى التالي:
builders: inject_generator: target: ":inject_generator" import: "package:inject_generator/inject_generator.dart" builder_factories: - "summarizeBuilder" - "generateBuilder" build_extensions: ".dart": - ".inject.summary" - ".inject.dart" auto_apply: dependents build_to: source
إنه في الواقع نفس المحتوى الموجود في vendor/inject.dart/package/inject_generator/build.yaml
الملفات vendor/inject.dart/package/inject_generator/build.yaml
باستثناء سطر واحد: build_to: cache
تم استبدال build_to: source
بـ build_to: source
.
الآن يمكننا تشغيل build_runner
، وسيقوم بإنشاء الكود المطلوب (وتوفير رسائل خطأ إذا تعذر حل بعض التبعيات) وبعد ذلك يمكننا تشغيل Flutter build كالمعتاد.
ربح
هذا كل شيء. يجب عليك أيضًا التحقق من الأمثلة المقدمة مع المكتبة نفسها ، وإذا كان لديك بعض الخبرة مع مكتبة Dagger ، inject
مألوفة جدًا لك.