Search code examples
javaandroidnfc

Is there any way to stop my Android App from scanning nfc tags in the background?


So let's say I wanna make an app that scans an nfc tag when the user taps the "scan tag" button.

Currently I am having a few issues with it even though I spent the last week trying to figure it out on my own😅

The main issue that I am facing is that the app will scan nfc tags in the background (or at least looks like it) and will open a new instance of my app but with the name of "Nfc Service" (which is not what I named my app).

The other issue that I am facing is that, in the code, when I try to call my "readTag()" mehtod, it only works if it opens that "Nfc Service" thing I said before, otherwise it doesn't read the tag, but only breaks. From my understanding it looks like the readTag() method is running in the background rather than when I tell it to.

TL;DR: I don't want my app to scan nfc tags in the background but only on a button press.

Help would be massively appreciated especially because my deadline it tomorrow 😅 T-T

Here is some of my code (I can't show everything unfortunately):

AndroidManifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="ips.software.iasset_ips">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.NFC" />
    <uses-feature android:name="android.hardware.nfc" android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.IAssetIPS">
        <activity android:name=".WebViews.AssetInfoWebView"></activity>
        <activity android:name=".Activities.MainActivity">
            <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" />

                <category android:name="android.intent.category.LAUNCHER" />
                <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>
    </application>

</manifest>

xml/nfc_tech_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.NfcA</tech>
    </tech-list>
</resources>

MainActivity: (just the parts relevant to the question)

// package + all the imports
// ...

public class MainActivity extends AppCompatActivity {

    private Button scanButton;

    NfcAdapter adapter;
    Tag tag;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        configureScanButton();
    }

    private void configureScanButton() {
        scanButton= findViewById(R.id.scanButton);
        scanButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                readTag();
             }
        });
    }

    public void readTag() {
        adapter = NfcAdapter.getDefaultAdapter(this); // initialize the nfc reader
        checkNfcSupport(); // check if device supports nfc, if not close and Toast.show
        tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG); // read the tag
        decodeSerialNumberFromTag(); // can't show you this function, but it's only decoding, it shouldn't be the cause of the problem
        Toast.makeText(this, "Serial Number: " + serialNumber, Toast.LENGTH_LONG).show();
    }
}

So yeah i think that's all, it would also be nice if I could call the readTag() method whenever I want to scan a new tag.

I talk too much, thank you in advance :D


Solution

  • The only way to start the interaction of you App to an NFC Tag is via the NFC Service, but if you program it properly it is possible to achieve the your desired behaviour.

    First off, the manifest Intent filter are really only about telling the NFC Service to start/restart your App when a Tag of the right type comes in to range.
    There is an interaction with the "launchMode" of your App as detailed in https://stackoverflow.com/a/64834600/2373819

    The way to handle an NFC tag when your App is running is to use enableForegroundDispatch or use the better enableReaderMode APIs which tell the NFC Service to pass you the Tag object in different ways when a Tag comes in to range.

    An example of the better enableReaderMode API for reading and writing is at https://stackoverflow.com/a/64921434/2373819 (You can ignore the writing bit)

    It is not the normal workflow to do something with a Tag only when you press a button as a Tag might or might not be in range, what you are actually doing in your current readTag method when you press the button is just processing the Intent that was stored when the activity was started no actually I/O for reading is being done to the Tag. The Tag UID and any NDEF message stored on the Tag will have been cached in the Tag Object/Intent when it came in to range.

    To do the similar with enableReaderMode in the onTagDiscovered method you would store the Tag Object is a global variable in the Activity class and then process it when you press the button with checking the Tag object is not null i.e. user has pressed the button with not Tag has every been in range.

    If want to read other non cached data when you press the button you need to handle Tag out or range errors and other I/O errors as you would if your were processing the Tag immediately when it came in to range, just you are more likely to have them.

    I would say it is always good practise when using NFC in your App to enableForegroundDispatch or use the better enableReaderMode to as the NFC service to send you the Tag Object even if you don't want to do anything with it at that point in time because it will prevent the NFC service launching another App to handle it or even itself displaying a basic information screen over the top of your App about the Tag that came in to range. e.g. have an empty method in onNewIntent or onTagDiscovered