Pour utiliser MQTT avec Flutter, ajoutez le package mqtt_client, créez un MqttServerClient pour Android, iOS et desktop ou un MqttBrowserClient pour Flutter web, connectez-vous, abonnez-vous et écoutez le stream updates. Le piège le plus courant : les deux clients n'utilisent pas le même transport. Le client server parle TCP ou TLS, tandis que le client browser ne peut utiliser que les WebSockets.
Ce tutoriel construit un petit service MQTT réutilisable pour Flutter, montre les configurations TCP, TLS et WebSocket sécurisé vers broker.connectmq.com, et se termine par un moyen rapide de tester l'aller-retour complet depuis votre navigateur.
Installer mqtt_client
mqtt_client est un package Dart pur : il fonctionne donc sur toutes les plateformes Flutter sans plugin natif.
flutter pub add mqtt_clientLe package contient deux classes de client dans deux bibliothèques distinctes. Celle que vous importez détermine le transport :
| Classe | Import | Plateformes | Transports |
|---|---|---|---|
MqttServerClient | package:mqtt_client/mqtt_server_client.dart | Android, iOS, macOS, Windows, Linux | TCP, TLS, WebSocket, WebSocket sécurisé |
MqttBrowserClient | package:mqtt_client/mqtt_browser_client.dart | Flutter web | WebSocket et WebSocket sécurisé uniquement |
mqtt_server_client.dart importe dart:io, qui n'existe pas dans le navigateur ; l'importer dans un build web échoue donc à la compilation. Nous réglerons cela plus bas avec un import conditionnel. Si vous écrivez un outil en ligne de commande en Dart pur plutôt qu'une app, la même API s'applique : voir MQTT en Dart.
Première connexion en TCP
Commencez par la configuration la plus simple : TCP non chiffré sur le port 1883. Chaque client a besoin d'un client ID unique ; si deux appareils partagent le même, le broker déconnecte la session la plus ancienne chaque fois que l'autre se connecte. Le générateur de client ID aide à choisir un format.
import 'dart:math';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart';
Future<MqttServerClient> connectMqtt() async {
final clientId = 'flutter-${Random().nextInt(0xFFFFFF).toRadixString(16)}';
final client = MqttServerClient.withPort('broker.connectmq.com', clientId, 1883);
client.logging(on: false);
client.setProtocolV311();
client.keepAlivePeriod = 30;
client.connectTimeoutPeriod = 5000; // millisecondes
client.onConnected = () => print('connecté');
client.onDisconnected = () => print('déconnecté');
client.connectionMessage = MqttConnectMessage()
.withClientIdentifier(clientId)
.startClean()
.withWillTopic('testmqtt/flutter-demo/$clientId/status')
.withWillMessage('offline')
.withWillQos(MqttQos.atLeastOnce);
try {
await client.connect(); // ou client.connect('username', 'password')
} on Exception catch (e) {
print('échec de connexion : $e');
client.disconnect();
rethrow;
}
final state = client.connectionStatus?.state;
if (state != MqttConnectionState.connected) {
throw Exception('le broker a refusé la connexion : ${client.connectionStatus}');
}
return client;
}connect() lève une NoConnectionException quand le broker rejette le CONNECT (identifiants incorrects, client ID refusé) et une SocketException quand l'hôte ou le port est injoignable. Afficher client.connectionStatus donne le code de retour, à rechercher dans le tableau des reason codes MQTT.
S'abonner et recevoir des messages
Les messages entrants arrivent sur client.updates, un stream Dart. Abonnez-vous d'abord, puis écoutez une seule fois, et non une fois par abonnement :
const topic = 'testmqtt/flutter-demo/sensors/#';
void listen(MqttServerClient client) {
client.subscribe(topic, MqttQos.atLeastOnce);
client.updates!.listen((messages) {
for (final m in messages) {
final publish = m.payload as MqttPublishMessage;
final text = MqttPublishPayload.bytesToStringAsString(publish.payload.message);
print('${m.topic}: $text');
}
});
}bytesToStringAsString décode les octets en texte. Pour du JSON, passez le résultat à jsonDecode ; pour des payloads binaires (CBOR, Protobuf), utilisez directement publish.payload.message, un buffer d'octets typé.
Dans un widget, conservez la StreamSubscription renvoyée par listen et annulez-la dans dispose(), ou exposez le stream via votre solution de gestion d'état (Provider, Riverpod, Bloc) : les widgets se reconstruisent ainsi à partir d'une source unique au lieu d'ouvrir chacun leur propre connexion.
Publier
Les payloads se construisent avec MqttClientPayloadBuilder :
import 'dart:convert';
void publishReading(MqttServerClient client, double celsius) {
final builder = MqttClientPayloadBuilder()
..addUTF8String(jsonEncode({'celsius': celsius, 'ts': DateTime.now().toIso8601String()}));
client.publishMessage(
'testmqtt/flutter-demo/sensors/kitchen',
MqttQos.atLeastOnce,
builder.payload!,
retain: false,
);
}Préférez addUTF8String à addString dès que le texte peut contenir des caractères non ASCII : addString écrit un octet par unité de code UTF-16 et peut abîmer les accents et les emojis.
TLS sur le port 8883
En production, passez le client server en TLS. Les brokers dotés d'un certificat d'une CA publique, comme broker.connectmq.com, sont vérifiés via le magasin de confiance du système, sans fichier supplémentaire :
import 'dart:io';
final client = MqttServerClient.withPort('broker.connectmq.com', clientId, 8883)
..secure = true
..securityContext = SecurityContext.defaultContext;
await client.connect('username', 'password');Pour un broker avec un certificat auto-signé ou une CA privée, créez un SecurityContext(withTrustedRoots: false) et appelez setTrustedCertificatesBytes() avec le certificat de la CA chargé depuis vos assets. Évitez onBadCertificate: (_) => true en dehors d'une expérience locale rapide : il accepte tous les certificats et annule TLS sans prévenir. Si le handshake échoue, tester la connexion TLS depuis un terminal indique si le problème vient du broker ou de l'app.
Flutter web : MqttBrowserClient en WSS
Dans le navigateur, MQTT passe par les WebSockets. Les pages servies en HTTPS ne peuvent ouvrir que des connexions wss:// : utilisez donc le port WebSocket sécurisé et indiquez le chemin.
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_browser_client.dart';
MqttClient createClient(String clientId) {
final client = MqttBrowserClient('wss://broker.connectmq.com/mqtt', clientId)
..port = 8084
..keepAlivePeriod = 30
..websocketProtocols = MqttClientConstants.protocolsSingleDefault;
return client;
}Notez que le port est défini comme propriété, pas dans l'URL. La ligne websocketProtocols envoie l'en-tête de sous-protocole mqtt, attendu par la plupart des brokers. Un mauvais chemin se manifeste généralement par une fermeture du WebSocket juste après l'upgrade ; MQTT sur WebSockets détaille le handshake.
Un seul code pour toutes les plateformes
Créez un lib/mqtt_server.dart équivalent qui renvoie un MqttServerClient (par exemple sur 8883 avec TLS), puis choisissez le bon fichier à la compilation :
export 'mqtt_server.dart'
if (dart.library.js_interop) 'mqtt_browser.dart';Le reste de l'app n'importe que mqtt_factory.dart et package:mqtt_client/mqtt_client.dart, et travaille avec la classe de base commune MqttClient. Connect, subscribe, publish et le stream updates se comportent de la même façon des deux côtés.
Reconnexion et cycle de vie de l'app
Les réseaux mobiles coupent sans cesse les connexions : passage du Wi-Fi au réseau mobile, tunnels, système qui suspend votre app. mqtt_client intègre une reconnexion automatique :
client
..autoReconnect = true
..resubscribeOnAutoReconnect = true
..onAutoReconnect = () => print('connexion perdue, reconnexion...')
..onAutoReconnected = () => print('reconnecté');Avec resubscribeOnAutoReconnect (true par défaut), le client rejoue vos abonnements après la reconnexion, ce qui compte car une clean session les oublie côté broker. Avec une clean session, le broker ne conserve pas les messages publiés pendant la coupure ; si l'app ne doit rien manquer, connectez-vous avec une session persistante (sans startClean(), avec un client ID stable) et abonnez-vous en QoS 1. Testez cela sur votre propre instance privée de broker : l'endpoint de test partagé ne conserve pas les sessions hors ligne.
N'oubliez pas qu'iOS et Android suspendent les apps en arrière-plan, ce qui ferme le socket quel que soit le keep-alive. Observez le cycle de vie avec WidgetsBindingObserver, déconnectez proprement dans paused et reconnectez dans resumed. Pour la livraison en arrière-plan, les notifications push sont le bon outil ; voir déconnexions en arrière-plan sur Android et limites d'arrière-plan sur iOS.
Checklist par plateforme
- Android : ajoutez
<uses-permission android:name="android.permission.INTERNET"/>dansandroid/app/src/main/AndroidManifest.xml. Les builds debug l'incluent automatiquement, d'où le fameux « ça marche en debug, pas en release ». - macOS : ajoutez
com.apple.security.network.clientàtruedansDebugProfile.entitlementsetRelease.entitlements, sinon le sandbox bloque les sockets sortants. - iOS : rien de plus pour TLS. App Transport Security concerne les API de chargement d'URL, pas le TCP brut ni
ws://vers un nom d'hôte, mais utilisez quand même TLS en production. - Web :
MqttBrowserClient,wss://, le bon port et le bon chemin. Le navigateur bloque le contenu mixte (un brokerws://depuis une pagehttps://).
Tester l'aller-retour
Lancez l'app, ouvrez le client MQTT en ligne dans un navigateur, connectez-vous à broker.connectmq.com en WebSocket sécurisé et abonnez-vous à testmqtt/flutter-demo/#. Vous devez voir les mesures publiées par votre app, et tout ce que vous publiez depuis le navigateur sur testmqtt/flutter-demo/sensors/test doit apparaître dans la console de debug Flutter.
Voir les messages de votre app Flutter dans le navigateur
Si rien n'arrive, vérifiez dans cet ordre : l'état de connexion après connect(), la correspondance entre le topic d'abonnement et celui de publication (le topic matcher aide avec les wildcards) et la sortie de debug après client.logging(on: true). Les exceptions les plus fréquentes et leurs solutions sont réunies dans erreurs MQTT Flutter.
Avec un fichier factory, un seul stream de messages et la reconnexion automatique activée, le même code Flutter dialogue avec le broker depuis un téléphone, un ordinateur et un onglet de navigateur. Commencez avec l'endpoint partagé, passez à TLS et aux WebSockets sécurisés avec identifiants, et gardez le client navigateur ouvert pendant le développement pour voir chaque message envoyé par votre app.
Questions fréquentes
Pourquoi mon app Flutter MQTT fonctionne sur Android mais pas sur le web ?
Les navigateurs ne peuvent pas ouvrir de sockets TCP bruts, donc MqttServerClient ne fonctionne pas dans Flutter web. Utilisez MqttBrowserClient avec une URL wss://, le port WebSocket du broker (par exemple 8084) et le bon chemin, comme /mqtt.
mqtt_client prend-il en charge MQTT 5 ?
Non. mqtt_client implémente MQTT 3.1 et 3.1.1. Si vous avez besoin de fonctionnalités MQTT 5 comme les reason codes, les user properties ou le session expiry, utilisez le package mqtt5_client du même auteur, dont l'API est très proche.
Faut-il des permissions particulières pour utiliser MQTT dans Flutter ?
Sur Android, les builds release ont besoin de la permission INTERNET dans AndroidManifest.xml (les builds debug l'ajoutent automatiquement). Sur macOS, activez l'entitlement des connexions sortantes (com.apple.security.network.client). iOS et le web n'ont besoin de rien.


