Flutter State Management: Riverpod, Provider ve Bloc Karşılaştırması

14 dakika okuma9 Şubat 2026Güncellendi: 9 Mar 2026
Flutter state managementRiverpodProvider FlutterBloc patternFlutter durum yönetimiRiverpod tutorialFlutter Blocstate management comparisonFlutter architecture

# Flutter State Management Rehberi: Riverpod, Provider ve Bloc Karşılaştırması

State management seçimi, Flutter projelerinde ölçeklenebilirliği, test edilebilirliği ve geliştirme hızını doğrudan etkiler. Üç büyük çözümü de üretim projelerinde kullandıktan sonra, yüzeysel karşılaştırmaların ötesine geçen kapsamlı bir rehber hazırlamak istedim.

State Management Neden Bu Kadar Önemli?

Flutter'da her widget, state'i değiştiğinde yeniden çizilir. Küçük bir uygulamada `setState()` gayet yeterlidir. Ancak uygulama büyüdükçe ciddi sorunlarla karşılaşırsınız: tekrarlanan iş mantığı, öngörülemeyen rebuild'ler, birbirine sıkı bağlı widget'lar ve yazmaktan kaçındığınız testler. Doğru bir state management çözümü tüm bu sorunları ortadan kaldırır.

Üç Büyük Aday

Provider

Provider, Flutter ekibinin resmi olarak önerdiği ilk topluluk çözümüydü. `InheritedWidget` mekanizmasını geliştirici dostu bir API ile sarar ve basit uygulamalar için hâlâ sağlam bir tercihtir.

dart
class=class="code-string">"code-comment">// Provider ile basit bir sayaç tanımlama
class CounterModel extends ChangeNotifier {
  int _count = class="code-number">0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

class=class="code-string">"code-comment">// Providerclass="code-string">'ı widget ağacının en üstünde kaydetme
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CounterModel(),
      child: const MyApp(),
    ),
  );
}

