Solution 1 :
I have not solved the issue yet but I found a way to reduce the numbers of errors generated by this library upgrade.
What I did was to downgrade the Google Billing Library from version 3.0.1 to version 2.1.0 and even though I still get some errors in Firebase (SKU is null), the majority of users are now able to purchase the products.
Also, I implemented a method that is called whenever the Google Billing library connection cannot be started when the activity is first opened, so more exactly this is restart billing connection method.
I would recommend you to try the same thing at least for now if you are experiencing the same issue because it seems that the Google Billing library still has some issues that need to be fixed.
1. In build.gradle(app) add this line:
implementation 'com.android.billingclient:billing:2.1.0'
2. Add the BILLING permission in AndroidManifest.xml file because the older versions of this library still require it:
<uses-permission android_name="com.android.vending.BILLING" />
3. Create a restart billing connection method:
public void restartBillingConnection() {
billingClient = BillingClient.newBuilder(Objects.requireNonNull(getActivity())).enablePendingPurchases().setListener(ChooseOptionsFragment.this).build();
billingClient.startConnection(new BillingClientStateListener() {@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
Log.d(TAG, "Connection finished");
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
List < String > skuList = new ArrayList < >();
skuList.add(ITEM_SKU_AD_REMOVAL);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {@Override
public void onSkuDetailsResponse(@NonNull BillingResult billingResult, List < SkuDetails > skuDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
for (Object skuDetailsObject: skuDetailsList) {
skuDetails = (SkuDetails) skuDetailsObject;
sku = skuDetails.getSku();
String price = skuDetails.getPrice();
if (ITEM_SKU_AD_REMOVAL.equals(sku)) {
skuPrice = price;
BillingFlowParams flowParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build();
billingClient.launchBillingFlow(Objects.requireNonNull(Objects.requireNonNull(getActivity())), flowParams);
}
else {
Log.d(TAG, "Sku is null");
}
}
Log.d(TAG, "i got response");
Log.d(TAG, String.valueOf(billingResult.getResponseCode()));
Log.d(TAG, billingResult.getDebugMessage());
}
else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ERROR) {
Toast.makeText(getActivity(), "Error in completing the purchase!", Toast.LENGTH_SHORT).show();
}
}
});
}
else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_TIMEOUT) {
Toast.makeText(getActivity(), "Service timeout!", Toast.LENGTH_SHORT).show();
}
else {
Toast.makeText(getActivity(), "Failed to connect to the billing client!", Toast.LENGTH_SHORT).show();
}
}@Override
public void onBillingServiceDisconnected() {
restartBillingConnection();
}
});
}
4. Make sure that this method is called when the Google Billing service gets disconnected:
@Override
public void onBillingServiceDisconnected() {
restartBillingConnection();
}
Hope that this solution will help you to fix the issue for now. If you will have another way to have it completely fixed please leave an answer in this post.
Solution 2 :
I had the same or maybe just a similar problem. But I have found the reason in my case. I could reproduce it in the following way:
- Disable internet connection
- Reinstall the app
- Try to start the In-App purchase like described here: Link
- Seems to be similar to your routine
The querySkuDetailsAsync
returned null
for every item and when initiatePurchaseFlow
is called null
is passed to it and the BillingFlowParams.Builder
. I assume that that has changed and before it did not throw the Exception but just handled it differently. I fixed this now by checking if the item in the Map is null
and then I display a warning that an Internet connection is required.
Solution 3 :
I believe this may be occurring due to concurrency (ie: buy button being hit twice).
Try disabling the “buy” button while the whole billing flow is running.
Problem :
I have an Android app released on the Google Play Store, and last week I released a new update, just to fix some small issues. Starting with the day when I added the updated version on the Play Store, I could see on Firebase Crashlytics that there are issues when someone is trying to purchase an app feature.
Before I released the updated version in production, I added the app on the Alpha Testing so I can make sure that the InAppPurchase work, and it does.
When someone else is trying to purchase an app feature I can see that this Fatal Exception is thrown:
Fatal Exception: java.lang.IllegalArgumentException: SKU cannot be null.
at com.android.billingclient.api.BillingFlowParams$Builder.build(com.android.billingclient:[email protected]@3.0.0:23)
The SKU’s are still active on my “Managed Products” list.
This is the code that I use to initialize the billing client (within a fragment):
billingClient = BillingClient.newBuilder(getActivity())
.enablePendingPurchases()
.setListener(purchasesUpdatedListener)
.build();
This is the code that I use to start the connection:
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
Log.d(TAG, "Connection finished");
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
List<String> skuList = new ArrayList<>();
skuList.add("unlock_keyboard");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(@NonNull BillingResult billingResult,
List<SkuDetails> skuDetailsList) {
// Process the result.
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
for (Object skuDetailsObject : skuDetailsList) {
skuDetails = (SkuDetails) skuDetailsObject;
sku = skuDetails.getSku();
}
Log.d(TAG, "i got response");
Log.d(TAG, String.valueOf(billingResult.getResponseCode()));
Log.d(TAG, billingResult.getDebugMessage());
}
}
});
}
}
This is the code that I use to handle the purchase:
PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
@Override
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> list) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) {
for (Purchase purchase : list) {
handlePurchase(purchase);
Log.d(TAG, "Purchase completed" + billingResult.getResponseCode());
}
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
Log.d(TAG, "User Canceled" + billingResult.getResponseCode());
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
if ("unlock_keyboard".equals(sku)) {
KeyboardAlreadyPurchasedConfirmation();
}
Log.d(TAG, "Item Already owned" + billingResult.getResponseCode());
}
}
};
In order to launch the billing flow, the user must click on a button within a dialog. Here is the code:
builder.setPositiveButton(
getString(R.string.purchase_keyboard),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
sku = "unlock_keyboard";
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
billingClient.launchBillingFlow(Objects.requireNonNull(getActivity()), flowParams);
}
});
In the previous version of my app, this situation never happened, it just started after the new update. I’ll just need to know what can cause this issue, could it be a problem with my code or just a issue with Google Play Services? I’ll have to specify that this was happening on different devices with different Android versions.
Thanks a lot in advance.
Comments
Comment posted by mili2501
remove .setSkuDetails(skuDetails) and add .setSku(YOUR_SKU) .setType(BillingClient.SkuType.INAPP) to your flowParams
Comment posted by isthemartin
do you have items on List
Comment posted by L3n95
Do you have solved the issue? I have the same problem. For me it started to occur after I upgraded Google Play Billing library to version 3.0.
Comment posted by Alexandru Dumitru
Unfortunately I did not solve the issue yet. I tried many different things like removing the app from some countries thinking that it might be just a problem with the Google Play Service that is no longer working for those countries but that solution did not work. The thing is that a few users were able to purchase the products after I upgraded Google Play Billing library to version 3.0. So this makes me think that the issue is not with my code, it’s just an issue with the Google Play Billing library, an issue that no one from Google is informing us about, which is very disappointing.
Comment posted by Eren Tüfekçi
any solution for this?