Flutter CaptureSDK - Version 2.1.8

This is the Flutter CatureSDK for Socket Mobile's Capture library.

From 20th of March 2026 - Noticeable change from version 2.0

There are 2 choices to connect and use our Bluetooth LE readers:

  • Install our Socket Mobile Companion app which takes care of the discovery and the selection of the reader to connect to.

  • Write some code to fit the Bluetooth discovery flow to your application's design by creating an UI that starts and shows discovered devices and select them to be paired to your application.

See our Getting Started section in our documentation.

Devices compatibility and CaptureSDK versions

Devices < 2.0 2.0 2.1
SocketCam C860 ✅ ✅ ✅
SocketCam C820 ✅ ✅ ✅
S720/D720/S820 ✅ ✅ ✅
D600, S550, and all other barcode scanners ✅ ✅ ✅
S370 ✅ ✅ ✅
S320 ✅ ✅ ✅
S721 (new Bluetooth LE barcode scanner) ❌ ✅ ✅
Magic Dock and SM Link ❌ ❌ ✅

What's new in version 2.1

Those USB-C devices allows you to connect our Bluetooth Low Energy scanners directly to a device without any additional line of code other than implementing the current Flutter CaptureSDK. It handles the connection and you can use our scanners out of the box.

Installation

Install the flutter package by adding the following to your pubspec.yaml file.

dependencies:
  ...
  capturesdk_flutter: ^2.1.8
  ...

Then, in your application's main.dart, you can import the Flutter CaptureSDK by adding this line to the top of your file.

import 'package:capturesdk_flutter/capturesdk.dart';

For the rest of the things to add in your project, go to iOS and Android.

Getting started iOS (first section) - Important note

CocoaPods is soon to be deprecated. Flutter is replacing CocoaPods with Swift Package Manager (SPM), and this plugin supports SPM (Flutter 3.44+, on by default from 3.47). To migrate your app:

  1. cd ios && pod deintegrate
  2. Delete Podfile, Podfile.lock and Pods/
  3. Remove the Pods-Runner #include lines from ios/Flutter/Debug.xcconfig and ios/Flutter/Release.xcconfig and other configurations you may have
  4. Run flutter run: Flutter adds the SPM integration to your Xcode project

More details in Flutter's SPM guide.

Getting started

Create a Capture instance with the line Capture capture = Capture(logger);. logger is an optional argument that is passed to Capture and can be helpful with tracing values and requests throughout the app, particularly in Capture and HttpTransport where the bulk of the Capture logic and requests are handled.

Open the connection wth the Capture library by using the open method on the Capture instance. See below.

int? response = await capture.openClient(appInfo, _onCaptureEvent);
stat = 'handle: $response';
mess = 'capture open success';

appInfo is an instance of the AppInfo class and consists of the same parameters found in other CaptureSDKs. See an example of appInfo below.

final appInfo = AppInfo(
        'android:com.example.example',
        'MC4CFQDNCtjazxILEh8oyT6w/wlaVKqS1gIVAKTz2W6TB9EgmjS1buy0A+3j7nX4',
        'ios:com.example.example',
        'MC0CFA1nzK67TLNmSw/QKFUIiedulUUcAhUAzT6EOvRwiZT+h4qyjEZo9oc0ONM=',
        'bb57d8e1-f911-47ba-b510-693be162686a');

To generate app info, head to the docs and follow the prompts to register your app and generate your appInfo credentials. See the important section at the end of the README for more information specific to Flutter.

Next, _onCaptureEvent is the callback passed to open that can handle the event notifications from the CaptureSDK. Below are three important events to consider, which are accessible in the CaptureEventIds class.

deviceArrival is the event that is triggered when the scanner connects to your device.

deviceRemoval is the event that is triggered when the scanner is disconnected from the device.

decodedData is the event that is triggered when you scan something with the connected scanner.

