Ahmet Balaman LogoAhmet Balaman

Flutter: FutureBuilder ile Asenkron Listeleme

personAhmet Balaman
calendar_today
FlutterFutureBuilderAsyncAwaitWidgetAPI

FutureBuilder, Flutter'da asenkron işlemleri widget'lar içinde kullanmak için tasarlanmış özel bir yapıdır. Widget'lar doğrudan async özelliğine sahip olmadığından, asenkron fonksiyonları UI'da kullanmak için FutureBuilder gereklidir.

Neden FutureBuilder?

  • async özelliği olan fonksiyonları kullanırken await ile sadece yapması gereken işlemi bitirene kadar çalışmasını sağlarız
  • Fakat await kullanmak için async özelliği olan fonksiyon içinde olmamız gereklidir
  • async özelliği olan fonksiyonu widget içinde kullanmak istediğimizde async özelliği olması gerekmektedir
  • Widget'larda bu özellik yoktur!
  • Widget içinde async özelliğini kullanmak için FutureBuilder yapısı gereklidir

Canlı Demo

Aşağıdaki interaktif örnekte bu widget'ı deneyebilirsiniz:

💡 Eğer yukarıdaki örnek açılmazsa, DartPad linkine tıklayarak yeni sekmede çalıştırabilirsiniz.

Temel Kullanım

FutureBuilder<String>(
  future: fetchData(), // async fonksiyon
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Text('Hata: ${snapshot.error}');
    } else if (snapshot.hasData) {
      return Text('Veri: ${snapshot.data}');
    }
    return Text('Veri bulunamadı');
  },
)

// Async fonksiyon
Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 2));
  return 'Merhaba Flutter!';
}

Önemli Özellikler

Özellik Açıklama
future Beklenecek Future nesnesi
builder UI oluşturan fonksiyon
initialData Başlangıç verisi

ConnectionState Durumları

Durum Açıklama
none Future henüz atanmadı
waiting Future çalışıyor, bekleniyor
active Stream için aktif veri akışı
done Future tamamlandı

ListView ile FutureBuilder

API'den veri çekip liste oluşturma:

class MyListPage extends StatelessWidget {
  Future<List<String>> fetchItems() async {
    // API çağrısı simülasyonu
    await Future.delayed(Duration(seconds: 2));
    return ['Flutter', 'Dart', 'Firebase', 'Android', 'iOS'];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Liste')),
      body: FutureBuilder<List<String>>(
        future: fetchItems(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          }
          
          if (snapshot.hasError) {
            return Center(child: Text('Hata: ${snapshot.error}'));
          }
          
          if (!snapshot.hasData || snapshot.data!.isEmpty) {
            return Center(child: Text('Veri bulunamadı'));
          }

          final items = snapshot.data!;
          return ListView.builder(
            itemCount: items.length,
            itemBuilder: (context, index) {
              return ListTile(
                leading: CircleAvatar(child: Text('${index + 1}')),
                title: Text(items[index]),
              );
            },
          );
        },
      ),
    );
  }
}

GridView ile FutureBuilder

Aynı çalışmayı GridView.builder içinde de kullanabiliriz:

class MyGridPage extends StatelessWidget {
  Future<List<Map<String, dynamic>>> fetchProducts() async {
    await Future.delayed(Duration(seconds: 2));
    return [
      {'name': 'Ürün 1', 'price': 99, 'color': Colors.red},
      {'name': 'Ürün 2', 'price': 149, 'color': Colors.blue},
      {'name': 'Ürün 3', 'price': 199, 'color': Colors.green},
      {'name': 'Ürün 4', 'price': 249, 'color': Colors.orange},
      {'name': 'Ürün 5', 'price': 79, 'color': Colors.purple},
      {'name': 'Ürün 6', 'price': 129, 'color': Colors.teal},
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Ürünler')),
      body: FutureBuilder<List<Map<String, dynamic>>>(
        future: fetchProducts(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          }
          
          if (snapshot.hasError) {
            return Center(child: Text('Hata: ${snapshot.error}'));
          }
          
          if (!snapshot.hasData || snapshot.data!.isEmpty) {
            return Center(child: Text('Ürün bulunamadı'));
          }

          final products = snapshot.data!;
          return GridView.builder(
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              mainAxisSpacing: 8,
              crossAxisSpacing: 8,
            ),
            padding: EdgeInsets.all(8),
            itemCount: products.length,
            itemBuilder: (context, index) {
              final product = products[index];
              return Card(
                color: product['color'],
                child: Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(
                        product['name'],
                        style: TextStyle(
                          color: Colors.white,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      SizedBox(height: 8),
                      Text(
                        '₺${product['price']}',
                        style: TextStyle(color: Colors.white),
                      ),
                    ],
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

API'den Gerçek Veri Çekme

import 'dart:convert';
import 'package:http/http.dart' as http;

class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

class UsersPage extends StatelessWidget {
  Future<List<User>> fetchUsers() async {
    final response = await http.get(
      Uri.parse('https://jsonplaceholder.typicode.com/users'),
    );
    
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      return data.map((json) => User.fromJson(json)).toList();
    } else {
      throw Exception('Kullanıcılar yüklenemedi');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Kullanıcılar')),
      body: FutureBuilder<List<User>>(
        future: fetchUsers(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          }
          
          if (snapshot.hasError) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.error, size: 64, color: Colors.red),
                  SizedBox(height: 16),
                  Text('Hata: ${snapshot.error}'),
                ],
              ),
            );
          }
          
          final users = snapshot.data!;
          return ListView.builder(
            itemCount: users.length,
            itemBuilder: (context, index) {
              final user = users[index];
              return Card(
                margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4),
                child: ListTile(
                  leading: CircleAvatar(
                    child: Text(user.name[0]),
                  ),
                  title: Text(user.name),
                  subtitle: Text(user.email),
                  trailing: Icon(Icons.chevron_right),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

FutureBuilder Best Practices

1. Future'ı State'te Saklama

class MyPage extends StatefulWidget {
  @override
  _MyPageState createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  late Future<List<String>> _futureData;

  @override
  void initState() {
    super.initState();
    _futureData = fetchData(); // Future'ı bir kez oluştur
  }

  Future<List<String>> fetchData() async {
    await Future.delayed(Duration(seconds: 2));
    return ['Veri 1', 'Veri 2', 'Veri 3'];
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<List<String>>(
      future: _futureData, // Aynı Future'ı kullan
      builder: (context, snapshot) {
        // ...
      },
    );
  }
}

2. Yenileme Özelliği

class _MyPageState extends State<MyPage> {
  late Future<List<String>> _futureData;

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  void _loadData() {
    setState(() {
      _futureData = fetchData();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Veri'),
        actions: [
          IconButton(
            icon: Icon(Icons.refresh),
            onPressed: _loadData, // Yenile
          ),
        ],
      ),
      body: FutureBuilder<List<String>>(
        future: _futureData,
        builder: (context, snapshot) {
          // ...
        },
      ),
    );
  }
}

Özet

  • FutureBuilder: Widget'larda async kullanımı için
  • ConnectionState: Bekleme, hata, tamamlanma durumları
  • snapshot.hasData: Veri kontrolü
  • snapshot.hasError: Hata kontrolü
  • ListView.builder + FutureBuilder: Dinamik asenkron liste
  • GridView.builder + FutureBuilder: Dinamik asenkron grid

FutureBuilder, API çağrıları ve veritabanı işlemleri gibi asenkron operasyonları Flutter UI'da kullanmak için vazgeçilmez bir yapıdır.

Yorumlar