Report: State of in-app subscriptions in the US 2023 Get a report

React Nativeアプリ内購入: シンプルな実装。チュートリアル

Ivan Dorofeev

Updated: 5月 23, 2023

11 min read

Content

62fdf143e0d4c00ce1483eba jp android tutorial 1 configuration 10

クロスプラットフォームのアプリ開発フレームワークは、開発者の負担を確実に軽減して、一度に複数のプラットフォーム用のアプリを構築できるようにします。ただし、いくつかの欠点があります。たとえば、React Nativeには、アプリ内購入 (in-app purchase) を実装するための既成のツールがありません。そのため、必然的にサードパーティのライブラリに着目する必要があります。

アプリ内購入の実装にはどのようなオプションがありますか?

React Nativeアプリのアプリ内サブスクリプション (in-app subscription) で人気のあるライブラリは、react-native-iapとexpo-in-app-purchasesです。ただし、他のライブラリと比較して、react-native-adaptyには多数のメリットがあるため、このライブラリについて説明します。

● 他のライブラリとは異なり、サーバーベース (server-side) の購入検証 (purchase validation) に対応しています。

プロモーションオファー (promo offer) から前払い (pay upfront) 機能まで、App Storeで最近実装されたすべての機能をサポートしています。また、今後の新機能への対応も迅速です。

● より明確でシンプルなコードになります。

● 完全なリリースサイクルを通過しなくても、提供するプロダクトを変更したり、新しいオファーを追加または削除したりできます。ベータ版をリリースして承認を待つ必要はありません。

その他にもAdapty SDKには、充実した機能が揃っています。すべての主要な指標、コホート分析 (Cohort analysis)、サーバーベースの購入検証、ペイウォール (paywall) のA/Bテスト (A/B test) 、柔軟なセグメンテーションによるプロモーションキャンペーン、サードパーティの分析ツールの統合など、組み込みの分析ツールを利用できます。

記事の内容

ここでは、React Nativeアプリでのアプリ内購入の設定について説明します。今回の内容は次のとおりです。

1. React Nativeアプリのアプリ内購入でExpoを使用できない理由

2. デベロッパーアカウントの作成

3. Adaptyの設定:

App Storeの設定

Playストアの設定

4. サブスクリプションの追加

5. ペイウォールの作成

6. React Native-adaptyのインストール

7. サンプルアプリと結果

このガイドでは、サブスクリプションユーザーに猫の写真を表示して、その他のユーザーにサブスクリプションのオファーを提示するアプリの構築を試みます。

React Native アプリのアプリ内購入でExpoを使用できない理由

簡単に言うと、Expoの「マネージド」は、App Storeが提供する購入処理用のネイティブメソッド (別名StoreKit) をサポートしていません。引き続きReact Nativeを使用するか、Expo Bare Workflowを使用する必要があります。

Expoの使用を検討していた方はがっかりするかもしれませんが、使うことはできません。 Expoは、アプリ開発を効率化するReact Nativeフレームワークです。ただし、マネージドワークフローは、購入やサブスクリプションの処理と互換性がありません。Expoでは、StoreKitに必要なメソッドとコンポーネント (両方ともJavaScriptのみ) でネイティブコードを使用しません。モバイルストアでJavaScriptを使用してアプリ内購入を実装する方法はないため、「イジェクト」する必要があります。

デベロッパーアカウントの作成

まず、App Storeアカウントを作成して、iOSとAndroid の両方で購入とサブスクリプションを作成して設定する必要があります。20分以内で完了できるはずです。

App Store ConnectやGoogle Play Consoleでデベロッパーアカウントとプロダクトをまだ設定していない場合は、次のガイドを参照してください。

iOSの場合:ガイドの最初から「SKProductのリストの取得」の見出しまでお読みください。その後、ネイティブ実装について説明します。

Androidの場合:ガイドの最初から「アプリ内のプロダクトリストの取得」の見出しまでお読みください。

