onPurchasesUpdated called multiple times - java

I have set up in-app billing on an Android app (java). When I call launchBillingFlow on the BillingClient:
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build();
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//the system displays the Google Play purchase screen
} else {
Log.e(TAG, "Billing failed: + " + billingResult.getDebugMessage());
}
This is what my onPurchasesUpdated (from PurchasesUpdatedListener) looks like:
#Override
public void onPurchasesUpdated(#NonNull BillingResult billingResult, #Nullable List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& purchases != null) {
for (Purchase purchase : purchases) {
for (String sku : purchase.getSkus()) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (skuConsumables.contains(sku)) {
handlePurchaseConsumable(purchase);
} else if (skuNonconsumables.contains(sku)) {
handlePurchaseNonconsumable(purchase);
}
}
}
}
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
billingServiceListener.receivePurchaseError();
} else {
// Handle any other error codes.
billingServiceListener.receivePurchaseError();
}
}
onPurchasesUpdated is called six times, each time with a responseCode of OK. Twice onPurchasesUpdated is called with zero purchases, that's fine. What I am confused about is how to deal with the four times onPurchasesUpdated is called with one purchase. And it seems as though each of these four purchase objects are indistinguishable - the same packageName, acknowledged, orderId, productId, purchaseState, purchaseToken, etc.
To complicate things, for consumable in-app billing, (these are consumable) I am then calling ConsumeResponseListener and onConsumeResponse is also returning four times, each time with responseCode of OK.
private void handlePurchaseConsumable(Purchase purchase) {
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
ConsumeResponseListener listener = new ConsumeResponseListener() {
#Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// Handle the success of the consume operation.
}
}
};
billingClient.consumeAsync(consumeParams, listener);
}
Is this behaviour seen by others? I am using static responses while in development, could this be the reason? If people are seeing this, how do you deal with this - do you keep track of what purchase you have attempted, and then when the first response is returned do you register that the purchase was successful and ignore subsequent times that onPurchasesUpdated returns a purchase if you weren't expecting a payment? Though I've seen that Android permits cash purchases with enablePendingPurchases, so that can't be a solution...

This can happen for 2 reasons.
if you are using an outdated version of the google-billing API.
if you forgot to destroy the billing client at the finish of the activity.

I've checked Samples for Google Play In-app Billing and how I can see the main idea of usage the billing client is to use it with activity/fragment lifecycles. So your billing client should implement DefaultLifecycleObserver. We need it to have connect/disconnect billing client use UI lifecycle. In other case we could have few instances of billing client in the App and few listeners that will be triggered with success purchase in same time. So code should looks like:
Activity/Fragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(billingLifecycleManager)
}
BillingLifecycleManager:
class BillingLifecycleManager {
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
billingClient = BillingClient.newBuilder(app)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build()
if (billingClient.isReady) {
billingClient.startConnection(...)
}
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
if (billingClient.isReady) {
billingClient.endConnection()
}
}
}

Related

Android Google Play Billing - querySkuDetailsAsync resurning empty list

I have an Android project where I want to use com.android.billingclient.api version 4.0.0, which would replace an old billing library that google doesn't allow any more (com.anjlab.android.iab.v3). I've implemented the methods for a one-time purchase, but when querying the SKU Details with billingClient.querySkuDetailsAsync using the SKU string for the product, I get an empty result set. I've been assured that the SKU is correct, so I don't know where the error might be.
Also, the old implementation required to provide a license key, which isn't the case with the new library. Do I need to define it somewhere else in the app?
Here's the code where it fails:
List<String> skuList = new ArrayList<>();
skuList.add(SKU_ID);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(SkuType.INAPP);
final Activity v = this;
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
Has anyone a suggestion what to do?
This is how I query the SKU details within my app.
You can try to use this example and see if this works for you.
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_ADREMOVAL);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
billingClient.querySkuDetailsAsync(params.build(),
(billingResult1, skuDetailsList) -> {
// Process the result.
if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
for (Object skuDetailsObject : skuDetailsList) {
skuDetails = (SkuDetails) skuDetailsObject;
String sku = skuDetails.getSku();
String price = skuDetails.getPrice();
if (ITEM_SKU_ADREMOVAL.equals(sku)) {
removeadsPrice = price;
}
else {
Log.d(TAG,"Sku is null");
}
}
Log.d(TAG, "i got response");
Log.d(TAG, String.valueOf(billingResult1.getResponseCode()));
Log.d(TAG, billingResult1.getDebugMessage());
}
else if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.ERROR) {
Toast.makeText(MainActivity.this, "Error in completing the purchase!", Toast.LENGTH_SHORT).show();
}
});
}
else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_TIMEOUT) {
Toast.makeText(MainActivity.this, "Service timeout!", Toast.LENGTH_SHORT).show();
}
else {
Toast.makeText(MainActivity.this, "Failed to connect to the billing client!", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onBillingServiceDisconnected() {
restartConnection();
}
});
Getting this one working properly depends on several different factors
Have you published your app to Play Console or at least to an
internal track or something?
Do you have active products or subscriptions on your Google Play
Console?
Have you configured your licensed testers?
Please see the documentation for more info.

