Handle errors in Kotlin Multiplatform SDK

This page covers error handling in the Adapty Kotlin Multiplatform SDK.

Error handling basics

Adapty SDK methods are suspend functions that return an AdaptyResult, which is either a success or an error. Handle both cases with onSuccess and onError:

import com.adapty.kmp.Adapty

Adapty.getProfile()
    .onSuccess { profile ->
        // Handle success
    }
    .onError { error ->
        // Handle error
        println("Adapty error: ${error.message}")
    }

AdaptyResult is a sealed class, so you can also branch on it directly when you need the result as a value:

import com.adapty.kmp.models.AdaptyResult

when (val result = Adapty.getProfile()) {
    is AdaptyResult.Success -> {
        val profile = result.value
        // Handle success
    }
    is AdaptyResult.Error -> {
        val error = result.error
        // Handle error
    }
}

Common error codes

AdaptyError.code is an AdaptyErrorCode enum value, not a number — compare it against the enum constant, not against the numeric code. The numbers below are shown only because they are what a log line or a support ticket usually carries.

Error codeNumberDescriptionSolution
AdaptyErrorCode.NO_PRODUCT_IDS_FOUND1000None of the products in the paywall are available in the store.See Fix for Code-1000 noProductIDsFound error.
AdaptyErrorCode.CANT_MAKE_PAYMENTS1003In-app purchases are not allowed on this device.See Fix for Code-1003 cantMakePayments error.
AdaptyErrorCode.PRODUCT_NOT_FOUND22The product requested for purchase is not available in the store.Check that the product is configured in the store and in the Adapty Dashboard.
AdaptyErrorCode.NETWORK_FAILED2005The network request failed.Ask the user to check their connection, or retry the call.
AdaptyErrorCode.ADAPTY_NOT_INITIALIZED20The SDK was not activated before the call.Wait for Adapty.activate to complete before any other SDK call.

For the full list of codes, see AdaptyErrorCode in the SDK models reference.

Handle specific errors

Network errors

import com.adapty.kmp.Adapty
import com.adapty.kmp.models.AdaptyErrorCode

Adapty.getFlow("YOUR_PLACEMENT_ID")
    .onSuccess { flow ->
        // Use the flow
    }
    .onError { error ->
        when (error.code) {
            AdaptyErrorCode.NETWORK_FAILED -> {
                // Network error - show offline message
                showOfflineMessage()
            }
            else -> {
                showErrorMessage(error.message)
            }
        }
    }

Purchase errors

makePurchase reports failures and outcomes separately. An AdaptyResult.Error means the call failed; a successful result returns an AdaptyPurchaseResult that tells you whether the user completed the purchase, canceled it, or left it pending:

import com.adapty.kmp.Adapty
import com.adapty.kmp.models.AdaptyErrorCode
import com.adapty.kmp.models.AdaptyPurchaseResult

Adapty.makePurchase(product)
    .onSuccess { purchaseResult ->
        when (purchaseResult) {
            is AdaptyPurchaseResult.Success -> showSuccessMessage()
            AdaptyPurchaseResult.UserCanceled -> {
                // The user dismissed the store sheet — not an error
            }
            AdaptyPurchaseResult.Pending -> {
                // Awaiting an out-of-band payment, such as a prepaid plan
                showPendingMessage()
            }
        }
    }
    .onError { error ->
        when (error.code) {
            AdaptyErrorCode.CANT_MAKE_PAYMENTS -> showPaymentNotAvailableMessage()
            AdaptyErrorCode.PRODUCT_NOT_FOUND -> showProductNotAvailableMessage()
            else -> showPurchaseErrorMessage(error.message)
        }
    }

Error recovery strategies

Retry on network errors

Because SDK methods are suspend functions, a retry loop is a plain loop with delay between attempts:

import com.adapty.kmp.Adapty
import com.adapty.kmp.models.AdaptyErrorCode
import com.adapty.kmp.models.AdaptyFlow
import com.adapty.kmp.models.AdaptyResult
import kotlinx.coroutines.delay

suspend fun getFlowWithRetry(placementId: String, maxRetries: Int = 3): AdaptyFlow? {
    repeat(maxRetries) { attempt ->
        when (val result = Adapty.getFlow(placementId)) {
            is AdaptyResult.Success -> return result.value
            is AdaptyResult.Error -> {
                if (result.error.code != AdaptyErrorCode.NETWORK_FAILED) {
                    showErrorMessage(result.error.message)
                    return null
                }
                delay(1000L * (attempt + 1))
            }
        }
    }
    return null
}

Fall back to cached data

You don’t need to cache flows yourself. Adapty SDK caches them on the device and returns the cached copy when the network fails, so pass a fetch policy instead of writing your own cache:

import com.adapty.kmp.Adapty
import com.adapty.kmp.models.AdaptyPaywallFetchPolicy

Adapty.getFlow(
    placementId = "YOUR_PLACEMENT_ID",
    fetchPolicy = AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad
)
    .onSuccess { flow ->
        // Use the flow
    }
    .onError { error ->
        showErrorMessage(error.message)
    }

With ReturnCacheDataElseLoad, users on a patchy connection get a flow faster, at the cost of possibly not seeing the latest version. If neither the network nor the cache produces a flow, the SDK falls back to the fallback file you bundle with the app.

Next steps