Flutter: FutureBuilder ile Asenkron Listeleme
Ahmet Balaman
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.