יצירת תוסף Flutter לפונקציונליות מקורית
אתה משלב SDK קנייני של שותף—ספריית C++ עם קריאות מקוריות. אין תוספים מוכנים ב-pub.dev, ועטיפה דרך ffi אינה מעשית בגלל ניהול מחזור חיים מורכב—לדוגמה, טיפול נכון בזיכרון וסנכרון עם ה-Dart isolate. נשמע מוכר? אז אנחנו בונים תוסף מותאם אישית באמצעות Platform Channels. צוות הפיתוח הנייד שלנו, עם ניסיון של 5+ שנים, מטפל בפרויקטים כאלה מקצה לקצה: מעיצוב API ועד פרסום ב-pub.dev ואינטגרציה לפרויקט שלך. יישמנו למעלה מ-30 תוספים מותאמים אישית ללקוחות בפינטק, בריאות ו-IoT. אנו מקצצים בעלויות אינטגרציה בעד 40% באמצעות Pigeon ואופטימיזציה של ערוצי פלטפורמה.
איך לבחור את ערוץ הפלטפורמה הנכון?
בחירת הערוץ הנכון היא הבסיס לתוסף יציב. בואו נשווה שלוש אפשרויות:
| ערוץ | מטרה | תרחיש אופייני |
|---|---|---|
MethodChannel |
קריאה למתודה וקבלת תוצאה (בקשה/תגובה) | קבלת גרסת מערכת הפעלה, קריאת קובץ |
EventChannel |
הזרמת אירועים מקוד מקורי ל-Dart | הזרמת נתוני חיישנים, הרשמה להודעות BLE |
BasicMessageChannel |
העברת נתונים דו-כיוונית עם קודק מותאם אישית | החלפת מבנים מורכבים שאינם מתאימים ל-StandardMessageCodec |
דוגמה אופיינית: תוסף למכשירי BLE. סריקת מכשירים—EventChannel (זרם רציף של מכשירים שנמצאו). חיבור/ניתוק—MethodChannel. קבלת הודעות מאפיין—שוב EventChannel.
למה Pigeon עדיף על סריאליזציה ידנית?
StandardMessageCodec (ברירת מחדל) תומך בפרימיטיבים, List, Map. עבור אובייקטים מותאמים אישית, אנו מסריאליזציה ל-Map<String, dynamic> בצד Dart ומקבלים HashMap ב-Kotlin / [String: Any] ב-Swift. אבל זה נוטה לשגיאות הקלדה בשמות מפתחות. החלופה—Pigeon: כלי מצוות Flutter שמייצר APIs בטוחים מבחינת טיפוסים ממפרט .dart. Pigeon מייצר קוד Kotlin/Swift עם מחלקות מוקלדות, ומבטל שגיאות זמן ריצה משגיאות הקלדה בשמות מתודות. שימוש ב-Pigeon מקצר את זמן הפיתוח ב-30–40% ומפשט את התחזוקה. לפי התיעוד הרשמי של Flutter, Pigeon מומלץ לתוספים עם סריאליזציה מורכבת. פרטים נוספים ב-Platform Channels.
השווה בין שתי האסטרטגיות:
| גישה | בטיחות טיפוסים | זמן פיתוח | תחזוקה |
|---|---|---|---|
| סריאליזציה ידנית | לא (שגיאות זמן ריצה) | ארוך יותר (נדרשים בדיקות) | קשה יותר (הסכמה על מפתחות) |
| Pigeon | כן (בזמן קומפילציה) | 30–40% מהיר יותר | קל יותר (יצירה אוטומטית) |
איך אנחנו מפתחים את התוסף: שלבים
- ניתוח דרישות: אילו פונקציות מקוריות, הרשאות, מחזור חיים. אנו מעריכים היקף: 1–2 מתודות לפלטפורמה אחת, או 10+ עם EventChannel לשתיהן.
- עיצוב Dart API: חוזה דרך Pigeon או MethodChannel/EventChannel ידני. קביעת סוגי נתונים.
-
יישום iOS (Swift) ו-Android (Kotlin): שימוש ב-
FlutterPlugin,ActivityAware,MethodCallHandler. טיפול בכל מקרי הקצה והשגיאות. - בדיקות: בדיקות יחידה ב-Dart, בדיקות אינטגרציה בשתי הפלטפורמות, בדיקות על מכשירים אמיתיים.
- אינטגרציה לפרויקט שלך: חיבור דרך תלות git או פרסום ב-pub.dev.
- תיעוד: README עם API, דוגמאות, CHANGELOG.
טעויות נפוצות ב-Platform Channel
- קריאה כפולה ל-
result.success()ב-Android—IllegalStateException: Reply already submitted. אנו מוודאים שכל קריאה מסתיימת בדיוק פעם אחת. -
EventSinkדליפה בסיבוב מסך ב-Android. פתרון: איפוסsinkב-onCancel()ובדיקה לפני כל קריאה. - אי-התאמה בשם מתודה בין Dart לקוד מקורי. Pigeon מבטל זאת.
מבנה התוסף
נוצר דרך flutter create --template=plugin my_plugin. מבנה:
my_plugin/
lib/my_plugin.dart — Dart API
android/src/.../MyPlugin.kt — Android реализация
ios/Classes/MyPlugin.swift — iOS реализация
example/ — пример приложения для тестированияהצד Dart מצהיר על החוזה:
class MyPlugin {
static const MethodChannel _channel = MethodChannel('my_plugin');
static Future<String?> getPlatformVersion() async {
return await _channel.invokeMethod<String>('getPlatformVersion');
}
static Stream<ScanResult> get scanResults {
return const EventChannel('my_plugin/scan_results')
.receiveBroadcastStream()
.map((data) => ScanResult.fromMap(Map<String, dynamic>.from(data)));
}
} יישום Android: FlutterPlugin + ActivityAware
ב-Android, התוסף מיישם my_plugin/ lib/my_plugin.dart — Dart API android/src/.../MyPlugin.kt — Android реализация ios/Classes/MyPlugin.swift — iOS реализация example/ — пример приложения для тестирования למחזור חיים, class MyPlugin { static const MethodChannel _channel = MethodChannel('my_plugin'); static Future<String?> getPlatformVersion() async { return await _channel.invokeMethod<String>('getPlatformVersion'); } static Stream<ScanResult> get scanResults { return const EventChannel('my_plugin/scan_results') .receiveBroadcastStream() .map((data) => ScanResult.fromMap(Map<String, dynamic>.from(data))); } } לטיפול בקריאות. אם נדרש FlutterPlugin (לדוגמה, לבקשות הרשאות), בנוסף MethodCallHandler:
class MyPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
private lateinit var channel: MethodChannel
private var activity: Activity? = null
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(binding.binaryMessenger, "my_plugin")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"getPlatformVersion" -> result.success("Android ${android.os.Build.VERSION.RELEASE}")
else -> result.notImplemented()
}
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
}נקודה קריטית: Activity, ActivityAware, ו-class MyPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { private lateinit var channel: MethodChannel private var activity: Activity? = null override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(binding.binaryMessenger, "my_plugin") channel.setMethodCallHandler(this) } override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { "getPlatformVersion" -> result.success("Android ${android.os.Build.VERSION.RELEASE}") else -> result.notImplemented() } } override fun onAttachedToActivity(binding: ActivityPluginBinding) { activity = binding.activity } } חייבים להיקרא בדיוק פעם אחת. קריאה ל-result.success() פעמיים גורמת לקריסה עם result.error(). אנו מבטיחים שאין שגיאות כאלה בקוד שלנו.
יישום iOS ב-Swift
public class MyPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(
name: "my_plugin",
binaryMessenger: registrar.messenger()
)
let instance = MyPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
default:
result(FlutterMethodNotImplemented)
}
}
} EventChannel ודליפות זיכרון
בשימוש ב-result.notImplemented() ב-Android, הצד המקורי מקבל result.success(). דליפה אופיינית: החזקת IllegalStateException: Reply already submitted בשדה, Activity נוצר מחדש בסיבוב מסך, public class MyPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel( name: "my_plugin", binaryMessenger: registrar.messenger() ) let instance = MyPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "getPlatformVersion": result("iOS " + UIDevice.current.systemVersion) default: result(FlutterMethodNotImplemented) } } } ישן לא מאומת—קריאה ל-EventChannel לאחר השמדה זורקת חריגה. פתרון: איפוס EventSink ב-EventSink ובדיקה לפני כל קריאה.
פרסום וגרסאות
לשימוש פנימי, התוסף חי במאגר git ומחובר דרך תלות EventSink או sink.success() ב-sink. לפרסום ב-pub.dev, הרץ onCancel() עם שדות path חובה (git, pubspec.yaml) ו-flutter pub publish מלא.
מה כלול בעבודה
- Dart API ויישום מקורי ל-iOS ו-Android
- אינטגרציה לפרויקט שלך (דוגמת שימוש)
- תיעוד API ובנייה
- פרסום ב-pub.dev או גישה למאגר git
- הבטחה לערוצי פלטפורמה יציבים וטיפול בשגיאות
לוחות זמנים
ציר זמן פיתוח: פשוט (1–2 מתודות, פלטפורמה אחת)—2–4 ימים. תוסף מלא חוצה פלטפורמות עם EventChannel, הרשאות וטיפול במקרי קצה—2–4 שבועות. התמחור נקבע לאחר ניתוח. קבל ייעוץ: צור קשר להערכת הפרויקט שלך. כבר יישמנו למעלה מ-30 תוספים מותאמים אישית ללקוחות בפינטק, בריאות ו-IoT—הצטרף אליהם. הזמן פיתוח תוסף עכשיו.







