Search code examples
javaandroidnfcoverwritendef

How to write values to NFC tag from two activities without overwriting existing values?


I am trying to write an Android application that has two activities which write multiple values (Strings/text) to the same NFC tag. I have managed to make one activity to write values to a tag, but how do I prevent the second activity from overwriting the values of the first activity?

public class MainActivity extends AppCompatActivity {

NfcAdapter nfcAdapter;
ToggleButton tglReadWrite;
Button anotherActivtybtn;
EditText txtName;
EditText txtCountry;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    nfcAdapter = NfcAdapter.getDefaultAdapter(this);
    tglReadWrite = (ToggleButton)findViewById(R.id.tglReadWrite);
    txtName = (EditText)findViewById(R.id.idName);
    txtCountry= (EditText)findViewById(R.id.idCountry);
    anotherActivtybtn= (Button)findViewById(R.id.anotherActivtybtn);

    anotherActivtybtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent= new Intent(MainActivity.this, AnotherActivity.class);
            startActivity(intent);
        }
    });
}

@Override
protected void onResume() {
    super.onResume();
    if(nfcAdapter !=null){

        /* go to phone's nfc settings */
        if(!nfcAdapter.isEnabled()){
            showNfcSettings();
        }
        enableForegroundDispatchSystem();
    }

}

private void showNfcSettings() {
    Intent nfcSettingIntent = new Intent(Settings.ACTION_NFC_SETTINGS);
    startActivity(nfcSettingIntent);
}

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

    disableForegroundDispatchSystem();
}

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);

    if (intent.hasExtra(NfcAdapter.EXTRA_TAG)) {
        Toast.makeText(this, "NfcIntent!", Toast.LENGTH_SHORT).show();

        if(tglReadWrite.isChecked())
        {
            Parcelable[] parcelables = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

            if(parcelables != null && parcelables.length > 0)
            {
                readTextFromMessage((NdefMessage) parcelables[0]);
            }else{
                Toast.makeText(this, "No NDEF messages found!", Toast.LENGTH_SHORT).show();
            }

        }else{
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            NdefMessage ndefMessage = createNdefMessage(txtName.getText().toString(), txtCountry.getText().toString());

            writeNdefMessage(tag, ndefMessage);
        }

    }
}

private void readTextFromMessage(NdefMessage ndefMessage) {
    NdefRecord[] ndefRecords = ndefMessage.getRecords();

    if(ndefRecords != null && ndefRecords.length>0){

        NdefRecord ndefRecord = ndefRecords[0];
        NdefRecord ndefRecord2 = ndefRecords[1];

        String tagContent = getTextFromNdefRecord(ndefRecord);
        String tagContent2 = getTextFromNdefRecord(ndefRecord2);

        txtName.setText(tagContent);
        txtCountry.setText(tagContent2);

    }else
    {
        Toast.makeText(this, "No NDEF records found!", Toast.LENGTH_SHORT).show();
    }

}

private void enableForegroundDispatchSystem() {
    Intent intent = new Intent(this, MainActivity.class).addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);

    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

    IntentFilter[] intentFilters = new IntentFilter[]{};

    nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null);
}

private void disableForegroundDispatchSystem() {
    nfcAdapter.disableForegroundDispatch(this);
}

private void formatTag(Tag tag, NdefMessage ndefMessage) {
    try {
        NdefFormatable ndefFormatable = NdefFormatable.get(tag);

        if (ndefFormatable == null) {
            Toast.makeText(this, "Tag is not ndef formatable!", Toast.LENGTH_SHORT).show();
        }

        else{
            ndefFormatable.connect();
            ndefFormatable.format(ndefMessage);
            ndefFormatable.close();

            Toast.makeText(this, "Tag writen!", Toast.LENGTH_SHORT).show();
        }
    } catch (Exception e) {
        Log.e("formatTag", e.getMessage());
    }
}

private void writeNdefMessage(Tag tag, NdefMessage ndefMessage) {
    try {

        if (tag == null) {
            Toast.makeText(this, "Tag object cannot be null", Toast.LENGTH_SHORT).show();
            return;
        }

        Ndef ndef = Ndef.get(tag);

        if (ndef == null) {
            // format tag with the ndef format and writes the message.
            formatTag(tag, ndefMessage);
        } else {
            ndef.connect();
            if (!ndef.isWritable()) {
                Toast.makeText(this, "Tag is not writable!", Toast.LENGTH_SHORT).show();

                ndef.close();
                return;
            }

            ndef.writeNdefMessage(ndefMessage);
            ndef.close();

            Toast.makeText(this, "Tag writen!", Toast.LENGTH_SHORT).show();

        }

    } catch (Exception e) {
        Log.e("writeNdefMessage", e.getMessage());
    }

}

private NdefRecord createTextRecord(String content) {
    try {
        byte[] language;
        language = Locale.getDefault().getLanguage().getBytes("UTF-8");

        final byte[] text = content.getBytes("UTF-8");
        final int languageSize = language.length;
        final int textLength = text.length;
        final ByteArrayOutputStream payload = new ByteArrayOutputStream(1 + languageSize + textLength);

        payload.write((byte) (languageSize & 0x1F));
        payload.write(language, 0, languageSize);
        payload.write(text, 0, textLength);

        return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payload.toByteArray());

    } catch (UnsupportedEncodingException e) {
        Log.e("createTextRecord", e.getMessage());
    }
    return null;
}

private NdefMessage createNdefMessage(String content, String content2) {

    NdefRecord ndefRecord = createTextRecord(content);
    NdefRecord ndefRecord2 = createTextRecord(content2);

    NdefMessage ndefMessage = new NdefMessage(new NdefRecord[]{ndefRecord, ndefRecord2});

    return ndefMessage;
}

public void tglReadWriteOnClick(View view){
    txtName.setText("");
    txtCountry.setText("");
}

public String getTextFromNdefRecord(NdefRecord ndefRecord)
{
    String tagContent = null;
    try {
        byte[] payload = ndefRecord.getPayload();
        String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
        int languageSize = payload[0] & 0063;
        tagContent = new String(payload, languageSize + 1,
                payload.length - languageSize - 1, textEncoding);
    } catch (UnsupportedEncodingException e) {
        Log.e("getTextFromNdefRecord", e.getMessage(), e);
    }
    return tagContent;
}
}

Is there a way to do that?


Solution

  • An NFC Forum type tag can only contain one NDEF message. Consequently, the method ndef.writeNdefMessage(ndefMessage) will always overwrite any existing NDEF message with the new message passed as the argument ndefMessage.

    That NDEF message may, of course, consist of as many NDEF records as you like (or actually as fit on your tag).

    If you would like to preserve exisiting data, you would therefore need to read the existing NDEF message first, e.g. by

    Parcelable[] parcelables = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
    if (parcelables != null && parcelables.length > 0) {
        NdefMessage ndefMessage = (NdefMessage)parcelables[0];
        ...
    

    You could then parse this message and keep/drop the parts (NDEF records or data elements) that you would like to preserve/remove. Finally, you would add your new parts to that message and write the whole message to the tag again.