6

I am writing an app that uses and externally connected USB barcode/RFID scanner. The data being scanned in, is "compound" data. Here is an example:

=+03000=W12560712345600=%2800&>0090452359

That is from a compound data scan. The delimiter in the data is the equals sign (=) or the ampersand (&). The first bit =+03000 says that there are three data parts in the scan:

=W12560712345600

=%2800

&>0090452359

This data can have any number of data parts from one to N.

In my Android app, I have a form with three EditText elements. What I need to do with this compound scanned data is to break it up using the delimiters, and stick each piece of data into the proper EditText field.

I have, after much pain, been made aware of the fact that I cannot just manipulate stdin, and that I need to use a TextWatcher on one of my EditText fields to capture the scanned data so I can manipulate it.

My issue is that I cannot figure out how to do this. Here is what I have:

activity_main.xml

<LinearLayout>
    <TextView />
    <EditText android:id="@+id/datafield01" />
    <EditText android:id="@+id/datafield02" />
    <EditText android:id="@+id/datafield03" />
    <Button />
</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        EditText dataField01 = (EditText)findViewById(R.id.datafield01);
        EditText dataField02 = (EditText)findViewById(R.id.datafield02);
        EditText dataField02 = (EditText)findViewById(R.id.datafield03);

        dataField01.addTextChangedListener(editTextWatcher);
    }

    TextWatcher editTextWatcher = new TextWatcher(){
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after){
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count){
        }

        @Override
        public void afterTextChanged(CharSequence s){
        }
    };
}

I have tried capturing the CharSequence into a StringBuffer - using before, on, and afterTextChanged - so I can manipulate it and place it into the correct EditText element, but I have been unsuccessful.

Modified MainActivity.java

public class MainActivity extends AppCompatActivity {
    private StringBuffer stringBuffer;
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        stringBuffer = new StringBuffer();

        EditText dataField01 = (EditText)findViewById(R.id.datafield01);
        EditText dataField02 = (EditText)findViewById(R.id.datafield02);
        EditText dataField02 = (EditText)findViewById(R.id.datafield03);
        dataField01.addTextChangedListener(editTextWatcher);

        System.out.println(stringBuffer.toString());
    }

    TextWatcher editTextWatcher = new TextWatcher(){
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after){
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count){
            stringBuffer.append(s);
        }

        @Override
        public void afterTextChanged(CharSequence s){
        }
    };
}

Whenever I try to System.out.println(stringbuffer); there is nothing there.

The scanned code does not give a line break or any other kind of delimiter that it has come to an end, and the number of data parts can be one to N, but each data part does have a known, fixed length.

There has got to be other people doing something like this, but my Google searches have been fruitless.

So, is there any way to accomplish what I am trying to do, or am I completely out of luck?

Thanks.

Brian
  • 1,726
  • 2
  • 24
  • 62

3 Answers3

0

I'm sure that there is a way to do this with some TextWatcher implementations; switching focus when you see the delimiter char, etc.

But I think it's easier to handle when you know you have all the input from the barcode scanner. Then you can simply parse the data and put it in the proper places.

So the trick is to figure out when you have all the scanner input so you can parse it.

Here's some code using a trick I learned from looking at Android's Filter class for adapters. There is a delay mechanism to filter only once for multiple rapid keypresses. (Sadly, it's Google private so us mere mortals aren't supposed to use this helpful feature.)

Since your scanner is apparently acting like a keyboard, a pause in the keypresses should mean that the scanner has finished its input. Let's use that to determine when the scan is complete.

First, we'll define a callback interface just because I'm an IoC kind of guy:

    interface ScanProcessor {

       void process(CharSequence input);
    }