Adaptyの設定

react-native-adaptyの場合、最初にAdaptyダッシュボードを設定する必要があります。それほど時間はかかりませんが、Adaptyがハードコーディングよりも優れているという上記のすべてのメリットを得られます。

3番目の手順では、App StoreとGoogle Playの設定を求められます。

61dd2e3df0eb779b1966c336 wwqbyclqbg22r4uxyue6f wty 8kx1lgeepf9 ctrv5mcvrihoe1upbhw 0jth wqwkhjc4az

iOSの場合、次のことを行う必要があります。

● バンドルIDを指定する

● App Storeサーバ通知を設定する

● App Store Connect共有シークレットを指定する

これらのフィールドは、購入を完了するために必要です。

各フィールドには、手順ごとの方法を含む「説明」のヒントがあります。不明な点がある場合は、これらのヒントを確認してください。

バンドルIDは、アプリの一意のIDです。 Xcodeの [Targets] > [App Name] > [General] で指定したIDと一致する必要があります。

61dd2e3d0bcefe3f92ed87c6 ofteg 29 hasv9sywd5gpm4vxtbwppkdlkqkatwvl3jprjkq3ffp3xzdcuett5qf uzavsew

Androidの場合、必須フィールドは「パッケージ名」と「サービスアカウントキーファイル」です。これらすべてのフィールドには、説明に関するヒントがあります。Androidでのパッケージ名は、iOSでのバンドルIDに対応しています。コードで指定したものと一致する必要があります。これは、android.defaultConfig.applicationIdの/android/app/build.gradleファイルで確認できます。

61dd2e3dad4d57621f800483 qbh6fhwgcmjiqsriilrm2ubc2xjswr8buchwntern5xn8fkxqxgep4hbrftvqyfu3rgx4dg x3c6iyvemvxarmvemiu

4番目の手順では、Adapty SDKをアプリに連携するよう求められます。ただし、後で説明するため、今はこの手順を飛ばしてください。

新規登録したら、[設定] タブを確認してください。ここにパブリックSDKキーが表示されているため、覚えておきましょう。キーは後で必要になります。

61dd2e3d3fce0994fd3f0c38 0 z1xu4tx6yoskp5gysva4jz ksmpt59lwmlpt8mol5fmyuqn1gygyvmqf52rgz5drhlvtp2gekrd0pwygz8pydmejdeskwggxp5 f6kevd ftdziotbnzpcv6bsqgojjk9unzt

サブスクリプションの追加

Adaptyでは、さまざまなサブスクリプションのプロダクトを使用します。猫の写真のサブスクリプションプランは、毎週、半年ごと、または毎年のいずれかです。これらのオプションは、それぞれ個別のAdaptyプロダクトになります。

ダッシュボードで、プロダクトが1つあることを指定しましょう。[Products & A/B Tests] → [Products] をクリックし、[Create product] をクリックします。

ここでは、プロダクト名、つまりサブスクリプションがAdaptyダッシュボードでどのように表示されるかを指定する必要があります。

App StoreプロダクトID と Play StoreアイテムID も指定する必要があります。必要に応じて、期間と分析用の名前も指定します。[Save] をクリックします。

61dd2e3d3abe166794791c09 fukrvgjrhor1rtmrrmvq4mar f3rdr2nqpbojb44aa5ipwcq 9bbq7ajpq085sroobeltpnw9rtdfah a0pqoqthyqbj8ayaahikjioylj1gszmlfh mhwlqowynrpaabjjkvoln

ペイウォールの作成

ここでは、ユーザーの有料機能へのアクセス権を制限して、サブスクリプションのオファーを表示する画面として、ペイウォールを設計する必要があります。作成したプロダクトをペイウォールに追加する必要があります。これを行うには、同じセクション ([Products & A/B Tests] → [Paywalls]) で [Create paywall] をクリックします。

● 名前を見ただけで、どのペイウォールであるかを簡単に推測できるようなペイウォール名を選択してください。

