querySkuDetailsAsync callback never called - android

I think to have followed all the steps correctly from the documentation but I can't reach the callback of querySkuDetailsAsync, no errors reported. The app is working correctly with IAB, now I'm only migrating from old library to the new 'com.android.billingclient:billing:2.0.3' but many problems.
Another question, in the new library is also necessary the use of License Key from Play Console? I don't find documentation about using that in the new library.
I can correctly and without errors reach this line billingClient.querySkuDetailsAsync(params, (billingResult2, skuDetailsList) ->
Sku ids are correctly
private void setupIab()
{
billingClient = BillingClient.newBuilder(getApplicationContext()).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener()
{
#Override
public void onBillingSetupFinished(BillingResult billingResult)
{
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK)
{
List<String> skuList = new ArrayList<> ();
skuList.add("test_sku_1");
SkuDetailsParams params = SkuDetailsParams.newBuilder().setSkusList(skuList).setType(BillingClient.SkuType.INAPP).build();
billingClient.querySkuDetailsAsync(params, (billingResult2, skuDetailsList) ->
{
// Process the result.
if (billingResult2.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null)
{
}
});
}
}
#Override
public void onBillingServiceDisconnected()
{
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
}
Best regards

The code seems correct form me and is similar to what I use, but I do not call querySkuDetailsAsync within onBillingSetupFinished, I call it only when the user buy something.
Maybe when onBillingSetupFinished runs, the setup is not really finished yet, you could try using this, so it will be called just later:
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
new Handler().post(() -> {
List<String> skuList = new ArrayList<>();
skuList.add("test_sku_1");
SkuDetailsParams params = SkuDetailsParams.newBuilder().setSkusList(skuList).setType(BillingClient.SkuType.INAPP).build();
billingClient.querySkuDetailsAsync(params, (billingResult2, skuDetailsList) ->
{
// Process the result.
if (billingResult2.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
}
});
});
}

Related

Android subscription/purchase is not found in code despite being defined in Google Play Console

I am trying to add a subscription to my app. I have created a subscription called "pro_subscription" in the Google Play Console and made sure it's enabled. I then created a new Alpha build and released it to myself, with an account I have configured for testing.
We added com.android.vending.BILLING to the permissions and already had the internet permission.
No matter what we've tried, when we try to fetch the subscription in order to purchase it, no results are found.
We have tried to fetch using Android APIs directly, in which mutableList?.isEmpty() is true:
fun querySkuDetails() {
    val purchasesUpdatedListener =
        PurchasesUpdatedListener { billingResult, purchases ->
            // To be implemented in a later section.
        }
    var billingClient = BillingClient.newBuilder(activity as MainActivity)
        .setListener(purchasesUpdatedListener)
        .enablePendingPurchases()
        .build()
    billingClient.startConnection(object : BillingClientStateListener {
        override fun onBillingSetupFinished(billingResult: BillingResult) {
            if (billingResult.responseCode ==  BillingClient.BillingResponseCode.OK) {
                // The BillingClient is ready. You can query purchases here.
                val skuList = ArrayList<String>()
                skuList.add("subscription")
                skuList.add("pro_subscription")
                val params = SkuDetailsParams.newBuilder()
                params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
                billingClient.querySkuDetailsAsync(params.build(), SkuDetailsResponseListener() { billingResult: BillingResult, mutableList: MutableList<SkuDetails>? ->
                    if (mutableList?.isEmpty()!!) {
                        Log.d("OFFERS: ", "mutableList is empty")
                        return#SkuDetailsResponseListener
                    }
                })
            }
        }
        override fun onBillingServiceDisconnected() {
            // Try to restart the connection on the next request to
            // Google Play by calling the startConnection() method.
        }
    })
And we also tried using the third-party revenuecat by configuring an offering and then fetching it, in which offerings.all.isEmpty() is true:
Purchases.sharedInstance.getOfferingsWith(
    onError = { error ->
        /* Optional error handling */
        throw IllegalArgumentException()
    },
    onSuccess = { offerings ->
        if (offerings.current == null) {
            Log.d("OFFERS: ", "offerings.current is null")
        }
        if (offerings.all.isEmpty()) {
            Log.d("OFFERS: ", "offerings.all.size is 0")
        }
        // Display current offering with offerings.current
        Purchases.sharedInstance.purchasePackageWith(
            activity as MainActivity,
            offerings.current?.getPackage("pro")!!,
            onError = { error, userCancelled -> /* No purchase */
                if (userCancelled) {
                    val requestCancelledToast = Toast.makeText(
                        context,
                        R.string.purchase_request_cancelled_toast,
                        Toast.LENGTH_LONG
                    )
                    requestCancelledToast.show()
                } else {
                    throw IllegalArgumentException()
                }
            },
            onSuccess = { product, purchaserInfo ->
                if (purchaserInfo.entitlements["pro"]?.isActive == true) {
                    // Unlock that great "pro" content
                    try {
                        val adView =
                            container!!.findViewById<AdView>(R.id.adView)
                        (adView.parent as ViewGroup).removeView(
                            adView
                        )
                    } catch (e: Exception) {
                        val errorRemoveToast = Toast.makeText(
                            context,
                            R.string.error_remove_ad,
                            Toast.LENGTH_LONG
                        )
                        errorRemoveToast.show()
                    }
                }
            })
    })
In both cases, no error is printed, caught, or otherwise identified.
Anyone know why we are not able to fetch the purchases based on the above steps? Thanks!
Did you add Library of billing on the app's gradle?
It is;
dependencies {
val billing_version = "4.0.0"
implementation("com.android.billingclient:billing-ktx:$billing_version")
}

How to correctly use querySkuDetailsAsync?

I am trying to use the billing library and I am getting info from the official Android Developer site here. But I am finding a lot of trouble. Mainly compiling problems. It looks like that documentation is not completed. When I started following step by step I had to search for a lot of extra information. Now I am stuck trying to do querySkuDetailsAsync()
This is my code:
billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
List<String> skuList = new ArrayList<> ();
skuList.add("sp_hide_ads_year_01");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
//*** I want to Continue here ***
}
});
}
}
#Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
The compiler (Android Studio) says:
'onSkuDetailsResponse(BillingResult, List)' in 'Anonymous
class derived from
com.android.billingclient.api.SkuDetailsResponseListener' clashes with
'onSkuDetailsResponse(BillingResult, List)' in
'com.android.billingclient.api.SkuDetailsResponseListener'; both
methods have erasure, yet neither overrides the other
I have no idea what this means. any help here?
By the way, I use
implementation 'com.android.billingclient:billing:2.1.0'
I changed the line:
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList)
for this one, and everything compiled:
public void onSkuDetailsResponse(BillingResult billingResult, List<com.android.billingclient.api.SkuDetails> skuDetailsList)

