Flutter Platform Channels: Native Kod Entegrasyonu
# Flutter Platform Channels ve Native Entegrasyon
Flutter tek bir kod tabanından iOS, Android ve daha fazlasına uygulama geliştirmenizi sağlar. Ancak er ya da geç, yalnızca platforma özgü kodla erişilebilen bir şeye ihtiyaç duyarsınız: tescilli bir SDK, mevcut bir plugin'i olmayan bir donanım sensörü ya da Flutter'ın henüz sarmalamadığı bir platform API'si. İşte tam bu noktada platform channel'lar devreye girer.
Platform channel'lar, Dart ile native kod arasındaki iletişim köprüsüdür. iOS'ta Swift/Objective-C, Android'de Kotlin/Java kodunu çağırmanıza, sonuçları geri almanıza ve hatta sürekli veri akışı oluşturmanıza olanak tanır. Bir projede istemcinin tescilli biyometrik SDK'sını entegre etmem gerektiğinde, Flutter plugin'i yoktu. Platform channel'lar projeyi kurtardı. Başka bir seçenek yoktu. Bu mekanizmayı anlamak, uygulamayı sıfırdan yazmak yerine doğrudan entegrasyon yapmamı sağladı.
Üç Kanal Türü
Flutter, farklı iletişim kalıpları için tasarlanmış üç farklı kanal türü sunar. Doğru olanı seçmek önemlidir.
MethodChannel
En yaygın kullanılan tür. Uzak prosedür çağrısı gibi çalışır: Dart bir metot adı ve argümanlar gönderir, native taraf mantığı çalıştırır ve sonucu döndürür. İletişim asenkron ve çift yönlüdür -- native kod da Dart tarafında metotları çağırabilir.
EventChannel
Native'den Dart'a sürekli veri akışı için tasarlanmıştır. Sensör okumaları, konum güncellemeleri veya Bluetooth tarama sonuçları gibi senaryolar düşünün. Native tarafta bir akış kurulur, Dart tarafında standart bir `Stream` nesnesiyle dinlersiniz.
BasicMessageChannel
En basit kanal türü. Seçtiğiniz bir codec (binary, string, JSON) ile yapılandırılmamış mesajlar gönderir ve alır. Metot çağrısı semantiğine ihtiyaç duymadığınız hafif iletişim senaryoları için kullanışlıdır.
MethodChannel: Ana İş Gücü
Dart Tarafı
import class="code-string">'package:flutter/services.dart';
class BatteryService {
static const _channel = MethodChannel(class="code-string">'com.example.app/battery');
Future<int> getBatteryLevel() async {
try {
final int level = await _channel.invokeMethod(class="code-string">'getBatteryLevel');
return level;
} on PlatformException catch (e) {
throw Exception(class="code-string">'Pil seviyesi alınamadı: ${e.message}');
}
}
Future<bool> isCharging() async {
final bool charging = await _channel.invokeMethod(class="code-string">'isCharging');
return charging;
}
}iOS Tarafı (Swift)
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let batteryChannel = FlutterMethodChannel(
name: "com.example.app/battery",
binaryMessenger: controller.binaryMessenger
)
batteryChannel.setMethodCallHandler { (call, result) in
switch call.method {
case "getBatteryLevel":
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
let level = Int(device.batteryLevel * 100)
if level >= 0 {
result(level)
} else {
result(FlutterError(
code: "UNAVAILABLE",
message: "Simülatörde pil seviyesi mevcut değil",
details: nil
))
}
case "isCharging":
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
let state = device.batteryState
result(state == .charging || state == .full)
default:
result(FlutterMethodNotImplemented)
}
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}Android Tarafı (Kotlin)
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.os.BatteryManager
import android.content.Context
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.app/battery"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"getBatteryLevel" -> {
val batteryManager =
getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val level = batteryManager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
)
if (level >= 0) {
result.success(level)
} else {
result.error("UNAVAILABLE", "Pil seviyesi alınamadı", null)
}
}
"isCharging" -> {
val batteryManager =
getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val charging = batteryManager.isCharging
result.success(charging)
}
else -> result.notImplemented()
}
}
}
}Kanal adı `com.example.app/battery` her iki tarafta da aynı olmalıdır. Plugin'lerle çakışmayı önlemek için ters-alan-adı gösterimi kullanıyorum.
EventChannel: Native Veri Akışı
EventChannel, native kodun sürekli veri ürettiği senaryolarda doğru araçtır.
Dart Tarafı
class AccelerometerService {
static const _eventChannel = EventChannel(class="code-string">'com.example.app/accelerometer');
Stream<AccelerometerReading> get readings {
return _eventChannel.receiveBroadcastStream().map((event) {
final data = Map<String, double>.from(event as Map);
return AccelerometerReading(
x: data[class="code-string">'x']!,
y: data[class="code-string">'y']!,
z: data[class="code-string">'z']!,
);
});
}
}
class AccelerometerReading {
final double x, y, z;
const AccelerometerReading({required this.x, required this.y, required this.z});
}iOS Tarafı (Swift)
import CoreMotion
class AccelerometerStreamHandler: NSObject, FlutterStreamHandler {
private let motionManager = CMMotionManager()
func onListen(withArguments arguments: Any?,
eventSink events: @escaping FlutterEventSink) -> FlutterError? {
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdates(to: .main) { data, error in
if let error = error {
events(FlutterError(
code: "SENSOR_ERROR",
message: error.localizedDescription,
details: nil
))
return
}
if let data = data {
events(["x": data.acceleration.x,
"y": data.acceleration.y,
"z": data.acceleration.z])
}
}
return nil
}
func onCancel(withArguments arguments: Any?) -> FlutterError? {
motionManager.stopAccelerometerUpdates()
return nil
}
}`onCancel` metodunu mutlaka doğru şekilde implement edin. Dart stream'i iptal edildiğinde sensörü durdurmayı unutmak, platform channel kodundaki en yaygın kaynak sızıntısı hatalarından biridir.
BasicMessageChannel
Metot çağrısı yapısına ihtiyaç duymadan basit mesajlar göndermek istediğinizde `BasicMessageChannel` en hafif seçenektir.
const messageChannel = BasicMessageChannel<String>(
class="code-string">'com.example.app/status',
StringCodec(),
);
class=class="code-string">"code-comment">// Mesaj gönder
await messageChannel.send(class="code-string">'ping');
class=class="code-string">"code-comment">// Nativeclass="code-string">'den mesaj al
messageChannel.setMessageHandler((String? message) async {
print('Native taraftan gelen: $messageclass="code-string">');
return 'onaylandı';
});Pratikte `BasicMessageChannel`'ı doğrudan nadiren kullanırım. `MethodChannel` çoğu durumu daha iyi karşılar. Ancak ham yapılandırma dizeleri veya basit durum bayrakları gibi senaryolarda bilinmesinde fayda var.
Pigeon: Tip Güvenli Kanallar
Platform channel kodunu elle yazmak hem zahmetli hem de hataya açıktır. Kanal adlarında yazım hataları olabilir, argüman map'lerinde anahtarlar eksik kalabilir ve dönüş tipleri tipi belirsiz `dynamic` değerlerdir. Pigeon bunların hepsini çözer.
Pigeon, Flutter ekibinin geliştirdiği bir kod üretim aracıdır. API'nizi bir Dart dosyasında tanımlarsınız, Pigeon sizin için Dart, Swift/Objective-C, Kotlin/Java ve C++ kodunu üretir. Her şey tip güvenlidir.
API Tanımı
import class="code-string">'package:pigeon/pigeon.dart';
class DeviceInfo {
String? model;
String? osVersion;
int? batteryLevel;
}
@HostApi()
abstract class DeviceApi {
DeviceInfo getDeviceInfo();
bool isFeatureSupported(String featureName);
}
@FlutterApi()
abstract class DeviceEventApi {
void onBatteryLevelChanged(int level);
}Üreticiyi çalıştırın:
dart run pigeon --input pigeons/device_api.dartBir projede elle yazılmış kanallardan Pigeon'a geçtiğimde, serileştirme hatalarının sayısı sıfıra düştü. İlk kurulum yirmi dakika sürer ama ilk refactor'da kendini amorti eder.
FFI: dart:ffi ile Doğrudan Native Çağrılar
Platform channel'lar mesaj iletimi yoluyla asenkron iletişim kurar. Performans kritik kodlar için `dart:ffi` kullanarak bu mekanizmayı tamamen atlayabilirsiniz. FFI, C/C++ fonksiyonlarını neredeyse sıfır ek yük ile doğrudan Dart'tan çağırır.
import class="code-string">'dart:ffi';
import class="code-string">'package:ffi/ffi.dart';
typedef NativeAddFunc = Int32 Function(Int32 a, Int32 b);
typedef DartAddFunc = int Function(int a, int b);
class NativeMath {
late final DartAddFunc _add;
NativeMath() {
final dylib = DynamicLibrary.open(class="code-string">'libnative_math.so');
_add = dylib.lookupFunction<NativeAddFunc, DartAddFunc>(class="code-string">'add_numbers');
}
int add(int a, int b) => _add(a, b);
}İlgili C kodu:
#include <stdint.h>
int32_t add_numbers(int32_t a, int32_t b) {
return a + b;
}FFI, görüntü işleme, kriptografi veya mesaj serileştirme ek yükünün kabul edilemez olduğu hesaplamalar için idealdir. Ancak yalnızca C uyumlu kütüphanelerle çalışır. Swift veya Kotlin API'leri için hala platform channel'lara ihtiyacınız var.
Büyük API yüzeyleri için C header dosyalarından otomatik Dart bağlamaları oluşturmak üzere `ffigen` paketini kullanın. FFI bağlamalarını elle yazmak bellek hatalarına davetiye çıkarır.
Hata Yönetimi
Platform sınırındaki hata yönetimi, platform channel kodunun en karmaşık noktasıdır. Native taraftaki istisnalar otomatik olarak Dart'a iletilmez. Hata iletim mekanizmalarını açıkça kullanmanız gerekir.
Native'den Dart'a Hatalar
iOS'ta `FlutterError` döndürün:
result(FlutterError(code: "PERMISSION_DENIED",
message: "Kamera izni verilmedi",
details: ["requiredPermission": "camera"]))Android'de `result.error()` kullanın:
result.error("PERMISSION_DENIED",
"Kamera izni verilmedi",
mapOf("requiredPermission" to "camera"))Dart tarafında `PlatformException` yakalayın:
try {
await _channel.invokeMethod(class="code-string">'startCamera');
} on PlatformException catch (e) {
if (e.code == class="code-string">'PERMISSION_DENIED') {
class=class="code-string">"code-comment">// İzin isteme dialogunu göster
} else {
debugPrint(class="code-string">'Platform hatası: ${e.code} - ${e.message}');
}
}Hata kodlarınızı platformlar arasında paylaşılan sabitler olarak tanımlayın. Dart'ta bir `ChannelErrorCodes` sınıfı tutup değerleri Swift ve Kotlin'de aynalıyorum. Bu, hata kodu dizelerindeki yazım hatalarının sessiz başarısızlıklara yol açmasını önler.
Platform Channels mı, Mevcut Plugin mi?
Herhangi bir platform channel kodu yazmadan önce pub.dev'i kapsamlı şekilde kontrol edin.
Mevcut bir plugin kullanın:
Kendi platform channel'larınızı yazın:
Sık Yapılan Hatalar
Thread Sorunları
Platform channel çağrıları platformun UI thread'inde gelir. Android'de bu ana thread demektir. Native kodunuz ağır iş yapıyorsa (ağ çağrıları, disk I/O, büyük hesaplamalar), arka plan thread'ine yönlendirmelisiniz. Aksi takdirde arayüz donar.
"code-comment">// Yanlış: Ana thread'i bloke eder
call.method == "processImage" -> {
val result = heavyImageProcessing() "code-comment">// Arayüzü dondurur
result.success(result)
}
"code-comment">// Doğru: Arka plan thread'ine yönlendir
call.method == "processImage" -> {
thread {
val processed = heavyImageProcessing()
Handler(Looper.getMainLooper()).post {
result.success(processed)
}
}
}iOS'ta sonuç callback'inin ana thread'de çağrılması gerektiğini unutmayın:
case "processImage":
DispatchQueue.global(qos: .userInitiated).async {
let processed = self.heavyImageProcessing()
DispatchQueue.main.async {
result(processed)
}
}Serileştirme Tuzakları
Platform channel'lar Standard Method Codec kullanır ve sınırlı bir tip kümesini destekler: `null`, `bool`, `int`, `double`, `String`, `Uint8List`, `Int32List`, `Float64List`, `List` ve `Map`. Bu tiplerden biri olmayan özel bir Dart nesnesi veya native nesne göndermeye çalışırsanız, çalışma zamanında codec hatası alırsınız.
Karmaşık nesneleri kanal üzerinden göndermeden önce her zaman `Map
result() Birden Fazla Kez Çağırmak
Native tarafta aynı metot çağrısı için `result()` fonksiyonunu birden fazla kez çağırmak uygulamayı çökertir. Bu hata özellikle hem başarı hem de hata callback'inin tetiklenebileceği callback tabanlı API'lerde kolay yapılır.
Kişisel Deneyimim
Bir fintech projesinde Flutter plugin'i olmayan native bir ödeme SDK'sını entegre etmem gerektiğinde, platform channel'lar tek seçenekti. SDK, karmaşık callback zincirleri, özel hata tipleri ve native olarak sunulması gereken UI bileşenleri içeriyordu.
Elle yazılmış MethodChannel'larla başladım. Çalıştı, ama üç dilde serileştirme kodunu sürdürmek acı vericiydi. SDK her güncellendiğinde Dart modelini, Swift serileştirmesini ve Kotlin serileştirmesini güncellememiz gerekiyordu. Üçüncü SDK güncellemesinden sonra Pigeon'a geçtim. Geçiş tek bir öğleden sonra sürdü ve sonraki SDK güncellemeleri, Pigeon tanım dosyasını güncelleyip üreticiyi yeniden çalıştırma meselesine dönüştü.
Temel ders: İletişim modelini anlamak için platform channel'larla başlayın, sonra iki-üçten fazla metotunuz olduğunda Pigeon'a geçin. C API'si olarak ifade edilebilen performans kritik kodlar için FFI kullanın.
Sonuç
Platform channel'lar, Flutter'ı gerçek anlamda esnek kılan kaçış kapısıdır. Framework'ün sunduklarıyla sınırlı değilsiniz. Herhangi bir native API, herhangi bir tescilli SDK, herhangi bir platform yeteneği erişilebilir durumdadır. Üç kanal türü farklı iletişim kalıplarını karşılar, Pigeon boilerplate'i ortadan kaldırır ve FFI performans kritik senaryoları halleder.
Bu araçlarda uzmanlaşın ve Flutter'ın baş edemeyeceği bir entegrasyon zorluğu kalmaz.
Native entegrasyon konusunda yardıma ihtiyacınız varsa veya platform channel mimarinizi birlikte tasarlamak isterseniz, benimle iletişime geçin.
İlgili Makaleler
Flutter Nedir? Kapsamlı Başlangıç Rehberi
Flutter'ın ne olduğunu, nasıl çalıştığını ve neden modern ürün ekiplerinin tercih ettiğini öğrenin. Dart, widget yapısı ve çoklu platform geliştirme sürecini keşfedin.
Flutter Performans Optimizasyonu: Kapsamlı Rehber
Flutter uygulamanızın performansını sistematik olarak artırın. Rebuild optimizasyonu, bellek yönetimi, lazy loading ve profiling tekniklerini öğrenin.
Flutter Testing Rehberi: Unit, Widget ve Integration Test
Flutter projelerinde test stratejisi kurun. Unit, widget ve integration test katmanlarını doğru sorumluluklarla yapılandırın.
Flutter Projeniz mi Var?
iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.
İletişime Geç