0

I'm creating an application in which I have to write a series of values that come to me from a file to an NFC card and I've been reading and I don't know where to really start, I have a few doubts.

First of all I understand that the ideal is to create a class that handles the NFC, although I suppose this is optional and can be done in the same class. The problem is that the tutorials I see are only using activities and make use of the onNewIntent method.

Being in a fragment I can not call this method so it is the first step in which I lose, and I do not know if this method is necessary because as I understand this is to launch the application even if it is closed as if it were a reader, correct me if I'm wrong. I appreciate if you can guide me a little in what I should do because after so much reading I've gone a little crazy.

Ludiras
  • 338
  • 3
  • 20

1 Answers1

0

The first place I would start is thinking about how to store the data.

Is the data custom to your application or do you want to share it with other App's?

Will the App be just writing the data once or will it be updating it (wanting to Append Data to existing data stored on the card?

Update: from your comments about the data type you are probably better using the higher level NDEF format to store your Data using a custom mime type. This is assuming your chosen card type supports this. Note the example I gave is reading/writing using low level commands read and writing page by page.

How many bytes of data do you want to store (Influences the card technology)

You probably want to also think about what NFC card technology you want to use, probably a good choice of one of the NTAG 21x series of cards.

What is the minimum version of Android you want to target?

I would not use the newIntent method as this is very unreliable for writing data, I would use the enableReaderMode if you are targeting a high enough version of Android.

https://developer.android.com/reference/android/nfc/NfcAdapter.html#enableReaderMode(android.app.Activity,%20android.nfc.NfcAdapter.ReaderCallback,%20int,%20android.os.Bundle)

Some of the answers to what you need to think about will affect some of the details of the example.

Update: based on comments Even though you are using Fragments I would still put the mechanics of NFC handling in the Activity.

The reason for this is because the OS is still handling tag discovery, if you don't "claim" the NFC hardware in every Fragment then it is possible especially with the NDEF data format, for the OS to display a screen over your App if the user present a card at the wrong time, giving a bad user experience.

In my multi Activity App I "claim" the NFC hardware in every Activity even if a lot of them do "On Tag discovered, do nothing" because they are not the NFC Activity.

So unless you want to write the same code in every Fragment, it would be much better to call the NFC stuff from your one Activity and then in onTagDiscovered does something like (pseudo code):-

Updated:

if displaying the NFC user prompt Fragment.
get data to file.
write data to the card.
Notify user that it is done.
else
do nothing when other fragments are displayed.

or you could have card writing at any time the App is open (again best done in activity not in any fragment)

If card is presented no matter what fragment is being display
get data from the file
write data to the card
Notify user that it is done.

Sorry I cannot do an example in Kotlin but here is a barebone of a Java Example, extracted from my App (not tested, so there could be copy and pasted errors)


public class MainActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback{

private NfcAdapter mNfcAdapter;

@Override
    protected void onCreate(Bundle savedInstanceState) {
    // All normal onCreate Stuff

    // Listen to NFC setting changes
    this.registerReceiver(mReceiver, filter);
    }

    // Listen for NFC being turned on while in the App
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();

            if (action.equals(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)) {
                final int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
                        NfcAdapter.STATE_OFF);
                switch (state) {
                    case NfcAdapter.STATE_OFF:
                    // Tell the user to turn NFC on if App requires it
                        break;
                    case NfcAdapter.STATE_TURNING_OFF:
                        break;
                    case NfcAdapter.STATE_ON:
                        enableNfc();
                        break;
                    case NfcAdapter.STATE_TURNING_ON:
                        break;
                }
            }
        }
    };

    @Override
    protected void onResume() {
        super.onResume();

        enableNfc();

    }

    @Override
    protected void onPause() {
        super.onPause();
        if(mNfcAdapter!= null)
            mNfcAdapter.disableReaderMode(this);
    }




    private void enableNfc(){
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);

        if(mNfcAdapter!= null && mNfcAdapter.isEnabled()) {

            // Work around some buggy hardware that checks for cards too fast
            Bundle options = new Bundle();
            options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 1000);


            // Listen for all types of card when this App is in the foreground
            // Turn platform sounds off as they misdirect users when writing to the card
            // Turn of the platform decoding any NDEF data
            mNfcAdapter.enableReaderMode(this,
                    this,
                    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 |
                            NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK |
                            NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
                    options);
        } else {
            // Tell the user to turn NFC on if App requires it
        }
    }

    public void onTagDiscovered(Tag tag) {

        // This is run in a separate Thread to UI

        StringBuilder Uid = new StringBuilder();

        boolean successUid = getUID(tag, Uid);
        if (!successUid){
            // Not a successful read
            return;
        } else {
            // Feedback to user about successful read

            Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
            v.vibrate(500);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // Update the UI / notify user  
                }
            });
            // Finish Task
            try {
                Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
                Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
                r.play();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public boolean getUID(Tag tag, StringBuilder Uid){
        NfcA mNfcA = NfcA.get(tag);

        if (mNfcA != null) {
            // The tag is NfcA capable
            try {
                mNfcA.connect();
                // Do a Read operation at page 0 an 1
                byte[] result = mNfcA.transceive(new byte[] {
                        (byte)0x3A,  // FAST_READ
                        (byte)(0 & 0x0ff),
                        (byte)(1 & 0x0ff),
                });

                if (result == null) {
                    // either communication to the tag was lost or a NACK was received
                    // Log and return
                    return false;
                } else if ((result.length == 1) && ((result[0] & 0x00A) != 0x00A)) {
                    // NACK response according to Digital Protocol/T2TOP
                    // Log and return
                    return false;
                } else {
                    // success: response contains ACK or actual data
                    for (int i = 0; i < result.length; i++) {
                        // byte 4 is a check byte
                        if (i == 3) continue;
                        Uid.append(String.format("%02X ", result[i]));
                    }

                    // Close and return
                    try {
                        mNfcA.close();
                    } catch (IOException e) {
                    }
                    return true;
                }

            } catch (TagLostException e) {
                // Log and return
                return false;
            } catch (IOException e){
                // Log and return
                return false;
            } finally {
                try {
                    mNfcA.close();
                } catch (IOException e) {
                }
            }
        } else {
            // Log error
            return false;
        }
    }

}

