Ders İçeriği

Flutter'da durum yönetimi, uygulamanızın verilerini ve kullanıcı arayüzünü senkronize tutmanın anahtarıdır. Temel seviyede setState ile basit durum yönetimini gördük. Ancak daha büyük ve karmaşık uygulamalarda, setState yeterli olmayabilir ve uygulamanın performansını veya yönetilebilirliğini olumsuz etkileyebilir. Bu nedenle, Flutter topluluğu tarafından geliştirilen çeşitli durum yönetimi çözümleri bulunmaktadır.

1. Provider

provider paketi, Flutter'da en popüler ve kullanımı kolay durum yönetimi çözümlerinden biridir. Bağımlılık enjeksiyonu (dependency injection) ve durum yönetimi için basit ama güçlü bir yol sunar. Temel olarak, bir veriyi (durumu) widget ağacında aşağıya doğru iletmek ve bu verideki değişiklikleri dinleyen widget'ları otomatik olarak güncellemek için kullanılır.
Temel Kavramlar:
ChangeNotifier: Durumu değiştiğinde dinleyicilere bildirim gönderen bir sınıf. Durumunuzu yönetmek için bu sınıfı miras alırsınız.
ChangeNotifierProvider: Bir ChangeNotifier örneğini widget ağacına sağlar. Bu sayede altındaki widget'lar bu örneğe erişebilir ve değişikliklerini dinleyebilir.
Consumer: Sağlanan bir ChangeNotifier'daki değişiklikleri dinleyen ve durum değiştiğinde kendini yeniden oluşturan bir widget.
Provider.of<T>(context): Bir Provider tarafından sağlanan veriye erişmek için kullanılır. listen: false parametresi ile sadece veriye erişebilir, değişiklikleri dinlemezsiniz. listen: true (varsayılan) ile değişiklikleri dinler ve widget'ı yeniden oluşturursunuz.
Örnek: Basit Bir Sayaç Uygulaması
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// 1. Durumu yönetecek ChangeNotifier sınıfı
class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // Dinleyicilere durumun değiştiğini bildir
  }

  void decrement() {
    _count--;
    notifyListeners();
  }
}

void main() {
  runApp(
    // 2. ChangeNotifierProvider ile Counter örneğini widget ağacına sağla
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Provider Sayaç Örneği',
      theme: ThemeData.light(),
      home: const CounterScreen(),
    );
  }
}

class CounterScreen extends StatelessWidget {
  const CounterScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 3. Consumer ile durumu dinle ve UI'ı güncelle
    return Scaffold(
      appBar: AppBar(title: const Text('Provider Sayaç')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'Sayaç Değeri:',
              style: TextStyle(fontSize: 20),
            ),
            Consumer<Counter>(
              builder: (context, counter, child) {
                return Text(
                  '${counter.count}',
                  style: Theme.of(context).textTheme.headlineLarge,
                );
              },
            ),
            SizedBox(height: 30),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    // 4. Provider.of ile duruma eriş ve metodu çağır (listen: false)
                    Provider.of<Counter>(context, listen: false).decrement();
                  },
                  child: Icon(Icons.remove),
                ),
                SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    Provider.of<Counter>(context, listen: false).increment();
                  },
                  child: Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
Provider Kullanımının Avantajları:
Basitlik: Küçük ve orta ölçekli uygulamalar için öğrenmesi ve kullanması oldukça kolaydır.
Performans: Sadece değişen widget'ları yeniden oluşturarak performansı optimize eder.
Test Edilebilirlik: Durum mantığını UI'dan ayırarak daha kolay test edilebilir kod yazmanızı sağlar.
Topluluk Desteği: Geniş bir topluluğa ve iyi bir dokümantasyona sahiptir.

2.2. BLoC/Cubit (Giriş)