class=class="code-string">"code-comment">// Widget içinde state'i tüketme
class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    final counter = context.watch<CounterModel>();
    return Scaffold(
      body: Center(child: Text(class="code-string">'Sayaç: ${counter.count}')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterModel>().increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Güçlü yönleri: Minimum boilerplate, kolay öğrenme eğrisi, Flutter ekibinin kapsamlı dokümantasyonu.

Zayıf yönleri: `BuildContext`'e bağımlılık, provider'ları birleştirme zorluğu, sınırlı derleme zamanı güvenliği.

Riverpod

Riverpod, Provider'ın aynı yazar tarafından geliştirilen ruhani halefidir. `BuildContext` bağımlılığını ortadan kaldırır, derleme zamanı güvenliği sağlar ve provider'ları birleştirmek ile override etmek için çok daha zengin bir API sunar.

dart
class=class="code-string">"code-comment">// Riverpod ile basit bir sayaç tanımlama
final counterProvider = NotifierProvider<CounterNotifier, int>(
  CounterNotifier.new,
);

class CounterNotifier extends Notifier<int> {
  @override
  int build() => class="code-number">0;

  void increment() {
    state++;
  }
}

class=class="code-string">"code-comment">// Kök widgetclass="code-string">'ta sarmalayıcıya gerek yok—sadece ProviderScope kullanın
void main() {
  runApp(
    const ProviderScope(child: MyApp()),
  );
}

class=class="code-string">"code-comment">// Widget içinde state'i tüketme
class CounterPage extends ConsumerWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Scaffold(
      body: Center(child: Text(class="code-string">'Sayaç: $count')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Güçlü yönleri: `BuildContext` bağımlılığı yok, derleme zamanı provider güvenliği, mükemmel test desteği, yerleşik önbellekleme ve otomatik temizlik.

Zayıf yönleri: Provider'a göre daha dik öğrenme eğrisi, küçük projeler için kod üretimi kurulumu ağır gelebilir.

Bloc

Bloc, katı bir olay güdümlü desen uygular: widget'lar olay gönderir, Bloc bunları işler ve yeni state'ler yayar. Bu tek yönlü akış son derece öngörülebilirdir ve tutarlılığın geliştirme hızından daha önemli olduğu büyük ekiplerde iyi ölçeklenir.

dart
class=class="code-string">"code-comment">// Eventclass="code-string">'ler
sealed class CounterEvent {}
class CounterIncremented extends CounterEvent {}

class=class="code-string">"code-comment">// State bu basit örnekte sadece bir int
class=class="code-string">"code-comment">// Bloc tanımı
class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(class="code-number">0) {
    on<CounterIncremented>((event, emit) {
      emit(state + class="code-number">1);
    });
  }
}

class=class="code-string">"code-comment">// flutter_bloc ile sağlama ve tüketme
void main() {
  runApp(
    BlocProvider(
      create: (_) => CounterBloc(),
      child: const MyApp(),
    ),
  );
}

class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: BlocBuilder<CounterBloc, int>(
          builder: (context, count) => Text('Sayaç: $count'),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () =>
            context.read<CounterBloc>().add(CounterIncremented()),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Güçlü yönleri: Yüksek düzeyde öngörülebilir tek yönlü akış, mükemmel DevTools desteği, her state geçişi event'ler aracılığıyla izlenebilir.

Zayıf yönleri: Ciddi miktarda boilerplate (event, state, bloc sınıfları), basit özellikler için gereğinden fazla karmaşık.

Aynı Özellik, Üç Farklı Yaklaşım: Asenkron Veri Çekme

Sayaç önemsiz bir örnek. Gerçek bir senaryoya bakalım: API'den kullanıcı profili çekme.

Provider Yaklaşımı

dart
class UserProfileModel extends ChangeNotifier {
  UserProfile? _profile;
  bool _loading = false;
  String? _error;

  UserProfile? get profile => _profile;
  bool get loading => _loading;
  String? get error => _error;

  Future<void> fetchProfile(String userId) async {
    _loading = true;
    _error = null;
    notifyListeners();
    try {
      _profile = await ApiService().getUser(userId);
    } catch (e) {
      _error = e.toString();
    } finally {
      _loading = false;
      notifyListeners();
    }
  }
}

Riverpod Yaklaşımı

dart
final userProfileProvider =
    FutureProvider.autoDispose.family<UserProfile, String>((ref, userId) {
  return ref.watch(apiServiceProvider).getUser(userId);
});

class=class="code-string">"code-comment">// Widgetclass="code-string">'ta yükleme, hata ve veri durumları otomatik olarak ele alınır
class UserProfilePage extends ConsumerWidget {
  final String userId;
  const UserProfilePage({super.key, required this.userId});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final profileAsync = ref.watch(userProfileProvider(userId));
    return profileAsync.when(
      loading: () => const CircularProgressIndicator(),
      error: (err, stack) => Text('Hata: $err'),
      data: (profile) => Text(profile.name),
    );
  }
}

Bloc Yaklaşımı

dart
class=class="code-string">"code-comment">// Eventclass="code-string">'ler
sealed class UserProfileEvent {}
class UserProfileRequested extends UserProfileEvent {
  final String userId;
  UserProfileRequested(this.userId);
}

class=class="code-string">"code-comment">// State'ler
sealed class UserProfileState {}
class UserProfileInitial extends UserProfileState {}
class UserProfileLoading extends UserProfileState {}
class UserProfileLoaded extends UserProfileState {
  final UserProfile profile;
  UserProfileLoaded(this.profile);
}
class UserProfileError extends UserProfileState {
  final String message;
  UserProfileError(this.message);
}

class=class="code-string">"code-comment">// Bloc
class UserProfileBloc extends Bloc<UserProfileEvent, UserProfileState> {
  final ApiService api;
  UserProfileBloc(this.api) : super(UserProfileInitial()) {
    on<UserProfileRequested>((event, emit) async {
      emit(UserProfileLoading());
      try {
        final profile = await api.getUser(event.userId);
        emit(UserProfileLoaded(profile));
      } catch (e) {
        emit(UserProfileError(e.toString()));
      }
    });
  }
}

Riverpod'un asenkron durumları neredeyse sıfır boilerplate ile ele aldığına dikkat edin. Bloc, açıkça tanımlanmış event ve state sınıfları gerektirir. Provider ise ikisinin arasında kalır ve yükleme/hata takibini manuel olarak yapmanızı ister.

Detaylı Karşılaştırma Tablosu

| Kriter | Provider | Riverpod | Bloc |

|---|---|---|---|

| Boilerplate | Düşük | Orta | Yüksek |

| Öğrenme eğrisi | Kolay | Orta | Dik |

| Derleme zamanı güvenliği | Sınırlı | Güçlü | Orta |

| Test edilebilirlik | İyi | Mükemmel | Mükemmel |

| Ölçeklenebilirlik | Orta | Yüksek | Yüksek |

| DevTools desteği | Temel | İyi | Mükemmel |

| Asenkron işleme | Manuel | Yerleşik | Manuel (olay güdümlü) |

| BuildContext bağımlılığı | Var | Yok | Var (sağlama için) |

| Topluluk büyüklüğü | Geniş | Hızla büyüyor | Geniş |

| Kod üretimi | Gerekmez | Opsiyonel ama önerilir | Gerekmez |

Geçiş Planı: Provider'dan Riverpod'a

Mevcut bir Provider tabanlı uygulamanız varsa ve Riverpod'a geçmek istiyorsanız, riskli bir toptan yeniden yazımdan kaçınan aşamalı bir yaklaşım önerebilirim.

Aşama 1: Riverpod'u Provider'ın yanına ekleyin

Uygulama kökünüzü hem `ProviderScope` hem de mevcut `MultiProvider` ile sarın. İkisi çakışmadan bir arada çalışır.

dart
void main() {
  runApp(
    ProviderScope(
      child: MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (_) => AuthModel()),
          class=class="code-string">"code-comment">// ... mevcut provider'lar
        ],
        child: const MyApp(),
      ),
    ),
  );
}

