Flutter State Yönetimine Giriş

Flutter State Yönetimine Giriş

Flutter ince bi katman kutulardan oluşmuş bir makyaj aslında. Altta işler olur biter en son flutter motoru bu yeni durumu mayajlayarak(şık widgetler ile) yansıtır. Burada yansıtılacak olan yeni görünüm, ekrana çizilecek olan veri içeren widgetlerlerden oluşan sonraki kare. Dolayısıyla sonraki karenin ihtiyaç duyduğu bilgi kare çizilmeden önce hazır olmalı.

State : Ekranın yeniden çizilmesini ve ekrana yansılmasını gerektiren değer değişikiğinden başka bir şey değildir. 

Örneğin yeni bir flutter projesi oluşturduğumuzda yüklenen sayı artırma uygulamasında mevcut sayı değerinin bir artması bir sonraki karede gösterilmek zorundadır(ekranı yeniden çizilmeye zorlayan değer değişikliği!). Burada bir değer değişikliği var ve bu değer değişikliğini gerçekleştiğinde bunu flutter a bildirmeliyiz ki kendini yeniden ekrana bu yeni değerle çizerek yansıtsın.(Sonraki karenin ihtiyaç duyduğu bilgi kare çizilmeden önce hazır olmalı!)

Öyleyse…

A. Yönetilmesi gereken state senaryoları

1. State(değer/bilgi değişikliği) in değiştiğini flutter a haber vermeyi yönetmek.

2. state in kapsam alanını yönetmek. (Bir widget içerisindeki state e başka bir widget ihtiyaç duyabilir.)

3. state’in üretilme olaylarını yönetmek.(kullanıcı etkileşimi ile üretilen, uzak sunucudan bildirimle üretilen state vss gibi)

4.state in üretilmesi zaman alan durumlarını yönetmek(future).

5.state in üretilme operasyonlarını yönetmek.

6.statein ilk üretilme anını yönetmek(splash ekranı,sayfa değişikliği vs..)

7.state i sürdürülebilir kılmak

8.state i üreten ve tüken durumlarını yönetmek.

Bütün bunlar setstate yöntemiyle olacak gibi görünmüyor….(olsada çok maliyetli). 

 

Öyleyse…

B.widgetler için bazı ön kabuller…

1.Widgetler yalın olmalıdır.

2. widgetlerin görevi bir bilgiyi sarıp sarmalayıp süsleyerek göstermekten ibarettir.

3.widgetler çizilmeden önce tüketeceği veri/state hazırlanmış/yüklemiş olmalıdır.

4.bir widgetin kapsamı sadece ihtiyaç duyduğu state yada stateleri sarmalı diğerlerini dışlamalıdır.(gereksiz yere diğer widgetlerin yeniden çizimesine yolaçmamalıdır!)

5.widget state i transformasyona uğratıp kullanacaksa(örneğin bir liste tutan bir state i filtreleme gibi) bunu sağlayan operasyonlar widget içerisinde tanımlanmamalıdır.(1. kural)

6. state tutmayan/bir state abone olmayan widetlere gereksiz context geçilmemelidir.(örneğin bir buton döndüren kendi widgetimizi yazacağımızı varsayarak .Bunun için kendi widgetimizi builder içeren bir widget ile oluşturmak yerine bir metod ile döndürebiliriz)

vs…

 

Diğer bir yazıda bu maddeleri birerbirer ele almayı düşünüyorum. Aşağıda state yönetimine örnek sayı artırma uygulaması bulunmakta…
motivasyonunuzun taze kalması dileğiyle…
//main.dart

import 'package:flutter/material.dart';
import 'RunnApp.dart';
main() =>runApp(RunApp());


//RunApp.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'Wp/WpHome.dart';
import 'Ws/WsCounter/StWsCounter.dart';

class RunApp extends StatelessWidget {
  const RunApp( {Key key,}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home:    ChangeNotifierProvider (
        create: (_) => StWsCounter()..load(),
        child:WpHome(),
   
      ),
    );
  }
}
 
 

//WpHome.dart

