Fase 3 · Minggu 8

Flutter Bluetooth Classic

Scan, pair, connect, dan kirim/terima data via Bluetooth Classic di Flutter.

Setup Flutter Project

Terminalflutter create bt_led_control
cd bt_led_control
YAML — pubspec.yamldependencies:
  flutter:
    sdk: flutter
  flutter_bluetooth_serial: ^0.4.0

Permissions di AndroidManifest.xml:

XML<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
⚠️ Android 12+: Butuh permission BLUETOOTH_CONNECT dan BLUETOOTH_SCAN. Untuk Android < 12, butuh ACCESS_FINE_LOCATION.

Flutter: Scan & Connect

Dart — lib/main.dartimport 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: const Color(0xFF0F172A)),
      home: const BluetoothPage(),
    );
  }
}

class BluetoothPage extends StatefulWidget {
  const BluetoothPage({super.key});
  @override
  State<BluetoothPage> createState() => _BluetoothPageState();
}

class _BluetoothPageState extends State<BluetoothPage> {
  BluetoothConnection? _connection;
  List<BluetoothDevice> _devices = [];
  bool _isConnected = false;
  bool _isLedOn = false;
  String _status = 'Disconnected';
  String _response = '';

  @override
  void initState() {
    super.initState();
    _getBondedDevices();
  }

  // Ambil daftar device yang sudah di-pair
  Future<void> _getBondedDevices() async {
    List<BluetoothDevice> devices =
        await FlutterBluetoothSerial.instance.getBondedDevices();
    setState(() => _devices = devices);
  }

  // Connect ke device
  Future<void> _connect(BluetoothDevice device) async {
    setState(() => _status = 'Connecting...');

    try {
      BluetoothConnection connection =
          await BluetoothConnection.toAddress(device.address);

      setState(() {
        _connection = connection;
        _isConnected = true;
        _status = 'Connected to ${device.name}';
      });

      // Listen for incoming data
      connection.input?.listen((Uint8List data) {
        String incoming = String.fromCharCodes(data).trim();
        if (incoming.isNotEmpty) {
          setState(() => _response = incoming);
          _parseResponse(incoming);
        }
      }).onDone(() {
        setState(() {
          _isConnected = false;
          _status = 'Disconnected';
        });
      });

    } catch (e) {
      setState(() => _status = 'Error: $e');
    }
  }

  void _parseResponse(String data) {
    try {
      var json = jsonDecode(data);
      if (json['led'] != null) {
        setState(() => _isLedOn = json['led'] == 'ON');
      }
    } catch (_) {}
  }

  void _sendCommand(String command) {
    if (_connection == null) return;
    String json = '{"command":"$command"}\n';
    _connection!.output.add(Uint8List.fromList(json.codeUnits));
    _connection!.output.allSent;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('BT LED Controller'),
        backgroundColor: const Color(0xFF1E293B),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _getBondedDevices,
          ),
        ],
      ),
      body: _isConnected ? _buildControlView() : _buildDeviceList(),
    );
  }

  // Daftar device bluetooth
  Widget _buildDeviceList() {
    return Column(children: [
      Padding(
        padding: const EdgeInsets.all(16),
        child: Text(_status,
          style: const TextStyle(color: Colors.grey)),
      ),
      Expanded(
        child: ListView.builder(
          itemCount: _devices.length,
          itemBuilder: (context, index) {
            var device = _devices[index];
            return ListTile(
              leading: const Icon(Icons.bluetooth,
                color: Colors.blueAccent),
              title: Text(device.name ?? 'Unknown'),
              subtitle: Text(device.address),
              trailing: ElevatedButton(
                onPressed: () => _connect(device),
                child: const Text('Connect'),
              ),
            );
          },
        ),
      ),
    ]);
  }

  // Kontrol LED setelah connected
  Widget _buildControlView() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(_status,
            style: const TextStyle(color: Colors.greenAccent)),
          const SizedBox(height: 30),
          Icon(Icons.lightbulb, size: 100,
            color: _isLedOn ? Colors.amber : Colors.grey[700]),
          const SizedBox(height: 20),
          Text(_isLedOn ? 'LED: ON' : 'LED: OFF',
            style: TextStyle(fontSize: 24,
              fontWeight: FontWeight.bold,
              color: _isLedOn
                  ? Colors.greenAccent
                  : Colors.redAccent)),
          const SizedBox(height: 30),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: () => _sendCommand('ON'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.greenAccent),
                child: const Text('ON',
                  style: TextStyle(color: Colors.black)),
              ),
              const SizedBox(width: 20),
              ElevatedButton(
                onPressed: () => _sendCommand('OFF'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.redAccent),
                child: const Text('OFF'),
              ),
            ],
          ),
          const SizedBox(height: 20),
          Text('Response: $_response',
            style: const TextStyle(
              color: Colors.grey, fontSize: 11)),
          const SizedBox(height: 20),
          TextButton(
            onPressed: () {
              _connection?.close();
              setState(() {
                _isConnected = false;
                _status = 'Disconnected';
              });
            },
            child: const Text('Disconnect',
              style: TextStyle(color: Colors.redAccent)),
          ),
        ],
      ),
    );
  }
}
Paired Devices ESP32_IoT Connect JBL Speaker 📱 Step 1: Select device Connected ✓ 💡 LED: ON ON OFF 📱 Step 2: Control LED App Flow: 1. Show paired devices 2. Tap "Connect" 3. Establish BT connection 4. Listen for data 5. Send ON/OFF commands 6. Update UI from response

App flow: Device List → Connect → Control LED via Bluetooth

Checklist Minggu 8