Note: The accompanying code is available on GitHub.

The topic for today is how to add barcode scanning functionalities to the «Inventory» sample application we built in the previous post about Xamarin.Forms and FreshMVVM.

To integrate barcode scanning functionalities we’ve different options:

  1. Do Nothing - Barcode scanning in enabled by default simulating keyboard entry: the application receives the data in the selected input field.
  2. Integrate Barcode scanning API from Zebra’s EMDK for Xamarin - This is time consuming and add a dependency to our project. We can see this as an option for applications that want to have the maximum control on the Barcode scanning capabilities of Zebra devices.
  3. Use DataWedge Intent APIs - This allows to have a very good control of the barcode scanning functionality using standard Android interfaces (Intents, in this case), without the need to add any additional dependency to the project.

For this project we’re going to adopt the third option, that I believe is the best one for most of the data-entry applications that are going to run on Android powered Zebra devices.

DataWedge Integration

DataWedge (DW for the rest of this article) on Zebra’s Android devices shares only its name with the application running on legacy Windows Zebra’s devices. DW on Android is a service that is always available to application to scan barcodes and send the data to the foreground application through different channels. Over time DW expanded its programmatic-ability adding functionalities to its Intent APIs.You can read an excellent introduction to DataWedge and its APIs in this blog post by Darryn Campbell.The basic concept for DW is that it’s profile driven, and you can specify which profile needs to be active when a particular application/activity is in the foreground. This allows to have a very fine grained control of the barcode scanning capability out of the box, without having to delve in any API. But, if you need more functionalities, the features are there, and easy addition to your application through an Android Intent!

Configuring DataWedge

DataWedge can be configured in different ways:

  1. Manually - Through it’s user interface on the device as explained in DW’s documentation.
  2. Programmatically - Through the DataCapture profile API in the Zebra’s EMDK for Xamarin (but this method has been deprecated in the last EMDK for Xamarin version).
  3. Programmatically using Intents - Through the DW Intent APIs.
  4. From configuration files - You can export a configuration from one device and copy the file on other devices inside the /enterprise/device/settings/datawedge/autoimport. This is explained in DW’s documentation.

Given that we want to integrate DW using its Intent APIs, we’re going to use the third method to create a DW profile for our application.

Create a DataWedge Profile

The easiest way to do this, is to send the intent when the Activity of our Xamarin Forms application is created, as explained in Darryn’s blog, we can use the CreateProfile Intent to create the initial profile and then set the parameters, input and output, using the SetProfile Intent command:

  private void CreateProfile()

 

  {

  String profileName = EXTRA_PROFILE_NAME;

  SendDataWedgeIntentWithExtra(ACTION_DATAWEDGE_FROM_6_2, EXTRA_CREATE_PROFILE, profileName);


  // Now configure that created profile to apply to our application

  Bundle profileConfig = new Bundle();

  profileConfig.PutString("PROFILE_NAME", EXTRA_PROFILE_NAME);

  profileConfig.PutString("PROFILE_ENABLED", "true"); // Seems these are all strings

  profileConfig.PutString("CONFIG_MODE", "UPDATE");

  Bundle barcodeConfig = new Bundle();

  barcodeConfig.PutString("PLUGIN_NAME", "BARCODE");

  barcodeConfig.PutString("RESET_CONFIG", "true"); // This is the default but never hurts to specify

  Bundle barcodeProps = new Bundle();

  barcodeConfig.PutBundle("PARAM_LIST", barcodeProps);

  profileConfig.PutBundle("PLUGIN_CONFIG", barcodeConfig);

  Bundle appConfig = new Bundle();

  appConfig.PutString("PACKAGE_NAME", this.PackageName); // Associate the profile with this app

  appConfig.PutStringArray("ACTIVITY_LIST", new String[] { "*" });

  profileConfig.PutParcelableArray("APP_LIST", new Bundle[] { appConfig });

  SendDataWedgeIntentWithExtra(ACTION_DATAWEDGE_FROM_6_2, EXTRA_SET_CONFIG, profileConfig);

  // You can only configure one plugin at a time, we have done the barcode input, now do the intent output

  profileConfig.Remove("PLUGIN_CONFIG");

  Bundle intentConfig = new Bundle();

  intentConfig.PutString("PLUGIN_NAME", "INTENT");

  intentConfig.PutString("RESET_CONFIG", "true");

  Bundle intentProps = new Bundle();

  intentProps.PutString("intent_output_enabled", "true");

  intentProps.PutString("intent_action", DataWedgeReceiver.IntentAction);

  intentProps.PutString("intent_delivery", "2");

  intentConfig.PutBundle("PARAM_LIST", intentProps);

  profileConfig.PutBundle("PLUGIN_CONFIG", intentConfig);

  SendDataWedgeIntentWithExtra(ACTION_DATAWEDGE_FROM_6_2, EXTRA_SET_CONFIG, profileConfig);

  }

 