import 'package:flutter/material.dart';
import 'package:flutternewpatter/Mx/MxOp/MxOpSayiIslemleri.dart';
import 'package:flutternewpatter/Mx/MxWs/MxWsButton.dart';
import 'package:flutternewpatter/Mx/MxWs/MxWsListe.dart';
import 'package:flutternewpatter/Ws/WsCounter/StWsCounter.dart';
import 'package:flutternewpatter/Ws/WsCounter/WsCounter.dart';
import 'package:flutternewpatter/Ws/WsWidget.dart';
import 'package:provider/provider.dart';
import '../AppTest/AppTest.dart';

class WpHome extends StatelessWidget
    with MxWsButton, MxWsList, MxOpSayiIslemleri {
  WpHome({Key key}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
    AppTest.setwidgetG();
    return Scaffold(
        appBar: AppBar(
          title: Text("Home"),
        ),
        body: Container(
          margin: EdgeInsets.all(20),
          alignment: Alignment.center,
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Container(
 
                  color: Colors.red.shade100,
                  child: WsWidget(
                    widget: (BuildContext context) {
                      final _state = Provider.of<StWsCounter>(context);
                      return Column(
                        children: <Widget>[
                          Text("Rebuild Değerleri ve state abonelikleri",
                              style: TextStyle(
                                  fontSize: 12,
                                  color: Colors.red,
                                  fontWeight: FontWeight.bold)),
                          SizedBox(
                            height: 10,
                          ),
                           Text("Home  : " + AppTest.widgetG),
                          Text("WsCount : Provider.of<StWsCounter>(context) " + AppTest.widgetA),
                          Text("ListeWidget : Provider.of<StWsCounter>(context): " + AppTest.widgetB),
                          Text("Buton YuzArtir : Provider.of<StWsCounter>(context,listen:false): " + AppTest.widgetC),
                          Text("Buton Azalt :Provider.of<StWsCounter>(context): " + AppTest.widgetD),
                          Text("Buton Artir  :Provider.of<StWsCounter>(context): " + AppTest.widgetE),
                          Text("Buton Reset  :Provider.of<StWsCounter>(context): " + AppTest.widgetF),
                         
                    
                        ],
                      );
                    },
                  ),
                ),

                WsCounter(),

                Expanded(
                  child: Container(
                    margin: EdgeInsets.all(5),
                    padding: EdgeInsets.all(20),
                    color: Colors.green.shade100,
                    child: WsWidget(
                      widget: (BuildContext context) {
                        final _state = Provider.of<StWsCounter>(context);
                        AppTest.setwidgetB();

                        return Column(
                          children: <Widget>[
                            Text("ListeWidget ",
                                style: TextStyle(
                                    fontSize: 20,
                                    color: Colors.red,
                                    fontWeight: FontWeight.bold)),
                            SizedBox(
                              height: 10,
                            ),
                            Expanded(
                              child: mxWsListeGetir(
                                  context, mxOpCiftSayilariGetir(_state.count)),
                            ),
                          ],
                        );
                      },
                    ),
                  ),
                ),

//yada iş yükünü state in op sine aktararak getirebiliriz fakat bu durumda çift sayılar listesini bir "state" olarak saklamamız gerekecektir.
              ],
            ),
          ),
        ),
        floatingActionButton: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          crossAxisAlignment: CrossAxisAlignment.end,
          children: <Widget>[
            Row(
              children: <Widget>[
                Expanded(
                  child: WsWidget(
                    widget: (BuildContext context) {
                      AppTest.setwidgetC();
                      final state =
                          Provider.of<StWsCounter>(context, listen: false);
                      return RaisedButton(
                        color: Colors.orange,
                        child: Text("Buton YuzArtir"),
                        onPressed: !state.hasdata
                            ? null
                            : () => state.setSimpleOpYuzArtir(),
                      );
                    },
                  ),
                ),
                SizedBox(
                  width: 5.0,
                ),
                Expanded(
                  child: WsWidget(
                    widget: (BuildContext context) {
                      AppTest.setwidgetD();
                      final state = Provider.of<StWsCounter>(context);
                      return RaisedButton(
                        color: Colors.green,
                        child:
                            !state.hasdata ? Text("Buton Azalt") : Text("Buton Azalt"),
                        onPressed: !state.hasdata
                            ? null
                            : () => state.setAzalt(),
                      );
                    },
                  ),
                ),
                SizedBox(
                  width: 5.0,
                ),
                Expanded(
                  child: WsWidget(
                    widget: (BuildContext context) {
                      AppTest.setwidgetE();
                      final state = Provider.of<StWsCounter>(context);
                      return RaisedButton(
                        color: Colors.green,
                        child:
                            !state.hasdata ? Text("Buton Artir") : Text("Buton Artir"),
                        onPressed: !state.hasdata
                            ? null
                            : () => state.setArtir(),
                      );
                    },
                  ),
                ),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              crossAxisAlignment: CrossAxisAlignment.end,
              children: <Widget>[
                Expanded(
                  child: WsWidget(
                    widget: (BuildContext context) {
                      AppTest.setwidgetF();
                      final state = Provider.of<StWsCounter>(context);
                      return RaisedButton(
                        color: Colors.green,
                        child:
                            !state.hasdata ? Text("Buton Reset") : Text("Buton Reset"),
                        onPressed:
                            !state.hasdata ? null : () => state.setCount(0),
                      );
                    },
                  ),
                ),
  
              ],
            ),
          ],
        ));
  }
}
 
 
 //WsWidget.dart