● ペイウォールIDを使用して、このペイウォールをアプリに表示します。サンプルアプリでは、「cats_paywall」を使用します。

● [Product] ドロップダウンで、サブスクリプションを選択します。

[Save & publish] をクリックします。

61dd2e3ddced150628dcbffb uw2xexcu4rprox7 esl56chynz2mu1iqtae1xlweot55m7lqpugfjxtzp kyg vtawjwjhcpkmuno ysa i el8xnv2v 6seczhpmdhy7xsswndb1 2kbuu9bfhqltew h4yq5tj

設定は以上です。次に、依存関係を追加してコードを記述します。

react-native-adaptyのインストール

1. まず、依存関係を追加します。

yarn add react-native-adapty

2. iOSポッドをインストールします。CLIポッドをお持ちでない場合は、ダウンロードすることを強くお勧めします。iOS開発では、確かに多くのポッドが必要になります。

#pods get installed into the native iOS project, which, by default, is the /ios folderpod install --project-directory=ios

3. iOS React NativeプロジェクトはObj-Cで記述されているため、Obj-CがSwiftライブラリを読み取れるように、Swift Bridging Headerを作成する必要があります。そのためには、Xcodeプロジェクトを開いて新しいSwiftファイルを作成するだけです。Xcodeでは、Swift Bridging Headerを作成するかどうかを尋ねられるため、[Create] をクリックします。

61dd2e3da26af882142a7518 o2nycy3aeotxdr5m bcojaezt3hnj uzz 1jim7k25r9bn2jedf yhyvhgwx2qlequduyul0hwxxpjixeoe
61dd2e3ef99ba01c6f90aa08 rldljwkg131iukgbpxmwbjwu5q0m mhqxje3s9i1hljldxeau b8ehfimaw2ttxqq

4. Android の場合、プロジェクト (デフォルトでは「/android/build.gradle」) がバージョン1.4.0以降のkotlin-gradle-pluginを使用していることを確認します。

... buildscript { 
  ... dependencies { 
    ... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0" 
  } 
} ...

5. Androidの場合、アプリの設定ファイル (デフォルトでは「/android/app/build.gradle」) にあるmultiDexを有効にする必要があります。

... 
android { 
  ... defaultConfig { 
    ... multiDexEnabled true 
  } 
}

これで準備が完了したので、コーディングを開始できるようになりました

アプリでのプロダクトリストの取得

react-native-adaptyには、さまざまな便利な機能があります。遅かれ早かれ必ず必要になるため、フローの最初でライブラリを初期化する必要があります。アプリのコーディング画面で上にスクロールして (App.tsxでも同様に実行できます)、初期化を開始しましょう。

// import the method
import { activateAdapty } from 'react-native-adapty';

// We’ve had this App component in our app’s root
const App: React.FC = () => {
  ...
// we’re invoking it once in a root component on mount
  useEffect(() => {
    activateAdapty({ sdkKey: 'MY_PUBLIC_KEY' });
  },[]);
  ...
}

ここでは、MY_PUBLIC_KEYをダッシュ​​ボード設定にあるパブリックSDKキーに置き換えます。実際には、 activateAdapty()メソッドは複数回、複数の場所で呼び出すことができますが、引き続きこの方法を使用してください。

これで、Adapty ダッシュボードに追加したプロダクトを取得できます。

import { adapty } from 'react-native-adapty';

async function getProducts() {
	const {paywalls, products} = await adapty.paywalls.getPaywalls();

	return products;
}

それでは、実践してみましょう。ペイウォールからプロダクトを閲覧して購入できる軽量アプリを作成してみます。

サンプルアプリ

基本ロジックが複雑になりすぎないように、ここから先は簡潔にします。また、TypeScriptでコーディングして、どのタイプがどこで使用されているかを示します。テストには、懐かしいiPhone 8を使用します。iOS 14以降、App StoreではエミュレーターでStoreKitを使用することが禁止されるようになりました。実機を使ってのみテストできます。

