Flutter: Asynchrone Listen mit FutureBuilder
Ahmet Balaman
FlutterFutureBuilderAsyncAwaitWidgetAPI
FutureBuilder ist eine spezielle Struktur in Flutter, um asynchrone Abläufe innerhalb von Widgets zu nutzen. Da Widgets selbst nicht async sein können, wird FutureBuilder benötigt, um asynchrone Funktionen in der Oberfläche einzusetzen.
Warum FutureBuilder?
- Bei Funktionen mit async-Fähigkeit verwenden wir await, damit erst nach Abschluss der Aufgabe weitergearbeitet wird
- Um await nutzen zu können, müssen wir uns jedoch innerhalb einer Funktion mit async-Fähigkeit befinden
- Wenn wir eine async-Funktion innerhalb eines Widgets nutzen wollen, müsste dieses selbst async-fähig sein
- Widgets besitzen diese Fähigkeit nicht!
- Deshalb ist die FutureBuilder-Struktur nötig, um async innerhalb von Widgets zu verwenden
Live-Demo
Sie können dieses Widget im interaktiven Beispiel unten ausprobieren:
💡 Falls das Beispiel oben nicht lädt, klicken Sie auf DartPad, um es in einem neuen Tab auszuführen.
Grundlegende Verwendung
FutureBuilder<String>(
future: fetchData(), // async function
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else if (snapshot.hasData) {
return Text('Data: ${snapshot.data}');
}
return Text('No data found');
},
)
// Async function
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return 'Hello Flutter!';
}Wichtige Eigenschaften
| Eigenschaft | Beschreibung |
|---|---|
future |
Das Future-Objekt, auf das gewartet wird |
builder |
Funktion, die die Oberfläche aufbaut |
initialData |
Anfangswert der Daten |
Zustände von ConnectionState
| Zustand | Beschreibung |
|---|---|
none |
Es wurde noch kein Future zugewiesen |
waiting |
Das Future läuft, es wird gewartet |
active |
Aktiver Datenstrom bei einem Stream |
done |
Das Future ist abgeschlossen |
FutureBuilder mit ListView
Daten von einer API abrufen und eine Liste erzeugen:
class MyListPage extends StatelessWidget {
Future<List<String>> fetchItems() async {
// API call simulation
await Future.delayed(Duration(seconds: 2));
return ['Flutter', 'Dart', 'Firebase', 'Android', 'iOS'];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('List')),
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('Error: ${snapshot.error}'));
}
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No data found'));
}
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]),
);
},
);
},
),
);
}
}FutureBuilder mit GridView
Denselben Ansatz können wir mit GridView.builder verwenden:
class MyGridPage extends StatelessWidget {
Future<List<Map<String, dynamic>>> fetchProducts() async {
await Future.delayed(Duration(seconds: 2));
return [
{'name': 'Product 1', 'price': 99, 'color': Colors.red},
{'name': 'Product 2', 'price': 149, 'color': Colors.blue},
{'name': 'Product 3', 'price': 199, 'color': Colors.green},
{'name': 'Product 4', 'price': 249, 'color': Colors.orange},
{'name': 'Product 5', 'price': 79, 'color': Colors.purple},
{'name': 'Product 6', 'price': 129, 'color': Colors.teal},
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Products')),
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('Error: ${snapshot.error}'));
}
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No products found'));
}
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),
),
],
),
),
);
},
);
},
),
);
}
}Echte Daten von einer API abrufen
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('Failed to load users');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Users')),
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('Error: ${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),
),
);
},
);
},
),
);
}
}Best Practices für FutureBuilder
1. Das Future im State ablegen
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(); // Create Future once
}
Future<List<String>> fetchData() async {
await Future.delayed(Duration(seconds: 2));
return ['Data 1', 'Data 2', 'Data 3'];
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<String>>(
future: _futureData, // Use the same Future
builder: (context, snapshot) {
// ...
},
);
}
}2. Funktion zum Aktualisieren
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('Data'),
actions: [
IconButton(
icon: Icon(Icons.refresh),
onPressed: _loadData, // Refresh
),
],
),
body: FutureBuilder<List<String>>(
future: _futureData,
builder: (context, snapshot) {
// ...
},
),
);
}
}Zusammenfassung
- FutureBuilder: Um async in Widgets zu nutzen
- ConnectionState: Zustände für Warten, Fehler und Abschluss
- snapshot.hasData: Prüfung auf vorhandene Daten
- snapshot.hasError: Prüfung auf Fehler
- ListView.builder + FutureBuilder: Dynamische asynchrone Liste
- GridView.builder + FutureBuilder: Dynamisches asynchrones Raster
FutureBuilder ist unverzichtbar, um asynchrone Abläufe wie API-Aufrufe und Datenbankzugriffe in der Flutter-Oberfläche zu nutzen.