I'm not at all familiar with NFC tag detection and I'm trying to set up a listener for any NFC tag detected in an activity. I want to just display a toast message when the activity detects an NFC tag but I'm having trouble doing so.
MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity)
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Toast.makeText(applicationContext, "NFC Tag Detected", Toast.LENGTH_LONG).show()
}
And in my manifest I have this:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
I'm testing this using a Samsung Galaxy S6 and S7. When I put them together while the activity is running on one of them, I want to be able to see a toast message but so far nothing is showing up. I don't need to read the tag, I don't care what type of tag it is, I only need to know that there was a tag detected.
The intent filters that you registered in your app manifest do not make much sense.
android.nfc.action.TAG_DISCOVERED
(when used in the manifest) is only a fall-back mechanism to catch any NFC tags that are not handled by other apps.android.nfc.action.NDEF_DISCOVERED
also needs a data type specification to actually catch NFC tags that contain specific NDEF records. It won't match any tags without one.android.nfc.action.TECH_DISCOVERED
also needs a tech to catch specific tag technologies. It won't match any tags without one.Moreover, you would probably want to put each intent action in a separate <intent-filter>
to have better control over categories, data types, etc.
However, since you are only interested in receiving NFC discovery events while your activity is in the foreground, you have better and (somewhat) more reliable options to detect tags: the foreground dispatch system and the reader mode API.
You would want to choose between one of them:
You can register your activity to receive NFC intents during onResume()
:
@Override
public void onResume() {
super.onResume();
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
}
That's probably something like this in Kotlin (though not tested):
fun onResume() {
super.onResume()
val pendingIntent = PendingIntent.getActivity(this, 0, Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0)
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null)
}
Make sure to unregister this again during onPause()
:
@Override
public void onPause() {
super.onPause();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.disableForegroundDispatch(this);
}
Kotlin:
fun onPause() {
super.onPause()
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
nfcAdapter.disableForegroundDispatch(this)
}
You will then receive NFC events as TAG_DISCOVERED intents through onNewIntent()
:
@Override
public void onNewIntent(Intent intent) {
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
// TODO: process intent
}
}
Kotlin:
fun onNewIntent(intent: Intent) {
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
// TODO: process intent
}
}
With the reader mode API, you register your activity for receiving NFC callbacks (no intents are used here!) during onStart()
:
@Override
public void onStart() {
super.onStart();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.enableReaderMode(this, new NfcAdapter.ReaderCallback() {
@Override
public void onTagDiscovered(Tag tag) {
// TODO: use NFC tag
}
}, NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B | NfcAdapter.FLAG_READER_NFC_F | NfcAdapter.FLAG_READER_NFC_V | NfcAdapter.FLAG_READER_NFC_BARCODE, null);
}
Kotlin:
fun onStart() {
super.onStart()
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
nfcAdapter.enableReaderMode(this, object : NfcAdapter.ReaderCallback() {
fun onTagDiscovered(tag: Tag) {
// TODO: use NFC tag
}
}, NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_NFC_B or NfcAdapter.FLAG_READER_NFC_F or NfcAdapter.FLAG_READER_NFC_V or NfcAdapter.FLAG_READER_NFC_BARCODE, null)
}
You also should make sure to unregister during onStop()
:
@Override
public void onStop() {
super.onStop();
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
nfcAdapter.disableReaderMode(this);
}
Kotlin:
fun onStop() {
super.onStop()
val nfcAdapter = NfcAdapter.getDefaultAdapter(this)
nfcAdapter.disableReaderMode(this)
}
You receive discovered tag handles through the onTagDiscovered(Tag tag)
callback method above. Instead, you could, of course, also implement the NfcAdapter.ReaderCallback
interface on your activity class and pass this
instead of an anonymous class to the enableReaderMode
method.