Google Nearby Messages API: Attempting to perform a high-power operation from a non-Activity Context

Calling subscribe on the Google Nearby Messages API for Android results in the Exception:
Attempting to perform a high-power operation from a non-Activity Context
My code:
public void subscribe(final Promise promise) {
_messagesClient = Nearby.getMessagesClient(reactContext.getApplicationContext(), new MessagesOptions.Builder().setPermissions(NearbyPermissions.BLE).build());
_subscribeOptions = new SubscribeOptions.Builder()
.setStrategy(Strategy.BLE_ONLY)
.setCallback(new SubscribeCallback() {
#Override
public void onExpired() {
super.onExpired();
emitErrorEvent(EventType.BLUETOOTH_ERROR, true);
}
}).build();
Log.d(getName(), "Subscribing...");
if (_messagesClient != null) {
if (_isSubscribed) {
promise.reject(new Exception("An existing callback is already subscribed to the Google Nearby Messages API! Please unsubscribe before subscribing again!"));
} else {
_messagesClient.subscribe(_listener, _subscribeOptions).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Exception e = task.getException();
Log.d(getName(), "Subscribed!" + e.getLocalizedMessage());
if (e != null) {
_isSubscribed = false;
promise.reject(e);
} else {
_isSubscribed = true;
promise.resolve(null);
}
}
});
}
} else {
promise.reject(new Exception("The Messages Client was null. Did the GoogleNearbyMessagesModule native constructor fail to execute?"));
}
}
Note: The promise Parameter is from React Native, I'm trying to create a wrapper for the API.
At the Log.d event inside my OnCompleteListener, it prints:
Subscribed!2803: Attempting to perform a high-power operation from a non-Activity Context
I do have the API Key and the required Permissions (BLUETOOTH, BLUETOOTH_ADMIN) in my AndroidManifest.xml.
On iOS the API calls work fine.
Solved it! The context
reactContext.getApplicationContext()
is not a valid Activity Context for the Nearby API! I had to use
getCurrentActivity()
which is a method from the base class ReactContextBaseJavaModule.

In-app billing acknowledgePurchase issue [using google play in-app billing library]

