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

Flutter结合Future与MQTT实时更新ListView/GridView内容方案咨询

解决方案:维护可变状态结合REST初始化与MQTT实时更新

你需要的是一个可动态更新的本地数据源,初始通过REST接口填充,后续通过MQTT消息修改数据源并触发UI刷新。FutureBuilder只适合一次性数据加载,StreamBuilder需要持续数据流,而你的场景更适合手动维护状态:


基础实现:StatefulWidget + setState

这是最直观的方案,适合新手快速上手:

1. 定义数据模型

先创建对应REST和MQTT消息的Item模型:

class Item {
  final String id;
  final String name;
  // 根据你的接口添加其他字段

  Item({required this.id, required this.name});

  // 从JSON解析Item
  factory Item.fromJson(Map<String, dynamic> json) => Item(
        id: json['id'],
        name: json['name'],
      );
}

2. 实现页面逻辑

在StatefulWidget中维护列表状态,初始化时加载REST数据,同时建立MQTT连接监听更新:

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

class ItemListScreen extends StatefulWidget {
  const ItemListScreen({super.key});

  @override
  State<ItemListScreen> createState() => _ItemListScreenState();
}

class _ItemListScreenState extends State<ItemListScreen> {
  List<Item> _items = [];
  bool _isLoading = true;
  late MqttClient _mqttClient;

  @override
  void initState() {
    super.initState();
    // 先加载初始数据
    _loadInitialItems();
    // 初始化MQTT连接
    _setupMqtt();
  }

  // 从REST接口获取初始列表
  Future<void> _loadInitialItems() async {
    try {
      final response = await http.get(Uri.parse('https://your-api-url/items'));
      if (response.statusCode == 200) {
        final List<dynamic> rawData = json.decode(response.body);
        setState(() {
          _items = rawData.map((json) => Item.fromJson(json)).toList();
          _isLoading = false;
        });
      } else {
        setState(() => _isLoading = false);
        // 可添加错误提示
      }
    } catch (e) {
      setState(() => _isLoading = false);
      // 处理网络异常
    }
  }

  // 配置MQTT连接与消息监听
  void _setupMqtt() async {
    _mqttClient = MqttClient('your-mqtt-server-address', 'unique-client-id');
    _mqttClient.logging(on: false);

    // 连接成功后的回调
    _mqttClient.onConnected = () {
      // 订阅更新主题
      _mqttClient.subscribe('item-update-topic', MqttQos.atLeastOnce);
      // 监听消息
      _mqttClient.updates!.listen((messageList) {
        final message = messageList[0].payload as MqttPublishMessage;
        final payloadStr = MqttPublishPayload.bytesToStringAsString(message.payload.message);
        final updatedItem = Item.fromJson(json.decode(payloadStr));
        
        // 找到对应项替换并刷新UI
        setState(() {
          final targetIndex = _items.indexWhere((item) => item.id == updatedItem.id);
          if (targetIndex != -1) {
            _items[targetIndex] = updatedItem;
          }
          // 如果需要处理新增项,取消下面注释
          // else {
          //   _items.add(updatedItem);
          // }
        });
      });
    };

    // 构建连接消息
    final connMsg = MqttConnectMessage()
        .withClientIdentifier('unique-client-id')
        .keepAliveFor(60);

    _mqttClient.connectionMessage = connMsg;

    // 尝试连接
    try {
      await _mqttClient.connect();
    } catch (e) {
      _mqttClient.disconnect();
      // 处理连接失败逻辑
    }
  }

  @override
  void dispose() {
    _mqttClient.disconnect();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) {
      return const Center(child: CircularProgressIndicator());
    }

    return ListView.builder(
      itemCount: _items.length,
      itemBuilder: (ctx, index) {
        final item = _items[index];
        return ListTile(
          title: Text(item.name),
          // 根据需求添加其他UI元素
        );
      },
    );
  }
}

进阶方案:状态管理库(以Provider为例)

如果项目规模较大,用状态管理库可以避免StatefulWidget的状态臃肿,让数据逻辑与UI分离:

1. 创建ItemProvider

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

class ItemProvider extends ChangeNotifier {
  List<Item> _items = [];
  bool _isLoading = true;
  late MqttClient _mqttClient;

  List<Item> get items => _items;
  bool get isLoading => _isLoading;

  ItemProvider() {
    _loadInitialItems();
    _setupMqtt();
  }

  Future<void> _loadInitialItems() async {
    try {
      final response = await http.get(Uri.parse('https://your-api-url/items'));
      if (response.statusCode == 200) {
        final List<dynamic> rawData = json.decode(response.body);
        _items = rawData.map((json) => Item.fromJson(json)).toList();
        _isLoading = false;
        notifyListeners();
      } else {
        _isLoading = false;
        notifyListeners();
      }
    } catch (e) {
      _isLoading = false;
      notifyListeners();
    }
  }

  void _setupMqtt() async {
    // MQTT配置逻辑同前,唯一区别是更新数据后调用notifyListeners()
    _mqttClient = MqttClient('your-mqtt-server-address', 'unique-client-id');
    _mqttClient.onConnected = () {
      _mqttClient.subscribe('item-update-topic', MqttQos.atLeastOnce);
      _mqttClient.updates!.listen((messageList) {
        final message = messageList[0].payload as MqttPublishMessage;
        final payloadStr = MqttPublishPayload.bytesToStringAsString(message.payload.message);
        final updatedItem = Item.fromJson(json.decode(payloadStr));
        
        final targetIndex = _items.indexWhere((item) => item.id == updatedItem.id);
        if (targetIndex != -1) {
          _items[targetIndex] = updatedItem;
          notifyListeners(); // 通知UI刷新
        }
      });
    };

    // 连接逻辑同前
    final connMsg = MqttConnectMessage()
        .withClientIdentifier('unique-client-id')
        .keepAliveFor(60);
    _mqttClient.connectionMessage = connMsg;

    try {
      await _mqttClient.connect();
    } catch (e) {
      _mqttClient.disconnect();
    }
  }
}

2. 在UI中使用Provider

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

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

  @override
  Widget build(BuildContext context) {
    return Consumer<ItemProvider>(
      builder: (ctx, provider, child) {
        if (provider.isLoading) {
          return const Center(child: CircularProgressIndicator());
        }
        return ListView.builder(
          itemCount: provider.items.length,
          itemBuilder: (ctx, index) {
            final item = provider.items[index];
            return ListTile(title: Text(item.name));
          },
        );
      },
    );
  }
}

核心思路总结

  • 放弃用FutureBuilder/StreamBuilder直接绑定一次性Future或无数据流的场景,改为手动维护可变列表状态
  • 初始数据通过REST接口加载后更新状态
  • MQTT收到更新消息时,找到列表中对应ID的项替换,再通过setState或状态管理库的通知方法触发UI刷新
  • 记得在页面销毁时断开MQTT连接,避免资源泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 01:47:52