andrey-ushakov / esc_pos_bluetooth

ESC/POS (thermal, receipt) printing for Flutter & Dart (Android/iOS)
BSD 3-Clause "New" or "Revised" License
243 stars 314 forks source link

I am getting error Unhandled Exception: MissingPluginException(No implementation found for method startScan on channel flutter_bluetooth_basic/methods) #145

Open rohit0713 opened 3 days ago

rohit0713 commented 3 days ago

I am not able to scan the bluetooth due to error

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: MissingPluginException(No implementation found for method stopScan on channel flutter_bluetooth_basic/methods) E/flutter ( 4507): #0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:332:7) E/flutter ( 4507): E/flutter ( 4507): #1 BluetoothManager.stopScan (package:flutter_bluetooth_basic/src/bluetooth_manager.dart:122:5) E/flutter ( 4507): E/flutter ( 4507): #2 PrinterBluetoothManager.stopScan (package:esc_pos_bluetooth/src/printer_bluetooth_manager.dart:67:5) E/flutter ( 4507): E/flutter ( 4507): I/flutter ( 4507): Error starting scan. E/flutter ( 4507): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: MissingPluginException(No implementation found for method startScan on channel flutter_bluetooth_basic/methods) E/flutter ( 4507): #0 BluetoothManager.scan (package:flutter_bluetooth_basic/src/bluetooth_manager.dart:85:7) E/flutter ( 4507): E/flutter ( 4507): #1 _nullDataHandler (dart:async/stream_impl.dart:517:1) E/flutter ( 4507): E/flutter ( 4507):

rohit0713 commented 3 days ago

@andrey-ushakov Please help to fix the issue.

vishnudasRapidor commented 3 days ago

@andrey-ushakov facing same issue. please look into it.

singgihmardianto commented 3 days ago

Hi there, can you give more context on this one? Maybe some code snippets?

Basically before you scan a bluetooth, please make sure:

  1. You have list the permission in AndroidManifest.xml
  2. Request the user permission

Remember that you will need to run on a real device instead of an emulator.

rohit0713 commented 3 days ago

@singgihmardianto Manifest Permissions: android:name="android.permission.ACCESS_FINE_LOCATION" android:name="android.permission.ACCESS_COARSE_LOCATION" android:name="android.permission.WAKE_LOCK" android:name="android.permission.BLUETOOTH" android:name="android.permission.BLUETOOTH_ADMIN" android:name="android.permission.BLUETOOTH_SCAN"
android:name="android.permission.BLUETOOTH_CONNECT" android:name="android.permission.FOREGROUND_SERVICE"

Code: Future scanDevices() async { var status = await Permission.bluetoothConnect.status; if (status.isDenied || status.isPermanentlyDenied) { status = await Permission.bluetoothConnect.request(); if (!status.isGranted) { Fluttertoast.showToast(msg: 'Bluetooth connect permission denied'); return false; } } var res = await BluetoothEnable.enableBluetooth; if (res == 'false') { Fluttertoast.showToast(msg: 'Bluetooth permission denied'); return false; }

status = await Permission.bluetoothScan.status;
if (status.isDenied || status.isPermanentlyDenied) {
  status = await Permission.bluetoothScan.request();
  if (!status.isGranted) {
    Fluttertoast.showToast(msg: 'Bluetooth scan permission denied');
    return false;
  }
}

status = await Permission.locationWhenInUse.status;
if (status.isDenied || status.isPermanentlyDenied) {
  status = await Permission.locationWhenInUse.request();
  if (!status.isGranted) {
    Fluttertoast.showToast(msg: 'Location permission denied');
    return false;
  }
}

_discoveredDevices.clear();
notifyListeners();
try {
  printerManager.scanResults.listen((results) async {
    _discoveredDevices = results;
    await Future.forEach(results, (element) {
      if (element.name == _selectedDevice?.name) {
        _selectedDevice = element;
      }
    });
    notifyListeners();
  });
  printerManager.startScan(const Duration(seconds: 4));

  printerManager.stopScan();
  notifyListeners();
  return true;
} catch (e) {
  print("Error scanning for devices: $e");
  return false;
}

}