import 'package:flutter/material.dart';
typedef typMyWidget = Widget Function(BuildContext context);

class WsWidget extends StatelessWidget {
  WsWidget({@required this.widget, Key key}) : super(key: key);
  final typMyWidget widget;
  @override
  Widget build(BuildContext context) => widget(context);
}

  
 
//WsCounter.dart


import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'StWsCounter.dart';
import '../../AppTest/AppTest.dart';
class WsCounter extends StatelessWidget {
    WsCounter({Key key}) : super(key: key);
 
  @override
  Widget build(BuildContext context) {
     AppTest.setwidgetA();
     
  final state = Provider.of<StWsCounter>(context);

    return Container ( margin: EdgeInsets.all(5),
       padding: EdgeInsets.all(20),color: Colors.red.shade100 ,child:Column(children: <Widget>[
Text("WsCounter", style:TextStyle(fontSize: 18,) ),
 !state.hasdata ? Container(width: 20,height: 20, child: CircularProgressIndicator()):
         !state.success ? Text("Bir hata oluştu"):
              Container(
                child: Text("Güncel Sayi : " + state.count.toString()),
              ),
              ],),);
 
}
}
  
  //StWsBase.dart

import 'package:flutter/foundation.dart';

class StWsBase with ChangeNotifier {
  bool _hasdata = false;
  bool _success = false;
  bool get hasdata => _hasdata;
  bool get success => _success;


  setNhasdata(bool hasdata, [ bool success=true]){
    _hasdata = hasdata;
    _success = success;
    notifyListeners();
  }

  setQhasdata(bool hasdata, [ bool success=true]){
     _hasdata = hasdata;
     _success = success;
  } 

}
  
 
 //StWsCounter.dart


import '../../Base/StWsBase.dart';
import 'OpStWsCounter.dart';

///isimlendirme kuralları:
///Her sınıf önce kendi ön ekini alır sonra alt sınıfın ön ekini alır ...tekrar tekrar
///Önekler:
///Ws : Widget single : sayfa içinde kullanılan widgetler
///Wp : widget page : sayfa widgetleri
///St :  State : state/durum içeren sınıflar
///Md : Model
///Mx : Mixin
///..Base : .. aite base sınıf
///Ac : App common : ugulamanın ortal statiklerini içeren sınıf
///

///Örnek: WsCounter
///StWsCounter :  WsCounter widgetine ait state sınıfı
///OpStWsCounter :WsCounter widgetine ait StWsCounter sınıfına ait Operasyon sınıfı
///MdStWsCounter : StWsCounter sınıfına ait model sınıfı

