Bölüm 9 — Konu 40: Platform Channels
Dizi · 40/61 Flutter Türkçe Tutorial
- Bölüm 1 — Konu 1: Flutter Nedir, Mimarisi (Widget → Element → RenderObject, Skia/Impeller)
- Bölüm 1 — Konu 2: Ortam Kurulumu (Flutter SDK, Android Studio/VS Code, Emulator/Simulator, DevTools'a İlk Bakış)
- Bölüm 1 — Konu 3: İlk Proje Yapısı (`pubspec.yaml`, `lib/`, Klasör Mimarisi, `flutter create` Anatomisi)
- Bölüm 1 — Konu 4: Hot Reload / Hot Restart Ne Yapıyor, Neden Önemli
- Bölüm 2 — Konu 5: "Her Şey Widget'tır" Felsefesi, Widget Ağacı
- Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)
- Bölüm 2 — Konu 7: Temel Layout — Container, Row, Column, Stack, Padding, Align, Center
- Bölüm 2 — Konu 8: Constraint Sistemi — "Constraints Go Down, Sizes Go Up, Parent Sets Position"
- Bölüm 2 — Konu 9: `Expanded`, `Flexible`, `Spacer`, Intrinsic Widget'lar
- Bölüm 3 — Konu 10: MaterialApp/CupertinoApp, Scaffold, AppBar
- Bölüm 3 — Konu 11: `ListView`, `GridView`, `SingleChildScrollView` (+ Builder Pattern)
- Bölüm 3 — Konu 12: Text, TextStyle, Icon, Image, Asset Yönetimi
- Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
- Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes
- Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
- Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)
- Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)
- Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder
- Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`
- Bölüm 5 — Konu 20: HTTP İstekleri (`http` / `dio` Paketleri)
- Bölüm 5 — Konu 21: JSON Serialization (Manuel → `json_serializable`/`freezed`)
- Bölüm 5 — Konu 22: Local Storage — `shared_preferences` → `Hive` → `sqflite`/Drift
- Bölüm 6 — Konu 23: InheritedWidget ve InheritedModel — "Neden setState Yetmiyor" Sorusunun Cevabı
- Bölüm 6 — Konu 24: Provider Paketi
- Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)
- Bölüm 6 — Konu 26: BLoC/Cubit Pattern (`flutter_bloc`)
- Bölüm 6 — Konu 27: GetX (Tartışmalı Ama Yaygın)
- Bölüm 6 — Konu 28: Karşılaştırma — Hangi Projede Hangisi?
- Bölüm 7 — Konu 29: Navigator 2.0 (Router, RouteInformationParser, RouterDelegate)
- Bölüm 7 — Konu 30: `go_router` Paketi (Pratik ve Modern Çözüm)
- Bölüm 7 — Konu 31: Deep Linking
- Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
- Bölüm 7 — Konu 33: Dependency Injection (`get_it`, `injectable`)
- Bölüm 7 — Konu 34: Clean Architecture Uyarlaması, SOLID Prensipleri
- Bölüm 8 — Konu 35: Implicit Animasyonlar
- Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)
- Bölüm 8 — Konu 37: Hero Animasyonları
- Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
- Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu
- Bölüm 9 — Konu 40: Platform Channels
- Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)
- Bölüm 9 — Konu 42: Kamera, Konum, Sensörler
- Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
- Bölüm 9 — Konu 44: Android/iOS Build Sistemleri
- Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
- Bölüm 10 — Konu 46: CI/CD
- Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
- Bölüm 10 — Konu 48: DevTools Profiling
- Bölüm 10 — Konu 49: Lazy Loading, Pagination, Büyük Liste Performansı
- Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)
- Bölüm 11 — Konu 51: Supabase Alternatifi
- Bölüm 11 — Konu 52: GraphQL (Opsiyonel)
- Bölüm 11 — Konu 53: Store Yayınlama Süreci (İmzalama, Listing, Versiyonlama)
- Bölüm 11 — Konu 54: App Size, Obfuscation, Flavor Yönetimi
- Bölüm 12 — Konu 55: Flutter Web / Desktop
- Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
- Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)
- Bölüm 12 — Konu 58: Plugin Geliştirme, pub.dev'e Paket Yayınlama
- Bölüm 12 — Konu 59: Monorepo Mimarisi (Melos)
- Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine
- Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n)
İçindekiler 10 başlık
- Flutter'ın Native ile İlişkisi — Kısa Hatırlatma
- Temel Fikir — İki Taraf, Bir "Kanal İsmi"
- MethodChannel — Dart Tarafı
- Native Taraf — Android (Kotlin)
- Native Taraf — iOS (Swift)
- EventChannel — Sürekli Veri Akışı İçin
- Pratik Gerçek — Genelde Elle Yazmana Gerek Kalmaz
- Pigeon — Daha Tip Güvenli Bir Alternatif
- 🎯 Bu Dersten Çıkarılması Gerekenler
- 📝 Ödevler
Bölüm 9'a ("Platform & Native") hoş geldin. Şimdiye kadar öğrendiğimiz her şey Dart/Flutter dünyasının içindeydi. Ama bazen, cihazın pil seviyesini okumak, native bir SDK'yı (örneğin bir ödeme kütüphanesini) kullanmak, ya da Flutter'ın henüz sarmalamadığı bir platform özelliğine erişmek gerekir. Bu derste, Dart kodunun Android'in Kotlin/Java'sıyla ya da iOS'un Swift/Objective-C'siyle nasıl "konuştuğunu" öğreneceğiz.
Flutter'ın Native ile İlişkisi — Kısa Hatırlatma
Bölüm 1 Konu 1'de öğrendiğimiz mimariyi hatırla: Flutter, kendi widget'larını Skia/Impeller ile doğrudan ekrana çizer — yani native UI bileşenlerini kullanmaz (bir Android Button'ı ya da iOS UIButton'ı değildir, Flutter'ın kendi çizdiği bir dikdörtgendir). Ama Flutter uygulaması, sonuçta gerçek bir Android/iOS uygulamasının içinde çalışır — ve bazen, o native ortamın kendi API'lerine (kamera, konum, dosya sistemi, üçüncü parti SDK'lar) erişmek gerekir. Platform Channel, bu ihtiyacı karşılayan köprüdür.
Temel Fikir — İki Taraf, Bir "Kanal İsmi"
┌─────────────────────┐ Platform Channel ┌──────────────────────┐
│ Dart tarafı │ ── 'com.ornek.app/pil' ──────► │ Native taraf │
│ (MethodChannel) │ ◄── sonuç/hata ──────────────── │ (Kotlin veya Swift) │
└─────────────────────┘ └──────────────────────┘İki taraf da aynı string ismindeki bir kanalı kullanarak birbirini bulur — tıpkı Bölüm 7 Konu 30'da go_router'da bir rota ismiyle (path) eşleşme yapmamız gibi, burada da kanal ismi ('com.ornek.app/pil' gibi) eşleşme anahtarı.
MethodChannel — Dart Tarafı
import 'package:flutter/services.dart';
class PilServisi {
static const _kanal = MethodChannel('com.ornek.app/pil');
static Future<int> pilSeviyesiniAl() async {
try {
final int seviye = await _kanal.invokeMethod('pilSeviyesiAl');
return seviye;
} on PlatformException catch (hata) {
print('Pil seviyesi alınamadı: ${hata.message}');
return -1;
}
}
}Parça parça inceleyelim:
MethodChannel('com.ornek.app/pil') — kanalın adı, genelde paket.adi/ozellik formatında (çakışmayı önlemek için). Bu isim, native tarafta birebir aynı yazılmalı.
await _kanal.invokeMethod('pilSeviyesiAl') — Bölüm 5 Konu 18'de öğrendiğimiz async/await'i hatırla; native tarafa bir çağrı yapmak asenkron bir işlemdir (native kod çalışıp bir sonuç dönene kadar Dart bekler) — bu yüzden Future<int> döner.
on PlatformException catch (hata) — Dart Bölüm 8'de öğrendiğimiz özel exception yakalama; native taraftan bir hata gelirse (kod bulunamadı, native tarafta bir exception fırlatıldı vb.), bu PlatformException olarak Dart'a taşınır.
Native Taraf — Android (Kotlin)
// android/app/src/main/kotlin/.../MainActivity.kt
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val KANAL = "com.ornek.app/pil"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, KANAL).setMethodCallHandler { call, result ->
if (call.method == "pilSeviyesiAl") {
val seviye = pilSeviyesiniGetir()
if (seviye != -1) {
result.success(seviye)
} else {
result.error("KULLANILAMIYOR", "Pil seviyesi alınamadı", null)
}
} else {
result.notImplemented()
}
}
}
private fun pilSeviyesiniGetir(): Int {
// gerçek Android API çağrısı burada olurdu
return 85
}
}Kotlin bilmene gerek yok (bu tutorial'ın kapsamı dışında) — ama yapıyı tanıman önemli: call.method, Dart tarafındaki invokeMethod('pilSeviyesiAl') çağrısındaki string ile eşleşiyor (Bölüm 3 Konu 12'de öğrendiğimiz switch/if mantığına benzer bir yönlendirme). result.success(seviye), Dart tarafındaki await'in döndüreceği değeri belirliyor; result.error(...), Dart tarafında PlatformException olarak yakalanacak hatayı oluşturuyor.
Native Taraf — iOS (Swift)
// ios/Runner/AppDelegate.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 kanal = FlutterMethodChannel(name: "com.ornek.app/pil", binaryMessenger: controller.binaryMessenger)
kanal.setMethodCallHandler { (call, result) in
if call.method == "pilSeviyesiAl" {
result(85) // gerçek iOS API çağrısı burada olurdu
} else {
result(FlutterMethodNotImplemented)
}
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}Kritik gözlem: Aynı kanal ismi ("com.ornek.app/pil") ve aynı metod ismi ("pilSeviyesiAl"), hem Android hem iOS tarafında birebir aynı yazılmalı — Dart tarafındaki MethodChannel kodu, hangi platformda çalıştığını bilmez, sadece kanal ismiyle "doğru tarafı" bulur. Bu, Bölüm 7'de öğrendiğimiz interface/implementasyon ayrımına (bir Repository arayüzünün farklı implementasyonları gibi) kavramsal olarak benzer — Dart tarafı, hangi native implementasyonun çalıştığından habersiz, sadece sözleşmeye (kanal ismi + metod isimleri) güveniyor.
EventChannel — Sürekli Veri Akışı İçin
MethodChannel, tek seferlik bir istek-cevap içindir (bir soru sor, bir cevap al). Native taraftan sürekli veri akması gerekiyorsa (örneğin bir sensörden gelen sürekli okuma), EventChannel kullanılır — Bölüm 5 Konu 19'da öğrendiğimiz Stream kavramının native köprü karşılığıdır:
class SensorServisi {
static const _kanal = EventChannel('com.ornek.app/ivmeolcer');
static Stream<double> ivmeVerisiAl() {
return _kanal.receiveBroadcastStream().map((veri) => veri as double);
}
}StreamBuilder<double>( // Bölüm 5 Konu 19'u hatırla
stream: SensorServisi.ivmeVerisiAl(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const CircularProgressIndicator();
return Text('İvme: ${snapshot.data}');
},
)Native taraf, EventChannel'a bir EventSink üzerinden sürekli değer gönderir (sink.success(deger)), Dart tarafında bu, tanıdık bir Stream<double>'a dönüşür — Bölüm 5 Konu 19'daki StreamBuilder ile aynı şekilde tüketilir.
Pratik Gerçek — Genelde Elle Yazmana Gerek Kalmaz
Önemli bir uyarı: Gerçek geliştirmede, çoğu zaman platform channel'ı elle yazman gerekmez — camera, geolocator, battery_plus, local_auth gibi hazır paketler, bu native köprüyü senin için zaten kurmuş durumda, sen sadece Dart tarafındaki basit API'yi çağırırsın (Bölüm 9 Konu 42'de kamera/konum paketlerini göreceğiz). Platform channel'ı elle yazmak, genelde şu durumlarda gerekir:
- Çok niş bir native SDK'ya erişim gerekiyor ve hazır bir Flutter paketi yok.
- Kendi bir Flutter paketini (plugin) yazıp yayınlıyorsun (Bölüm 12 Konu 58'de göreceğiz).
- Var olan bir native uygulamaya Flutter'ı entegre ediyorsun (add-to-app senaryosu).
Pigeon — Daha Tip Güvenli Bir Alternatif
Yukarıdaki MethodChannel kodunda dikkat ettiysen, call.method bir string, dönüş değeri dinamik (Any/Object) — yazım hatası riski var, derleme zamanında yakalanmaz. Flutter ekibinin resmi olarak sunduğu pigeon paketi, bir arayüz tanımından (Dart'ta yazılan bir "şema" dosyasından) hem Dart hem Kotlin hem Swift tarafı için tip güvenli kod üretir (Dart Bölüm 13'te öğrendiğimiz build_runner code generation mantığına benzer). Büyük/uzun soluklu platform channel ihtiyaçlarında pigeon, elle MethodChannel yazmaktan daha sağlam bir tercihtir — bu tutorial kapsamında sadece var olduğunu bilmen yeterli.
🎯 Bu Dersten Çıkarılması Gerekenler
- Platform Channel, Dart kodu ile native (Kotlin/Swift) kod arasında, ortak bir kanal ismi üzerinden çalışan bir köprüdür.
MethodChannel, tek seferlik istek-cevap iletişimi için kullanılır;invokeMethod()asenkron çalışır (Futuredöner), native taraftaki hatalarPlatformExceptionolarak yakalanır.- Native tarafta (Android: Kotlin, iOS: Swift),
setMethodCallHandlerile gelen çağrılarcall.methodstring'ine göre yönlendirilir,result.success()/result.error()ile cevap verilir. EventChannel, native taraftan sürekli veri akışı gerektiğinde kullanılır — Dart tarafında tanıdık birStreamolarak tüketilir.- Pratikte, çoğu native ihtiyaç zaten hazır paketlerle (camera, geolocator vb.) karşılanır — elle platform channel yazmak niş durumlar içindir.
pigeon, elle yazılanMethodChannelkoduna göre daha tip güvenli, code-generation tabanlı bir alternatiftir.
📝 Ödevler
- [ ] Basit bir
MethodChannelkur (Android veya iOS tarafında, hangisini geliştiriyorsan), sabit bir değer (örn.42) döndüren bir native metod yaz ve Dart tarafından çağır. - [ ] Native tarafta bilerek bir
result.error(...)(Android) / hata durumu (iOS) tetikle, Dart tarafındaPlatformException'ı yakalayıp ekranda göster. - [ ] Kendi cümlelerinle, "
MethodChannelileEventChannelarasındaki farkın, Dart Bölüm 5'te öğrendiğimizFutureileStreamarasındaki farka nasıl karşılık geldiğini" açıkla. - [ ]
battery_plusgibi hazır bir paketin kaynak koduna (pub.dev üzerinden GitHub'a) göz at, kendi native tarafındaMethodChannelkullanıp kullanmadığını doğrula.
Sıradaki konu: Bölüm 9 — Konu 41: Permission Yönetimi (permission_handler)