Aşama 2: Önce uç özellikleri taşıyın

Diğer provider'lara fazla bağımlı olmayan bağımsız ekran veya özelliklerle başlayın. `ChangeNotifier` sınıflarını Riverpod notifier'larına dönüştürün, widget'lardaki `context.watch` çağrılarını `ref.watch` ile değiştirin.

Aşama 3: Paylaşılan servisleri taşıyın

Kimlik doğrulama, API istemcileri ve diğer paylaşılan servisleri Riverpod provider'larına taşıyın. Riverpod provider'ları global olduğu ve `BuildContext` gerektirmediği için bu adım genellikle kodu sadeleştirir.

Aşama 4: Provider'ı kaldırın

Tüm özellikler taşındıktan sonra, kökteki `MultiProvider`'ı kaldırın ve `pubspec.yaml`'dan `provider` paketini çıkarın.

Sık Yapılan State Management Hataları

1. Her şeyi global state'e koymak

Her veri parçasının bir provider veya bloc'a ait olması gerekmez. Form alanı görünürlüğü veya animasyon ilerlemesi gibi yalnızca UI ile ilgili durumlar için `setState` veya `ValueNotifier` ile yerel widget state'i gayet yeterlidir.

2. Gereksiz widget yeniden çizimleri

Sadece tek bir alana ihtiyacınız varken modelin tamamını izlemek, gereksiz rebuild'lere neden olur. Riverpod'da `select`, Bloc'ta `BlocSelector` kullanarak yalnızca belirli dilimleri dinleyin.

dart
class=class="code-string">"code-comment">// Kötü: kullanıcı modelindeki herhangi bir değişiklikte yeniden çizer
final user = ref.watch(userProvider);

class=class="code-string">"code-comment">// İyi: sadece isim değiştiğinde yeniden çizer
final name = ref.watch(userProvider.select((u) => u.name));

3. Widget'lara iş mantığı koymak

Widget'lar state okumalı ve aksiyon tetiklemeli, mantık içermemelidir. `build` metodunuz hangi API'yi çağıracağına `if/else` zincirleriyle karar veriyorsa, bu mantık bir notifier veya bloc'a taşınmalıdır.

4. Temizlemeyi ihmal etmek

Controller'ları, stream'leri veya subscription'ları dispose etmeyi unutmak bellek sızıntılarına yol açar. Riverpod'un `autoDispose` özelliği bunu zarif bir şekilde halleder; Provider ve Bloc'ta temizlik konusunda disiplinli olmanız gerekir.

5. Proje ihtiyacı yerine popülerliğe göre seçmek

En iyi state management çözümü, ekibinizin anlayıp sürdürebileceği çözümdür. İyi uygulanmış bir Provider kod tabanı, kötü anlaşılmış bir Bloc mimarisini her zaman yener.

Kişisel Deneyimlerim

Projelerimde, geliştirdiğim uygulamaların büyük çoğunluğu için Riverpod'un en verimli seçenek olduğunu gördüm. Derleme zamanı güvenliği, provider bağımlılık hatalarını çalışma zamanına ulaşmadan yakalar ve `FutureProvider` ile `AsyncValue` ikilisi, API odaklı ekranlarda o kadar çok boilerplate'i ortadan kaldırır ki sanki farklı bir framework kullanıyormuşsunuz gibi hissettiriyor. Özellikle bir Riverpod provider'ını test etmek için widget ağacı oluşturmanın gerekmemesini çok değerli buluyorum.

Öte yandan, her geliştiricinin aynı katı kalıbı izlemesi gereken büyük ekiplerle çalıştığım projelerde Bloc'u tercih ediyorum. Tek başınıza geliştirirken boilerplate gibi hissettiren event/state seremonisi, aynı özelliğe beş kişi dokunduğunda değerli bir iletişim protokolüne dönüşüyor.

Hız ve hızlı iterasyonun uzun vadeli mimariden daha önemli olduğu prototip ve MVP'ler için hâlâ Provider kullanıyorum. Riverpod'a geçiş yolu yeterince pürüzsüz olduğundan, Provider ile başlamak çıkmaz bir sokak değil.

Sonuç

2026'daki yeni Flutter projelerin çoğu için Riverpod, tür güvenliği, test edilebilirlik ve geliştirici deneyimi arasında en güçlü dengeyi sunuyor. Provider, küçük uygulamalar için pragmatik bir başlangıç noktası olmaya devam ediyor ve Bloc, ekibinizin katı mimari korkuluklardan fayda gördüğü durumlarda doğru seçim.

Flutter projeniz için aşamalı geçiş stratejisi veya mimari inceleme isterseniz benimle iletişime geçebilirsiniz.

İlgili Makaleler

Flutter Projeniz mi Var?

iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.

İletişime Geç