Androidアプリ開発

Google Play Billing Library 5対応

この記事は約39分で読めます。
スポンサーリンク

こんにちは、まっさん(@Tera_Msaki)です。

この記事はAndroidスマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Javaでの開発経験、XML構文規則、Androidのアプリ開発経験がある方を対象としています。
Androidのアプリ開発でお役にたててれば、嬉しいです。
(これからAndroidのアプリ開発やJavaでの開発を始めたい方への案内は、記事の最後で紹介します)

この記事のテーマ


GooglePlay BillingLibrary5 で非推奨となった querySkuDetailsAsync の代わりに
launchBillingFlow に引き渡す billingFlowParams を生成する


2022年11月以降のアプリ内課金の実装は、BillingLibrary4 以降の使用が必須となります。
ここでは、最新バージョンにあたる BillingLibrary5 への移行について説明します。

非推奨となった querySkuDetailsAsync の対応

◎ポイント
Google Play Billing Library 5( com.android.billingclient:billing:5 ) では、
querySkuDetailsAsync、SkuDetails などが非推奨になり、
替わりに queryPurchasesAsync、ProductDetails を
使用した実装に変更する必要があります。

対応前

querySkuDetailsAsync を呼び出して、SkuDetails のリストを取得します。
SkuDetails のリストを引数に
BillingFlowParams を生成して、
launchBillingFlow を呼び出す実装を行います。

Java 対応前コーディング(参考)

◎アプリ内課金クラス(InAppBilling)
購入可能リスト取得では、querySkuDetailsAsync を使用して、SkuDetails を取得します。
SkuDetails を引数として、購入フローを起動します。

public class InAppBilling {
    private BillingClient               billingClient;
    private final ArrayList<String>     errorMessage = new ArrayList<>();
    private List<Purchase>              arrayListPurchase = new ArrayList<>();
    private List<SkuDetails>            arraySkuDetails = new ArrayList<>();
    private final int                   responseCode = Integer.MAX_VALUE;
    // インタフェース
    public interface OnBillingClientStateListener {
        void onBillingClientState(int responseCode);
    }
    public interface OnConsumeAsyncListener {
        void consumeAsync(int consume);
    }
    public interface OnPurchasesUpdatedListener {
        void purchasesUpdated(int purchase);
    }
    public interface OnPurchasesResponseListener {
        void queryPurchasesResponse(int responseCode);
    }
    public interface OnSkuDetailsResponseListener {
        void skuDetailsResponse(int responseCode);
    }
    // コンストラクタ
    public InAppBilling(Context context,
                        OnBillingClientStateListener onBillingClientStateListener,
                        OnPurchasesUpdatedListener onPurchasesUpdatedListener,
                        OnConsumeAsyncListener onConsumeAsyncListener) {
        billingClient = BillingClient.newBuilder(context).setListener((billingResult, list) -> {
            switch (billingResult.getResponseCode()) {
                case BillingClient.BillingResponseCode.OK:
                    if (null != list) {
                        for (Purchase purchase : list) {
                            if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
                                billingClient.consumeAsync(ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(), (consumeResult, s) -> {
                                    if (consumeResult.getResponseCode() != BillingClient.BillingResponseCode.OK)
                                        errorMessage.add(String.format("Error while consuming : %s", consumeResult.getDebugMessage()));
                                    onConsumeAsyncListener.consumeAsync(consumeResult.getResponseCode());
                                });
                            }
                        }
                    }
                    break;
                case BillingClient.BillingResponseCode.USER_CANCELED:
                case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:
                    break;
                case BillingClient.BillingResponseCode.DEVELOPER_ERROR:
                default:
                    errorMessage.add(String.format("BillingResult : %s", billingResult.getDebugMessage()));
                    break;
            }
            onPurchasesUpdatedListener.purchasesUpdated(billingResult.getResponseCode());
        }).enablePendingPurchases().build();
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingServiceDisconnected() {
                    onBillingClientStateListener.onBillingClientState(responseCode);
            }
            @Override
            public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
                onBillingClientStateListener.onBillingClientState(billingResult.getResponseCode());
            }
        });
    }
    // エラーメッセージ取得
    public ArrayList<String> getErrorMessage() {
        return errorMessage;
    }
    public int getConnectionState() {
        return billingClient.getConnectionState();
    }
    // BillingClient切断
    public void endConnection() {
        if (getConnectionState() ==  BillingClient.ConnectionState.CONNECTED)
            billingClient.endConnection();
    }
    // 購入済みリスト取得
    public void queryPurchasesAsync(OnPurchasesResponseListener onPurchasesResponseListener) {
        arrayListPurchase = new ArrayList<>();
        billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, (result, list) -> {
            arrayListPurchase.addAll(list);
            onPurchasesResponseListener.queryPurchasesResponse(result.getResponseCode());
        });
    }
    public List<Purchase> getArrayListPurchase() {
        return arrayListPurchase;
    }
    // 購入可能リスト取得
    public void querySkuDetailsAsync(String sku, OnSkuDetailsResponseListener onSkuDetailsResponseListener) {
        List<String> skuList = new ArrayList<>();
        arraySkuDetails = new ArrayList<>();
        skuList.add(sku);
        SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
        params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
        billingClient.querySkuDetailsAsync(params.build(), (billingResult, list) -> {
            if (list != null) arraySkuDetails.addAll(list);
            onSkuDetailsResponseListener.skuDetailsResponse(billingResult.getResponseCode());
        });
    }
    public List<SkuDetails> getArraySkuDetails() {
        return  arraySkuDetails;
    }
    // 購入フロー起動
    public int launchBillingFlow(Activity activity, SkuDetails skuDetails) {
        BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build();
        return billingClient.launchBillingFlow(activity, billingFlowParams).getResponseCode();
    }
    // 消費
    public void consumeAsync(Purchase purchase, OnConsumeAsyncListener onConsumeAsyncListener) {
        billingClient.consumeAsync(ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(), (consumeResult, s) -> {
            if (consumeResult.getResponseCode() != BillingClient.BillingResponseCode.OK)
                errorMessage.add(String.format("Error while consuming : %s", consumeResult.getDebugMessage()));
            onConsumeAsyncListener.consumeAsync(consumeResult.getResponseCode());
        });
    }
}

