Para usar MQTT no Flutter, adicione o pacote mqtt_client, crie um MqttServerClient para Android, iOS e desktop ou um MqttBrowserClient para Flutter web, conecte, assine e escute o stream updates. O que mais confunde é que os dois clientes usam transportes diferentes: o cliente server fala TCP ou TLS, enquanto o cliente browser só pode usar WebSockets.
Este tutorial monta um pequeno serviço MQTT reutilizável para Flutter, mostra as configurações TCP, TLS e WebSocket seguro contra broker.connectmq.com e termina com uma forma rápida de testar o caminho completo pelo navegador.
Instalar o mqtt_client
O mqtt_client é um pacote Dart puro, então funciona em todas as plataformas Flutter sem plugins nativos:
flutter pub add mqtt_clientO pacote traz duas classes de cliente em duas bibliotecas separadas. A que você importa define o transporte:
| Classe | Import | Plataformas | Transportes |
|---|---|---|---|
MqttServerClient | package:mqtt_client/mqtt_server_client.dart | Android, iOS, macOS, Windows, Linux | TCP, TLS, WebSocket, WebSocket seguro |
MqttBrowserClient | package:mqtt_client/mqtt_browser_client.dart | Flutter web | Apenas WebSocket e WebSocket seguro |
O mqtt_server_client.dart importa dart:io, que não existe no navegador, então importá-lo num build web quebra na compilação. Vamos resolver isso mais adiante com um import condicional. Se você está escrevendo uma ferramenta de linha de comando em Dart puro em vez de um app, a mesma API vale: veja MQTT em Dart.
Primeira conexão via TCP
Comece pela configuração mais simples: TCP sem criptografia na porta 1883. Todo cliente precisa de um client ID único; se dois dispositivos usam o mesmo, o broker derruba a sessão mais antiga sempre que o outro conecta. O gerador de client ID ajuda a escolher um formato.
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; // milissegundos
client.onConnected = () => print('conectado');
client.onDisconnected = () => print('desconectado');
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('falha ao conectar: $e');
client.disconnect();
rethrow;
}
final state = client.connectionStatus?.state;
if (state != MqttConnectionState.connected) {
throw Exception('o broker recusou a conexão: ${client.connectionStatus}');
}
return client;
}O connect() lança NoConnectionException quando o broker rejeita o CONNECT (credenciais erradas, client ID bloqueado) e uma SocketException quando o host ou a porta não podem ser alcançados. Imprimir client.connectionStatus mostra o código de retorno, que você pode consultar na tabela de reason codes MQTT.
Assinar e receber mensagens
As mensagens recebidas chegam em client.updates, um stream Dart. Assine primeiro e depois escute uma única vez, não uma vez por assinatura:
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');
}
});
}O bytesToStringAsString decodifica os bytes como texto. Para JSON, passe o resultado para jsonDecode; para payloads binários (CBOR, Protobuf) use diretamente publish.payload.message, que é um buffer de bytes tipado.
Num widget, guarde a StreamSubscription retornada por listen e cancele-a em dispose(), ou exponha o stream pela sua solução de gerenciamento de estado (Provider, Riverpod, Bloc) para que os widgets sejam reconstruídos a partir de uma única fonte de verdade, em vez de cada um abrir a própria conexão.
Publicar
Os payloads são montados com 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,
);
}Use addUTF8String em vez de addString quando o texto puder ter caracteres não ASCII; o addString grava um byte por unidade de código UTF-16 e pode corromper acentos, cedilhas e emojis.
TLS na porta 8883
Em produção, mude o cliente server para TLS. Brokers com certificado de uma CA pública, como broker.connectmq.com, são verificados pelo repositório de confiança do sistema operacional, então nenhum arquivo extra é necessário:
import 'dart:io';
final client = MqttServerClient.withPort('broker.connectmq.com', clientId, 8883)
..secure = true
..securityContext = SecurityContext.defaultContext;
await client.connect('username', 'password');Para um broker com certificado autoassinado ou CA privada, crie um SecurityContext(withTrustedRoots: false) e chame setTrustedCertificatesBytes() com o certificado da CA carregado dos seus assets. Evite onBadCertificate: (_) => true fora de um experimento local rápido: ele aceita qualquer certificado e anula o TLS sem avisar. Se o handshake falhar, testar a conexão TLS pelo terminal mostra se o problema está no broker ou no app.
Flutter web: MqttBrowserClient via WSS
No navegador, o MQTT roda sobre WebSockets. Páginas servidas por HTTPS só podem abrir conexões wss://, então use a porta WebSocket segura e inclua o path:
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;
}Repare que a porta é definida como propriedade, não dentro da URL. A linha websocketProtocols envia o header de subprotocolo mqtt, que a maioria dos brokers espera. Um path errado costuma aparecer como um fechamento do WebSocket logo após o upgrade; MQTT sobre WebSockets explica o handshake em detalhes.
Um único código para todas as plataformas
Crie um lib/mqtt_server.dart equivalente que retorne um MqttServerClient (por exemplo na 8883 com TLS) e escolha o arquivo certo em tempo de compilação:
export 'mqtt_server.dart'
if (dart.library.js_interop) 'mqtt_browser.dart';O restante do app importa apenas mqtt_factory.dart e package:mqtt_client/mqtt_client.dart, e trabalha com a classe base comum MqttClient. Connect, subscribe, publish e o stream updates se comportam igual nos dois.
Reconexão e ciclo de vida do app
Redes móveis derrubam conexões o tempo todo: troca de Wi-Fi para dados móveis, túneis, o sistema suspendendo seu app. O mqtt_client tem reconexão automática embutida:
client
..autoReconnect = true
..resubscribeOnAutoReconnect = true
..onAutoReconnect = () => print('conexão perdida, reconectando...')
..onAutoReconnected = () => print('reconectado');Com resubscribeOnAutoReconnect (o padrão é true) o cliente refaz suas assinaturas após reconectar, o que importa porque uma clean session as esquece no lado do broker. Numa clean session o broker não guarda mensagens publicadas enquanto você estava desconectado; se o app não pode perder dados, conecte com sessão persistente (omita startClean(), mantenha um client ID estável) e assine com QoS 1. Teste isso na sua própria instância privada de broker: o endpoint compartilhado de testes não guarda sessões offline.
Lembre que iOS e Android suspendem apps em segundo plano, o que fecha o socket independentemente do keep-alive. Observe o ciclo de vida com WidgetsBindingObserver, desconecte de forma limpa em paused e reconecte em resumed. Para entrega em segundo plano, a ferramenta certa são as notificações push; veja desconexões em segundo plano no Android e limites de segundo plano no iOS.
Checklist por plataforma
- Android: adicione
<uses-permission android:name="android.permission.INTERNET"/>emandroid/app/src/main/AndroidManifest.xml. Builds de debug incluem a permissão automaticamente, por isso "funciona em debug, falha em release" é tão comum. - macOS: adicione
com.apple.security.network.clientcom valortrueemDebugProfile.entitlementseRelease.entitlements, ou o sandbox bloqueia sockets de saída. - iOS: nada extra para TLS. O App Transport Security vale para as APIs de carregamento de URL, não para TCP direto nem
ws://para um hostname, mas use TLS em produção mesmo assim. - Web: use
MqttBrowserClient,wss://, a porta e o path corretos. O navegador bloqueia conteúdo misto (um brokerws://a partir de uma páginahttps://).
Testar o caminho completo
Rode o app, abra o cliente MQTT online num navegador, conecte em broker.connectmq.com via WebSocket seguro e assine testmqtt/flutter-demo/#. Você deve ver as leituras que o app publica, e o que você publicar do navegador em testmqtt/flutter-demo/sensors/test deve aparecer no console de debug do Flutter.
Veja no navegador as mensagens do seu app Flutter
Se nada chegar, verifique nesta ordem: o estado da conexão depois do connect(), se o tópico da assinatura casa com o da publicação (o topic matcher ajuda com wildcards) e a saída de debug após client.logging(on: true). As exceções mais frequentes e suas correções estão reunidas em erros MQTT no Flutter.
Com um arquivo factory, um único stream de mensagens e reconexão automática ativada, o mesmo código Flutter conversa com o broker a partir de um celular, um desktop e uma aba do navegador. Comece pelo endpoint compartilhado, passe para TLS e WebSockets seguros com credenciais, e mantenha o cliente no navegador aberto durante o desenvolvimento para ver cada mensagem que o app envia.
Perguntas frequentes
Por que meu app Flutter com MQTT funciona no Android mas não na web?
Navegadores não conseguem abrir sockets TCP diretos, então o MqttServerClient não funciona no Flutter web. Use o MqttBrowserClient com uma URL wss://, a porta WebSocket do broker (por exemplo 8084) e o path correto, como /mqtt.
O mqtt_client suporta MQTT 5?
Não. O mqtt_client implementa MQTT 3.1 e 3.1.1. Se você precisa de recursos do MQTT 5 como reason codes, user properties ou session expiry, use o pacote mqtt5_client do mesmo autor, que tem uma API bem parecida.
Preciso de permissões especiais para usar MQTT no Flutter?
No Android, builds de release precisam da permissão INTERNET no AndroidManifest.xml (builds de debug adicionam sozinhas). No macOS, ative o entitlement de conexões de saída (com.apple.security.network.client). iOS e web não precisam de nada extra.