Where the SendDataWedgeIntentWithExtra is implemented as:

  private void SendDataWedgeIntentWithExtra(String action, String extraKey, Bundle extras)

 

  {

  Intent dwIntent = new Intent();

  dwIntent.SetAction(action);

  dwIntent.PutExtra(extraKey, extras);

  SendBroadcast(dwIntent);

  }


  private void SendDataWedgeIntentWithExtra(String action, String extraKey, String extraValue)

  {

  Intent dwIntent = new Intent();

  dwIntent.SetAction(action);

  dwIntent.PutExtra(extraKey, extraValue);

  SendBroadcast(dwIntent);

  }

 

Now we just need to define a couple of constants in the MainActivity class and we can call the CreateProfile method from the OnCreate callback:

  private static string ACTION_DATAWEDGE_FROM_6_2 = "com.symbol.datawedge.api.ACTION";

 

  private static string EXTRA_CREATE_PROFILE = "com.symbol.datawedge.api.CREATE_PROFILE";

  private static string EXTRA_SET_CONFIG = "com.symbol.datawedge.api.SET_CONFIG";

  private static string EXTRA_PROFILE_NAME = "Inventory DEMO";


  protected override void OnCreate(Bundle bundle)

  {

  TabLayoutResource = Resource.Layout.Tabbar;

  ToolbarResource = Resource.Layout.Toolbar;


  base.OnCreate(bundle);


  global::Xamarin.Forms.Forms.Init(this, bundle);


  var repository = new Repository(FileAccessHelper.GetLocalFilePath("items.db3"));

  FreshIOC.Container.Register(repository);


  CreateProfile();


  LoadApplication(new App());

  }

 

Let’s try to run this application on a Zebra device and see if the profile it’s created.

This is the initial situation inside DataWedge:

datawedge_1.png

After we run the application, we can see that there’s now a profile with the name Inventory DEMO:

datawedge_2.png

This is the name we have chosen in our application:

private static string EXTRA_PROFILE_NAME = "Inventory DEMO";

 

If we look into this profile, we can see that it’s associated with our application:

 

datawedge_3.png

 

This is because we have set the Package name to this.PackageName:

 

  appConfig.PutString("PACKAGE_NAME", this.PackageName); // Associate the profile with this app

  appConfig.PutStringArray("ACTIVITY_LIST", new String[] { "*" });

 

Handling the Data Coming from DataWedge

So far we’ve created the DW’s profile so that we’re going to receive the barcode data as a broadcast intent.

What we want to implement is that, when we receive the barcode data, we’re going to search for it in the database and open the detail view with the data we’ve retrieved from the database. If the barcode we’ve just scanned is not in the database, we’ll create a new record and show the ItemPage as well.

GetItem from Database

So, the first step is to add to modify the Repository.cs to search for a Barcode creating a GetItem method that, given the barcode string, retrieve an Item from the database:

  public Task<List<Item>> GetItem(string barcode)

 

  {

  return conn.QueryAsync<Item>("select * from Item where barcode=?", barcode);

  }

Handle barcode scanning message (ItemListPageModel)

We’re now going to implement inside the ItemListPageModel class the method that receive the scanned barcode, search for the item in the database, create a new Item if the barcode is not already in the database, and then open the ItemPage for this Item:

  private void ScanBarcode(string barcode)

 

  {

  Item item;


  Task<List<Item>> getItemTask = _repository.GetItem(barcode);

  getItemTask.Wait();

  if (getItemTask.Result.Count() < 1)

  {

  item = new Item{Name = "", Barcode = barcode};

  } else {

  item = getItemTask.Result.First<Item>();

  }

 

  CoreMethods.PushPageModel<ItemPageModel>(item);

  }

 

Receiving the Barcode Message

To receive the barcode in the Inventory application, we’re going to use Xamarin Forms’ MessagingCenter. This is a very low friction solution that can easily implemented in our ItemListPageModel class, in the init method:

  public override void Init(object initData)

 

  {

  LoadItems();

  if (Items.Count() < 1)

  {

  CreateSampleData();

  }


  MessagingCenter.Subscribe<App, string>(this, "ScanBarcode", (sender, arg) => {

  ScanBarcode(arg);

  });

  }

 

