Camera scanning with SocketCam

Support

The Flutter CaptureSDK now has support for SocketCam C820 and C860.

Requirements

In order to use SocketCam in your Flutter app, you will need to install or upgrade the Flutter CaptureSDK version 1.5.

iOS Requirements

You will need to update pods with pod install --repo-update.

In your Info.plist, you need to add the key to allow access to the camera. Add the below code to the bottom of your dict tag.

<key>NSCameraUsageDescription</key>
        <string>Need to enable camera access for SocketCam products</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<key>UISupportedExternalAccessoryProtocols</key>
<array>
<string>com.socketmobile.chs</string>
</array>
<key>NSCameraUsageDescription</key>
<string>Need to enable camera access for SocketCam products</string>
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>sktcompanion</string>
</array>

Android Requirements

In AndroidManifest.xml you will need to add the below code.

<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<meta-data android:name="com.socketmobile.capture.APP_KEY" android:value="{YOUR_APP_KEY}"/>
<meta-data android:name="com.socketmobile.capture.DEVELOPER_ID" android:value="{YOUR_DEVELOPER_ID}"/>

You might also have to add the file network_security_config.xml file to android/app/src/main/res/xml in order to avoid a clearText permissions error. See the code below for the file.

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false" />
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="false">localhost</domain>
        <domain includeSubdomains="false">127.0.0.1</domain>
    </domain-config>
</network-security-config>

Then, in your AndroidManifest.xml, in your first application tag, add this property:

android:networkSecurityConfig="@xml/network_security_config"

For more information, check out the Android docs.

Using SocketCam

Once you have all of the changes above, you can start using SocketCam in your Flutter App. In order to do this, in your app you will need to first enable SocketCam, using a setProperty to set the socketCamStatus property.Once it is enabled, to open the view finder and start scanning you can set the trigger by setting the trigger property.

Below is an example of enabling SocketCam.

  Future<void> _setSocketCamStatus(int? arg) async {
  CaptureProperty property = CaptureProperty(
    id: CapturePropertyIds.socketCamStatus,
    type: CapturePropertyTypes.byte,
    value: arg,
  );

  try {
    CaptureProperty? data =
        await widget.socketCamCapture?.setProperty(property);
    setStatus(
      'successfully changed socket cam status: ${_supportOpts[arg]?['message']}',
    );
  } on CaptureException catch (exception) {
    String code = exception.code.toString();
    String message = exception.message;
    setStatus('failed to set socket cam status: $code :', message);
  }
}

Finally, here is how to set the trigger property.

final List<Map<String, dynamic>> triggerOptions = [
    {'label': 'Start', 'value': Trigger.start},
    {'label': 'Stop', 'value': Trigger.stop},
    {'label': 'Continuous Scan', 'value': Trigger.continuousScan},
];

...

Future<void> _setSocketCamTrigger() async {
    CaptureProperty property = CaptureProperty(
        id: CapturePropertyIds.triggerDevice, // Replace with the correct id value for TriggerDevice
        type: CapturePropertyTypes.byte, // Replace with the correct type value for Byte
        value: _triggerType,
    );
    try {
        var data = await widget.socketCamDevice?.setProperty(property);
        var triggerOpt = triggerOptions.firstWhere((x) => x["value"] == _triggerType);
        setStatus("successfully changed TriggerDevice: '${triggerOpt["label"]}'");
    } on CaptureException catch (exception) {
        String code = exception.code.toString();
        String message = exception.message; // Replace with your errorCheck method
        setStatus("failed to set TriggerDevice: $code : $message");
    }
}

In order to check if you already have SocketCam enabled, you can use a getProperty request like so.

Future<void> getSocketCamStatus() async {
    CaptureProperty property = const CaptureProperty(
        id: CapturePropertyIds.socketCamStatus,
        type: CapturePropertyTypes.none,
        value: {},
    );

    try {
        var data = await widget.socketCamCapture!.getProperty(property);
        Map<String, dynamic> x = _supportOpts[data.value]!;
        setState(() {
            _socketCamEnabled = data.value;
        });
        setStatus('successfully retrieved SocketCamStatus: ${x['message']}');
    } on CaptureException catch (exception) {
        String code = exception.code.toString();
        String message = exception.message;
        setStatus('failed to get SocketCamStatus: $code : $message');
    }
}

By checking if SocketCam is enabled you won’t need to go through the trouble of re-enabling it.

Differentiating SocketCam from other devices

It is recommended in the UI to save the capture instance that is tied to SocketCam separately from other device capture instances as to avoid get/set property and usage conflicts. You can differentiate a SocketCam device from another device, such as a D740, by checking the device type in the device arrival event. See the below code from an onCaptureEvent callback.

  _onCaptureEvent(e, handle) {
      if (e == null) {
          return;
      } else if (e.runtimeType == CaptureException) {
          _updateVals("${e.code}", e.message, e.method, e.details);
          return;
      }

      logger.log('onCaptureEvent from: ', '$handle');

      switch (e.id) {
          case CaptureEventIds.deviceArrival:
              Capture deviceCapture = Capture(logger);
              _openDeviceHelper(deviceCapture, e, false, handle);
              break;
      ...
  }

  ...

  Future _openDeviceHelper(Capture deviceCapture, CaptureEvent e, bool isManager, int handle) async {
  // deviceArrival checks that a device is available
  // openDevice allows the device to be used (for decodedData)
  List<DeviceInfo> arr = _devices;

  DeviceInfo _deviceInfo = e.deviceInfo;

  logger.log('Device ${isManager ? 'Manager' : ''} Arrival =>',
      '${_deviceInfo.name} (${_deviceInfo.guid})');

  try {
    int res = await deviceCapture.openDevice(_deviceInfo.guid, _capture);
    // res is 0 if no error, if not zero there is error in catch
    if (res == 0 && !arr.contains(_deviceInfo)) {
      if (SocketCamTypes.contains(_deviceInfo.type)) {
        setState(() {
          _socketcamDevice = deviceCapture;
          _socketCamHandle = handle;
        });
      } else {
        setState(() {
          _deviceCapture = deviceCapture;
        });
      }
      arr.add(_deviceInfo);
      setState(() {
        _devices = arr;
      });
    }
    _updateVals('Device${isManager ? ' Manager' : ''} Opened',
        'Successfully added "${_deviceInfo.name}"');
  } on CaptureException catch (exception) {
    _updateVals(exception.code.toString(), exception.message,
        exception.method, exception.details);
  }
}