Tutorial: how to implement in-app purchases into a flutter app

Murat Menzilci
8 min readFeb 10, 2022

Flutter is a relatively new framework developed by Google which lets you quickly create cross-platform apps. The other popular framework is React Native by Facebook. Flutter apps are built for both iOS and Android at once. Therefore, a purchase library must be compatible with both StoreKit and Billing Library.

Architecture-wise, any payment plugin — including our Adapty Flutter SDK — is a wrapper around the native libraries, StoreKit and Billing Library. In our case, it’s a wrapper around our own Adapty iOS and Android SDK libraries.

We’ve just created a Discord server to help app developers, marketers and products managers to learn, share and educate about app business. You can join us! https://go.adapty.io/join-discord

Adapty help monetize mob apps so mobile developers can earn more and invest back in the business. Learn more about Adapty here.

Open source libraries for in-app purchases in Flutter-based apps

Popular solutions for purchases in Flutter apps are the open source plugins in_app_purchase (developed by the team behind Flutter) and flutter_inapp_purchase (non-official plugin).

These plugins were made to implement client-side purchases. They don’t feature server-side receipt validation. You’ll need to set up your own server-side infrastructure to validate receipts and collect payment analytics concerning renewals, refunds, trials, cancellations, and more.

What’s more, these libraries are usually slow to support new store features. Just to name a few of these, right now, they lack promo offers, pay-as-you-go features, and the pay upfront feature for iOS.

Since our library talks to our server, it features all of these:

  • Purchase server validation;
  • Full native payment features support;
  • DRY syntax;
  • Subscriptions and purchases analytics and some other data collection;
  • A/B tests, as well as quick experimenting with purchases, pricing, and promo periods;
  • Paywalls and promo offers.

To keep this article brief and easy to read, we’ll link a few articles we’ve previously written about some steps you should take before you start working on purchases for your Flutter app.

Creating purchases on iOS and Android

First, you’ll need to create a developer account if you don’t have one. Next, you’ll need to create a weekly purchase for both iOS and Android. We’ve explained how to do this in our previous articles:

Creating purchases for iOS

Creating purchases for Android

Once you’re done, you can start creating and configuring your project.

Creating and configuring the project

To get your purchases running both in your implementation and our system, you’ll need to fill in these technical details.

For iOS, you’ll need to:

  • Specify the Bundle ID to link your Adapty project to your app;
  • Set up App Store Server Notifications in App Store Connect, so that you can be notified about subscription events;
  • Specify the App Store Shared Secret, which the Adapty server will use to establish a connection to Apple’s servers and validate receipts.

For Android, both Package Name and Service Account Key File must be specified. Package name is the iOS Bundle ID for Android. You need to use the same one that’s used in the code. It can be found in the /android/app/build.gradle file, which is situated in the android.defaultConfig.applicationId directory.

Configuring products and paywalls

An Adapty product encapsulates those of various stores. It’s just the App Store and Google Play for now, but we’ll introduce other stores in future. The idea behind this approach is to make cross-platform stats collection easier, as well as let you work with top-level entities, as opposed to identifiers.

Paywall is an abstract entity that contains a product array and a remote config file (which is a JSON file containing the metadata you specified in your dashboard). You hard code the paywall ID into your app — this ID is used to retrieve the config file and product data. You can edit both of these without having to update the app. In the usual scenario, you design the paywall and fill it with the data received from us.

Creating a product

Let’s use Google Play Console and App Store Connect to create a product that corresponds to a weekly subscription. Fill in the ID of corresponding products, which you can retrieve from payment systems. It’s important to note that since App Store Connect has no API, you’ll need to do this manually. Just once, though.

Creating a paywall

When creating a paywall, the key thing is to specify its ID in a convenient format. This identifier will later be used by the SDK to request the paywall data. We believe this to be a good approach: With this architecture, there’s no need to hard code your products on the client side. You can be flexible with your product management, versioning, tests, etc. The alternative option would be Firebase JSON with a set of built-in products. This doesn’t provide error validation and isn’t as user-friendly, though.

Voila! Having created a product and a paywall, you’re now ready for your first purchase. Let’s proceed to installing the SDK.

How to use the SDK

Let’s look at the main functions we’ll need to configure and use subscriptions.

Insalling the library

First, you’ll need to add the adapty_flutter library to your project. To do that, add the following dependency to your pubspec.yaml file:

dependencies:
adapty_flutter: ^1.0.4

After that, run:

flutter pub get

From here, you can import Adapty SDK into your app like this:

import 'package:adapty_flutter/adapty_flutter.dart';

Configuration

You’ll need to configure your app to work with Adapty. To do so, add the AdaptyPublicSdkKey flag with your public SDK key into Info.plist (for iOS) or into AndroidManifest.xml (for Android).

You can find your SDK key in Adapty settings:

Info.plist:

<dict>
...
<key>AdaptyPublicSdkKey</key>

AndroidManifest.xml:

<application ...>
...
<meta-data
android:name="AdaptyPublicSdkKey"
android:value="PUBLIC_SDK_KEY" />
</application>

