Flutter'da State Management: Provider Kullanımı ve Temel Kavramlar

Flutter ile mobil uygulama geliştirirken uygulamanın boyutu büyüdükçe karşılaşılan en büyük zorluklardan biri Durum Yönetimi (State Management) konusudur. Bir ekrandaki verinin değiştiğinde diğer ekranların da bu değişimden anında haberdar olması gerekir. İşte bu noktada Google'ın da önerdiği, öğrenmesi ve uygulaması oldukça pratik olan Provider paketi devreye giriyor.


Bu makalede, Provider'ın temel mantığını anlayacak ve adım adım projelerimize nasıl entegre edebileceğimizi göreceğiz.

State Management (Durum Yönetimi) Neden Gerekli?

Standart bir Flutter uygulamasında state (durum) yönetmek için genellikle StatefulWidget ve setState() fonksiyonunu kullanırız. Ancak setState() sadece bulunduğu widget ağacını (tree) yeniden çizer.

Eğer uygulamanızın derinliklerindeki bir butona tıklandığında, en üstteki bir profil resminin veya sepet simgesinin güncellenmesi gerekiyorsa, veriyi widget'lar arasında sürekli parametre olarak taşımak (prop drilling) kodunuzu karmaşık ve yönetilemez hale getirir. Provider, veriyi merkezi bir yerde tutup, sadece o veriyi dinleyen (ihtiyacı olan) widget'ların güncellenmesini sağlayarak bu sorunu çözer.

Provider'ın Temel Aktörleri

Provider paketini kullanırken sıkça karşılaşacağımız üç temel kavram vardır:

  1. ChangeNotifier: Değişkenlerimizi ve bu değişkenleri güncelleyen fonksiyonlarımızı tuttuğumuz model sınıfıdır. Veri değiştiğinde dinleyicilere haber vermek için notifyListeners() metodunu kullanır.

  2. ChangeNotifierProvider: Model sınıfımızı widget ağacına enjekte eden ve alt widget'ların bu modele erişmesini sağlayan sarmalayıcı (wrapper) widget'tır.

  3. Consumer (veya Provider.of / context.watch): Modeldeki veriyi dinleyen ve veri değiştiğinde sadece kendi içindeki yapıyı yeniden çizen (rebuild) yapıdır.


Adım Adım Provider Entegrasyonu (Sayaç Uygulaması)

Klasik Flutter sayaç (counter) uygulamasını Provider mantığı ile sıfırdan yazalım.

Adım 1: Paketi Projeye Dahil Etme

Öncelikle pubspec.yaml dosyanıza provider paketini eklemeniz gerekir:

YAML:
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.2  # Sürüm numarası güncel duruma göre değişebilir

Adım 2: Model Sınıfını Oluşturma (ChangeNotifier)

Sayacın değerini ve onu artıran fonksiyonu tutacak olan model sınıfımızı oluşturalım.

Dart:
// Yazar: Nuri TIRAŞ
import 'package:flutter/material.dart';

class CounterModel with ChangeNotifier {
  int _count = 0; // Dışarıdan doğrudan erişimi engellemek için private

  // Değeri okumak için getter
  int get count => _count;

  // Değeri artırıp dinleyicilere haber veren fonksiyon
  void increment() {
    _count++;
    notifyListeners(); // Bu satır, veriyi dinleyen tüm widget'ları tetikler
  }
}

Adım 3: Provider'ı Uygulamaya Tanıtma

Oluşturduğumuz modeli tüm uygulamanın veya belirli bir ekranın erişebileceği en üst noktada (genellikle main.dart içinde) tanımlamalıyız.

Dart:
// Yazar: Nuri TIRAŞ
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart'; // Modelimizi import ettik

void main() {
  runApp(
    // ChangeNotifierProvider ile uygulamamızı sarmalıyoruz
    ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: const MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Provider Eğitimi',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const CounterScreen(),
    );
  }
}

Adım 4: Veriyi Okuma ve Ekranı Güncelleme

Artık state'imizi dinlemeye hazırız. Ekranda sayacı gösterecek olan metin Consumer ile sarmalanacak, butona tıklandığında ise veri güncellenecektir.

