Skip to content

Commit 45329d0

Browse files
author
Nate Eagleson
committed
[WIP] Fix inAppPurchase.getProducts() method on Android
The old implementation fails when there are more than twenty products and the user owns some of them. This is ripped shamelessly from this PR that upstream has never accepted: AlexDisler#57 Note that this undoes the conceptually-wrong change that *was* accepted to deal with this problem: AlexDisler#94
1 parent c66b615 commit 45329d0

File tree

5 files changed

+58
-94
lines changed

5 files changed

+58
-94
lines changed

src/android/IabHelper.java

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@
1515

1616
package com.alexdisler.inapppurchases;
1717

18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.Set;
21+
import java.util.TreeSet;
22+
23+
import android.content.pm.ResolveInfo;
24+
25+
import org.json.JSONException;
1826
import android.app.Activity;
1927
import android.app.PendingIntent;
2028
import android.content.ComponentName;
@@ -71,6 +79,9 @@
7179
*
7280
*/
7381
public class IabHelper {
82+
83+
public static final int QUERY_SKU_DETAILS_BATCH_SIZE = 20;
84+
7485
// Is debug logging enabled?
7586
boolean mDebugLog = false;
7687
String mDebugTag = "IabHelper";
@@ -1087,46 +1098,66 @@ int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteE
10871098
int querySkuDetails(String itemType, Inventory inv, List<String> moreSkus)
10881099
throws RemoteException, JSONException {
10891100
logDebug("Querying SKU details.");
1090-
ArrayList<String> skuList = new ArrayList<String>();
1091-
skuList.addAll(inv.getAllOwnedSkus(itemType));
1101+
Set<String> storeSkus = new TreeSet<String>();
1102+
final List<String> allOwnedSkus = inv.getAllOwnedSkus(itemType);
1103+
storeSkus.addAll(inv.getAllOwnedSkus(itemType));
10921104
if (moreSkus != null) {
10931105
for (String sku : moreSkus) {
1094-
if (!skuList.contains(sku)) {
1095-
skuList.add(sku);
1106+
if (!storeSkus.contains(sku)) {
1107+
storeSkus.add(sku);
10961108
}
10971109
}
10981110
}
10991111

1100-
if (skuList.size() == 0) {
1101-
logDebug("queryPrices: nothing to do because there are no SKUs.");
1112+
if (storeSkus.size() == 0) {
1113+
logDebug("querySkuDetails(): nothing to do because there are no SKUs.");
11021114
return BILLING_RESPONSE_RESULT_OK;
11031115
}
11041116

1105-
Bundle querySkus = new Bundle();
1106-
querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList);
1107-
Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(),
1108-
itemType, querySkus);
1109-
1110-
if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
1111-
int response = getResponseCodeFromBundle(skuDetails);
1112-
if (response != BILLING_RESPONSE_RESULT_OK) {
1113-
logDebug("getSkuDetails() failed: " + getResponseDesc(response));
1114-
return response;
1117+
// Split the sku list in blocks of no more than QUERY_SKU_DETAILS_BATCH_SIZE elements.
1118+
ArrayList<ArrayList<String>> batches = new ArrayList<ArrayList<String>>();
1119+
ArrayList<String> tmpBatch = new ArrayList<String>(QUERY_SKU_DETAILS_BATCH_SIZE);
1120+
int iSku = 0;
1121+
for (String sku : storeSkus) {
1122+
tmpBatch.add(sku);
1123+
iSku++;
1124+
if (tmpBatch.size() == QUERY_SKU_DETAILS_BATCH_SIZE || iSku == storeSkus.size()) {
1125+
batches.add(tmpBatch);
1126+
tmpBatch = new ArrayList<String>(QUERY_SKU_DETAILS_BATCH_SIZE);
11151127
}
1116-
else {
1117-
logError("getSkuDetails() returned a bundle with neither an error nor a detail list.");
1128+
}
1129+
1130+
logDebug("querySkuDetails() batches: " + batches.size() + ", " + batches);
1131+
1132+
for (ArrayList<String> batch : batches) {
1133+
Bundle querySkus = new Bundle();
1134+
querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, batch);
1135+
if (mService == null) {
1136+
logError("unable to get sku details: service is not connected.");
11181137
return IABHELPER_BAD_RESPONSE;
11191138
}
1120-
}
1139+
Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(), itemType, querySkus);
1140+
1141+
if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
1142+
int response = getResponseCodeFromBundle(skuDetails);
1143+
if (response != BILLING_RESPONSE_RESULT_OK) {
1144+
logDebug("getSkuDetails() failed: " + getResponseDesc(response));
1145+
return response;
1146+
} else {
1147+
logError("getSkuDetails() returned a bundle with neither an error nor a detail list.");
1148+
return IABHELPER_BAD_RESPONSE;
1149+
}
1150+
}
11211151

1122-
ArrayList<String> responseList = skuDetails.getStringArrayList(
1123-
RESPONSE_GET_SKU_DETAILS_LIST);
1152+
ArrayList<String> responseList = skuDetails.getStringArrayList(RESPONSE_GET_SKU_DETAILS_LIST);
11241153

1125-
for (String thisResponse : responseList) {
1126-
SkuDetails d = new SkuDetails(itemType, thisResponse);
1127-
logDebug("Got sku details: " + d);
1128-
inv.addSkuDetails(d);
1154+
for (String thisResponse : responseList) {
1155+
SkuDetails d = new SkuDetails(itemType, thisResponse);
1156+
logDebug("Got sku details: " + d);
1157+
inv.addSkuDetails(d);
1158+
}
11291159
}
1160+
11301161
return BILLING_RESPONSE_RESULT_OK;
11311162
}
11321163