Why does implementation of onPurchasesUpdated() result in ITEM_ALREADY_OWNED and an empty purchases list?

I'm currently implementing in-app purchases for virtual coins that are consumable.
I'm using an internal test track and my test account is connected.
Launch billing flow is initiated by a button.
However there are a few issues.
The purchase list is null, which I don't believe is normal.
#Override
public void onPurchasesUpdated(BillingResult billingResult, #Nullable List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
} else {
}
The if statement will not run.
If I try to force test handlePurchases()'s functionality by passing this snippet to the else statement in onPurchasesUpdated(), it will result in a null object reference error because its trying to iterate through an empty list.
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
BillingResult code returns: ITEM_ALREADY_OWNED
To fix this I tried to call consumeAsync(), as shown in the code below.
public void handlePurchase(Purchase purchase) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
// Grant entitlement to the user.
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
client.consumeAsync(consumeParams, consumeResponseListener);
}
}
Additionally, I refunded all of the the existing orders in the Play Consoles' order management.
My question is why the ITEM_ALREADY_OWNED error still remains, and why the purchases list is empty.

android billing how to enable enablePendingPurchases()

I've moved from an old gradle of billing api, to the most recent to date, and now I've tried adding
BillingClient.Builder enablePendingPurchases = BillingClient.newBuilder(this).setListener(this);
but I can not get it to work, here's the error
Caused by: java.lang.IllegalArgumentException: Support for pending purchases must be enabled. Enable this by calling 'enablePendingPurchases()' on BillingClientBuilder.
at com.android.billingclient.api.BillingClient$Builder.build(BillingClient.java:309)
at com.aplicacion.vivaluganoapp.ar.ponerDineroActivity.setupBillingClient(ponerDineroActivity.java:144)
at com.aplicacion.vivaluganoapp.ar.ponerDineroActivity.onCreate(ponerDineroActivity.java:125)
complete code:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_poner_dinero);
recyclerProduct.setHasFixedSize(true);
recyclerProduct.setLayoutManager(new LinearLayoutManager(this));
BillingClient.Builder enablePendingPurchases = BillingClient.newBuilder(this).setListener(this);
enablePendingPurchases.build();
setupBillingClient();
}
private void setupBillingClient() {
billingClient = BillingClient.newBuilder (this).setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(BillingResult responseCode) {
int maca = BillingClient.BillingResponseCode.OK;
String maca2 = String.valueOf(maca);
String maca3 = String.valueOf(responseCode);
if (maca3 == maca2)
{
Toast.makeText(ponerDineroActivity.this, "WORKS", Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(ponerDineroActivity.this, "ERROR", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onBillingServiceDisconnected() {
Toast.makeText(ponerDineroActivity.this, "Disconnected from Billing", Toast.LENGTH_SHORT).show();
}
});
}
if I place only:
BillingClient.Builder enablePendingPurchases = BillingClient.newBuilder(this);
the error is:
Caused by: java.lang.IllegalArgumentException: Please provide a valid listener for purchases updates.
any help? i'm tired of trying
From the first stacktrace in your question
Enable this by calling 'enablePendingPurchases()'
we can find documentation for method enablePendingPurchases()
This method is required to be called to acknowledge your application
has been updated to support purchases that are pending. Pending
purchases are not automatically enabled since your application will
require updates to ensure entitlement is not granted before payment
has been secured. For more information on how to handle pending
transactions see
https://developer.android.com/google/play/billing/billing_library_overview
If this method is not called, BillingClient instance creation fails.
Your line of code should be:-
enablePendingPurchases = BillingClient.newBuilder(this)
.enablePendingPurchases()
.setListener(this);
Instead of :-
enablePendingPurchases = BillingClient.newBuilder(this).setListener(this);
This worked for me.
Just add enablePendingPurchases() like below:
billingClient = BillingClient.newBuilder(this)
.setListener(this)
.enablePendingPurchases()
.build();
BillingClient billingClient =
BillingClient.newBuilder(context!!)
.enablePendingPurchases()
.setListener(this)
build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode==BillingClient.BillingResponseCode.OK) {
skuList = HashMap()
skuList.put(BillingClient.SkuType.SUBS, listOf(getString(R.string.subscription_monthly),getString(R.string.subscription_yearly)))
querySkuDetailsAsync(BillingClient.SkuType.SUBS,skuList.get(BillingClient.SkuType.SUBS),object :SkuDetailsResponseListener{
override fun onSkuDetailsResponse(billingResult: BillingResult?, skuDetailsList: MutableList<SkuDetails>?) {
DebugLog.e("DATAAA "+skuDetailsList?.size+"")
}
})
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})

