Hero Image

Subscriptions in Flutter - The Complete Implementation Guide

By Alexander Thiele - Average reading time: 15 minutes

Hello, fellow flutter developers!

This complete documentation will guide you from a no subscription flutter app to a solid implementation of subscriptions into your flutter app.

Our focus will be Auto-Renewable Subscriptions. Quick explanation: If a user purchases an Auto-Renewable Subscription, the user will be granted access to a special service or content for a week, a month, a quarter, 6 months or a year. The user can always decide to cancel or renew the subscription before the next period starts.

There are 3 ways to implement subscriptions in your app. The first would be to integrate the official Android and iOS purchase libraries provided by Apple and Google. This would require you to write Swift and Kotlin and create a bridge to the flutter application.

To avoid re-inventing the wheel, the flutter devs created a package that manages the native implementation and created a bridge to your flutter app (option #2). If you use the flutter package, you only need to handle the flutter implementation of subscriptions and manually add your created subscription Ids and validate the receipt on your server.

The third way would be to use LinkFive which uses the official flutter package but adds a dynamic layer on top of the official plugin and gives you the option to dynamically change your subscription offering even after you have released the app and also validates purchases with the LinkFive backend system.

Option 1: Use the Flutter Plugin in_app_purchase (Provider example)

Luckily there is a ready-to-use plugin that has already implemented the native Android and iOS integration of subscriptions for your app. The plugin uses the official Android and iOS libraries for subscriptions and creates a bridge to your Flutter app.

Let's start right away with the plugin.

1. Add the In-App Purchase Dependency

Just add the dependency to your pubspec.yaml file or add the dependency with the flutter client:

$ flutter pub add in_app_purchase

in your pubspec.yaml:

dependencies:
in_app_purchase: ^1.0.9

The version might be outdated already. please check the latest version on Pub.dev

2. Initializing the Plugin and connecting to the underlying store

You can read all the detailed steps on the official documentation. We will outline the key parts.

Listen for subscription purchase updates

The plugin uses streams to communicate with the native subscription integration. You can use any state management system along with the plugin in your app. You can also just use a StatefulWidget but it's not recommended. I would suggest the Provider or Riverpod package for your state management. There are other state management systems like bloc or getX, but for simplicity, we will focus on the provider package.

Create a new file in your provider folder: subscriptions_provider.dart and listen for subscription updates.

class SubscriptionsProvider extends ChangeNotifier {

// save the stream to cancel it onDone
late StreamSubscription _streamSubscription;

SubscriptionsProvider() {
final Stream<List<PurchaseDetails>> purchaseUpdated = InAppPurchase.instance.purchaseStream;

_streamSubscription = purchaseUpdated.listen((purchaseDetailsList) {
// Handle the purchased subscriptions
_listenToPurchaseUpdated(purchaseDetailsList);

}, onDone: () {
_streamSubscription.cancel();

}, onError: (error) {
// handle the error
});
}

_listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
// Purchased Subscriptions
}
}

The provider will listen to purchase updates whenever you start consuming the SubscriptionProvider. Let's jump directly into the method.

_listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {

// check each item in the provider list
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {

// Sometimes the purchase is not completely done yet, in this case, show the pending UI again.
if (purchaseDetails.status == PurchaseStatus.pending) {
_showPendingUI();

} else {

// The status is NOT pending, lets check for an error
if (purchaseDetails.status == PurchaseStatus.error) {
// This happens if you close the app or dismiss the purchase dialog.
_handleError(purchaseDetails.error!);

} else if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {

// Huge SUCCESS! This case handles the happy case whenever the user purchased or restored the purchase
_verifyPurchaseAndEnablePremium(purchaseDetails);

}

// Whenever the purchase is done, complete it by calling complete.
if (purchaseDetails.pendingCompletePurchase) {
await InAppPurchase.instance.completePurchase(purchaseDetails);
}
}
});
}

Whenever the status is purchased or restored, we have to check if the purchase is valid and if true, show the premium features according to the subscription plan.

_verifyPurchaseAndEnablePremium(PurchaseDetails purchaseDetails){

// check if the purchase is valid by calling your server including the receipt data.
bool valid = await _verifyPurchase(purchaseDetails);
if (valid) {

// Purchase is valid, time to enable all subscription features.
_enablePremiumFeatures(purchaseDetails);

} else {

// The receipt is not valid. Don't enable any subscription features.
// _handleInvalidPurchase(purchaseDetails);
}
}

In any case, you should verify that a subscription is valid on your server by sending the receipt data to your server's endpoint and calling either Apple or Google to verify whether it is valid or not.

