1
votes

My Android app at the moment consumes a URL for a Google spreadsheet through pasting from the clipboard, reading a QR code, or reading from NFC. I'm having trouble writing to an NFC tag and I get this error:

[ERROR:nfa_rw_act.cc(1571)] Unable to write NDEF. Tag maxsize=137, request write size=171

I cannot write to this tag because the payload I'm trying to write it is larger than the writable space on it.

All I'm trying to do is write the URL I've already read (from clipboard or QR) to an NFC tag, and also add my app record so it launches the Play Store to my app in case the user doesn't have it installed already. Unfortunately, it seems this is too much data. I thought about maybe only including the spreadsheetID value, but I think this will add complications in the future when I inevitably want to add support for spreadsheets outside of Google Sheets.

Here's how I'm writing it currently:

public NdefMessage createNdefMessage() {
    String text = "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit?usp=sharing";

    NdefRecord appRecord = NdefRecord.createApplicationRecord(context.getPackageName());
    NdefRecord relayRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, new String("application/" + context.getPackageName()).getBytes(Charset.forName("US-ASCII")), null, text.getBytes());

    return new NdefMessage(new NdefRecord[] {relayRecord, appRecord});
}

Is writing just the ID ("1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms" in this case) my only option? I'm also assuming the NFC tag I'm using is an average size and not a super tiny one for 2018.

EDIT:

Thanks to Michael I was able to get it to fit, though barely (134/137 bytes). I write the URI to NFC via this:

NdefRecord relayRecord = NdefRecord.createUri(text);

I added this intent filter to catch it:

<intent-filter>
            <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <data android:scheme="https" android:host="docs.google.com"/>
        </intent-filter>

And I read the NFC tag with this:

NdefMessage[] messages = new NdefMessage[rawMessages.length];

        for (int i = 0; i < rawMessages.length; i++) {
            messages[i] = (NdefMessage) rawMessages[i];
        }

        for (NdefRecord r : messages[0].getRecords()) {
            if (r.getTnf() == NdefRecord.TNF_WELL_KNOWN) {
                byte[] payload = r.getPayload();
                try {
                    String payloadText = new String(payload, 1, payload.length - 1, "UTF-8");
                    int firstByte = payload[0];
                    return getUriPrefix(firstByte) + payloadText;
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    return "Read error";
                }
            }
        }

And the first byte from the URI compression I get via this, even though I only ever assign "04" (4 when I read it as int) in my app:

private String getUriPrefix(int firstByte) {
    if (firstByte == 0) {
        return "";
    } else if (firstByte == 1) {
        return "http://www.";
    } else if (firstByte == 2) {
        return "https://www.";
    } else if (firstByte == 3) {
        return "http://";
    } else if (firstByte == 4) {
        return "https://";
    } else {
        return "";
    }
}
1

1 Answers

1
votes

The error message that you got in the log is pretty clear. Your NDEF message is too large to fit the data area of the tag. The obvious solution would be to use NFC tags with a sufficiently large storage capacity -- there's quite a few larger tags available. There's nothing else you could do about it if you want to store exactly that NDEF message.

However, there is ways to improve the NDEF message itself. You currently use a MIME type record to store a URL. That's definitely not the best choice of a record type to store a URL. (or actually any application-specific data). The MIME type that you chose costs ("application/" + context.getPackageName()).length() = 31 bytes (assuming your package name is 19 bytes).

  • If you used an NFC Forum external type record instead, you could create a much shorter type name of the form "mydomain.tld:appurl", so this would save quite a few bytes.

    relayRecord = NdefRecord.createExternal("mydomain.tld", "appurl", text.getBytes());
    
  • Since you want to store a URL, there's even a more efficient (and readily available) record type: the NFC Forum URI well-known type. The type name of that record consists only of the single letter "U", so this would already save 30 bytes compared to your MIME type record. Moreover, the URI record type uses a compression scheme to further reduce the size of the record: there is a list of well-known URI prefixes (e.g. "https://") that can be represented as a single byte in the URI record (the method NdefRecord.createUri() will automatically take care of that compression). Consequently, you would save another 7 bytes.

    relayRecord = NdefRecord.createUri(text);