Odk-x use intent to scan barcode in html page

Hello everyone,

I have HTML page like Geotagger where I can Search in the list using input text,
But I want to make a button where I can use the zxing barcode.
so I can easily find what I want from the list.
how can I make Intent in the javascript?
can anybody help me?

Thanks

You can use the odkcommon.js injected interface to launch an intent using the odkCommon.doAction function.

Brief example:

    $('#barcode').on('click', function() {
        var dispatchStruct = JSON.stringify({actionTypeKey: actionBarcode,
            htmlPath:htmlFileNameValue, userAction:userActionValue});

        odkCommon.doAction(dispatchStruct, 'com.google.zxing.client.android.SCAN', null);
    });

hello @W_Brunette
I did use the odkcommon.js to launch an intent but I can not receive data back .

window.addEventListener(“load”, function () {
odkCommon.registerListener(
function () {
alert(“inside the listener”);
var action = odkCommon.viewFirstQueuedAction();
$(“#searchForText”).val(“inside the listener”);
if (action !== null) {
console.debug(action);
$(“#searchForText”).val(JSON.stringify(action));
var dispatchStruct = action.dispatchStruct;
if (dispatchStruct && dispatchStruct.userAction == “scan-kast-code”) {
var jsonValue = action.jsonValue;
$(“#searchForText”).val(“status = " + jsonValue.status);
if (jsonValue.status === 0) {
$(”#searchForText").val(jsonValue.result);
console.log(jsonValue);
}
}
// process action – be idempotent!
// if processing fails, the action will still
// be on the queue.
odkCommon.removeFirstQueuedAction();
}
});
$(“#barcode”).click(function () {
var dispatchStruct = { userAction: “scan-kast-code” };
var intentArgs = {
// extras: extrasBundle,
// uri: // set the data field of intent to this
// data: // unless data is supplied – that takes precedence
// type: // set the intent type to this value
// package: // set the intent package to this value
};

    return odkCommon.doAction(dispatchStruct,
        'com.google.zxing.client.android.SCAN',
        intentArgs);
});

})

the registerListener doesn’t work or i don’t know how to use it.

Can you check with me, please.

There are a few issues with your listener:

  1. action.dispatchStruct is a JSON string, you need to parse it to read its content.
  2. action.jsonValue.status is expected to be -1
  3. The result of the scan is stored in action.jsonValue.result.SCAN_RESULT

If the issue is that the listener isn’t being called at all, try calling your listener right after registering it.

Hi @W_Brunette @linl33,

I am getting the same issue. This code worked correctly with old ODK, but it does not work with ODK-X as the listener never get called:

 let saveBarcodeFromScan = () => {
      let value = odkCommon.viewFirstQueuedAction();
      console.log("saveBarcodeFromScan: " + value);
      if ( value !== null && value !== undefined ) {
          let action = JSON.parse(value);
          let dispatch = JSON.parse(action.dispatchStruct);
          if (dispatch.userAction === 'barcodeScan' && action.jsonValue.status === -1) {
            let barcode = action.jsonValue.result.SCAN_RESULT;
            callback(barcode);
            odkCommon.removeFirstQueuedAction();
          }
      }
    };
odkCommon.registerListener(saveBarcodeFromScan);
odkCommon.doAction(JSON.stringify({ userAction: 'barcodeScan' }), 'com.google.zxing.client.android.SCAN', null);

Calling the listener right after registration also does not help:

odkCommon.registerListener(saveBarcodeFromScan);
saveBarcodeFromScan(); // immediately call the listener
odkCommon.doAction(JSON.stringify({ userAction: ‘barcodeScan’ }), ‘com.google.zxing.client.android.SCAN’, null);

This is a blocker for me as barcode scanner is one of my production key feature.

1 Like

Hello,
Are you just trying to get the value of the scan?
If so, I believe something like this can do it?

  1. Create a function to handle the call back and give a name to your userAction (in this example we’ll call it “thatsScanny”) like e.g.
function javaChange() {
    var action = odkCommon.viewFirstQueuedAction();
    if ( action !== null ) {
        console.log("call back called");
        if (action.dispatchStruct.userAction == "thatsScanny") {
            odkCommon.removeFirstQueuedAction();
            if (action.jsonValue.result && action.jsonValue.result.SCAN_RESULT) {
                var scanRes = action.jsonValue.result.SCAN_RESULT;
                // call you function to handle the code.. e.g doSearch()
                doSearch(scanRes); 
            }
        }
    }
}
  1. Hook it up in the pages initialization method,e.g.
function display() { // we will call this onLoad
    console.log("Setting up display");
    odkCommon.registerListener( javaChange );
    javaChange(); // invoke it once, just because...

   // Assuming you have a button with id = btnScan
    $("#btnScan").on("click", function() {
        var dispatchStruct = {userAction: 'thatsScanny'};
        var x = odkSurvey.scanBarcode(dispatchStruct);
        console.log(x);
    })

}

best regards,
/emil

1 Like

Thanks for your quick reply, @Emil. It seems that your suggestion is very similar to my code. I cannot see the difference and why my code does not work. The problem is the registered listener never get called after odkCommonIf.doAction() is finished.

1 Like

I think a main difference, is that I use the odkSurvey.scanBarcode() method instead of calling odkCommenIf.doAction()

odkSurvey.scanBarcode(dispatchStruct);

Also; I need to invoke it once on page load for some reason:

javaChange(); // invoke it once, just because...
1 Like

Hi Emil, could you point me to documentation of odkSurvey.scanBarcode() method.

1 Like

Hmmm… that’s indeed a very good question :laughing:
I looked at the source:

1 Like

Hi @Emil,

It does not work for me as odkSurvey.js in turn also calls OdkCommon.doAction().
I tried debugging ODK Table app with Android Studio and found why the listener never gets called. I will list my findings in another topic.

Anyway, I could make it work with a workaround. Thanks for following up.

1 Like

@Thien_Mai please let us know what you find so we can fix it.

1 Like

@W_Brunette, please correct me if I am wrong. As my understanding, once doAction() is finished, the Android app will call this method in ODKWebView to signal its client to call the registered listener:
ODKWebView.class:

public void signalQueuedActionAvailable() {
        this.log.i("ODKWebView", "[" + this.hashCode() + "] signalQueuedActionAvailable()");
        this.loadJavascriptUrl("javascript:window.odkCommon.signalQueuedActionAvailable()", true);
        this.shouldReloadAfterLoad = true;
    }

Let’s take a look at implementation of loadJavascriptUrl():

private synchronized void loadJavascriptUrl(final String javascriptUrl, boolean suppressIfFrameworkIsNotLoaded) {
        if (!this.isInactive()) {
            if (this.isLoadPageFrameworkFinished || !suppressIfFrameworkIsNotLoaded) {
                this.log.i("ODKWebView", "[" + this.hashCode() + "] loadJavascriptUrl: IMMEDIATE: " + javascriptUrl);
                if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
                    this.post(new Runnable() {
                        public void run() {
                            ODKWebView.this.loadUrl(javascriptUrl);
                        }
                    });
                } else {
                    this.loadUrl(javascriptUrl);
                }
            }
        }
    }

this.isLoadPageFrameworkFinished is controlling whether the WebView should sends signal to client.

The problem I have found is: Once the barcode scanner app is launched, the current view is paused which then set this.isLoadPageFrameworkFinished to false. However, it is never set back to true then (e.g after the barcode scanner app exits and the view is resumed)

public void onPause() {
        ...
        this.isLoadPageFrameworkFinished = false;
    }

That’s what I have observed through app debugging. Any thoughts?

2 Likes

That looks like a good finding! Perhaps that could also explain why I need to call javaChange(); once before scanning works?

1 Like

@Emil It does not work for me and I do not really understand why it works for you. As my understanding, when ODK Table cannot send signal for its client to fetch the result of doAction() (your javaChange() call), the result will then be retained in guardedQueuedActions list in ODK Table. Your next call of javaChange() will actually fetch that result and remove the result from the ODK. That means, nth call of listener will fetch result of (n-1)th doAction()?

1 Like

@Thien_Mai here is some code I pulled from one of our projects that calls the barcode scanner and returns the barcode inside Tables.

Hopefully this will help. Let us know if you have direct questions about the code snippet. I think I copied enough.

/* action added to button */
 $('#barcode').on('click', doActionZxing);

/* code */
var actionBarcode = 0;
var htmlFileNameValue = "delivery_start";
var userActionValue = "launchBarcode";

util.actionTypeKey = 'actionTypeKey'

function doActionZxing() {
    var dispatchStruct = JSON.stringify({
        [util.actionTypeKey]: actionBarcode,
        htmlPath: htmlFileNameValue,
        userAction: userActionValue
    });

    odkCommon.doAction(dispatchStruct, 'com.google.zxing.client.android.SCAN', null);
}


function callBackFn () {
    var action = odkCommon.viewFirstQueuedAction();
    console.log('callback entered with action: ' + action);

    if (action === null || action === undefined) {
        // The queue is empty
        return;
    }

    var dispatchStr = JSON.parse(action.dispatchStruct);
    if (dispatchStr === null || dispatchStr === undefined) {
        console.log('Error: missing dispatch struct');
        odkCommon.removeFirstQueuedAction();
        return;
    }

    var actionType = dispatchStr[util.actionTypeKey];
    console.log('callBackFn: actionType: ' + actionType);

    if (actionType === actionBarcode) {
        handleBarcodeCallback(action, dispatchStr);
    } else {
        handleCallback(action, dispatchStr);
    }

    odkCommon.removeFirstQueuedAction();
}
1 Like

Hi, this might be coming a bit late but I noticed you registered the listener and called it immediately even before calling the odkCommon.doAction. The goal is to register the listener on page load and call it immediately after registering it because when the button to scan is clicked, control leaves the app and it is later returned to the activity that launched the intent - reason why it is good to register the listener and call it at once on page load.

I ran into this post because I faced similar issues. :grinning:

2 Likes