↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)

5 dk okuma #flutter
Dizi · 50/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 12 başlık
  1. Neden "Backend-as-a-Service"?
  2. Firebase Authentication — Kullanıcı Girişi
  3. Cloud Firestore — Gerçek Zamanlı NoSQL Veritabanı
  4. Veri Yazma
  5. Tek Seferlik Okuma vs Gerçek Zamanlı Dinleme
  6. .where() — Sorgulama
  7. Firestore ile Model Sınıfları — Bölüm 5 Konu 21'e Geri Dönüş
  8. Firebase Storage — Dosya Yükleme
  9. Cloud Functions — "Sunucu Tarafı Mantık" İçin Son Parça
  10. Güvenlik Kuralları — Neden Client'tan Erişim Güvenli Olabiliyor?
  11. 🎯 Bu Dersten Çıkarılması Gerekenler
  12. 📝 Ödevler

Bölüm 11'e ("Backend & Yayınlama") hoş geldin. Bölüm 9 Konu 43'te Firebase'i sadece push notification için kullanmıştık — aslında Firebase, çok daha geniş bir "backend-as-a-service" (BaaS) platformu. Bu derste, bir uygulamanın kendi sunucusunu yazmadan ihtiyaç duyduğu dört temel şeyi (kullanıcı girişi, veritabanı, dosya depolama, sunucu mantığı) nasıl karşılayacağını göreceğiz.

Neden "Backend-as-a-Service"?

Bölüm 5 Konu 20'de http/dio ile kendi sunucuna istek atmayı öğrenmiştik — ama o sunucuyu birinin yazması gerekiyordu. Firebase, yaygın backend ihtiyaçlarının (kullanıcı kaydı, veri saklama, dosya yükleme) hazır, ölçeklenen bir altyapısını sunar — sen sıfırdan bir backend yazmadan, doğrudan Flutter'dan bu servislere bağlanırsın. Bu, özellikle MVP/tek kişilik projelerde (senin de önceliğin olan "hızlı ürün çıkarma" hedefine) çok değerlidir.

Firebase Authentication — Kullanıcı Girişi

bash
flutter pub add firebase_auth
dart
import 'package:firebase_auth/firebase_auth.dart';

Future<UserCredential?> kayitOl(String eposta, String sifre) async {
  try {
    return await FirebaseAuth.instance.createUserWithEmailAndPassword(
      email: eposta,
      password: sifre,
    );
  } on FirebaseAuthException catch (hata) {
    print('Kayıt hatası: ${hata.code}'); // örn. 'email-already-in-use'
    return null;
  }
}

Future<UserCredential?> girisYap(String eposta, String sifre) async {
  try {
    return await FirebaseAuth.instance.signInWithEmailAndPassword(
      email: eposta,
      password: sifre,
    );
  } on FirebaseAuthException catch (hata) {
    print('Giriş hatası: ${hata.code}'); // örn. 'wrong-password', 'user-not-found'
    return null;
  }
}

FirebaseAuthException, Dart Bölüm 8'de öğrendiğimiz özel exception kavramının Firebase tarafındaki karşılığı — .code alanı, hangi hatanın olduğunu (yanlış şifre, zaten kayıtlı e-posta vb.) ayırt etmeni sağlar; bu, Konu 40'ta öğrendiğimiz PlatformException'ın mantığına çok benziyor.

Kullanıcının giriş durumunu takip etmek — bir Stream olarak:

dart
Stream<User?> kullaniciDurumu = FirebaseAuth.instance.authStateChanges();

Bölüm 5 Konu 19'da öğrendiğimiz Stream kavramının doğal bir kullanımı — kullanıcının giriş durumu değiştiğinde (giriş yaptı, çıkış yaptı, oturumu süresi doldu) bu stream otomatik olarak yeni bir değer yayınlar. Bunu, Bölüm 6'da öğrendiğimiz bir state management çözümüyle (Riverpod'da bir StreamProvider gibi) birleştirerek, uygulamanın her yerinde "kullanıcı giriş yapmış mı?" sorusuna anında cevap verebilirsin:

dart
StreamBuilder<User?>(
  stream: FirebaseAuth.instance.authStateChanges(),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return const AnaEkran(); // giriş yapılmış
    }
    return const GirisEkrani(); // giriş yapılmamış
  },
)

Bu kalıp, Bölüm 7 Konu 30'da go_router'ın redirect özelliğiyle birleştiğinde, "giriş yapılmadan belirli ekranlara erişilemez" kuralını merkezi olarak uygulamanın standart yoludur.

Cloud Firestore — Gerçek Zamanlı NoSQL Veritabanı

bash
flutter pub add cloud_firestore

Firestore, veriyi koleksiyonlar (collections) ve belgeler (documents) halinde saklar — Dart Bölüm 4'te öğrendiğimiz Map<String, dynamic> yapısına çok benzer, iç içe geçebilen bir JSON-benzeri yapı:

metin
kullanicilar (koleksiyon)
  └─ kullanici_id_1 (belge)
        isim: "Ahmet"
        yas: 30
  └─ kullanici_id_2 (belge)
        isim: "Ayşe"
        yas: 25

Veri Yazma

dart
await FirebaseFirestore.instance.collection('urunler').add({
  'isim': 'Kablosuz Kulaklık',
  'fiyat': 899.90,
  'stokta': true,
});

.add(...), Firestore'un otomatik olarak benzersiz bir ID ürettiği yeni bir belge oluşturur — Bölüm 8 Konu 37'de Hero için, Bölüm 10 Konu 47'de key için öğrendiğimiz "benzersiz kimlik" ihtiyacının, burada Firestore tarafından otomatik karşılandığını görüyorsun.

Tek Seferlik Okuma vs Gerçek Zamanlı Dinleme

dart
// Tek seferlik okuma (Future)
final belge = await FirebaseFirestore.instance.collection('urunler').doc('urun123').get();
print(belge.data()?['isim']);

// GERÇEK ZAMANLI dinleme (Stream) — verinin GÜNCEL halini SÜREKLİ alırsın
Stream<QuerySnapshot> urunAkisi = FirebaseFirestore.instance
    .collection('urunler')
    .where('stokta', isEqualTo: true) // Dart Bölüm 5'te öğrendiğimiz .where() FİLTRELEMESİNE benzer
    .snapshots();
dart
StreamBuilder<QuerySnapshot>(
  stream: urunAkisi,
  builder: (context, snapshot) {
    if (!snapshot.hasData) return const CircularProgressIndicator();

    final belgeler = snapshot.data!.docs;
    return ListView.builder( // Bölüm 3 Konu 11 + Bölüm 10 Konu 49'u hatırla
      itemCount: belgeler.length,
      itemBuilder: (context, index) {
        final urun = belgeler[index].data() as Map<String, dynamic>;
        return ListTile(title: Text(urun['isim']));
      },
    );
  },
)

Firestore'un en güçlü özelliği burada: .snapshots(), bir Stream döner — veritabanında başka bir kullanıcı (ya da senin kendi uygulaman, başka bir cihazdan) bir belge eklerse/değiştirirse/silerse, bu stream otomatik olarak yeni veriyi yayınlar, sen hiçbir "yenile" butonuna ihtiyaç duymazsın. Bu, Bölüm 9 Konu 42'de geolocator'ın getPositionStream()'i ile aynı "sürekli akan veri" felsefesini, bu sefer bir veritabanı için uyguluyor.

.where() — Sorgulama

.where('stokta', isEqualTo: true), Dart Bölüm 5'te öğrendiğimiz koleksiyon .where() metoduna kavramsal olarak benziyor ama önemli bir fark var: Dart'ın .where()'i client'ta, zaten indirilmiş bir listeyi filtreler; Firestore'un .where()'i, filtrelemeyi sunucu tarafında yapar — yani sadece eşleşen belgeler cihazına indirilir, bu da hem veri tasarrufu hem hız sağlar.

Firestore ile Model Sınıfları — Bölüm 5 Konu 21'e Geri Dönüş

dart
class Urun {
  final String id;
  final String isim;
  final double fiyat;

  Urun({required this.id, required this.isim, required this.fiyat});

  factory Urun.fromFirestore(DocumentSnapshot belge) {
    final veri = belge.data() as Map<String, dynamic>;
    return Urun(id: belge.id, isim: veri['isim'], fiyat: veri['fiyat']);
  }

  Map<String, dynamic> toFirestore() => {'isim': isim, 'fiyat': fiyat};
}

Bu, Bölüm 5 Konu 21'de öğrendiğimiz fromJson/toJson kalıbının birebir aynısı — sadece kaynak JSON yerine bir DocumentSnapshot. Bölüm 7'de öğrendiğimiz repository pattern ile birleştirirsen, Firestore, ProfilRepositoryImpl'in içindeki Dio çağrısının yerine geçen bir veri kaynağı olarak doğrudan slot içine oturur — domain katmanın (use case'ler) hangi veritabanını kullandığından habersiz kalmaya devam eder.

Firebase Storage — Dosya Yükleme

bash
flutter pub add firebase_storage
dart
Future<String> profilFotografiYukle(File dosya, String kullaniciId) async {
  final referans = FirebaseStorage.instance.ref().child('profil_fotograflari/$kullaniciId.jpg');
  await referans.putFile(dosya);
  return await referans.getDownloadURL(); // yüklenen dosyanın herkese açık URL'si
}

