From 16f07c1d69666bb387c8868156703e7d0e2c1c55 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Mon, 28 Jul 2025 21:05:36 +0300 Subject: [PATCH 1/3] Update billing API to v8 --- .../ui/screens/support/SupportActivity.java | 32 +++++----- .../ui/screens/support/SupportViewModel.java | 10 +-- .../support/repository/SupportRepository.java | 61 ++++++++++++------- 3 files changed, 59 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportActivity.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportActivity.java index aae6837..48a5059 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportActivity.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportActivity.java @@ -10,7 +10,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider; -import com.android.billingclient.api.SkuDetails; +import com.android.billingclient.api.ProductDetails; import com.d4rk.androidtutorials.java.databinding.ActivitySupportBinding; import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate; @@ -43,7 +43,7 @@ protected void onCreate(Bundle savedInstanceState) { binding.buttonWebAd.setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://bit.ly/3p8bpjj")))); - supportViewModel.initBillingClient(this::querySkuDetails); + supportViewModel.initBillingClient(this::queryProductDetails); binding.buttonLowDonation.setOnClickListener(v -> initiatePurchase("low_donation")); binding.buttonNormalDonation.setOnClickListener(v -> initiatePurchase("normal_donation")); @@ -51,25 +51,25 @@ protected void onCreate(Bundle savedInstanceState) { binding.buttonExtremeDonation.setOnClickListener(v -> initiatePurchase("extreme_donation")); } - private void querySkuDetails() { - List skuList = List.of("low_donation", "normal_donation", "high_donation", "extreme_donation"); - supportViewModel.querySkuDetails(skuList, skuDetailsList -> { - for (SkuDetails skuDetails : skuDetailsList) { - switch (skuDetails.getSku()) { - case "low_donation" -> binding.buttonLowDonation.setText(skuDetails.getPrice()); - case "normal_donation" -> - binding.buttonNormalDonation.setText(skuDetails.getPrice()); - case "high_donation" -> - binding.buttonHighDonation.setText(skuDetails.getPrice()); - case "extreme_donation" -> - binding.buttonExtremeDonation.setText(skuDetails.getPrice()); + private void queryProductDetails() { + List productIds = List.of("low_donation", "normal_donation", "high_donation", "extreme_donation"); + supportViewModel.queryProductDetails(productIds, productDetailsList -> { + for (ProductDetails productDetails : productDetailsList) { + String price = productDetails.getOneTimePurchaseOfferDetails() != null + ? productDetails.getOneTimePurchaseOfferDetails().getFormattedPrice() + : ""; + switch (productDetails.getProductId()) { + case "low_donation" -> binding.buttonLowDonation.setText(price); + case "normal_donation" -> binding.buttonNormalDonation.setText(price); + case "high_donation" -> binding.buttonHighDonation.setText(price); + case "extreme_donation" -> binding.buttonExtremeDonation.setText(price); } } }); } - private void initiatePurchase(String sku) { - supportViewModel.initiatePurchase(this, sku); + private void initiatePurchase(String productId) { + supportViewModel.initiatePurchase(this, productId); } @Override diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportViewModel.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportViewModel.java index b1f70e0..d70fa4c 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportViewModel.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/SupportViewModel.java @@ -24,13 +24,13 @@ public void initBillingClient(Runnable onConnected) { repository.initBillingClient(onConnected); } - public void querySkuDetails(List skuList, - SupportRepository.OnSkuDetailsListener listener) { - repository.querySkuDetails(skuList, listener); + public void queryProductDetails(List productIds, + SupportRepository.OnProductDetailsListener listener) { + repository.queryProductDetails(productIds, listener); } - public void initiatePurchase(Activity activity, String sku) { - repository.initiatePurchase(activity, sku); + public void initiatePurchase(Activity activity, String productId) { + repository.initiatePurchase(activity, productId); } public void initMobileAds(ActivitySupportBinding binding) { diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java index ce4c4d2..649ec9a 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java @@ -9,8 +9,9 @@ import com.android.billingclient.api.BillingClientStateListener; import com.android.billingclient.api.BillingFlowParams; import com.android.billingclient.api.BillingResult; -import com.android.billingclient.api.SkuDetails; -import com.android.billingclient.api.SkuDetailsParams; +import com.android.billingclient.api.PendingPurchasesParams; +import com.android.billingclient.api.ProductDetails; +import com.android.billingclient.api.QueryProductDetailsParams; import com.d4rk.androidtutorials.java.databinding.ActivitySupportBinding; import com.google.android.gms.ads.AdRequest; import com.google.android.gms.ads.MobileAds; @@ -25,7 +26,7 @@ public class SupportRepository { private final Context context; - private final Map skuDetailsMap = new HashMap<>(); + private final Map productDetailsMap = new HashMap<>(); private BillingClient billingClient; public SupportRepository(Context context) { @@ -41,7 +42,10 @@ public void initBillingClient(Runnable onConnected) { billingClient = BillingClient.newBuilder(context) .setListener((billingResult, purchases) -> { }) - .enablePendingPurchases() + .enablePendingPurchases( + PendingPurchasesParams.newBuilder() + .enableOneTimeProducts() + .build()) .build(); billingClient.startConnection(new BillingClientStateListener() { @@ -63,40 +67,51 @@ public void onBillingServiceDisconnected() { } /** - * Query your SKU details for in-app items. + * Query your product details for in-app items. * Typically called after billing client is connected. */ - public void querySkuDetails(List skuList, OnSkuDetailsListener listener) { + public void queryProductDetails(List productIds, OnProductDetailsListener listener) { if (billingClient == null || !billingClient.isReady()) { return; } - SkuDetailsParams params = SkuDetailsParams.newBuilder() - .setSkusList(skuList) - .setType(BillingClient.SkuType.INAPP) + + List products = productIds.stream() + .map(id -> QueryProductDetailsParams.Product.newBuilder() + .setProductId(id) + .setProductType(BillingClient.ProductType.INAPP) + .build()) + .toList(); + + QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder() + .setProductList(products) .build(); - billingClient.querySkuDetailsAsync(params, (billingResult, skuDetailsList) -> { + billingClient.queryProductDetailsAsync(params, (billingResult, productDetailsList) -> { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK - && skuDetailsList != null) { - for (SkuDetails skuDetails : skuDetailsList) { - skuDetailsMap.put(skuDetails.getSku(), skuDetails); + && productDetailsList != null) { + for (ProductDetails productDetails : productDetailsList) { + productDetailsMap.put(productDetails.getProductId(), productDetails); } if (listener != null) { - listener.onSkuDetailsRetrieved(skuDetailsList); + listener.onProductDetailsRetrieved(productDetailsList); } } }); } /** - * Launch the billing flow for a particular SKU. + * Launch the billing flow for a particular product. */ - public void initiatePurchase(Activity activity, String sku) { - if (skuDetailsMap.containsKey(sku)) { - SkuDetails skuDetails = skuDetailsMap.get(sku); - if (skuDetails != null) { + public void initiatePurchase(Activity activity, String productId) { + if (productDetailsMap.containsKey(productId)) { + ProductDetails details = productDetailsMap.get(productId); + if (details != null) { + BillingFlowParams.ProductDetailsParams productParams = + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(details) + .build(); BillingFlowParams flowParams = BillingFlowParams.newBuilder() - .setSkuDetails(skuDetails) + .setProductDetailsParamsList(List.of(productParams)) .build(); billingClient.launchBillingFlow(activity, flowParams); } @@ -113,10 +128,10 @@ public void initMobileAds(ActivitySupportBinding binding) { } /** - * Callback interface for when SKU details are fetched. + * Callback interface for when product details are fetched. */ - public interface OnSkuDetailsListener { - void onSkuDetailsRetrieved(List skuDetailsList); + public interface OnProductDetailsListener { + void onProductDetailsRetrieved(List productDetailsList); } } \ No newline at end of file From cf8511947c531f68059a84947ffe37a9739c1a5d Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Mon, 28 Jul 2025 21:27:02 +0300 Subject: [PATCH 2/3] Fix product details query callback --- .../java/ui/screens/support/repository/SupportRepository.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java index 649ec9a..c264ee1 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java @@ -86,7 +86,9 @@ public void queryProductDetails(List productIds, OnProductDetailsListene .setProductList(products) .build(); - billingClient.queryProductDetailsAsync(params, (billingResult, productDetailsList) -> { + billingClient.queryProductDetailsAsync(params, result -> { + BillingResult billingResult = result.getBillingResult(); + List productDetailsList = result.getProductDetailsList(); if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && productDetailsList != null) { for (ProductDetails productDetails : productDetailsList) { From d5511e6b366d89af68e2e2bcee51e8720a531b31 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Mon, 28 Jul 2025 21:50:27 +0300 Subject: [PATCH 3/3] fix: update billing product details query --- .../support/repository/SupportRepository.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java index c264ee1..aa4cde1 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/support/repository/SupportRepository.java @@ -16,6 +16,7 @@ import com.google.android.gms.ads.AdRequest; import com.google.android.gms.ads.MobileAds; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -75,20 +76,19 @@ public void queryProductDetails(List productIds, OnProductDetailsListene return; } - List products = productIds.stream() - .map(id -> QueryProductDetailsParams.Product.newBuilder() - .setProductId(id) - .setProductType(BillingClient.ProductType.INAPP) - .build()) - .toList(); + List products = new ArrayList<>(); + for (String id : productIds) { + products.add(QueryProductDetailsParams.Product.newBuilder() + .setProductId(id) + .setProductType(BillingClient.ProductType.INAPP) + .build()); + } QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder() .setProductList(products) .build(); - billingClient.queryProductDetailsAsync(params, result -> { - BillingResult billingResult = result.getBillingResult(); - List productDetailsList = result.getProductDetailsList(); + billingClient.queryProductDetailsAsync(params, (billingResult, productDetailsList) -> { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && productDetailsList != null) { for (ProductDetails productDetails : productDetailsList) {