--- name: widgets-async description: Flutter async widgets - FutureBuilder, StreamBuilder, and handling asynchronous operations --- # Async Widgets Flutter provides widgets for building UIs based on asynchronous data. ## FutureBuilder Build widgets based on Future completion. ```dart FutureBuilder( future: fetchData(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator(); } if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } if (snapshot.hasData) { return Text('Data: ${snapshot.data}'); } return const Text('No data'); }, ) ``` ## FutureBuilder States ```dart FutureBuilder( future: fetchData(), builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: return const Text('Not started'); case ConnectionState.waiting: return const CircularProgressIndicator(); case ConnectionState.active: return const Text('Loading...'); case ConnectionState.done: if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } return Text('Data: ${snapshot.data}'); } }, ) ``` ## StreamBuilder Build widgets based on Stream events. ```dart StreamBuilder( stream: counterStream(), initialData: 0, builder: (context, snapshot) { if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } return Text('Count: ${snapshot.data}'); }, ) ``` ## StreamBuilder with ConnectionState ```dart StreamBuilder( stream: dataStream(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const CircularProgressIndicator(); } if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } if (!snapshot.hasData) { return const Text('No data'); } return Text('Data: ${snapshot.data}'); }, ) ``` ## Complete Example ```dart class DataWidget extends StatelessWidget { const DataWidget({super.key}); Future fetchData() async { await Future.delayed(const Duration(seconds: 2)); return 'Data loaded'; } @override Widget build(BuildContext context) { return FutureBuilder( future: fetchData(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } if (snapshot.hasError) { return Scaffold( appBar: AppBar(title: const Text('Error')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error, size: 48, color: Colors.red), const SizedBox(height: 16), Text('Error: ${snapshot.error}'), ElevatedButton( onPressed: () { // Retry }, child: const Text('Retry'), ), ], ), ), ); } return Scaffold( appBar: AppBar(title: const Text('Data')), body: Center( child: Text('Data: ${snapshot.data}'), ), ); }, ); } } ``` ## StreamBuilder with List ```dart StreamBuilder>( stream: itemsStream(), builder: (context, snapshot) { if (!snapshot.hasData) { return const CircularProgressIndicator(); } final items = snapshot.data!; return ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return ListTile(title: Text(items[index])); }, ); }, ) ``` ## Key Points - Use `FutureBuilder` for one-time async operations - Use `StreamBuilder` for continuous data streams - Check `connectionState` to handle loading states - Use `hasError` to check for errors - Use `hasData` to check if data is available - Provide `initialData` for StreamBuilder when needed - Handle all connection states for better UX - Use `snapshot.data` to access the result - Use `snapshot.error` to access errors