Apphud SDK

repository·master·Indexed 42 days ago

https://github.com/apphud/apphudsdk

An all-in-one subscription infrastructure and revenue analytics platform for mobile app developers. The SDK (version 4.5.0) manages in-app purchases, A/B experiments, and real-time revenue metrics across iOS (Swift), Android (Kotlin), React Native, and Flutter, utilizing StoreKit 2 as its primary purchase engine.

Tokens
9.4K
Snippets
13
Records
39
Agent score
52%

What's inside Apphud

  1. Available Apphud SDKs by Platform

    master

    Apphud provides specialized SDKs for various mobile and cross-platform environments to manage auto-renewable subscriptions and in-app purchases:

    • iOS (Swift)
    • Android (Kotlin)
    • React Native (Cross-platform)
    • Flutter (Cross-platform)
  2. Understand the ApphudSDK module structure and platform support

    master

    The ApphudSDK is distributed as a single SPM target or CocoaPods pod. While the source is organized into Public/, Internal/, and ApphudUI/ folders for developer organization, these are conventions within a single module, not separate products.

    Platform Support

    • Core SDK: Supports iOS 15, macOS 13, tvOS 16, watchOS 9, and visionOS 1.
    • UI Components: The ApphudUI/ directory contains iOS-only code (UIKit/WebKit) for rendering Rules screens and Figma paywalls. This code is compile-guarded using #if os(iOS).
    • Cross-Platform Logic: Files like ApphudRule.swift, ApphudRuleScreen.swift, and ApphudPaywallScreen.swift do not rely on UIKit and are available on all supported platforms (iOS, macOS, tvOS, watchOS, and visionOS).
  3. Understand the Apphud SDK architecture and components

    master

    The Apphud SDK (version 4.5.0) is built around a single SPM target (ApphudSDK) that uses several internal singletons to manage state and logic. While the public API is accessed via the Apphud class, the engine is driven by ApphudInternal.shared.

    Core Components:

    • Apphud (Public Entry Point): A final class containing static methods. It holds no state and forwards all calls to the internal engine.
    • ApphudInternal.shared (The Engine): Manages the current user, paywalls, placements, permission groups, and delegates.
    • ApphudAsyncStoreKit.shared (StoreKit 2 Engine): The primary purchase engine. It handles product caching, product.purchase(options:), and listens to Transaction.updates and PurchaseIntent.intents.
    • ApphudStoreKitWrapper.shared (StoreKit 1 Compatibility): Used for observer-mode tracking of transactions not started by the SDK, receipt refreshing, and providing a feeder for the deprecated SKProduct surface.
    • ApphudHttpClient.shared: Handles request building, retries, and host fallback.
    • ApphudDataActor.shared: A global actor managing file-based caches and the pending user-property queue.
    • ApphudLoggerService.shared: Manages paywall analytics and load-time metrics.
  4. How receipt and transaction submission works

    master

    Apphud uses a single upload mechanism via submitReceipt to synchronize purchase data with the backend. This process handles both StoreKit 1 and StoreKit 2 transactions.

    Key Submission Details

    • Endpoint: POST /v1/subscriptions
    • Data Sent: Includes device_id, user_id, environment, bundle_id, receipt_data (base64), transaction_id, and jws (for StoreKit 2).
    • Product Info: For real purchases, the payload includes product_bundle_id, paywall_id, placement_id, variation_identifier, experiment_id, screen_id, and rule_id.
    • Concurrency: The SDK uses a single-flight slot submittingTransaction to prevent redundant uploads. Non-owning callers (like restoration or eligibility checks) join the in-flight request. Owning callers (ownsTransaction: true) that differ from the in-flight transaction will receive an immediate error to prevent finishing a transaction on a foreign result.
    • Retry Logic: If an upload fails, the SDK uses scheduleSubmitReceiptRetry with an increasing delay. It also sets a requiresReceiptSubmissionKey in UserDefaults to ensure the receipt is re-submitted during the next registration attempt.
    // Note: This is an internal method used by the SDK to sync transactions
    submitReceipt(productInfo:apphudProduct:transactionIdentifier:transactionProductIdentifier:transactionState:receipt_string:transactionJws:notifyDelegate:eligibilityCheck:ownsTransaction:fromScreen:callback:) async
  5. Apphud SDK Naming Conventions

    master

    To maintain consistency, follow these naming rules:

    • Prefixes: Every type must be prefixed with Apphud. Internal free functions use the lowercase apphud prefix (e.g., apphudLog, apphudIsSandbox).
    • File Naming:
      • ApphudInternal+<Concern>.swift for engine extensions.
      • +I.swift, +Extensions.swift, or +Macros.swift for ApphudUI controller extensions.
      • *Wrapper for SK1 compatibility.
      • *AsyncStoreKit for the SK2 engine.
      • *Observer for for await listeners.
      • *Actor or *Storage for actors.
      • *Manager for screen coordination.
      • *Controller for view controllers.
      • *Service for the logger.
      • *Tests for XCTest cases.
    • Constants:
      • Public constants use APPHUD_UPPER_SNAKE_CASE.
      • Specific exceptions: _ApphudCamelCase is used for _ApphudUserPropertyKey* strings and _Apphud*FinishTransactionNotification names.
      • UserDefaults keys are stored in *Key properties or string literals colocated with their accessor.
    • JSON Mapping:
      • Wire format is snake_case; Swift uses camelCase.
      • Exceptions (Public snake_case properties):
        • ApphudRule: rule_name, screen_name (both @objc), screen_id, paywall_id, paywall_identifier.
        • ApphudRuleScreen: status_bar_color (not @objc).
  6. Identify public entry points and core types in ApphudSDK

    master

    The Sources/Public/ directory contains all the symbols intended for host app consumption. Key components include:

    • Apphud: The primary entry point containing all static methods for SDK interaction.
    • ApphudUser: Represents the current user, handling identity and properties.
    • ApphudSubscription: Manages subscription status and product information.
    • ApphudPaywall & ApphudPlacement: Handles the wiring between products and paywall placements.
    • ApphudRule: Used to handle rules and screens triggered via notifications or push.
    • ApphudError: The error type used for handling SDK-specific failures.
  7. How Apphud handles transactions and deduplication

    master

    Apphud implements a single-flight mechanism for transaction handling to prevent duplicate uploads.

    1. Ownership: The first arrival of a transaction (either via a direct purchase call or Transaction.updates) 'owns' the transaction processing.
    2. Deduplication: Subsequent arrivals for the same transaction ID will await the existing task instead of starting a new upload.
    3. Completion: The SDK calls transaction.finish() only when the submission to Apphud returns no error. If a submission error occurs, the transaction remains unfinished so it can be redelivered by StoreKit.
    4. Observer Mode: If your app uses its own StoreKit 1 payment logic, Apphud enters 'Observer Mode'. In this mode, the SDK tracks transactions but allows the host app to decide whether to finish them via the ApphudDelegate.
  8. StoreKit 1 Compatibility and Swizzling

    master

    The ApphudStoreKitWrapper provides compatibility for StoreKit 1. It acts as an SKPaymentTransactionObserver but does not initiate payments itself.

    Key Behaviors

    • Transaction Observation: It processes .purchased transactions on the main actor. It also handles transaction removal by posting _ApphudDidFinishTransactionNotification and _ApphudWillFinishTransactionNotification.
    • Swizzling: The SDK performs SKPaymentQueue.doSwizzle() to exchange add(_:) with apphudAdd(_:). This allows the SDK to automatically set the applicationUsername (or appAccountToken for SK2) to ensure transactions are correctly associated with the Apphud user.
    • Swizzling Activation: Swizzling is enabled after the first user load (via preparePaywalls), unless the server explicitly sends swizzle_disabled: true or the SDK is running in Flutter observer mode.
    • Receipt Refreshing: The SDK manages SKReceiptRefreshRequest and uses a 15-second watchdog timer to ensure that receipt refresh attempts do not hang indefinitely.
  9. Use performWhenUserRegistered to gate Apphud features

    master

    Because Apphud relies on an asynchronous registration process, many features cannot function until a currentUser is established. To ensure code runs only when the SDK is ready, use performWhenUserRegistered(allowFailure:callback:).

    • Standard usage: If currentUser is nil, the block is queued and executed immediately after the first successful registration.
    • Handling failures: If you set allowFailure: true, the block will also execute if registration fails permanently (e.g., hitting retry caps or receiving non-retryable errors like invalidAPiKey or unauthorized).
    • Logout: Calling logout() will also trigger any pending allowFailure: true blocks to prevent them from waiting indefinitely.
  10. How Paywalls, Placements, and Products are loaded

    master

    Apphud follows a specific lifecycle to ensure UI components have the necessary data:

    1. User Registration: The process begins when a user is identified/registered.
    2. Server Fetch: The SDK calls POST /v1/customers to retrieve configured paywalls and placements.
    3. StoreKit Synchronization: Once paywalls/placements are received, the SDK identifies all required product IDs and fetches the corresponding SKProducts (StoreKit 2) from the App Store.
    4. Readiness: The delegate.paywallsDidFullyLoad or delegate.placementsDidFullyLoad methods are called once the products are fetched and mapped to the paywalls/placements.

    Key Models:

    • ApphudPaywall: Represents a configured paywall UI configuration.
    • ApphudPlacement: A logical grouping used to decide which paywall to show.
    • ApphudProduct: The primary product model used for purchases and eligibility checks. It wraps the underlying SKProduct.
  11. Understand the Apphud Threading Model

    master

    Understanding the threading model is critical for avoiding race conditions and ensuring UI responsiveness when using the SDK.

    • Main Actor: The SDK's core state (current user, paywalls, placements, etc.) and all public APIs (start, purchase, placements) are @MainActor.
    • Callbacks: All delegate methods and public callbacks are guaranteed to be delivered on the main thread.
    • HTTP Requests: Network requests are initiated on a background task (.userInitiated) but their results are dispatched back to the @MainActor.
    • StoreKit: StoreKit callbacks (like SKPaymentTransactionObserver) arrive on StoreKit's own threads and are immediately hopped to the @MainActor by the SDK.
    • Background Processing: Heavy lifting, such as walking Transaction.all or managing the ApphudDataActor (caches, pending properties), happens on background threads or dedicated actors to keep the main thread free.
  12. Handle purchase results with ApphudPurchaseResult

    master

    When a purchase completes, Apphud returns an ApphudPurchaseResult. A successful purchase is defined as having no error and an active subscription or purchase.

    Key properties to check:

    • success: Boolean indicating if the transaction was successful and an active entitlement was found.
    • error: Contains error details if the purchase failed.
    • subscription: The active subscription associated with the purchase.
    • purchase: The specific purchase details.
    • isPending: True if the transaction is in a pending state (e.g., 'Ask to Buy' or SCA).