Next, activate the SDK by invoking this code on the Flutter side — e.g. in the main() method of your project:

void main() {
runZoned(() async {
Adapty.activate();
final installId = await Service.getOrCreateInstallId();
await Adapty.identify(***customer-user-id***);
await Adapty.setLogLevel(AdaptyLogLevel.verbose);
runApp(MyApp());
});
}

The void Adapty.activate() function activates the Adapty_Flutter library:

Future<bool> Adapty.identify(String customerUserId)

Adapty.identify lets you retrieve the user’s id. Adapty sends it over to the subscription and analytics systems to assign events to this profile. You’ll also be able to find your clients by customerUserId in Profiles.

Adapty logs error messages and other important data to help you understand what’s going on. The

Future<void> Adapty.identify(AdaptyLogLevel value)

function lets you choose one of the three possible values:

  • AdaptyLogLevel.none (the default setting): nothing will be logged.
  • AdaptyLogLevel.errors: only error messages will be logged.
  • AdaptyLogLevel.verbose: method calls, API requests and responses, and error messages will be logged.

Retrieving paywalls

To retrieve the paywall list, run this code:

try {
final GetPaywallsResult getPaywallsResult = await Adapty.getPaywalls(forceUpdate: Bool);
final List<AdaptyPaywall> paywalls = getPaywallsResult.paywalls;
} on AdaptyError(adaptyError) {}
catch(e) {}

The Adapty.getPaywalls() function returns the GetPaywallsResult object that contains:

  • Paywalls: A paywall array ( AdaptyPaywall ). The model contains the product list, paywall ID, custom payload, and a few other values.

Making purchases

In the previous step, you’ve retrieved the paywall array. Now, we’ll look for the one you need to display those products in your UI. Let’s assume this paywall is named your_paywall_id:

final List? paywalls = getPaywallsResult.paywalls;
myPaywall = paywalls?.firstWhere((paywall) => paywall.developerId == "your_paywall_id", orElse: null);

Next, using the product array from the products field, display all of its items. Let’s say the user wants to buy the first product. For the sake of simplicity, we’ll assume it to be the first item of the product array.

final AdaptyProduct? product = myPaywall?.products?.first;

To initiate the purchase, invoke the makePurchaseResult function. (Remember to wrap this in a try-catch block to still receive all error messages from the SDK.)

final MakePurchaseResult makePurchaseResult = await Adapty.makePurchase(product);

Once the function has been successfully executed, the makePurchaseResult variable will take the result as its value. Here’s how to check the access level after the purchase has been completed:

final isPremium = makePurchaseResult?.purchaserInfo?.accessLevels['premium']?.isActive ?? false;

Note that AdaptyErrorCode.paymentCancelled means the user canceled the purchase themselves, which means it isn’t an actual error message.

To restore purchases, use the .restorePurchases() method:

try {
final RestorePurchasesResult restorePurchasesResult = await Adapty.restorePurchases();
// "premium" is an identifier of default access level
if (restorePurchasesResult?.purchaserInfo?.accessLevels['premium']?.isActive ?? false) {
// grant access to premium features
}
} on AdaptyError catch (adaptyError) {}
catch (e) {}

Take into account that both the MakePurchaseResult and RestorePurchasesResult objects include purchaserInfo. This object contains data about access levels, subscriptions, and purchases. Usually, you can tell whether the user can access the premium features of your app just by checking their access level.

Subscription status

To avoid having to check through the entire preceding transaction chain, we’re introducing the term “access level.” Access level is a flag that explains how much of the app’s functionality the user can access. If they haven’t made any purchases yet, then their access level will be null. Otherwise, it’ll be what you specified for your product.

For example, you can have two access levels, namely, silver and golden. Different purchases will unlock different access levels and features. Most apps have just one access level.

To see whether the subscription is active, it’s enough to check if the user has an active access level. You can use the.getPurchaserInfo() method to do just that:

try {
AdaptyPurchaserInfo purchaserInfo = await Adapty.getPurchaserInfo();
// "premium" is an identifier of default access level
if (purchaserInfo.accessLevels['premium']?.isActive ?? false) {
// grant access to premium features
}
} on AdaptyError catch (adaptyError) {}
catch (e) {}

You can also subscribe to the .purchaserInfoUpdateStream stream to timely learn of any changes in the subscriber’s access level. Here’s how:

Adapty.purchaserInfoUpdateStream.listen((purchaserInfo) {
print('#Adapty# Purchaser Info Updated');
if (purchaserInfo.accessLevels['premium'].isActive) {
// grant access to premium features
}
});

Conclusion

We’ve designed our SDK in a way to help you quickly integrate payments into your app. What’s more, we’ve also tried to simplify all the other steps you might need to take, such as AB testing, analytics, and further integrations.

We’ll provide full purchase functionality free of charge if your revenue amounts to less than $10,000 per month. Save yourself months of work and focus on the most important thing: your product.

--

--

Murat Menzilci

📱 I write about how to make money with mobile application development, news about App Store and Google Play Store, working as a freelancer and making money. 💰