skills/flutter/references/widgets-async.md

4.5 KiB

name description
widgets-async 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.

FutureBuilder<String>(
  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

FutureBuilder<String>(
  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.

StreamBuilder<int>(
  stream: counterStream(),
  initialData: 0,
  builder: (context, snapshot) {
    if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    }
    
    return Text('Count: ${snapshot.data}');
  },
)

StreamBuilder with ConnectionState

StreamBuilder<String>(
  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

class DataWidget extends StatelessWidget {
  const DataWidget({super.key});

  Future<String> fetchData() async {
    await Future.delayed(const Duration(seconds: 2));
    return 'Data loaded';
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      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

StreamBuilder<List<String>>(
  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