Skip to content

Flutter Integration Guide for ThingLarp IoT Platform

This guide demonstrates how to integrate your Flutter application with the backend: 1. Authenticate the User via REST API (JWT). 2. Fetch Historical Data to initialize dashboard charts. 3. Obtain MQTT Session Credentials (POST /mqtt/session). 4. Subscribe to Live Data over MQTT (TLS on mobile / WebSockets on Web).


1. Dependencies (pubspec.yaml)

dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.0
  mqtt_client: ^10.4.0
  fl_chart: ^0.68.0 # High-performance charting

2. Authentication & Data Service (iot_api_service.dart)

import 'dart:convert';
import 'package:http/http.dart' as http;

class IotApiService {
  final String baseUrl;
  String? _jwtToken;

  IotApiService({required this.baseUrl});

  // 1. User Login
  Future<bool> login(String username, String password) async {
    final response = await http.post(
      Uri.parse('$baseUrl/auth/login'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'username': username, 'password': password}),
    );

    if (response.statusCode == 200) {
      final data = jsonDecode(response.body);
      _jwtToken = data['access_token'];
      return true;
    }
    return false;
  }

  // 2. Request Short-Lived MQTT Session Token
  Future<Map<String, dynamic>> requestMqttSession() async {
    final response = await http.post(
      Uri.parse('$baseUrl/mqtt/session'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $_jwtToken',
      },
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    }
    throw Exception('Failed to acquire MQTT session: ${response.body}');
  }

  // 3. Query Historical Points for Charts
  Future<Map<String, dynamic>> getHistoricalData(int channelId, {String resolution = 'auto'}) async {
    final response = await http.get(
      Uri.parse('$baseUrl/channels/$channelId/data?resolution=$resolution'),
      headers: {'Authorization': 'Bearer $_jwtToken'},
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body);
    }
    throw Exception('Failed to load history');
  }
}

3. Real-Time MQTT Client (iot_mqtt_service.dart)

import 'dart:convert';
import 'package:mqtt_client/mqtt_client.dart';
import 'package:mqtt_client/mqtt_server_client.dart'; // Mobile (TCP TLS)
// For Flutter Web, use: import 'package:mqtt_client/mqtt_browser_client.dart';

class IotMqttService {
  final String brokerHost;
  final int port;
  final String username;
  final String password;
  MqttServerClient? _client;

  IotMqttService({
    required this.brokerHost,
    this.port = 8883,
    required this.username,
    required this.password,
  });

  Future<void> connect(int channelId, Function(Map<String, dynamic>) onDataReceived) async {
    _client = MqttServerClient.withPort(brokerHost, 'flutter_${username}', port);
    _client!.secure = true;
    _client!.logging(on: false);
    _client!.keepAlivePeriod = 30;

    final connMessage = MqttConnectMessage()
        .authenticateAs(username, password)
        .withClientIdentifier('flutter_${username}')
        .startClean()
        .withWillQos(MqttQos.atLeastOnce);
    _client!.connectionMessage = connMessage;

    try {
      print('Connecting to MQTT broker at $brokerHost:$port...');
      await _client!.connect();
      print('Connected to MQTT!');

      final topic = 'channels/$channelId/data';
      _client!.subscribe(topic, MqttQos.atLeastOnce);

      _client!.updates!.listen((List<MqttReceivedMessage<MqttMessage>> messages) {
        final recMess = messages[0].payload as MqttPublishMessage;
        final payload = MqttPublishPayload.bytesToStringAsString(recMess.payload.message);

        try {
          final decoded = jsonDecode(payload);
          if (decoded is Map<String, dynamic>) {
            onDataReceived(decoded);
          } else if (decoded is List) {
            for (var item in decoded) {
              if (item is Map<String, dynamic>) {
                onDataReceived(item);
              }
            }
          }
        } catch (e) {
          print('Error decoding MQTT packet: $e');
        }
      });
    } catch (e) {
      print('MQTT connection failed: $e');
      _client?.disconnect();
    }
  }

  void disconnect() {
    _client?.disconnect();
  }
}

4. Dashboard Implementation Pattern

// 1. In initState():
final api = IotApiService(baseUrl: 'https://api.example.com');
await api.login('alex', 'password123');

// 2. Fetch history:
final history = await api.getHistoricalData(channelId);
// Populate initial line chart points...

// 3. Connect live stream:
final session = await api.requestMqttSession();
final mqtt = IotMqttService(
  brokerHost: 'mqtt.example.com',
  port: 8883, // 443 with MqttBrowserClient for Web
  username: session['username'],
  password: session['password'],
);

await mqtt.connect(channelId, (newData) {
  setState(() {
    // Dynamically append new readings to your charts in real-time!
    // Supports any arbitrary field sent by the device (temperature, battery_v, etc.)
  });
});