App.tsxルートコンポーネント

1. まず、ペイウォール表示ボタンを含むApp.tsxルートコンポーネントを作成しましょう。すでにreact-native-navigationからナビゲーションを設定しています。公式ドキュメントで推奨されているreact-navigationオプションよりもはるかに優れていると考えられます。

問題

import React, { useEffect, useState } from "react";
import { Button, StyleSheet, View } from "react-native";
import { adapty, activateAdapty, AdaptyPaywall } from "react-native-adapty";

export const App: React.FC = () => {
  const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);

  useEffect(() => {
    async function fetchPaywalls(): Promise<void> {
      await activateAdapty({ sdkKey: "MY_PUBLIC_KEY" });

      const result = await adapty.paywalls.getPaywalls();
      setPaywalls(result.paywalls);
    }

    fetchPaywalls();
  }, []);

  return (
    <View style={styles.container}>
      <Button
        title="Show the paywall"
        onPress={() => {
          const paywall = paywalls.find(
            (paywall) => paywall.developerId === "cats_paywall"
          );

          if (!paywall) {
            return alert("There is no such paywall");
          }
					// Switching to a paywall...
        }}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: "center", justifyContent: "center" },
});

何が起きているのでしょうか?マウント時に、fetchPaywalls()関数が呼び出されます。 SDK をアクティベーションして、ペイウォールをその状態で保存するため、ユーザーはボタンをタップした後に取得されるまで待つ必要がありません。ビューには、ダッシュボードで以前に設計したペイウォール画面に移動するボタンが1つのみ表示されます。

実際、ペイウォールを状態に保存せずに、ここで取得することは可能です。デフォルトでは、adapty.paywalls.getPaywalls()は (起動時にキャッシュした後) キャッシュストレージから取得します。つまり、メソッドがサーバーと通信するのを待つ必要はありません。

結果は次のとおりです。

react native in-app purchases tutorial payall

ペイウォールコンポーネント

2. 同じファイルにペイウォールコンポーネントを書きましょう。

// there are more imports here
import React, { useEffect, useState } from "react";
import {
  Button,
  SafeAreaView,
  StyleSheet,
  Text,
  View,
  PlatformColor,
} from "react-native";
import {
  adapty,
  activateAdapty,
  AdaptyPaywall,
  AdaptyProduct,
} from "react-native-adapty";
import { Navigation } from "react-native-navigation";

// ...

interface PaywallProps {
  paywall: AdaptyPaywall;
  onRequestBuy: (product: AdaptyProduct) => void | Promise<void>;
}
export const Paywall: React.FC<PaywallProps> = ({ paywall, onRequestBuy }) => {
  const [isLoading, setIsLoading] = useState<boolean>(false);

  return (
    <SafeAreaView style={styles.container}>
      {paywall.products.map((product) => (
        <View key={product.vendorProductId}>
          <Text>{product.localizedTitle}</Text>
          <Button
            title={`Buy for за ${product.localizedPrice}`}
            disabled={isLoading}
            onPress={async () => {
              try {
                setIsLoading(true);
                await onRequestBuy(product);
              } catch (error) {
                alert("Error occured :(");
              } finally {
                setIsLoading(false);
              }
            }}
          />
        </View>
      ))}
    </SafeAreaView>
  );
};

// A new key
const styles = StyleSheet.create({
  container: { flex: 1, alignItems: "center", justifyContent: "center" },
  paywallContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "space-evenly",
    backgroundColor: PlatformColor("secondarySystemBackground"),
  },
});

ここでは、ペイウォールからプロダクトをマッピングし、各プロダクトの横に購入ボタンを表示します。

画面の登録

3. 表示を確認するために、この画面をreact-native-navigationに登録しましょう。他のナビゲーションを使用している場合は、この手順をスキップしてください。ルートindex.jsファイルは次のようになります。