Bölüm 9 Konu 42'de image_picker ile cihazdan bir fotoğraf seçmeyi öğrenmiştik — Storage, o fotoğrafı buluta yüklemenin standart yoludur. getDownloadURL()'ün döndürdüğü URL'yi, Bölüm 3 Konu 12'de öğrendiğimiz Image.network(...) ile doğrudan kullanabilirsin.

Cloud Functions — "Sunucu Tarafı Mantık" İçin Son Parça

Auth, Firestore, Storage, client'tan doğrudan erişilebilir — ama bazı işlemlerin client'ta çalışmaması gerekir (örn. bir ödeme onaylama, hassas bir hesaplama, ya da bir kullanıcı silindiğinde ilişkili tüm verilerin otomatik temizlenmesi). Cloud Functions, JavaScript/TypeScript (ya da Python) ile yazılan, Firebase'in kendi sunucularında çalışan fonksiyonlardır — Flutter tarafından çağrılabilir ya da Firestore'daki bir değişikliğe tepki olarak otomatik tetiklenebilir.

dart
// Flutter tarafı — bir Cloud Function'ı çağırma
final callable = FirebaseFunctions.instance.httpsCallable('siparisOnayla');
final sonuc = await callable.call({'siparisId': '123'});

Not: Cloud Functions'ı yazmak TypeScript/JavaScript bilgisi gerektirir — senin kariyerinde de zaten planladığın (Flutter sonrası TypeScript) yol, tam olarak burada bir araya geliyor: Flutter (client) + Cloud Functions (TypeScript sunucu tarafı), aynı Firebase projesinde doğal bir kombinasyon oluşturur.

Güvenlik Kuralları — Neden Client'tan Erişim Güvenli Olabiliyor?

Firestore/Storage'a doğrudan client'tan erişmek ilk bakışta güvensiz görünebilir — "herkes benim veritabanıma yazabilir mi?" Cevap, Security Rules ile kontrol edilir (Firebase Console'da, ayrı bir dille yazılan kurallar):

metin
match /urunler/{urunId} {
  allow read: if true;                          // herkes okuyabilir
  allow write: if request.auth != null;          // sadece GİRİŞ YAPMIŞ kullanıcılar yazabilir
}

Bu kurallar, her istek için sunucu tarafında (Firebase'in kendi altyapısında) kontrol edilir — client kodu ne yaparsa yapsın, kurallara uymayan bir işlem reddedilir. Bu, Bölüm 9 Konu 41'de izinlerin native tarafta doğrulanmasına biraz benzer bir fikir: client'a güvenmezsin, kuralları her zaman merkezi bir yerde uygularsın.


🎯 Bu Dersten Çıkarılması Gerekenler

  • Firebase, kullanıcı girişi (Auth), veritabanı (Firestore), dosya depolama (Storage) ve sunucu mantığı (Functions) için kendi backend'ini yazmana gerek bırakmayan bir platform sunar.
  • authStateChanges(), kullanıcının giriş durumunu bir Stream olarak sunar — Bölüm 7'deki go_router redirect ile birleştiğinde merkezi bir giriş kontrolü sağlar.
  • Firestore, .get() (tek seferlik) ve .snapshots() (gerçek zamanlı Stream) ile okunabilir; .where() filtrelemesi sunucu tarafında çalışır.
  • Firestore verisi, Bölüm 5 Konu 21'deki fromJson/toJson kalıbıyla birebir aynı şekilde model sınıflarına dönüştürülür ve Bölüm 7'deki repository pattern'e doğal olarak oturur.
  • Storage, dosya yükleme/URL alma işini standartlaştırır; Cloud Functions, client'ta çalıştırılmaması gereken sunucu-taraflı mantığı barındırır.
  • Security Rules, client'a güvenmeden, sunucu tarafında erişim kontrolü uygular.

📝 Ödevler

  • [ ] Firebase Auth ile e-posta/şifre kayıt ve giriş akışı kur, FirebaseAuthException'ın farklı .code değerlerini (yanlış şifre, zaten kayıtlı e-posta) kullanıcıya anlaşılır mesajlarla göster.
  • [ ] authStateChanges() ile, giriş durumuna göre GirisEkrani/AnaEkran arasında otomatik geçiş yapan bir StreamBuilder kur.
  • [ ] Firestore'a bir koleksiyon (örn. "notlar") için veri ekle, .snapshots() ile gerçek zamanlı dinleyip ListView.builder ile göster; başka bir cihazdan (veya Firebase Console'dan) veri ekleyip anında güncellendiğini doğrula.
  • [ ] Firebase Storage'a bir resim yükle, getDownloadURL() ile aldığın URL'yi Image.network ile göster.
  • [ ] Firebase Console'da basit bir Security Rule yaz (örn. "sadece giriş yapmış kullanıcılar yazabilir"), giriş yapmadan yazma denediğinde reddedildiğini doğrula.

Sıradaki konu: Bölüm 11 — Konu 51: Supabase Alternatifi