Rovenue SDK
Platforms

iOS (Swift)

Install and wire the Rovenue SDK in a Swift app.

Install the SDK

Swift Package Manager — in Xcode choose File → Add Package Dependencies and enter the repository URL, or add it to your Package.swift:

dependencies: [
  .package(url: "https://github.com/broverse/rovenue-ios", from: "0.16.0"),
],
targets: [
  .target(name: "YourApp", dependencies: ["Rovenue"]),
]

Supported platforms: iOS 16.0+ (device and simulator) and macOS 12.0+.

CocoaPods — add to your Podfile and run pod install:

pod 'Rovenue'

Configure the SDK

Call Rovenue.configure once, before any SDK method is used. AppDelegate.application(_:didFinishLaunchingWithOptions:) is the correct place:

import UIKit
import Rovenue

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    do {
      try Rovenue.configure(
        apiKey: "rov_pub_YOUR_API_KEY",
        logLevel: .info
      )
    } catch {
      // Only throws for a blank/malformed apiKey — a configuration
      // problem to fix before shipping, not a runtime condition to
      // recover from.
      print("Rovenue.configure failed: \(error)")
    }
    return true
  }
}

Self-hosting? baseUrl is optional and defaults to the hosted endpoint https://api.rovenue.io. Override it to point at your own deployment, e.g. Rovenue.configure(apiKey: "rov_pub_YOUR_API_KEY", baseUrl: "https://api.acme.com"). The host must use https:// — plain http:// is accepted only for localhost during local development.

The SDK reads CFBundleShortVersionString from the main bundle automatically — you do not need to pass appVersion manually.

Types on this page

// Swift mirror of Rovenue.configure's parameters
func configure(
  apiKey: String,
  baseUrl: String? = nil,      // optional — defaults to https://api.rovenue.io
  logLevel: LogLevel = .warn,  // .off | .error | .warn | .info | .debug | .trace
  appVersion: String? = nil,   // override auto-detected version
  environment: String? = nil
) throws
FieldTypeDescription
apiKeyStringPublic SDK key from your Rovenue dashboard. Prefix rov_pub_.
baseUrlString?Optional. Defaults to https://api.rovenue.io. Override only when self-hosting (https://; http:// allowed for localhost only).
logLevelLogLevelVerbosity of SDK logs. Defaults to .warn — turn up to .info/.debug locally, never ship .debug/.trace in production.
appVersionString?Overrides the version auto-read from the bundle.
environmentString?Optional environment label forwarded with requests.

Call configure exactly once, before any other SDK call — the ideal location is application(_:didFinishLaunchingWithOptions:). configure itself only throws for a blank/malformed apiKey; there is no RovenueError.notConfigured case. Calling any other SDK method (Rovenue.shared.…) before configure has ever run is a hard, uncatchable crash (fatalError) — not a throwable error on any platform. This is an ordering bug to fix at the call site, not a condition to catch. See Calling the SDK before configure().

Wire foreground signals

The SDK uses setForeground to drive its internal polling cadence. Wire it from your SceneDelegate:

import UIKit
import Rovenue

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

  func sceneWillEnterForeground(_ scene: UIScene) {
    Rovenue.shared.setForeground(true)
  }

  func sceneDidEnterBackground(_ scene: UIScene) {
    Rovenue.shared.setForeground(false)
  }
}

If your app uses the SwiftUI @main lifecycle rather than a SceneDelegate, observe scenePhase from the environment and call setForeground from .onChange(of: scenePhase).


Identifying users

Anonymous subscribers get a permanent rovenueId automatically. Call identify once your own auth flow completes, to link that install to your backend user:

import Rovenue

// After your auth flow completes:
try await Rovenue.shared.identify("user_123")

// When your user signs out:
try await Rovenue.shared.logOut()

let user = await Rovenue.shared.currentUser()
// user.rovenueId (always present), user.appUserId (nil until identify())

See Identifying Users for the full identity model, including what happens when identify and logOut are called.


Purchases

Use getOfferings() to fetch products configured in your Rovenue dashboard, then purchase(_:) to drive the full StoreKit 2 purchase flow. The SDK validates the receipt server-side and resolves with updated entitlements.