See an example of event handling below in _onCaptureEvent.

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

        setState(() {
          _deviceCapture = deviceCapture;
        });

        _openDeviceHelper(deviceCapture, e);
        break;
      case CaptureEventIds.deviceRemoval:
        _closeDeviceHelper(e, handle);
        break;

      case CaptureEventIds.decodedData:
        setState(() {
          /// storing scanned data in state for future use
          _currentScan = e;
        });
        _updateVals('Decoded Data', "Successful scan!");
        break;
    }
  }

The data the user will need to anticipate will be a CaptureEvent which might contain an instance of DecodedData, CaptureException, a client or device handle, etc. When you first connect to the service, the response will be a client handle (an integer) or a CaptureException instance will be thrown.

It's important to create another Capture instance when you have successfully connected the scanner to your device (see var newCapture = Capture(logger);). This capture instance is tied to your device and will allow the root capture instance to remain open, regardless of what happens with your device. The new instance allows you to create a capture connection to the device to handle various actions specific to the connected device, such as getProperty and setProperty.

Getting started iOS

Your app's iOS deployment target must be 15.0 or later. Go to ios/Runner/Info.plist and at the bottom, just above </dict>, include the below code.

<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth is needed to connect to a Socket Mobile device</string>
<key>UISupportedExternalAccessoryProtocols</key>
<array>
  <string>com.socketmobile.chs</string>
</array>
<key>NSCameraUsageDescription</key>
  <string>Need to enable camera access for SocketCam</string>
<key>LSApplicationQueriesSchemes</key>
<array>
  <string>sktcompanion</string>
</array>

For SocketCam C860 which is an enhanced version of SocketCam C820, you also need to add the following key to your Info.plist: LSApplicationQueriesSchemes (Queried URL Schemes) with a new item: sktcompanion (in lower case).

In order to use it you have to install Socket Mobile Companion on your device.

You can find more details about SocketCam C860 on our website.

Getting started Android

You will need to update the network configuration to enable the Android Capture client. You can find out more about network configuration.

In order to pass the internet permissions, you need to have the below line in your Android manifest.

<uses-permission android:name="android.permission.INTERNET" />

In order to use SocketCam C820, you will need to add the below to your Android manifest.

    <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}"/>

Where it says YOUR_APP_KEY, you need to include the android app key that you got for your app when you registered it. Where it says YOUR_DEVELOPER_ID is the developer ID you use for your socket mobile developer portal.

ALSO: The package name in AndroidManifest.xml, it needs to be both all lowercase and must match your Bundle ID that you have in your app's registration information in your dev portal.

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.yourpackagename">

In the MainActivity.java file, register the CaptureSDK as a plugin:

package com.example.example; // Replace with your app's package name

import com.capturesdk_flutter.CaptureModule; // import CaptureModule Native Modules
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;

public class MainActivity extends FlutterActivity {
    @Override
    public void configureFlutterEngine(FlutterEngine flutterEngine) {
        flutterEngine.getPlugins().add(new CaptureModule(getApplicationContext())); // register CaptureSDK as a plugin here
    }
}

In your app's builde.gradle file add the 2 following options:

buildTypes {
    release {
        minifyEnabled false       <------- to add
        shrinkResources false     <------- to add
        signingConfig = signingConfigs.debug
    }
}

Enable Start Capture Service on Android

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 their app's AndroidManifest.xml file, the developer will need to add the below property into the <application> tag.

android:networkSecurityConfig="@xml/network_security_config"

Finally, add the below line into just before the AndroidManifest.xml file's closing </manifest> tag.

<queries>
    <package android:name="com.socketmobile.companion"/>
  </queries>

For more on the network security configuration for Android, please check out the cleartext section in the Android docs.

Important

To register your app for Flutter, you can select the Flutter language first, and then you can pick both platform options; Android and iOS. Thanks to our brand new Developer Portal, it will then generate the app keys credentials for both Android and iOS apps. By default the Bundle ID / package name is the same, which is a good practice to follow.

iOS app registration

All you need to do is inlcude the iOS and Android appKey and appId, respectively, to the same AppInfo instance in your Flutter source code.

