↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)

5 dk okuma #flutter
Dizi · 25/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 13 başlık
  1. Riverpod Neden Var? — Provider'ın Sınırlamaları
  2. Paketi Ekleme
  3. ProviderScope — Uygulamayı Sarmalamak
  4. Provider (Riverpod'un Kendi Terimi) — Basit, Değişmeyen Değerler
  5. Notifier — Değişebilir State'i Tanımlamanın Güncel Yolu
  6. Widget İçinde Kullanım — ConsumerWidget
  7. ref.read() — Callback'lerde Kullanım
  8. ConsumerStatefulWidget — StatefulWidget + Riverpod
  9. Bir Adım İleri — Code Generation (@riverpod)
  10. Provider vs Riverpod — Karşılaştırma
  11. Pratik Tavsiye
  12. 🎯 Bu Dersten Çıkarılması Gerekenler
  13. 📝 Ödevler

Riverpod Neden Var? — Provider'ın Sınırlamaları

Bir önceki derste Provider'ı öğrendik — güçlü bir araç, ama BuildContext'e bağımlı olmasından kaynaklanan bazı sınırlamaları var:

dart
// Provider ile — context olmadan state'e erişemezsin
context.read<SepetModeli>().ekle('Ürün');

Bu, widget ağacının dışında (örneğin bir initState() dışı yardımcı fonksiyonda, ya da tamamen widget olmayan bir test kodunda) state'e erişmeyi zorlaştırır. Ayrıca, Provider'da aynı tipte birden fazla provider tanımlamak (örneğin iki farklı SepetModeli — belki bir "geçici sepet", bir "onaylı sepet") karmaşıklaşır.

Riverpod, aynı ekip (Provider'ın da yaratıcısı) tarafından geliştirilen, bu sınırlamaları çözen bir sonraki nesil state management çözümüdür. Önemli: "Riverpod", Provider'ın harflerinin anagramıdır (P-R-O-V-I-D-E-R → R-I-V-E-R-P-O-D) — bu, paketin Provider'ın ruhani halefi olduğunu isim düzeyinde bile gösteriyor.

Bir uyarı: Riverpod, zaman içinde API'sini bir kez büyük ölçüde değiştirdi. İnternette veya eski kaynaklarda göreceğin StateNotifier + StateNotifierProvider kalıbı artık eski (legacy) kabul ediliyor — hâlâ çalışıyor ama Riverpod ekibi yeni projelerde bunun yerine Notifier + NotifierProvider kullanılmasını öneriyor. Bu derste doğrudan güncel kalıbı öğreneceğiz.

Paketi Ekleme

bash
flutter pub add flutter_riverpod

ProviderScope — Uygulamayı Sarmalamak

dart
void main() {
  runApp(
    ProviderScope( // tüm provider'ları barındıran kök widget
      child: MyApp(),
    ),
  );
}

ProviderScope, tüm provider'ları (Riverpod'da "provider" terimi biraz farklı kullanılır — birazdan göreceğiz) barındıran, context'e bağımlı olmayan bir depoyu (store) temsil eder. runApp()'ın en dışına, bir kere, uygulamanın kökünde konur.

Provider (Riverpod'un Kendi Terimi) — Basit, Değişmeyen Değerler

dart
final versiyonProvider = Provider<String>((ref) => '1.0.0');

Dart Bölüm 5 Konu 21'de öğrendiğimiz fonksiyonları değişkene atama kavramını hatırlarsan — burada Provider<String>(...), bir fonksiyon alıyor ((ref) => '1.0.0', bir anonim fonksiyon), ve bu, global bir değişken gibi tanımlanıyor. Ama dikkat: bu gerçek bir global değişken değil — Riverpod'un kendi izole edilmiş deposunda yaşıyor (Dart Bölüm 12'de öğrendiğimiz kütüphane/scope kavramına benzer bir izolasyon).

Notifier — Değişebilir State'i Tanımlamanın Güncel Yolu

Provider dersinde ChangeNotifier ile notifyListeners() çağırarak state güncellemesi yapmıştık. Riverpod'un güncel karşılığı Notifier sınıfıdır:

dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

class SepetNotifier extends Notifier<int> {
  @override
  int build() => 0; // başlangıç state — StatefulWidget'taki initState gibi düşünebilirsin

  void ekle() {
    state = state + 1; // 'state' özel bir alan — atama otomatik olarak dinleyicileri günceller
  }
}

final sepetProvider = NotifierProvider<SepetNotifier, int>(SepetNotifier.new);

Notifier<int> — Dart Bölüm 9'da öğrendiğimiz generic sınıf kullanımı burada net görülüyor: <int>, bu notifier'ın hangi tipte state tuttuğunu belirtiyor.

build() metodu — eski yaklaşımdan temel fark: Eski StateNotifier API'sinde başlangıç değeri constructor'da (super(0)) veriliyordu. Güncel Notifier API'sinde, tıpkı bir widget'ın build() metodu gibi, build() metodu çağrılır ve döndürdüğü değer başlangıç state'i olur. Bu tasarım, Riverpod'un widget yaşam döngüsüyle tutarlı bir mantık kurmasını sağlıyor — provider'lar da widget'lar gibi "yeniden inşa edilebilir" (örneğin ref.invalidate() ile).

SepetNotifier.new — bu, Dart'ın constructor tear-off sözdizimi: (ref) => SepetNotifier() yazmak yerine, doğrudan constructor'a bir referans veriyoruz (fonksiyonu değişkene atama kavramının constructor'lara uygulanmış hali). İkisi de aynı işi yapar, .new biraz daha kısa ve idiomatik.

Kritik fark — notifyListeners() yerine state =: Provider'da notifyListeners()'ı elle çağırıyorduk (Bölüm 6 Konu 24'ü hatırla). Riverpod'da, state alanına atama yapmak, otomatik olarak dinleyicilere haber verir — bu, Dart Bölüm 6'da öğrendiğimiz setter kavramına benzer bir "gizli mekanizma" (Riverpod'un kendi Notifier sınıfı, arka planda bir setter/getter kullanır).

NotifierProvider<SepetNotifier, int> — burada iki generic tip parametresi var: SepetNotifier (notifier class'ının kendisi) ve int (tuttuğu state'in tipi). Dart Bölüm 9'da öğrendiğimiz "birden fazla tip parametresi" kavramının (Map<K, V> gibi) burada tekrar kullanıldığını görüyorsun.

Widget İçinde Kullanım — ConsumerWidget

dart
class SepetIkonu extends ConsumerWidget { // StatelessWidget YERİNE ConsumerWidget!
  const SepetIkonu({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) { // EK bir parametre: WidgetRef!
    final sepetSayisi = ref.watch(sepetProvider);
    return Badge(
      label: Text('$sepetSayisi'),
      child: Icon(Icons.shopping_cart),
    );
  }
}

ConsumerWidget — Bölüm 2 Konu 5'te öğrendiğimiz StatelessWidget'ın Riverpod'a özel bir versiyonudur (Dart Bölüm 7'de öğrendiğimiz kalıtım hiyerarşisini hatırla — ConsumerWidget, aslında StatelessWidget'a çok benzer bir işlev görür ama Riverpod'un ref mekanizmasını build()'e ekler).

WidgetRef ref — bu, Provider'daki context.watch<T>() çağrısının yerini alan bir mekanizma. ref.watch(sepetProvider), tıpkı context.watch<SepetModeli>() gibi çalışır — ama context'e bağımlı değildir, ref üzerinden çalışır.

ref.read() — Callback'lerde Kullanım

dart
ElevatedButton(
  onPressed: () {
    ref.read(sepetProvider.notifier).ekle(); // .notifier ile NOTIFIER'a erişim
  },
  child: Text('Sepete Ekle'),
)

sepetProvider.notifier — Riverpod'da, ref.watch(sepetProvider) sadece state değerini (int) verir; metodları çağırmak için, .notifier ile notifier nesnesinin kendisine erişmen gerekir. Bu, Bölüm 6 Konu 24'te öğrendiğimiz Provider'daki context.read<SepetModeli>().ekle()'nin, biraz daha açık (explicit) bir versiyonu.

ConsumerStatefulWidget — StatefulWidget + Riverpod

dart
class SepetEkrani extends ConsumerStatefulWidget {
  const SepetEkrani({super.key});

  @override
  ConsumerState<SepetEkrani> createState() => _SepetEkraniState();
}

class _SepetEkraniState extends ConsumerState<SepetEkrani> {
  @override
  void initState() {
    super.initState();
    // burada ref, this.ref üzerinden erişilebilir
  }

  @override
  Widget build(BuildContext context) {
    final sepetSayisi = ref.watch(sepetProvider); // ConsumerState içinde 'ref' otomatik mevcut
    return Text('$sepetSayisi');
  }
}

Bu, Bölüm 2 Konu 6'da öğrendiğimiz iki-sınıflı StatefulWidget yapısının, Riverpod'a özel versiyonudur (ConsumerStatefulWidget + ConsumerState) — initState() gibi yaşam döngüsü metodlarına ek olarak, ref'e de erişim sağlar.

Bir Adım İleri — Code Generation (@riverpod)

Riverpod ekibinin resmi olarak önerdiği en güncel yaklaşım, provider'ları elle yazmak yerine riverpod_generator paketiyle otomatik ürettirmektir (Dart Bölüm 13 Konu 66'da öğrendiğimiz build_runner mekanizmasını hatırla):

dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'sepet_provider.g.dart';

@riverpod
class SepetNotifier extends _$SepetNotifier {
  @override
  int build() => 0;

  void ekle() {
    state = state + 1;
  }
}

Bu kod, dart run build_runner build çalıştırıldığında, yukarıda elle yazdığımız NotifierProvider tanımını senin için otomatik üretir (üstelik daha az kod ve daha güçlü tip çıkarımıyla). Bu tutorial'da öğrenmeye devam ettiğimiz elle yazılan Notifier/NotifierProvider kalıbı, arka planda tam olarak neyin üretildiğini anlaman için gerekli temel — code generation'ı, bu temeli kavradıktan sonra kullanmak çok daha mantıklı olur. Şimdilik elle yazma kalıbına odaklanmamız yeterli.

Provider vs Riverpod — Karşılaştırma

Provider Riverpod
BuildContext gerekli mi? ✅ Evet ❌ Hayır
Derleme zamanı güvenliği Orta (bazı hatalar runtime'da) Yüksek (çoğu hata derleme zamanında yakalanır)
Aynı tipte birden fazla provider Zor Kolay (family modifier ile — ileri konu)
Test edilebilirlik Orta Yüksek (context bağımlılığı olmadığı için)
Öğrenme eğrisi Düşük Biraz daha yüksek
Topluluk trendi Hâlâ yaygın, ama Yeni projelerde giderek daha çok tercih ediliyor

Pratik Tavsiye

Yeni başlayan biri için, Provider ile başlamak (kavramları öğrenmek için) makul bir yoldur — az önce gördüğün gibi, Riverpod'daki kavramlar (watch, read, notifier pattern) Provider'ınkilere çok benziyor. Ama büyük, uzun soluklu bir proje için, Riverpod'un sunduğu daha güçlü tip güvenliği ve context bağımsızlığı, uzun vadede daha sürdürülebilir bir seçimdir. Bu tutorial'ın ilerleyen kısımlarında (özellikle Bölüm 8'de, ileri state management pattern'lerinde), her ikisini de göreceğiz.


🎯 Bu Dersten Çıkarılması Gerekenler

  • Riverpod, Provider'ın BuildContext bağımlılığını ortadan kaldıran, daha güçlü tip güvenliğine sahip bir sonraki nesil state management çözümüdür.
  • ProviderScope, uygulamanın kökünde bir kere sarmalayıcı olarak kullanılır; NotifierProvider + Notifier<T>, güncel state yönetim kalıbıdır (eski kaynaklarda göreceğin StateNotifierProvider/StateNotifier artık legacy).
  • build() metodu, başlangıç state'ini döndürür (eski API'deki super(0) çağrısının yerini aldı); state = ... ataması, Provider'daki notifyListeners() çağrısının otomatikleştirilmiş halidir.
  • ConsumerWidget/ConsumerStatefulWidget, ref parametresi aracılığıyla, context.watch/context.read'in Riverpod karşılığı olan ref.watch/ref.read'i kullanır.
  • .notifier, sadece state değerine değil, notifier'ın metodlarına erişmek için kullanılır.
  • Riverpod ekibinin resmi önerisi artık @riverpod code generation'dır — bu tutorial'da elle yazma kalıbını öğrenmek, arkasındaki mekanizmayı anlaman için tercih edildi.

📝 Ödevler

  • [ ] Notifier<int> ile basit bir sayaç yaz, NotifierProvider ile tanımla, ConsumerWidget ile ekrana bağla.
  • [ ] ref.watch ile değeri gösteren, ref.read(...).notifier ile bir metodu çağıran bir buton yaz.
  • [ ] ConsumerStatefulWidget kullanarak, initState() içinde bir işlem yapan (örn. bir başlangıç değeri ayarlayan) bir widget yaz.
  • [ ] Aynı basit sepet uygulamasını (Bölüm 6 Konu 24'teki), bu sefer Provider yerine Riverpod ile yeniden yaz, iki yaklaşımı karşılaştır.
  • [ ] Kendi cümlelerinle, "Riverpod'un BuildContext'e bağımlı olmaması neden değerli" sorusunu açıkla.

Sıradaki konu: Bölüm 6 — Konu 26: BLoC/Cubit Pattern (flutter_bloc)