I am new to android and trying to implement in-app billing first time.
I am using google play in-app library.
https://developer.android.com/google/play/billing/billing_library_overview
I want to implement consumable in-app purchase. I am using 'android.test.purchased' reserved id for testing. I could load skuDetails and make purchase successfully and consume purchase successfully
here is my handlePurchase method with consumeAsync
void handlePurchase(Purchase purchase) {
BillingClient client = BillingClient.newBuilder(NewAdActivity.this)
.enablePendingPurchases()
.setListener(this)
.build();
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
System.out.println("item successfully purchased");
if (!purchase.isAcknowledged()) {
ConsumeParams consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.setDeveloperPayload(purchase.getDeveloperPayload())
.build();
ConsumeResponseListener consumeResponseListener = new ConsumeResponseListener() {
#Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchaseToken != null) {
System.out.println("SUCCESSFULLY consumed PURCHASE");
providecontent();
}
else {
System.out.println("FAILED TO consume:”);
}
}
};
client.consumeAsync(consumeParams, consumeResponseListener);
}
}
}
Does it also acknowledge purchase when I consume purchase? Do I need to set "acknowledged":true in purchase.originalJson manually?
Is my code correct to consume purchased item? or I need to include a separate acknowledgePurchase before consuming item.
Please reply. Any help is truly appreciated.
Thanks.
For consumable products, you want to use consumeAsync(). For products that aren't consumable, you want to use acknowledgePurchase(). For more about acknowledging purchases in your app, check out the official documentation: https://developer.android.com/google/play/billing/billing_library_overview#acknowledge
Welcome to stackoverflow !
You are incorrectly using consumeAsync(), to acknowledge a purchase you should call acknowledgePurchase().
consumeAsync() removes the item purchased, for example if the purchase allows the user to play 10 times and he uses all of them then you would call consumeAsync() to let the user to buy the item again
An example:
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
// Grant entitlement to the user.
boolean signOk = verifyPurchaseSignature(purchase.getOriginalJson(), purchase.getSignature());
if (!signOk) {
// Alert the user about wrong signature
return;
} else if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
#Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
//Give thanks for the purchase
}
});
}
}

Geo-tagging in Android

I am working on an Application that requires the user to be able to geo-tag picture taken via the camera. The application is to encourage user geo-tag trees to encourage afforestation and reduce global warming.
Is it possible to implement this feature and how can I implement it?
NB: This is my first major project.
Creating a comfortable user experience with location is a big task. These are some of the concerns you'll have to address:
The user may have disabled location globally.
The app may not have the permission to use location.
You want to ask the user for permission and/or to enable location.
You don't want to bother the user by asking over and over again.
Asking for permission means pausing your activity in favor of a system activity that asks for permission. There's a similar but distinct mechanism to ask to enable the location.
Your activity will resume when the user responds, you need to override a special callback method that receives the result.
It's not good to try to fetch location just when you need it. It may take time. Instead you have to ask Android at the outset to push location updates to you.
The user may revoke the location permission while using the app, you must constantly re-check the permissions, be resilient to sudden exceptions, and repeat the request to receive location updates when the location permission comes back.
Here are some snippets from my project that can help you get started:
private val Context.fusedLocationProviderClient get() = getFusedLocationProviderClient(this)
private suspend fun FusedLocationProviderClient.tryFetchLastLocation(): Location? =
lastLocation.await()
?.also { info { "Got response from getLastLocation()" } }
?: run { warn { "getLastLocation() returned null" }; null }
suspend fun Context.canUseLocationFg() =
appHasLocationPermission() &&
locationSettingsException(locationRequestFg, locationRequestBg) == null
import android.Manifest.permission.ACCESS_FINE_LOCATION
import androidx.core.content.PermissionChecker.PERMISSION_GRANTED
fun Context.appHasLocationPermission() =
checkSelfPermission(this, ACCESS_FINE_LOCATION) == PERMISSION_GRANTED
suspend fun Context.locationSettingsException(
vararg locationRequests: LocationRequest
): ApiException? = try {
getSettingsClient(this)
.checkLocationSettings(LocationSettingsRequest.Builder()
.addAllLocationRequests(locationRequests.asList()).build())
.await()
null
} catch (e: ApiException) { e }
const val CODE_REQUEST_FINE_LOCATION = 13
const val CODE_RESOLVE_API_EXCEPTION = 14
suspend fun Fragment.checkAndCorrectPermissionsAndSettings() {
with(context!!) {
if (!appHasLocationPermission()) {
warn { "FG: our app has no permission to access fine location" }
delay(WAIT_MILLISECONDS_BEFORE_ASKING)
if (!appHasLocationPermission()) {
startIntentRequestLocationPermission()
return
}
}
if (locationSettingsException(locationRequestFg, locationRequestBg) == null) return
warn { "FG: ResolvableApiException for location request (probably location disabled)" }
if (!mainPrefs.shouldAskToEnableLocation) return
delay(WAIT_MILLISECONDS_BEFORE_ASKING)
locationSettingsException(locationRequestFg, locationRequestBg)
?.let { it as? ResolvableApiException }
?.also { startIntentResolveException(it) }
}
}
fun Fragment.startIntentRequestLocationPermission() =
requestPermissions(arrayOf(ACCESS_FINE_LOCATION), CODE_REQUEST_FINE_LOCATION)
fun Fragment.startIntentResolveException(e: ResolvableApiException) =
startIntentSenderForResult(e.resolution.intentSender, CODE_RESOLVE_API_EXCEPTION, null, 0, 0, 0, null)
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode != CODE_REQUEST_FINE_LOCATION) return
permissions.zip(grantResults.asList())
.find { (perm, _) -> perm == ACCESS_FINE_LOCATION }
?.also { (_, result) ->
if (result == PermissionChecker.PERMISSION_GRANTED) {
info { "User has granted us the fine location permission" }
} else {
warn { "User hasn't granted us the fine location permission (grant result: ${grantResults[0]})" }
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode != CODE_RESOLVE_API_EXCEPTION) return
if (resultCode == Activity.RESULT_OK) {
info { "ResolvableApiException is now resolved" }
} else {
warn { "ResolvableApiException resolution failed with code $resultCode" }
activity!!.mainPrefs.applyUpdate { setShouldAskToEnableLocation(false) }
}
}
val locationRequestFg = LocationRequest().apply {
interval = 1000
fastestInterval = 10
priority = PRIORITY_BALANCED_POWER_ACCURACY
}
suspend fun Context.receiveLocationUpdatesFg(locationState: LocationState) {
fusedLocationProviderClient.apply {
tryFetchLastLocation()?.also {
info { "lastLocation: ${it.description}" }
locationState.location = it
}
LocationCallbackFg.locationState = locationState
requestLocationUpdates(locationRequestFg, LocationCallbackFg, null).await()
info(CC_PRIVATE) { "FG: started receiving location updates" }
}
}
object LocationCallbackFg : LocationCallback() {
var locationState: LocationState? = null
override fun onLocationResult(result: LocationResult) {
val lastLocation = result.lastLocation
info { "FG: received location ${lastLocation.description}" }
locationState?.apply { location = lastLocation }
?: warn { "LocationCallbackFg received an event while not in use" }
}
}
The code relies on Task.await(), this is in Kotlin's library kotlinx-coroutines-play-services.

