İçeriğe geç / Skip to content / Zum Inhalt
Ahmet Balaman LogoAhmet Balaman

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.

Kommentare