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

Flutter'da state (durum) yönetimi, uygulamanın büyüdükçe en kritik tasarım kararlarından birine dönüşür. Bu yazıda Provider, Riverpod ve Bloc çözümlerini aynı senaryo üzerinden karşılaştıracağız: bir ürün listesi ile sepet yönetimi.

1. Provider

Google'ın önerdiği, basit ve anlaşılır çözümdür. InheritedWidget üzerine inşa edilmiştir.

// pubspec.yaml: provider: ^6.1.2

// Model
class SepetModel extends ChangeNotifier {
  final List<String> _urunler = [];
  List<String> get urunler => List.unmodifiable(_urunler);
  int get adet => _urunler.length;

  void ekle(String urun) {
    _urunler.add(urun);
    notifyListeners();
  }

  void cikar(String urun) {
    _urunler.remove(urun);
    notifyListeners();
  }
}

// main.dart
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => SepetModel(),
      child: const UygulamaWidget(),
    ),
  );
}

// Okuma
class SepetBadge extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final adet = context.watch<SepetModel>().adet;
    return Badge(label: Text(""));
  }
}

// Yazma (rebuild olmadan)
ElevatedButton(
  onPressed: () => context.read<SepetModel>().ekle("Laptop"),
  child: const Text("Sepete Ekle"),
)

2. Riverpod

Provider'ın eksikliklerini gidermek için tasarlanan Riverpod, compile-time güvenli, testable ve widget ağacından bağımsız çalışır.

// pubspec.yaml: flutter_riverpod: ^2.5.1

// Provider tanımı
final sepetProvider = StateNotifierProvider<SepetNotifier, List<String>>(
  (ref) => SepetNotifier(),
);

class SepetNotifier extends StateNotifier<List<String>> {
  SepetNotifier() : super([]);

  void ekle(String urun) => state = [...state, urun];
  void cikar(String urun) => state = state.where((u) => u != urun).toList();
}

// main.dart
void main() {
  runApp(const ProviderScope(child: UygulamaWidget()));
}

// ConsumerWidget ile kullanım
class UrunKarti extends ConsumerWidget {
  final String urun;
  const UrunKarti(this.urun, {super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final sepet = ref.watch(sepetProvider);
    final sepette = sepet.contains(urun);

    return ListTile(
      title: Text(urun),
      trailing: IconButton(
        icon: Icon(sepette ? Icons.shopping_cart : Icons.add_shopping_cart),
        onPressed: () => sepette
            ? ref.read(sepetProvider.notifier).cikar(urun)
            : ref.read(sepetProvider.notifier).ekle(urun),
      ),
    );
  }
}

// Türetilmiş state — AsyncNotifierProvider
final urunlerProvider = AsyncNotifierProvider<UrunlerNotifier, List<String>>(
  () => UrunlerNotifier(),
);

class UrunlerNotifier extends AsyncNotifier<List<String>> {
  @override
  Future<List<String>> build() async {
    return await ApiServis().urunleriGetir();
  }

  Future<void> yenile() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() => ApiServis().urunleriGetir());
  }
}

3. Bloc / Cubit

Büyük ölçekli uygulamalar için tasarlanmış, event-driven mimari sunar. Cubit daha basit, Bloc daha kapsamlıdır.

// pubspec.yaml: flutter_bloc: ^8.1.5

// Cubit (basit)
class SepetCubit extends Cubit<List<String>> {
  SepetCubit() : super([]);

  void ekle(String urun) => emit([...state, urun]);
  void cikar(String urun) => emit(state.where((u) => u != urun).toList());
  void temizle() => emit([]);
}

// Bloc (event-driven)
// Events
abstract class SepetEvent {}
class UrunEkle extends SepetEvent { final String urun; UrunEkle(this.urun); }
class UrunCikar extends SepetEvent { final String urun; UrunCikar(this.urun); }

// State
class SepetState { final List<String> urunler; const SepetState(this.urunler); }

class SepetBloc extends Bloc<SepetEvent, SepetState> {
  SepetBloc() : super(const SepetState([])) {
    on<UrunEkle>((event, emit) =>
        emit(SepetState([...state.urunler, event.urun])));
    on<UrunCikar>((event, emit) =>
        emit(SepetState(state.urunler.where((u) => u != event.urun).toList())));
  }
}

// UI kullanımı
BlocProvider(
  create: (_) => SepetBloc(),
  child: BlocBuilder<SepetBloc, SepetState>(
    builder: (context, state) {
      return Text("Sepet:  ürün");
    },
  ),
)

Karşılaştırma Tablosu

ÖzellikProviderRiverpodBloc
Öğrenme eğrisiDüşükOrtaYüksek
Compile-time güvenlikKısmiTamTam
Test edilebilirlikİyiMükemmelMükemmel
Büyük proje uygunluğuOrtaİyiMükemmel
BoilerplateAzAzFazla
Devtools desteğiYokKısmiTam

Ne Zaman Hangisini Seçmeli?

  • Provider: Küçük-orta projeler, prototip, hızlı geliştirme
  • Riverpod: Orta-büyük projeler, async-heavy uygulamalar, modern yaklaşım
  • Bloc: Kurumsal projeler, büyük ekipler, katı mimari gerektiren durumlar

Sonuç

Üç çözüm de production-ready olgunluktadır. Yeni başlıyorsanız Riverpod ile başlamanızı öneririm — Provider'ın sadeliğini, Bloc'un güvenilirliğini bir araya getirir. Büyük ekiplerde ise Bloc'un öngörülebilir event döngüsü ve DevTools desteği tercih sebebidir.