8

I am trying to implement android in-app updates. I am following the tutorial given in the official portal but the dialog is not shown when startUpdateflow method is called. Please find below is the chunk of code I am executing.

final AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(this);
    Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();
    appUpdateInfoTask.addOnSuccessListener(new OnSuccessListener<AppUpdateInfo>() {
        @Override
        public void onSuccess(AppUpdateInfo appUpdateInfo) {
            try {
                Toast.makeText(getApplicationContext(),"Success.", Toast.LENGTH_SHORT).show();
                appUpdateManager.startUpdateFlowForResult(
                        appUpdateInfo,
                        AppUpdateType.FLEXIBLE,
                        SplashScreenActivity.this,
                        APP_UPDATE_REQUEST_CODE
                );
            } catch (IntentSender.SendIntentException e) {
                Toast.makeText(getApplicationContext(),"Exception received", Toast.LENGTH_SHORT).show();
                e.printStackTrace();
            }
        }
    });

I am able to see Success toast and the line

appUpdateManager.startUpdateFlowForResult() is called

but this is not showing update dialog with No Thanks and Update buttons as shown in the portal. I tried using FakeAppUpdateManager too but that also didn't help.

Requesting help here!

Varun A M
  • 1,103
  • 2
  • 14
  • 29

4 Answers4

8

Here is what I noticed; in other words, your mileage may vary.

In-app Update works as advertised, but in order for it to work the way you expect it to, there are a few things that need to be in place.

  1. You need to install your test app from Google Play; it won't work if you just install it from Android Studio.

  2. Installing it from Google Play can be done via "Open testing", "Closed testing", or "Internal testing", but (I think) those tracks allow only release builds (i.e. can't set breakpoints), so I suggest you start with "Internal app sharing". Just upload your .apk or .aab to https://play.google.com/console/internal-app-sharing. The only thing that doesn't seem to work is setting the update priority in "Internal app sharing". I'd like to hear how others handle it.

  3. FakeAppUpdateManager does NOT show the update dialog. You just mock what Google Play would do by issuing commands, such as fakeAppUpdateManager.downloadStarts(), but there won't be any dialogs. Refer to this.

  4. An update can be both FLEXIBLE and IMMEDIATE at the same time. In other words, if you check appUpdateInfo.isUpdateTypeAllowed(FLEXIBLE) and appUpdateInfo.isUpdateTypeAllowed(IMMEDIATE), they can be both true. It's up to you to decide what to do at that point, depending on what the update's priority is.

These are the steps I took.

  1. Set versionCode to 2, build either .apk or .aab (in debug version), and upload it to "Internal app sharing". Make note of its download URL.

  2. Set versionCode to 1, and do the same as step 1.

  3. Open the URL from step 2 (i.e. version 1) from the phone. It will open Google Play and ask you to download the app. Download, and open it.

  4. Open the URL from step 1 (i.e. version 2) from the phone, but DON'T tap on the "Update" button; just bring the app version 1 to the foreground.

  5. While the app is running, attach debugger (Android Studio -> Run -> Attach Debugger to Android Process).

  6. Check the update (appUpdateManager.appUpdateInfo.addOnSuccessListener {...}), start the update (appUpdateManager.startUpdateFlowForResult(...)), etc.

solamour
  • 2,764
  • 22
  • 22
  • Hey, thank you for your answer, this was really helpful to test it. I have one question, Is the app relaunched after an IMMEDIATE update? It's not working for me and I'd like to know if is something on my side – IAmJulianAcosta May 01 '21 at 02:32
  • I wasn't sure how it went, so I just tried again. When I call appUpdateManager.startUpdateFlowForResult() with "IMMEDIATE", the app automatically restarts after updating. – solamour May 01 '21 at 04:23
  • thanks, but at least Android should have let the dialog appear for testing even if installed from AS. – M. Usman Khan Jun 22 '23 at 18:06
  • 1
    @M.UsmanKhan, I completely agree with you; it would make testing a whole lot easier. Ideally, there should be a special "test" flag, so that when it's on, installing from Android Studio would yield the same flow. – solamour Jun 22 '23 at 19:06
4

I was facing the same problem. I found this in the doc for the method startUpdateFlowForResult in the com.google.android.play.core.appupdate.AppUpdateManager class

Each {@link com.google.android.play.core.appupdate.AppUpdateInfo AppUpdateInfo} instance can be used only in a single call to this method. If you need to call it multiple times - for instance, when retrying to start a flow in case of failure you need to get a fresh {@link com.google.android.play.core.appupdate.AppUpdateInfo AppUpdateInfo} from {@link #getAppUpdateInfo()}.

So, getting new instance after canceled or failed update did the trick

S.Slavova
  • 41
  • 2
1

Official Documentation: https://developer.android.com/guide/app-bundle/in-app-updates

Constraint: In-app update works only with devices running Android 5.0 (API level 21) or higher

Step 1: Add dependency:

dependencies {

    implementation 'com.google.android.play:core:1.5.0'
    ...
}

Step 2: Check for update availability and start if it's available

Create an instance of the AppUpdateManagerFactory

 appUpdateManager = AppUpdateManagerFactory.create(context);

Register the listener for the response of the FLEXIBLE update Request

        appUpdateManager.registerListener(listener);

After that gets the appUpdateInfo

Task<AppUpdateInfo> appUpdateInfo = appUpdateManager.getAppUpdateInfo();

            appUpdateInfo.addOnSuccessListener(new OnSuccessListener<AppUpdateInfo>() {
                @Override
                public void onSuccess(AppUpdateInfo appUpdateInfo) {
                    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
                        if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
                            Log.d("App update A", "Flexible");
                            int updateType = FLEXIBLE;
                            requestUpdate(appUpdateInfo, updateType);
                        } else if (appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
                            Log.d("App update B", "IMMEDIATE");
                            int updateType = IMMEDIATE;
                            requestUpdate(appUpdateInfo, updateType);
                        }
                    }
                }
            });

