# Capture Helper

Capture Helper is the recommended API for integrating Socket Mobile barcode scanning and NFC reading into your application. Covers importing CaptureHelper, opening a Capture session with AppKey/AppID/DeveloperID credentials, and handling events: device arrival and removal, device discovery, decoded data (scanned barcode or NFC data), power (battery updates) and error events. Includes a minimal working implementation example.

Capture Helper is the easiest and recommended way for adding the barcode scanning and NFC reading capabilities to an application.

On Android `CaptureHelper` role is played by the `Capture` builder and EventBus event subscriptions.

Start by importing the required types:

## Java

```java
import android.util.Log;

import com.socketmobile.capture.android.Capture;
import com.socketmobile.capture.android.events.ConnectionStateEvent;
import com.socketmobile.capture.client.CaptureClient;
import com.socketmobile.capture.client.ConnectionState;
import com.socketmobile.capture.client.DataEvent;
import com.socketmobile.capture.client.DeviceClient;
import com.socketmobile.capture.client.DeviceState;
import com.socketmobile.capture.client.DeviceStateEvent;
import com.socketmobile.capture.types.DecodedData;

import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
```

## Kotlin

```kotlin
import android.util.Log

import com.socketmobile.capture.android.Capture
import com.socketmobile.capture.android.events.ConnectionStateEvent
import com.socketmobile.capture.client.CaptureClient
import com.socketmobile.capture.client.ConnectionState
import com.socketmobile.capture.client.DataEvent
import com.socketmobile.capture.client.DeviceClient
import com.socketmobile.capture.client.DeviceState
import com.socketmobile.capture.client.DeviceStateEvent
import com.socketmobile.capture.types.DecodedData

import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
```

## Swift

```swift
import CaptureSDK
```

## C#

```csharp
using SocketMobile.Capture;
```

## Dart

```dart
import 'package:capturesdk_flutter/capturesdk.dart';
```

## JavaScript

```javascript
import { CaptureHelper } from 'react-native-capture';
```

## Capture Helper Initialization

The first thing is to import the CaptureSDK as above.

On iOS, C#, JavaScript and Dart, Capture Helper is opened by passing in argument the completion handler that is called with the result code when the open has completed. On Android build the helper with **Capture.builder()** and the result arrives as a **ConnectionStateEvent**.

This is how the application starts receiving the CaptureSDK notifications such as **device arrival, device removal or decoded data events**. Following is an illustration of such code:

## Java

```java
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.socketmobile.capture.android.Capture;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Build and start the Capture service.
        // AppKey, AppID and DeveloperID are declared as <meta-data> entries
        // inside the <application> element of AndroidManifest.xml.
        // Capture registers this Activity for events automatically via
        // ActivityLifecycleCallbacks installed by builder().build().
        Capture.builder(getApplicationContext())
                .enableLogging(BuildConfig.DEBUG)
                .build();
    }
}
```

## Kotlin

```kotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.socketmobile.capture.android.Capture

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Build and start the Capture service.
        // AppKey, AppID and DeveloperID are declared as <meta-data> entries
        // inside the <application> element of AndroidManifest.xml.
        // Capture registers this Activity for events automatically via
        // ActivityLifecycleCallbacks installed by builder().build().
        Capture.builder(applicationContext)
            .enableLogging(BuildConfig.DEBUG)
            .build()
    }
}
```

## Swift

```swift
import UIKit
import CaptureSDK

class MyViewController: UIViewController {

    // Capture Helper shareInstance allows to share the same instance of Capture Helper with the
    // entire application. That static property can be used in any views but it is recommended
    // to open only once Capture Helper (in the main view controller) and pushDelegate, popDelegate
    // each time a new view requiring scanning capability is loaded or unloaded respectively.
    var captureHelper = CaptureHelper.sharedInstance

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view
        // to make all the capture helper delegates and completion handlers able to
        // update the UI without the app having to dispatch the UI update code,
        // set the dispatchQueue property to the DispatchQueue.main
        captureHelper.dispatchQueue = DispatchQueue.main

        // There is a stack of delegates the last push is the
        // delegate active, when a new view requiring notifications from the
        // scanner, then push its delegate and pop its delegate when the view is done
        captureHelper.pushDelegate(self)
        
        let appInfo = SKTAppInfo();
        appInfo.developerID = "[YOUR DEVELOPER ID]"
        appInfo.appID = "[YOUR IOS APP ID]"
        appInfo.appKey = "[YOUR IOS APP KEY]"
        
        captureHelper.openWithAppInfo(appInfo, withCompletionHandler: { (result) in
            print("Result of CaptureSDK initialization: \(result.rawValue)")
        })
    }

}
```

