MethodChannel: استدعاء الكود الأصلي من Dart
MethodChannel: استدعاء الكود الأصلي من Dart
MethodChannel هو الآلية الرئيسية التي يوفرها Flutter لاستدعاء كود خاص بالمنصة (أصلي) من Dart. يُنشئ قناةً اتصال ثنائية الاتجاه ومسماة بين طبقة Dart والطبقة الأصلية لنظام Android (Kotlin/Java) أو iOS (Swift/Objective-C). على الجانب الـ Dart تستدعي طريقة مسماة؛ الجانب الأصلي يعالج ذلك الاستدعاء ويُعيد نتيجة. التبادل بأكمله غير متزامن ويستخدم برنامج ترميز (codec) لتسلسل المعاملات وقيم الإرجاع.
null، bool، int، double، String، Uint8List، Int32List، Int64List، Float64List، List، وMap. يجب تسلسل الكائنات المعقدة إلى أحد هذه الأنواع قبل عبور حدود القناة.إنشاء MethodChannel
على الجانب الـ Dart، تُنشئ كائناً من MethodChannel باسم قناة فريد. هذا الاسم سلسلة نصية يجب أن تتطابق بدقة على جانبي Dart والأصلي — فأي اختلاف يعني بصمت عدم إيجاد معالج ورمي PlatformException بالرمز MissingPluginException.
تعريف واستخدام MethodChannel
import 'package:flutter/services.dart';
class BatteryService {
// اسم القناة يجب أن يطابق التسجيل الأصلي بدقة
static const MethodChannel _channel = MethodChannel('com.example.myapp/battery');
/// يُعيد مستوى البطارية الحالي كعدد صحيح (0–100).
Future<int> getBatteryLevel() async {
try {
final int level = await _channel.invokeMethod<int>('getBatteryLevel') ?? -1;
return level;
} on PlatformException catch (e) {
// الكود الأصلي أطلق خطأً — أوصله للمستدعي
throw Exception('فشل الحصول على مستوى البطارية: ${e.message}');
}
}
}
النقاط الرئيسية في المقطع أعلاه:
- يُستورد
MethodChannelمنpackage:flutter/services.dart - تُعلَن القناة
static const— أنشئها مرة واحدة وأعد استخدامها في كل مكان invokeMethod<T>(methodName, [arguments])دالة عامة (generic): تُحدد نوع الإرجاع المتوقع لكي يقوم Dart بتحويل القيمة المفككة تلقائياً- يُعيد الاستدعاء
Future<T?>— دائماً استخدمawaitداخل دالةasync - احتِ كل استدعاء في كتلة
try/catchللـPlatformException
تمرير المعاملات إلى الطرق الأصلية
يمكنك تمرير معامل ثانٍ اختياري إلى invokeMethod. يمكن أن يكون أي قيمة يدعمها برنامج الترميز — وعادةً ما يكون Map<String, dynamic> عندما تحتاج إرسال معاملات متعددة مسماة.
إرسال المعاملات عبر القناة
class FilePickerService {
static const MethodChannel _channel = MethodChannel('com.example.myapp/files');
/// يفتح منتقي الملفات الأصلي مُصفَّياً وفق [allowedExtensions].
Future<String?> pickFile({required List<String> allowedExtensions}) async {
try {
final String? path = await _channel.invokeMethod<String>(
'pickFile',
<String, dynamic>{
'extensions': allowedExtensions, // List<String> آمن مع برنامج الترميز
'multiple': false,
},
);
return path; // null إذا ألغى المستخدم العملية
} on PlatformException catch (e) {
debugPrint('خطأ في pickFile [${e.code}]: ${e.message}');
return null;
}
}
}
معالجة PlatformException
عندما يعجز الكود الأصلي عن تلبية طلب ما يُطلق استثناء منصة يصل إلى جانب Dart كـ PlatformException. يحمل هذا الاستثناء ثلاثة حقول:
- code — رمز خطأ قصير يقرأه الجهاز (مثلاً
"UNAVAILABLE"،"PERMISSION_DENIED") - message — وصف مقروء من الإنسان
- details — بيانات إضافية اختيارية (أي قيمة يدعمها برنامج الترميز)
إضافةً إلى PlatformException، يمكن أن يُطلق invokeMethod استثناء MissingPluginException عندما لا يُسجَّل أي معالج أصلي لاسم القناة، واستثناء FormatException عندما لا يمكن تحويل القيمة المُعادة إلى النوع T المطلوب. امسك كلاً منها بالشكل المناسب في كود الإنتاج.
معالجة دقيقة للاستثناءات
Future<String> readSecureValue(String key) async {
const channel = MethodChannel('com.example.myapp/keychain');
try {
final String? value = await channel.invokeMethod<String>('read', {'key': key});
if (value == null) throw StateError('المفتاح غير موجود: $key');
return value;
} on PlatformException catch (e) {
switch (e.code) {
case 'PERMISSION_DENIED':
throw Exception('المصادقة البيومترية مطلوبة.');
case 'NOT_FOUND':
throw Exception('لا توجد قيمة مخزنة للمفتاح "$key".');
default:
throw Exception('خطأ أصلي [${e.code}]: ${e.message}');
}
} on MissingPluginException {
throw UnsupportedError('قناة Keychain غير مسجلة على هذه المنصة.');
}
}
أفضل الممارسات لاستخدام MethodChannel
- استخدم تسمية DNS العكسي — مثلاً
com.yourcompany.appname/featurenameلتجنب تضارب الأسماء مع الإضافات - احصرها في فئة خدمة — لا تبعثر استدعاءات
invokeMethodالخام عبر شجرة الودجات؛ مركزها في فئة خدمة أو مستودع Dart مخصص - عالج الأخطاء دائماً — العمليات الأصلية قد تفشل بسبب الصلاحيات أو قيود إصدار نظام التشغيل أو غياب الأجهزة
- اجعل القنوات محددة النطاق — قناة واحدة لكل نطاق ميزة (بطارية، كاميرا، keychain) بدلاً من قناة "شاملة" واحدة
- اختبر على أجهزة حقيقية — كثير من واجهات برمجة التطبيقات الأصلية (NFC، البيومتريات) غير متاحة على المحاكيات
channel.setMethodCallHandler() على جانب Dart لتسجيل معالج يمكن للكود الأصلي استدعاؤه في أي وقت. هذا مغطى في الدرس التالي عن الاتصال ثنائي الاتجاه.خلاصة
يُجسّر MethodChannel العالمين: Dart والمنصة الأصلية عبر قناة مسماة. تستدعي invokeMethod<T>(name, args) وتنتظر الـ Future الناتجة. تُسلسَل المعاملات وقيم الإرجاع بواسطة StandardMethodCodec. دائماً امسك PlatformException — واختيارياً MissingPluginException — للتعامل مع الأخطاء الأصلية بسلاسة. يُبقي احتواء استدعاءات القناة في فئة خدمة مخصصة كودك في Flutter نظيفاً وقابلاً للاختبار ومنفصلاً عن تفاصيل المنصة.