okadan / flutter-nfc-manager

A Flutter plugin for accessing the NFC features on Android and iOS.
https://pub.dev/packages/nfc_manager
MIT License
207 stars 134 forks source link

iOS Channel sent a message from native to Flutter on a non-platform thread #162

Open nopbyte opened 12 months ago

nopbyte commented 12 months ago

Hi,

I have noticed there is a log line the first time I try to scan a tag in an IOS device.

[VERBOSE-2:shell.cc(1004)] The 'plugins.flutter.io/nfc_manager' channel sent a message from native to Flutter on a non-platform thread. Platform channel messages must be sent on the platform thread. Failure to do so may result in data loss or crashes, and must be fixed in the plugin or application code creating that channel. See https://docs.flutter.dev/platform-integration/platform-channels#channels-and-platform-threading for more information.

This looks like something that should be addressed to avoid app crashes?

Here is a self-contained example. I made it stateful to make sure I was not leaving a session open. The first time the NFC library is used after start will show the line above in the debug console.

import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:nfc_manager/nfc_manager.dart';

class GetNFC extends StatefulWidget {
  const GetNFC({super.key});
  @override
  State<StatefulWidget> createState() {
    return GetNFCState();
  }
}

class GetNFCState extends State<GetNFC> {
  bool sessionOpen = false;
  bool success = false;
  String? value;
  @override
  void dispose() {
    if (sessionOpen) {
      NfcManager.instance.stopSession();
    }
    super.dispose();
  }

  String logNFCError(var error) {
    String logLine =
        'NFC Error: message ${error.message} type ${error.type} detrails ${error.details}';
    log(logLine);
    return logLine;
  }

  @override
  Widget build(BuildContext context) {
    if (value != null) {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(value!),
          ElevatedButton(
              onPressed: () {
                setState(() {
                  value = null;
                  sessionOpen = false;
                  success = false;
                });
              },
              child: const Text('Restart'))
        ],
      );
    }
    return FutureBuilder<bool>(
      future: NfcManager.instance.isAvailable(),
      builder: (context, nfcAvailableSnapShot) {
        if (nfcAvailableSnapShot.hasData) {
          if (nfcAvailableSnapShot.data == true) {
            sessionOpen = false;
            NfcManager.instance.startSession(
              alertMessage: 'Scan the card now',
              onDiscovered: (NfcTag tag) async {
                try {
                  sessionOpen = true;
                  try {
                    Ndef? ndef = Ndef.from(tag);
                    final cachedMessage = ndef!.cachedMessage;
                    NdefRecord? record = cachedMessage!.records
                        .where((e) =>
                            e.typeNameFormat == NdefTypeNameFormat.nfcWellknown)
                        .firstOrNull;
                    if (record != null) {
                      final languageCodeLength = record.payload.first;
                      final textBytes =
                          record.payload.sublist(1 + languageCodeLength);
                      success = true;
                      NfcManager.instance.stopSession();
                      sessionOpen = false;
                      setState(() {
                        value = ascii.decode(textBytes);
                      });
                    }
                  } catch (e) {
                    if (sessionOpen) {
                      NfcManager.instance.stopSession();
                    }
                    setState(() {
                      value = logNFCError(e);
                    });
                  }
                } catch (e) {
                  if (sessionOpen) {
                    NfcManager.instance.stopSession();
                  }
                  setState(() {
                    value = logNFCError(e);
                  });
                }
              },
              onError: (error) async {
                if (success) return;
                if (sessionOpen) {
                  NfcManager.instance.stopSession();
                }
                setState(() {
                  value = logNFCError(error);
                });
              },
            );
          }
          return const Text('Please tap the card on your phone');
        } else {
          return const CircularProgressIndicator();
        }
      },
    );
  }
}

void main() {
  runApp(const MyApp());
}

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

  static GlobalKey mtAppKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        key: MyApp.mtAppKey,
        title: 'NfcTestApp',
        theme: ThemeData(
          useMaterial3: true,
        ),
        home: const Scaffold(body: Center(child: GetNFC())));
  }
}

