> ## Documentation Index
> Fetch the complete documentation index at: https://seam-feat-mintlify-migration-iteration.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Using Unlock With Tap

> This feature enables your app to perform unlock operations using mobile credentials.

Unlocking with SeamSDK uses proximity to communicate credentials to door readers. When a user initiates an unlock, the SDK performs scanning, error handling, and unlock events using `Seam.shared.unlock(using:)`.

Using this process involves the following steps:

1. Retrieve available mobile credentials.
2. Monitor for permission or credential errors. Unhandled errors are thrown as `credentialErrors([SeamCredentialError])`.
3. Perform the unlock operation.
4. Handle unlock status events.
5. Cancel the unlock operation if needed.

***

## 1. Retrieve Mobile Credentials

Access the current credentials through the published `credentials` array on `Seam.shared`:

<Tabs>
  <Tab title="iOS Swift">
    ```swift theme={null}
    import SeamSDK
    import Combine

    // Access credentials
    let credentials = Seam.shared.credentials

    // Observe updates with Combine
    let credentialsSubscription = Seam.shared.$credentials
    .sink { creds in
    // Update UI with new credentials array.
    }

    ```
  </Tab>

  <Tab title="Android Kotlin">
    ```kotlin theme={null}
    import co.seam.core.api.SeamCredential
    import co.seam.core.api.SeamSDK

    // Collect credentials
    SeamSDK.getInstance().credentials.collect { credentials ->
        // Update UI with new credentials array.
    }
    ```
  </Tab>
</Tabs>

## 2. Monitor Credential Errors

Permission or setup issues appear in each credential’s `errors` property. Observe like this:

<Tabs>
  <Tab title="iOS Swift">
    ```swift theme={null}
    import SeamSDK
    import Combine

    let errorSubscription = Seam.shared.$credentials
    .flatMap { creds in creds.publisher }
    .map { $0.errors }
    .sink { errors in
    // Handle errors, for example, `.userInteractionRequired`, `.expired`, etc.
    }

    ```
  </Tab>

  <Tab title="Android Kotlin">
    ```kotlin theme={null}
    import co.seam.core.api.SeamCredential
    import co.seam.core.api.SeamSDK
    import co.seam.core.sdkerrors.SeamCredentialError
    import co.seam.core.sdkerrors.SeamError
    import co.seam.core.sdkerrors.SeamRequiredUserInteraction

    SeamSDK.getInstance().credentials.collect { credentialsList ->
        val errors = credentialsList.flatMap { it.errors }
        errors.forEach { error ->
            when (error) {
                is SeamCredentialError.Expired -> { /* handle credential expiration error */}
                is SeamCredentialError.Loading -> { /* handle not loaded yet */ }
                is SeamCredentialError.Unknown -> { /* handle unknown error */}
                is SeamCredentialError.UserInteractionRequired -> {
                    handleUserInteractionRequired(error.interaction)
                }
            }
        }
    }

    fun handleUserInteractionRequired(interaction: SeamRequiredUserInteraction) {
        when (interaction) {
            is SeamRequiredUserInteraction.CompleteOtpAuthorization -> { /* handle OTP authorization */ }
            is SeamRequiredUserInteraction.EnableBluetooth -> { /* handle Bluetooth error */ }
            is SeamRequiredUserInteraction.EnableInternet -> { /* handle Internet connection error*/ }
            is SeamRequiredUserInteraction.GrantPermissions -> { /* handle permissions error*/ }
        }
    }
    ```
  </Tab>
</Tabs>

> **Note:** The `.errors` array on credentials represents per-credential issues, though some issues may be repeated across several credentials (for example, bluetooth requirements). SDK- or credential-level errors (such as invalid token or expired credential) are thrown directly by methods like `unlock(using:)` because `SeamError` or `SeamCredentialError` and must be handled through `do/catch`.

## 3. Perform Unlock Operation

The call to `Seam.shared.unlock(using:)` may throw:

* `SeamError`: For SDK-level issues (for example, invalid token, uninitialized SDK).
* `SeamCredentialError`: For credential-specific issues (for example, expired credential, device not eligible). Ensure that you wrap the call in `do/catch` blocks to handle these errors.

Use Async/Await or Combine to initiate an unlock with a selected credential:

<Tabs>
  <Tab title="iOS Swift">
    <Info>
      If the Apple Wallet dialog appears when using the BLE unlock, see [Handling System Permissions](/capability-guides/mobile-access/mobile-device-sdks/handling-system-permissions#platform-setup-requirements) for steps to prevent it and ensure a smoother unlockWithTap experience.
    </Info>

    ```swift theme={null}
    import SeamSDK
    import Combine

    let credentialId = credentials.first!.id

    // Async/Await example
    Task {
        do {
            for try await event in try Seam.shared.unlock(using: credentialId).values {
                switch event {
                case .grantedAccess:
                    // The lock granted access. Show a success indicator.
                default:
                    print("Unlock event: \(event)")
                    // Access wasn't granted. Inform the user and offer retry.
                }
            }
        } catch {
            // Handle unlock error, for example, invalid credential or SDK error
            print("Unlock error: \(error)")
        }
    }

    // Combine example
    do {
        let unlockPublisher = try Seam.shared.unlock(using: credentialId)
        let unlockSubscription = unlockPublisher.sink(
            receiveCompletion: { _ in
                // Unlock completed.
            },
            receiveValue: { event in
                switch event {
                case .grantedAccess:
                    // The lock granted access—show a success indicator.
                default:
                    // Access wasn't granted, inform the user and offer retry.
                    print("Unlock event: \(event)")
                }
            }
        )
        // Retain `unlockSubscription`; discarding it will cancel the unlock attempt.
    } catch {
        // Handle unlock initialization error.
        print("Unlock error: \(error)")
    }
    ```
  </Tab>

  <Tab title="Android Kotlin">
    ```kotlin theme={null}
    import co.seam.core.api.SeamCredential
    import co.seam.core.api.SeamSDK
    import co.seam.core.events.SeamUnlockEvent
    import co.seam.core.sdkerrors.SeamCredentialError
    import co.seam.core.sdkerrors.SeamError
    import co.seam.core.sdkerrors.SeamRequiredUserInteraction

    val seamSDK = SeamSDK.getInstance()

    // Perform unlock
    try {
    val credentialId = credential.id
    // Timeout is optional
    seamSDK.unlock(
    credentialId = credentialId,
    timeout = 30.seconds
    )
    } catch (seamError: SeamError) {
    when (seamError) {
    is SeamError.ActivationRequired -> {
    // handle error when SDK is not activated
    }
    is SeamError.CredentialErrors -> {
    val credentialErrors = seamError.errors
    handleCredentialErrors(credentialErrors)
    // handle error when there are credential errors
    }
    is SeamError.InitializationRequired -> {
    // handle error when SDK is not initialized
    }
    is SeamError.IntegrationNotFound -> {
    // handle error when integration is not found, Such as Assa Abloy, Latch and Salto
    }
    is SeamError.InternetConnectionRequired -> {
    // handle error when internet connection is required
    }
    is SeamError.InvalidClientSessionToken -> {
    // handle error when client session token is invalid
    }
    else -> {
    // handle other errors
    }
    }
    }

    // Handle credential errors on unlock
    fun handleCredentialErrors(credentialErrors: List<SeamCredentialError>) {
    credentialErrors.forEach { credentialError ->
    when (credentialError) {
    is SeamCredentialError.Invalid -> {
    // handle error when credential is invalid
    }

                is SeamCredentialError.Expired -> {
                    // handle error when credential is expired
                }

                is SeamCredentialError.Loading -> {
                    // handle error when credential is not loaded yet
                }

                is SeamCredentialError.UserInteractionRequired -> {
                    // handle user interaction required credential error
                }

                is SeamCredentialError.Unknown -> {
                    // handle unknown credential error
                }
            }
        }

    }

    ```
  </Tab>
</Tabs>

***

## 4. Handle Unlock Events

Handle each `SeamUnlockEvent` to update your UI and logic. Available events:

* **launched**\
  Unlock operation has started.
* **grantedAccess**\
  Access was granted by the lock.
* **timedOut**\
  Unlock operation timed out without success.
* **connectionFailed(debugDescription:)**\
  Unlock operation failed to connect; `debugDescription` may contain additional details.

<Tabs>
  <Tab title="iOS Swift">
    Async/Await example:

    ```swift theme={null}
    Task {
        do {
            for try await event in Seam.shared.unlock(using: credentialID) {
                switch event {
                case .launched:
                    // Show scanning indicator.
                case .grantedAccess:
                    // Show access granted.
                case .timedOut:
                    // Show timeout and offer retry.
                case .connectionFailed(let debugDescription):
                    // Show error with debugDescription.
                }
            }
        } catch {
            // Handle thrown errors.
        }
    }
    ```

    Combine example:

    ```swift theme={null}
    do {
        let unlockPublisher = try Seam.shared.unlock(using: credentialID)
        let unlockSubscription = unlockPublisher
            .receive(on: RunLoop.main)
            .sink(
                receiveCompletion: { _ in
                    // unlock completed.
                },
                receiveValue: { event in
                    switch event {
                    case .launched:
                        // Show scanning indicator.
                    case .grantedAccess:
                        // Show success indicator.
                    case .timedOut:
                        // Operation timed out—offer retry.
                    case .connectionFailed(let debugDescription):
                        // Show debugDescription details.
                    }
                }
            )
        // Retain `unlockSubscription` as a property and cancel when appropriate (for example, in deinit or viewWillDisappear).
    } catch {
        // Handle unlock initialization error.
        print("Unlock initialization error: \(error)")
    }
    ```
  </Tab>

  <Tab title="Android Kotlin">
    ```kotlin theme={null}
    // Start collecting unlock events before unlock
    coroutineScope.launch {
        seamSDK.unlockStatus.collect { event ->
            when (event) {
                is SeamUnlockEvent.ScanningStarted -> { /* handle scanning started */}
                is SeamUnlockEvent.Connecting -> { /* handle connecting */}
                is SeamUnlockEvent.AccessGranted -> { /* handle access granted */}
                is SeamUnlockEvent.Timeout -> { /* handle timeout */}
                is SeamUnlockEvent.ReaderError -> { /* handle reader error */}
                else -> { /* handle other events */}
            }
        }
    }
    ```
  </Tab>
</Tabs>

## 5. Cancel the Unlock Operation

Stop scanning by canceling your active task or subscription.

<Tabs>
  <Tab title="iOS Swift">
    ```swift theme={null}
    // For Combine
    unlockSubscription.cancel()

    // For Async/Await, cancel the Task as needed.

    ```
  </Tab>

  <Tab title="Android Kotlin">
    ```kotlin theme={null}
    val seamSDK = SeamSDK.getInstance()
    // The unlock function returns a Job
    val job = seamSDK.unlock(
        credentialId = credentialId,
        timeout = 30.seconds
    )

    // Canceling the job stops the unlock process
    job.cancel()
    ```
  </Tab>
</Tabs>