◎アプリ内課金の実装部
アプリ内課金クラス(InAppBilling)の購入可能リスト取得では、
戻り値として SkuDetails を受け取り、launchBillingFlow を起動しています。

    // android.test.purchased	        正常に購入出来るテスト用プロダクトID
    // android.test.canceled	        購入のキャンセルをテストできるプロダクトID
    // android.test.refunded	        払い戻しが行われた時のレスポンスをテストできるプロダクトID
    // android.test.item_unavailable	購入希望商品が存在しなかった時をシミュレートできるプロダクトID
    private static final String       code = "android.test.purchased";
    private InAppBilling              inAppBilling;
    :
        inAppBilling = new InAppBilling(context,
                responseCode -> {
                    if (responseCode == BillingClient.BillingResponseCode.OK) {
                        // 購入済みリスト取得
                        inAppBilling.queryPurchasesAsync(responseCode1 -> {
                            if (responseCode1 == BillingClient.BillingResponseCode.OK) {
                                List<Purchase> listPurchase = inAppBilling.getArrayListPurchase();
                                if (listPurchase.size() > 0) {
                                    for (Purchase purchase : listPurchase) {
                                        // 消費リクエスト
                                        inAppBilling.consumeAsync(purchase, consume -> {
                                            :  
                                        });
                                    }
                                } else {
                                    // 購入可能リスト取得
                                    inAppBilling.querySkuDetailsAsync(code, responseCode2 -> {
                                        if (responseCode2 == BillingClient.BillingResponseCode.OK) {
                                            List<SkuDetails> listSkuDetails = inAppBilling.getArraySkuDetails();
                                            if (listSkuDetails.size() > 0) {
                                                for (SkuDetails skuDetails : listSkuDetails) {
                                                    if (inAppBilling.launchBillingFlow(this, skuDetails) != 0) {
                                                        for (String errorMessage : inAppBilling.getErrorMessage()) {
                                                            Log.d(TAG, errorMessage);
                                                        }
                                                    }
                                                }
                                            } else {
                                                :
                                            }
                                        }
                                    });
                                }
                            }
                        });
                    } else {
                        for (String errorMessage : inAppBilling.getErrorMessage()) {
                            Log.d(TAG, errorMessage);
                        }
                        :
                    }
                },
                purchase -> {
                    if (purchase != BillingClient.BillingResponseCode.OK) {
                        for (String errorMessage : inAppBilling.getErrorMessage()) {
                            Log.d(TAG, errorMessage);
                        }
                        :
                    }
                },
                consume -> {
                    // 消費リクエスト
                    if (consume == BillingClient.BillingResponseCode.OK) {
                        :
                    } else {
                        :
                    }
                }
        );
    }

対応後

queryPurchasesAsync を呼び出して、ProductDetails のリストを取得します。
ProductDetails
のリストを引数にBillingFlowParams を生成して、
launchBillingFlow を呼び出す実装を行います。

Java 対応後コーディング