You could also skip this part but this will open your business to false-positive subscribers.

3. Connect to the underlying store and receive your subscription data

Whenever you think it's time to offer your subscriptions to your user, you need to get subscription data such as the price, the currency and the duration of the subscription. If you started from scratch, there are probably no subscriptions created on the Google Play Store and Apple App Store yet. This is expected and not an issue. You can continue with the integration and replace the subscription Ids later on.

Let's create another method inside the subscriptions_provider.dart to fetch the information we need.

fetchSubscriptions() async {
final bool available = await InAppPurchase.instance.isAvailable();
if (!available) {
// The store cannot be reached or accessed.
// This could also be the case if you run the app on emulator.
// Please use a physical device for testing.
return;
}

// Hardcode subscriptionIds you want to offer.
const Set<String> _subscriptionIds = <String>{'sub1', 'sub2'};
final ProductDetailsResponse response = await InAppPurchase.instance.queryProductDetails(_subscriptionIds);

if (response.notFoundIDs.isNotEmpty) {
// IDs that does not exist on the underlying store.
}

// all existing product are inside the productDetails.
List<ProductDetails> products = response.productDetails;

// Store the subscription and notify all listeners
notifyListeners();
}

You can now show the subscriptions you want to offer and update your UI elements accordingly.

Also, you need to add the subscription IDs manually, which makes it very difficult to change the subscription offer. You may want to try LinkFive to dynamically add the subscriptions to your app and change the subscriptions in seconds without creating and uploading a new app version.

4. Purchase a subscription

To make a purchase, you need to call the purchase method with the productDetail the user selected. A subscription is a non-consumable product since the user can only purchase a subscription once at a time. once the subscription is active, the user cannot purchase it again. If the subscription has ended or got cancelled by the user, the subscription can be purchased again.

// prepare the PurchaseParam
PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails);

// Purchase the Subscription
InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam);

The purchase flow should start now and will call the subscription listener in any case on success or error.

5. Restore a subscription

If the user pressed on the restore button of your paywall (which is required on iOS), call the restorePurchases method. This will fetch all existing subscriptions and deliver them to the purchase listener which we created in the beginning.

InAppPurchase.instance.restorePurchases();

6. Run the App without creating Google Play or App Store subscriptions

If you provide the wrong subscription identifiers or did not create any Ids yet, the response notFoundIDs will be filled with the provided IDs. This is expected for the first run.

If you start with Android, please upload your Android App to the Google Play Store and create your subscriptions afterwards. If you start with iOS, just go to App Store Connect and create your Subscriptions.

Option 2: Use the Flutter Plugin linkfive_purchases (Provider example)

The plugin takes care of delivering the subscriptions IDs to your app by adding a layer between the in_app_purchase and adds much more flexibility to your flutter app. It automatically fetches the subscriptions which you can configure via the web app and adds additional functionality e.g. a dynamic paywall to your flutter app.

A short overview can be found on our Flutter Integration page.

Easiest Integration of Flutter Subscriptions in your app with LinkFive

Let's start by adding the plugin to your flutter project.

1. Add the LinkFive Purchases Dependency

Add the dependency to your pubspec.yaml file or add the dependency with the flutter client:

$ flutter pub add linkfive_purchases

in your pubspec.yaml:

dependencies:
linkfive_purchases: ^1.5.0

The version might be outdated already. please check the latest version on LinkFive Pub.dev page

2. Add Subscriptions using the LinkFive Purchases Plugin

It's really easy to implement subscriptions with LinkFive, there are only 3 steps to add subscriptions to your flutter app. First, you need to initialize the plugin with the provider API KEY.

// Get your API key after sign up
LinkFivePurchases.init("LinkFive Api Key");

3. Listen to Subscription Updates

Just listen to updates via the LinkFive Stream and show the result to the user:

// Subscriptions to offer to the user 
LinkFivePurchases.products;

// Active subscriptions the user purchased
LinkFivePurchases.activeProducts;

4. Purchase the Subscription

Purchase the product whenever the user clicks on the subscription.

// This handles the entire purchase process
LinkFivePurchases.purchase(productDetails);

That‘s it and enough to offer subscriptions to your users. Let's look into the provider example of the plugin.

Use the ready-to-use Provider Plugin

It's even easier with the LinkFive provider plugin available on pub.dev

dependencies:
linkfive_purchases_provider: ^1.5.0

And all you need to do is to register the provider with the API Key.

MultiProvider(
providers: [
// ...
ChangeNotifierProvider(
create: (context) => LinkFiveProvider("API_KEY"),
lazy: false,
),
]
)

