지난번까지는 ESP32 디바이스의 Advertising, 플러터 앱에서 BLE 디바이스를 검색하는 것까지 했습니다. 이번에는 검색을 했으니, 연결을 해보도록 하겠습니다.
디바이스의 목록이 화면에 뜨죠? 각 목록을 옆으로 스와이프해서 연결할 수 있도록 할겁니다. 제가 제작한 화면은 아래와 같아요.
로직상의 에러때문에 연결 후 바로 연결할 수 있는 장비가 없다고 표시되고 있지만, 조만간 수정하겠습니다.
flutter_slidable 패키지는 아이폰처럼 스와이프되는 ListTile 위젯을 제공해요. 이를 이용할겁니다. 아래의 명령어를 프로젝트 루트에서 입력하면 다운받을 수 있습니다.
$ flutter pub add flutter_slidable
이제 이 패키지를 이용해서 화면에 구성되는 ListTile위젯을 제작할겁니다. connect_item.dart파일을 생성해서 새로운 컴포넌트를 구성했습니다.
import 'package:flutter/material.dart';
import 'package:flutter_bluetooth_app/src/data/model/bluetooth_device_model.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class ConnectItem extends StatelessWidget {
final DeviceModel data;
final void Function(BuildContext)? disconnect;
final void Function()? move;
const ConnectItem(
{super.key, required this.data, this.disconnect, this.move});
Widget build(BuildContext context) {
return Slidable(
endActionPane: ActionPane(
extentRatio: 0.3,
motion: const DrawerMotion(),
children: [
SlidableAction(
spacing: 2,
padding: const EdgeInsets.all(8.0),
onPressed: disconnect,
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: Icons.close,
label: 'disconnect',
borderRadius: BorderRadius.circular(12.0),
),
],
),
child: Card(
color: (data.isConnected) ? Colors.lightGreen : Colors.white,
elevation: 5.0,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0)),
child: ListTile(
onTap: move,
leading: const Icon(
Icons.bluetooth,
color: Color(0xff03b6dc),
size: 40,
),
title: Text(
(data.name.isEmpty) ? 'Unknown' : data.name,
style: const TextStyle(
fontFamily: 'Roboto',
fontSize: 20,
fontWeight: FontWeight.w400),
),
subtitle: Text(data.id.toString()),
),
),
);
}
}
이제 이를 이용해서 랜더링되는 아이템들은 옆으로 스와이프해서 원하는 작업을 할 수 있습니다. 스와이프해서 나타나는 옵션은 연결과 해제입니다.
BLE연결과 해제는 굉장히 간단합니다. 이미 Advertising하는 BLE 디바이스를 앱에서 발견했다면, 해당 디바이스의 device 정보만 알면 연결할 수 있습니다. 처음에 검색해서 발견되는 BLE 디바이스들은 모델을 이용해서 Factory method 패턴으로 구성하였습니다. 다시 한번 이해를 위해서 모델을 보겠습니다.
class DeviceModel {
BluetoothDevice? device;
late String name;
late DeviceIdentifier id;
late bool isConnected;
DeviceModel(
{required this.device,
required this.name,
required this.id,
required this.isConnected});
// 모델의 Named Constructor를 Factory 키워드로 구성
factory DeviceModel.fromScan(ScanResult result) {
return DeviceModel(
device: result.device,
name: result.device.name,
id: result.device.id,
isConnected: false,
);
}
}
이처럼 모델에는 ScanResult 클래스에는 BluetoothDevice클래스인 device 정보를 같이 담습니다. 그리고 이 device에는 connect메소드가 내장되어 있습니다. 해당 메소드를 이용해서 연결할 수 있어요. 해제도 마찬가지입니다. disconnect메소드가 내장되어 있습니다. 해당 기능을 이용해서 BLE와 연결 및 해제를 하는 모듈을 만들겠습니다.
class BluetoothApi {
static Future<void> connectDevice(DeviceModel deviceModel) async {
deviceModel.device!.connect();
}
static Future<void> disconnect(DeviceModel deviceModel) async {
await deviceModel.device!.disconnect();
}
}
이 모듈은 디바이스 모델을 받아서 연결을 수행합니다. 이 모듈 클래스는 컨트롤러에서 직접 호출해서 연결 및 해제를 할겁니다. 그리고 당연히 연결 및 해제를 진행하면서 디바이스 모델의 isConnected를 갱신하면서 사용자가 연결된 것을 인지할 수 있도록 색상을 바꾸는 작업을 추가했습니다. 아래는 연결함수를 호출하는 코드의 일부분입니다.
//컨트롤러에서 호출하는 연결함수
Future<void> connectDevice(DeviceModel deviceModel) async {
try {
BluetoothApi.connectDevice(deviceModel).then((value) {
...
deviceModel.isConnected = true;
...
});
} catch (e) {
//예외 처리
_showErrorToast();
}
}
원래는 가공 로직도 있지만, 편의상 생략하였습니다. 연결 해제도 비슷하게 구성할 수 있어요. 이제 완성입니다 !