Android SDK 6.x to 7.x migration guide

We've upgraded the Google Play Billing Library dependency to version 6 in this release, which has led to major changes in the product and subscription management parts of our SDK. These are described below.

Upgrading version

Increase the dependency version in your app build.gradle file to upgrade your Qonversion SDK to the latest version

dependencies {    
     implementation 'io.qonversion.android.sdk:sdk:7.+'
}

Product updates

Qonversion and Google Play product link changes

Google Play has recently upgraded the structure of their subscription products. We've written about it in our blog. In short, they combined the same subscriptions with different durations into a single subscription with different base plans. In addition, they announced different kinds of offers to base plans, including free trials or introductory prices. In this release, we begin to support those changes.

Previously Qonversion subscription products required a Google Play subscription identifier. Now you can also set a specific base plan for the subscription. Different base plans of the same subscription can be linked to different Qonversion products. Let's have a look at an example.

Let's say you sell monthly and annual subscriptions in your app, both on Android and iOS. With the previous Qonversion SDK versions, you would specify two Qonversion products (premium_monthly and premium_annual), two App Store products (premium_monthly_ios and premium_annual_ios), and two Google Play products (premium_monthly_android and premium_annual_android). Then you would link those store products to Qonversion products as follows:

premium_monthly - premium_monthly_ios and premium_monthly_android,

premium_annual - premium_annual_ios and premium_annual_android.

With the new Google Play Subscriptions structure, you can create only one subscription for Android - premium_android and two base plans for it (monthly and annual). With this done, you can now use a single subscription for both Qonversion products as follows:

premium_monthly - premium_monthly_ios and premium_android with monthly base plan,

premium_annual - premium_annual_ios and premium_android with annual base plan.

Store details for the QProduct class

Speaking of the SDK side - the first big part of the changes is the addition of the new basePlanId field to QProduct which can be set to the product via Qonversion Dashboard.

We have deprecated the QProduct.skuDetails field, because SkuDetails contain information only for backward compatible base plans and are now deprecated in the Google Play Billing Library. Instead, we have added the new storeDetails field of type QProductStoreDetails, which contains all the store information for the Google Play Product. You can find the specifications of that class on this page.

If basePlanId is not specified for a product, it means one of the following:

  • it is a one-time (in-app) product that can not have any base plans,
  • it is a product that was not updated in the Qonversion dashboard after this version was released.

In the case of the in-app product, both skuDetails and storeDetails fields will contain information about that in-app. But in the second case, when there is a subscription product without a specified base plan, the skuDetails field will contain information about the backward compatible base plan, while the storeDetails field will only contain common subscription information without base-plan-specific data. It means that you should start using storeDetails field for base-plan-specific data (like price, duration, and so on) for subscription products only if you've specified their base plan IDs in Qonversion product settings.

The other QProduct fields changes

Along with the above changes, we have changed the type field and removed the duration and trialDuration fields from the product class. The type and duration were previously being set via the dashboard, and the trialDuration was calculated from the store details and was represented as an enumeration with several common durations. Now, the product type is calculated from the store details. We've also added the Intro value to QProductType to separate trial and intro products. An Unknown value was also added for the cases where we are unable to determine the product type. The duration and trialDuration fields were replaced with subscriptionPeriod and trialPeriod. Both properties are of type QSubscriptionPeriod and the values are also calculated from the store details.

The last thing to note is that we've also changed the prettyPrice field calculation. Earlier it was calculated from skuDetails, while now we first try to use storeDetails if possible and only then fall back to skuDetails.

QProduct updates summary

Below is the shortened summary of the QProduct changes:

  • new basePlanId field, specifying concrete subscription base plan, added,
  • skuDetails field deprecated,
  • new storeDetails field, containing information about Google Play product, added;
  • the type field is now calculated based on store details, instead of the value set via the Qonversion dashboard. The Intro and Unknown values were added to QProductType to separate cases of trial and intro products and also the case when we are unable to determine the product type;
  • the duration and trialDuration fields were replaced with subscriptionPeriod and trialPeriod fields of type QSubscriptionPeriod with the values calculated from the store details;
  • the prettyPrice field now prefers storeDetails to get the price from, and only then - skuDetails.

Purchase flow updates

In this release, we have changed the purchase and updatePurchase methods signatures. Previously there were several methods for different sets of arguments. Now we've arranged all those properties into classes - QPurchaseModel and QPurchaseUpdateModel, that should be instantiated and provided to the single purchase or updatePurchase method.

You can create instances of those classes either manually or by using the utility functions toPurchaseModel and toPurchaseUpdateModel added to the QProduct class.

Below is an example of how the purchase flow has changed:

// Old
Qonversion.shared.purchase(this, product, callback = object: QonversionEntitlementsCallback {...})

// New
val purchaseModel = product.toPurchaseModel()
Qonversion.shared.purchase(this, purchaseModel, callback = object: QonversionEntitlementsCallback {...})
// Old
Qonversion.getSharedInstance().purchase(this, product, new QonversionEntitlementsCallback() {...});