///senaryo:
///  state nesnesine bir talep gelir-gelmez hemen state kendisini veri hazır değil konumuna alır. hasdata= false.(burada succes in önemi yoktur)
///  istenen veri state operasyonu(StOp...) tarafından yerine getirilir getirilmez state kendisini hasdata=true veri hazır konumuna alır ve ayrıca
/// başarılı bir şekilde gerçekleşmişse succes=true ya,  başarısız ise succes=false a alır. bu atamalar tek bir setNhasdata (bool hasdata, [succes=ture]) ile yapılır.succesin set metodu yokur.
/// ayrıca succes varsayıılan olarak true dir.
/// (try{ sacces=ture} catch {sacces=false, hasdata=true}
/// kısaca state set eden metod yapımız idealde şöyle olmalı
/// statetimiz
///   int _count;
/// int get count =>_count;
///
/// dynamic _error;
/// dynamic get error => _error;
///
/// setstate metodumuz
/// setState(int value){
///
///    setNhasdata(false);
/// try{
///    _count =value;
/// setNhasdata(true);//saccess varsayılan olarak true dir yazmaya gerek yok
///
/// }catch{
///  _error = "Getirilemedi yada gösterilmek istenen mesaj";
/// setNhasdata(true,false);
/// }
///
/// }
/// setNhasdata: state i kullanan widgetlere statin değiştiğini haber verir bu sayede widgetler kendilerini yeniden oluşurur.
///  setQhasdata ise sessiz bir şekilde state i günceller.söz konusu stati dinleyen widgetlere state deki değişim haber verilmez. widgetler kendilerini yeniden oluşturmaz.

///hasdata: state verisinin hazır olup/var olup olmadığını haber vermerkten sorumlu.
///sacces : istenen verinin düzgün gelip gelmediğinden sorumlu.Talep edilen veri gelmiş (hasdata=true) fakat try catch te hata olmuş olabilir(success=false durumu)


///her state StWsBase den extends almak zorundadir.
/// state deki değişim için gerekli işler statin op si tarafından gerekleştirilir.Basit değişimler hariç state metodaları yalın olmalıdır.
class StWsCounter extends StWsBase {
  final _op = OpStWsCounter();

  int _count;
  int get count => _count;

  ///State ilk oluşturulduğunda yalnızca bir defa çağrılacak olan metod.
  ///state nin ihtiyaç duyduğu ilk değerler load metodu ile genelde sharedpreften, veritabanından yada doğrudan verilerek state e yuklenir.
  void load() {
    setQhasdata(false);
    try {
      _count = 0;
      setQhasdata(true);
    } catch (e) {
      print("Hata: load" + e.toString());
      setQhasdata(true, false);
    }
  }

  void setCount(int value)  {
    setNhasdata(false);
    try {
      _count = value;
      setNhasdata(true);
    } catch (e) {
      print("Hata:" + e.toString());
      setNhasdata(true, false);
    }
  }

  setArtir() {
  //  setNhasdata(false);// yeni state değeri elde etmek bekleyen bir işlemi gerektirmiyor yani Future değil! o yüzden setNhasdata(false); gerekli değil. yazılmaz!

    try {
      _count = _count+1;
      setNhasdata(true);
    } catch (e) {
      print("Hata:" + e.toString());
      setNhasdata(true, false);
    }
  }

  Future<void> setAzalt() async {
 /// yeni state değeri elde etmek bekleyen bir işlemi gerektiriyor yani Future ! o yüzden setNhasdata(false); gerekli olabilir. genel kabulum yazılır!
 setNhasdata(false);
    try {
      await Future.delayed(Duration(seconds: 2));
      _count = _count - 1;
      setNhasdata(true);
    } catch (e) {
      print("Hata:" + e.toString());
      setNhasdata(true, false);
    }
  }

  ///State sınıfı yalın bir arayuze sahip olmalıdır.State in değişimi için gerekli işler genel kural olarak statin kendi
  ///Operasyon sınıfı(bu örnekte StOpWsConter) tarafından gerçekletirilmelidir.
  Future<void> setSimpleOpYuzArtir() async {
    setNhasdata(false);

    try {
      _count = await _op.setSimpleOpYuzArtir(_count);
      setNhasdata(true);
    } catch (e) {
      print("Hata:" + e.toString());
      setNhasdata(true, false);
    }
  }
}

  
 
//OpStWsCounter.dart

class OpStWsCounter {

  Future<int> setSimpleOpYuzArtir(int value) async {
    var nvalue = 100 * value;
    return await Future.value(nvalue);
  } 

}

 
 
  //MxWsList.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

mixin MxWsList {
  ListView mxWsListeGetir(BuildContext context, List<int> ciftSayilar) {
    return ListView.builder(
        scrollDirection: Axis.vertical,
        padding: EdgeInsets.all(8.0),
        itemCount: ciftSayilar.length,
        itemBuilder: (BuildContext context, sira) {
          return Text(ciftSayilar[sira].toString());
        });
  }
}

  
   
 //MxWsButton.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutternewpatter/Ws/WsCounter/StWsCounter.dart';