◎アプリ内課金クラス(InAppBilling)
購入可能リスト取得では、queryProductDetailsAsync を使用して、ProductDetailsを取得します。
ProductDetails を引数として、購入フローを起動します。

public class InAppBilling {
    private BillingClient               billingClient;
    private final ArrayList<String>     errorMessage = new ArrayList<>();
    private List<Purchase>              arrayListPurchase = new ArrayList<>();
    private final List<ProductDetails>  arrayProductDetails = new ArrayList<>();
    private final int                   responseCode = Integer.MAX_VALUE;
    // インタフェース //
    public interface OnBillingClientStateListener {
        void onBillingClientState(int responseCode);
    }
    public interface OnConsumeAsyncListener {
        void consumeAsync(int consume);
    }
    public interface OnPurchasesUpdatedListener {
        void purchasesUpdated(int purchase);
    }
    public interface OnPurchasesResponseListener {
        void queryPurchasesResponse(int responseCode);
    }
    public interface OnSkuDetailsResponseListener {
        void skuDetailsResponse(int responseCode);
    }
    // コンストラクタ //
    public InAppBilling(Context context,
                        OnBillingClientStateListener onBillingClientStateListener,
                        OnPurchasesUpdatedListener onPurchasesUpdatedListener,
                        OnConsumeAsyncListener onConsumeAsyncListener) {
        billingClient = BillingClient.newBuilder(context).setListener((billingResult, list) -> {
            switch (billingResult.getResponseCode()) {
                case BillingClient.BillingResponseCode.OK:
                    if (null != list) {
                        for (Purchase purchase : list) {
                            if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
                                billingClient.consumeAsync(ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(), (consumeResult, s) -> {
                                    if (consumeResult.getResponseCode() != BillingClient.BillingResponseCode.OK)
                                        errorMessage.add(String.format("Error while consuming : %s", consumeResult.getDebugMessage()));
                                    onConsumeAsyncListener.consumeAsync(consumeResult.getResponseCode());
                                });
                            }
                        }
                    }
                    break;
                case BillingClient.BillingResponseCode.USER_CANCELED:
                case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:
                    break;
                case BillingClient.BillingResponseCode.DEVELOPER_ERROR:
                default:
                    errorMessage.add(String.format("BillingResult : %s", billingResult.getDebugMessage()));
                    break;
            }
            onPurchasesUpdatedListener.purchasesUpdated(billingResult.getResponseCode());
        }).enablePendingPurchases().build();
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingServiceDisconnected() {
                onBillingClientStateListener.onBillingClientState(responseCode);
            }
            @Override
            public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
                onBillingClientStateListener.onBillingClientState(billingResult.getResponseCode());
            }
        });
    }
    // エラーメッセージ取得 //
    public ArrayList<String> getErrorMessage() {
        return errorMessage;
    }
    // コネクションステート取得 //
    public int getConnectionState() {
        return billingClient.getConnectionState();
    }
    // BillingClient切断 //
    public void endConnection() {
        if (getConnectionState() ==  BillingClient.ConnectionState.CONNECTED)
            billingClient.endConnection();
    }
    // 購入済みリスト取得 //
    public void queryPurchasesAsync(OnPurchasesResponseListener onPurchasesResponseListener) {
        arrayListPurchase = new ArrayList<>();
        billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build(), (result, list) -> {
            // List<Purchase>
            arrayListPurchase.addAll(list);
            onPurchasesResponseListener.queryPurchasesResponse(result.getResponseCode());
        });
    }
    public List<Purchase> getArrayListPurchase() {
        return arrayListPurchase;
    }
    // 購入可能リスト取得 //
    public void queryProductDetailsAsync(String product, OnSkuDetailsResponseListener onSkuDetailsResponseListener) {
        List<QueryProductDetailsParams.Product> productList = new ArrayList<>();
        productList.add(QueryProductDetailsParams.Product.newBuilder()
                .setProductId(product)
                .setProductType(BillingClient.ProductType.INAPP)
                .build());
        QueryProductDetailsParams.Builder params = QueryProductDetailsParams.newBuilder();
        params.setProductList(productList);
        billingClient.queryProductDetailsAsync(params.build(), (billingResult, list) -> {
            // List<ProductDetails>
            arrayProductDetails.addAll(list);
            onSkuDetailsResponseListener.skuDetailsResponse(billingResult.getResponseCode());
        });
    }
    public List<BillingFlowParams.ProductDetailsParams> getProductDetailsParams() {
        List<BillingFlowParams.ProductDetailsParams> productDetailsParamsList = new ArrayList<>();
        for (ProductDetails productDetails : arrayProductDetails)
            // List<ProductDetailsParams>
            productDetailsParamsList.add(BillingFlowParams.ProductDetailsParams.newBuilder().setProductDetails(productDetails).build());
        return productDetailsParamsList;
    }
    // 購入フロー起動 //
    public int launchBillingFlow(Activity activity, List<BillingFlowParams.ProductDetailsParams> skuDetails) {
        BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setProductDetailsParamsList(skuDetails).build();
        return billingClient.launchBillingFlow(activity, billingFlowParams).getResponseCode();
    }
    // 消費 //
    public void consumeAsync(Purchase purchase, OnConsumeAsyncListener onConsumeAsyncListener) {
        billingClient.consumeAsync(ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(), (consumeResult, s) -> {
            if (consumeResult.getResponseCode() != BillingClient.BillingResponseCode.OK)
                errorMessage.add(String.format("Error while consuming : %s", consumeResult.getDebugMessage()));
            onConsumeAsyncListener.consumeAsync(consumeResult.getResponseCode());
        });
    }
}