After that requestUpdate() method,

private void requestUpdate(AppUpdateInfo appUpdateInfo, int updateType) {
        try {
            appUpdateManager.startUpdateFlowForResult(appUpdateInfo, updateType, HomeActivity.this, MY_REQUEST_CODE);
        } catch (IntentSender.SendIntentException e) {
            e.printStackTrace();
        }
    }

Step 3: Listen to update state

Lastly the listener

InstallStateUpdatedListener listener = new InstallStateUpdatedListener() {
        @Override
        public void onStateUpdate(InstallState state) {
            Log.d("installState", state.toString());
            if (state.installStatus() == InstallStatus.DOWNLOADED) {
                // After the update is downloaded, show a notification
                // and request user confirmation to restart the app.
                // SnackBarManager.getSnackBarManagerInstance().showSnackBar(GaanaActivity.this, "An update has just been downloaded.", true);
                popupSnackbarForCompleteUpdate();
            }
        }
    };

popupSnackbarForCompleteUpdate() method,

private void popupSnackbarForCompleteUpdate() {
        Snackbar snackbar =
                Snackbar.make(findViewById(android.R.id.content), "An update has just been downloaded.", Snackbar.LENGTH_INDEFINITE);
        snackbar.setAction("Restart", view -> appUpdateManager.completeUpdate());
        snackbar.setActionTextColor(getResources().getColor(android.R.color.white));
        snackbar.show();
    }

Step 4: Get a callback for update status

You can capture the Result as well,

@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == MY_REQUEST_CODE) {
            System.out.println("App Update = " + resultCode);
            if (resultCode != RESULT_OK) {
                System.out.println("Update flow failed! Result code: " + resultCode);
                // If the update is cancelled or fails,
                // you can request to start the update again.
            }
        }
    }

You have to unregister the listener as well

@Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("installState", "destroy");
        appUpdateManager.unregisterListener(listener);
    }
mini developer
  • 302
  • 3
  • 7
1

THIS ANSWER IS A HACK!

I have had quite a bit of trouble with in app updates. the issue being that this library was made for users that stay in the same activity where startUpdateFlowForResult is called from and then whenever the user does something unexpected like checking some other app or going to the next activity everything breaks down and the update doesnt get completed. and you can try to fix it with endless boilerplate but since calling a snackbar that is activity agnostic is super complex that will cost you at least some gray hairs and leave you with delicate code that probably still is somewhat buggy.

THE ALTERNATIVE

I finally gave upand decided to redirect my users directly to GooglePlayStore:

if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE ) { //maybe you could add some additional checks like priority level here
                makeUpdateDialog(mContext).show();
}

I use the In-app Updates library exclusively to check for availability and then I show a dialog with the following code in the action button:

try {
                    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(ownGooglePlayLink)));
} catch (android.content.ActivityNotFoundException anfe) {
                    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(ownWebLink)));
}

public static final String ownGooglePlayLink="market://details?id=com.my.package.name";
public static final String ownWebLink="https://play.google.com/store/apps/details?id=com.my.package.name";

This is admitedly very hacky but its not as bad as it seems. I find that the user experience remmains good and the user can either skip your update or return to the app after initializing the update. that way the update is handled directly by google and you dont have to take care of it.

It would be nice If google provided a second dialog to let the user decide if he wants to install the app instead of the tedious snackbar approach

quealegriamasalegre
  • 2,887
  • 1
  • 13
  • 35