Flutter mobil uygulama geliştirmede Pigeon kullanarak Dart ve native katmanlar arasındaki veri sözleşmesini tip güvenli, test edilebilir ve ölçülebilir hale getirin. Şema evrimi, hata kodları ve kanal gecikmesini ele alın.
Flutter Mobil Uygulama Geliştirmede Pigeon ile Tip Güvenli Kanallar
Flutter mobil uygulama geliştirmede MethodChannel yerine Pigeon sözleşmesi
Bir flutter eğitimi veya flutter kursu içinde MethodChannel genellikle hızlı bir başlangıç olarak gösterilir; ancak büyüyen kod tabanında String kanal adı, dinamik Map anahtarları ve runtime cast'leri gerçek bir sözleşme değildir. Örneğin Dart tarafında 'token_expiry' beklerken Android tarafının 'tokenExpiry' yazması ancak ilgili akış çalıştığında görünür. pigeon, tek bir Dart şemasından Dart, Kotlin ve Swift bağlayıcıları üretir. Bu, cross platform mobil uygulama geliştirme ekiplerinde API yüzeyini kod incelemesinde değişiklik olarak görünür kılar.
Pigeon tanımını uygulama katmanından ayrı bir pigeons/ dizininde tutun ve her alanın nullability kararını iş kuralıyla verin. Aşağıdaki örnekte expiresAtEpochMs nullable'dır; çünkü native depoda token bulunabilir ama sunucu JWT son kullanma alanını taşımıyor olabilir. Buna karşılık value nullable değildir: çağıran taraf token yoksa boş String ile devam etmek yerine açık bir hata almalıdır.
import 'package:pigeon/pigeon.dart';
@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/generated/token_store.g.dart',
kotlinOut: 'android/app/src/main/kotlin/com/acme/app/TokenStore.g.kt',
kotlinOptions: KotlinOptions(package: 'com.acme.app'),
swiftOut: 'ios/Runner/TokenStore.g.swift',
))
class TokenStoreConfig {}
class TokenRecord {
String value;
int? expiresAtEpochMs;
TokenRecord({required this.value, this.expiresAtEpochMs});
}
@HostApi()
abstract class TokenStoreApi {
TokenRecord? read();
void write(TokenRecord record);
void clear();
}Üretimi yerel geliştiricinin eline bırakmayın; CI aşamasında üretip çalışma ağacının temiz olduğunu doğrulayın. Bu kontrol, şema değiştiği halde iOS veya Android bağlayıcısının commit edilmemesi sorununu yakalar. Komutu paket kökünde çalıştırın: dart run pigeon --input pigeons/token_store.dart && git diff --exit-code -- lib/generated android/app/src/main/kotlin ios/Runner. dart programlama eğitimi açısından önemli incelik şudur: Pigeon dosyası da Dart'tır, fakat burada yazılan sınıflar domain modeliniz değildir. Generated API'ye ait taşıma nesnelerini doğrudan Riverpod, Bloc veya repository katmanına sızdırmak, native şema değişikliğini uygulamanın geri kalanına gereksizce yayar.
Pigeon şemasında nullability, sürümleme ve hata sınırları
Şemaya yeni zorunlu alan eklemek eski native binary ile çalışan Dart kodunu kırar. Mobil dağıtımda Dart kodu ile Android/iOS binary'si normalde birlikte çıkar, fakat add-to-app, bağımsız Flutter module güncellemesi veya enterprise kademeli dağıtım bu varsayımı bozar. Geriye uyumlu değişiklik için önce nullable alan ekleyin, Dart'ta varsayılanı belirleyin, tüm hedef binary'ler yayıldıktan sonra ancak ayrı bir breaking-change planıyla zorunlu hale getirin. API davranışını da sürümleyin: sadece alanları değil, örneğin clear() işleminin biyometrik anahtarı silip silmediğini sözleşmede açıklayın.
Native istisnaları ham haliyle taşımayın. Kotlin'deki KeyPermanentlyInvalidatedException veya iOS'taki Keychain hata kodu Dart domain katmanının bağımlılığı olmamalıdır. Bunları sınırlı ve belgelenmiş PlatformException kodlarına dönüştürün; çağıran taraf yalnızca güvenli biçimde toparlanabileceği kodları tanısın. Aşağıdaki Android uygulaması, oturum akışının token yok ile güvenli depo hatasını ayırmasına izin verir.
class AndroidTokenStoreApi(
private val store: TokenStore
) : TokenStoreApi {
override fun read(): TokenRecord? = try {
store.read()?.let { TokenRecord.Builder()
.setValue(it.value)
.setExpiresAtEpochMs(it.expiresAtEpochMs)
.build() }
} catch (e: KeyPermanentlyInvalidatedException) {
throw FlutterError(
"key_invalidated",
"The device credential changed; sign-in is required.",
null
)
} catch (e: GeneralSecurityException) {
throw FlutterError("secure_storage_failed", "Unable to read token.", null)
}
}Dart tarafında yalnızca beklenen hata kodlarını işleyin ve diğerlerini gözlem aracına bağlamadan yutmayın. Özellikle her PlatformException için token silmek yaygın bir hatadır: disk doluluğu veya geçici Keychain erişim problemi gibi durumlarda kullanıcının geçerli oturumunu geri döndürülemez biçimde kaybedersiniz. key_invalidated için oturumu sıfırlayın, secure_storage_failed için yeniden deneme veya destek akışı üretin ve Sentry gibi bir araçta code, platform ve uygulama sürümünü etiket olarak gönderin.
Future<Token?> loadToken(TokenStoreApi api) async {
try {
final record = await api.read();
return record == null ? null : Token(record.value, record.expiresAtEpochMs);
} on PlatformException catch (error, stackTrace) {
if (error.code == 'key_invalidated') return null;
await Sentry.captureException(error, stackTrace: stackTrace);
throw TokenStorageUnavailable(error.code);
}
}Flutter state management katmanında platform bağımlılığını izole etmek
flutter state management içinde generated TokenStoreApi nesnesini doğrudan widget veya notifier'a vermek, testleri binary messenger'a bağlar. Bunun yerine küçük bir repository arayüzü tanımlayın. Böylece Riverpod, Bloc veya başka bir durum yönetimi çözümü native ayrıntıyı değil SessionRepository davranışını görür. Üretimde Pigeon adapter'ı, testte bellek içi fake kullanılır; testler emulator, Keychain veya Android Keystore gerektirmez.
abstract interface class SessionRepository {
Future<Token?> currentToken();
Future<void> save(Token token);
}
final sessionRepositoryProvider = Provider<SessionRepository>((ref) {
return PigeonSessionRepository(TokenStoreApi());
});
class SessionController extends AsyncNotifier<SessionState> {
@override
Future<SessionState> build() async {
final token = await ref.read(sessionRepositoryProvider).currentToken();
return token == null ? const SignedOut() : SignedIn(token);
}
}Bu sınırın ikinci faydası eşzamanlılık kontrolüdür. Örneğin kullanıcı logout'a bastığında clear(), aynı anda yenileme isteğinin save() çağrısıyla yarışabilir. Native kanaldaki mesaj sırası, HTTP isteğinin tamamlanma sırasını garanti etmez. Repository içinde monoton bir oturum nesli tutun; eski nesilden dönen yenileme sonucunu yazmayın. Bu teknik, logout sonrası token'ın tekrar depoya yazılması gibi yalnızca üretimde görülen yarışları engeller.
class SessionWriter {
int _generation = 0;
Future<void> signOut(SessionRepository repository) async {
_generation++;
await repository.clear();
}
Future<void> persistRefresh(SessionRepository repository, Token token, int startedAt) async {
if (startedAt != _generation) return;
await repository.save(token);
}
int beginRefresh() => _generation;
}Buradaki edge case, uygulamanın process ölümünden sonra bellekteki neslin kaybolmasıdır. Bu nedenle token kaydına server'ın verdiği issuedAt veya artan bir sessionVersion ekleyin; depodan okurken sunucu oturum sürümüyle karşılaştırın. Sadece Dart tarafında sayaç tutmak yeniden başlatma sonrası koruma sağlamaz. Repository birim testinde sıralamayı doğrulayın: fake repository'nin save metodunu Completer ile bekletin, logout çalıştırın, sonra completer'ı tamamlayın ve son depolama durumunun boş kaldığını assert edin.
Kanal çağrılarını DevTools ile ölçmek ve toplu API tasarlamak
Pigeon tip güvenliği sağlar, fakat çok sayıda küçük mesajın gecikmesini otomatik olarak düşürmez. Örneğin açılışta 40 tercih için 40 ayrı read(key) çağrısı yapmak; Dart serileştirme, platform thread'e geçiş, native dispatch ve dönüş serileştirmesini 40 kez çalıştırır. Ölçümü debug modda yapmayın: flutter run --profile ile fiziksel cihazda çalıştırın, Flutter DevTools Performance görünümünde başlangıç akışını kaydedin ve en az 20 soğuk olmayan tekrarın p50/p95 değerini karşılaştırın.
Dart zaman çizelgesine kendi işaretlerinizi ekleyin. DevTools'ta yalnızca platform zamanını görmek, gecikmenin Pigeon serileştirmesinden mi yoksa native depolamadan mı kaynaklandığını söylemez. Aşağıdaki blok, toplu okumanın uygulama tarafındaki süresini ayrı bir event olarak gösterir. Aynı test senaryosunda önce tekli çağrıları, sonra readAll çağrısını kaydedin; p95 değerini CI performans bütçenize örneğin 16 ms frame sınırından bağımsız bir açılış hedefi olarak koyun.
import 'dart:developer' as developer;
Future<Map<String, String?>> loadBootstrapValues(SettingsApi api) async {
return developer.Timeline.timeSync('settings.readAll', () async {
return api.readAll(<String>[
'locale', 'theme', 'lastWorkspace', 'featureFlags'
]);
});
}Şema değişikliği somut olarak tekli metod yerine toplu metod eklemektir. İlk sürümde read(String key) metodunu kaldırmayın; eski çağıranlar için bırakın, kritik başlangıç yolu yeni API'ye geçsin. Native tarafta tek transaction veya tek Keychain sorgusu kullanabilmek için anahtar listesini bir defada işleyin. Önce-sonra raporunda çağrı sayısını, DevTools'taki settings.readAll p50/p95 değerini ve native trace süresini yan yana yazın. Sadece ortalama süreye bakmak, ara sıra 100 ms üstüne çıkan Keystore erişimlerini gizler.
@HostApi()
abstract class SettingsApi {
String? read(String key);
Map<String, String?> readAll(List<String> keys);
}
// Android implementasyonunda tek snapshot alınır.
override fun readAll(keys: List<String>): Map<String, String?> {
val snapshot = sharedPreferences.all
return keys.associateWith { key -> snapshot[key] as? String }
}Generated kanallar için contract test ve release kontrolü
Pigeon üretimi derleme hatalarının bir kısmını yakalar, ancak davranış sözleşmesini doğrulamaz. Android ve iOS'ta boş değer, Unicode anahtar, büyük payload ve hata kodu için integration test yazın. Flutter'ın integration_test paketi ile gerçek binary messenger üzerinden çağrı yaparak generated codec, native implementasyon ve Dart adapter'ını birlikte doğrulayabilirsiniz. Test verisinde emoji içeren bir değer kullanmak önemlidir; byte uzunluğunu karakter sayısı sayan native kodlar bu durumda kesilmiş UTF-8 üretir.
Release kontrolünde generated dosyaların elle değiştirilmediğini ve şemanın tekrar üretilebilir olduğunu doğrulayın. Örnek GitHub Actions adımı şudur: dart format --set-exit-if-changed pigeons/ && dart run pigeon --input pigeons/token_store.dart && git diff --exit-code. Ardından Android için emulator, iOS için simulator üzerinde aynı integration test paketini çalıştırın. Bu yaklaşım, bir platformdaki generated dosyanın eski kalmasıyla oluşan 'Dart derleniyor ama iOS çağrıda codec hatası veriyor' sınıfı hatayı merge öncesinde yakalar.
Sözleşme değişikliğinde PR açıklamasına üç veri ekleyin: eklenen veya kaldırılan metodlar, nullability değişimleri ve hata kodu matrisi. Örneğin TokenRecord.value: String -> String? teknik olarak küçük görünür, fakat repository'nin 'token yok' ve 'bozuk native cevap' durumlarını ayırma kabiliyetini yok eder. Bu disiplin, Flutter katmanını native platformlardan tamamen soyutlamaz; bunun yerine iki taraf arasındaki kaçınılmaz bağımlılığı derlenebilir, ölçülebilir ve test edilebilir bir sınıra taşır.
İlgili Eğitim
Sık Sorulan Sorular
Flutter mobil uygulama geliştirmede Pigeon ne zaman MethodChannel yerine kullanılmalı?
Dart ve Kotlin/Swift arasında 3 veya daha fazla metod, yapılandırılmış veri modeli ya da kritik hata kodları varsa Pigeon kullanın. CI'da `dart run pigeon --input pigeons/api.dart` çalıştırıp generated dosyalar için `git diff --exit-code` kontrolü ekleyin. Tek seferlik, prototip seviyesindeki bir çağrı için MethodChannel kabul edilebilir; ancak Map anahtarlarını string literal olarak çoğaltmayın.
Flutter state management içinde Pigeon API doğrudan provider'a verilir mi?
Doğrudan vermeyin. Generated API'yi `PigeonSessionRepository` gibi bir adapter içinde tutun ve provider'dan bir repository arayüzü yayınlayın. Böylece notifier testinde binary messenger yerine fake repository kullanır, `PlatformException` kodlarını da tek noktada domain hatasına dönüştürürsünüz.
Cross platform mobil uygulama geliştirme projelerinde platform kanal gecikmesi nasıl ölçülür?
Fiziksel cihazda `flutter run --profile` ile çalıştırın, DevTools Performance kaydı alın ve Dart tarafında `Timeline.timeSync('settings.readAll', ...)` işareti ekleyin. Tekli 40 çağrı ile bir `readAll(List
Dart programlama eğitimi kapsamında Pigeon şemasına yeni alan eklerken nelere dikkat edilmeli?
Önce nullable alan ekleyin, Dart adapter'ında deterministik varsayılan tanımlayın ve eski binary'lerle integration test çalıştırın. Yeni alanı aynı anda zorunlu yapmak, kademeli dağıtım veya add-to-app senaryosunda eski native bağlayıcının mesajı üretememesine neden olabilir.
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.