You can use the provider to check if there is an active subscription or to offer subscriptions to your user.

Complete Provider Example

If you don't want to use the LinkFive provider plugin, you can of course create your own provider. Let‘s start with the provider file and create a new file with the name: subscriptions_provider.dart in your provider folder.

class SubscriptionsProvider extends ChangeNotifier {
List<LinkFiveVerifiedReceipt> verifiedReceipt = [];

LinkFiveSubscriptionData? availableSubscriptions = null;

SubscriptionsProvider() {
LinkFivePurchases.init("API KEY");

// Stream of subscriptions to offer to the user
LinkFivePurchases.products.listen(_listenToSubscriptionData);

// Stream of active subscriptions
LinkFivePurchases.activeProducts.listen(_listenToPurchaseUpdated);
}

_listenToSubscriptionData(LinkFiveSubscriptionData? linkFiveSubscriptionData) {
availableSubscriptions = linkFiveSubscriptionData;
notifyListeners();
}

_listenToPurchaseUpdated(LinkFiveActiveSubscriptionData? linkFiveActiveSubscriptionData) {
verifiedReceipt = linkFiveActiveSubscriptionData?.subscriptionList ?? [];
notifyListeners();
}

fetchSubscriptions() async {
LinkFivePurchases.fetchSubscriptions();
}

restoreSubscriptions() async {
LinkFivePurchases.restore();
}

purchase(ProductDetails productDetail) async {
LinkFivePurchases.purchase(productDetail);
}
}

The Provider is now ready to use in your App and you will see the LinkFive Debug messages:

I/flutter (32079): LinkFive: Store reachable
I/flutter (32079): LinkFive: push active sub data with size 0
I/flutter (32079): LinkFive: set purchaseState value PurchaseState.NOT_PURCHASED

Flutter Custom Paywall UI Plugin

There is also a custom Paywall UI Plugin available on pub.dev. The Plugin was made by LinkFive to easily add a paywall in combination with the purchases plugin. The UI package offers a couple of designs that you can use in your app for free - even if you don't use LinkFive.

Simple Paywall UI plugin for Flutter

Seamless integration with LinkFive Purchases Plugin

The plugin works immediately together with the linkfive_purchase plugin. No additional work needed

Only a few lines of code are required to display your subscriptions.

SimplePaywall(
callbackInterface: linkFivePurchases,
subscriptionListData: subscriptionData,
// ...
});

The subscriptionData can be received through your provider or built yourself.

As an example using the Provider plugin: Pass the callbackInterface and subscriptionDataList from the provider to the Paywall and you're all set.

Consumer<SubscriptionsProvider>(
builder: (_, provider, __) {
return PaywallScaffold(
child: SimplePaywall(
callbackInterface: provider.callbackInterface,
subscriptionListData: provider.getSubscriptionListData(context),
// ...
));
},
);

The result will show the subscriptions and it will handle everything related to a purchase. The page is currently an empty paywall that you can customize yourself using the plugin.

The Paywall is ready for use!

Out of the box Purchase State Management

The plugin also manages the UI state of the paywall and the Plugin makes sure that either the success page or the premium page is displayed if the user has purchased Premium. You can always navigate to your premium page.

// no additional code needed

Check out the Pub.dev page of the paywall plugin.

Enable Subscriptions for Android

Before you can create your subscriptions in the Google Play Store, Google wants you to upload an app version with the billing permission. You can also check if you already added the permission by going to the in-app products page in the developer console (Monetize > Products > In-app products). If it says: Your app doesn't have any in-app products yet - To add in-app products, you need to add the BILLING permission to your APK. Then you need to upload a new app version with the billing permissions first.

Add the BILLING Permission to your APK

Since 2017 it's not necessary to add the permission manually anymore. You can still add it manually but the permission will be merged automatically with the purchase library later.

Add Permission Manually

You can still add it manually, to do it, open the AndroidManifest.xml file. You can find the file in the following folder in your flutter project: android > app > src > main > AndroidManifest.xml

Add the com.android.vending.BILLING Permission to the Manifest root section.

<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
package="app.tnx.equixo">

<!-- other permissions -->
<uses-permission android:name="com.android.vending.BILLING" />

<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<!-- your activities -->

Upload your App to the Google Play Store

Now, build a new app version and upload it to your Alpha or internal track (Both work and are only for internal use)

After you've successfully uploaded a new version, you can go ahead and create your subscriptions.


First published on Oct 27, 2021, last updated on Nov 25, 2021

Ready to get started?

Sign Up