Here is the output of my flutter --version:

Flutter 3.13.8 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 6c4930c4ac (6 days ago) • 2023-10-18 10:57:55 -0500
Engine • revision 767d8c75e8
Tools • Dart 3.1.4 • DevTools 2.25.0

Also, I can't say for certain this is related, but also sometimes I see the following messages show up in the debug logs, and some time after that I see an NfcErrorType.unknown after a second or so.

[CoreNFC] -[NFCHardwareManager queueReaderSession:sessionConfig:completionHandler:]:105 error=Error Domain=NFCError Code=203 "System resource unavailable" [CoreNFC] -[NFCTagReaderSession beginSessionWithConfig:]:458 error:Error Domain=NFCError Code=203 "System resource unavailable" UserInfo={NSLocalizedDescription=System resource unavailable}, errorCode: 0xcb I could not reproduce the previous two log entries reliably with the example (only the log line about the "non-related platform channel"), but if anyone has any pointers for me I would appreciate it.

nopbyte commented 11 months ago

Just wanted to provide an update. After using the profiling from XCode I noticed that in the case of my app I have Thermal State: serious after a while. So most likely the errors at the bottom are due to some performance error I have in my code.

That being said, the error about the non-platform thread is not related to my app and I expect it to be reproducible with any iOS device.

FritzMatthaeus commented 9 months ago

i have created a pull request to fix this problem.

umitceeelik commented 8 months ago

i have created a pull request to fix this problem.

Hello, i am new on flutter. how can i use the plugin via github with your commit. I was adding the plugin to my project with pub.dev? Can you help how can i add the plugin ?

FritzMatthaeus commented 8 months ago

@okadan has merged it into the repository.

As long as @okadan has not published it to pub.dev, you can put the github project directly as dependency. Go to your pubspec.yaml and in dependencies you add:

flutter-nfc-manager:
  git:
    url: https://github.com/okadan/flutter-nfc-manager
    branch: master

instead of

flutter-nfc-manager: ^3.3.0

Please double check the correct tab in the yaml-file in case you copy&paste it. I could only do it with whitespaces here. So better not copy&paste.

umitceeelik commented 8 months ago

@okadan has merged it into the repository.

As long as @okadan has not published it to pub.dev, you can put the github project directly as dependency. Go to your pubspec.yaml and in dependencies you add:

flutter-nfc-manager:
  git:
    url: https://github.com/okadan/flutter-nfc-manager
    branch: master

instead of

flutter-nfc-manager: ^3.3.0

Please double check the correct tab in the yaml-file in case you copy&paste it. I could only do it with whitespaces here. So better not copy&paste.

Thank you for your quick response. #113 can you look at also this problem ?

FritzMatthaeus commented 8 months ago

Thank you for your quick response. #113 can you look at also this problem ?

as it is written in the documentation, this plugin requires iOS 13+ to function. So it won't work with iOS12 and iPhones 4-6. I think later models have been provided with iOS13+.

umitceeelik commented 8 months ago

Thank you for your quick response. #113 can you look at also this problem ?

as it is written in the documentation, this plugin requires iOS 13+ to function. So it won't work with iOS12 and iPhones 4-6. I think later models have been provided with iOS13+.

So, can i ignore this framework for these devices? So, nfc is only one part of my project. So if the device which is before iOS13, it will not need nfc. Can i make something like that ?, I need this because i cant launch the app on iOS12 :/

FritzMatthaeus commented 8 months ago

Best way is to make your App available for Devices with iOS13+ only. The only way to "ignore" a framework on iOS12 is this:

https://stackoverflow.com/questions/68818599/plugin-development-use-flutter-package-plugin-for-specific-platform-only-e-g

In this case you can exclude it, but you will have to ensure that you are not calling the Package in your Dart Code on Devices below iOS13. As you are a beginner, i do not recommend to do this.