Search code examples
androidandroid-intentkotlintagsnfc

Display toast message when NFC tag is detected


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.


Solution

  • 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:

    • If you are on Android 4.4+ and if you are only interested in detecting other tags (and no peer-to-peer mode devices). I would strongly suggest that you use the reader mode API since it gives you much better control over what tags you want to detect and how these tags should be processed. Also, if you want to be able to detect and speak to a HCE application on another Android device, your only option is the reader mode API.
    • If you also want to support devices before Android 4.4 or if you also want to receive data from peer-to-peer devices (e.g. over Android Beam), you will need to stick to the foreground dispatch system.

    Foreground Dispatch System

    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
        }
    }
    

    Reader Mode API

    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.