Flutter Platform Channels: Native Kod Entegrasyonu

13 dakika okuma9 Mart 2026
Flutter platform channelsFlutter MethodChannelFlutter native integrationFlutter PigeonFlutter FFIFlutter iOS integrationFlutter Android integrationFlutter EventChannel

# 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ı

dart
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)

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)

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ı

dart
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)

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.

dart
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ı

dart
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:

bash
dart run pigeon --input pigeons/device_api.dart

Bir 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.

dart
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:

c
#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:

swift
result(FlutterError(code: "PERMISSION_DENIED",
                    message: "Kamera izni verilmedi",
                    details: ["requiredPermission": "camera"]))

Android'de `result.error()` kullanın:

kotlin
result.error("PERMISSION_DENIED",
             "Kamera izni verilmedi",
             mapOf("requiredPermission" to "camera"))

Dart tarafında `PlatformException` yakalayın:

dart
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:

  • Bakımı iyi yapılan bir plugin varsa (pub.dev puanını, son güncellemeyi ve issue tracker'ı kontrol edin).
  • Plugin ihtiyaçlarınızı tamamen veya büyük ölçüde karşılıyorsa.
  • Plugin, Flutter ekibinden veya güvenilir bir yayıncıdan geliyorsa.
  • Kendi platform channel'larınızı yazın:

  • Tescilli bir native SDK entegre etmeniz gerekiyorsa.
  • İhtiyacınız olan platform API'si için plugin yoksa.
  • Mevcut plugin'ler bakımsız veya kritik hataları varsa.
  • Native implementasyon üzerinde ayrıntılı kontrol istiyorsanız.
  • Performans gereksinimleri mesaj iletimi yerine FFI gerektiriyorsa.
  • 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.

    kotlin
    "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:

    swift
    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` formatına serileştirin. Daha da iyisi, bu tür hataları tamamen önlemek için Pigeon kullanın.

    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 Projeniz mi Var?

    iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.

    İletişime Geç