StreamBuilder

监听数据流并自动重建UI的组件

StreamBuilder是Flutter中用于监听数据流(Stream)并自动重建UI的组件。它通过监听Stream对象,在数据发生变化时自动调用builder函数重新构建界面,实现响应式编程。

核心逻辑:

  • 订阅指定的Stream对象
  • 监听数据流中的事件(数据、错误、完成)
  • 根据最新的快照(AsyncSnapshot)数据重建UI
  • 自动管理订阅生命周期,避免内存泄漏

使用场景

  • 实时数据展示: 聊天消息、股票价格、实时监控数据
  • 网络请求状态管理: 加载中、成功、错误状态的UI展示
  • 用户输入响应: 搜索框实时搜索、表单验证
  • 数据库监听: Firestore实时数据库更新显示

示例

1. 基础计数器应用

import 'dart:async';
import 'package:flutter/material.dart';

class CounterExample extends StatefulWidget {
  
  _CounterExampleState createState() => _CounterExampleState();
}

class _CounterExampleState extends State<CounterExample> {
  final StreamController<int> _controller = StreamController<int>();
  int _counter = 0;

  void _incrementCounter() {
    _counter++;
    _controller.sink.add(_counter);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('StreamBuilder 计数器')),
      body: Center(
        child: StreamBuilder<int>(
          stream: _controller.stream,
          initialData: 0,
          builder: (context, snapshot) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('点击次数:'),
                Text(
                  '${snapshot.data}',
                  style: Theme.of(context).textTheme.headline4,
                ),
                if (snapshot.hasError)
                  Text('错误: ${snapshot.error}'),
              ],
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: Icon(Icons.add),
      ),
    );
  }

  
  void dispose() {
    _controller.close();
    super.dispose();
  }
}

2. 网络请求状态管理

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class NetworkExample extends StatelessWidget {
  final Stream<String> _dataStream = () {
    final controller = StreamController<String>();
    
    // 模拟网络请求
    Future.delayed(Duration(seconds: 2), () async {
      try {
        final response = await http.get(Uri.parse('https://api.example.com/data'));
        if (response.statusCode == 200) {
          controller.sink.add(utf8.decode(response.bodyBytes));
        } else {
          controller.sink.addError('HTTP ${response.statusCode}');
        }
      } catch (e) {
        controller.sink.addError('网络请求失败: $e');
      } finally {
        controller.close();
      }
    });
    
    return controller.stream;
  }();

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('网络请求示例')),
      body: StreamBuilder<String>(
        stream: _dataStream,
        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('错误: ${snapshot.error}'),
                ],
              ),
            );
          }
          
          if (snapshot.hasData) {
            return Center(
              child: Text('数据: ${snapshot.data}'),
            );
          }
          
          return Center(child: Text('无数据'));
        },
      ),
    );
  }
}

3. 实时搜索功能

import 'dart:async';
import 'package:flutter/material.dart';

class SearchExample extends StatefulWidget {
  
  _SearchExampleState createState() => _SearchExampleState();
}

class _SearchExampleState extends State<SearchExample> {
  final TextEditingController _searchController = TextEditingController();
  final StreamController<List<String>> _resultsController = StreamController<List<String>>();
  final List<String> _allItems = ['苹果', '香蕉', '橙子', '葡萄', '西瓜', '菠萝', '芒果'];

  
  void initState() {
    super.initState();
    _searchController.addListener(_onSearchChanged);
  }

  void _onSearchChanged() {
    final query = _searchController.text.toLowerCase();
    if (query.isEmpty) {
      _resultsController.sink.add([]);
      return;
    }

    final results = _allItems
        .where((item) => item.toLowerCase().contains(query))
        .toList();
    _resultsController.sink.add(results);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('实时搜索示例')),
      body: Column(
        children: [
          Padding(
            padding: EdgeInsets.all(16),
            child: TextField(
              controller: _searchController,
              decoration: InputDecoration(
                labelText: '搜索水果',
                prefixIcon: Icon(Icons.search),
                border: OutlineInputBorder(),
              ),
            ),
          ),
          Expanded(
            child: StreamBuilder<List<String>>(
              stream: _resultsController.stream,
              builder: (context, snapshot) {
                if (!snapshot.hasData || snapshot.data!.isEmpty) {
                  return Center(child: Text(_searchController.text.isEmpty 
                      ? '请输入搜索关键词' 
                      : '未找到相关结果'));
                }

                return ListView.builder(
                  itemCount: snapshot.data!.length,
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text(snapshot.data![index]),
                      leading: Icon(Icons.fruit),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
    );
  }

  
  void dispose() {
    _searchController.dispose();
    _resultsController.close();
    super.dispose();
  }
}

注意点

常见问题

  1. 内存泄漏风险: 忘记关闭StreamController可能导致内存泄漏
  2. 不必要的重建: Stream频繁发射数据会导致UI频繁重建,影响性能
  3. 错误处理不完善: 未正确处理错误状态可能导致应用崩溃
  4. 初始状态处理: 忽略initialData可能导致空数据异常

优化技巧

  1. 使用rxdart进行流操作: 利用debouncedistinct等操作符优化性能
stream: _searchController.textChanges
    .debounceTime(Duration(milliseconds: 300))
    .distinct(),
  1. 合理使用initialData: 提供合适的初始数据避免空值检查
initialData: LoadingState.loading,
  1. 条件重建: 在builder中使用条件判断避免不必要的重建
builder: (context, snapshot) {
  if (snapshot.connectionState == ConnectionState.waiting && 
      !snapshot.hasData) {
    return LoadingWidget(); // 只在真正需要时显示加载
  }
  // ... 其他逻辑
}

最佳实践

  1. 始终在dispose中关闭StreamController
  2. 使用AsyncSnapshotconnectionState进行精确的状态管理
  3. 为关键操作添加错误边界处理
  4. 考虑使用StreamBuilderkey属性控制重建行为

构造函数

const StreamBuilder<T>({
  Key? key,
  T? initialData,
  required Stream<T> stream,
  required AsyncWidgetBuilder<T> builder,
})

属性

属性名属性类型说明
keyKey?组件标识键,用于控制组件重建
initialDataT?初始数据,在流开始前使用
streamStream<T>要监听的数据流
builderAsyncWidgetBuilder<T>构建函数,接收上下文和快照

关键属性详解

stream(Stream)

  • 作用: 指定要监听的数据源
  • 重要性: 核心属性,决定StreamBuilder的行为
  • 使用要点: 确保Stream正确管理生命周期,避免内存泄漏

builder(AsyncWidgetBuilder)

  • 作用: 根据数据快照构建UI
  • 参数: 接收BuildContextAsyncSnapshot
  • 最佳实践: 根据snapshot.connectionStatesnapshot.hasData/hasError进行条件渲染

initialData(T?)

  • 作用: 提供初始数据,避免空值检查
  • 使用场景: 适合在数据加载前显示有意义的初始状态
  • 注意事项: 如果Stream立即发射数据,initialData可能不会被使用