## C#

```csharp
// .NET MAUI
using SocketMobile.Capture;

public partial class MainPage : ContentPage
{
    private CaptureHelper _capture;

    public MainPage()
    {
        InitializeComponent();

        // Initialize CaptureHelper
        capture = new CaptureHelper();

        // Register for the Capture Helper events
        capture.DeviceArrival += CaptureDeviceArrival;
        capture.DeviceRemoval += CaptureDeviceRemoval;
        capture.DecodedData += CaptureDecodedData;
        capture.DevicePowerState += CaptureDevicePowerState;
        capture.Errors += CaptureErrors;
        capture.Terminate += CaptureTerminate;
    }

    private async void OpenCapture()
    {
        string appId = "";
        string developerId = "";
        string appKey = "";

        switch (DeviceInfo.Platform)
        {
            case var p when p == DevicePlatform.iOS:
                appId = "[YOUR IOS APP ID]";
                developerId = "[YOUR DEVELOPER ID]";
                appKey = "[YOUR IOS APP KEY]";
                break;

            case var p when p == DevicePlatform.Android:
                appId = "[YOUR ANDROID APP ID]";
                developerId = "[YOUR DEVELOPER ID]";
                appKey = "[YOUR ANDROID APP KEY]";
                break;

            case var p when p == DevicePlatform.WinUI:
                appId = "[YOUR WINDOWS APP ID]";
                developerId = "[YOUR DEVELOPER ID]";
                appKey = "[YOUR WINDOWS APP KEY]";
                break;
        }

        Task<long> task = capture.OpenAsync(appId, developerId, appKey);

        if (!SktErrors.SKTSUCCESS(task.Result))
        {
            Console.WriteLine($"Open Capture Failed - Error: {task.Result}");
        }
    }
}
```

## Dart

```dart
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:capturesdk_flutter/capturesdk.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const AppInfo appInfo = AppInfo(
    appIdAndroid: '[YOUR ANDROID APP ID]',
    appKeyAndroid: '[YOUR ANDROID APP KEY]',
    appIdIos: '[YOUR IOS APP ID]',
    appKeyIos: '[YOUR IOS APP KEY]',
    developerId: '[YOUR DEVELOPER ID]',
  );

  final CaptureHelper helper = CaptureHelper();

  @override
  void initState() {
    super.initState();
    if (Platform.isAndroid) {
      CapturePlugin.startCaptureService().then((_) async {
        await _openHelper();
      });
    } else {
      _openHelper();
    }
  }

  Future<void> _openHelper() async {
    try {
      await helper.open(
        appInfo,
        onDeviceArrival: (CaptureHelperDevice device) {},
        onDeviceRemoval: (CaptureHelperDevice device) {},
        onDecodedData: (DecodedData data, CaptureHelperDevice device) {},
        onError: (CaptureException e) {},
      );
    } on CaptureException catch (e) {
      debugPrint('Open failed: ${e.code} ${e.message}');
    }
  }

  @override
  void dispose() {
    helper.close();
    super.dispose();
  }
}
```

## JavaScript

```javascript
import { useEffect } from 'react';
import { CaptureHelper } from 'react-native-capture';

const appInfo = {
  appIdIos: '[YOUR IOS APP ID]',
  appKeyIos: '[YOUR IOS APP KEY]',
  appIdAndroid: '[YOUR ANDROID APP ID]',
  appKeyAndroid: '[YOUR ANDROID APP KEY]',
  developerId: '[YOUR DEVELOPER ID]',
};

useEffect(() => {
  const helper = new CaptureHelper({
    appInfo
  });

  helper
    .open()
    .then(() => {})
    .catch(() => {});

  return () => {
    helper.close().catch(() => {});
  };
}, []);
```

On Android, the Java and Kotlin tabs above show the recommended `capture-android` integration. The plain Java SDK can also be used directly without `capture-android` — see the [Plain Java SDK](/docs/appendix/plain-java-sdk) appendix.

