PhilipsHue / flutter_reactive_ble

Flutter library that handles BLE operations for multiple devices.
https://developers.meethue.com/
Other
667 stars 335 forks source link

Cannot connect to BLE device when using Firebase Messaging on Android and setting a background message handler #277

Closed soyangel closed 3 years ago

soyangel commented 3 years ago

Describe the bug Cannot connect to BLE device when using Firebase Messaging on Android and setting a background message handler.

To Reproduce Steps to reproduce the behavior:

  1. Configure Firebase Messaging
  2. Use the code pasted below (with it cannot connect to device)
  3. When commenting the line with FirebaseMessaging.onBackgroundMessage it connects successfully

Expected behavior Should connect successfully to BLE device

Smartphone / tablet

Peripheral device

Additional context pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  flutter_reactive_ble: ^3.1.0
  permission_handler: ^7.1.0
  firebase_core: "^1.2.0"
  firebase_messaging: "^10.0.0"

main.dart

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'package:permission_handler/permission_handler.dart';

final flutterReactiveBle = FlutterReactiveBle();
FirebaseMessaging messaging = FirebaseMessaging.instance;

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(MyApp());

  Permission.location.request();
  _initFirebase();
}

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  print("Handling a background message: ${message.messageId}");
}

void _initFirebase() async {
  await Firebase.initializeApp();
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Map<String, DiscoveredDevice> _devicesNear = {};

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

  void _startBleScan() {
    flutterReactiveBle.scanForDevices(
      withServices: [],
      requireLocationServicesEnabled: true,
      scanMode: ScanMode.lowLatency,
    ).listen((device) {
      if (!_devicesNear.containsKey(device.id)) {
        setState(() {
          _devicesNear[device.id] = device;
        });
      }
    }, onError: (err) {
      print('error scanning: $err');
    });
  }

  void _connectToDevice(String deviceId) async {
    flutterReactiveBle
        .connectToDevice(
      id: deviceId,
      connectionTimeout: Duration(seconds: 5),
    )
        .listen((ConnectionStateUpdate st) async {
      print('connection state update: $st');
    });
  }

  Widget _buildItem(BuildContext context, int index) {
    var device = _devicesNear.values.elementAt(index);

    return InkWell(
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Container(
          child: Text(
            '${device.id}: ${device.name}',
            style: TextStyle(fontSize: 18),
          ),
        ),
      ),
      onTap: () {
        _connectToDevice(device.id);
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BLE example'),
      ),
      body: Center(
        child: ListView.builder(
          itemBuilder: (context, index) => _buildItem(context, index),
          itemCount: _devicesNear.length,
        ),
      ),
    );
  }
}
remonh87 commented 3 years ago

Thanks for reporting I can reproduce this behavior on the example app. Will investigate the issue today

remonh87 commented 3 years ago

Fixed in version 3.1.1. which is now available on pub.dev

soyangel commented 3 years ago

I have checked and it connects right but now I'm having a problem with subscriptions to characteristics not being called.

As before if I comment the FirebaseMessaging.onBackgroundMessage I can receive data from a characteristic correctly.

I have a small example for our custom BLE hardware, I guess you should be able to reproduce with something similar.

(Let me know If you prefer this as a separate issue)

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  flutter_reactive_ble: ^3.1.1
  permission_handler: ^7.1.0
  firebase_core: "^1.2.0"
  firebase_messaging: "^10.0.0"
  pinenacl: ^0.2.0
  msgpack_dart: ^1.0.0

main.dart

import 'dart:typed_data';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'package:msgpack_dart/msgpack_dart.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:pinenacl/tweetnacl.dart';

var myServiceUUID = Uuid.parse('00dac273-80b3-475c-83fc-56540d498c68');
var myCharUUID = Uuid.parse('01dac273-80b3-475c-83fc-56540d498c68');

final flutterReactiveBle = FlutterReactiveBle();
FirebaseMessaging messaging = FirebaseMessaging.instance;

QualifiedCharacteristic? characteristic;

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(MyApp());

  Permission.location.request();
  _initFirebase();
}

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  print("Handling a background message: ${message.messageId}");
}

void _initFirebase() async {
  await Firebase.initializeApp();

  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Map<String, DiscoveredDevice> _devicesNear = {};

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

  void _startBleScan() {
    flutterReactiveBle.scanForDevices(
      withServices: [],
      requireLocationServicesEnabled: true,
      scanMode: ScanMode.lowLatency,
    ).listen((device) {
      if (!_devicesNear.containsKey(device.id)) {
        setState(() {
          _devicesNear[device.id] = device;
        });
      }
    }, onError: (err) {
      print('error scanning: $err');
    });
  }

  void _connectToDevice(String deviceId) async {
    flutterReactiveBle
        .connectToDevice(
      id: deviceId,
      connectionTimeout: Duration(seconds: 5),
    )
        .listen((ConnectionStateUpdate st) async {
      print('connection state update: $st');

      if (st.connectionState == DeviceConnectionState.connected) {
        print('writing data to device');

        if (characteristic == null) {
          var services = await flutterReactiveBle.discoverServices(deviceId);
          print('services: $services');

          for (var service in services) {
            print('service: $service');
            if (service.serviceId == myServiceUUID) {
              for (var char in service.characteristicIds) {
                if (char == myCharUUID) {
                  characteristic = QualifiedCharacteristic(
                    serviceId: myServiceUUID,
                    characteristicId: myCharUUID,
                    deviceId: deviceId,
                  );

                  flutterReactiveBle
                      .subscribeToCharacteristic(characteristic!)
                      .listen((data) {
                    print('received data=$data');
                  });
                  break;
                }
              }
            }
          }
        }

        print('before writing data');
        await flutterReactiveBle
            .writeCharacteristicWithoutResponse(characteristic!, value: [3]);
        print('after writing data');

        var sk = Uint8List(TweetNaCl.secretKeyLength);
        var pk = Uint8List(TweetNaCl.publicKeyLength);
        TweetNaCl.crypto_box_keypair(pk, sk);

        var msg = serialize({'t': 'l2', 'pk': pk});

        var txBuf = List<int>.from(msg);

        while (txBuf.isNotEmpty) {
          var chunkLength = txBuf.length;
          if (chunkLength > 20) {
            chunkLength = 20;
          }
          var chunk = txBuf.sublist(0, chunkLength);

          print('before write chunk');

          await flutterReactiveBle.writeCharacteristicWithoutResponse(
              characteristic!,
              value: chunk);

          print('chunk written');

          txBuf.removeRange(0, chunkLength);
        }
      }
    });
  }

  Widget _buildItem(BuildContext context, int index) {
    var device = _devicesNear.values.elementAt(index);

    return InkWell(
      child: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Container(
          child: Text(
            '${device.id}: ${device.name}',
            style: TextStyle(fontSize: 18),
          ),
        ),
      ),
      onTap: () {
        _connectToDevice(device.id);
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BLE example'),
      ),
      body: Center(
        child: ListView.builder(
          itemBuilder: (context, index) => _buildItem(context, index),
          itemCount: _devicesNear.length,
        ),
      ),
    );
  }
}
remonh87 commented 3 years ago

hmm thought I fixed everything hopefully I have some time tomorrow

remonh87 commented 3 years ago

before I publish a new version can you test on branch fix-read-subscribe-background-issues ? I did test it myself and it should work now

soyangel commented 3 years ago

Of course, tomorrow will try it on our app.

Thanks!

soyangel commented 3 years ago

Seems ok on our app.

Thanks again guys! 👏👏👏

remonh87 commented 3 years ago

fix is available in version 3.1.1+1