Flutter mobil uygulama geliştirme projelerinde Pigeon kullanarak Dart ile Kotlin/Swift arasındaki platform kanalını tip güvenli, test edilebilir ve ölçülebilir biçimde kurmayı ele alıyoruz.
Flutter Mobil Uygulama Geliştirmede Pigeon ile Tip Güvenli Kanallar
Flutter mobil uygulama geliştirme için MethodChannel sınırı
Kamera üreticisine özgü bir SDK, Android Keystore anahtarı veya iOS Keychain erişim grubu kullanmanız gerektiğinde Dart kodu platform API'sine geçmek zorundadır. MethodChannel ile doğrudan Map<String, dynamic> taşımak küçük entegrasyonlarda kabul edilebilir; ancak alan adı hataları, sayısal tür dönüşümleri ve platformlar arası hata sözleşmesinin dağılması derleme zamanında görünmez. Örneğin Android tarafında Long, Dart tarafında int olarak beklenirken bir alanın yanlışlıkla String gönderilmesi ancak ilgili cihaz akışında ortaya çıkar.
Önce çağrı yüzeyini ölçülebilir biçimde çıkarın: hangi yöntem, hangi payload boyutu, hangi hata kodları ve hangi iş parçacığı kısıtları var? Flutter DevTools'ta Network yerine Performance görünümünü kullanın; platform çağrısını dart:developer ile işaretlemek, UI thread'de bloklama olup olmadığını Timeline'da ayırmanızı sağlar. Bu ayrım özellikle cross platform mobil uygulama geliştirme ekiplerinde Android ve iOS implementasyonlarının aynı Dart API'sini koruması için gereklidir.
import 'dart:developer' as developer;
Future<T> tracedPlatformCall<T>(
String name,
Future<T> Function() operation,
) async {
return developer.Timeline.timeSync(
name,
operation,
arguments: {'boundary': 'dart-to-native'},
);
}
final token = await tracedPlatformCall(
'secure-token.read',
() => deviceApi.readToken('refresh_token'),
);Buradaki kritik incelik şudur: kanal çağrısını her build() içinde başlatmayın. Bir yeniden çizim sırasında FutureBuilder(future: deviceApi.readToken(...)) ile yeni Future üretmek, state değişiminde çağrıyı tekrarlar; Keychain/Keystore erişimi ve kanal serileştirmesi gereksiz yere çoğalır. Future'ı initState içinde saklayın veya kullandığınız flutter state management katmanında tekil bir komut olarak tetikleyin.
Pigeon ile flutter state management katmanından tipli API üretmek
Pigeon, API şemasından Dart çağrı kodunu ve Kotlin/Swift host arayüzlerini üretir. Böylece repository veya notifier yalnızca üretilen DeviceApi tipini bilir; platform kanalı adı ve codec ayrıntıları UI katmanına sızmaz. Bir flutter eğitimi veya flutter kursu kapsamında bu ayrım çoğu zaman basit bir kanal örneğinin ötesine geçmez; üretim kodunda asıl kazanç, şema değiştiğinde Dart ve native derleyicilerinin aynı sözleşme kırılmasını göstermesidir.
Şemayı uygulama paketinden ayrı bir pigeons/ dizininde tutun ve çıktıları kaynak kontrolüne alma kararını ekip standardı yapın. CI üzerinde yeniden üretim yapıyorsanız çıktıları commit etmeyip fark kontrolü yapmak, geliştiricinin yerel olarak eski üreteç çıktısıyla derleme almasını engeller.
// pigeons/device_api.dart
import 'package:pigeon/pigeon.dart';
class SecureValue {
SecureValue({required this.value, required this.updatedAtEpochMs});
final String value;
final int updatedAtEpochMs;
}
@HostApi()
abstract class DeviceApi {
SecureValue? readToken(String key);
void writeToken(String key, String value);
void deleteToken(String key);
}Üretimi tekrarlanabilir bir komuta bağlayın; komutun sürümünü pubspec.lock ile kilitlemek önemlidir, çünkü üreteç değişikliği native dosya imzalarını etkileyebilir.
dart run pigeon --input pigeons/device_api.dart --dart_out lib/platform/generated/device_api.g.dart --kotlin_out android/app/src/main/kotlin/com/example/app/DeviceApi.g.kt --kotlin_package com.example.app --swift_out ios/Runner/DeviceApi.g.swiftNullable dönüşteki edge case'i açıkça tasarlayın: SecureValue? yalnızca "kayıt bulunamadı" anlamına gelsin. Keychain erişim grubunun yetkisiz olması, Keystore anahtarının geçersizleşmesi veya disk I/O hatası null olmamalıdır; bunları platformda istisna olarak üretin ve Dart'ta kullanıcı oturumunu düşürme, yeniden deneme veya telemetri kararını hata türüne göre verin. Aksi halde gerçek güvenlik hatası boş token gibi yorumlanır.
Kotlin ve Swift tarafında hata sözleşmesini sabitleme
Android implementasyonunda üretilen arayüzü doğrudan platform servisinin üstüne koymak yerine küçük bir adapter kullanın. Örnekte getString sonucu olmayan kayıt için null döndürür; şifre çözme veya depolama hatası ise istisna olarak Pigeon hata zarfına taşınır. Android tarafında anahtar okumasını UI thread'e zorlayan bir SDK varsa, implementasyonu coroutine ile I/O dispatcher'a taşıyın; native ana thread'de bloklanan 40 ms'lik çağrı Flutter raster thread'i değil ama uygulamanın platform event işleme kapasitesini etkiler.
class DeviceApiImpl(
private val storage: SecureStorage,
) : DeviceApi {
override fun readToken(key: String): SecureValue? {
return storage.getString(key)?.let { value ->
SecureValue(value, System.currentTimeMillis())
}
}
override fun writeToken(key: String, value: String) {
require(key.matches(Regex("[a-z0-9_.-]{1,80}"))) {
"INVALID_KEY"
}
storage.putString(key, value)
}
}Swift'te aynı semantiği koruyun: Keychain'den errSecItemNotFound gelirse nil, diğer OSStatus değerlerinde ise hata üretin. Yaygın hata, tüm OSStatus sonuçlarını boş değer gibi yutmaktır; bu yaklaşım erişim grubu entitlement hatasını ilk kurulumda değil, kullanıcının oturumunun rastgele kaybolduğu bir destek kaydında görünür hale getirir. Hata ayrıntısında token veya kişisel veri taşımayın; yalnızca operation=read, sayısal status ve anahtarın hash'lenmiş etiketi gibi diagnostik veri kaydedin.
Dart tarafında platform ayrıntısını widget'a taşımayın. Örneğin Riverpod kullanılıyorsa Notifier yalnızca DeviceApi sonucundan durum üretmeli, PlatformException metnini UI'a vermemelidir. Bu yapı dart programlama eğitimi sırasında anlatılan bağımlılık enjeksiyonu prensibini somutlaştırır: testte gerçek binary messenger yerine sahte bir TokenStore vererek oturum akışını native çalıştırmadan doğrulayabilirsiniz.
abstract interface class TokenStore {
Future<String?> read();
}
final class PigeonTokenStore implements TokenStore {
PigeonTokenStore(this._api);
final DeviceApi _api;
@override
Future<String?> read() async =>
(await _api.readToken('refresh_token'))?.value;
}Platform kanalında profil alma ve payload maliyetini doğrulama
Kanal katmanında optimizasyon yapmadan önce profil build ile baz çizgi alın: flutter run --profile komutuyla gerçek cihazda akışı en az 20 kez çalıştırın, DevTools Performance görünümünde secure-token.read Timeline event'lerinin p50 ve p95 sürelerini kaydedin. Debug build'deki assertion'lar, JIT davranışı ve servis protokolü ek yükü bu karşılaştırma için güvenilir değildir. Android için aynı senaryoda Perfetto kaydı alıp Kotlin metodunun ana thread mi yoksa binder/I/O beklemesinde mi olduğunu doğrulayın.
Örnek bir önce-sonra deneyi: API ilk tasarımda 200 ayar anahtarını Map<String, dynamic> olarak tek seferde döndürüyor ve çağrı başına ortalama 48 KB codec payload üretiyor olsun. Şemayı yalnızca gerekli üç alanı taşıyan SecureValue ve ayrı readToken çağrısına dönüştürdükten sonra aynı cihaz, aynı release benzeri veri ve 20 tekrar altında p95 payload/süreyi karşılaştırın. Amaç "daha hızlı" demek değil; örneğin DevTools event argümanına payload byte sayısını koyarak 48 KB'den 300 byte altına inip inmediğini ve p95'in I/O nedeniyle değişmediğini ayırmaktır.
Ölçümü otomatikleştirmek için entegrasyon testini gerçek platforma koşturun; test başarısı tek başına süre garantisi vermez, ancak regressions için üst sınır sağlar.
// integration_test/platform_latency_test.dart
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('token read stays below the latency budget', (tester) async {
final watch = Stopwatch()..start();
await deviceApi.readToken('refresh_token');
watch.stop();
expect(watch.elapsedMilliseconds, lessThan(150));
});
}Bu 150 ms sınırını evrensel sabit kabul etmeyin; düşük seviye cihaz, soğuk Keychain erişimi ve emülatör için dağılım farklıdır. CI'da fiziksel cihaz havuzu yoksa mutlak milisaniye assertion'ını nightly job'a alın, normal PR hattında ise API sözleşmesi ve hata eşleme testlerini çalıştırın. Flutter state management katmanında ayrıca aynı anahtar için eşzamanlı iki okumanın iki native çağrı açıp açmadığını kontrol edin; gerekli ise repository içinde devam eden Future'ı cache'leyerek istek birleştirme uygulayın.
Üretilen kanal kodunu CI ve sürümleme akışına bağlamak
Pigeon şemasındaki alan sırası ve nullable değişiklikleri kablosuz güncelleme gibi düşünülmemelidir: Flutter istemcisi ile native host kodu aynı uygulama paketi içinde derlense bile feature branch'lerde üretilen dosya unutulabilir. CI'da önce üretim komutunu çalıştırıp sonra çalışma ağacının temiz kaldığını doğrulayın. Bu kontrol, şema değiştirip yalnızca Dart çıktısını commit eden bir değişikliği merge öncesinde yakalar.
dart run pigeon --input pigeons/device_api.dart --dart_out lib/platform/generated/device_api.g.dart --kotlin_out android/app/src/main/kotlin/com/example/app/DeviceApi.g.kt --kotlin_package com.example.app --swift_out ios/Runner/DeviceApi.g.swift
git diff --exit-code -- lib/platform/generated android/app/src/main/kotlin ios/RunnerŞema uyumluluğunda güvenli yön, yeni alanı nullable eklemek ve eski davranışı korumaktır; zorunlu alan eklemek eski host implementasyonlarını derleme aşamasında kırar. Bu kırılma monorepo için istenebilir, fakat bağımsız paketlenen eklentilerde migration notu ve eşzamanlı platform değişikliği gerektirir. Flutter mobil uygulama geliştirme kod tabanında generated dosyalara manuel düzenleme yapmayın: bir sonraki üretim değişikliği sessizce siler.
Bu yaklaşım, cross platform mobil uygulama geliştirme işinde platforma özgü kodu yok etmez; sınırı açık hale getirir. UI ekibi TokenStore sözleşmesini, Android/iOS ekipleri ise üretilen host arayüzünü sahiplenir. Sonuç olarak hata kodları, null semantiği, payload şekli ve performans bütçesi code review'da incelenebilir somut artefaktlara dönüşür.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
Flutter eğitimi kapsamında MethodChannel yerine Pigeon ne zaman kullanılmalı?
Birden fazla yöntem, nullable veri, platforma özgü hata veya Kotlin/Swift tarafında ayrı ekip sahipliği varsa Pigeon kullanın. Tek bir fire-and-forget çağrıda MethodChannel yeterli olabilir; ancak Pigeon şemasını pigeons/*.dart altında tutup dart run pigeon ile üretmek, alan adı ve tip uyuşmazlıklarını derleme aşamasına taşır.
Flutter kursu projelerinde Pigeon üretilen kodu Git'e eklemeli miyim?
Ekipte yerel üreteç sürümü farklılaşmasını azaltmak için generated dosyaları commit edip CI'da yeniden üretim sonrası git diff --exit-code kontrolü çalıştırmak pratik bir yaklaşımdır. Dosyaları commit etmeyecekseniz CI ve tüm geliştirici ortamları aynı kilitli Pigeon bağımlılığını kurmak zorundadır.
Dart programlama eğitimi için platform kanal gecikmesi nasıl ölçülür?
flutter run --profile ile fiziksel cihazda çalışın, çağrıyı Timeline.timeSync ile işaretleyin ve Flutter DevTools Performance ekranında en az 20 tekrarın p50/p95 değerini kaydedin. Değişiklikten sonra aynı veri ve cihazla tekrar ölçün; Android tarafındaki beklemeyi ayırmak için Perfetto izi kullanın.
Flutter state management içinde platform kanal çağrısı build metodunda yapılır mı?
Yapılmamalı. build() tekrar çalıştıkça yeni Future üretilebilir ve aynı native çağrı yinelenir. Çağrıyı notifier/repository komutuna taşıyın; eşzamanlı istekler bekleniyorsa anahtar bazlı devam eden Future cache'i ile tek native çağrıda birleştirin.
AI / LLM Discovery
Bu makale Opendart Akademi Flutter eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.