BLoC (Business Logic Component) ve Cubit, Flutter'da durum yönetimi için popüler ve daha yapılandırılmış yaklaşımlardır. Özellikle büyük ve karmaşık uygulamalarda, iş mantığını kullanıcı arayüzünden tamamen ayırarak kodun daha düzenli, test edilebilir ve ölçeklenebilir olmasını sağlarlar.
BLoC (Business Logic Component):
BLoC deseni, olayları (events) girdi olarak alır, iş mantığını işler ve yeni durumları (states) çıktı olarak verir. Her durum değişikliği bir olay tarafından tetiklenir ve bu olaylar BLoC'a gönderilir. BLoC, bu olaylara göre yeni durumlar üretir.
Cubit:
Cubit, BLoC'un daha basit bir versiyonudur. Olaylar yerine doğrudan fonksiyon çağrıları ile durum değişikliklerini tetikler. Daha az boilerplate kodu gerektirir ve daha küçük, daha basit durumlar için idealdir. Temel olarak, bir fonksiyon çağrısı ile yeni bir durum yayan bir sınıftır.
Temel Kavramlar (Cubit Üzerinden):
Cubit Sınıfı: Durumu yöneten ve durum değişikliklerini yayan sınıftır. emit metodu ile yeni durumlar yayar.
State Sınıfı: Cubit tarafından yayılan durumları temsil eden sınıftır. Genellikle equatable paketi ile karşılaştırılabilir hale getirilir.
BlocProvider: Bir Cubit/BLoC örneğini widget ağacına sağlar.
BlocBuilder: Bir Cubit/BLoC'daki durum değişikliklerini dinleyen ve durum değiştiğinde kendini yeniden oluşturan bir widget.
BlocListener: Bir Cubit/BLoC'daki durum değişikliklerini dinleyen, ancak UI'ı yeniden oluşturmayan bir widget. Genellikle SnackBar gösterme, navigasyon yapma gibi yan etkiler için kullanılır.
BlocConsumer: Hem BlocBuilder hem de BlocListener'ın özelliklerini birleştiren bir widget.
Örnek: Basit Bir Sayaç Uygulaması (Cubit ile)
Öncelikle flutter_bloc ve equatable paketlerini pubspec.yaml dosyanıza eklemeniz gerekir:
dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.1.3 # En güncel sürümü kullanın
  equatable: ^2.0.5 # En güncel sürümü kullanın

Sonra flutter pub get komutunu çalıştırın.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';

// 1. Durumu temsil eden sınıf
class CounterState extends Equatable {
  final int count;

  const CounterState(this.count);

  @override
  List<Object> get props => [count];
}

// 2. Cubit sınıfı
class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(const CounterState(0)); // Başlangıç durumu

  void increment() => emit(CounterState(state.count + 1));
  void decrement() => emit(CounterState(state.count - 1));
}

void main() {
  runApp(
    // 3. BlocProvider ile Cubit örneğini widget ağacına sağla
    BlocProvider(
      create: (context) => CounterCubit(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Cubit Sayaç Örneği',
      theme: ThemeData.light(),
      home: const CounterScreen(),
    );
  }
}

class CounterScreen extends StatelessWidget {
  const CounterScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Cubit Sayaç')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'Sayaç Değeri:',
              style: TextStyle(fontSize: 20),
            ),
            // 4. BlocBuilder ile durumu dinle ve UI'ı güncelle
            BlocBuilder<CounterCubit, CounterState>(
              builder: (context, state) {
                return Text(
                  '${state.count}',
                  style: Theme.of(context).textTheme.headlineLarge,
                );
              },
            ),
            SizedBox(height: 30),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    // 5. Cubit metodunu çağır
                    context.read<CounterCubit>().decrement();
                  },
                  child: Icon(Icons.remove),
                ),
                SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    context.read<CounterCubit>().increment();
                  },
                  child: Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
BLoC/Cubit Kullanımının Avantajları:
Ayrım: İş mantığını UI'dan tamamen ayırır, bu da kodun daha temiz ve yönetilebilir olmasını sağlar.
Test Edilebilirlik: İş mantığı ayrı bir sınıfta olduğu için kolayca test edilebilir.
Ölçeklenebilirlik: Büyük ve karmaşık uygulamalar için idealdir.
Tahmin Edilebilirlik: Durum değişiklikleri olaylar veya fonksiyon çağrıları ile açıkça tetiklendiği için uygulamanın davranışı daha tahmin edilebilirdir.

2.3. GetX (Giriş)

