Mit Flutter MQTT spricht eine einzige Dart-Codebasis unter Android, iOS, Desktop und im Web mit Ihren Geräten. In diesem Tutorial verbinden Sie eine Flutter-App mit dem Paket mqtt_client mit einem Cloud-Broker, zeigen Live-Sensormesswerte an und senden Ein/Aus-Befehle an ein Gerät – mit TLS auf nativen Plattformen und sicherem WebSocket im Browser.
Was Sie bauen
Die App hat einen Bildschirm: einen Live-Temperaturwert, der sich bei jeder Veröffentlichung eines Geräts aktualisiert, einen Chip für den Verbindungsstatus und einen Schalter, der einen Befehl zum Ein- oder Ausschalten eines Relais veröffentlicht. Unter der Haube nutzt sie zwei Topics:
| Topic | Richtung | Payload-Beispiel |
|---|---|---|
connectmq/demo/<your-id>/sensor/temperature |
Gerät zur App | {"value": 22.4} |
connectmq/demo/<your-id>/relay/set |
App zum Gerät | ON oder OFF |
Zum Testen verwenden wir den Öffentlichen MQTT-Broker unter broker.connectmq.com. Er akzeptiert anonyme Verbindungen mit jeder nicht leeren Client ID, ist aber nur für Tests und Prototypen gedacht: Alles darauf ist für jeden lesbar. Nutzen Sie also ein zufälliges Topic-Präfix und senden Sie niemals echte Produktionsdaten. Für den Produktivbetrieb setzen Sie die Zugangsdaten Ihres eigenen ConnectMQ-Brokers ein; die Struktur des Codes ändert sich dabei nicht.
Ein Flutter-MQTT-Paket wählen
Die am weitesten verbreitete Option ist mqtt_client auf pub.dev. Das Paket liefert zwei Client-Klassen:
MqttServerClientfür Android, iOS, macOS, Windows und Linux. Er spricht unverschlüsseltes TCP, TLS oder WebSocket.MqttBrowserClientfür Flutter Web, der nur WebSocket nutzen kann, weil Browser keine rohen TCP-Sockets erlauben.
mqtt_client implementiert MQTT 3.1.1. Brauchen Sie MQTT-5-Funktionen wie Message Expiry oder User Properties, pflegt derselbe Autor mqtt5_client mit einer sehr ähnlichen API. Für eine App mit Dashboard und Befehlen wie diese ist 3.1.1 völlig ausreichend. Wenn Sie Bibliotheken über Sprachgrenzen hinweg vergleichen, behandelt die Übersicht der MQTT-Client-Bibliotheken die Abwägungen.
Fügen Sie die Abhängigkeit hinzu:
flutter pub add mqtt_client
Ports und Transporte
Der Broker stellt vier Listener bereit, und Sie wählen pro Plattform einen davon:
| Plattform | Klasse | Transport | Port |
|---|---|---|---|
| Android, iOS, Desktop | MqttServerClient |
TLS | 8883 |
| Android, iOS, Desktop (hinter strengen Firewalls) | MqttServerClient mit useWebSocket |
WSS | 8084, Pfad /mqtt |
| Flutter Web | MqttBrowserClient |
WSS | 8084, Pfad /mqtt |
| Nur lokales Debugging | MqttServerClient |
TCP | 1883 |
Port 1883 ist unverschlüsselt; nutzen Sie ihn nur für schnelle Experimente. Alles, was Ihren Laptop verlässt, sollte 8883 oder 8084 verwenden. Hintergründe zu Browser-Transporten finden Sie in MQTT über WebSocket.
Den Flutter-MQTT-Verbindungsservice bauen
Halten Sie MQTT aus Ihren Widgets heraus. Eine kleine Service-Klasse besitzt den Client, stellt einen Stream von Temperaturen und einen Verbindungsstatus bereit und bietet eine Methode setRelay. Flutter Web kann dart:io nicht importieren, daher erzeugen wir den Client über einen bedingten Import.
lib/mqtt/client_factory.dart:
export 'client_factory_server.dart'
if (dart.library.js_interop) 'client_factory_web.dart';
lib/mqtt/client_factory_server.dart:
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
MqttClient createClient(String host, String clientId) {
final client = MqttServerClient.withPort(host, clientId, 8883);
client.secure = true; // TLS, gegen den System-Truststore validiert
return client;
}
lib/mqtt/client_factory_web.dart:
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_browser_client.dart';
MqttClient createClient(String host, String clientId) {
final client = MqttBrowserClient.withPort('wss://$host/mqtt', clientId, 8084);
client.websocketProtocols = MqttClientConstants.protocolsSingleDefault;
return client;
}
lib/mqtt/mqtt_service.dart:
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:mqtt_client/mqtt_client.dart';
import 'client_factory.dart';
class MqttService {
MqttService({
this.host = 'broker.connectmq.com',
this.username,
this.password,
String? clientId,
String? topicPrefix,
}) : clientId = clientId ?? 'flutter-${Random().nextInt(1000000000)}',
topicPrefix = topicPrefix ?? 'connectmq/demo/${Random().nextInt(1000000000)}';
final String host;
final String? username;
final String? password;
final String clientId;
final String topicPrefix;
late final MqttClient _client;
final _temperature = StreamController<double>.broadcast();
final _connected = StreamController<bool>.broadcast();
Stream<double> get temperature => _temperature.stream;
Stream<bool> get connected => _connected.stream;
String get sensorTopic => '$topicPrefix/sensor/temperature';
String get relayTopic => '$topicPrefix/relay/set';
Future<void> connect() async {
_client = createClient(host, clientId);
_client.keepAlivePeriod = 30;
_client.autoReconnect = true;
_client.resubscribeOnAutoReconnect = true;
_client.logging(on: false);
_client.onConnected = () => _connected.add(true);
_client.onDisconnected = () => _connected.add(false);
_client.onAutoReconnected = () => _connected.add(true);
final connMessage = MqttConnectMessage()
.withClientIdentifier(clientId)
.startClean();
if (username != null && password != null) {
connMessage.authenticateAs(username!, password!);
}
_client.connectionMessage = connMessage;
try {
await _client.connect();
} on Exception {
_client.disconnect();
rethrow;
}
if (_client.connectionStatus?.state != MqttConnectionState.connected) {
throw StateError('MQTT connect failed: ${_client.connectionStatus}');
}
_client.subscribe(sensorTopic, MqttQos.atLeastOnce);
_client.updates!.listen(_onMessages);
}
void _onMessages(List<MqttReceivedMessage<MqttMessage?>> messages) {
for (final m in messages) {
final publish = m.payload as MqttPublishMessage;
final text = MqttPublishPayload.bytesToStringAsString(publish.payload.message);
try {
final value = (jsonDecode(text)['value'] as num).toDouble();
_temperature.add(value);
} catch (_) {
// Fehlerhafte Payloads ignorieren, statt den Stream abstürzen zu lassen.
}
}
}
void setRelay(bool on) {
final builder = MqttClientPayloadBuilder()..addString(on ? 'ON' : 'OFF');
_client.publishMessage(relayTopic, MqttQos.atLeastOnce, builder.payload!);
}
void dispose() {
_client.disconnect();
_temperature.close();
_connected.close();
}
}
Einige Details sind hier wichtig. autoReconnect plus resubscribeOnAutoReconnect sorgt dafür, dass Sie bei einem wackeligen Mobilfunknetz nicht zwar verbunden, aber taub sind. startClean() ist richtig für einen UI-Client, dem es nur um Live-Daten geht; ein Client, der offline keine Befehle verpassen darf, würde stattdessen eine persistente Session nutzen (siehe MQTT Persistent Sessions). Das JSON-Parsing ist abgesichert, weil jeder auf ein öffentliches Topic veröffentlichen kann.
Die Flutter-Oberfläche
lib/main.dart:
import 'package:flutter/material.dart';
import 'mqtt/mqtt_service.dart';
void main() => runApp(const MaterialApp(home: DeviceScreen()));
class DeviceScreen extends StatefulWidget {
const DeviceScreen({super.key});
@override
State<DeviceScreen> createState() => _DeviceScreenState();
}
class _DeviceScreenState extends State<DeviceScreen> {
final mqtt = MqttService();
bool relayOn = false;
String? error;
@override
void initState() {
super.initState();
mqtt.connect().catchError((e) => setState(() => error = '$e'));
}
@override
void dispose() {
mqtt.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter MQTT Demo')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
StreamBuilder<bool>(
stream: mqtt.connected,
initialData: false,
builder: (_, s) => Chip(
label: Text(s.data! ? 'Connected' : 'Disconnected'),
),
),
if (error != null) Text(error!, style: const TextStyle(color: Colors.red)),
const SizedBox(height: 24),
StreamBuilder<double>(
stream: mqtt.temperature,
builder: (_, s) => Text(
s.hasData ? '${s.data!.toStringAsFixed(1)} °C' : '-- °C',
style: Theme.of(context).textTheme.displayMedium,
),
),
const SizedBox(height: 24),
SwitchListTile(
title: const Text('Relay'),
value: relayOn,
onChanged: (v) {
setState(() => relayOn = v);
mqtt.setRelay(v);
},
),
const SizedBox(height: 24),
SelectableText('Topic prefix: ${mqtt.topicPrefix}'),
],
),
),
);
}
}
Das Topic-Präfix wird auf dem Bildschirm angezeigt, damit Sie es zum Testen in ein Terminal kopieren können. Wägen Sie Flutter gegen native oder JavaScript-Stacks ab, vergleichen Sie es mit den Versionen derselben App als iOS MQTT Client in Swift und mit React Native MQTT.
Mit einem simulierten Gerät testen
Hardware brauchen Sie noch nicht. Öffnen Sie zwei Terminals und übernehmen Sie mit den Mosquitto-Kommandozeilen-Tools die Rolle des Geräts. Ersetzen Sie PREFIX durch den Wert, den die App anzeigt:
# Die Befehle der App beobachten
mosquitto_sub -h broker.connectmq.com -p 1883 -i sim-sub-81723 -t "PREFIX/relay/set" -v
# Alle 2 Sekunden einen simulierten Temperaturwert veröffentlichen
while true; do
mosquitto_pub -h broker.connectmq.com -p 1883 -i sim-pub-81723 \
-t "PREFIX/sensor/temperature" -m "{\"value\": $((RANDOM % 10 + 18)).$((RANDOM % 10))}"
sleep 2
done
Legen Sie den Schalter in der App um, und im Subscriber-Terminal sollte ON oder OFF ankommen. Wenn Sie später ein echtes Board anschließen, verwendet das ESP32-MQTT-Tutorial dasselbe Topic-Muster.
Flutter-MQTT-Topics für mehr Geräte strukturieren
Ein Gerät ist einfach. Wächst die App zu einer Geräteliste, vermeiden Sie eine Subscription pro Geräte-Widget. Abonnieren Sie einmal eine Wildcard wie <prefix>/+/sensor/temperature, lesen Sie die Geräte-ID in _onMessage aus dem Topic aus und geben Sie statt eines Einzelwerts eine nach Gerät geschlüsselte Map aus. So bleibt die Zahl der Subscriptions konstant, was auf geteilten Brokern mit Limits pro Client wichtig ist; der öffentliche MQTT-Broker erlaubt 10 Subscriptions pro Client. Baut Ihr Team eine Service-App für Instandhalter im Werk, lohnt es sich, das Präfix gleich an der ISA-95-Hierarchie eines Unified Namespace (UNS) auszurichten, etwa acme/werk-muenchen/montage/linie-1/+/sensor/temperature. Dann liest die App dieselben Topics wie MES und Dashboards.
Zwei weitere Gewohnheiten zahlen sich aus. Lassen Sie Geräte ihren aktuellen Zustand mit gesetztem Retain-Flag veröffentlichen, damit eine frisch geöffnete App sofort den echten Relaiszustand zeigt statt eines Standardwerts false; wie das geht, erklärt der Leitfaden zu MQTT Retained Messages. Und lassen Sie das Gerät Befehle auf einem separaten Zustands-Topic wie relay/state bestätigen, statt der Schalterstellung zu vertrauen. Die Oberfläche sollte zeigen, was das Gerät meldet, nicht was der Nutzer angetippt hat – geht ein Befehl verloren, springt der Schalter mit der nächsten Meldung auf den tatsächlichen Zustand zurück.
Vom öffentlichen MQTT-Broker in den Produktivbetrieb
Der öffentliche MQTT-Broker ist für Experimente gedacht. Für ein echtes Produkt nutzen Sie einen privaten Broker, am einfachsten einen verwalteten MQTT-Broker, damit sich nur Ihre Geräte und Apps verbinden können:
- Registrieren Sie sich im ConnectMQ-Portal, wählen Sie einen Plan und erstellen Sie einen Broker.
- Kopieren Sie den generierten Benutzernamen, das Passwort und die Client ID.
- Erzeugen Sie den Service damit:
MqttService(username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD', clientId: 'YOUR_CLIENT_ID', topicPrefix: 'acme/site1'). - Bleiben Sie bei TLS auf Port 8883 (oder WSS auf 8084 fürs Web).
Bei ConnectMQ sind Client IDs an die ausgestellten Zugangsdaten gebunden; jede gleichzeitig verbundene App-Installation braucht daher ihre eigene Identität. Liefern Sie nicht ein gemeinsames Passwort in jeder Kopie einer öffentlichen App aus; stellen Sie Zugangsdaten pro Nutzer oder pro Gerät über ein Backend unter Ihrer Kontrolle aus und betrachten Sie alles, was in eine mobile Binärdatei eingebettet ist, als auslesbar.
Plattform-Stolperfallen und Fehlerbehebung
| Symptom | Wahrscheinliche Ursache | Lösung |
|---|---|---|
| Android-Release-Build verbindet sich nicht, Debug funktioniert | Internetberechtigung fehlt | <uses-permission android:name="android.permission.INTERNET"/> in die Haupt-AndroidManifest.xml eintragen |
| macOS-App scheitert mit „Operation not permitted“ | Sandbox blockiert ausgehenden Netzwerkverkehr | com.apple.security.network.client in den Entitlements-Dateien aktivieren |
| Web-Build verbindet sich nie | MqttServerClient oder ws:// auf einer HTTPS-Seite verwendet |
MqttBrowserClient mit wss:// auf 8084 und Pfad /mqtt verwenden |
| Verbindet sich, bricht dann jede Minute ab | Keep-alive länger als das Leerlauf-Timeout des NAT | keepAlivePeriod auf 20 bis 30 Sekunden senken |
| Nachrichten stoppen nach dem Fortsetzen der App | Socket im Hintergrund beendet, kein Resubscribe | autoReconnect und resubscribeOnAutoReconnect aktiviert lassen |
| Connection refused, „identifier rejected“ | Leere oder doppelte Client ID | Pro Session eine eindeutige, nicht leere Client ID verwenden |
Mobile Betriebssysteme suspendieren Hintergrund-Apps aggressiv. Versuchen Sie unter iOS nicht, eine MQTT-Verbindung offen zu halten, während die App im Hintergrund ist; verbinden Sie sich neu, wenn die App in den Vordergrund zurückkehrt, und nutzen Sie Push-Benachrichtigungen für Alarme, die ein schlafendes Smartphone erreichen müssen. Der Leitfaden zum MQTT-Keep-alive-Intervall erklärt, wie Sie Timeouts für Mobilfunknetze abstimmen.
Wenn Sie sehen müssen, was tatsächlich über die Leitung geht, bietet das ConnectMQ-Portal einen MQTT-Client im Browser und einen Live-Topic-Explorer – der schnellste Weg, um festzustellen, ob ein Problem in Ihrer App oder im Gerät liegt.
Starten Sie mit ConnectMQ
Ihre Flutter-App ist bereit für einen echten Broker. Erstellen Sie unter app.connectmq.com einen kostenlosen Serverless-Broker, ganz ohne Kreditkarte, kopieren Sie die generierten Zugangsdaten in MqttService, und Ihre Android-, iOS-, Desktop- und Web-Builds teilen sich denselben sicheren Endpunkt. Wenn Sie der kostenlosen Stufe entwachsen, funktioniert derselbe Code auch in den kostenpflichtigen und dedizierten Plänen.
Häufig gestellte Fragen
Funktioniert Flutter MQTT unter Flutter Web?
Ja, aber nur über WebSocket. Verwenden Sie MqttBrowserClient mit wss://broker.connectmq.com/mqtt auf Port 8084; Browser können keine rohen TCP-Verbindungen zu Port 1883 oder 8883 öffnen.
Unterstützt das Paket mqtt_client MQTT 5?
Nein, mqtt_client implementiert MQTT 3.1.1. Nutzen Sie das Schwesterpaket mqtt5_client, wenn Sie MQTT-5-Funktionen wie User Properties oder Message Expiry brauchen.
Kann ich eine MQTT-Verbindung aufrechterhalten, während meine Flutter-App im Hintergrund ist?
Auf Mobilgeräten nicht zuverlässig, weil iOS und Android Hintergrund-Sockets suspendieren. Verbinden Sie sich beim Fortsetzen neu und nutzen Sie Push-Benachrichtigungen für Alarme, die Nutzer bei geschlossener App erreichen müssen.
Welche QoS sollte eine Flutter-App verwenden?
QoS 1 ist ein guter Standard für Befehle und Messwerte, die Sie nicht verlieren wollen. Der kostenlose ConnectMQ-Serverless-Plan unterstützt QoS bis 1, kostenpflichtige Pläne ergänzen QoS 2.



