Hello all, I have an issue that is somewhat related to a previous post of mine here: https://developer.zebra.com/thread/32976
While, based on the answer of that thread, there were some known issues with the older EMDK service and updating to the latest did help a lot; my implementation of the scanner just does not seem right. It is working and with the EMDK update, working a lot better but I wanted to ask those more experienced to weigh in on a possible more efficient approach.
Basically, my application has two main activities. I need to be able to scan on both actvities. What I have done so far is taken the scanner code, very similar to the EMDK docs and the sample application and wrapped it all in a singleton class. The idea is that activity A can get the instance to my singleton scanner class (and since this will typically be first lauch, the singleton can instantiate itself). From activity A, a user can switch to activity B. Activity A is not finished but rather, backgrounded as the two activities can switch back and forth. I know one of the biggest concerns with a singleton is thread safety. Let me say that I don't have to worry about that here but by all means, if there is a better way feel free to give your suggestion.
So, here are the questions or issues I am having so far:
1. If I just get the instance to the singleton with each onResume of the activity (which I tried first), do I have to pass in or update the context each time? GetEMDKManager call requires me to pass in a context. If it is called and instantiated from activity A, the instance will continue to have activity A context. Doesn't this present a problem when I am trying to use it in activity B? Should I update the context when I get instance?
2. I assume you cannot simply update the context because the EMDKManager was created with the other context. Therefore, you would need to completely dispose everything when switching activities so you can call GetEMDKManager again and pass in the new context. Is that correct? This is how I am doing it now. More on that below.
3. I was having issues where, if I just call to pause the scanner during the onPause of activity A, then when you switch to activity B, you get the singleton and call to resume the scanner (which is basically InitScanner) during the onResume, it acted like I had two instances of the class even though it is a singleton because I was getting double scan result event handler raising on activity B.
All that said, I am basically doing this now:
1. Activity A launches and gets the instance of the singleton, it is null so it instantiates itself. It does this in onResume
2. In onPause of Activity A, I call to pause the scanner, then destroy and then dispose the singleton instance.
3. In onResume of Activity B, I call to get the instance of the singleton and again, it is null since I disposed everything during onPause of activity A. This allows me to pass in the new context.
4. In onPause of Activity B, I do the same thing as Activity A so that the scanner is paused and the singleton instance is disposed so that activity A can get the instance again but pass in context when switching back.
Maybe this is the best way but it just feels clunky. Seems that passing the scanner back and forth between activities should be easier. Maybe I am missing something.
Thanks!
Mike
Best practice to use the scanner across multiple activities |
7 Replies
This is how I use it in my activitySorry but I got it wrong. It is not going across activities but rather on fragments... However I still believe it will work with activities as long as the host activity is still on the background. The caller activity always stay alive in the background, so hope it helps you....
namespace Scanner.Droid.Activities
{
[Activity(Name = "com.scanner.HomeActivity", Label = "HomeActivity", LaunchMode = LaunchMode.SingleTask)]
[IntentFilter(new[] { "com.scanner.RECVR" }, Categories = new[] { Intent.CategoryDefault })]
public class HomeActivity : Activity, EMDKManager.IEMDKListener
{
private ScannerBroadcaster scannerBroadcaster;
private bool isSymbolDevice = false;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_home);
if (isSymbolDevice = ScannerBroadcaster.hasLaserScannerDevice())
{
scannerBroadcaster = new ScannerBroadcaster();
scannerBroadcaster.OnCreate(this, this);
scannerBroadcaster.scanDataReceived += (s, scanData) =>
{
// Your barcode = scanData, Maybe
};
}
}
public void OnClosed()
{
if (isSymbolDevice)
{
scannerBroadcaster.OnClosed();
}
}
public void OnOpened(EMDKManager p0)
{
if (isSymbolDevice)
{
scannerBroadcaster.OnOpened(p0, this);
}
}
protected override void OnResume()
{
base.OnResume();
if (isSymbolDevice)
{
RegisterReceiver(scannerBroadcaster, scannerBroadcaster.intentFilter);
}
}
protected override void OnPause()
{
base.OnPause();
if (isSymbolDevice)
{
UnregisterReceiver(scannerBroadcaster);
}
}
protected override void OnDestroy()
{
base.OnDestroy();
if (isSymbolDevice)
{
scannerBroadcaster.OnDestroy();
}
}
public void performSoftTrigger()
{
if (isSymbolDevice)
{
scannerBroadcaster.performSoftTrigger(this);
}
}
}
}
You still have to go to the profile manager and create a "Broadcast" profile. On mine interface class there is instructions in the comments on how to do it in case you dont already know it!
Thats my interface. Initialize this class in the host activity:
using Android.App;
using Android.Content;
using Android.Media;
using Android.Widget;
using Symbol.XamarinEMDK;
using System;
using System.Text.RegularExpressions;
using static Symbol.XamarinEMDK.EMDKManager;
namespace CMS.Scanner.Droid.Util
{
/*
* Install the EDMK nuget tool - Places a EMDK menu in the Visual Studio menu bar
* Install EMK component
* Create a profile named: Broadcast with Target MX Version = 4.4 -> Find the version of the device (our symbol motorola = 4.4)
*
* Configurations:
* Data Capture -> 1) Activity Selection
* -> 2) Data Inpunt -> Barcode
* -> 3) Data Delivery -> Intent
*
* 1.1) Under Applications set your package name
* 1.2) Under Host Activity set your host activity name
*
* 2.1) Under Barcode Scanner Input set Enable: Enable
* 2.2) Under Decoders set Interleaved 2of5: Enable
*
* 3.1) Under Intent Output set Enable: Enable,
* Action: .RECVR,
* Category: android.intent.category.DEFAULT,
* Delivery: Broadcast Intent
* 3.2) Under Basic Data Formatting set Enable: Enable
* Send Data: Enable
*/
// RegisterReceiver OnResume RegisterReceiver(scannerBroadcaster, scannerBroadcaster.intentFilter);
// UnregisterReceiver OnPause UnregisterReceiver(scannerBroadcaster);
// OnCreate, OnDestroy life cycles calls
// Host must implement EMDKManager.IEMDKListener and call OnClosed, OnOpened
// Add these properties to the host activity
// Replace and
// for your project package name and the laser scanner host activity name respectively
// [Activity(Name = ".", LaunchMode = LaunchMode.SingleTask)]
// [IntentFilter(new[] { ".RECVR" }, Categories = new[] { Intent.CategoryDefault })]
[BroadcastReceiver]
internal class ScannerBroadcaster : BroadcastReceiver
{
private static string your_package = "com.scanner"; //
private static string ourIntentAction = your_package + ".RECVR";
private Context context;
private static string DWAPI_TOGGLE_SCANNING = "TOGGLE_SCANNING";
private static string intentCategory = "android.intent.category.DEFAULT";
private static string BARCODE_SCANNER_LOWER_TAG = "scanner";
private static string motorolasolutions = "com.motorolasolutions.emdk.datawedge.";
private static string DATA_STRING_TAG = motorolasolutions + "data_string";
private static string SOURCE_TAG = motorolasolutions + "source";
private static string ACTION_SOFTSCANTRIGGER = motorolasolutions + "api.ACTION_SOFTSCANTRIGGER";
private static string EXTRA_PARAM = motorolasolutions + "api.EXTRA_PARAMETER";
public IntentFilter intentFilter;
public event EventHandler scanDataReceived;
private const string mProfileNameBroadcastIntent = "Broadcast";
// Current available and tested laser scanner devices' list
private static string[] laser_devices = {
"TC700H",
};
private EMDKManager mEmdkManager = null;
private ProfileManager mProfileManager = null;
private const string mProfileBroadcast = "Broadcast";
private AudioManager audioManager;
public ScannerBroadcaster()
{
intentFilter = new IntentFilter(ourIntentAction);
intentFilter.AddCategory(intentCategory);
}
public void OnCreate(Context context, IEMDKListener listener)
{
EMDKResults result = EMDKManager.GetEMDKManager(Application.Context, listener);
if (result.StatusCode != EMDKResults.STATUS_CODE.Success)
{
Toast.MakeText(context, "Error opening the EMDK Manager", ToastLength.Long).Show();
}
audioManager = (AudioManager)context.GetSystemService(Context.AudioService);
}
public void OnDestroy()
{
if (mProfileManager != null)
{
mProfileManager = null;
}
if (mEmdkManager != null)
{
mEmdkManager.Release();
mEmdkManager = null;
}
}
public void OnClosed()
{
if (mEmdkManager != null)
{
mEmdkManager.Release();
mEmdkManager = null;
}
}
public void OnOpened(EMDKManager emdkManager, Context context)
{
mEmdkManager = emdkManager;
mProfileManager = (ProfileManager)mEmdkManager.GetInstance(EMDKManager.FEATURE_TYPE.Profile);
// Broadcast Specifics
EMDKResults results = mProfileManager.ProcessProfile(mProfileBroadcast, ProfileManager.PROFILE_FLAG.Set, new String[] { "" });
if (results.StatusCode == EMDKResults.STATUS_CODE.Failure)
{
Toast.MakeText(context, "Failed to set profile Broadcast", ToastLength.Long).Show();
}
}
public void performSoftTrigger(Context context)
{
var intent = new Intent();
intent.SetAction(ACTION_SOFTSCANTRIGGER);
intent.PutExtra(EXTRA_PARAM, DWAPI_TOGGLE_SCANNING);
context.SendBroadcast(intent);
}
public static bool hasLaserScannerDevice()
{
string deviceModel = Android.OS.Build.Model.ToUpper();
foreach (var laser_device in laser_devices)
{
if (laser_device.ToUpper().Equals(deviceModel))
{
return true;
}
}
return false;
}
public override void OnReceive(Context context, Intent intent)
{
this.context = context;
audioManager?.AdjustVolume(Adjust.Same, VolumeNotificationFlags.PlaySound);
handleDecodeData(intent);
}
private void handleDecodeData(Intent i)
{
if (i == null)
return;
if (i.Action.Equals(ourIntentAction))
{
string source = i.GetStringExtra(SOURCE_TAG);
if (source.ToLowerInvariant().Equals(BARCODE_SCANNER_LOWER_TAG))
{
string data = i.GetStringExtra(DATA_STRING_TAG);
// Remove form feed
data = Regex.Replace(data, @"[\u000A\u000B\u000C\u000D\u2028\u2029\u0085]+", String.Empty);
if (data != null && data.Length > 0)
{
scanDataReceived?.Invoke(context, data);
}
}
}
}
}
}
I have managed to achieve a solution to a similar problem using a broadcast profile.I implement the EMDKManager.IEMDKListener in the hosting activity and any other activities that are called from there can use the scanner object. If A implements the listener, then A call B and B call C, then B and C and A can use the scanner. However, if you kill activity A then you can no longer can use the broadcast. It depends on how your application navigation works and finding out the best location to instantiate your scanner.If it is still relevant to you just let me know and I can lend you a hand
Gustavo,
Yes, please share!!! We are not getting much traction on this from Zebra. If you have a solution that works, and works better, please share it.
Thanks!!!
Mike
Get Outlook for Androidhttps://aka.ms/ghei36>
Hello all,
Okay, in an attempt to figure this problem out and/or get some assistance, I create a spin off sample project from the "Barcode Scanning Tutorial" demo project which can be downloaded from Zebra, from the EMDK scanning documents.
My spin off project is attached. I modified the project to have two activities that you can switch back and forth between. I also created a wrapper class for the scanner, not using a singleton for now (although that could be an option) and then stripped out all of the barcode scanning calls from the activities and instead instantiated and called the wrapper.
This project seems to work okay, performance is also okay. However, it is just a start. I would like others in the community and/or Zebra to look this over so we can build off of it. Please suggest or make improvements. I am far from an expert so this would really help not only me but the community to get a clean scanning wrapper and sample for multiple activities.
Some examples of thoughts I have:
1. Should the wrapper be a singleton?
2. Can the initialization and lifecycle calls be handled better?
3. When I check if scanner is null during lifecycle, should I also re-instantiate if it is?
4. The event handlers seem to be working fine but is there a better way?
I did notice one bug and I believe it exists in the raw Barcode Scanning Tutorial (ie before I modified it). If you scan slowly or at a normal pace, everything works fine. However, if you scan very quickly, just keep mashing on the button, you can get the app to die and a popup that states the EMDK service has stopped. This is not a high priority because I realize that most users will never mash the button that fast. However, when time permits, it should be investigated because it should not just crash. Maybe there is something that can be done now to prevent it that I am not aware of? I think a test would be to disable the scanner while reading and re-enable after a read? Along those lines, note that I added a way to enable and disable the scanner in the wrapper.
Hope this helps and helps to get this sample wrapped up. Zebra, once it is clean and you give it your blessing, I hope you continue to use it and include it in the demos/docs to help others that need a good example of scanning from multiple activities or with a wrapper.
Also, I hope to get help soon as this has been lingering for a few weeks for me (and longer for others). I want to get this finalized.
Thanks!
Mike
Is there any ideas on this? It is still a real problem for me. I have the EMDK service in a singleton wrapper per another post but is this the best way? I am still having random issues with:
1. Taking too long to initialize and get instance, particularly when switching activities.
2. OnScanData event raises twice at times, which leads me to believe somehow wrapper was instantiated twice.
3. Scanner appears to be instantiated but clearly is not. I do not get scans (or LED aim).
I think maybe the wrapper is the right way to go but maybe the problem now is knowing what things to do and when during the lifecycle. In other words, do I just pause and resume the scanner on both activites? Should I destroy the instance of the singleton when switching activities? Etc...
Thanks!