# Plain Java SDK

Integrate Socket Mobile barcode scanning and NFC reading on Android using the plain Java CaptureSDK (com.socketmobile:capture) directly, without the capture-android wrapper. Covers when to choose this path, the CaptureClient and Configuration setup with AppKey credentials, connecting with a ConnectionCallback, receiving events through CaptureClient.Listener (device arrival, decoded data, battery, power, errors), Bluetooth LE discovery with DiscoveryListener, opening devices manually and threading considerations. Includes Java and Kotlin code samples.

For most Android applications, the recommended integration is the **capture-android** library described in [Capture Helper](/docs/using-capturesdk/capture-helper): it manages the service lifecycle for you and delivers events through EventBus subscriptions tied to your Activities.

The plain Java SDK (**com.socketmobile:capture**) can also be used directly, without **capture-android**. It is a good candidate when the CaptureSDK lives in a layer of your architecture that does not have an Activity, or does not follow the regular Android application lifecycle — for example a background service or a shared business layer.

With this path your application talks to **CaptureClient** directly and receives events through plain listener interfaces instead of EventBus.

> **Only the startup differs**
>
> All Java and Kotlin examples elsewhere in this documentation apply to both integration paths — only the initialization and the event registration shown on this page differ. Everything else (triggering, symbologies, properties, device features) uses the same <b>CaptureClient</b> and <b>DeviceClient</b> methods.

## Dependency

Replace the **capture-android** dependency with the plain **capture** module (EventBus is not required for this integration path):

```groovy
dependencies {
    implementation 'com.socketmobile:capture:2.0.x'
}
```

The AndroidManifest.xml and Network Security Config requirements from the [Android getting started page](/docs/getting-started) still apply, but the AppKey is passed in code instead of the manifest metadata.

## Initialization

Create a **Configuration** with your application credentials, create the **CaptureClient**, register your listener and connect. The connection result is reported to the **ConnectionCallback**:

## Java

```java
AppKey appKey = new AppKey("[YOUR ANDROID APP KEY]", "[YOUR ANDROID APP ID]", "[YOUR DEVELOPER ID]");

Configuration configuration = new Configuration()
        .appKey(appKey)
        .setContext(getApplicationContext())
        .enableLogging(Level.INFO);

CaptureClient client = new CaptureClient(configuration);

// Register the listener before connecting (see the events code below)
client.setListener(captureListener);

client.connect(new ConnectionCallback() {
    @Override
    public void onConnectionStateChanged(ConnectionState state) {
        // CONNECTING, CONNECTED, READY, DISCONNECTING, DISCONNECTED
        if (state.hasError()) {
            // Handle the error, e.g. Companion service not running
        }
    }
});

// When your component shuts down:
// client.disconnect();
```

## Kotlin

```kotlin
val appKey = AppKey("[YOUR ANDROID APP KEY]", "[YOUR ANDROID APP ID]", "[YOUR DEVELOPER ID]")

val configuration = Configuration()
    .appKey(appKey)
    .setContext(applicationContext)
    .enableLogging(Level.INFO)

val client = CaptureClient(configuration)

// Register the listener before connecting (see the events code below)
client.setListener(captureListener)

client.connect { state ->
    // CONNECTING, CONNECTED, READY, DISCONNECTING, DISCONNECTED
    if (state.hasError()) {
        // Handle the error, e.g. Companion service not running
    }
}

// When your component shuts down:
// client.disconnect()
```

Call **disconnect()** when your component shuts down. To retry the initial connection automatically, use **connect(maxRetries, callback)**.

## Receiving the events

All CaptureSDK notifications are delivered through a single **CaptureClient.Listener** registered with **setListener()**:

## Java

```java
CaptureClient.Listener captureListener = new CaptureClient.Listener() {

    @Override
    public void onDeviceManagerStateEvent(DeviceManagerStateEvent event) {
        // The device manager drives the Bluetooth LE devices.
        // Open it when it becomes available.
        if (event.getState().intValue() == DeviceState.AVAILABLE) {
            event.getDevice().open();
        }
    }

    @Override
    public void onDeviceStateEvent(DeviceStateEvent event) {
        switch (event.getState().intValue()) {
            case DeviceState.AVAILABLE:
                // Devices are not opened automatically: open them here
                event.getDevice().open();
                break;
            case DeviceState.READY:
                // Device is connected and ready to use
                break;
            case DeviceState.GONE:
                // Device has disconnected
                break;
            default:
                break;
        }
    }

    @Override
    public void onData(DataEvent event) {
        // Callbacks are not delivered on the main thread:
        // post to it before touching the UI
        String decodedData = event.getData().getString();
    }

    @Override
    public void onBatteryLevelChanged(BatteryLevelEvent event) {
        // Battery level notification (if enabled on the device)
    }

    @Override
    public void onPowerStateChanged(PowerStateEvent event) {
        // Power state notification (if enabled on the device)
    }

    @Override
    public void onError(CaptureError error) {
        // Handle unexpected errors
    }
};
```