Dart:
// Yazar: Nuri TIRAŞ
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Provider Sayaç Örneği'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Butona basılma sayısı:'),
            // Sadece bu widget'ın yeniden çizilmesi için Consumer kullanıyoruz
            Consumer<CounterModel>(
              builder: (context, counter, child) {
                return Text(
                  '${counter.count}',
                  style: Theme.of(context).textTheme.headlineMedium,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // İşlem yaparken ekranda çizim güncellemesine gerek yoksa listen: false kullanılır
        onPressed: () {
          Provider.of<CounterModel>(context, listen: false).increment();
        },
        tooltip: 'Artır',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Özet ve İpuçları

  • notifyListeners() metodunu çağırmayı unutursanız, veriniz arka planda güncellense bile ekrana yansımaz.

  • Bir butona tıklama gibi sadece fonksiyon tetikleyeceğiniz durumlarda Provider.of<Model>(context, listen: false) kullanarak gereksiz ekran yenilemelerinin önüne geçin. listen: false performansı artırır.

  • Görsel yapının tamamı yerine sadece verinin değiştiği ufak alanı Consumer ile sarmalamak her zaman en iyi pratiktir.

Provider, sağladığı bu temiz mimari sayesinde projelerinizi çok daha profesyonel ve okunabilir bir seviyeye taşıyacaktır. İyi kodlamalar!


Makalenizin bütünlüğünü bozmadan, okuyucuların ileri seviye kullanıma geçişini kolaylaştıracak MultiProvider bölümünü aşağıda hazırladım. Bu bölümü doğrudan makalenizin sonuna, "Özet ve İpuçları" kısmından hemen önce veya sonra ekleyebilirsiniz.


İleri Seviye: Birden Fazla Modeli Yönetmek (MultiProvider)

Gerçek dünya projelerinde, tek bir CounterModel uygulamamızın tüm ihtiyaçlarını karşılamaz. Çoğu zaman kullanıcı oturum bilgileri, tema ayarları (Karanlık/Aydınlık mod) ve sepet durumu gibi birbirinden tamamen bağımsız verileri aynı anda yönetmemiz gerekir.

Bu modellerin her biri için ChangeNotifierProvider'ları iç içe yazmak kodun okunabilirliğini düşürür ("Widget Hell" olarak da bilinir). Bunun yerine Provider paketi bize MultiProvider yapısını sunar.

Örneğin, hem bir tema yöneticimiz hem de bir kullanıcı yöneticimiz olduğunu varsayalım:

1. Modellerimizi Oluşturalım

Tema Modeli (theme_model.dart):

Dart:
// Yazar: Nuri TIRAŞ
import 'package:flutter/material.dart';

class ThemeModel with ChangeNotifier {
  bool _isDark = false; // Varsayılan olarak aydınlık tema

  bool get isDark => _isDark;

  void toggleTheme() {
    _isDark = !_isDark;
    notifyListeners();
  }
}

Kullanıcı Modeli (user_model.dart):

Dart:
// Yazar: Nuri TIRAŞ
import 'package:flutter/material.dart';

class UserModel with ChangeNotifier {
  String _userName = "Misafir";

  String get userName => _userName;

  void login(String name) {
    _userName = name;
    notifyListeners();
  }
}

2. MultiProvider ile Modelleri Enjekte Etme

Şimdi main.dart dosyamızda MultiProvider kullanarak bu iki modeli aynı anda uygulamamıza tanıtalım.

Dart:
// Yazar: Nuri TIRAŞ
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'theme_model.dart';
import 'user_model.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => ThemeModel()),
        ChangeNotifierProvider(create: (context) => UserModel()),
      ],
      child: const MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    // Tema değişikliğini anlık dinlemek için context.watch kullanabiliriz
    final themeModel = context.watch<ThemeModel>();

    return MaterialApp(
      title: 'MultiProvider Kullanımı',
      theme: themeModel.isDark ? ThemeData.dark() : ThemeData.light(),
      home: const UserProfileScreen(),
    );
  }
}

3. Farklı Modelleri Aynı Ekranda Kullanma

Ekranlarımızda (örneğin UserProfileScreen içinde) bu farklı modellere aynı anda erişmek oldukça basittir. İstediğimiz modeli tipiyle çağırarak kullanabiliriz.

Dart:
// Yazar: Nuri TIRAŞ
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'theme_model.dart';
import 'user_model.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Profil ve Ayarlar'),
        actions: [
          IconButton(
            icon: const Icon(Icons.brightness_6),
            onPressed: () {
              // Sadece tetikleme yapacağımız için listen: false
              Provider.of<ThemeModel>(context, listen: false).toggleTheme();
            },
          )
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Sadece kullanıcı modelini dinleyen Consumer
            Consumer<UserModel>(
              builder: (context, userParams, child) {
                return Text(
                  'Merhaba, ${userParams.userName}!',
                  style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                );
              },
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Provider.of<UserModel>(context, listen: false).login('Öğrenci');
              },
              child: const Text('Giriş Yap'),
            )
          ],
        ),
      ),
    );
  }
}

MultiProvider kullanarak projenizin mimarisini temiz tutabilir ve farklı mantıksal işlemleri birbirine karıştırmadan, modüler bir şekilde geliştirmeye devam edebilirsiniz. Ölçeklenebilir büyük Flutter projelerinin temelinde bu modüler yaklaşım yatmaktadır.

Yorumlar

Bu blogdaki popüler yayınlar

Dart Uygulama Sınavı: Pardus ETAP 23 Kurulum Otomasyonu

Dart Programlama Dil Uygulama Sınavı Çalışma Soruları

Pardus ETAP 23 İçin Flutter ile Dijital "Öğrenci Seçici" Uygulaması