final appInfo = AppInfo(
        'android:com.example.example',
        'MC4CFQDNCtjazxILEh8oyT6w/wlaVKqS1gIVAKTz2W6TB9EgmjS1buy0A+3j7nX4',
        'ios:com.example.example',
        'MC0CFA1nzK67TLNmSw/QKFUIiedulUUcAhUAzT6EOvRwiZT+h4qyjEZo9oc0ONM=',
        'bb57d8e1-f911-47ba-b510-693be162686a');

In order for it to work, you will need to add five argments to AppInfo in the following order:

  1. Android appId
  2. AppKey after Android registration
  3. iOS appId
  4. AppKey after iOS registration
  5. Developer ID

SocketCam

SocketCam turns the device's built-in camera into a barcode scanner. The SDK supports two display modes:

Full-screen mode (default)

The simplest integration. Call setTrigger(Trigger.start) on the SocketCam device and the SDK handles the camera UI:

  • iOS: call SocketCamView.presentFullScreen() after the trigger to present the native view controller.
  • Android: the SDK opens its own full-screen camera Activity automatically — no extra code needed.
await device.setTrigger(Trigger.start);
if (Platform.isIOS) {
  await SocketCamView.presentFullScreen();
}

Custom view mode

Embeds the camera preview inside your own Flutter layout via the SocketCamView widget.

  • iOS: works out of the box — mount SocketCamView(device: device) anywhere in your widget tree.
  • Android: requires changes to the native CaptureModule.java (see below).

Switching between modes on Android

On Android, the camera mode is determined at build time by the CaptureExtension configuration. CaptureExtension is a singleton — the first build() call locks in the configuration for the process lifetime. You cannot switch between full-screen and custom view at runtime.

To migrate from full-screen to custom view on Android:

  1. In android/.../CaptureModule.java, uncomment the CustomViewListener code in buildAndStartExtension() and the related fields and methods (search for "Custom view mode" comments).
  2. In your Dart code, mount a SocketCamView widget (minimum 400×400 pixels) and call setTrigger(Trigger.start) on the device.

To migrate from custom view back to full-screen on Android:

  1. In CaptureModule.java, comment out the CustomViewListener code in buildAndStartExtension() and the related fields/methods.
  2. In your Dart code, remove the SocketCamView widget. Just call setTrigger(Trigger.start) — the SDK will open its camera Activity.

Note: On iOS, both modes coexist and can be switched at runtime without any native code changes.

For more information about the Flutter CaptureSDK, please visit the documentation.

For a full demonstration, check out this video.

Build & Run Instructions

  1. cd example
  2. Run flutter pub get

Then you can run the app through Android Studio and Xcode or through Visual Studio Code directly with the debugger module. See this page for more details: https://docs.flutter.dev/tools/vs-code#running-and-debugging.

Android

  1. cd android
  2. Run flutter run or open the project in Android Studio to run on connected iOS device.

iOS

The example uses SPM (not CocoaPods anymore). Run flutter run, or open ios/Runner.xcworkspace in Xcode, to run on a connected iOS device.

Bluetooth Classic picker on iOS (UIScene)

With the UIScene lifecycle (Apple makes it mandatory after iOS 26), the system picker shown by addBluetoothDevice(mode: BluetoothDiscoveryMode.bluetoothClassic) stays invisible: EAAccessoryManager only creates its window when the app delegate has one. Once your app is migrated to UIScene, add a scene delegate that gives the app delegate the scene's window.

ios/Runner/SceneDelegate.swift (add it to the Runner target in Xcode):

import UIKit
import Flutter

class SceneDelegate: FlutterSceneDelegate {
  override func scene(
    _ scene: UIScene,
    willConnectTo session: UISceneSession,
    options connectionOptions: UIScene.ConnectionOptions
  ) {
    super.scene(scene, willConnectTo: session, options: connectionOptions)
    (UIApplication.shared.delegate as? FlutterAppDelegate)?.window = window
  }
}

ios/Runner/Info.plist, in UIApplicationSceneManifest, point the scene to it:

<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>

On iOS versions before 26.5, the plugin also moves the picker window into the active scene for you. See the example app.