2

I am trying to implement in app updates in my app, but there is a disconnect between the documentation/tutorials that I have looked at and the actual final implementation.

I followed various code tutorials from:

https://developer.android.com/guide/playcore/in-app-updates/kotlin-java#start-update https://www.section.io/engineering-education/android-application-in-app-update-using-android-studio/ https://medium.com/android-news/implement-in-app-update-in-android-68892bd11e35 https://www.raywenderlich.com/8034025-in-app-updates-getting-started

The code itself is fairly straightforward.

But what I found missing from all of these tutorials was how to actually call the in-app updates. These tutorials all seem to make a dedicated in-app update activity. How do I launch this in-app update activity from my main activity? Or alternatively, how do I incorporate the in app update code into my already existing main activity?

I assume I want some sort of async launch of an in app update listener or something along those lines, but I can't seem to wrap my head around the last step of integrating all the in-app update code/in-app update activity into my app.

Edit: here's a sample of what I've tried

public class MainActivity extends AppCompatActivity implements RegionViewAdapter.ItemClickListener {
    private InstallStateUpdatedListener installStateUpdatedListener;
    private static final int FLEXIBLE_APP_UPDATE_REQ_CODE = 123;
    // if you change this value, you must also change it in the app build.gradle
    private final String currentVersion = "2021.06.6";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        appUpdateManager = AppUpdateManagerFactory.create(this);

        installStateUpdatedListener = state -> {
            if (state.installStatus() == InstallStatus.DOWNLOADED) {
                popupSnackbarForCompleteUpdate();
            } else if (state.installStatus() == InstallStatus.INSTALLED) {
                removeInstallStateUpdateListener();
            } else {
                Toast.makeText(getApplicationContext(), "InstallStateUpdatedListener: state: " + state.installStatus(), Toast.LENGTH_LONG).show();
            }
        };

    ...
    }
...
    public void checkUpdate() {

        // Returns an intent object that you use to check for an update.
        Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

        // Checks that the platform will allow the specified type of update.
        appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
            if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
                    && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
                startUpdateFlow(appUpdateInfo);
            } else if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                popupSnackbarForCompleteUpdate();
            }
        });

    }

    // Displays the snackbar notification and call to action.
    private void popupSnackbarForCompleteUpdate() {
        Snackbar snackbar =
                Snackbar.make(
                        findViewById(R.id.my_drawer_layout),
                        "An update has just been downloaded.",
                        Snackbar.LENGTH_INDEFINITE);
        snackbar.setAction("RESTART", view -> appUpdateManager.completeUpdate());
        snackbar.setActionTextColor(
                getResources().getColor(R.color.snackbar_action_text_color));
        snackbar.show();
    }

    private void startUpdateFlow(AppUpdateInfo appUpdateInfo) {
        try {
            appUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.FLEXIBLE, this, FLEXIBLE_APP_UPDATE_REQ_CODE);
        } catch (IntentSender.SendIntentException e) {
            e.printStackTrace();
        }
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == FLEXIBLE_APP_UPDATE_REQ_CODE) {
            if (resultCode == RESULT_CANCELED) {
                Toast.makeText(getApplicationContext(), "Update canceled by user! Result Code: " + resultCode, Toast.LENGTH_LONG).show();
            } else if (resultCode == RESULT_OK) {
                Toast.makeText(getApplicationContext(),"Update success! Result Code: " + resultCode, Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(getApplicationContext(), "Update Failed! Result Code: " + resultCode, Toast.LENGTH_LONG).show();
                checkUpdate();
            }
        }
    }

    private void removeInstallStateUpdateListener() {
        if (appUpdateManager != null) {
            appUpdateManager.unregisterListener(installStateUpdatedListener);
        }
    }


    // Checks that the update is not stalled during 'onResume()'.
    // However, you should execute this check at all app entry points.
    @Override
    protected void onResume() {
        super.onResume();

        appUpdateManager
                .getAppUpdateInfo()
                .addOnSuccessListener(appUpdateInfo -> {
                    // If the update is downloaded but not installed,
                    // notify the user to complete the update.
                    if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                        popupSnackbarForCompleteUpdate();
                    }
                });
    }

}
factor2
  • 155
  • 9
  • Just execute the code whenever you want to do the update? Where is the problem? – dan1st Jul 30 '21 at 19:56
  • Why you assume `async launch`? Does that specified anywhere in documentation? – Zain Jul 30 '21 at 20:00
  • Yeah that's what all the tutorials tell me to do as well, but unfortunately it doesn't seem to help me. I guess it's something simple to everyone but me lol. It's hard to articulate exactly where my disconnect is – factor2 Jul 30 '21 at 20:01
  • No, the async launch isn't specified anywhere, I just have no idea how the code is actually initiated – factor2 Jul 30 '21 at 20:02
  • The async launch happens automatically using `appUpdateInfoTask.addOnSuccessListener` so, you don't have to bother with this. – Zain Jul 30 '21 at 21:48
  • This answer helped me figure things out: https://stackoverflow.com/questions/55939853/how-to-work-with-androids-in-app-update-api – factor2 Aug 05 '21 at 01:13

2 Answers2

7

THIS ANSWER IS A HACK!

I have had quite a bit of trouble with in app updates. the issue being that this librery 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 whatsapp 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.

quealegriamasalegre
  • 2,887
  • 1
  • 13
  • 35
  • Thanks for the answer! I ended up doing something very similar, but I abandoned in app updates altogether. It's also very hacky, but I just host a version code in my cloud bucket, then check that code against the code in the app on startup. If the code in the installed app is less than the one in the bucket, I do exactly as you do and redirect users to Google Play. Your method is a bit better though, since I have to manually update my bucket after each update – factor2 Oct 21 '21 at 13:04
  • @quealegriamasalegre I think this is a very good method. – M DEV May 13 '22 at 12:31
  • Agreed that for `AppUpdateType.FLEXIBLE` updates, you really don't get much benefit from using the UI provided by the in-app update library and that navigating to the Play Store gives you all of the "do everything in the background" benefits. I only use the UI provided by `startUpdateFlowForResult` if this is an `AppUpdateType.IMMEDIATE` update. The blocking UI isn't great but sometimes needed. – tir38 Nov 17 '22 at 18:39
  • I agree that Immedite updates are still well served by the in-app update library. For me the issue is tracking flexible updates across activities and screens in a sensible manner. Regarding immediate (code breaking) updates I still dont know if it is even possible to force an update to happen immediately after you push it on all users devices. In my experience google always tries to push them on a best effort basis. I am due to change my backend and I dont know if I can do it without some users loose functionality until they instal the update. – quealegriamasalegre Nov 18 '22 at 17:30
0

are you looking for this:

call to prompt for FlexibleUpdate, this will pop up the google prompt and the user can tap "Install" from there. It seems to handle the download/install all in one and i dont have to do the "reload" snackbar step the docs were talking about.

  appUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.FLEXIBLE, activity, APP_UPDATE_REQUEST_CODE)
Chad
  • 96
  • 4