import 'package:provider/provider.dart';

mixin MxWsButton {

  Widget mxWsButtonArtir(BuildContext context){
        final state = Provider.of<StWsCounter>(context);
  return RaisedButton(
      child: !state.hasdata ? Text("Pasif") : Icon(Icons.add),
      onPressed: !state.hasdata ? null : () => state.setArtir(),
    );

  }

  Widget mxWsButtonAzalt( BuildContext context){
        final state = Provider.of<StWsCounter>(context);
  return RaisedButton(
      child: !state.hasdata ? Text("Pasif") : Icon(Icons.remove),
      onPressed: !state.hasdata ? null : () => state.setAzalt(),
    );

  }
 
 

}
  
 
   //MxOpSayiIslemleri.dart

mixin MxOpSayiIslemleri {
  List<int> mxOpCiftSayilariGetir(int sayi) {
    var ciftSayilar = <int>[];

    for (int i=0; i<=sayi; i++) if (i % 2 == 0) ciftSayilar.add(i);
    return ciftSayilar;
  }

}


  

This Post Has 6 Comments

  1. Onur

    Çok güzel bilgiler, değerli vaktiniz harcayıp böyle paylaşımlar yapmanız takdire şayan, teşekkür ederim.

  2. admin

    Teşekkür ederim.

    1. Aytaç

      Hocam öncelikle bende teşekkür ederim, size zahmet çok anlayamadığım Türkçe kaynağın hiç olmadığı (get it) package’ni yeni bir makalede anlatabilir misiniz ?

      1. fdAdmin

        Bir göz gezdirdiğimde kısaca şunu söyleyebilirim : uygulama düzeyinde heryerden erişilebilir nesnelerin (tekil olarak) elde etmek ve yönetmek(dispose/yok etmek) için bir paket gibi görünüyor.Bunu bir singlaton nesne yada ust seviye bir başlatmayla(paket içinde çıplak olarak final or= Ornek(); gibi tanımlayarak) yapabilirsiniz halihazırda.Kanaatim Flutter’ da asıl sorun heryerden erişilebilir state tutmak değil, state değişimi ile senkron çalışan rebuild elde etmek. get it paketi stateki değişimleri futurebulder veya bir takım yöntemlerle sağlıyor gibi görünüyor. provider paketine bir göz gezdirmenizi tavsiye ederim.Aynı işi materyal app üzerinde ChangeNotifierProvider olmayan bir provider “Provider( create: (_) => new MyModel(), child: dispose: ) ” ile sağlayabilirsiniz.provider paketi hem state e (yalnız alt widgetlerden) heryerden erişim sağlayabilir hemde ekranın yeniden çizilmesini senkronize edebilirsiniz.
        aslında temel amaç, state yönetimiyle ilgili. State verisinin ilk yüklenme değerinin future(geç gelen bir değer) içermesi.State yönetimi ile ilgili tüm paketler bu sorunun etranda dönüp dolaşıp yeni bir yaklaşımla bu sorunu aşmaya pratik çözümler bulmaya çalışıyor. Düşünün sayfa daha yüklenmeden önce sayfada gösterilecek verinin/bilginin hazır olması gerekir.veri hazır değilse sayfa, verinin sonradan geleceğini bilmeli ona göre bir bekleme widgeti göstermeli bir taraftan da veri getirme (future) metodunu çalıştırmalıdır.(hem state üretmeyi tetiklemeli hem rebuildi yönetmeli) bu durum önceki kısa makalede belirtilen maddelere giriyor. burda ilginç olan şey future bir değer beklerken widgetin bir şekilde rebuild edildiği senaryo işte bu tür sorunları ele almak ayrı bir kavramı state yönetimi kavramını zorunlu kılıyor. get it paket açıklamasında bir state yönetimi paketi iddiasında olmadığını belirtmekle birlikte çeşitli yöntemler sunmuş.tahminim her yerden erişilebilir servisler için ve özellikle test edileblirlik sağlamak için yazılmış bir paket. …motivasyonunuzu yüksek tutmanız dileğiyle iyi çalışmalar…

        1. Aytaç

          Çok teşekkür ederim

  3. fuat

    tskler güzel bir yazi olmus

Bir cevap yazın