import "react-native-gesture-handler";
import { Navigation } from "react-native-navigation";

import { App, Paywall } from "./App";

Navigation.registerComponent("Home", () => App);
Navigation.registerComponent("Paywall", () => Paywall);

Navigation.events().registerAppLaunchedListener(() => {
  Navigation.setRoot({
    root: { stack: { children: [{ component: { name: "Home" } }] } },
  });
});

「ペイウォールを表示する」ボタン

4. 次に、「ペイウォールを表示する」ボタンにアクションを割り当てる必要があります。この場合、[Navigation] を介してモーダルが表示されます。

Navigation.showModal<PaywallProps>({
    component: {
      name: "Paywall",
      passProps: {
        paywall,
        onRequestBuy: async (product) => {
          const purchase = await adapty.purchases.makePurchase(product);
          // Doing everything we need
          console.log("purchase", purchase);
        },
      },
    },
  });

App.tsxファイル全体:

import React, { useEffect, useState } from "react";
import {
  Button,
  SafeAreaView,
  StyleSheet,
  Text,
  View,
  PlatformColor,
} from "react-native";
import {
  adapty,
  activateAdapty,
  AdaptyPaywall,
  AdaptyProduct,
} from "react-native-adapty";
import { Navigation } from "react-native-navigation";
export const App: React.FC = () => {
  const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);
  useEffect(() => {
    async function fetchPaywalls(): Promise<void> {
      await activateAdapty({
        sdkKey: "MY_PUBLIC_KEY",
      });

      const result = await adapty.paywalls.getPaywalls();
      setPaywalls(result.paywalls);
    }

    fetchPaywalls();
  }, []);

  return (
    <View style={styles.container}>
      <Button
        title="Show paywall"
        onPress={() => {
          const paywall = paywalls.find(
            (paywall) => paywall.developerId === "cats_paywall"
          );

          if (!paywall) {
            return alert("There is no such paywall");
          }

          Navigation.showModal<PaywallProps>({
            component: {
              name: "Paywall",
              passProps: {
                paywall,
                onRequestBuy: async (product) => {
                  const purchase = await adapty.purchases.makePurchase(product);
                  // Doing everything we need
                  console.log("purchase", purchase);
                },
              },
            },
          });
        }}
      />
    </View>
  );
};

interface PaywallProps {
  paywall: AdaptyPaywall;
  onRequestBuy: (product: AdaptyProduct) => void | Promise<void>;
}
export const Paywall: React.FC<PaywallProps> = ({ paywall, onRequestBuy }) => {
  const [isLoading, setIsLoading] = useState<boolean>(false);

  return (
    <SafeAreaView style={styles.paywallContainer}>
      {paywall.products.map((product) => (
        <View key={product.vendorProductId}>
          <Text>{product.localizedTitle}</Text>
          <Button
            title={`Buy for ${product.localizedPrice}`}
            disabled={isLoading}
            onPress={async () => {
              try {
                setIsLoading(true);
                await onRequestBuy(product);
              } catch (error) {
                alert("Error occured :(");
              } finally {
                setIsLoading(false);
              }
            }}
          />
        </View>
      ))}
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: "center", justifyContent: "center" },
  paywallContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "space-evenly",
    backgroundColor: PlatformColor("secondarySystemBackground"),
  },
});

6203b7f5d4192527a3d05ac8 react native tutorial 2 cropped

以上で、ペイウォールをユーザーに表示できるようになりました。

サンドボックスでiOSのサブスクリプションをテストする場合は、独自のサンドボックステスターアカウントを作成する必要があります。テストを効率化するために、サンドボックスサブスクリプションはすぐに無効になることに注意してください。Androidの場合、追加のアカウントは必要ありません。エミュレーターでテストを実行することもできます。

Subscribe to Adapty newsletter

Get fresh paywall ideas, subscription insights, and mobile app news every month!

