javaandroidin-app-update

Where to put in-app update implementation in Android


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();
                    }
                });
    }

}

Solution

  • 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.