GetX, Flutter için hızlı, güçlü ve çok yönlü bir mikro çerçevedir. Durum yönetimi, bağımlılık yönetimi ve rota yönetimi gibi birçok özelliği tek bir pakette sunar. Özellikle hızlı geliştirme ve performans odaklı uygulamalar için popülerdir.
Temel Kavramlar:
GetxController: Durumu yöneten ve UI'ı güncelleyen sınıftır. update() metodu ile dinleyicilere bildirim gönderir.
GetBuilder: Bir GetxController'daki değişiklikleri dinleyen ve durum değiştiğinde kendini yeniden oluşturan bir widget.
Obx: Reaktif programlama yaklaşımıyla, sadece değişen değerleri dinleyen ve ilgili widget'ı güncelleyen bir widget. .obs ile gözlemlenebilir (observable) değişkenler tanımlanır.
Get.put(): Bir GetxController örneğini oluşturur ve bağımlılık enjeksiyonu için kaydeder.
Get.find(): Kaydedilmiş bir GetxController örneğine erişir.
Örnek: Basit Bir Sayaç Uygulaması (GetX ile)
Öncelikle get paketini pubspec.yaml dosyanıza eklemeniz gerekir:
dependencies:
  flutter:
    sdk: flutter
  get: ^4.6.5 # En güncel sürümü kullanın

Sonra flutter pub get komutunu çalıştırın.
import 'package:flutter/material.dart';
import 'package:get/get.dart';

// 1. GetxController sınıfı
class CounterController extends GetxController {
  // Reaktif değişken (Obx için)
  var count = 0.obs; // .obs ile gözlemlenebilir hale getirilir

  // Normal değişken (GetBuilder için)
  int normalCount = 0;

  void increment() {
    count.value++; // Reaktif değişkeni güncelle
    normalCount++; // Normal değişkeni güncelle
    update(); // GetBuilder'ı dinleyenleri güncelle
  }

  void decrement() {
    count.value--;
    normalCount--;
    update();
  }
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 2. Controller'ı kaydet
    Get.put(CounterController());

    return GetMaterialApp(
      title: 'GetX Sayaç Örneği',
      theme: ThemeData.light(),
      home: const CounterScreen(),
    );
  }
}

class CounterScreen extends StatelessWidget {
  const CounterScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Controller örneğine eriş
    final CounterController controller = Get.find();

    return Scaffold(
      appBar: AppBar(title: const Text('GetX Sayaç')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'Sayaç Değeri (Obx):',
              style: TextStyle(fontSize: 20),
            ),
            // 3. Obx ile reaktif değişkeni dinle
            Obx(() => Text(
                  '${controller.count.value}',
                  style: Theme.of(context).textTheme.headlineLarge,
                )),
            SizedBox(height: 20),
            const Text(
              'Sayaç Değeri (GetBuilder):',
              style: TextStyle(fontSize: 20),
            ),
            // 4. GetBuilder ile normal değişkeni dinle
            GetBuilder<CounterController>(
              builder: (ctrl) {
                return Text(
                  '${ctrl.normalCount}',
                  style: Theme.of(context).textTheme.headlineLarge,
                );
              },
            ),
            SizedBox(height: 30),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    controller.decrement();
                  },
                  child: Icon(Icons.remove),
                ),
                SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    controller.increment();
                  },
                  child: Icon(Icons.add),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
GetX Kullanımının Avantajları:
Hız ve Performans: Minimal yeniden derleme ile yüksek performans sunar.
Kolaylık: Az boilerplate kodu ile hızlı geliştirme imkanı sağlar.
Çok Yönlülük: Durum yönetimi, bağımlılık yönetimi, rota yönetimi gibi birçok özelliği bir arada sunar.
Reaktif Programlama: .obs ve Obx ile reaktif programlamayı kolaylaştırır.
Durum yönetimi, Flutter uygulamalarının kalbinde yer alır. Uygulamanızın karmaşıklığına ve ihtiyaçlarına göre Provider, BLoC/Cubit veya GetX gibi farklı çözümlerden birini seçebilirsiniz. Her birinin kendine özgü avantajları ve kullanım senaryoları vardır. Başlangıç için Provider genellikle daha kolaydır, ancak büyük projelerde BLoC/Cubit veya GetX daha iyi bir yapı sağlayabilir.