src/js/index-android.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,13 @@ const nativeCall = (name, args = []) => {
2424
});
2525
};
2626

27-
const chunkedGetSkuDetails = (productIds) => {
28-
// We need to chunk the getSkuDetails call cause it is only allowed to provide a maximum of 20 items per call
29-
return utils.chunk(productIds, 19).reduce((promise, productIds) => {
30-
return promise.then((result) => {
31-
return nativeCall('getSkuDetails', productIds).then((items) => result.concat(items));
32-
});
33-
}, Promise.resolve([]));
34-
};
35-
3627
inAppPurchase.getProducts = (productIds) => {
3728
return new Promise((resolve, reject) => {
3829
if(!inAppPurchase.utils.validArrayOfStrings(productIds)) {
3930
reject(new Error(inAppPurchase.utils.errors[101]));
4031
} else {
4132
nativeCall('init', []).then(() => {
42-
return chunkedGetSkuDetails(productIds);
33+
return nativeCall('getSkuDetails', productIds);
4334
})
4435
.then((items) => {
4536
const arr = items.map((val) => {

src/js/utils.js

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,3 @@ utils.validArrayOfStrings = (val) => {
2424
utils.validString = (val) => {
2525
return (val && val.length && typeof val === 'string');
2626
};
27-
28-
utils.chunk = (array, size) => {
29-
if (!Array.isArray(array)) {
30-
throw new Error('Invalid array');
31-
}
32-
33-
if (typeof size !== 'number' || size < 1) {
34-
throw new Error('Invalid size');
35-
}
36-
37-
const times = Math.ceil(array.length / size);
38-
return Array
39-
.apply(null, Array(times))
40-
.reduce((result, val, i) => {
41-
return result.concat([array.slice(i * size, (i + 1) * size)]);
42-
}, []);
43-
};

www/index-android.js

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,6 @@ utils.validArrayOfStrings = function (val) {
6666
utils.validString = function (val) {
6767
return val && val.length && typeof val === 'string';
6868
};
69-
70-
utils.chunk = function (array, size) {
71-
if (!Array.isArray(array)) {
72-
throw new Error('Invalid array');
73-
}
74-
75-
if (typeof size !== 'number' || size < 1) {
76-
throw new Error('Invalid size');
77-
}
78-
79-
var times = Math.ceil(array.length / size);
80-
return Array.apply(null, Array(times)).reduce(function (result, val, i) {
81-
return result.concat([array.slice(i * size, (i + 1) * size)]);
82-
}, []);
83-
};
8469
'use strict';
8570

8671
/*!
@@ -113,24 +98,13 @@ var nativeCall = function nativeCall(name) {
11398
});
11499
};
115100

116-
var chunkedGetSkuDetails = function chunkedGetSkuDetails(productIds) {
117-
// We need to chunk the getSkuDetails call cause it is only allowed to provide a maximum of 20 items per call
118-
return utils.chunk(productIds, 19).reduce(function (promise, productIds) {
119-
return promise.then(function (result) {
120-
return nativeCall('getSkuDetails', productIds).then(function (items) {
121-
return result.concat(items);
122-
});
123-
});
124-
}, Promise.resolve([]));
125-
};
126-
127101
inAppPurchase.getProducts = function (productIds) {
128102
return new Promise(function (resolve, reject) {
129103
if (!inAppPurchase.utils.validArrayOfStrings(productIds)) {
130104
reject(new Error(inAppPurchase.utils.errors[101]));
131105
} else {
132106
nativeCall('init', []).then(function () {
133-
return chunkedGetSkuDetails(productIds);
107+
return nativeCall('getSkuDetails', productIds);
134108
}).then(function (items) {
135109
var arr = items.map(function (val) {
136110
return {

www/index-ios.js

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,6 @@ utils.validArrayOfStrings = function (val) {
2828
utils.validString = function (val) {
2929
return val && val.length && typeof val === 'string';
3030
};
31-
32-
utils.chunk = function (array, size) {
33-
if (!Array.isArray(array)) {
34-
throw new Error('Invalid array');
35-
}
36-
37-
if (typeof size !== 'number' || size < 1) {
38-
throw new Error('Invalid size');
39-
}
40-
41-
var times = Math.ceil(array.length / size);
42-
return Array.apply(null, Array(times)).reduce(function (result, val, i) {
43-
return result.concat([array.slice(i * size, (i + 1) * size)]);
44-
}, []);
45-
};
4631
'use strict';
4732

4833
/*!

0 commit comments

Comments
 (0)