And it was working last week but suddenly it started throwing exception while scanning the bluetooth

vishnudasRapidor commented 3 days ago

Here is my code

AndroidManifest.xml


<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />

<!-- New Bluetooth permissions in Android 12 https://developer.android.com/about/versions/12/features/bluetooth-permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- legacy for Android 11 or lower -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />

<!-- legacy for Android 9 or lower -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="28" />

<application
    android:label="bluetooth_printer"
    android:name="${applicationName}"
    android:icon="@mipmap/ic_launcher">

    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:launchMode="singleTop"
        android:taskAffinity=""
        android:theme="@style/LaunchTheme"
        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
        android:hardwareAccelerated="true"
        android:windowSoftInputMode="adjustResize">
        <!-- Specifies an Android theme to apply to this Activity as soon as
             the Android process has started. This theme is visible to the user
             while the Flutter UI initializes. After that, this theme continues
             to determine the Window background behind the Flutter UI. -->
        <meta-data
          android:name="io.flutter.embedding.android.NormalTheme"
          android:resource="@style/NormalTheme"
          />
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>
    <!-- Don't delete the meta-data below.
         This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
    <meta-data
        android:name="flutterEmbedding"
        android:value="2" />

</application>
<!-- Required to query activities that can process text, see:
     https://developer.android.com/training/package-visibility and
     https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.

     In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
    <intent>
        <action android:name="android.intent.action.PROCESS_TEXT"/>
        <data android:mimeType="text/plain"/>
    </intent>
</queries>


