Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
Dizi · 43/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 13 başlık
- Genel Akış — Kim Kiminle Konuşuyor?
- Kurulum — Firebase Projesi Bağlama
- İzin İsteme — iOS İçin Zorunlu
- FCM Token — Cihazın "Adresi"
- Üç Farklı Uygulama Durumu, Üç Farklı Davranış
- 1. Uygulama Ön Planda (Foreground) — onMessage
- 2. Uygulama Arka Planda (Background) — onMessageOpenedApp
- 3. Uygulama Tamamen Kapalı (Terminated) — getInitialMessage
- Ön Planda Bildirim Gösterme — flutter_local_notifications
- Arka Plan Mesaj İşleyicisi — Neden main() Dışında, Üst Seviyede Olmalı?
- Konu Bazlı Yayın (Topics) — Toplu Bildirim
- 🎯 Bu Dersten Çıkarılması Gerekenler
- 📝 Ödevler
Bölüm 9'un son konusuna geldik. Şimdiye kadar öğrendiğimiz her şey, uygulama açıkken kullanıcıyla etkileşimdi. Push notification, bunun tam tersi bir senaryo: uygulama kapalıyken bile, sunucudan kullanıcıya bir mesaj ulaştırmak. Bunun için Flutter ekosisteminde en yaygın kullanılan araç Firebase Cloud Messaging (FCM)'dir.
Genel Akış — Kim Kiminle Konuşuyor?
Senin Sunucun (veya Firebase Console)
│
│ "Bu kullanıcıya şu mesajı gönder"
▼
Firebase Cloud Messaging (FCM) sunucuları
│
│ Bildirimi cihaza iletir
▼
Kullanıcının Telefonu (uygulama açık/kapalı/arka planda fark etmez)Her cihazdaki her uygulama kurulumu, FCM tarafından benzersiz bir "token" ile tanımlanır — sunucun, "şu token'a sahip cihaza bildirim gönder" diyerek belirli bir kullanıcıya ulaşır. Bu, Bölüm 5 Konu 20'de öğrendiğimiz HTTP isteklerinin tam tersi yönde çalışan bir mekanizma — orada Dart, sunucuya istek gönderiyordu; burada sunucu, Dart'a (FCM üzerinden) veri itiyor (bu yüzden "push").
Kurulum — Firebase Projesi Bağlama
flutter pub add firebase_core firebase_messagingPaketleri eklemek teknik başlangıç — asıl kurulum, flutterfire configure komutuyla (Firebase CLI aracı) yapılır; bu komut, Firebase konsolunda oluşturduğun projeyi, Android/iOS platform dosyalarına (google-services.json, GoogleService-Info.plist) otomatik olarak bağlar. Bu adım tutorial'ın kapsamı dışında (Firebase konsolu kullanımı gerektirir) ama akışı bilmen önemli.
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized(); // async main() öncesi gerekli
await Firebase.initializeApp();
runApp(const MyApp());
}WidgetsFlutterBinding.ensureInitialized() — Dart Bölüm 5'te öğrendiğimiz async/await'i main() içinde kullanabilmek için gereken bir hazırlık çağrısı; Flutter'ın widget motorunu, herhangi bir asenkron işlem (Firebase bağlantısı gibi) yapılmadan önce başlatır.
İzin İsteme — iOS İçin Zorunlu
Future<void> bildirimIzniIste() async {
final ayarlar = await FirebaseMessaging.instance.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (ayarlar.authorizationStatus == AuthorizationStatus.authorized) {
print('Kullanıcı bildirimlere izin verdi');
}
}Platform farkı önemli: Android'de (13 öncesi) bildirim izni otomatik verilmiş sayılırdı; Android 13'ten (API 33) itibaren, Android da çalışma zamanında izinister hale geldi — yani Konu 41'de öğrendiğimiz POST_NOTIFICATIONS izniyle aynı mantığa girdi. iOS'ta ise bu izin her zaman zorunluydu. firebase_messaging'in requestPermission()'ı, her iki platformda da tutarlı çalışan tek bir API sunuyor — Bölüm 9'un başından beri gördüğümüz "platforma özgü karmaşıklığı, tek bir Dart API'sinin arkasına gizleme" desenin bir başka örneği.
FCM Token — Cihazın "Adresi"
Future<String?> tokenAl() async {
final token = await FirebaseMessaging.instance.getToken();
print('FCM Token: $token');
return token;
}Bu token'ı, uygulamanın kendi sunucusuna (Bölüm 5 Konu 20'de öğrendiğimiz http/dio ile) göndermen gerekir — sunucun, hangi token'ın hangi kullanıcıya ait olduğunu kendi veritabanında eşleştirip saklar, ileride o kullanıcıya bildirim göndermek istediğinde bu token'ı kullanır.
Üç Farklı Uygulama Durumu, Üç Farklı Davranış
Push notification'ların en kafa karıştırıcı yanı, uygulamanın hangi durumda olduğuna göre (açık/arka planda/tamamen kapalı) farklı kod yollarının çalışmasıdır:
1. Uygulama Ön Planda (Foreground) — onMessage
FirebaseMessaging.onMessage.listen((RemoteMessage mesaj) {
print('Ön planda mesaj geldi: ${mesaj.notification?.title}');
// Dikkat: uygulama açıkken, sistem OTOMATİK bir bildirim GÖSTERMEZ!
// Kendi bildirim UI'ını göstermen gerekir (aşağıda göreceğiz).
});Kritik ve sık yapılan bir hata kaynağı: Uygulama açıkken bir push notification gelirse, işletim sistemi varsayılan olarak bildirimi ekranda göstermez — sadece onMessage stream'ine bir olay düşer. Bunun mantığı var: kullanıcı zaten uygulamanın içindeyse, sistem bildirimi yerine uygulama içi bir gösterim (bir SnackBar, bir banner) daha doğru bir kullanıcı deneyimidir. Yine de sistem bildirimi gibi görünen bir şey göstermek istersen, flutter_local_notifications paketiyle elle tetiklersin (birazdan göreceğiz).
2. Uygulama Arka Planda (Background) — onMessageOpenedApp
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage mesaj) {
print('Kullanıcı bildirime tıklayıp uygulamayı açtı: ${mesaj.data}');
// Bölüm 7 Konu 30'u hatırla — go_router ile ilgili ekrana yönlendir
// router.go('/urun/${mesaj.data['urunId']}');
});Uygulama arka plandayken (kapatılmamış ama görünür değil) bir bildirim gelirse, sistem otomatik olarak bildirimi gösterir. Kullanıcı bildirime tıklayıp uygulamayı öne getirdiğinde, onMessageOpenedApp tetiklenir — bu, Bölüm 7'de öğrendiğimiz deep linking ile doğal bir birleşim noktasıdır: bildirimin içindeki veriye (mesaj.data) göre, kullanıcıyı doğrudan ilgili ekrana yönlendirebilirsin.
3. Uygulama Tamamen Kapalı (Terminated) — getInitialMessage
Future<void> baslangicMesajiniKontrolEt() async {
final RemoteMessage? mesaj = await FirebaseMessaging.instance.getInitialMessage();
if (mesaj != null) {
print('Uygulama, bir bildirime tıklanarak AÇILDI: ${mesaj.data}');
// yine ilgili ekrana yönlendirme mantığı
}
}Uygulama tamamen kapalıyken bir bildirime tıklanırsa, uygulama sıfırdan başlar — onMessageOpenedApp bu senaryoda tetiklenmez (çünkü uygulama zaten çalışmıyordu, dinleyecek bir şey yoktu). Bunun yerine, main() çalıştıktan sonra getInitialMessage() ile "beni bir bildirim mi başlattı?" diye elle kontrol etmen gerekir.
Bu üçünü neden ayrı ayrı ele almak zorundayız? Çünkü Flutter/Dart kodu, uygulama kapalıyken çalışmıyor — her senaryo, işletim sisteminin kendi bildirim mekanizmasıyla farklı şekilde etkileşime giriyor, ve Flutter bunu üç farklı API ile sana yansıtıyor.
Ön Planda Bildirim Gösterme — flutter_local_notifications
flutter pub add flutter_local_notificationsfinal _yerelBildirimler = FlutterLocalNotificationsPlugin();
Future<void> onPlandaBildirimGoster(RemoteMessage mesaj) async {
const androidDetay = AndroidNotificationDetails(
'onemli_kanal', // kanal ID (Android 8+ için zorunlu)
'Önemli Bildirimler',
importance: Importance.high,
);
const detaylar = NotificationDetails(android: androidDetay);
await _yerelBildirimler.show(
mesaj.hashCode, // bildirim ID'si (Dart Bölüm 6 — her nesnenin hashCode'u vardır)
mesaj.notification?.title,
mesaj.notification?.body,
detaylar,
);
}
// onMessage dinleyicisinde çağır:
FirebaseMessaging.onMessage.listen(onPlandaBildirimGoster);Bu paket, FCM'den bağımsız olarak da çalışabilen, yerel (cihazda anlık oluşturulan) bildirimler göstermeni sağlar — burada onu, "ön planda gelen FCM mesajını, sanki sistem bildirimi gibi göster" amacıyla kullanıyoruz. NotificationDetails, Dart Bölüm 6'da öğrendiğimiz named parametreler ile platforma özgü ayarları (Android kanalı, iOS ses/rozet ayarları) bir arada tutan bir yapılandırma nesnesi.
Arka Plan Mesaj İşleyicisi — Neden main() Dışında, Üst Seviyede Olmalı?
@pragma('vm:entry-point')
Future<void> arkaPlanMesajIsleyici(RemoteMessage mesaj) async {
print('Arka planda/kapalıyken mesaj işleniyor: ${mesaj.messageId}');
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(arkaPlanMesajIsleyici);
runApp(const MyApp());
}@pragma('vm:entry-point') — bu garip görünen anotasyon zorunludur, çünkü arka plan işleyicisi, Dart Bölüm 13'te öğrendiğimiz isolate kavramını hatırlarsan, ayrı bir isolate'te, hatta uygulamanın kendisi kapalıyken bile çalıştırılabilir. Dart'ın derleyicisi, normalde "kullanılmayan" kodu optimize ederek kaldırabilir (tree-shaking) — @pragma('vm:entry-point'), derleyiciye "bu fonksiyonu silme, dışarıdan (native taraftan) çağrılacak" demenin yolu. Fonksiyonun üst seviye (top-level, bir sınıfın içinde değil) tanımlanması da aynı sebepten zorunlu — ayrı bir isolate'in, bir widget'ın state'ine ya da closure'a erişimi olmaz (Dart Bölüm 13'teki isolate izolasyonunu hatırla).
Konu Bazlı Yayın (Topics) — Toplu Bildirim
await FirebaseMessaging.instance.subscribeToTopic('kampanyalar');
// artık sunucu, TÜM 'kampanyalar' abonelerine TEK bir çağrıyla bildirim gönderebilir
await FirebaseMessaging.instance.unsubscribeFromTopic('kampanyalar');Her kullanıcıya tek tek token ile bildirim göndermek yerine (örneğin "yeni bir kampanya başladı" gibi herkese gidecek bir bildirim için), kullanıcıları bir "konuya" (topic) abone edip, sunucunun tek bir çağrıyla o konudaki herkese ulaşmasını sağlayabilirsin — bir e-posta bülteni aboneliğine kavramsal olarak çok benzer.
🎯 Bu Dersten Çıkarılması Gerekenler
- FCM, sunucunun bir cihaza kendi FCM token'ı üzerinden bildirim "itmesini" (push) sağlar; token, sunucunun tarafında kullanıcıyla eşleştirilip saklanmalıdır.
- Uygulamanın durumuna göre üç farklı API devreye girer:
onMessage(ön planda),onMessageOpenedApp(arka plandan tıklanarak açılma),getInitialMessage()(tamamen kapalıyken tıklanarak açılma). - Uygulama ön plandayken, sistem bildirimi otomatik göstermez —
flutter_local_notificationsile elle göstermen gerekir. - Arka plan mesaj işleyicisi, üst seviyede tanımlanmalı ve
@pragma('vm:entry-point')ile işaretlenmelidir — çünkü ayrı bir isolate'te, uygulama kapalıyken bile çalıştırılabilir. subscribeToTopic(), tek tek token yönetmeden, toplu bildirim göndermeyi sağlar.
📝 Ödevler
- [ ] Firebase projesi kurup (Firebase Console +
flutterfire configure), uygulamana FCM'i entegre et, kendi FCM token'ını konsolda yazdır. - [ ] Firebase Console'dan (veya Postman ile FCM API'sine) test bir bildirim gönder; uygulama sırasıyla ön planda, arka planda ve kapalıyken, üç farklı davranışı gözlemle.
- [ ]
flutter_local_notificationsile, ön planda gelen bir FCM mesajını sistem bildirimi gibi gösteren kodu ekle. - [ ] Bir bildirime tıklandığında,
mesaj.dataiçindeki bir ID'yi kullanarakgo_routerile ilgili detay ekranına yönlendiren kodu yaz (hemonMessageOpenedApphemgetInitialMessageiçin). - [ ] Kendi cümlelerinle, "neden arka plan mesaj işleyicisinin üst seviyede tanımlanması gerektiğini", Dart'ın isolate kavramıyla ilişkilendirerek açıkla.
Sıradaki konu: Bölüm 9 — Konu 44: Android/iOS Build Sistemleri (Bölüm 9'un Son Konusu)