Provider를 이용해서 앱의 상태관리를 하시기로 결정하셨다면 Provider의 고급 사용을 위한 ChageNotiferProvide의 사용방법을 알아야 합니다. 이 글에서는 ChangeNotifierProvider의 기본 사용법에 대해서 Counter 예제와 연계하여 알아보고, 이에 고급 사용을 위해서 Bluetooth 모듈의 활용을 위한 WinBle 패키지와 ChangeNotifierProvider의 연계 사용방법에 대해서 함께 알아보도록 하겠습니다.
연관된 글
다음의 글을 같이 읽어보시면 해당 글을 이해하시는데 더욱 도움이 됩니다.
Flutter Provider 패키지의 가장 기본 사용방법
ChangeNotifier, addListener를 이용해 앱 상태관리하기
ChangeNotifierProvider 기본 사용
ChangeNotifier를 계속 바라보고 있으면서 ChangeNotifier.notifyListeners가 호출되면 자신의 자식 위젯들을 재생성하는 Provider를 ChangeNotifierProvider라고 합니다. ChangeNotifierProvider를 이용한 가장 대표적인 예제가 Counter 예제입니다. 너무나도 잘 정리되어 있는 글들이 많이 있고 간단한 기능의 예제이기 때문에 본문에서 직접 설명하지는 않겠습니다.
ChangeNotifier를 상속받도록 정의한 클래스(MyChangeNotifier)가 따로 정의되어 있고, 정의된 ChangeNotifier를 생성하기 위해서 다음과 같이 create 함수를 사용합니다.
ChangeNotifierProvider( create: (_) => new MyChangeNotifier(), child: ... )
만약 ChangeNotifier 인스턴스(variable)를 미리 생성해 놓았고 이를 이용하기 위해서는 ChangeNotifierProvider.value를 이용하며 아래와 같이 사용할 수 있습니다.
MyChangeNotifier variable; ChangeNotifierProvider.value( value: variable, child: ... )
앞선 예제에서와 같이 create 함수에 인스턴스를 바로 전달하지 않습니다. create는 default constructor로써 ChangeNotifier를 새롭게 생성하는 것으로 기존의 인스턴스를 전달하면 에러를 발생시킵니다.
MyChangeNotifier variable; ChangeNotifierProvider( create: (_) => variable, // 에러발생 child: ... )
WinBle는 Windows Desktop 기반의 앱에서 블루투스 모듈을 사용할 수 있도록 해주는 패키지입니다. WinBle와 ChangeNotifierProvider를 이용해서 블루투스 모듈을 연결하고 모듈로부터 데이터를 읽는 방법에 대해서 알아보도록 하겠습니다.
블루투스 모듈에서 ChangeNotifierProvider 사용하기
ChangeNotifier 생성
먼저 코드 구성을 보면 다음과 같습니다.
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:win_ble/win_ble.dart'; class BluetoothNotifier extends ChangeNotifier { StreamSubscription? scanStream; StreamSubscription? connectionStream; StreamSubscription? bleStateStream; List<BleDevice> _devices = <BleDevice>[]; List<BleDevice> get deviceList => _devices; BleState bleState = BleState.Unknown; // 2 initWinBle() { // 1 WinBle.initialize(enableLog: true); connectionStream = WinBle.connectionStream.listen((event) {}); // Listen to Scan Stream , we can cancel in onDispose() // 3 scanStream = WinBle.scanStream.listen((event) { if (!_devices.any((element) => element.address == event.address)) { _devices.add(event); } }); // Listen to Ble State Stream // 4 bleStateStream = WinBle.bleState.listen((BleState state) { bleState = state; }); notifyListeners(); } // 5 startScanning() { WinBle.startScanning(); notifyListeners(); } // 6 stopScanning() { WinBle.stopScanning(); notifyListeners(); } }
- 먼저 WinBle 모듈을 사용하기 위해서는 초기화를 시켜주어야 합니다. 초기화는 WinBle 프로그램을 실행시킨다고 보시면 됩니다. WinBle.initialize 함수를 이용해서 초기화 할 수 있습니다.
- ChangeNotifier에서는 iniWinBle라는 함수를 별도로 생성하여 함수가 호출될 때 WinBle 초기화를 수행하게 됩니다.
- 블루투스 연결가능 장비 목록을 스캔하기 위한 스트림으로 scanStream을 정의합니다. 계속 모니터링을 하는 과정에서 _devices 목록에 없는 장비의 목록이 식별될 경우 이를 추가합니다.
- bleStateStream은 형태 WinBle의 연결상태를 모니터링합니다. 초기값은 BleState.Unknown 상태로 되어 있으며, 정상적으로 초기화가 되어 연결이 되면 상태가 BleState.On으로 변경됩니다.
- 스캔을 시작합니다.
- 스캔을 종료합니다.
ChangeNotifierProvider 생성
앞서 ChangeNotifer를 생성하였으니 이를 이용해서 Provider를 생성해 보도록 하겠습니다. Provider는 하위의 위젯들이 사용할 수 있도록 사용하는 위젯들의 공통의 최상위에 위치해야 합니다. 저는 main.dart에 생성하였습니다.
Provider를 사용하는 입장에서 단일 Provider를 사용하는 경우는 잘 없기 때문에 Multiprovider를 적용하는 방법에 대해서 알아보겠습니다.
main.dart 파일에 다음과 같이 작성합니다.
import 'package:provider/provider.dart'; import 'notifier/bluetooth_notifier.dart'; void main() { runApp( MultiProvider( providers: [ ChangeNotifierProvider<BluetoothNotifier>( create: (context) => BluetoothNotifier(), ), ], child: const MyApp(), ), ); }
ChangeNotifier를 BluetoothNotifier 하나만 이용하고 상태 변화를 지속적으로 추적하기 때문에 ChangeNotifierProvider를 이용합니다.
ChangeNotifierProvider 사용하기
마지막으로 UI에서 ChangeNotitifierProvider를 사용하는 방법에 대해서 알아보겠습니다. BluetoothTest라는 위젯을 만들어 화면의 중간에 BleStatus와 블루투스 연결 가능한 디바이스 검색 결과를 ListView를 이용해서 보여줍니다.
import 'package:flutter/material.dart'; import 'package:bluetooth_notifier.dart'; import 'package:provider/provider.dart'; class BluetoothTest extends StatelessWidget { BluetoothTest({super.key}); @override Widget build(BuildContext context) { // 1 final bluetoothNotifier = context.watch<BluetoothNotifier>(); // 2 final devices = context.watch<BluetoothNotifier>().deviceList; return Scaffold( appBar: AppBar( title: const Text('Bluetooth Test'), ), body: Column( children: [ Expanded( // 3 child: ListView.builder( itemCount: devices.length, itemBuilder: (context, index) { final device = devices[index]; return Text(device.address); }, ), ), Flexible( // 4 child: Text(bluetoothNotifier.bleState.toString()), ) ], ), // 5 floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ IconButton( icon: const Icon(Icons.start), onPressed: () { bluetoothNotifier.initWinBle(); }, ), IconButton( icon: const Icon(Icons.add), onPressed: () { bluetoothNotifier.startScanning(); }, ), IconButton( icon: const Icon(Icons.remove), onPressed: () { bluetoothNotifier.stopScanning(); }, ), ], ), ); } }
- BluetoothNotifier 타입의 bluetoothNotifier 인스턴스를 정의합니다. context.watch()를 이용하였기 때문에 BluetoothNotifier에서 상태변화가 일어나 notifyListeners함수가 호출되면 bluetoothNotifer와 관련된 내용도 함께 업데이트 됩니다.
- 디바이스 목록은 별도록 deviceList라는 getter를 이용해서 devices에 저장합니다.
- 앞서 받은 devices 리스트의 내용을 ListView.builder를 이용해서 화면에 보여줍니다.
- 디바이스 리스트 아래에 bleStatus값을 보여줍니다.
- FAB(Floating Action Button)을 생성하고 3개의 아이콘을 정의합니다. 처음부터 WinBle 초기화, 장치 스캔, 장치 스캔 종료 버튼을 정의합니다.
Reference
https://pub.dev/documentation/provider/latest/provider/ChangeNotifierProvider-class.html