ユーザーにアクティブなサブスクリプションがあるかどうかを確認する

エンドユーザーに有料コンテンツへのアクセスを許可するために、アクティブなサブスクリプション データをどこに保存するかを指定しなければなりません。Adaptyでは、ユーザーに関連付けられたすべての購入情報が保存されるため、この場合にも役立ちます。仮にユーザーがサブスクリプションを利用していない場合は、ペイウォールボタンが表示されるとします。サブスクリプションを利用している場合は、ユーザーに猫の写真を表示します。.

アクティブなサブスクリプションデータはサーバーまたはキャッシュストレージから取得されるため、ローダーが必要になります。効率化のために、isLoadingとisPremiumの状態を追加しましょう。

// ...
export const App: React.FC = () => {
	const [isLoading, setIsLoading] = useState<boolean>(true);
	const [isPremium, setIsPremium] = useState<boolean>(false);
  const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);

	useEffect(() => {
    async function fetchPaywalls(): Promise<void> {
      try {
        await activateAdapty({
          sdkKey: "MY_PUBLIC_KEY",
        });

        const profile = await adapty.purchases.getInfo();
        const isSubscribed = profile.accessLevels.premium.isActive;
        setIsPremium(isSubscribed);

        if (!isSubscribed) {
          const result = await adapty.paywalls.getPaywalls();
          setPaywalls(result.paywalls);
        }
      } finally {
        setIsLoading(false);
      }
    }

    fetchPaywalls();
  }, []);

  // ...
}
// ...

変更点は次のとおりです。状態にフラグを追加しました。fetchPaywalls()の記述全体がtry-catchブロックにラップされるようになり、あらゆる想定されるシナリオでコードがsetIsLoading(false)に到達するようになりました。ユーザーにアクティブなサブスクリプションがあるかどうかを確認するために、ユーザーのプロファイル (すべてのサブスクリプションデータを含む) を取得し、profile.accessLevels.premium.isActiveの値を確認します。必要に応じてアクセスレベル (accessLevels) を使用できますが (基本的には「ゴールド」や「プレミアム」などのサブスクリプションレベルにすぎません)、ここでは既定値のままにします。Adaptyでは、有料機能へのアクセスレベルを自動的に作成します。ほとんどのアプリでは、これで十分です。isActiveは、このアクセスレベルのアクティブなサブスクリプションがある間は「true」のままになります

ここからは、すべてが簡単に思えるはずです。ユーザーにプレミアムレベルのサブスクリプションステータスがある場合、ペイウォールを取得する必要はありません。ローダーを無効にしてコンテンツを表示するだけです。

export const App: React.FC = () => {
// ...
const renderContent = (): React.ReactNode => {
  if (isLoading) {
    return <Text>Loading...</Text>;
  }

  if (isPremium) {
    return (
      <Image
        source={{
          url: "https://25.media.tumblr.com/tumblr_lugj06ZSgX1r4xjo2o1_500.gif",
          width: Dimensions.get("window").width * 0.8,
          height: Dimensions.get("window").height * 0.8,
        }}
      />
    );
  }

  return (
    <Button
      title="Show paywall"
      onPress={() => {
        const paywall = paywalls.find(
          (paywall) => paywall.developerId === "cats_paywall"
        );

        if (!paywall) {
          return alert("There is no such paywall");
        }

        Navigation.showModal<PaywallProps>({
          component: {
            name: "Paywall",
            passProps: {
	            paywall,
		          onRequestBuy: async (product) => {
		            const purchase = await adapty.purchases.makePurchase(product);
	              const isSubscribed =
		              purchase.purchaserInfo.accessLevels?.premium.isActive;
                setIsPremium(isSubscribed);
                Navigation.dismissAllModals();
              },
            },
          },
        });
      }}
    />
  );
};

return <View style={styles.container}>{renderContent()}</View>;
};

ここでは、コンテンツをレンダリングする関数といくつかのロジックをonRequestBuyに追加しています。つまり、isPremiumの状態を更新して、モーダルを終了します。