## Kotlin

```kotlin
val captureListener = object : CaptureClient.Listener {

    override fun onDeviceManagerStateEvent(event: DeviceManagerStateEvent) {
        // The device manager drives the Bluetooth LE devices.
        // Open it when it becomes available.
        if (event.state.intValue() == DeviceState.AVAILABLE) {
            event.device.open()
        }
    }

    override fun onDeviceStateEvent(event: DeviceStateEvent) {
        when (event.state.intValue()) {
            DeviceState.AVAILABLE -> {
                // Devices are not opened automatically: open them here
                event.device.open()
            }
            DeviceState.READY -> {
                // Device is connected and ready to use
            }
            DeviceState.GONE -> {
                // Device has disconnected
            }
        }
    }

    override fun onData(event: DataEvent) {
        // Callbacks are not delivered on the main thread:
        // post to it before touching the UI
        val decodedData = event.data.string
    }

    override fun onBatteryLevelChanged(event: BatteryLevelEvent) {
        // Battery level notification (if enabled on the device)
    }

    override fun onPowerStateChanged(event: PowerStateEvent) {
        // Power state notification (if enabled on the device)
    }

    override fun onError(error: CaptureError) {
        // Handle unexpected errors
    }
}
```

> **Differences from capture-android**
>
> Two things the <b>capture-android</b> wrapper normally does are now your application's responsibility:
>
> <b>Opening devices</b> — when a device (or the device manager) arrives in the <b>AVAILABLE</b> state, call <b>open()</b> on it as shown above. The device then transitions to <b>READY</b>.
>
> <b>Threading</b> — listener callbacks are not delivered on the main thread. Post to the main thread before updating the UI.

## Bluetooth LE discovery

To discover Bluetooth LE devices, register a **CaptureClient.DiscoveryListener**. The discovery itself is started and the devices are connected with the same **addDevice()**, **connectDevice()** and **removeDevice()** calls described in [Connect Bluetooth LE devices](/docs/connect-bluetooth-le-devices):

## Java

```java
client.setDiscoveryListener(new CaptureClient.DiscoveryListener() {

    @Override
    public void onDeviceDiscovered(DeviceDiscoveryEvent event) {
        DiscoveredDevice device = event.getDiscoveredDevice();
        if (device != null) {
            String name = device.getName();
            String uniqueIdentifier = device.getUniqueIdentifier();
            // Keep the discovered device in a list to show it in your own UI
        }
    }

    @Override
    public void onDiscoveryCompleted(DeviceDiscoveryCompleteEvent event) {
        if (event.isComplete()) {
            // Discovery has ended
        }
    }
});
```

## Kotlin

```kotlin
client.setDiscoveryListener(object : CaptureClient.DiscoveryListener {

    override fun onDeviceDiscovered(event: DeviceDiscoveryEvent) {
        val device = event.discoveredDevice ?: return
        val name = device.name
        val uniqueIdentifier = device.uniqueIdentifier
        // Keep the discovered device in a list to show it in your own UI
    }

    override fun onDiscoveryCompleted(event: DeviceDiscoveryCompleteEvent) {
        if (event.isComplete) {
            // Discovery has ended
        }
    }
})
```

## Mapping from EventBus events

If you are porting code or following the other pages of this documentation, each EventBus subscription of **capture-android** corresponds to one listener callback:

| capture-android (EventBus **@Subscribe**) | Plain Java SDK |
| --- | --- |
| **ConnectionStateEvent** | **ConnectionCallback.onConnectionStateChanged()** |
| **DeviceManagerStateEvent** | **Listener.onDeviceManagerStateEvent()** |
| **DeviceStateEvent** | **Listener.onDeviceStateEvent()** |
| **DataEvent** | **Listener.onData()** |
| **BatteryLevelEvent** | **Listener.onBatteryLevelChanged()** |
| **PowerStateEvent** | **Listener.onPowerStateChanged()** |
| **CaptureError** | **Listener.onError()** |
| **DeviceDiscoveryEvent** | **DiscoveryListener.onDeviceDiscovered()** |
| **DeviceDiscoveryCompleteEvent** | **DiscoveryListener.onDiscoveryCompleted()** |