↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 9 — Konu 40: Platform Channels

4 dk okuma #flutter
Dizi · 40/61 Flutter Türkçe Tutorial
  1. Bölüm 1 — Konu 1: Flutter Nedir, Mimarisi (Widget → Element → RenderObject, Skia/Impeller)
  2. Bölüm 1 — Konu 2: Ortam Kurulumu (Flutter SDK, Android Studio/VS Code, Emulator/Simulator, DevTools'a İlk Bakış)
  3. Bölüm 1 — Konu 3: İlk Proje Yapısı (`pubspec.yaml`, `lib/`, Klasör Mimarisi, `flutter create` Anatomisi)
  4. Bölüm 1 — Konu 4: Hot Reload / Hot Restart Ne Yapıyor, Neden Önemli
  5. Bölüm 2 — Konu 5: "Her Şey Widget'tır" Felsefesi, Widget Ağacı
  6. Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)
  7. Bölüm 2 — Konu 7: Temel Layout — Container, Row, Column, Stack, Padding, Align, Center
  8. Bölüm 2 — Konu 8: Constraint Sistemi — "Constraints Go Down, Sizes Go Up, Parent Sets Position"
  9. Bölüm 2 — Konu 9: `Expanded`, `Flexible`, `Spacer`, Intrinsic Widget'lar
  10. Bölüm 3 — Konu 10: MaterialApp/CupertinoApp, Scaffold, AppBar
  11. Bölüm 3 — Konu 11: `ListView`, `GridView`, `SingleChildScrollView` (+ Builder Pattern)
  12. Bölüm 3 — Konu 12: Text, TextStyle, Icon, Image, Asset Yönetimi
  13. Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
  14. Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes
  15. Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
  16. Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)
  17. Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)
  18. Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder
  19. Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`
  20. Bölüm 5 — Konu 20: HTTP İstekleri (`http` / `dio` Paketleri)
  21. Bölüm 5 — Konu 21: JSON Serialization (Manuel → `json_serializable`/`freezed`)
  22. Bölüm 5 — Konu 22: Local Storage — `shared_preferences` → `Hive` → `sqflite`/Drift
  23. Bölüm 6 — Konu 23: InheritedWidget ve InheritedModel — "Neden setState Yetmiyor" Sorusunun Cevabı
  24. Bölüm 6 — Konu 24: Provider Paketi
  25. Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)
  26. Bölüm 6 — Konu 26: BLoC/Cubit Pattern (`flutter_bloc`)
  27. Bölüm 6 — Konu 27: GetX (Tartışmalı Ama Yaygın)
  28. Bölüm 6 — Konu 28: Karşılaştırma — Hangi Projede Hangisi?
  29. Bölüm 7 — Konu 29: Navigator 2.0 (Router, RouteInformationParser, RouterDelegate)
  30. Bölüm 7 — Konu 30: `go_router` Paketi (Pratik ve Modern Çözüm)
  31. Bölüm 7 — Konu 31: Deep Linking
  32. Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
  33. Bölüm 7 — Konu 33: Dependency Injection (`get_it`, `injectable`)
  34. Bölüm 7 — Konu 34: Clean Architecture Uyarlaması, SOLID Prensipleri
  35. Bölüm 8 — Konu 35: Implicit Animasyonlar
  36. Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)
  37. Bölüm 8 — Konu 37: Hero Animasyonları
  38. Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
  39. Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu
  40. Bölüm 9 — Konu 40: Platform Channels
  41. Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)
  42. Bölüm 9 — Konu 42: Kamera, Konum, Sensörler
  43. Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
  44. Bölüm 9 — Konu 44: Android/iOS Build Sistemleri
  45. Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
  46. Bölüm 10 — Konu 46: CI/CD
  47. Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
  48. Bölüm 10 — Konu 48: DevTools Profiling
  49. Bölüm 10 — Konu 49: Lazy Loading, Pagination, Büyük Liste Performansı
  50. Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)
  51. Bölüm 11 — Konu 51: Supabase Alternatifi
  52. Bölüm 11 — Konu 52: GraphQL (Opsiyonel)
  53. Bölüm 11 — Konu 53: Store Yayınlama Süreci (İmzalama, Listing, Versiyonlama)
  54. Bölüm 11 — Konu 54: App Size, Obfuscation, Flavor Yönetimi
  55. Bölüm 12 — Konu 55: Flutter Web / Desktop
  56. Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
  57. Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)
  58. Bölüm 12 — Konu 58: Plugin Geliştirme, pub.dev'e Paket Yayınlama
  59. Bölüm 12 — Konu 59: Monorepo Mimarisi (Melos)
  60. Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine
  61. Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n)
Dizinin sayfası →
İçindekiler 10 başlık
  1. Flutter'ın Native ile İlişkisi — Kısa Hatırlatma
  2. Temel Fikir — İki Taraf, Bir "Kanal İsmi"
  3. MethodChannel — Dart Tarafı
  4. Native Taraf — Android (Kotlin)
  5. Native Taraf — iOS (Swift)
  6. EventChannel — Sürekli Veri Akışı İçin
  7. Pratik Gerçek — Genelde Elle Yazmana Gerek Kalmaz
  8. Pigeon — Daha Tip Güvenli Bir Alternatif
  9. 🎯 Bu Dersten Çıkarılması Gerekenler
  10. 📝 Ö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"

metin
┌─────────────────────┐         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ı

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

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)

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:

dart
class SensorServisi {
  static const _kanal = EventChannel('com.ornek.app/ivmeolcer');

  static Stream<double> ivmeVerisiAl() {
    return _kanal.receiveBroadcastStream().map((veri) => veri as double);
  }
}
dart
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 (Future döner), native taraftaki hatalar PlatformException olarak yakalanır.
  • Native tarafta (Android: Kotlin, iOS: Swift), setMethodCallHandler ile gelen çağrılar call.method string'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 bir Stream olarak 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ılan MethodChannel koduna göre daha tip güvenli, code-generation tabanlı bir alternatiftir.

📝 Ödevler

  • [ ] Basit bir MethodChannel kur (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ında PlatformException'ı yakalayıp ekranda göster.
  • [ ] Kendi cümlelerinle, "MethodChannel ile EventChannel arasındaki farkın, Dart Bölüm 5'te öğrendiğimiz Future ile Stream arasındaki farka nasıl karşılık geldiğini" açıkla.
  • [ ] battery_plus gibi hazır bir paketin kaynak koduna (pub.dev üzerinden GitHub'a) göz at, kendi native tarafında MethodChannel kullanıp kullanmadığını doğrula.

Sıradaki konu: Bölüm 9 — Konu 41: Permission Yönetimi (permission_handler)