最終結果は次のとおりです。

ファイル全体:

import React, { useEffect, useState } from "react";
import {
  Button,
  SafeAreaView,
  StyleSheet,
  Text,
  View,
  PlatformColor,
  Image,
  Dimensions,
} from "react-native";
import {
  adapty,
  activateAdapty,
  AdaptyPaywall,
  AdaptyProduct,
} from "react-native-adapty";
import { Navigation } from "react-native-navigation";

export const App: React.FC = () => {
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [isPremium, setIsPremium] = useState<boolean>(false);
  const [paywalls, setPaywalls] = useState<AdaptyPaywall[]>([]);

  useEffect(() => {
    async function fetchPaywalls(): Promise<void> {
      try {
        await activateAdapty({
          sdkKey: "MY_PUBLIC_KEY",
        });

        const profile = await adapty.purchases.getInfo();
        const isSubscribed = profile.accessLevels.premium.isActive;
        setIsPremium(isSubscribed);

        if (!isSubscribed) {
          const result = await adapty.paywalls.getPaywalls();
          setPaywalls(result.paywalls);
        }
      } finally {
        setIsLoading(false);
      }
    }

    fetchPaywalls();
  }, []);

  const renderContent = (): React.ReactNode => {
    if (isLoading) {
      return <Text>Loading...</Text>;
    }

    if (isPremium) {
      return (
        <Image
          source={{
            uri: "https://25.media.tumblr.com/tumblr_lugj06ZSgX1r4xjo2o1_500.gif",
            width: Dimensions.get("window").width * 0.8,
            height: Dimensions.get("window").height * 0.8,
          }}
        />
      );
    }

    return (
      <Button
        title="Show a paywall"
        onPress={() => {
          const paywall = paywalls.find(
            (paywall) => paywall.developerId === "cats_paywall"
          );

          if (!paywall) {
            return alert("There is no such a paywall");
          }

          Navigation.showModal<PaywallProps>({
            component: {
              name: "Paywall",
              passProps: {
                paywall,
                onRequestBuy: async (product) => {
                  const purchase = await adapty.purchases.makePurchase(product);
                  const isSubscribed =
                    purchase.purchaserInfo.accessLevels?.premium.isActive;
                  setIsPremium(isSubscribed);
                  Navigation.dismissAllModals();
                },
              },
            },
          });
        }}
      />
    );
  };

  return <View style={styles.container}>{renderContent()}</View>;
};
interface PaywallProps {
  paywall: AdaptyPaywall;
  onRequestBuy: (product: AdaptyProduct) => void | Promise<void>;
}
export const Paywall: React.FC<PaywallProps> = ({ paywall, onRequestBuy }) => {
  const [isLoading, setIsLoading] = useState<boolean>(false);

  return (
    <SafeAreaView style={styles.paywallContainer}>
      {paywall.products.map((product) => (
        <View key={product.vendorProductId}>
          <Text>{product.localizedTitle}</Text>
          <Button
            title={`Buy for ${product.localizedPrice}`}
            disabled={isLoading}
            onPress={async () => {
              try {
                setIsLoading(true);
                await onRequestBuy(product);
              } catch (error) {
                alert("An error occured :(");
              } finally {
                setIsLoading(false);
              }
            }}
          />
        </View>
      ))}
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: "center", justifyContent: "center" },
  paywallContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "space-evenly",
    backgroundColor: PlatformColor("secondarySystemBackground"),
  },
});

まとめ

最終的に、優れたデザインで便利なサブスクリプションアプリを構築することができました。有料プランを購入したユーザーは猫の写真を閲覧でき、それ以外のユーザーには代わりにペイウォール画面が表示されます。このガイドでは、アプリにアプリ内購入を実装するために必要なすべての手順を説明しました。StoreKitについて詳細を学びたい方は、引き続きご注目ください。ありがとうございました。