EventChannel: بث الأحداث الأصلية إلى Dart
EventChannel: بث الأحداث الأصلية إلى Dart
EventChannel هو آلية Flutter لإنشاء تدفق بيانات مستمر وغير متزامن ينتقل من الكود الأصلي للمنصة (Android/iOS) إلى Dart. على عكس MethodChannel الذي يعمل بنمط الطلب والاستجابة (استدعاء واحد، نتيجة واحدة)، يُمثل EventChannel تدفقاً مبثوثاً: الجانب الأصلي يدفع الأحداث كلما وقعت، ويتفاعل جانب Dart معها عبر اشتراك Stream قياسي. تشمل الاستخدامات الشائعة في الواقع: قراءات الاستشعارات، تغييرات الاتصال بالشبكة، تحديثات الموقع الجغرافي، نتائج مسح Bluetooth، ومراقبة مستوى البطارية.
EventChannel تدفق أحادي الاتجاه — الكود الأصلي يدفع الأحداث إلى Dart. إذا احتجت أيضاً لإرسال بيانات من Dart إلى الكود الأصلي، اجمعه مع MethodChannel.كيف يعمل EventChannel
لدورة الحياة جانبان يعكسان بعضهما:
- جانب Dart — ينشئ
EventChannelباسم فريد ويستدعي.receiveBroadcastStream()للحصول علىStream. الاشتراك في ذلك التدفق (listen) يُشير للكود الأصلي بالبدء، وإلغاء الاشتراك يُشير له بالتوقف. - الجانب الأصلي — يُسجل
StreamHandler(Android:EventChannel.StreamHandler؛ iOS:FlutterStreamHandler). دالة الاستجابةonListenتستقبلEventSink(Android) /FlutterEventSink(iOS) التي تُستخدم لدفع الأحداث. دالة الاستجابةonCancelتُفكك أي موارد للمنصة.
com.example.myapp/battery.التنفيذ على جانب Dart
على جانب Dart تنشئ نسخة واحدة من EventChannel، وتُعرض التدفق، وتشترك فيه داخل StatefulWidget حتى تتمكن من إلغاء الاشتراك عند التخلص من الودجت:
Dart — تدفق مستوى البطارية
import 'package:flutter/services.dart';
// 1. أعلن عن القناة (نفس الاسم في الكود الأصلي)
const EventChannel _batteryChannel =
EventChannel('com.example.myapp/battery');
class BatteryPage extends StatefulWidget {
const BatteryPage({super.key});
@override
State<BatteryPage> createState() => _BatteryPageState();
}
class _BatteryPageState extends State<BatteryPage> {
// 2. احتفظ بالاشتراك لتتمكن من إلغائه
StreamSubscription<dynamic>? _subscription;
String _batteryLevel = 'Unknown';
@override
void initState() {
super.initState();
// 3. اشترك — هذا يُشغّل onListen في الجانب الأصلي
_subscription = _batteryChannel
.receiveBroadcastStream()
.listen(
(dynamic event) {
setState(() {
_batteryLevel = '$event%';
});
},
onError: (dynamic error) {
setState(() {
_batteryLevel = 'Error: ${(error as PlatformException).message}';
});
},
);
}
@override
void dispose() {
// 4. ألغِ — هذا يُشغّل onCancel في الجانب الأصلي
_subscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Battery Monitor')),
body: Center(
child: Text(
'Battery: $_batteryLevel',
style: Theme.of(context).textTheme.headlineMedium,
),
),
);
}
}
إعداد StreamHandler في Android (Kotlin)
في MainActivity.kt، سجّل القناة ونفّذ EventChannel.StreamHandler. تبدأ طريقة onListen مصدر الأحداث الأصلي وتحفظ EventSink. تُنظّف طريقة onCancel الموارد.
Android Kotlin — StreamHandler للبطارية
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.myapp/battery"
private var eventSink: EventChannel.EventSink? = null
private var batteryReceiver: BroadcastReceiver? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
EventChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, sink: EventChannel.EventSink) {
// احفظ المستقبِل لاستخدامه لاحقاً
eventSink = sink
// سجّل مستقبل البث
batteryReceiver = object : BroadcastReceiver() {
override fun onReceive(ctx: Context?, intent: Intent?) {
val level = intent?.getIntExtra(
BatteryManager.EXTRA_LEVEL, -1
) ?: -1
if (level == -1) {
sink.error("UNAVAILABLE", "Battery level unavailable", null)
} else {
sink.success(level)
}
}
}
registerReceiver(batteryReceiver,
IntentFilter(Intent.ACTION_BATTERY_CHANGED))
}
override fun onCancel(arguments: Any?) {
// ألغِ التسجيل لتجنب تسرب الذاكرة
unregisterReceiver(batteryReceiver)
batteryReceiver = null
eventSink = null
}
})
}
}
إعداد StreamHandler في iOS (Swift)
في AppDelegate.swift، سجّل القناة بـ FlutterEventChannel ونفّذ FlutterStreamHandler. استخدم Timer أو API النظام لدفع الأحداث عبر FlutterEventSink المحفوظ.
iOS Swift — StreamHandler للبطارية
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
private let channelName = "com.example.myapp/battery"
private var eventSink: FlutterEventSink?
private var timer: Timer?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterEventChannel(
name: channelName,
binaryMessenger: controller.binaryMessenger
)
batteryChannel.setStreamHandler(self)
UIDevice.current.isBatteryMonitoringEnabled = true
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
extension AppDelegate: FlutterStreamHandler {
func onListen(withArguments arguments: Any?,
eventSink events: @escaping FlutterEventSink) -> FlutterError? {
self.eventSink = events
// استطلع البطارية كل 10 ثوانٍ
timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { [weak self] _ in
let level = Int(UIDevice.current.batteryLevel * 100)
self?.eventSink?(level)
}
// أرسل فوراً
eventSink?(Int(UIDevice.current.batteryLevel * 100))
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
timer?.invalidate()
timer = nil
eventSink = nil
return nil
}
}
إلغاء التدفق وإدارة الموارد
التنظيف السليم ضروري لتجنب تسرب الذاكرة واستنزاف البطارية. اتبع هذه القواعد:
- احفظ دائماً
StreamSubscriptionالتي تُرجعهاlisten()واستدعِcancel()فيdispose(). - في Android، ألغِ تسجيل المستقبِلات، أوقف تحديثات الموقع، أو ألغِ المؤقتات داخل
onCancel. - في iOS، أبطل المؤقتات، أزل المراقبين، وأعد مرجع
eventSinkإلىnilفيonCancel. - لا تستدعِ أبداً
sink.success()بعد تشغيلonCancel— فعل ذلك يُعطل التطبيق.
EventSink / FlutterEventSink واستخدامه بعد استدعاء onCancel سيُطلق استثناءً. تحقق دائماً من القيمة الخالية أو اضبط مرجع المستقبِل على null/nil في معالج الإلغاء.تمرير معاملات إلى التدفق
يمكنك تمرير معاملات التهيئة من Dart إلى دالة الاستجابة الأصلية onListen عبر receiveBroadcastStream(arguments):
Dart — تمرير معاملات إلى onListen
// Dart: مرر معامل فترة التحديث
_subscription = _sensorChannel
.receiveBroadcastStream({'intervalMs': 500})
.listen((dynamic event) {
// معالجة الحدث
});
// Kotlin onListen تستقبله كخريطة Map
override fun onListen(arguments: Any?, sink: EventChannel.EventSink) {
val args = arguments as? Map<*, *>
val intervalMs = (args?.get("intervalMs") as? Int) ?: 1000
// استخدم intervalMs لضبط معدل استطلاع الاستشعار
}
الخلاصة
يتيح EventChannel تدفقاً مدفوعاً من الكود الأصلي إلى Dart للأحداث المستمرة. يستدعي جانب Dart receiveBroadcastStream().listen() للبدء ويلغي الاشتراك للتوقف. يستخدم Android EventChannel.StreamHandler مع EventSink؛ ويستخدم iOS FlutterStreamHandler مع FlutterEventSink. نظّف دائماً الموارد الأصلية في onCancel وأعد مرجع المستقبِل إلى null لمنع الأعطال بعد الإلغاء. تتيح المعاملات الاختيارية الممررة عبر receiveBroadcastStream(args) تهيئة التدفق الأصلي على أساس كل اشتراك.