FutureBuilder kullanırken karşılaşılan sorunlar

Flutter’da FutureBuilder ile asenkron bir şekilde networking yapıp, dönen sonucu kolaylıkla Widget içerisinde gösterebiliyoruz. Bu kadar kolay bir Widget’ın kullanımında doğal olarak bazı sorunlar gerçekleşebiliyor. Örneğin birden fazla çağrılabiliyor. Bu durumda nasıl bir aksiyon almalıyız bu yazımda bunu anlatacağım.

Öncelikle FutureBuilder neden bir çok defa çağrılıyor? setState() çağrıldığı durumlarda build() fonksiyonu yeniden tetikleniyor ve ekrandaki widget’lar yeniden derleniyor. Kısacası State’i güncelleyen kod parçacıkları bu duruma sebebiyet veriyor. Bunu iki farklı şekilde durdurabiliriz.

İlk olarak, Future metodunu dışarıda tanımlayıp, initState içerisinde bir değişkene atayıp, bu değişkenin referansını FutureBuilder içerisinde kullanırsak, FutureBuilder diğer setState metotlarından etkilenmez.

class FutureSample extends StatefulWidget {
  @override
  _FutureSampleState createState() => _FutureSampleState();
}

class _FutureSampleState extends State<FutureSample> {
  Future myFuture; // Future dışarıda tanımlanıyor

// İş yapacak fonksiyon yazılıyor.

  Future<String> _fetchData() async {
    await Future.delayed(Duration(seconds: 1));
    return 'data'
  }

// initState içerisinde fonksiyon bir değişkene atanıyor

  @override
  void initState() {
    myFuture = _fetchData();
    super.initState();
  }

// FutureBuilder içerisinde myFuture yalnızca referansıyla çağrılıyor

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: FutureBuilder(
          future: myFuture,
          builder: (ctx, snapshot) {
            if (snapshot.hasData) {
             return Text(snapshot.data.toString());
            }
            return CircularProgressIndicator();
          },
        ),
      ),
    );
  }

İkinci yöntem ise async/async.dart kütüphanesi ile AsyncMemoizer sınıfının runOnce metotuyla çözülebiliyor. runOnce içinde yer alan kod parçası o state içerisindeyken yalnızca bir kez çalışıyor.

class MemoizerSample extends StatefulWidget {
  @override
  _MemoizerSampleState createState() => _MemoizerSampleState();
}

class _MemoizerSampleState extends State<MemoizerSample> {
  AsyncMemoizer _memoizer;


//Bu fonksiyon çağrıldığında memoizer ile runOnce olarak execute edildiğinden yalnızca bir defa çağrılacak ve State değişmediği sürece sonraki çağrılmaları es geçecek. 
  _fetchData() async {
    return this._memoizer.runOnce(() async {
      await Future.delayed(Duration(seconds: 1));
      return 'data';
    });
  }


  @override
  void initState() {
    super.initState();
    _memoizer = AsyncMemoizer();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: FutureBuilder(
          future: _fetchData(),
          builder: (ctx, snapshot) {
            if (snapshot.hasData) {
              return Text(snapshot.data.toString());
            }
            return CircularProgressIndicator();
          },
        ),
      ),
    );
  }
}

FutureBuilder’ın state içerisinde bir kere çağrılmasını yukarıdaki yöntemlerle sağlayabilirsiniz. Eğer bir şeyleri yanlış yaptığınızı düşünüyorsanız, linkte yer alan FutureBuilder sayfasında şu yazıya dikkatinizi çekmek istiyorum.

The future must have been obtained earlier, e.g. during State.initStateState.didUpdateConfig, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder‘s parent is rebuilt, the asynchronous task will be restarted.

Eğer future methodunuzu FutureBuilder ile birlikte oluşturursanız, FutureBuilder’ın parent widget’ı yeniden derlendiğinde asenkron görev de yeniden başlatılacaktır. Sonuç olarak genel anlamda birinci yöntemi kullanmak en doğrusu olacaktır. 🙂 Fakat özel ihtiyaçlara binaen ikinci yöntemi de akılda tutmakta fayda var.