As businesses move to mobile solutions for their logistics and inventory processing, the need for fast and straightforward barcode processing is rapidly growing. In order to meet this trend, we implemented the EPX Barcode Scanning Framework. EPX provides developers with an Android-centric framework for supporting, managing and utilizing a wide array of software and hardware barcode solutions with a single API that puts developers first.
In the world of mobile development, the requirement for cross-platform support for multiple operating systems is driving the implementation of web-based applications in order to support a ‘write-once’ deployment for blended install bases. In general, providing native hardware access for these web-based applications is ‘clunky’, at best, and adding barcode support has been a non-trivial process.
In March of this year, we were approached by a customer looking to push their web-based inventory management solution onto the new smart terminal from Poynt (https://poynt.com/). In this article, I will demonstrate how we met that customer’s need by implementing a simple method of wrapping a web application within an android WebView and providing a way for the WebView to collect barcodes through the EPX Barcode Scanning Framework.
Intro to EPX Barcode Scanning
(If you are interested in getting started right away with the EPX Framework, you can download the EPX Core from the Google Play store: https://play.google.com/store/apps/details?id=com.... and our current SDK from our website: http://developer.sdgsystems.com/sdk/epx/EPX-B-SDK-...)
Before we launch into a WebView, let’s take a look at how to add barcode scanning to an Android Activity. All you need to do is instantiate some connection instances to the background EPX Service using the ScanManager class and then provide callbacks for the detection of new scanners and incoming scan data:
// Declare the ScanManager and Scanner objects
ScanManager scanManager;
Scanner scanner;
EasyScanner easyScanner;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
// Link to the scan manager service and provide a callback for when new scanners are detected in the system
scanManager = new ScanManager(Activity.this);
scanManager.setCallback(new ScanManager.ApiCallback() {
public void onScannersAvailable(List<ScannerInfo> scanners) {
if (scanner != null) return;
for (ScannerInfo scannerInfo : scanners) {
try {
// Connect to the newly discovered scanner
scanner = scanManager.openScanner(scannerInfo);
scanner.setCallback(scannerCallback);
easyScanner = new EasyScanner(scanner);
} catch (ScanException e) {
showMessage("Could not open " + scannerInfo + ": " + e);
}
}
}
});
} catch (Exception e) {
showMessage("Fatal error in onCreate(): " + e);
}
}
protected void onDestroy() {
super.onDestroy();
if (scanManager != null) {
// Disconnect from the ScanManager to clean up resources
scanManager.disconnect();
}
}
void showMessage(String msg) {
Toast t = Toast.makeText(this, msg, Toast.LENGTH_LONG);
t.show();
}
// Define the callback for incoming scan data
ScannerCallback scannerCallback = new ScannerCallback() {
public void onScanStatus(ScanStatus ScanStatus) {
showMessage("Got scan data " + ScanStatus);
}
};
That’s pretty much it. The basic hook into the scanning service is the ‘EasyScanner’ object. In addition to catching hardware-triggered scan events, EPX provides a software trigger that you call with
easyScanner.startScan()
Once you get a ScanStatus object, you can pull interesting things like the symbology, the scan data or even an image of the barcode if you’re using a scanner that supports imaging.
WebView Interaction with a JavascriptInterface
Now that we’ve gone over the basics of scanning, let’s take a look at the Android mechanism for interacting with web applications from a native Android app.
This information is a summary of the info on the android developer pages regarding JavascriptInterface implementation here: http://developer.android.com/guide/webapps/webview....
When you want to include a web application as a part of your Android application, the standard approach is to use a WebView. A WebView is a subclass of the Android View object that you can include in a standard layout which will contain an instance of the Android web rendering engine. Adding a WebView in your layout is as simple as:
<WebView android:id="@+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
Once you have the webview defined, you can set its location with a url:
WebView myWebView = (WebView) findViewById(R.id.webview);
myWebView.loadUrl("http://www.example.com");
The WebView is fully operational for web page display, but it should be noted that standard web browser tools such as the address bar or navigation buttons are not provided by default and need to be implemented by the developer if they are needed.
Once you are ready to start using JavaScript in your WebView, you need to access the websettings object and enable it:
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
This is enough to get you going with displaying your web content but what about the interaction that we need for barcode scanning? In order to provide your WebView with the ability to call native methods in your application, you need to create a new interface class that you will hand off to the WebView to handle user interaction. For example:
public class WebAppInterface {
Context mContext;
/** Instantiate the interface and set the context */
WebAppInterface(Context c) {
mContext = c;
}
/** Show a toast from the web page */
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
This simple class will allow the web page to display a Toast message using your application context. In order to hand off this interface, you need to provide it as the Javascript interface for your WebView:
WebView webView = (WebView) findViewById(R.id.webview);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");
The second parameter ‘Android’ defines the name of the Javascript object that will be made available to the web page. Calling the new interface from your web application will be a method call against the new object that you created:
<script type="text/javascript">
function showAndroidToast(toast) {
Android.showToast(toast);
}
</script>
Don’t forget to check the Android developer page for the security caveats and concerns when using webview binding in this manner
In comparison to the JavascriptInterface, calling a javascript method from your java code is much simpler:
On your web application:
<script type="text/javascript">
function testEcho(valueToEcho) {
document.write(valueToEcho);
}
</script>
In your Android application:
myWebView.loadUrl("javascript:testEcho('Hello World!')");
While this is very simple, there are two gotchas. The first is that return types are not handled in this implementation. If your Android app needs to have a value come BACK, it would need to define yet another @JavascriptInterface method which the javascript would then call with any information that the Android application needed after the fact. The second gotcha is that the webview CALLBACK actually runs in a different thread than your UI which will break threading rules if you need to trigger a round trip call from the webview into your application and then back into your webview. In order to get around this, you need to make sure that you run your loadUrl calls a ‘runOnUiThread’ like this:
runOnUiThread(new Runnable() {
@Override
public void run() {
myWebView.loadUrl("javascript:testEcho(‘Hello World!’)");
}
});
Tying it all together and enabling barcode scanning from your web application
At this point, we now have all of the pieces that we need to tie your native Android application to your web application and provide barcode scanning support through the EPX framework.
I’m using the web page located at http://sdgsystems.net/ScanningWebview/ScanningWebv... as the basis for our example. Here is the source:
<html>
<body>
<script type="text/javascript">
// collect a barcode
function startScan() {
Android.startScan();
}
// Android will call this function if it finds a scanner
function scannerFound() {
showText("scanner Found");
}
// Android will call this function if it doesn’t find a scanner
function scannerMissing() {
showText("scanner Missing");
}
// Use this function to display some text at the bottom of the page
function showText(text) {
document.body.innerHTML += (text + "<br/>");
}
// This function is called by the native java application with the barcode results
function scanResults(barcodes) {
showText(barcodes);
}
</script>
<input type="button" value="Click this button to start a barcode scan" onClick="startScan()" />
<br/>
<br/>
</body>
</html>
Now for our Android application. Here is the simple layout XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.sdgsystems.webviewbarcodeexample.ScanningWebviewActivity">
<WebView
android:id="@+id/myWebView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
And here is our final activity tying our barcode scanning and JavascriptInterface together:
Note: This activity assumes that you have added the EPX API jar to your application and have the INTERNET permission in your AndroidManifest.xml
package com.sdgsystems.webviewbarcodeexample;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.sdgsystems.epx.scanning.api.ScanException;
import com.sdgsystems.epx.scanning.api.ScanManager;
import com.sdgsystems.epx.scanning.api.ScanStatus;
import com.sdgsystems.epx.scanning.api.ScannerInfo;
import java.util.List;
public class ScanningWebviewActivity extends AppCompatActivity implements WebviewCallback {
// This is going to be our WebView object
WebView myWebView;
// These are our hooks into the Scanning Service
ScanManager scanManager;
ScanManager.Scanner scanner;
ScanManager.EasyScanner easyScanner;
public ScanningWebviewActivity() {}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scanning_webview);
// Start off by configuring the Webview with our location and JS interface
configureWebView();
}
// Configure
private void configureWebView() {
myWebView = (WebView) findViewById(R.id.myWebView);
// We need to wait for the webview to finish loading before we configure the scanner
// or we won’t be able to make JavaScript calls
myWebView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView webview, String URL) {
configureScanner();
}
});
myWebView.loadUrl("http://sdgsystems.net/ScanningWebview/ScanningWebviewExample.html");
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
//Send this activity to the webappinterface instance as an instance of 'WebviewCallback'
myWebView.addJavascriptInterface(new WebAppInterface(this), "Android");
}
// Set up the connection to the scanner and the callbacks for incoming scan data
private void configureScanner() {
try {
// Link to the scan manager service and provide a callback for when new scanners are detected in the system
scanManager = new ScanManager(getApplicationContext());
scanManager.setCallback(new ScanManager.ApiCallback() {
public void onScannersAvailable(List<ScannerInfo> scanners) {
//If we already have a scanner connection, don't connect to another one
if (scanner != null) return;
//Connect to the first available scanner
for (ScannerInfo scannerInfo : scanners) {
try {
// Connect to the newly discovered scanner
scanner = scanManager.openScanner(scannerInfo);
scanner.setCallback(scannerCallback);
easyScanner = new ScanManager.EasyScanner(scanner);
} catch (ScanException e) {
callJavascriptMethod("showText", "Could not open " + scannerInfo + ": " + e);
}
}
}
// Let the webview know once we have found a scanner. This method call is why we had to
// wait for the webview to finish loading before we set up the scanner connection
public void onConnected(boolean b) {
if(b) {
callJavascriptMethod("scannerFound", null);
}
else {
callJavascriptMethod("scannerMissing", null);
}
}
});
} catch (ScanException e) {
callJavascriptMethod("showText", "Error connecting to scanner: " + e.getMessage());
}
}
// Implement the ‘startScan()’ method defined by the WebviewCallback interface
@Override
public void startScan() {
if(myWebView != null ) {
Log.d("ScanningWebViewActivity", "Starting Scan...");
try {
easyScanner.startScan();
} catch (ScanException e) {
e.printStackTrace();
}
}
}
// A convenience method for calling javascript methods on the UI thread
public void callJavascriptMethod(final String methodName, final String parameter) {
if(myWebView != null) {
runOnUiThread(new Runnable() {
@Override
public void run() {
myWebView.loadUrl("javascript:" + methodName + "(" + parameter + ")");
}
});
}
}
// Define the callback for incoming scan data
ScanManager.ScannerCallback scannerCallback = new ScanManager.ScannerCallback() {
public void onScanStatus(ScanStatus ScanStatus) {
if(ScanStatus.getBarcode() != null) {
callJavascriptMethod("scanResults", ScanStatus.getBarcode());
}
}
};
// Activity lifecycle cleanup
protected void onDestroy() {
super.onDestroy();
if (scanManager != null) {
// Disconnect from the ScanManager to clean up resources
scanManager.disconnect();
}
}
}
// This is an interface to define the callback methods from the javascript interface
// It’s not entirely necessary, but it provides a clean distinction if you’re exposing
// a bunch of methods in your activity to the JavascriptInterface
interface WebviewCallback {
void startScan();
}
// This is the class instance that will be handed off to the web view and contains all methods defined for javascript access
class WebAppInterface {
WebviewCallback mCallback;
/** Instantiate the interface and set the context */
WebAppInterface(WebviewCallback c) {
mCallback = c;
}
/** Start an EPX scan from the web page */
@JavascriptInterface
public void startScan() {
mCallback.startScan();
}
}
Conclusion
Thanks for taking the time to read this far! Hopefully, this sample code will be a good starting point for tying the EPX framework into your web application and extending your barcode scanning capabilities beyond native Android applications. If you have any questions or comments, please email us at info@sdgsystems.com or use the comments section below.
Comments