◎アプリ内課金の実装部
アプリ内課金クラス(InAppBilling)の購入可能リスト取得では、
getProductDetailsParams を使用して、ProductDetails を受け取り、
launchBillingFlowを起動しています。

    // android.test.purchased	        正常に購入出来るテスト用プロダクトID
    // android.test.canceled	        購入のキャンセルをテストできるプロダクトID
    // android.test.refunded	        払い戻しが行われた時のレスポンスをテストできるプロダクトID
    // android.test.item_unavailable	購入希望商品が存在しなかった時をシミュレートできるプロダクトID
    private static final String       code = "android.test.purchased";
    private InAppBilling              inAppBilling;
    :
        inAppBilling = new InAppBilling(context,
                responseCode -> {
                    if (responseCode == BillingClient.BillingResponseCode.OK) {
                        // 購入済みリスト取得
                        inAppBilling.queryPurchasesAsync(responseCode1 -> {
                            if (responseCode1 == BillingClient.BillingResponseCode.OK) {
                                List<Purchase> listPurchase = inAppBilling.getArrayListPurchase();
                                if (listPurchase.size() > 0) {
                                    for (Purchase purchase : listPurchase) {
                                        // 消費リクエスト
                                        inAppBilling.consumeAsync(purchase, consume -> {
                                            :
                                        });
                                    }
                                } else {
                                    // 購入可能リスト取得
                                    inAppBilling.queryProductDetailsAsync(code, responseCode2 -> {
                                        if (responseCode2 == BillingClient.BillingResponseCode.OK) {
                                            if (inAppBilling.launchBillingFlow(this, inAppBilling.getProductDetailsParams()) != 0)
                                                for (String errorMessage : inAppBilling.getErrorMessage())
                                                    Log.d(TAG, errorMessage);
                                            else {
                                                :
                                            } 
                                        }
                                    });
                                }
                            }
                        });
                    } else {
                        for (String errorMessage : inAppBilling.getErrorMessage()) {
                            Log.d(TAG, errorMessage);
                        }
                        :
                    }
                },
                purchase -> {
                    if (purchase != BillingClient.BillingResponseCode.OK) {
                        for (String errorMessage : inAppBilling.getErrorMessage()) {
                            Log.d(TAG, errorMessage);
                        }
                        :
                    }
                },
                consume -> {
                    // 消費リクエスト
                    if (consume == BillingClient.BillingResponseCode.OK) {
                        :
                    } else {
                        :
                    }
                }
        );
    }

Google Play Billing Library 5対応 した Androidアプリです。

今回は、ここまでです。

参考 :Billing Library 5 への移行

データのバックアップにNASを使用していますが、読み書きの速度がイマイチです。
外付けポータブルSSDだと600MB/Sで内蔵HDDと比べて遜色ない性能ですね♪

誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にある
コメントか、
こちら
から、お願いいたします♪

ポチッとして頂けると、
次のコンテンツを作成する励みになります♪

ブログランキング・にほんブログ村へ

◎これからAndroidのアプリ開発やJavaでの開発を始めたい方へ

初めての Android のアプリ開発では、アプリケーション開発経験がない方や、
アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、
かなりの時間がかかりますので、オンラインスクールでの習得をおススメします。

未経験者からシステムエンジニアを目指すのに最適かと、
まずは無料相談から♪

無料でJava言語を学べるのは、かなり魅力的♪
でも、応募資格は35歳以下です

未経験者からプログラマーを目指すのに最適かと、
まずは無料カウンセリングから♪

カリキュラムとサポートがしっかりしています、
お得なキャンペーンとかいろいろやっています♪

ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適かと、
まずは無料オンライン相談から♪

コメント欄

タイトルとURLをコピーしました