How to implement In-App Updates in Android using Kotlin| In-app updates

When your users keep your app up to date on their devices, they can try new features and benefit from performance improvements and bug fixes.

In this article, we will learn how to implement In-App Updates in Android using Kotlin.

In-app updates is a Google Play Core libraries feature that prompts active users to update your app.

Update flows 

Your app can use the Google Play Core libraries to support the following UX flows for in-app updates:

Notes:

  • The in-app updates feature is supported on devices running Android 5.0 (API level 21) or higher. 
  • In-app updates are only supported for Android mobile devices, Android tablets, and Chrome OS devices.
  • In-app updates are not compatible with apps that use APK expansion files (.obb files).
There’re two ways to show an update inside your app:

Flexible
  • A popup appears, asking the user if they want to update the app. 
  • They can accept or deny it. If they accept it, the update will download in the background. 
  • This can be used when your update has some minor UI changes or performance improvements.


Immediate
  • This is a full-screen UX that requires the user to update the app to continue using it. 
  • This can be used when you have a critical update, like a security fix.
Adding library
Set up your development environment
// In your app’s build.gradle.kts file:
...
dependencies {
    // This dependency is downloaded from the Google’s Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation("com.google.android.play:app-update:2.0.1")

    // For Kotlin users also import the Kotlin extensions library for Play In-App Update:
    implementation("com.google.android.play:app-update-ktx:2.0.0")
    ...
}
..
Check for update availability
Before requesting an update, check if there is an update available for your app. Use AppUpdateManager to check for an update:
val appUpdateManager = AppUpdateManagerFactory.create(context)

// 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
        // This example applies an immediate update. To apply a flexible update
        // instead, pass in AppUpdateType.FLEXIBLE
        && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
    ) {
        // Request the update.
    }
}
..
The returned AppUpdateInfo instance contains the update availability status. 

Depending on the status of the update, the instance also contains the following:

If an update is available and the update is allowed, the instance also contains an intent to start the update.
If an in-app update is already in progress, the instance also reports the status of the in-progress update.


Check update staleness
Use clientVersionStalenessDays() to check the number of days since the update became available on the Play Store:
val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks whether the platform allows the specified type of update,
// and current version staleness.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && (appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
    }
}
..
Implementing in-app update
To make it easier to implement, we’re going to add all the things we need in a different file, and then we’re going to call it from the Activity we want to check for updates.

Create a new file, name it InAppUpdate, and paste the following code inside:

package com.boltuix.materialuiux

import android.graphics.Color
import android.os.Bundle
import android.util.Log
import com.google.android.material.snackbar.Snackbar
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import android.view.Menu
import android.view.MenuItem
import androidx.lifecycle.MutableLiveData
import com.boltuix.materialuiux.databinding.ActivityMainBinding
import com.google.android.play.core.appupdate.AppUpdateInfo
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

class MainActivity : AppCompatActivity() {


    //.......................................................................
    private lateinit var appUpdateManager: AppUpdateManager
    private val updateAvailable = MutableLiveData<Boolean>().apply { value = false }
    private var updateInfo: AppUpdateInfo? = null
    private var updateListener = InstallStateUpdatedListener { state: InstallState ->
        commonLog("update01:$state")
        if (state.installStatus() == InstallStatus.DOWNLOADED) {
            showUpdateSnackbar()
        }
    }
    private fun checkForUpdate() {
        appUpdateManager.appUpdateInfo.addOnSuccessListener {
            if (it.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE &&
                it.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
                updateInfo = it
                updateAvailable.value = true
                commonLog("update01:Version code available ${it.availableVersionCode()}")
                startForInAppUpdate(updateInfo)
            } else {
                updateAvailable.value = false
                commonLog("update01:Update not available")
            }
        }
    }
    private fun startForInAppUpdate(it: AppUpdateInfo?) {
        appUpdateManager.startUpdateFlowForResult(it!!, AppUpdateType.FLEXIBLE, this, 1101)
    }
    private fun showUpdateSnackbar() {
        try{
            val snackbar = Snackbar.make(binding.coordinator, "An update has just been downloaded.", Snackbar.LENGTH_INDEFINITE)
                .setAction("RESTART") { appUpdateManager.completeUpdate() }
            //snackbar.anchorView = binding.appBarMain.contentMain.bottomNav
            snackbar.setActionTextColor(Color.parseColor("#ffff4444"))
            snackbar.show()
        }catch (e:java.lang.Exception){
        }
    }
    private fun commonLog(message :String) {
        Log.d("tag001",message)
    }

    private lateinit var appBarConfiguration: AppBarConfiguration
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        WindowCompat.setDecorFitsSystemWindows(window, false)
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setSupportActionBar(binding.toolbar)

        val navController = findNavController(R.id.nav_host_fragment_content_main)
        appBarConfiguration = AppBarConfiguration(navController.graph)
        setupActionBarWithNavController(navController, appBarConfiguration)

        binding.fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                .setAnchorView(R.id.fab)
                .setAction("Action", null).show()
        }

        try{
            //.......................................................................
            appUpdateManager = AppUpdateManagerFactory.create(this)
            appUpdateManager.registerListener(updateListener)
            checkForUpdate()
        }catch (e:Exception){
            commonLog("update01:Update e1 ${e.message}")
        }


    }

    override fun onBackPressed() {
        try{
            appUpdateManager.unregisterListener(updateListener)
        }catch (e:Exception){
            commonLog("update01:Update e2 ${e.message}")
        }
    }


    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment_content_main)
        return navController.navigateUp(appBarConfiguration)
                || super.onSupportNavigateUp()
    }
}
..

GET source code on Github:

,,

Comments