12 Replies Latest reply on Oct 14, 2016 8:58 AM by Gustavo Costa

    Best practice to use the scanner across multiple activities

    Michael Bedford

      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

        • Re: Best practice to use the scanner across multiple activities
          Michael Bedford

          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!

          • Re: Best practice to use the scanner across multiple activities
            Michael Bedford

            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

              • Re: Best practice to use the scanner across multiple activities
                Darryn Campbell

                Hi Mike,

                 

                I was initially very concerned about the crashing you observe when mashing the scan button, I was able to reproduce the problem very easily.  Although the issue is present in the tutorial on which you based your prototype (https://github.com/EMDK/xamarin-samples/archive/BasicScanningTutorial.zip ) I was relieved to observe that this only seems to be a problem when running in the debugger.  When I run the app without the debugger connected it seems to work fine and I couldn't reproduce any crash.  I'll still ensure the issue is raised to our internal team but since this is during debug-only I suspect it will not be critical priority.

                 

                Regarding the application architecture, personally I would not use a singleton and what you have attached to the above post looks good with Activities A & B encapsulated.  Obviously there is a concern over performance if you are re-initializing the scanner each time you switch activities but if you say the performance is OK then that is good.  I do see a crash from the mono runtime when switching between the activities 10 times or so BUT like the last issue this is only present whilst debugging and doesn't seem to be an issue when the debugger is disconnected.

                 

                The scanner null logic also looks good to me.  The only way your scanner should be null is if your activity is destroyed in which case you should have your onCreate called anyway, recreating the object.

                 

                Not a thorough analysis by any means but hope that helps.

                Darryn.

                  • Re: Best practice to use the scanner across multiple activities
                    Michael Bedford

                    Hello Darryn,

                     

                    Thank you very much for the response and checking in to this.

                     

                    First off, good observations on the issue I had reported plus the crash you found. You are correct, I do most of my testing in debug so that is why I saw and reported it. If it does not happen otherwise, I am okay with that.

                     

                    Thank you for the feedback on my sample. I will stay away from a singleton. I am okay with performance, it does not seem too bad. But of course, I would be just fine if somebody suggested a better or faster way to go about it.

                     

                    In the meantime, I think I will just continue using what I have posted until/unless I hear of something else.

                     

                    Also, agree, the null logic may be a bit overkill since they should never be null anyway unless the activity is destroyed. I will probably leave it in there just for fun though.

                     

                    Thanks!

                • Re: Best practice to use the scanner across multiple activities
                  Gustavo Costa

                  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

                  • Re: Best practice to use the scanner across multiple activities
                    Gustavo Costa

                    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:   <"your_package_name">.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 <your_package_name> and <your_host_activity_name>

                        // for your project package name and the laser scanner host activity name respectively

                        // [Activity(Name = "<your_package_name>.<your_host_activity_name>", LaunchMode = LaunchMode.SingleTask)]

                        // [IntentFilter(new[] { "<your_package_name>.RECVR" }, Categories = new[] { Intent.CategoryDefault })]

                     

                     

                        [BroadcastReceiver]

                        internal class ScannerBroadcaster : BroadcastReceiver

                        {

                            private static string your_package = "com.scanner"; // <your_package_name>

                     

                     

                            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<string> 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);

                                        }

                                    }

                                }

                            }

                        }

                    }

                    • Re: Best practice to use the scanner across multiple activities
                      Gustavo Costa

                      This is how I use it in my activity
                      Sorry 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);

                                  }

                              }

                          }

                      }

                      • Re: Best practice to use the scanner across multiple activities
                        Gustavo Costa

                        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!