Andrew
  • 8,198
  • 2
  • 15
  • 35
  • They are cards that are going to be recorded with basic data such as date and time and the rest are whole and strings. After those same cards are recorded they will be used again to be recorded with the same fields but different values. The example you gave me is an activity but I'm using Fragment, what changes? – Ludiras Dec 18 '19 at 17:58
  • In a Fragment is a bit more complicated, it depends a bit on how the fragment is being used (shown), but A fragment can have the same lifecycle methods as an activity and the Callback and actual reading code and be anywhere (I have mine in a separate class) – Andrew Dec 18 '19 at 18:11
  • Mmmm okay, tomorrow I'll try to take a look at it and see if I can get it. – Ludiras Dec 18 '19 at 18:25
  • If you give us an idea how you are using a fragment (Is it the only fragment in your activity or is it displayed as part of a viewpager view) then it might help me adjust the example to match your needs – Andrew Dec 18 '19 at 20:10
  • In my application I only have a BaseActivity that extends a BaseFragment and all fragments extend from that BaseFragment. The navigation is done through Navigation Component but everything is fragment, there is only one activity in the whole application. – Ludiras Dec 18 '19 at 20:33
  • Is the data the been written to the card coming from one of the fragments? Because even with `enableReaderMode` the OS is still handling tag discover and if you use NDEF format even with a custom mime type if your App has not claimed the NFC hardware then the OS will display a screen over your app if the user presents the card at the wrong time (e.g. when in Fragment that has not claimed NFC). Thus I would actually still put a lot of the code in the Activity and then `onTagDiscovered` only does stuff when it has the right data from the Fragment. – Andrew Dec 18 '19 at 20:54
  • The data written to the card comes from a local file (such as SharedPreferences) in which it has previously been saved, and to save it in NFC it is retrieved from that same file. – Ludiras Dec 18 '19 at 21:10
  • Ok, data from a file make it easier to read/write from Activity as I think that is the best place to handle it even when using fragments, but you still need a Fragment to prompt the user to present the card and provide feedback when written. You could handle it without visual prompts. But would really need to a flow diagram of how you want card reading/writing to happen to best advise but most of the card handling is best done in the Activity in all cases anyway only possibly some user interaction in a Fragment. – Andrew Dec 18 '19 at 22:41
  • @Andrew this works but how to read the complete data?. How to know maximum pages to be read and how to extract the payload?. Is there any documentation around it. – Gautam Aug 22 '23 at 17:24