// New
final QPurchaseModel purchaseModel = product.toPurchaseModel();
Qonversion.getSharedInstance().purchase(this, purchaseModel, new QonversionEntitlementsCallback() {...});

And an updatePurchase flow example:

// Old
Qonversion.shared.updatePurchase(this, product, "oldProductId", callback = object: QonversionEntitlementsCallback {...})

// New
val purchaseUpdateModel = product.toPurchaseUpdateModel("oldProductId")
Qonversion.shared.updatePurchase(this, purchaseUpdateModel, callback = object: QonversionEntitlementsCallback {...})
// Old
Qonversion.getSharedInstance().updatePurchase(this, newProduct, "oldProductId", new QonversionEntitlementsCallback() {...});

// New
final QPurchaseUpdateModel purchaseUpdateModel = newProduct.toPurchaseUpdateModel("oldProductId");
Qonversion.getSharedInstance().updatePurchase(this, purchaseUpdateModel, new QonversionEntitlementsCallback() {...});

If you were using only product identifiers, you can create purchase models manually as follows:

// Old
Qonversion.shared.purchase(this, "productId", callback = object: QonversionEntitlementsCallback {...})

// New
val purchaseModel = QPurchaseModel("productId")
Qonversion.shared.purchase(this, purchaseModel, callback = object: QonversionEntitlementsCallback {...})
// Old
Qonversion.getSharedInstance().purchase(this, "productId", new QonversionEntitlementsCallback() {...});

// New
final QPurchaseModel purchaseModel = new QPurchaseModel("productId");
Qonversion.getSharedInstance().purchase(this, purchaseModel, new QonversionEntitlementsCallback() {...});
// Old
Qonversion.shared.updatePurchase(this, "newProductId", "oldProductId", callback = object: QonversionEntitlementsCallback {...})

// New
val purchaseUpdateModel = QPurchaseUpdateModel("newProductId", "oldProductId")
Qonversion.shared.updatePurchase(this, purchaseUpdateModel, callback = object: QonversionEntitlementsCallback {...})
// Old
Qonversion.getSharedInstance().updatePurchase(this, "newProductId", "oldProductId", new QonversionEntitlementsCallback() {...});

// New
final QPurchaseUpdateModel purchaseUpdateModel = new QPurchaseUpdateModel("newProductId", "oldProductId");
Qonversion.getSharedInstance().updatePurchase(this, purchaseUpdateModel, new QonversionEntitlementsCallback() {...});

If necessary, you can provide a specific offer identifier for subscription purchase.

// Specifying offer via the `toPurchaseModel` method:
val productOfferDetails = ... // Choose an offer from `storeDetails`
val purchaseModel = product.toPurchaseModel(productOfferDetails)

// Specifying offer ID via the `toPurchaseModel` method:
val purchaseModel = product.toPurchaseModel("offer_id")

// Specifying offer ID via the constructor:
val purchaseModel = QPurchaseModel("productId", "offer_id")
val purchaseUpdateModel = QPurchaseUpdateModel("newProductId", "oldProductId", "offer_id")

// Specifying offer ID after the purchase model creation:
purchaseModel.offerId = "offer_id"
// Specifying offer via the `toPurchaseModel` method:
final QProductOfferDetails productOfferDetails = ...; // Choose an offer from `storeDetails`
final QPurchaseModel purchaseModel = product.toPurchaseModel(productOfferDetails);

// Specifying offer ID via the `toPurchaseModel` method:
final QPurchaseModel purchaseModel = product.toPurchaseModel("offer_id");

// Specifying offer ID via the constructor:
final QPurchaseModel purchaseModel = new QPurchaseModel("productId", "offer_id");
final QPurchaseUpdateModel purchaseUpdateModel = new QPurchaseUpdateModel("newProductId", "oldProductId", "offer_id");

// Specifying offer ID after the purchase model creation:
purchaseModel.setOfferId("offer_id");

If provided, we will try to find and purchase the offer with the specified ID for the requested Qonversion product. If there is no offer with the specified ID, an error will be returned. If no offer ID is provided for the subscription purchase of Qonversion product with a specified base plan ID, then we will choose the most profitable offer for the client from all the available offers. We calculate the cheapest price for the client by comparing all the trial or intro phases and the base plan. For old Qonversion products (where the base plan ID is not specified), as well as for in-app products, the offer ID is ignored.

You can also remove any intro/trial offer from the purchase (use only a bare base plan). For that purpose, you should call removeOffer method of purchase model:

purchaseModel.removeOffer()
purchaseModel.removeOffer();

The other changes

  • Google Play Billing Library version was upgraded from 6.0.1 to the latest 6.1.0;
  • The minimal supported SDK version was upgraded from 16 to 19, the Kotlin version was also upgraded from 1.6 to 1.8;
  • The checkTrialIntroEligibility method was improved and now detects the eligibility based on store details;
  • QonversionErrorCode.SkuDetailsError was removed;
  • The QProductDuration and QTrialDuration classes were removed;
  • The previously deprecated method contextForScreenIntent was removed from AutomationsDelegate.