Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

  1. A card popped up from the bottom showing update is available
  2. When I clicked on "Update Hotstar" button, one dialog popped up (seems like it is provided by Google Play)

enter image description here

  1. Downloading was started in the background while the app was running
  2. After completion of the download, one SnackBar popped up showing app ready to install
  3. App restarted after the installation

enter image description here

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.

like image 465
pratiked Avatar asked May 01 '19 17:05

pratiked


People also ask

How does app update work?

After this 90-day period, the latest available version of the app is automatically installed with the default update behavior (as outlined above). After the app is updated to the latest available version, a new 90-day postponement period will begin the next time the developer publishes a new version of the app.

How can I test in-app updates in Android?

Use internal app sharing to test in-app updates by performing the following steps: Make sure your test device has a version of your app installed that supports in-app updates and was installed using an internal app sharing URL. Follow the Play Console instructions to share your app internally.

How can I implement force update in Android?

You can use https://appupgrade.dev/ service to force update you mobile apps. You need to create new version for your app versions you want to update in the app upgrade service and select whether you want to force it or just want to let users know that new version is available. See the response has force update true.


2 Answers

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

like image 141
pratiked Avatar answered Oct 02 '22 06:10

pratiked


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

like image 44
Sai Avatar answered Oct 02 '22 07:10

Sai