iOS—Device connection

United States

You can discover devices within Bluetooth (BLE) range to connect to a Clover Go reader, then request a connection. During connection, the Clover Go software development kit (SDK) checks for valid reader and merchant configuration settings and updates firmware and encryption keys as needed.

Prerequisites

Populate the NSBluetoothAlwaysUsageDescription entry in your project configuration before requesting a scan for devices. The iPhone operating system (iOS®) uses the entry to request BLE access on your behalf from your app user.

Steps

1. Discover devices

UseTo
CloverPaymentSDK.shared.scan()Initiate device discovery and receive results.
CloverPaymentSDK.shared.getPreviousConnectedReaders()Get a list of readers that you have successfully connected to in the past.
CloverPaymentSDK.shared.getPreviousConnectedReader()Return the last reader that successfully connected.

Methods

          /// Scan for Bluetooth `CardReader` devices. Automatically scans for 5 seconds, returning the devices it finds in real time.
          /// - Returns: `AsyncThrowingStream` of all readers as they are discovered.
          /// - Throws: `CloverError` and closes the stream if it encounters an issue. The stream closes when the scan completes.
    public func scan() -> AsyncThrowingStream<CardReader, Error>
          /// Get all previously connected card readers
          /// - Returns: async single array of `CardReader` objects containing any previously connected reader.
    public func getPreviousConnectedReaders() async throws -> [CardReader]
          /// Get the last connected reader. This is the object in the getPreviousConnectedReaders return that the SDK most recently connected to.
          /// - Returns: async single `CardReader` object
    public func getPreviousConnectedReader() async throws -> CardReader

📘

NOTE

The getPreviousConnectedReader/s functions return records of readers previously connected. The records may contain readers not in range, but that can be connected to.

2. Initiate a scan

var cardReaders = [CardReader]()

   override func viewDidAppear(_ animated: Bool) {
      super.viewDidAppear(animated)

      Task { @MainActor in
         do {
            for try await cardReader in CloverPaymentSDK.shared.scan() {
               print("Found Reader: \(cardReader.name ?? cardReader.id)")
               cardReaders.append(cardReader)
            }
         } catch {
            print("Error scanning for readers: \(error)")
         }
      }
   }

3. Set up observers to monitor the state of the Clover Go reader

The observers are intended for UIKit apps.

For SwiftUI, the CloverPaymentSDK.shared.connectedReader property is a wrapped @Published property that you may observe to update your user interface (UI).

Only use one method if they both receive the same updates simultaneously.

//hold the SDK observer subscriptions
private var subscriptions = Set<AnyCancellable>()
 
override func viewDidLoad() {
    super.viewDidLoad()
      
    //observe changes to the battery level
    CloverPaymentSDK.shared.readerBatteryPct.sink { pct in
        // ...
        // update label or graphical indication of battery level
        // ...
    }.store(in: &subscriptions)
      
    //observe changes to the connection state of the reader
    CloverPaymentSDK.shared.readerStatus.sink { state in
        switch state {
        case .initializing:
            // Update status label to display "Initializing" state
        case .updatingFW:
            // Update status label to display "Firmware Updating" state
        case .lowBattery:
            // Update status label to display a "Low Battery" warning
        case .ready:
            // Update status label to display a "Connected" state
        case .disconnected:
            // Handle disconnection event and display the disconnected state
        default:
            break
        }
    }.store(in: &subscriptions)
}

4. Connect to a discovered device

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
   Task { @MainActor in
      do {
         // Pass in a specific CardReader object to connect to that reader
         try await CloverPaymentSDK.shared.connect(reader: self.cardReaders[indexPath.row])
 
         print("Connected to reader")
      } catch {
         print("Error connecting to reader: \(error)")
      }
   }
}

5. Connect to the last previously connected device

