You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在StreamBuilder中创建列表并展示Firebase数据至Syncfusion折线图

解决Flutter中Syncfusion图表结合Firebase流展示传感器数据的问题及替代方案

一、修复Syncfusion折线图X轴无数据的问题

1. 定义传感器数据模型

先创建数据类解析Firebase返回的JSON数据:

class SensorData {
  final double ec;
  final double ph;
  final double rtd;
  final double humidity;
  final double temperature;
  final DateTime timestamp;

  SensorData({
    required this.ec,
    required this.ph,
    required this.rtd,
    required this.humidity,
    required this.temperature,
    required this.timestamp,
  });

  // 从Firebase Map数据转模型
  factory SensorData.fromMap(Map<String, dynamic> map) {
    return SensorData(
      ec: (map['EC'] as num).toDouble(),
      ph: (map['PH'] as num).toDouble(),
      rtd: (map['RTD'] as num).toDouble(),
      humidity: (map['湿度'] as num).toDouble(),
      temperature: (map['温度'] as num).toDouble(),
      timestamp: DateTime.fromMillisecondsSinceEpoch(map['timestamp']),
    );
  }
}

2. 转换Firebase单条数据流为列表流

onChildAdded每次只返回单条新增数据,需要将数据累加为完整列表流:

final List<SensorData> _sensorDataList = [];

// 转换流为累加后的列表流
Stream<List<SensorData>> get sensorDataStream {
  return FirebaseDatabase.instance.ref('sensor_data').onChildAdded.map((event) {
    final data = SensorData.fromMap(event.snapshot.value as Map<String, dynamic>);
    _sensorDataList.add(data);
    // 按时间戳排序,保证数据顺序正确
    _sensorDataList.sort((a, b) => a.timestamp.compareTo(b.timestamp));
    return List.from(_sensorDataList);
  });
}

3. 正确配置Syncfusion Cartesian Chart

在StreamBuilder中配置图表,重点处理X轴的时间映射:

StreamBuilder<List<SensorData>>(
  stream: sensorDataStream,
  builder: (context, snapshot) {
    if (!snapshot.hasData || snapshot.data!.isEmpty) {
      return const Center(child: Text('暂无数据'));
    }

    final dataList = snapshot.data!;

    return SfCartesianChart(
      primaryXAxis: DateTimeAxis(
        title: const AxisTitle(text: '时间'),
        labelFormat: 'HH:mm:ss', // 按需调整时间显示格式
      ),
      primaryYAxis: const NumericAxis(title: AxisTitle(text: '数值')),
      series: <LineSeries<SensorData, DateTime>>[
        LineSeries<SensorData, DateTime>(
          dataSource: dataList,
          xValueMapper: (SensorData data, _) => data.timestamp,
          yValueMapper: (SensorData data, _) => data.temperature,
          name: '温度',
          markerSettings: const MarkerSettings(isVisible: true),
        ),
        // 可添加其他传感器数据折线,比如PH值
        LineSeries<SensorData, DateTime>(
          dataSource: dataList,
          xValueMapper: (SensorData data, _) => data.timestamp,
          yValueMapper: (SensorData data, _) => data.ph,
          name: 'PH值',
          markerSettings: const MarkerSettings(isVisible: true),
        ),
      ],
      legend: const Legend(isVisible: true),
      tooltipBehavior: TooltipBehavior(enable: true),
    );
  },
)

核心问题说明

  • X轴无数据:大概率是时间戳未转换为DateTime类型,或xValueMapper未指向正确的时间字段;
  • 折线不连续:onChildAdded返回单条数据,必须累加为列表才能形成连续折线,否则图表只会显示最新的单个点。

二、替代方案:使用fl_chart实现折线图

如果Syncfusion配置仍有问题,可尝试fl_chart库,实现示例如下:

1. 添加依赖

在pubspec.yaml中加入:

dependencies:
  fl_chart: ^0.65.0

2. 构建折线图

复用之前的sensorDataStream流,在StreamBuilder中实现:

StreamBuilder<List<SensorData>>(
  stream: sensorDataStream,
  builder: (context, snapshot) {
    if (!snapshot.hasData || snapshot.data!.isEmpty) {
      return const Center(child: Text('暂无数据'));
    }

    final dataList = snapshot.data!;

    // 转换为fl_chart所需的点格式
    final temperatureSpots = dataList.map((data) {
      return FlSpot(
        data.timestamp.millisecondsSinceEpoch.toDouble(),
        data.temperature,
      );
    }).toList();

    final phSpots = dataList.map((data) {
      return FlSpot(
        data.timestamp.millisecondsSinceEpoch.toDouble(),
        data.ph,
      );
    }).toList();

    return LineChart(
      LineChartData(
        gridData: const FlGridData(show: true),
        titlesData: FlTitlesData(
          bottomTitles: AxisTitles(
            sideTitles: SideTitles(
              showTitles: true,
              getTitlesWidget: (value, meta) {
                // 将时间戳转为可读格式
                final time = DateTime.fromMillisecondsSinceEpoch(value.toInt());
                return Text(
                  '${time.hour}:${time.minute}:${time.second}',
                  style: const TextStyle(fontSize: 10),
                );
              },
            ),
          ),
          leftTitles: const AxisTitles(
            sideTitles: SideTitles(showTitles: true),
          ),
        ),
        borderData: FlBorderData(show: true),
        lineBarsData: [
          LineChartBarData(
            spots: temperatureSpots,
            isCurved: true,
            barWidth: 2,
            color: Colors.red,
            dotData: const FlDotData(show: true),
          ),
          LineChartBarData(
            spots: phSpots,
            isCurved: true,
            barWidth: 2,
            color: Colors.blue,
            dotData: const FlDotData(show: true),
          ),
        ],
        tooltipData: LineTooltipData(
          getTooltipItems: (touchedSpots) {
            return touchedSpots.map((spot) {
              final time = DateTime.fromMillisecondsSinceEpoch(spot.x.toInt());
              return LineTooltipItem(
                '${spot.y.toStringAsFixed(2)}\n${time.hour}:${time.minute}:${time.second}',
                const TextStyle(color: Colors.white),
              );
            }).toList();
          },
        ),
      ),
    );
  },
)

内容的提问来源于stack exchange,提问作者Erick Mahardika

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 08:29:58