How to work with Android's in-app update API?

I recently came across a new kind of app update flow which has provided by Google Play API. I liked this seamless flow to update an Android application. I observed the below-mentioned steps in the Hotstar app.
A card popped up from the bottom showing update is available
When I clicked on "Update Hotstar" button, one dialog popped up (seems like it is provided by Google Play)
Downloading was started in the background while the app was running
After completion of the download, one SnackBar popped up showing app ready to install
App restarted after the installation
How can I achieve this? There must be a way to communicate with Google Play. I went through many blogs. But, didn't find any solution. This could be an awesome feature for a developer if the auto app update is disabled by the user.
Step 1: Add dependency (build.gradle (app)):
dependencies {
implementation 'com.google.android.play:core:1.7.3'
...
}
Step 2: Check for update availability and start if it's available
private AppUpdateManager mAppUpdateManager;
private static final int RC_APP_UPDATE = 11;
In onStart() method:
mAppUpdateManager = AppUpdateManagerFactory.create(this);
mAppUpdateManager.registerListener(installStateUpdatedListener);
mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE /*AppUpdateType.IMMEDIATE*/)){
try {
mAppUpdateManager.startUpdateFlowForResult(
appUpdateInfo, AppUpdateType.FLEXIBLE /*AppUpdateType.IMMEDIATE*/, MainActivity.this, RC_APP_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
} else if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED){
//CHECK THIS if AppUpdateType.FLEXIBLE, otherwise you can skip
popupSnackbarForCompleteUpdate();
} else {
Log.e(TAG, "checkForAppUpdateAvailability: something else");
}
});
Step 3: Listen to update state
InstallStateUpdatedListener installStateUpdatedListener = new
InstallStateUpdatedListener() {
#Override
public void onStateUpdate(InstallState state) {
if (state.installStatus() == InstallStatus.DOWNLOADED){
//CHECK THIS if AppUpdateType.FLEXIBLE, otherwise you can skip
popupSnackbarForCompleteUpdate();
} else if (state.installStatus() == InstallStatus.INSTALLED){
if (mAppUpdateManager != null){
mAppUpdateManager.unregisterListener(installStateUpdatedListener);
}
} else {
Log.i(TAG, "InstallStateUpdatedListener: state: " + state.installStatus());
}
}
};
Step 4: Get a callback for update status
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_APP_UPDATE) {
if (resultCode != RESULT_OK) {
Log.e(TAG, "onActivityResult: app download failed");
}
}
}
Step 5: Flexible update
private void popupSnackbarForCompleteUpdate() {
Snackbar snackbar =
Snackbar.make(
findViewById(R.id.coordinatorLayout_main),
"New app is ready!",
Snackbar.LENGTH_INDEFINITE);
snackbar.setAction("Install", view -> {
if (mAppUpdateManager != null){
mAppUpdateManager.completeUpdate();
}
});
snackbar.setActionTextColor(getResources().getColor(R.color.install_color));
snackbar.show();
}
Step 6: Don't forget to unregister listener (in onStop method)
if (mAppUpdateManager != null) {
mAppUpdateManager.unregisterListener(installStateUpdatedListener);
}
Note: Add this listener in any one activity in your app preferably in MainActivity (Home page)
For testing, you can use FakeAppUpdateManager
https://developer.android.com/reference/com/google/android/play/core/appupdate/testing/FakeAppUpdateManager.html
Constraint: In-app update works only with devices running Android 5.0 (API level 21) or higher
Official Documentation: https://developer.android.com/guide/playcore/in-app-updates
Android officially announced the in-app updates to everyone today.
https://developer.android.com/guide/playcore/in-app-updates
Update:
Handling both IMMEDIATE and FLEXIBLE updates in a single activity; Kotlin way.
import android.app.Activity
import android.content.Intent
import android.content.IntentSender
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.material.snackbar.Snackbar
import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.install.InstallState
import com.google.android.play.core.install.InstallStateUpdatedListener
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.InstallStatus
import com.google.android.play.core.install.model.UpdateAvailability
import timber.log.Timber
class BaseUpdateCheckActivity : AppCompatActivity() {
private val appUpdateManager: AppUpdateManager by lazy { AppUpdateManagerFactory.create(this) }
private val appUpdatedListener: InstallStateUpdatedListener by lazy {
object : InstallStateUpdatedListener {
override fun onStateUpdate(installState: InstallState) {
when {
installState.installStatus() == InstallStatus.DOWNLOADED -> popupSnackbarForCompleteUpdate()
installState.installStatus() == InstallStatus.INSTALLED -> appUpdateManager.unregisterListener(this)
else -> Timber.d("InstallStateUpdatedListener: state: %s", installState.installStatus())
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_ad_view)
checkForAppUpdate()
}
private fun checkForAppUpdate() {
// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
// Request the update.
try {
val installType = when {
appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE) -> AppUpdateType.FLEXIBLE
appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE) -> AppUpdateType.IMMEDIATE
else -> null
}
if (installType == AppUpdateType.FLEXIBLE) appUpdateManager.registerListener(appUpdatedListener)
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
installType!!,
this,
APP_UPDATE_REQUEST_CODE)
} catch (e: IntentSender.SendIntentException) {
e.printStackTrace()
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == APP_UPDATE_REQUEST_CODE) {
if (resultCode != Activity.RESULT_OK) {
Toast.makeText(this,
"App Update failed, please try again on the next app launch.",
Toast.LENGTH_SHORT)
.show()
}
}
}
private fun popupSnackbarForCompleteUpdate() {
val snackbar = Snackbar.make(
findViewById(R.id.drawer_layout),
"An update has just been downloaded.",
Snackbar.LENGTH_INDEFINITE)
snackbar.setAction("RESTART") { appUpdateManager.completeUpdate() }
snackbar.setActionTextColor(ContextCompat.getColor(this, R.color.accent))
snackbar.show()
}
override fun onResume() {
super.onResume()
appUpdateManager
.appUpdateInfo
.addOnSuccessListener { appUpdateInfo ->
// If the update is downloaded but not installed,
// notify the user to complete the update.
if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
popupSnackbarForCompleteUpdate()
}
//Check if Immediate update is required
try {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
this,
APP_UPDATE_REQUEST_CODE)
}
} catch (e: IntentSender.SendIntentException) {
e.printStackTrace()
}
}
}
companion object {
private const val APP_UPDATE_REQUEST_CODE = 1991
}
}
Source Gist: https://gist.github.com/saikiran91/6788ad4d00edca30dad3f51aa47a4c5c
Trying to implement this, the official Google Documentation quoted in the accepted answer is syntactically incorrect. It took some research, but I finally found the correct syntax:
Instead of:
// Creates an instance of the manager.
AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);
// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfo = appUpdateManager.getAppUpdateInfo();
// Checks that the platform will allow the specified type of update.
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
// For a flexible update, use AppUpdateType.FLEXIBLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
// Request the update.
appUpdateManager.startUpdateFlowForResult(
// Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo,
// Or 'AppUpdateType.FLEXIBLE' for flexible updates.
AppUpdateType.IMMEDIATE,
// The current activity making the update request.
this,
// Include a request code to later monitor this update request.
MY_REQUEST_CODE);
}
Do this:
private AppUpdateManager appUpdateManager;
...
// onCreate(){
// Creates instance of the manager.
appUpdateManager = AppUpdateManagerFactory.create(mainContext);
// Don't need to do this here anymore
// Returns an intent object that you use to check for an update.
//Task<AppUpdateInfo> appUpdateInfo = appUpdateManager.getAppUpdateInfo();
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
// Checks that the platform will allow the specified type of update.
if ((appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE)
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE))
{
// Request the update.
try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
this,
REQUEST_APP_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
Then, code a similar bit of code in the onResume() override in case an install got hung up along the way:
//Checks that the update is not stalled during 'onResume()'.
//However, you should execute this check at all entry points into the app.
#Override
protected void onResume() {
super.onResume();
appUpdateManager
.getAppUpdateInfo()
.addOnSuccessListener(
appUpdateInfo -> {
if (appUpdateInfo.updateAvailability()
== UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
try {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.IMMEDIATE,
this,
REQUEST_APP_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
}
Please try this once. Official Document for reference
Step 1: In the build.gradle file add the below library (please check and update latest play code plugin version)
implementation 'com.google.android.play:core:1.6.4'
Step 2: Declare the following variables in class (Ex MainActivity.java)
private AppUpdateManager mAppUpdateManager;
private int RC_APP_UPDATE = 999;
private int inAppUpdateType;
private com.google.android.play.core.tasks.Task<AppUpdateInfo> appUpdateInfoTask;
private InstallStateUpdatedListener installStateUpdatedListener;
Step 3: In onCreate() method add the below code (initializing variables)
// Creates instance of the manager.
mAppUpdateManager = AppUpdateManagerFactory.create(this);
// Returns an intent object that you use to check for an update.
appUpdateInfoTask = mAppUpdateManager.getAppUpdateInfo();
//lambda operation used for below listener
//For flexible update
installStateUpdatedListener = installState -> {
if (installState.installStatus() == InstallStatus.DOWNLOADED) {
popupSnackbarForCompleteUpdate();
}
};
mAppUpdateManager.registerListener(installStateUpdatedListener);
Step 4: In onDestroy() method of activity just unregister the listener
#Override
protected void onDestroy() {
mAppUpdateManager.unregisterListener(installStateUpdatedListener);
super.onDestroy();
}
Step 5: In onResume() we need to listen to both Flexible and Immediate updates by the below code.
#Override
protected void onResume() {
try {
mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {
if (appUpdateInfo.updateAvailability() ==
UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
// If an in-app update is already running, resume the update.
try {
mAppUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
inAppUpdateType,
this,
RC_APP_UPDATE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
});
mAppUpdateManager.getAppUpdateInfo().addOnSuccessListener(appUpdateInfo -> {
//For flexible update
if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
popupSnackbarForCompleteUpdate();
}
});
} catch (Exception e) {
e.printStackTrace();
}
super.onResume();
}
Step 6: In onActivityResult() we need to handle user click actions(only for flexible update)
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_APP_UPDATE) {
//when user clicks update button
if (resultCode == RESULT_OK) {
Toast.makeText(MainActivity.this, "App download starts...", Toast.LENGTH_LONG).show();
} else if (resultCode != RESULT_CANCELED) {
//if you want to request the update again just call checkUpdate()
Toast.makeText(MainActivity.this, "App download canceled.", Toast.LENGTH_LONG).show();
} else if (resultCode == RESULT_IN_APP_UPDATE_FAILED) {
Toast.makeText(MainActivity.this, "App download failed.", Toast.LENGTH_LONG).show();
}
}
}
Step 7: Create a method to check update available or not and start the update (Immediate update)
private void inAppUpdate() {
try {
// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(new OnSuccessListener<AppUpdateInfo>() {
#Override
public void onSuccess(AppUpdateInfo appUpdateInfo) {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
// For a flexible update, use AppUpdateType.FLEXIBLE
&& appUpdateInfo.isUpdateTypeAllowed(inAppUpdateType)) {
// Request the update.
try {
mAppUpdateManager.startUpdateFlowForResult(
// Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo,
// Or 'AppUpdateType.FLEXIBLE' for flexible updates.
inAppUpdateType,
// The current activity making the update request.
MainActivity.this,
// Include a request code to later monitor this update request.
RC_APP_UPDATE);
} catch (IntentSender.SendIntentException ignored) {
}
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
Step 8: Finely create a snack dialog or any alert to show the user that flexible update is downloaded and ready to update(need used action to start update - only for flexible update)
private void popupSnackbarForCompleteUpdate() {
try {
Snackbar snackbar =
Snackbar.make(
findViewById(R.id.id_of_root_loyout),
"An update has just been downloaded.\nRestart to update",
Snackbar.LENGTH_INDEFINITE);
snackbar.setAction("INSTALL", view -> {
if (mAppUpdateManager != null){
mAppUpdateManager.completeUpdate();
}
});
snackbar.setActionTextColor(getResources().getColor(R.color.install_color));
snackbar.show();
} catch (Resources.NotFoundException e) {
e.printStackTrace();
}
}
Step 9: Now call the method with the in-app update type(Flexible or Immediate) wherever you want to start to check updates.
//For Immediate
inAppUpdateType = AppUpdateType.IMMEDIATE; //1
inAppUpdate();
//For Flexible
inAppUpdateType = AppUpdateType.FLEXIBLE; //0
inAppUpdate();
Points to Remember:
The flexible update will download first then it will notify the user that download completed then the user has to start the update(options given above step 8).
There is an option in google play console to test in-app sharing, just we can upload normal apk(no need signed apk)to test.
https://support.google.com/googleplay/android-developer/answer/9303479?hl=en
Need to enable in-app sharing option in your test device play store app.
How to Enable Internal App Sharing for Android?
Still, any issue in the play store, just clear cache and clear data then restart the device once and try.
My guess is that it is controlled by the app itself, rather than Google Play. I've developed apps that make an API call on startup to read the 'latest' version number and whether that version is a 'mandatory' update or not, and compares it to the running app version. If a new version is available, the user is presented with a dialog like the one you displayed (though their's is much nicer) alerting the user that an update is available. If the update is 'mandatory', then the message tells them that they must update the app before continuing. If they click Update, then they are taken to the App Store page where they initiate the download of the update as usual and the app exits. If they click Close, the app just exits. If the update is not mandatory, they are asked if they would like to update now, or continue. If they click Update, then they are taken to the App Store page where they initiate the download of the update as usual and the app exits. If they click Continue, then they are just taken into the existing version of the app.
I'm not sure how they managed the background download then kicked off the app update before exiting the app. That would be very nice, but our method above was also very easy and gives a lot of capability to the developer.
Try these libraries, where you can implement in few lines of code.
https://github.com/SanojPunchihewa/InAppUpdater
https://github.com/dnKaratzas/android-inapp-update
Google is testing an early version of an In-apps update API as described at this blog post.
It's only available for some early testing partners right now, but it should be available for all developers eventually. Keep your eye out on the Android Developers Blog and for announcements in the Play console.

Categories

Resources