>main.dart
```dart
import 'package:esc_pos_bluetooth/esc_pos_bluetooth.dart';
import 'package:esc_pos_utils/esc_pos_utils.dart';
import 'package:flutter/material.dart' hide Image;
import 'package:flutter/services.dart';
import 'package:image/image.dart';
import 'package:intl/intl.dart';
import 'package:oktoast/oktoast.dart';
import 'package:permission_handler/permission_handler.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return OKToast(
      child: MaterialApp(
        title: 'Bluetooth demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHomePage(title: 'Bluetooth demo'),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

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

class MyHomePageState extends State<MyHomePage> {
  PrinterBluetoothManager printerManager = PrinterBluetoothManager();
  List<PrinterBluetooth> _devices = [];

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

    printerManager.scanResults.listen((devices) async {
      // print('UI: Devices found ${devices.length}');
      setState(() {
        _devices = devices;
      });
    });
  }

  void _startScanDevices() async {
    await Permission.bluetooth.request().then((value) {
      if (!value.isGranted) {
        showToast('Bluetooth Permission denied');
        return;
      }
    });

    await Permission.bluetoothScan.request().then((value) {
      if (!value.isGranted) {
        showToast('Bluetooth scan Permission denied');
        return;
      }
    });
    await Permission.bluetoothConnect.request().then((value) {
      if (!value.isGranted) {
        showToast('Bluetooth connect Permission denied');
        return;
      }
    });
    setState(() {
      _devices = [];
    });
    printerManager.startScan(const Duration(seconds: 4));
  }

  void _stopScanDevices() {
    printerManager.stopScan();
  }

  Future<List<int>> demoReceipt(
      PaperSize paper, CapabilityProfile profile) async {
    final Generator ticket = Generator(paper, profile);
    List<int> bytes = [];

    // Print image
    // final ByteData data = await rootBundle.load('assets/rabbit_black.jpg');
    // final Uint8List imageBytes = data.buffer.asUint8List();
    // final Image? image = decodeImage(imageBytes);
    // bytes += ticket.image(image);

    bytes += ticket.text('GROCERYLY',
        styles: const PosStyles(
          align: PosAlign.center,
          height: PosTextSize.size2,
          width: PosTextSize.size2,
        ),
        linesAfter: 1);

    bytes += ticket.text('889  Watson Lane',
        styles: const PosStyles(align: PosAlign.center));
    bytes += ticket.text('New Braunfels, TX',
        styles: const PosStyles(align: PosAlign.center));
    bytes += ticket.text('Tel: 830-221-1234',
        styles: const PosStyles(align: PosAlign.center));
    bytes += ticket.text('Web: www.example.com',
        styles: const PosStyles(align: PosAlign.center), linesAfter: 1);

    bytes += ticket.hr();
    bytes += ticket.row([
      PosColumn(text: 'Qty', width: 1),
      PosColumn(text: 'Item', width: 7),
      PosColumn(
          text: 'Price',
          width: 2,
          styles: const PosStyles(align: PosAlign.right)),
      PosColumn(
          text: 'Total',
          width: 2,
          styles: const PosStyles(align: PosAlign.right)),
    ]);

    bytes += ticket.row([
      PosColumn(text: '2', width: 1),
      PosColumn(text: 'ONION RINGS', width: 7),
      PosColumn(
          text: '0.99',
          width: 2,
          styles: const PosStyles(align: PosAlign.right)),
      PosColumn(
          text: '1.98',
          width: 2,
          styles: const PosStyles(align: PosAlign.right)),
    ]);
    bytes += ticket.row([
      PosColumn(text: '1', width: 1),
      PosColumn(text: 'PIZZA', width: 7),
      PosColumn(
          text: '3.45',
          width: 2,
          styles: const PosStyles(align: PosAlign.right)),
      PosColumn(
          text: '3.45',
          width: 2,
          styles: const PosStyles(align: PosAlign.right)),
    ]);
    bytes += ticket.row([
      PosColumn(text: '1', width: 1),
      PosColumn(text: 'SPRING ROLLS', width: 7),
      PosColumn(
          text: '2.99',
          width: 2,
          styles: const PosStyles(align: PosAlign.right)),
      PosColumn(
          text: '2.99',
          width: 2,
          styles: const PosStyles(align: PosAlign.right)),
    ]);
    bytes += ticket.row([
      PosColumn(text: '3', width: 1),
      PosColumn(text: 'CRUNCHY STICKS', width: 7),
      PosColumn(
          text: '0.85',
          width: 2,
          styles: const PosStyles(align: PosAlign.right)),
      PosColumn(
          text: '2.55',
          width: 2,
          styles: const PosStyles(align: PosAlign.right)),
    ]);
    bytes += ticket.hr();

    bytes += ticket.row([
      PosColumn(
          text: 'TOTAL',
          width: 6,
          styles: const PosStyles(
            height: PosTextSize.size2,
            width: PosTextSize.size2,
          )),
      PosColumn(
          text: '\$10.97',
          width: 6,
          styles: const PosStyles(
            align: PosAlign.right,
            height: PosTextSize.size2,
            width: PosTextSize.size2,
          )),
    ]);

    bytes += ticket.hr(ch: '=', linesAfter: 1);

    bytes += ticket.row([
      PosColumn(
          text: 'Cash',
          width: 7,
          styles:
              const PosStyles(align: PosAlign.right, width: PosTextSize.size2)),
      PosColumn(
          text: '\$15.00',
          width: 5,
          styles:
              const PosStyles(align: PosAlign.right, width: PosTextSize.size2)),
    ]);
    bytes += ticket.row([
      PosColumn(
          text: 'Change',
          width: 7,
          styles:
              const PosStyles(align: PosAlign.right, width: PosTextSize.size2)),
      PosColumn(
          text: '\$4.03',
          width: 5,
          styles:
              const PosStyles(align: PosAlign.right, width: PosTextSize.size2)),
    ]);

    bytes += ticket.feed(2);
    bytes += ticket.text('Thank you!',
        styles: const PosStyles(align: PosAlign.center, bold: true));

    final now = DateTime.now();
    final formatter = DateFormat('MM/dd/yyyy H:m');
    final String timestamp = formatter.format(now);
    bytes += ticket.text(timestamp,
        styles: const PosStyles(align: PosAlign.center), linesAfter: 2);

    // Print QR Code from image
    // try {
    //   const String qrData = 'example.com';
    //   const double qrSize = 200;
    //   final uiImg = await QrPainter(
    //     data: qrData,
    //     version: QrVersions.auto,
    //     gapless: false,
    //   ).toImageData(qrSize);
    //   final dir = await getTemporaryDirectory();
    //   final pathName = '${dir.path}/qr_tmp.png';
    //   final qrFile = File(pathName);
    //   final imgFile = await qrFile.writeAsBytes(uiImg.buffer.asUint8List());
    //   final img = decodeImage(imgFile.readAsBytesSync());

    //   bytes += ticket.image(img);
    // } catch (e) {
    //   print(e);
    // }

    // Print QR Code using native function
    // bytes += ticket.qrcode('example.com');

    ticket.feed(2);
    ticket.cut();
    return bytes;
  }

  Future<List<int>> testTicket(
      PaperSize paper, CapabilityProfile profile) async {
    final Generator generator = Generator(paper, profile);
    List<int> bytes = [];

    bytes += generator.text(
        'Regular: aA bB cC dD eE fF gG hH iI jJ kK lL mM nN oO pP qQ rR sS tT uU vV wW xX yY zZ');
    // bytes += generator.text('Special 1: àÀ èÈ éÉ ûÛ üÜ çÇ ôÔ',
    //     styles: PosStyles(codeTable: PosCodeTable.westEur));
    // bytes += generator.text('Special 2: blåbærgrød',
    //     styles: PosStyles(codeTable: PosCodeTable.westEur));

    bytes += generator.text('Bold text', styles: const PosStyles(bold: true));
    bytes +=
        generator.text('Reverse text', styles: const PosStyles(reverse: true));
    bytes += generator.text('Underlined text',
        styles: const PosStyles(underline: true), linesAfter: 1);
    bytes += generator.text('Align left',
        styles: const PosStyles(align: PosAlign.left));
    bytes += generator.text('Align center',
        styles: const PosStyles(align: PosAlign.center));
    bytes += generator.text('Align right',
        styles: const PosStyles(align: PosAlign.right), linesAfter: 1);

    bytes += generator.row([
      PosColumn(
        text: 'col3',
        width: 3,
        styles: const PosStyles(align: PosAlign.center, underline: true),
      ),
      PosColumn(
        text: 'col6',
        width: 6,
        styles: const PosStyles(align: PosAlign.center, underline: true),
      ),
      PosColumn(
        text: 'col3',
        width: 3,
        styles: const PosStyles(align: PosAlign.center, underline: true),
      ),
    ]);

    bytes += generator.text('Text size 200%',
        styles: const PosStyles(
          height: PosTextSize.size2,
          width: PosTextSize.size2,
        ));

    // Print image
    final ByteData data = await rootBundle.load('assets/logo.png');
    final Uint8List buf = data.buffer.asUint8List();
    final Image image = decodeImage(buf)!;
    bytes += generator.image(image);
    // Print image using alternative commands
    // bytes += generator.imageRaster(image);
    // bytes += generator.imageRaster(image, imageFn: PosImageFn.graphics);

    // Print barcode
    final List<int> barData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 4];
    bytes += generator.barcode(Barcode.upcA(barData));

    // Print mixed (chinese + latin) text. Only for printers supporting Kanji mode
    // bytes += generator.text(
    //   'hello ! 中文字 # world @ éphémère &',
    //   styles: PosStyles(codeTable: PosCodeTable.westEur),
    //   containsChinese: true,
    // );

    bytes += generator.feed(2);

    bytes += generator.cut();
    return bytes;
  }

  void _testPrint(PrinterBluetooth printer) async {
    printerManager.selectPrinter(printer);

    // TODO Don't forget to choose printer's paper
    const PaperSize paper = PaperSize.mm80;
    final profile = await CapabilityProfile.load();

    // TEST PRINT
    // final PosPrintResult res =
    // await printerManager.printTicket(await testTicket(paper));

    // DEMO RECEIPT
    final PosPrintResult res =
        await printerManager.printTicket((await demoReceipt(paper, profile)));

    showToast(res.msg);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView.builder(
          itemCount: _devices.length,
          itemBuilder: (BuildContext context, int index) {
            return InkWell(
              onTap: () => _testPrint(_devices[index]),
              child: Column(
                children: <Widget>[
                  Container(
                    height: 60,
                    padding: const EdgeInsets.only(left: 10),
                    alignment: Alignment.centerLeft,
                    child: Row(
                      children: <Widget>[
                        const Icon(Icons.print),
                        const SizedBox(width: 10),
                        Expanded(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: <Widget>[
                              Text(_devices[index].name ?? ''),
                              Text(_devices[index].address!),
                              Text(
                                'Click to print a test receipt',
                                style: TextStyle(color: Colors.grey[700]),
                              ),
                            ],
                          ),
                        )
                      ],
                    ),
                  ),
                  const Divider(),
                ],
              ),
            );
          }),
      floatingActionButton: StreamBuilder<bool>(
        stream: printerManager.isScanningStream,
        initialData: false,
        builder: (c, snapshot) {
          if (snapshot.data!) {
            return FloatingActionButton(
              onPressed: _stopScanDevices,
              backgroundColor: Colors.red,
              child: const Icon(Icons.stop),
            );
          } else {
            return FloatingActionButton(
              onPressed: _startScanDevices,
              child: const Icon(Icons.search),
            );
          }
        },
      ),
    );
  }
}
rohit0713 commented 3 days ago

@singgihmardianto Actually I am getting error while scanning the bluetooth

rohit0713 commented 3 days ago

@singgihmardianto It was working properly last week but suddenly it stopped working and throwing exception

singgihmardianto commented 2 days ago

@rohit0713 all seems good.. what version are you using? I am using esc_pos_bluetooth: ^0.4.1 and works well on my end. Have you tried to run flutter clean and flutter pub get?

vishnudasRapidor commented 2 days ago

@singgihmardianto i'm also using the same version also. but having this issue. yes used flutter clean many times.

singgihmardianto commented 2 days ago

@vishnudasRapidor what's your targetSdkVersion and compiledSdkVersion?

vishnudasRapidor commented 2 days ago

@singgihmardianto compileSdk = 34 targetSdk = flutter.targetSdkVersion minSdk = 21

flutter 3.22.1 dart 3.4.1

vishnudasRapidor commented 6 hours ago

@singgihmardianto could you be able to identify the issue?

rohit0713 commented 6 hours ago

@singgihmardianto @vishnudasRapidor The issue is with the latest flutter version. It is not supporting in current flutter version. I think dependency should update according to the latest flutter version.

vishnudasRapidor commented 6 hours ago

@rohit0713 in my project or the esc_pos_bluetooth package dependencies?

rohit0713 commented 6 hours ago

@vishnudasRapidor In your project Use flutter 3.19.5 Version then it will work perfectly.

vishnudasRapidor commented 5 hours ago

so I need to downgrade. But the user already using will face problems once they upgrade right? But this the issue for package itself. @singgihmardianto since user don't downgrade always, resolve it on the package will be a better approach. Please currect me if I'm wrong.

singgihmardianto commented 2 hours ago

@vishnudasRapidor @rohit0713 yeah I think so. The only different with my machine is the flutter and dart version. Mine: flutter 3.19.3 dart 3.3.1

I think the best solution right now is to downgrade the version and waiting support for the latest flutter version.

vishnudasRapidor commented 2 hours ago

@singgihmardianto downgrading is not an option for us, because downgrading will cause errors from other packages. We are keeping dependencies updated for some reason. Planning to handle android part done by ourselves till new release comes.

Thank you @singgihmardianto @rohit0713 for the help ❤️