كتابة مكوّن Flutter إضافي: هيكل الحزمة وواجهة Dart
كتابة مكوّن Flutter إضافي: هيكل الحزمة وواجهة Dart
مكوّن Flutter الإضافي (Flutter plugin) هو حزمة Dart متخصصة تُغلّف وظائف خاصة بالمنصة (native) وتعرضها عبر واجهة Dart نظيفة. على عكس حزمة Dart الخالصة، يحتوي المكوّن على كود أصلي لمنصة واحدة أو أكثر (Android وiOS وmacOS وWindows وLinux والويب) إلى جانب طبقة Dart. إن فهم كيفية إنشاء الهيكل وتصميم واجهة Dart API للمكوّن هو أساس كل مشروع تكامل أصلي.
إنشاء مكوّن إضافي متحد (Federated Plugin)
النهج الحديث الموصى به هو المكوّن المتحد (federated plugin): يتم تقسيم الوظائف عبر حزم Dart متعددة حتى يمكن تنفيذ كل منصة بشكل مستقل دون المساس بكود المنصات الأخرى. استخدم Flutter CLI لتوليد الهيكل:
إنشاء هيكل مكوّن متحد
# توليد الحزمة الموجهة للتطبيق (التي يستوردها مطورو التطبيقات)
flutter create --template=plugin \
--org com.example \
--platforms android,ios,macos \
my_battery_info
# الأمر يُنتج الملفات والمجلدات التالية:
# my_battery_info/
# lib/my_battery_info.dart <-- واجهة Dart العامة
# lib/src/my_battery_info_platform_interface.dart
# android/ <-- الكود الأصلي لـ Android
# ios/ <-- الكود الأصلي لـ iOS
# macos/ <-- الكود الأصلي لـ macOS
# example/ <-- تطبيق Flutter توضيحي
# pubspec.yaml
# CHANGELOG.md README.md LICENSE
my_battery_info_platform_interface (العقد المجرد) وحزمة تنفيذ لكل منصة (مثل my_battery_info_android). القالب --template=plugin_ffi يُولّد هيكل مكوّن Dart FFI بدلاً من القائم على القنوات.فهم هيكل المجلدات
بعد إنشاء الهيكل، الحزم الثلاث الأكثر أهمية في المكوّن المتحد هي:
- الحزمة الموجهة للتطبيق (
my_battery_info) — الحزمة التي يضيفها مطورو التطبيقات إلىpubspec.yaml. تُعيد تصدير واجهة المنصة وتستدعي التنفيذ المسجّل. - حزمة واجهة المنصة (
my_battery_info_platform_interface) — تُعرّف الفئة المجردةMyBatteryInfoPlatformالتي يجب على كل تنفيذ منصة توسيعها. تعتمد فقط على Dart وحزمةplugin_platform_interface. - حزم تنفيذ المنصة (
my_battery_info_android،my_battery_info_ios، …) — كل منها يوفر فئة فرعية محددة منMyBatteryInfoPlatformويحتوي على الكود الأصلي لمنصة واحدة فقط.
تصميم واجهة Dart العامة
ملف lib/my_battery_info.dart في الحزمة الموجهة للتطبيق هو السطح العام الوحيد الذي يراه المستخدمون النهائيون. احتفظ به رفيعاً: أوكل كل استدعاء إلى مُفرد واجهة المنصة. تتبع واجهة Dart الجيدة هذه الاتفاقيات:
- يُفضَّل استخدام توابع ثابتة أو على مستوى أعلى تُعيد قيم
Future<T>مكتوبة بقوة. - ارمِ استثناءات مسماة وموثقة بدلاً من إرجاع قيم nullable كبدائل.
- اجعل الفئة
finalأو استخدم مُنشئ factory حتى لا يتمكن المستخدمون من توسيعها عن طريق الخطأ. - وثّق كل رمز عام بتعليق ثلاثي الشرطة
///.
واجهة Dart الموجهة للتطبيق — lib/my_battery_info.dart
import 'package:my_battery_info_platform_interface/my_battery_info_platform_interface.dart';
/// يوفر الوصول إلى مستوى بطارية الجهاز وحالة الشحن.
///
/// جميع التوابع غير متزامنة؛ تُفوّض إلى التنفيذ الخاص بالمنصة
/// المسجّل عبر [MyBatteryInfoPlatform.instance].
class MyBatteryInfo {
// منع إنشاء الكائنات — جميع الأعضاء ثابتة.
const MyBatteryInfo._();
/// يُعيد مستوى البطارية الحالي كنسبة مئوية (0–100).
///
/// يرمي [BatteryUnavailableException] إذا كان الجهاز لا يُبلّغ
/// عن معلومات البطارية (مثل أجهزة سطح المكتب بدون بطارية).
static Future<int> getBatteryLevel() {
return MyBatteryInfoPlatform.instance.getBatteryLevel();
}
/// يُعيد `true` إذا كان الجهاز يتشحن حالياً.
static Future<bool> isCharging() {
return MyBatteryInfoPlatform.instance.isCharging();
}
/// تدفق من تحديثات مستوى البطارية (يُصدر عند كل تغيير بنسبة 1%).
static Stream<int> get batteryLevelStream {
return MyBatteryInfoPlatform.instance.batteryLevelStream;
}
}
/// يُرمى عندما لا تتوفر معلومات البطارية على الجهاز الحالي.
class BatteryUnavailableException implements Exception {
const BatteryUnavailableException([this.message]);
final String? message;
@override
String toString() =>
'BatteryUnavailableException${message != null ? ': $message' : ''}';
}
عقد واجهة المنصة
تُعرّف حزمة واجهة المنصة الفئة المجردة التي تمثل العقد الذي يجب على كل تنفيذ الوفاء به. تعتمد على plugin_platform_interface لضمان أن التنفيذات المعتمدة فقط يمكن تعيينها لـ instance.
حزمة واجهة المنصة — lib/my_battery_info_platform_interface.dart
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
/// الواجهة التي يجب على جميع تنفيذات my_battery_info توسيعها.
///
/// يجب على تنفيذات المنصة توسيع هذه الفئة لا تنفيذها،
/// إذ أن [instance] لا يُطبّق واجهة Dart الكاملة.
abstract class MyBatteryInfoPlatform extends PlatformInterface {
MyBatteryInfoPlatform() : super(token: _token);
static final Object _token = Object();
static MyBatteryInfoPlatform _instance = _MethodChannelMyBatteryInfo();
/// المثيل الافتراضي لـ [MyBatteryInfoPlatform] للاستخدام.
static MyBatteryInfoPlatform get instance => _instance;
/// تجاوز لتوفير تنفيذ مخصص (يُستخدم في الاختبارات أو
/// حزم المنصة التي تُسجّل نفسها).
static set instance(MyBatteryInfoPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
/// يُعيد مستوى البطارية الحالي (0–100).
Future<int> getBatteryLevel() {
throw UnimplementedError('getBatteryLevel() has not been implemented.');
}
/// يُعيد ما إذا كان الجهاز يتشحن حالياً.
Future<bool> isCharging() {
throw UnimplementedError('isCharging() has not been implemented.');
}
/// تدفق من تحديثات مستوى البطارية.
Stream<int> get batteryLevelStream {
throw UnimplementedError('batteryLevelStream has not been implemented.');
}
}
MethodChannel. توجّه دائماً عبر واجهة المنصة. تجاوز الواجهة يكسر البنية المتحدة ويجعل من المستحيل على فرق المنصة تبادل التنفيذات.ربط pubspec.yaml
كل حزمة في الاتحاد تمتلك pubspec.yaml يُعلن عن دورها. تستخدم الحزمة الموجهة للتطبيق مفتاح flutter.plugin.platforms لإخبار Flutter بحزم التنفيذ التي تستخدمها على كل منصة:
- الحزمة الموجهة للتطبيق: تُدرج
flutter > plugin > platformsمشيرةً إلى حزم التنفيذ لكل منصة. - واجهة المنصة: حزمة Dart عادية؛ لا تحتاج مفتاح
flutter.plugin. - حزم التنفيذ: تُعلن
implements: my_battery_infoوdartPluginClass/pluginClassلمنصتها.
ملخص
إنشاء هيكل مكوّن Flutter متحد ينتج ثلاث طبقات منطقية: الحزمة الموجهة للتطبيق بواجهة Dart نظيفة، حزمة واجهة المنصة بالعقد المجرد، وحزمة تنفيذ منصة واحدة أو أكثر تحتوي على الكود الأصلي. يجب أن تكون طبقة Dart API رفيعة ومكتوبة بقوة وموثقة جيداً، وتُفوّض كل العمل إلى PlatformInterface.instance. هذا الهيكل يُعظّم إعادة استخدام الكود، ويسمح بمساهمات منصة مستقلة، ويحافظ على استقرار واجهتك العامة عبر التغييرات الأصلية.