func reconnectPreviousReader() {
    Task { @MainActor in
       do {
          // Passing nil for the reader instructs the SDK to connect to the last connected device
          try await CloverPaymentSDK.shared.connect(reader: nil)
           
          print("Connected to previous reader")
       } catch {
          print("Error connecting to reader: \(error)")
       }
    }
 }

Methods

/// Connect to a bluetooth `CardReader`
/// - Parameters:
///   - reader: The specific `CardReader` to connect to.  If no `CardReader` is provided, then we will attempt to connect to the last connected reader, if available.
/// - Throws: `CloverError` and closes the stream if it encounters an error.
/// - Precondition: `SDKState` must be `.ready`.
/// - Precondition: `isLoggedIn` must be `true`
/// - Precondition: The SDK must not be connected to a `CardReader`
public func connect(reader: CardReader?) async throws

Reader state observing

To monitor the reader's state, Clover supports SwiftUI and UIKit based apps.

  • For SwiftUI, the CloverPaymentSDK.shared.connectedReader property is a @Published that can be observed in your declarative user interface (UI). It is present or nil based on whether it is connected, with its .state and .batteryPct values changing periodically throughout the lifecycle of the reader.
  • For UIKit, the readerStatus and readerBatteryPct CurrentValueSubjects provide the same information provided by SwiftUI.

🚧

IMPORTANT

Clover updates the .connectedReader object and the .readerStatus and .readerBatteryPct subjects at the same time with the same events. Subscribe and observe the correct one for your UI, but not both. Otherwise, you receive update notifications for the same event through both mechanisms.

Methods and property definitions

/// Get the currently connected card reader.  Also provides a Publisher of the CardReader so you can subscribe to changes to this object.
/// - Returns: An optional, observable CardReader that publishes changes when the reader connects or disconnects (present vs nil), the reader's `.state`, and `.batteryLevel`
/// - Remark: Recommended for SwiftUI consumption, due to its nature as a @Published property.  Can also reference the projected value as a simple property.
/// - Warning: If you subscribe to this property and either the `readerState` or `readerBatteryPct` publishers, you'll receive duplicate notifications from both subscribers.
/// - Precondition: `SDKState` must be `.ready`
/// - Precondition: `isLoggedIn` must be `true`
@Published public internal(set) var connectedReader: CardReader?

/// Get the state of the CardReader now and over time.  Once connected, the `.connectedReader` property will contain a fully hydrated `CardReader` object that can be inspected.
/// - Remark: This is intended primarily for UIKit-based subscribers.
/// - Remark: Defaults to `.disconnected` before a connection is attempted, and returns to `.disconnected` once a reader session ends— the `.connectedReader` becomes nil and you should clean up any references that you hold to that reader.
/// - Warning: If you subscribe to this property and observe/subscribe to the `$connectedReader` publisher, you'll receive duplicate notifications about reader state changes from both publishers.
public let readerStatus = CurrentValueSubject<ReaderStatus, Never>(.disconnected)

/// Get the battery level of the currently connected card reader now and over time. Default value of nil when no reader is connected.
/// - Remark: This is intended primarily for UIKit-based subscribers.
/// - Warning: If you subscribe to this property and observe/subscribe to the `$connectedReader` publisher, you'll receive duplicate notifications about battery level changes from both publishers.
public let readerBatteryPct = CurrentValueSubject<Int?, Never>(nil)

Connection flow

When you connect to a card reader for the first time, updates to firmware and encryption keys may occur and can take a few minutes. Subsequent connections take a few seconds as the configuration is checked and verified as valid. Use the state from the .readerState observer or CloverPaymentSDK.shared.$connectedReader to inform your users of the connection flow status.

The connection process occurs asynchronously, with the connect function returning once the connection is established and the reader is ready to use. Any errors halt the connection process and display an error.

A typical connection flow status is as follows:

connect() is called > initializing > updatingFW (optional) > ready > lowBattery (optional, when the battery level drops below 20%) > disconnected (when session ends)

Clover Go SDK iOS device connection flow

Clover Go SDK iOS device connection flow