let offerings = try await Rovenue.shared.getOfferings()
if let pkg = offerings.current?.packages.first {
  do {
    let result = try await Rovenue.shared.purchase(pkg)
    if result.isDeferred {
      // Ask-to-Buy / parental-control hold — no entitlements yet. The SDK
      // reconciles it in the background; watch `Rovenue.shared.changes` for
      // `.entitlementsChanged` to know when it resolves.
    } else {
      // result.entitlements and result.virtualCurrencies are already up to date
    }
  } catch let e as RovenueError {
    switch e.kind {
    case .purchaseCanceled:
      break // User dismissed the sheet — no action needed
    default:
      throw e
    }
  }
}

// Re-validate all prior transactions (e.g. after reinstall):
try await Rovenue.shared.restorePurchases()
// Swift mirror of PurchaseResult
struct PurchaseResult {
  var entitlements: [Entitlement]
  var virtualCurrencies: [String: Int64]
  var productId: String
  var storeTransactionId: String
  var isDeferred: Bool // true for an Ask-to-Buy / parental-control hold
}

struct Entitlement {
  var id: String
  var isActive: Bool
  var productIdentifier: String
  var store: String
  var expiresIso: String? // ISO 8601 — present for time-limited entitlements
}
FieldTypeDescription
entitlements[Entitlement]Updated entitlement list after the purchase.
virtualCurrencies[String: Int64]Updated virtual-currency balances after the purchase (currency code → amount).
productIdStringThe product identifier that was purchased.
storeTransactionIdStringThe StoreKit transaction ID.
isDeferredBooltrue when the purchase is pending external approval — no entitlements are granted yet.

There is no RovenueError.purchasePending case — as of 0.16.0, a deferred purchase (Ask-to-Buy) does not throw at all; it returns normally with isDeferred = true. See Reference: Errors for the full 24-value ErrorKind taxonomy and Processing Purchases for the complete purchase API and the StoreProduct shape.


Funnels

getFunnel(placementId:locale:) resolves a placement into the funnel the current subscriber should see (see Placements & Funnels). The session is already open when it returns — hand it to a FunnelDriver and host RovenueFunnelView (see Show a Funnel):

import Rovenue

if let funnel = try await Rovenue.shared.getFunnel(placementId: "onboarding", locale: "en") {
  // nil when the placement has no assignment — render nothing.
  // Hand `funnel` to a FunnelDriver and host RovenueFunnelView (Show a Funnel).
  _ = funnel
}

Purchase attribution is automatic: the funnel's presentedContext is attached to the next purchase for you.

Declare the languages your app supports

A native funnel draws in the device's language only if your app declares that language. iOS hands an app Locale.current from the app's own declared localizations — CFBundleLocalizations in Info.plist, or the localizations your Xcode project has. An app that declares only English reaches the SDK as en_XX (en_TR on a Turkish device), so the funnel shows its English copy and English drawer strings (field placeholders, VoiceOver labels) even when the funnel carries a Turkish translation. List every language your funnels are translated into.

See Placements for how placements resolve to a funnel or an A/B experiment variant.


Observing changes

Rovenue.shared.changes is an AsyncStream of change events. Consume it inside a long-lived Task:

import Rovenue

Task {
  for await event in Rovenue.shared.changes {
    // event: ChangeEvent (.entitlementsChanged | .identityChanged | .virtualCurrenciesChanged)
    print("Rovenue change:", event)
    // Update your UI or local state here
  }
}

Cancel the Task (or call Rovenue.shared.shutdown()) when the owning object is deallocated.

See Reacting to Changes for a complete guide on handling live subscription events.


Debugging

Register a log handler to receive structured log entries from the SDK. Set it up before Rovenue.configure to capture initialisation logs:

Rovenue.shared.setLogHandler { entry in
  print("[\(entry.level)] \(entry.message)")
}

Funnels

A placement can serve a native funnel: getFunnel, then a FunnelDriver driving RovenueFunnelView (packages/sdk-swift/Sources/Rovenue/Rovenue.swift:717) — full path: Show a funnel.

if let funnel = try await Rovenue.shared.getFunnel(placementId: "onboarding") {
    _ = FunnelDriver(session: funnel.session)
}

What's next

On this page