flutter_nfc_kit
flutter_nfc_kit copied to clipboard
Android default NFC reader gets triggered
This issue only occurs on Android whenever I try to scan NFC tag using my own app where it triggers the default NFC reader of the phone.
Is there a way to prevent it and read only from the app library?
Sorry, I don't really understand your situation. On Android our native code will try to poll all types of NFC Technology with/without NDEF checking according to flags passed. You could refer to:
https://github.com/nfcim/flutter_nfc_kit/blob/6471b5931e6b6e4765bfad9bd3385880fcddd03b/android/src/main/kotlin/im/nfc/flutter_nfc_kit/FlutterNfcKitPlugin.kt#L291-L297
Can you actually detect polled tag in your code (that is, does await poll()
returns?) when scanning?
I have meant when I try to read NFC through my app, the default NFC reader of the phone opened. I want to prevent this and make the phone reads the NFC tag only inside my app. I hope this clarifies the issue.
I have meant when I try to read NFC through my app, the default NFC reader of the phone opened. I want to prevent this and make the phone reads the NFC tag only inside my app. I hope this clarifies the issue.
What you need might be the foreground dispatch mode, which is possible with Flutter, but needs some tricky implementation in native side. I do not have much time to handle this right now. You may try (and maybe send a PR!) that if you want to avoid the system stepping in when scanning tags.
Would be nice to avoid the system stepping in when scanning tags. Found this on SO:
if (pendingIntent == null) { pendingIntent = PendingIntent.getActivity(this, 0, new Intent(), 0); } adapter.enableForegroundDispatch(this, pendingIntent, null, null);
Would be nice to avoid the system stepping in when scanning tags. Found this on SO:
if (pendingIntent == null) { pendingIntent = PendingIntent.getActivity(this, 0, new Intent(), 0); } adapter.enableForegroundDispatch(this, pendingIntent, null, null);
Currently we are using reader mode only. I am not sure whether enabling foreground dispatch and reader mode simultaneously leads to current behaviour. Have you tried to enable foreground dispatch without doing anything in onNewIntent
? If this works I am happy to enable it to prevent system dialog from intervening with tag reading.
Would be nice to avoid the system stepping in when scanning tags. Found this on SO:
if (pendingIntent == null) { pendingIntent = PendingIntent.getActivity(this, 0, new Intent(), 0); } adapter.enableForegroundDispatch(this, pendingIntent, null, null);
I have tried foreground dispatching mode, but seems nothing changes . When you are not in reader mode, a tag scan will still trigger the system dialog. To solve this, I believe we have to implement a streaming mode with foreground dispatch, as I mentioned in https://github.com/nfcim/flutter_nfc_kit/issues/17#issuecomment-727222864.
Hello, is there any update regarding this issue?
Hello, is there any update regarding this issue?
@jacopofranza As mentioned above, I cannot solve this without adding support for streaming mode with foreground dispatch. However this would be a great change to our API, and requires a relatively large amount of work. So, sorry, but I cannot give any guarantee on this.
Workaround: Encode the card mem directly w/o using ntag, this prevents dialog to pop.
Workaround: Encode the card mem directly w/o using ntag, this prevents dialog to pop.
Hi Mythar, i am intersted in your workaround , but i do not understand what you wrote. How can i write using ntag (since ntag is a nfc tag ) ?
Workaround: Encode the card mem directly w/o using ntag, this prevents dialog to pop.
Hi Mythar, i am intersted in your workaround , but i do not understand what you wrote. How can i write using ntag (since ntag is a nfc tag ) ?
As I wrote, do not write to a ntag, write to the card mem directly - depends on what software you are using to encode your cards.
This is the real workaround to avoid the problem. In your manifest at the end of the main activity put:
<activity ... >
...
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter"/>
</activity>
In res/xml create a new file named "nfc_tech_filter" with the intent types you want to filter:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.NfcF</tech>
<tech>android.nfc.tech.NfcV</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NdefFormatable</tech>
<tech>android.nfc.tech.MifareClassic</tech>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
In the MainActivity insert the following lines to enable foreground dispatch:
override fun onResume() {
super.onResume()
val adapter: NfcAdapter = NfcAdapter.getDefaultAdapter(this)
val pendingIntent: PendingIntent = PendingIntent.getActivity(
this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
adapter.enableForegroundDispatch(this, pendingIntent, null, null)
}
override fun onPause() {
super.onPause()
val adapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)
adapter?.disableForegroundDispatch(this)
}
@jacopofranza Thanks for this! I will look into your solution later.
I wrote this for direct memory read of the NFCTagType.mifare_ultralight:
// Import
import 'dart:io';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_nfc_kit/flutter_nfc_kit.dart';
// hexStringToAscii
String hexStringToAscii(String hexString) {
List<String> split = [];
for (int i = 0; i < hexString.length; i = i + 2) {
if (hexString.substring(i, i + 2) != '00') {
split.add(hexString.substring(i, i + 2));
}
}
String ascii = List.generate(split.length,
(i) => String.fromCharCode(int.parse(split[i], radix: 16))).join();
return ascii;
}
// NFCMemory
class NFCMemory {
dynamic data;
String ascii;
// Constructor
NFCMemory({this.data, this.ascii});
factory NFCMemory.fromData(dynamic data) {
if ((data != null) && (data is String) && (data != '')) {
return new NFCMemory(
data: data,
ascii: hexStringToAscii(data)
);
} else {
return new NFCMemory(data: data, ascii: null);
}
}
}
// NFCInfo
class NFCInfo {
NFCTag tag;
NFCMemory memory;
// Constructor
NFCInfo({this.tag, this.memory});
}
// NFCDirect
class NFCDirect {
String alertMessage;
String successMessage;
NFCDirect({this.alertMessage, this.successMessage});
// isNFCAvailable
Future<bool> isAvailable() async {
NFCAvailability availability;
try {
availability = await FlutterNfcKit.nfcAvailability;
} on PlatformException {
availability = NFCAvailability.not_supported;
}
if (availability != NFCAvailability.available) {
return false;
} else {
return true;
}
}
// readNFCMemory
Future<NFCMemory> readMemory(NFCTag tag) async {
NFCMemory result;
if (tag.type == NFCTagType.mifare_ultralight) {
// 04 08 0C
var mem1 = await FlutterNfcKit.transceive("3004", timeout: Duration(seconds: 10)); // timeout is still Android-only, persist until next change
var mem2 = await FlutterNfcKit.transceive("3008", timeout: Duration(seconds: 10)); // timeout is still Android-only, persist until next change
var mem3 = await FlutterNfcKit.transceive("300C", timeout: Duration(seconds: 10)); // timeout is still Android-only, persist until next change
if ((mem1 is String) && (mem2 is String) && (mem3 is String)) {
result = NFCMemory.fromData(mem1 + mem2 + mem3);
}
}
return result;
}
// iOSRead
Future<NFCInfo> iOSRead() async {
NFCTag tag;
NFCMemory mem;
try {
tag = await FlutterNfcKit.poll(
readIso14443A: true,
readIso14443B: true,
readIso15693: false,
readIso18092: false,
iosAlertMessage: alertMessage,
);
// Read memory
mem = await readMemory(tag);
sleep(new Duration(seconds: 1));
await FlutterNfcKit.finish(
iosAlertMessage: successMessage
);
} catch (e) {
print(e);
}
return new NFCInfo(
tag: tag,
memory: mem
);
}
// androidRead
Future<NFCInfo> androidRead() async {
NFCTag tag;
NFCMemory mem;
try {
tag = await FlutterNfcKit.poll(
readIso14443A: true,
readIso14443B: true,
readIso15693: false,
readIso18092: false,
timeout: Duration(seconds: 10),
androidPlatformSound: false,
androidCheckNDEF: false,
);
// Read memory
mem = await readMemory(tag);
sleep(new Duration(seconds: 1));
await FlutterNfcKit.finish();
} catch (e) {
print(e);
}
return new NFCInfo(
tag: tag,
memory: mem
);
}
}
I encountered similar issue on Android, why it happens and how I solved it:
Issue:
After write tag, I called finishNfc
, and then immediately, the newly written tag triggered the default NFC behaviour and kicked user out of the app to the link that on the tag.
Why:
NFC session implementation on Android is actually simulated by turning activity into reader mode, which suppresses the default NFC behaviours. And finishing nfc turn off the reader mode, and then default Android NFC behaviour kicked in.
Solution:
On Android, I put a short delay after UI tells user NFC operation finished and before actually finish the NFC. And UI literally tells user to move the tag away from the phone. So it avoided the default NFC behaviour
Hope it helps
Thanks for the help! but Why if the tag is empty it keeps firing the alert to choose the app for scan?
I wrote this for direct memory read of the NFCTagType.mifare_ultralight:
// Import import 'dart:io'; import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; // hexStringToAscii String hexStringToAscii(String hexString) { List<String> split = []; for (int i = 0; i < hexString.length; i = i + 2) { if (hexString.substring(i, i + 2) != '00') { split.add(hexString.substring(i, i + 2)); } } String ascii = List.generate(split.length, (i) => String.fromCharCode(int.parse(split[i], radix: 16))).join(); return ascii; } // NFCMemory class NFCMemory { dynamic data; String ascii; // Constructor NFCMemory({this.data, this.ascii}); factory NFCMemory.fromData(dynamic data) { if ((data != null) && (data is String) && (data != '')) { return new NFCMemory( data: data, ascii: hexStringToAscii(data) ); } else { return new NFCMemory(data: data, ascii: null); } } } // NFCInfo class NFCInfo { NFCTag tag; NFCMemory memory; // Constructor NFCInfo({this.tag, this.memory}); } // NFCDirect class NFCDirect { String alertMessage; String successMessage; NFCDirect({this.alertMessage, this.successMessage}); // isNFCAvailable Future<bool> isAvailable() async { NFCAvailability availability; try { availability = await FlutterNfcKit.nfcAvailability; } on PlatformException { availability = NFCAvailability.not_supported; } if (availability != NFCAvailability.available) { return false; } else { return true; } } // readNFCMemory Future<NFCMemory> readMemory(NFCTag tag) async { NFCMemory result; if (tag.type == NFCTagType.mifare_ultralight) { // 04 08 0C var mem1 = await FlutterNfcKit.transceive("3004", timeout: Duration(seconds: 10)); // timeout is still Android-only, persist until next change var mem2 = await FlutterNfcKit.transceive("3008", timeout: Duration(seconds: 10)); // timeout is still Android-only, persist until next change var mem3 = await FlutterNfcKit.transceive("300C", timeout: Duration(seconds: 10)); // timeout is still Android-only, persist until next change if ((mem1 is String) && (mem2 is String) && (mem3 is String)) { result = NFCMemory.fromData(mem1 + mem2 + mem3); } } return result; } // iOSRead Future<NFCInfo> iOSRead() async { NFCTag tag; NFCMemory mem; try { tag = await FlutterNfcKit.poll( readIso14443A: true, readIso14443B: true, readIso15693: false, readIso18092: false, iosAlertMessage: alertMessage, ); // Read memory mem = await readMemory(tag); sleep(new Duration(seconds: 1)); await FlutterNfcKit.finish( iosAlertMessage: successMessage ); } catch (e) { print(e); } return new NFCInfo( tag: tag, memory: mem ); } // androidRead Future<NFCInfo> androidRead() async { NFCTag tag; NFCMemory mem; try { tag = await FlutterNfcKit.poll( readIso14443A: true, readIso14443B: true, readIso15693: false, readIso18092: false, timeout: Duration(seconds: 10), androidPlatformSound: false, androidCheckNDEF: false, ); // Read memory mem = await readMemory(tag); sleep(new Duration(seconds: 1)); await FlutterNfcKit.finish(); } catch (e) { print(e); } return new NFCInfo( tag: tag, memory: mem ); } }
it's not gonna work , do you have any other way to read NFC memory, dear ?
I encountered similar issue on Android, why it happens and how I solved it:
Issue:
After write tag, I called
finishNfc
, and then immediately, the newly written tag triggered the default NFC behaviour and kicked user out of the app to the link that on the tag.Why:
NFC session implementation on Android is actually simulated by turning activity into reader mode, which suppresses the default NFC behaviours. And finishing nfc turn off the reader mode, and then default Android NFC behaviour kicked in.
Solution:
On Android, I put a short delay after UI tells user NFC operation finished and before actually finish the NFC. And UI literally tells user to move the tag away from the phone. So it avoided the default NFC behaviour
Hope it helps
Thank you so much it works perfectly for me
This is the real workaround to avoid the problem. In your manifest at the end of the main activity put:
<activity ... > ... <intent-filter> <action android:name="android.nfc.action.TECH_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter"/> </activity>
In res/xml create a new file named "nfc_tech_filter" with the intent types you want to filter:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <tech-list> <tech>android.nfc.tech.IsoDep</tech> <tech>android.nfc.tech.NfcA</tech> <tech>android.nfc.tech.NfcB</tech> <tech>android.nfc.tech.NfcF</tech> <tech>android.nfc.tech.NfcV</tech> <tech>android.nfc.tech.Ndef</tech> <tech>android.nfc.tech.NdefFormatable</tech> <tech>android.nfc.tech.MifareClassic</tech> <tech>android.nfc.tech.MifareUltralight</tech> </tech-list> </resources>
In the MainActivity insert the following lines to enable foreground dispatch:
override fun onResume() { super.onResume() val adapter: NfcAdapter = NfcAdapter.getDefaultAdapter(this) val pendingIntent: PendingIntent = PendingIntent.getActivity( this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0) adapter.enableForegroundDispatch(this, pendingIntent, null, null) } override fun onPause() { super.onPause() val adapter: NfcAdapter = NfcAdapter.getDefaultAdapter(this) adapter.disableForegroundDispatch(this) }
Thank you for this solution, works nicely. Only thing to add is that NfcAdapter.getDefaultAdapter(this)
returns null
when the device doesn't have a NFC reader, and thus will throw a nice NullPointerException. So just make the NfcAdapter
type nullable, and handle the null
, for example:
override fun onResume() {
super.onResume()
val adapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)
val pendingIntent: PendingIntent = PendingIntent.getActivity(
this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_IMMUTABLE)
adapter?.enableForegroundDispatch(this, pendingIntent, null, null)
}
override fun onPause() {
super.onPause()
val adapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)
adapter?.disableForegroundDispatch(this)
}
@sampie777 Where do you put those functions?
My MainActivity.kt looks like, but I get errors when I try to run:
package com.myApp.myApp
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity: FlutterFragmentActivity() {
override fun onResume() {
super.onResume()
val adapter: NfcAdapter = NfcAdapter.getDefaultAdapter(this)
val pendingIntent: PendingIntent = PendingIntent.getActivity(
this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
adapter.enableForegroundDispatch(this, pendingIntent, null, null)
}
override fun onPause() {
super.onPause()
val adapter: NfcAdapter = NfcAdapter.getDefaultAdapter(this)
adapter.disableForegroundDispatch(this)
}
}
Errors:
e: file:///C:/Users/User/StudioProjects/app/android/app/src/main/kotlin/com/myApp/app/MainActivity.kt:8:22 Unresolved reference: NfcAdapter
e: file:///C:/Users/User/StudioProjects/app/android/app/src/main/kotlin/com/myApp/app/MainActivity.kt:8:35 Unresolved reference: NfcAdapter
e: file:///C:/Users/User/StudioProjects/app/android/app/src/main/kotlin/com/myApp/app/MainActivity.kt:9:28 Unresolved reference: PendingIntent
e: file:///C:/Users/User/StudioProjects/app/android/app/src/main/kotlin/com/myApp/app/MainActivity.kt:9:44 Unresolved reference: PendingIntent
e: file:///C:/Users/User/StudioProjects/app/android/app/src/main/kotlin/com/myApp/app/MainActivity.kt:10:26 Unresolved reference: Intent
e: file:///C:/Users/User/StudioProjects/app/android/app/src/main/kotlin/com/myApp/app/MainActivity.kt:10:59 Unresolved reference: Intent
e: file:///C:/Users/User/StudioProjects/app/android/app/src/main/kotlin/com/myApp/app/MainActivity.kt:15:22 Unresolved reference: NfcAdapter
e: file:///C:/Users/User/StudioProjects/app/android/app/src/main/kotlin/com/myApp/app/MainActivity.kt:15:35 Unresolved reference: NfcAdapter
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:compileDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction
> Compilation error. See log for more details
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 13s
Exception: Gradle task assembleDebug failed with exit code 1
@amilkarSingular yeah you are missing imports. Should be something like this:
package <yourpackage>
import android.app.PendingIntent
import android.content.Intent
import android.nfc.NfcAdapter
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity() {
override fun onResume() {
super.onResume()
val adapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)
val pendingIntent: PendingIntent = PendingIntent.getActivity(
this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_IMMUTABLE)
adapter?.enableForegroundDispatch(this, pendingIntent, null, null)
}
override fun onPause() {
super.onPause()
val adapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(this)
adapter?.disableForegroundDispatch(this)
}
}
@sampie777 should this code work in case I want to listen for poll results when my app is in background state (not terminated)
@sampie777 should this code work in case I want to listen for poll results when my app is in background state (not terminated)
I don't know, as I've not tested this and I've not dug that deep into the code. As far as I know, the code only prevents Android from executing its default handler for NFC scans while the app is in foreground. The app still receives these scans.
What happens when the app is in background: I don't know. Android will resume executing the default handler for NFC scans, but I think it's not part of this code if your app will also receive these scans. You may have to register your app as a default NFC scan listener or maybe just remove the onPause
method (again, I don't know what implications this might have).
The foreground lifetime of an activity happens between a call to onResume() until a corresponding call to onPause(). https://developer.android.com/reference/android/app/Activity.html?is-external=true#activity-lifecycle
Not Working in android because "mifareInfo":"{type=ultralight_c, size=176, blockSize=4, blockCount=44, sectorCount=null}"
Not Working in android because "mifareInfo":"{type=ultralight_c, size=176, blockSize=4, blockCount=44, sectorCount=null}"
Please see #126