We just need something that send this message!

Receiving Data from DataWedge

The DataWedge profile that we’re creating with the code we have introduced earlier is sending the data with a broadcast intent using the action DataWedgeReceiver.IntentAction. As you may have already guess, we need to create a broadcast receiver in the Inventory.Droid project using the Broadcast Receiver template and call it DataWedgeReceiver:

broadcast_receiver.png

Broadcast Receiver

The initial implementation of the broadcast receiver is to handle the data sent from DataWedge and send the data to the registered event handler:

  using System;

 

  using Android.Content;


  namespace Inventory.Droid

  {

  [BroadcastReceiver]

  public class DataWedgeReceiver : BroadcastReceiver

  {

  // This intent string contains the source of the data as a string

  private static string SOURCE_TAG = "com.motorolasolutions.emdk.datawedge.source";

  // This intent string contains the barcode symbology as a string

  private static string LABEL_TYPE_TAG = "com.motorolasolutions.emdk.datawedge.label_type";

  // This intent string contains the captured data as a string

  // (in the case of MSR this data string contains a concatenation of the track data)

  private static string DATA_STRING_TAG = "com.motorolasolutions.emdk.datawedge.data_string";

  // Intent Action for our operation

  public static string IntentAction = "barcodescanner.RECVR";

  public static string IntentCategory = "android.intent.category.DEFAULT";


  public event EventHandler<String> scanDataReceived;


  public override void OnReceive(Context context, Intent i)

  {

  // check the intent action is for us

  if (i.Action.Equals(IntentAction))

  {

  // define a string that will hold our output

  String Out = "";

  // get the source of the data

  String source = i.GetStringExtra(SOURCE_TAG);

  // save it to use later

  if (source == null)

  source = "scanner";

  // get the data from the intent

  String data = i.GetStringExtra(DATA_STRING_TAG);

  // let's define a variable for the data length

  int data_len = 0;

  // and set it to the length of the data

  if (data != null)

  data_len = data.Length;

  // check if the data has come from the barcode scanner

  if (source.Equals("scanner"))

  {

  // check if there is anything in the data

  if (data != null && data.Length > 0)

  {

  // we have some data, so let's get it's symbology

  String sLabelType = i.GetStringExtra(LABEL_TYPE_TAG);

  // check if the string is empty

  if (sLabelType != null && sLabelType.Length > 0)

  {

  // format of the label type string is LABEL-TYPE-SYMBOLOGY

  // so let's skip the LABEL-TYPE- portion to get just the symbology

  sLabelType = sLabelType.Substring(11);

  }

  else

  {

  // the string was empty so let's set it to "Unknown"

  sLabelType = "Unknown";

  }


  // let's construct the beginning of our output string

  Out = data.ToString() + "\r\n";

  }

  }


  if (scanDataReceived != null)

  {

  scanDataReceived(this, Out);

  }

  }

  }

  }

  }

 

Register the Broadcast Receiver

Now we’ve most of the pieces together, what we still miss is to create a DataWedgeReceiver instance setting the event to send the correct message that is expected in the ItemListPageModel class:

  _broadcastReceiver = new DataWedgeReceiver();

 

  App inventoryApp = new App();


  _broadcastReceiver.scanDataReceived += (s, scanData) =>

  {

  MessagingCenter.Send<App, string>(inventoryApp, "ScanBarcode", scanData);

  };

 

 

Then we can handle the registration of the broadcast receiver in the OnResume and OnPause callback handler, so that the receiver is only active when the application has the focus:

 

  protected override void OnResume()

 

  {

  base.OnResume();


  if (null != _broadcastReceiver)

  {

  // Register the broadcast receiver

  IntentFilter filter = new IntentFilter(DataWedgeReceiver.IntentAction);

  filter.AddCategory(DataWedgeReceiver.IntentCategory);

  Android.App.Application.Context.RegisterReceiver(_broadcastReceiver, filter);

  }

  }


  protected override void OnPause()

  {

  if (null != _broadcastReceiver)

  {

  // Unregister the broadcast receiver

  Android.App.Application.Context.UnregisterReceiver(_broadcastReceiver);

  }

  base.OnStop();

  }

 

We can now compile the application and test the barcode scanning integration on a Zebra’s Android device.

As a plus, the application can still be compiled and used for other Android devices or for iOS without any issue, simply the barcode scanning functionalities are only available on Zebra devices.

The full source code is available on the GitHub project. This version is saved with the tag datawedge.

 

 

Note:  You can find a better layout of the code on my personal website.