androidfluttertagsnfc

Android NFC is not getting an empty nfc tag


I'm using flutter nfc_manager to write on an nfc tag. It works great on iOS but in Android, it has a problem when I try to write on a empty nfc tag. Writing on an tag that has existing data is working fine. How should I fix it?

This is my iOS code

  Future<bool> _ndefWrite(NdefMessage ndefMessage) async {
    Completer<bool> completer = Completer<bool>();

    await NfcManager.instance.startSession(
      pollingOptions: {NfcPollingOption.iso14443, NfcPollingOption.iso15693},
      alertMessage: "NFC 태그를 스캔해주세요.",
      onDiscovered: (NfcTag tag) async {
        var ndef = Ndef.from(tag);
        if (ndef == null || !ndef.isWritable) {
          _errorMessage = "쓰기가 불가능한 NFC 태그입니다.";
          NfcManager.instance.stopSession(errorMessage: _errorMessage);
          completer.complete(false); // Use completer to complete with false
        } else {
          try {
            await ndef.write(ndefMessage);
            _alertMessage = "NFC 태그에 성공적으로 기록하였습니다.";
            NfcManager.instance.stopSession(alertMessage: _alertMessage);
            completer.complete(true); // Complete with true upon success
          } catch (error) {
            _errorMessage = error.toString();
            NfcManager.instance.stopSession(errorMessage: _errorMessage);
            completer.complete(false); // Complete with false on error
          }
        }
      },
      onError: (NfcError error) async {
        _errorMessage = error.message;
        NfcManager.instance.stopSession(errorMessage: _errorMessage);
        completer.complete(false); // Complete with false on error
      }
    );

    return completer.future.timeout(const Duration(seconds: 10), onTimeout: () {
      NfcManager.instance.stopSession(errorMessage: "NFC 스캔 시간 초과.");
      if (!completer.isCompleted) {
        completer.complete(false); // Complete with false only if not already completed
      }
      return false;
    });
  }

and this is my Android code. I think the problem is that it gets null when detects an empty Tag in code "Ndef.from(tag)" but in ios it works fine.

  Future<bool> _ndefWriteAndroid(NdefMessage ndefMessage) async {
    Completer<bool> completer = Completer<bool>();
    NFCDialog nfcDialog = NFCDialog(context: context, completer: completer);

    nfcDialog.show();
    await NfcManager.instance.startSession(
      onDiscovered: (NfcTag tag) async {
        Navigator.of(context, rootNavigator: true).pop();
        var ndef = Ndef.from(tag);
        if (ndef == null || !ndef.isWritable) {
          _errorMessage = "쓰기가 불가능한 NFC 태그입니다.";
          NfcManager.instance.stopSession(errorMessage: _errorMessage);
          completer.complete(false); // Use completer to complete with false
        } else {
          try {
            await ndef.write(ndefMessage);
            _alertMessage = "NFC 태그에 성공적으로 기록하였습니다.";
            NfcManager.instance.stopSession(alertMessage: _alertMessage);
            completer.complete(true); // Complete with true upon success
          } catch (error) {
            _errorMessage = error.toString();
            NfcManager.instance.stopSession(errorMessage: _errorMessage);
            completer.complete(false); // Complete with false on error
          }
        }
      },
    );

    return completer.future.timeout(const Duration(seconds: 10), onTimeout: () {
      Navigator.of(context, rootNavigator: true).pop();
      NfcManager.instance.stopSession(errorMessage: "NFC 스캔 시간 초과.");
      if (!completer.isCompleted) {
        completer.complete(false); // Complete with false only if not already completed
      }
      return false;
    });
  }

this is my intent filter in AndroidManifest.xml

    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
    <intent-filter>
        <action android:name="android.nfc.action.TECH_DISCOVERED" />
    </intent-filter>

Solution

  • The problem is that an Empty Tag is not an Ndef Tag, an Ndef Tag must have at least have an Empty TLV block.

    In Android to detect an Empty Tag that is capable of storing a Ndef message you need to look for a NdefFormatable Tag

    So you need to re-arrange the logic in your code to something like this

    var ndef = Ndef.from(tag);
    if (ndef == null) {
       var ndefFormatable = NdefFormatable.from(tag);
       if (ndefFormatable != null) {
         // Format and write message in one operation.
         await ndefFormatable.format(ndefMessage);
        _alertMessage = "NFC Success message";
        NfcManager.instance.stopSession(alertMessage: _alertMessage);
        completer.complete(true); // Complete with true upon success
       } else {
         errorMessage = "Some error message";
         NfcManager.instance.stopSession(errorMessage: _errorMessage);
         completer.complete(false); // Use completer to complete with false
       }
    } else if (!ndef.isWritable) {
      _errorMessage = "쓰기가 불가능한 NFC 태그입니다.";
      NfcManager.instance.stopSession(errorMessage: _errorMessage);
      completer.complete(false); // Use completer to complete with false
    } else {
      try {
        await ndef.write(ndefMessage);
        _alertMessage = "NFC 태그에 성공적으로 기록하였습니다.";
        NfcManager.instance.stopSession(alertMessage: _alertMessage);
        completer.complete(true); // Complete with true upon success
      } catch (error) {
        _errorMessage = error.toString();
        NfcManager.instance.stopSession(errorMessage: _errorMessage);
        completer.complete(false); // Complete with false on error
      }
    }
    

    Basically if you did not detect a Ndef Tag, try detecting a Ndeformatable Tag and then format(and write) you Ndef message there. If not either type then fail.