Android kotlin google play billing onPurchasesUpdated overrides nothing or is never used

I'm following this docs to implement google pay:
https://developer.android.com/google/play/billing/billing_library_overview
After running this function and purchage a product:
fun buy() {
val skuList = listOf("vip_small")
if (billingClient.isReady) {
val params = SkuDetailsParams
.newBuilder()
.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP)
.build()
billingClient.querySkuDetailsAsync(params) { responseCode, skuDetailsList ->
if (responseCode == BillingClient.BillingResponse.OK) {
Log.d("lets", "querySkuDetailsAsync, responseCode: $responseCode")
val flowParams = BillingFlowParams.newBuilder()
.setSku("vip_small")
.setType(BillingClient.SkuType.INAPP) // SkuType.SUB for subscription
.build()
val responseCodee = billingClient.launchBillingFlow(this, flowParams)
Log.d("lets", "launchBillingFlow responseCode: $responseCodee")
} else {
Log.d("lets", "Can't querySkuDetailsAsync, responseCode: $responseCode")
}
}
} else {
Log.d("lets", "Billing Client not ready")
}
}
which works fine I want to know if the purchage has been made so I add this code from the cods:
override fun onPurchasesUpdated(#BillingResponse responseCode: Int, purchases: List<Purchase>?) {
if (responseCode == BillingResponse.OK && purchases != null) {
for (purchase in purchases) {
handlePurchase(purchase)
}
} else if (responseCode == BillingResponse.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
} else {
// Handle any other error codes.
}
}
But I get the error 'onPurchasesUpdated' overrides nothing
So I remove override and get this "error"
Function "onPurchasesUpdated" is never used
What the hell?? How to call this damn function after the purchage has been made?
I got the solution.
You need to make the activity/fragment use the PurchasesUpdatedListener like:
class VIP : AppCompatActivity(), PurchasesUpdatedListener {
}
Then override will work

Categories

Resources