Important information about data capture interfaces:
Zebra strongly recommends using DataWedge for all applications that require barcode scanning and data capture. If using EMDK native Barcode APIs, migration to DataWedge interfaces is strongly advised.
Here are the facts:
- DataWedge APIs have the same capabilities currently available in EMDK.
- DataWedge intent-based APIs are easier and faster to implement than EMDK APIs.
- New features are always added to DataWedge first before being considered for addition to EMDK; there is no guarantee that the same features will be exposed in EMDK APIs.
Learn About DataWedge
Overview
This guide provides a walk-through of the steps for creating an application that uses Barcode/Scanning APIs to perform Scanning operations on a Zebra Android device without using Profile Wizard. The API uses Barcode Manager, which is the primary object for accessing barcode scanning features.
Note: The demo app in this guide is intended for tutorial purposes only and should not be used in production environments.
Create The Project
Start by creating a new project in Android Studio. Call it BasicScanningTutorial
to match later references in this guide. For help, see the Android Studio tutorial.
Enable Android Permissions
Modify the application's Manifest.xml
file to use the EMDK library and to set permissions for EMDK to scan the barcodes.
Enable permissions for
com.symbol.emdk.permission.EMDK
:<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.symbol.basicscansample1"> <uses-permission android:name="com.symbol.emdk.permission.EMDK" /> <application> … </application> </manifest>
Enable the EMDK library in the application node:
<application> <uses-library android:name="com.symbol.emdk" /> <activity> … </activity> </application>
Add references to the libraries:
import com.symbol.emdk.EMDKManager; import com.symbol.emdk.EMDKManager.EMDKListener; import com.symbol.emdk.EMDKResults; import com.symbol.emdk.barcode.BarcodeManager; import com.symbol.emdk.barcode.ScanDataCollection; import com.symbol.emdk.barcode.ScanDataCollection.ScanData; import com.symbol.emdk.barcode.Scanner; import com.symbol.emdk.barcode.Scanner.DataListener; import com.symbol.emdk.barcode.Scanner.StatusListener; import com.symbol.emdk.barcode.Scanner.TriggerType; import com.symbol.emdk.barcode.ScannerConfig; import com.symbol.emdk.barcode.ScannerException; import com.symbol.emdk.barcode.ScannerResults; import com.symbol.emdk.barcode.StatusData; import com.symbol.emdk.barcode.StatusData.ScannerStates;
Extend the activity to implement
EMDKListener
, implementStatusListener
for notifying client applications to notify scan status events and override itsonStatus
function, implementDataListener
for notifying client applications to notify data events and override itsonData
function:public class MainActivity extends Activity implements EMDKListener, StatusListener, DataListener { @Override public void onOpened(EMDKManager emdkManager) { // TODO Auto-generated method stub } @Override public void onClosed() { // TODO Auto-generated method stub } @Override public void onStatus(StatusData statusData) { // TODO Auto-generated method stub } @Override public void onData(ScanDataCollection scanDataCollection) { // TODO Auto-generated method stub } }
Create some global variables to hold the instance objects of
EMDKManager
,BarcodeManager
andScanner
. These variables are used throughout the code. This section also adds some UI elements starting with a TextView to display the status of the scanning operation and EditText to populate scanned barcode data.// Declare a variable to store EMDKManager object private EMDKManager emdkManager = null; // Declare a variable to store Barcode Manager object private BarcodeManager barcodeManager = null; // Declare a variable to hold scanner device to scan private Scanner scanner = null; // Text view to display status of EMDK and Barcode Scanning Operations private TextView statusTextView = null; // Edit Text used to display scanned barcode data private EditText dataView = null; // boolean flag to start scanning after scanner initialization // Used in OnStatus callback to ensure scanner is idle before read() method is called private boolean startRead = false;
Design a simple UI that has a TextView to display the status of scanning operation. Above that is an EditText element to populate scanned barcode data.
Remove all the code inside the
res/layout/activity_main.xml
folder and add the following XML layout code for the UI:<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" android:padding="16dip" tools:context=".MainActivity" > <EditText android:id="@+id/editText1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/textViewStatusTitle" android:layout_centerHorizontal="true" android:ems="10" android:fadeScrollbars="true" android:inputType="none|textMultiLine" /> <TextView android:id="@+id/textViewStatus" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="" /> <TextView android:id="@+id/textViewStatusTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/textViewStatus" android:layout_centerHorizontal="true" android:layout_marginBottom="15dp" android:text="Status:" /> </RelativeLayout>
In the
onCreate
method, take the reference of UI elements declared inres/layout/activity_main.xml
to use them in the Activity. Then callgetEMDKManager
to initialize EMDK and confirm that it's ready.// References to UI elements statusTextView = (TextView) findViewById(R.id.textViewStatus); dataView = (EditText) findViewById(R.id.editText1); // Requests the EMDKManager object. This is an asynchronous call and // should be called from the main thread. // The callback also will receive in the main thread without blocking // it until the EMDK resources are ready. EMDKResults results = EMDKManager.getEMDKManager(getApplicationContext(), this); // Check the return status of getEMDKManager() and update the // status TextView accordingly. if (results.statusCode != EMDKResults.STATUS_CODE.SUCCESS) { updateStatus("EMDKManager object request failed!"); return; } else { updateStatus("EMDKManager object initialization is in progress......."); }
Write a method
initBarcodeManager()
to initialize the Barcode Manager object:private void initBarcodeManager() { // Get the feature object such as BarcodeManager object for // accessing the feature. barcodeManager = (BarcodeManager) emdkManager.getInstance(EMDKManager.FEATURE_TYPE.BARCODE); // Add external scanner connection listener if (barcodeManager == null) { Toast.makeText(this, "Barcode scanning is not supported.", Toast.LENGTH_LONG).show(); finish(); } }
Write a method
initializeScanner()
to initialize and enable the scanner and its listeners by using the Barcode Manager object. Theenable
method enables the scanner hardware. This method does not turn on the laser to start scanning, but makes the scanner available to the application. If the same scanner is enabled by another application, calling theenable()
method results in aScannerException
:private void initScanner() { if (scanner == null) {
}// Get default scanner defined on the device scanner = barcodeManager.getDevice(BarcodeManager.DeviceIdentifier.DEFAULT); if (scanner != null) { // Implement the DataListener interface and pass the // pointer of this object to get the data callbacks. scanner.addDataListener(this); // Implement the StatusListener interface and pass the pointer // of this object to get the status callbacks. scanner.addStatusListener(this); // Hard trigger. When this mode is set, the user has to manually // press the trigger on the device after issuing the read call. // NOTE: For devices not having hard trigger, may use // TriggerType.SOFT_ALWAYS. scanner.triggerType = TriggerType.HARD; try { // Enable the scanner // NOTE: After calling enable(), user must wait for IDLE // status before calling any other scanner APIs // such as setConfig() or read(). scanner.enable(); } catch (ScannerException e) { updateStatus(e.getMessage()); deInitScanner(); } } else { updateStatus("Failed to initialize the scanner device."); } }
Create a method to release scanner resources when no longer required. Name this method
deInitilazeScanner
and call thedisable()
method to unlock the scanner and make it available to other applications:private void deInitScanner() { if (scanner != null) { try { // Release the scanner scanner.release(); } catch (Exception e) { updateStatus(e.getMessage()); } scanner = null; } }
Use the
onOpened
method to get a reference to theEMDKManager
. TheEMDKListener
interface triggers this event when EMDK is ready to be used. TheEMDKListener
interface must be implemented to get a reference to theEMDKManager
APIs. This event passes theEMDKManager
instance and assigns it to the global variableemdkManager
created in the earlier steps. Use that instance to get an instance of Barcode Manager to enable scanning:// Get a reference to the EMDKManager this.emdkManager = emdkManager; // Get a reference to the feature object - BarcodeManager initBarcodeManager(); // Initialize the scanner initScanner();
When a barcode is scanned, its data is received in the
onData
method callback. This step gets that data, processes it in the desired format and populates the EditText UI element:// The ScanDataCollection object gives scanning result and // the collection of ScanData. So check the data and its status. String dataStr = ""; if ((scanDataCollection != null) && (scanDataCollection.getResult() == ScannerResults.SUCCESS)) { ArrayList<ScanData> scanData = scanDataCollection.getScanData(); // Iterate through scanned data and prepare the dataStr for (ScanData data : scanData) { // Get the scanned data String barcodeData = data.getData(); // Get the type of label being scanned ScanDataCollection.LabelType labelType = data.getLabelType(); // Concatenate barcode data and label type dataStr = barcodeData + " " + labelType; }
}// Updates EditText with scanned data and type of label on UI thread. updateData(dataStr);
Note: Any extensive processing on received data should be done in a background thread so as not to block the UI thread.
The following helper function displays the data string on UI from data callbacks:
// Variable to hold scan data length private int dataLength = 0; private void updateData(final String result) { runOnUiThread(new Runnable() { @Override public void run() { // Update the dataView EditText on UI thread with barcode data and its label type if (dataLength++ >= 50) { // Clear the cache after 50 scans dataView.getText().clear(); dataLength = 0; } dataView.append(result + "\n"); } }); }
Whether scanning the barcode by pressing the hard scan key or keeping it idle, it returns the status of the scanner at a specific point in time in the overridden
onStatus()
method of the implementedStatusListener
interface. Since the status also is being displayed along with barcode data, the app can make use of this method and populate the status:// The status is returned on multiple cases. Check the state and take the action. // Get the current state of scanner in background. ScannerStates state = statusData.getState(); String statusStr = ""; // Different states of Scanner switch (state) { case IDLE: // Scanner is idle and ready to change configuration and submit read. statusStr = statusData.getFriendlyName()+" is enabled and idle...";
} // Update TextView with scanner state on UI thread. updateStatus(statusStr);// Change scanner configuration. This should be done while the scanner is in IDLE state. setConfig(); try { // Starts an asynchronous Scan. The method will NOT turn ON the // scanner beam, but puts it in a state in which the scanner // can be turned on automatically or by pressing a hardware trigger. scanner.read(); } catch (ScannerException e) { updateStatus(e.getMessage()); } break; case WAITING: // Scanner is waiting for trigger press to scan... statusStr = "Scanner is waiting for trigger press..."; break; case SCANNING: // Scanning is in progress... statusStr = "Scanning..."; break; case DISABLED: // Scanner is disabled statusStr = statusData.getFriendlyName()+" is disabled."; break; case ERROR: // Error has occurred during scanning statusStr = "An error has occurred."; break; default: break;
The following helper function displays the status string on UI from status callbacks and exceptions:
private void updateStatus(final String status) { runOnUiThread(new Runnable() { @Override public void run() { // Update the status text view on UI thread with current scanner state statusTextView.setText("" + status); } }); }
Scanner configuration changes should be done while the scanner is in IDLE state and according to get-modify-set approach. Also, it is recommended to use the
ScannerConfig.isParamSupported(String param)
API and check whether the parameter is supported on the device dynamically prior to set:private void setConfig() { if (scanner != null) { try { // Get scanner config ScannerConfig config = scanner.getConfig();
}// Enable haptic feedback if (config.isParamSupported("config.scanParams.decodeHapticFeedback")) { config.scanParams.decodeHapticFeedback = true; } // Set scanner config scanner.setConfig(config); } catch (ScannerException e) { updateStatus(e.getMessage()); } }
Override the
onDestroy()
method to release all theEMDKManager
resources:@Override protected void onDestroy() { super.onDestroy(); // Release all the EMDK resources if (emdkManager != null) { emdkManager.release(); emdkManager = null; } }
Lastly, clean up the objects created by EMDK manager in the
onClosed()
method in case EMDK closes unexpectedly. Restarting the application also might resolve this issue:@Override public void onClosed() { // The EMDK closed unexpectedly. Release all the resources. if (emdkManager != null) { emdkManager.release(); emdkManager = null; } updateStatus("EMDK closed unexpectedly! Please close and restart the application."); }
The sample app is now ready to use.
Running the Application
Connect the Zebra device (with the EMDK runtime pre-loaded in the BSP) to a USB port on the development host. Note: The device must be in USB Debug mode.
Run the application.
A status message appears indicating that the scanner is waiting for a trigger-press. At this point the scanner is enabled (scan status is IDLE) and scans can begin by pressing hard trigger button on the device.
Pressing the hard scan button causes the status listener in the code to start working; the current status of scanner (which is "Scanning") is displayed in the Status TextView.
Once finished with scanning, release the hard scan button. Data is populated to EditText and status "Idle" is displayed in the Status TextView.
This is how Barcode/Scanning APIs can be used to perform basic scanning operations on Zebra devices.
Important Programming Tips
Edit the manifest
Be sure to make the following required changes in the application's AndroidManifest.xml
.
Include the permission for EMDK:
<uses-permission android:name="com.symbol.emdk.permission.EMDK"/>
Use the EMDK library:
<uses-library android:name="com.symbol.emdk"/>
Use DataWedge v1.7.12 or higher to test the
ProfileManager.processProfile()
for DataWedge profiles.
Content Backup
Once the barcode is enabled, the read
method is called on the scanner and the scanning API provides starts an asynchronous scan. The method does turn on the scanner, but puts the scanner in a state in which it can be turned ON either automatically or by pressing a hardware trigger as determined by the Scanner.TriggerType
. The data notification must registered in order to scan and get the scan data. The read request can be canceled by issuing a cancelRead
. If a read()
is submitted while another read is pending, the method call fails. Therefore, Zebra recommends checking whether a read is pending by calling isReadPending()
before submitting a read()
. A read() also can be submitted from within onData
and onStatus
events. If called within onStatus
, it should be called only when IDLE status is received. If called within onData
, then checking for isReadPending()
is recommended.
Note: The
read
method allows a single barcode scan only. If multiple scans are desired, theread
method must be called multiple times.
What's Next
After completing this basic tutorial, the next logical step might be to explore the Barcode/Scanning APIs in depth, and use them to perform advanced scanning operations. Also see the Advanced Barcode Tutorial.