Capture Helper provides a set of events that can be chosen from in order to implement only the events required by your application.

Here is a brief descriptions of these events:

## Capture Helper Events

### Device Presence

This defines the **arrival** and **removal** events for a physical device or the camera scanning SocketCam, considered also as a device.

The device arrival occurs when a scanner is connected to the host. The device removal occurs when the scanner disconnects from the host.

### Device Discovery

This defines **devices discovery** and the **end of the discovery** events occurring during a discovery operation.

Please refer to [Connect to our products with CaptureSDK](/docs/connect-bluetooth-le-devices#add-bluetooth-low-energy-or-bluetooth-classic-devices-discovery).

### Device Decoded Data

This defines the **decoded data** event that CaptureSDK calls each time a scanner decodes a barcode or a contactless reader/writer decodes a NFC/RFID tag.

This event is called with the decoded data and the information related to the barcode such as the barcode symbology or the Tag such as Tag type.

### Device Power

This defines the **power state** and **battery level** events.

Both can be received from the scanner if it is configured to send these notifications.

### Buttons states

This defines the scanner's **buttons states** events.

### Error

CaptureSDK can send this notification in case of unexpected errors. Overall, errors are received in completion handlers when you call the main API functions.

### Logger (iOS only)

This event defines the didReceiveLogTrace delegate that CaptureSDK calls each time a log message is generated. This delegate can be used for debugging and analysis purpose.

Here is a typical initialization and implementation of those events:

## Java

```java
private static final String TAG = "Capture";

// Service connection state (Capture Helper open/close lifecycle)

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onCaptureServiceConnectionStateChange(ConnectionStateEvent event) {
    ConnectionState state = event.getState();
    CaptureClient client = event.getClient();

    if (state.hasError()) {
        Log.e(TAG, "Capture service error: " + state.getError().getMessage());
        return;
    }

    switch (state.intValue()) {
        case ConnectionState.CONNECTING:
        case ConnectionState.CONNECTED:
        case ConnectionState.READY:
        case ConnectionState.DISCONNECTING:
        case ConnectionState.DISCONNECTED:
            Log.d(TAG, "Capture service state: " + state);
            break;
    }
}

// Device Presence (arrival / removal / ready)

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onCaptureDeviceStateChange(DeviceStateEvent event) {
    DeviceClient device = event.getDevice();
    DeviceState state = event.getState();

    switch (state.intValue()) {
        case DeviceState.READY:
            Log.d(TAG, "Device ready: " + device.getDeviceType());
            break;
        case DeviceState.GONE:
            Log.d(TAG, "Device gone");
            break;
        default:
            Log.d(TAG, "Device state: " + state);
    }
}

// Device Decoded Data

@Subscribe(threadMode = ThreadMode.MAIN)
public void onScan(DataEvent event) {
    DecodedData data = event.getData();
    Log.d(TAG, "Decoded Data: " + data.getString());
}
```

## Kotlin

```kotlin
companion object {
    private const val TAG = "Capture"
}

// Service connection state (Capture Helper open/close lifecycle)

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onCaptureServiceConnectionStateChange(event: ConnectionStateEvent) {
    val state: ConnectionState = event.state
    val client: CaptureClient = event.client

    if (state.hasError()) {
        Log.e(TAG, "Capture service error: ${state.error.message}")
        return
    }

    when (state.intValue()) {
        ConnectionState.CONNECTING,
        ConnectionState.CONNECTED,
        ConnectionState.READY,
        ConnectionState.DISCONNECTING,
        ConnectionState.DISCONNECTED -> Log.d(TAG, "Capture service state: $state")
    }
}

// Device Presence (arrival / removal / ready)

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
fun onCaptureDeviceStateChange(event: DeviceStateEvent) {
    val device: DeviceClient = event.device
    val state: DeviceState = event.state

    when (state.intValue()) {
        DeviceState.READY -> Log.d(TAG, "Device ready: ${device.deviceType}")
        DeviceState.GONE -> Log.d(TAG, "Device gone")
        else -> Log.d(TAG, "Device state: $state")
    }
}

// Device Decoded Data

@Subscribe(threadMode = ThreadMode.MAIN)
fun onScan(event: DataEvent) {
    val data: DecodedData = event.data
    Log.d(TAG, "Decoded Data: ${data.string}")
}
```

## Swift

```swift
import UIKit
import CaptureSDK

extension MyViewController: UIViewController,
    CaptureHelperDevicePresenceDelegate,
    CaptureHelperDeviceDecodedDataDelegate,
    CaptureHelperDevicePowerDelegate,
    CaptureHelperErrorDelegate,
    CaptureHelperLoggerDelegate {

    // MARK: - CaptureHelperDevicePresenceDelegate

    func didNotifyArrivalForDevice(_ device: CaptureHelperDevice, withResult result: SKTResult) {
        print("didNotifyArrivalForDevice: \(String(describing: device.deviceInfo.name))")
    }
    
    func didNotifyRemovalForDevice(_ device: CaptureHelperDevice, withResult result: SKTResult) {
        print("didNotifyRemovalForDevice: \(String(describing: device.deviceInfo.name!))")
    }

    // MARK: - CaptureHelperDeviceDecodedDataDelegate
    // This delegate is called each time a decoded data is read from the scanner.
    // It has a result field that should be checked before using the decoded data.
    // It would be set to SKTCaptureErrors.E_CANCEL if the user taps on the cancel button in the SocketCam View Finder

    func didReceiveDecodedData(_ decodedData: SKTCaptureDecodedData?, fromDevice device: CaptureHelperDevice, withResult result: SKTResult) {
        if result == SKTCaptureErrors.E_NOERROR {
            if let string = decodedData?.stringFromDecodedData()! {
                print("Decoded Data \(String(describing: string))")
            }
        }
    }

    // MARK: - CaptureHelperDevicePowerDelegate

    func didChangeBatteryLevel(_ batteryLevel: Int, forDevice device: CaptureHelperDevice){
        print("Battery level did change: \(SKTHelper.getCurrentLevel(fromBatteryLevel: Int(batteryLevel)))% for device \(String(describing: device.deviceInfo.name!))")
    }

    func didChangePowerState(_ powerState: SKTCapturePowerState, forDevice device: CaptureHelperDevice) {
        print("Receive a power state change \(powerState) for device \(String(describing: device.deviceInfo.name!))")
    }

    // MARK: - CaptureHelperErrorDelegate

    func didReceiveError(_ error: SKTResult) {
        print("Receive a CaptureSDK error: \(error)")
    }

    // MARK: - CaptureHelperLoggerDelegate

    func didReceiveLogTrace(_ logTrace: String) {
        print(logTrace)
    }

}
```

## C#

```csharp
// Device Presence

void CaptureDeviceArrival(object sender, CaptureHelper.DeviceArgs e)
{
    Console.WriteLine($"Device arrival: {e.CaptureDevice.GetDeviceInfo().Name}");
}

void CaptureDeviceRemoval(object sender, CaptureHelper.DeviceArgs e)
{
    Console.WriteLine($"Device removal: {e.CaptureDevice.GetDeviceInfo().Name}");
}

// Device Decoded Data

void CaptureDecodedData(object sender, CaptureHelper.DecodedDataArgs e)
{
    if (SktErrors.SKTSUCCESS(e.Result))
    {
        Console.WriteLine($"Decoded Data: {e.DecodedData.DataToUTF8String}");
    }
}

// Device Power

void CaptureDevicePowerState(object sender, CaptureHelper.PowerStateArgs e)
{
    Console.WriteLine($"Power state changed: {e.State}");
}

// Error

void CaptureErrors(object sender, CaptureHelper.ErrorEventArgs e)
{
    Console.WriteLine($"Receive a CaptureSDK error: {e.Message}");
}

// Terminate

void CaptureTerminate(object sender, EventArgs e)
{
    Console.WriteLine("Capture has been terminated");
}
```

## Dart

```dart
await helper.open(
  appInfo,

  // Device Presence
  onDeviceArrival: (CaptureHelperDevice device) {
    debugPrint('Device arrival: ${device.name}');
  },
  onDeviceRemoval: (CaptureHelperDevice device) {
    debugPrint('Device removal: ${device.name}');
  },

  // Device Decoded Data
  onDecodedData: (DecodedData data, CaptureHelperDevice device) {
    debugPrint('Decoded Data from ${device.name}: ${data.data}');
  },

  // Device Power
  onBatteryLevel: (int level, CaptureHelperDevice device) {
    debugPrint('Battery level: $level%');
  },
  onPowerState: (PowerState state, CaptureHelperDevice device) {
    debugPrint('Power state: $state');
  },

  // Error
  onError: (CaptureException e) {
    debugPrint('CaptureSDK error: ${e.code} ${e.message}');
  },
);
```

## JavaScript

```javascript
import { CaptureHelper } from 'react-native-capture';

// appInfo declared above (see Initialization)

const helper = new CaptureHelper({
  appInfo,

  // Device Presence
  onDeviceArrival: (device) => {
    console.log(`Device connected: ${device.name}`);
  },
  onDeviceRemoval: (device) => {
    console.log(`Device disconnected: ${device.name}`);
  },

  // Device Decoded Data
  onDecodedData: (data, device) => {
    console.log(`Decoded Data from ${device.name}: ${data.data}`);
  },

  // Device Power
  onBatteryLevel: (level, device) => {
    console.log(`Battery level: ${level}% for ${device.name}`);
  },
  onPowerState: (state, device) => {
    console.log(`Power state ${state} for ${device.name}`);
  },

  // Error
  onError: ({ code, message }) => {
    console.error(`CaptureSDK error ${code}: ${message}`);
  },
});
```

## Minimal Implementation

Assuming the only thing that matters to the application is to receive the decoded data, a minimal implementation could be as simple as:

## Java

```java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Capture.builder(getApplicationContext())
                .enableLogging(BuildConfig.DEBUG)
                .build();
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onScan(DataEvent event) {
        DecodedData data = event.getData();
        Log.d("Capture", "Decoded Data: " + data.getString());
    }
}
```

## Kotlin

```kotlin
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Capture.builder(applicationContext)
            .enableLogging(BuildConfig.DEBUG)
            .build()
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onScan(event: DataEvent) {
        val data: DecodedData = event.data
        Log.d("Capture", "Decoded Data: ${data.string}")
    }
}
```

## Swift

```swift
import UIKit
import CaptureSDK

extension MyViewController: UIViewController, CaptureHelperDeviceDecodedDataDelegate {

    // MARK: - CaptureHelperDeviceDecodedDataDelegate
    // This delegate is called each time a decoded data is read from the scanner.
    // It has a result field that should be checked before using the decoded data.
    // It would be set to SKTCaptureErrors.E_CANCEL if the user taps on the cancel button in the SocketCam View Finder

    func didReceiveDecodedData(_ decodedData: SKTCaptureDecodedData?, fromDevice device: CaptureHelperDevice, withResult result: SKTResult) {
        if result == SKTCaptureErrors.E_NOERROR {
            if let string = decodedData?.stringFromDecodedData()! {
                print("Decoded Data \(String(describing: string))")
            }
        }
    }

}
```

## C#

```csharp
public Form1()
{
    InitializeComponent();

    capture = new CaptureHelper
    {
        ContextForEvents = SynchronizationContext.Current
    };
    capture.DecodedData += CaptureDecodedData;

    string appId = "[YOUR WINDOWS APP ID]";
    string developerId = "[YOUR DEVELOPER ID]";
    string appKey = "[YOUR WINDOWS APP KEY]";

    _ = capture.OpenAsync(appId, developerId, appKey);
}

void CaptureDecodedData(object sender, CaptureHelper.DecodedDataArgs e)
{
    if (SktErrors.SKTSUCCESS(e.Result))
    {
        Console.WriteLine(e.DecodedData.DataToUTF8String);
    }
}
```

## Dart

```dart
final CaptureHelper helper = CaptureHelper();

await helper.open(
  appInfo,
  onDecodedData: (DecodedData data, CaptureHelperDevice device) {
    debugPrint('Decoded Data: ${data.last.dataAsString()}');
  },
);
```

## JavaScript

```javascript
import { useEffect } from 'react';
import { CaptureHelper } from 'react-native-capture';

useEffect(() => {
  const helper = new CaptureHelper({
    appInfo,

    onDecodedData: (data, device) => {
      console.log(`Decoded Data: ${data.data}`);
    },
  });

  helper
    .open()
    .then(() => {})
    .catch(() => {});

  return () => {
    helper.close().catch(() => {});
  };

}, []);
```

You can find more features integrated into our [sample apps on our Github](https://github.com/SocketMobile).