Now we'll define a Handler class. This handler is going to run on the main (UI) thread so we can't be too busy in the processor.

    public static class InputDelayHandler extends Handler {

       private static final int INPUT_MESSAGE = 0x20170308;

       private static final int PROCESS_MESSAGE = 0x20170309;

       // You'll have to tweak this value to ensure all input is entered, 
       // while the app remains responsive to the scan
       private static final long DELAY_MS = 250;

       private CharSequence mInput;

       private ScanProcessor mScanProcessor;

       InputDelayHandler(ScanProcessor scanProcessor) {
           super();  // associate the handler with the Looper for the current thread
           mScanProcessor = scanProcessor;
       }

       void setInput(CharSequence input) {

           // removing this message is the key to how this works
           // since it's delayed, we can cancel before input is complete.
           removeMessages(PROCESS_MESSAGE);
           removeMessages(INPUT_MESSAGE);
           Message inputMessage = obtainMessage(INPUT_MESSAGE);
           inputMessage.obj = input;
           sendMessage(inputMessage);
           Message startMessage = obtainMessage(PROCESS_MESSAGE);
           sendMessageDelayed(startMessage, DELAY_MS);
       }

       @Override
       public void handleMessage(Message msg) {

           switch (msg.what) {
           case INPUT_MESSAGE:
               // remember the last input
               mInput = (CharSequence) msg.obj;
               break;
           case PROCESS_MESSAGE:
               // callback
               if (/*mInput looks like valid scan data*/) {
                   mScanProcessor.process(mInput);
               }
               break;
           }
       }
   }

Your class that has the EditText fields should keep a reference to the handler.

        private InputDelayHandler mHandler;

Now your TextWatcher just has to tell the handler that something happened:

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

            mHandler.setInput(s);
        }

And this hooks it all together:

            mHandler = new InputDelayHandler(new ScanProcessor() {

                @Override void process(CharSequence input) {

                    // parse the input, set the EditText fields, etc.
                }
            });
kris larson
  • 30,387
  • 5
  • 62
  • 74
0

Firstly, you should set the TextWatcher onto all 3 fields, that way it's unimportant which field has focus. Then concentrate only on afterTextChanged

private String[] mCompound;

public void afterTextChanged(CharSequence s) {
    //first check and make sure we only try to split up compound data
    //and don't loop forever each time we set data back to ourselves
    if (s.contains("=")) {

        //create an array of the compound data parts
        //by splitting every time we find = or &
        mCompound = s.split("(\\=)|(&)");
        //Your example =+03000=W12560712345600=%2800&>0090452359 is now
        //mCompound = { "+03000", "W12560712345600", "%2800", ">0090452359" }     

        //Clear/reset all our fields first
        dataField01.setText(null);
        dataField02.setText(null);
        dataField03.setText(null);

        //We check if >1 because we know mCompound[0] is always the count
        if (mCompound.length > 1) {
            //we wrap with string.valueof() because the data might just be a number
            dataField01.setText( String.valueOf(mCompound[1]) );
            if (mCompound.length > 2) {
                dataField02.setText( String.valueOf(mCompound[2]) );
                if (mCompound.length > 3) {
                    dataField02.setText( String.valueOf(mCompound[3]) );
                }
            }
        } 

    }
}
Nick Cardoso
  • 20,807
  • 14
  • 73
  • 124
0

What you really want to do here consists of several parts:

  1. Read the data from the rfid:

The proper way to implement this is first, to add the right permissions:

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

Then, to add your intent, which 'knows' what to do with the scanned data:

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <data android:mimeType="text/plain" />
</intent-filter>

Then, you want to add the NFC technologies your app supports, in a separate res xml file:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.IsoDep</tech>
        <tech>android.nfc.tech.NfcA</tech>
        <tech>android.nfc.tech.NfcB</tech>
        <tech>android.nfc.tech.NfcF</tech>
        <tech>android.nfc.tech.NfcV</tech>
        <tech>android.nfc.tech.Ndef</tech>
        <tech>android.nfc.tech.NdefFormatable</tech>
        <tech>android.nfc.tech.MifareClassic</tech>
        <tech>android.nfc.tech.MifareUltralight</tech>
    </tech-list>
</resources>

Finally, bind your activity to the intent, in the AndroidManifest.xml file:

<activity>
...
<intent-filter>
    <action android:name="android.nfc.action.TECH_DISCOVERED"/>
</intent-filter>

<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
    android:resource="@xml/nfc_tech_filter" />
...
</activity>
  1. Upon data received, you want to break the message into 3 parts, as you indicated, which are divided by a '=' sign or a '&' sign:

    Parcelable[] rawMsgs = intent.getParcelableArrayExtra( NfcAdapter.EXTRA_NDEF_MESSAGES);

    NdefMessage msg = (NdefMessage) rawMsgs[0]; String st = new String(msg.getRecords()[0].getPayload()); st. String[]tokens = st.split("=|&");

When finished, tokens array should hold your messages.

https://developer.android.com/guide/topics/connectivity/nfc/nfc.html#tag-dispatch

Daniel
  • 935
  • 2
  • 6
  • 11