IoT-Tutorials

Flutter MQTT Client: Eine plattformübergreifende IoT-App entwickeln

Ein praxisnahes Flutter-MQTT-Tutorial mit dem Paket mqtt_client, das Android-, iOS-, Desktop- und Web-Builds per TLS oder sicherem WebSocket mit einem Cloud-Broker verbindet, Live-Sensordaten liest und ein Gerät steuert.

Flutter-MQTT-App auf einem Smartphone und im Browser mit Live-Sensorwerten und einem Geräteschalter, verbunden mit einem Cloud-Broker
Probieren Sie es auf Ihrem eigenen MQTT-Broker ausHolen Sie sich im Free-Tarif einen privaten, verwalteten MQTT-Broker und starten Sie mit fertigen Codebeispielen.
Kostenlos testen Codebeispiele Keine Kreditkarte erforderlich

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:

  • MqttServerClient für Android, iOS, macOS, Windows und Linux. Er spricht unverschlüsseltes TCP, TLS oder WebSocket.
  • MqttBrowserClient fü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:

  1. Registrieren Sie sich im ConnectMQ-Portal, wählen Sie einen Plan und erstellen Sie einen Broker.
  2. Kopieren Sie den generierten Benutzernamen, das Passwort und die Client ID.
  3. Erzeugen Sie den Service damit: MqttService(username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD', clientId: 'YOUR_CLIENT_ID', topicPrefix: 'acme/site1').
  4. 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.

Verwandte Artikel

Die besten MQTT-CLI-Tools zum Testen, Skripten und Debuggen

Ein praxisnaher Vergleich von mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, der MQTT.js-CLI, curl und mqttui – mit demselben Test gegen broker.connectmq.com, damit Sie das richtige Tool zum Skripten und Debuggen wählen.

8 Min. Lesezeit

Alle Artikel →

Ihr privater MQTT-Broker ist nur wenige Klicks entfernt

Konto anlegen, Broker-Zugangsdaten erhalten und das erste Gerät verbinden — ohne Server, die installiert, gepatcht oder skaliert werden müssen.