From b5f4e6216bf751350a6321e5532ec0b284253d99 Mon Sep 17 00:00:00 2001 From: odaridavid Date: Tue, 5 Mar 2024 16:43:56 +0100 Subject: [PATCH] add playstore flexible updates --- app/build.gradle.kts | 3 + .../odaridavid/weatherapp/ui/MainActivity.kt | 27 +++++++- .../ui/update/UpdateAppException.kt | 3 + .../weatherapp/ui/update/UpdateManager.kt | 69 +++++++++++++++++++ .../ui/update/UpdateStateFactory.kt | 43 ++++++++++++ gradle/libs.versions.toml | 8 +++ 6 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateAppException.kt create mode 100644 app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateManager.kt create mode 100644 app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateStateFactory.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6c07e01..bd048f7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -198,6 +198,9 @@ dependencies { // Memory Leak Detection debugImplementation(libs.leakcanary) + + // In-app update + implementation(libs.bundles.google.play) } kapt { diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/ui/MainActivity.kt b/app/src/main/java/com/github/odaridavid/weatherapp/ui/MainActivity.kt index 4d771d9..dcac27f 100644 --- a/app/src/main/java/com/github/odaridavid/weatherapp/ui/MainActivity.kt +++ b/app/src/main/java/com/github/odaridavid/weatherapp/ui/MainActivity.kt @@ -1,9 +1,9 @@ package com.github.odaridavid.weatherapp.ui import android.annotation.SuppressLint -import android.app.Activity import android.location.Location import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts @@ -25,17 +25,23 @@ import com.github.odaridavid.weatherapp.designsystem.EnableLocationSettingScreen import com.github.odaridavid.weatherapp.designsystem.LoadingScreen import com.github.odaridavid.weatherapp.designsystem.RequiresPermissionsScreen import com.github.odaridavid.weatherapp.designsystem.theme.WeatherAppTheme +import com.github.odaridavid.weatherapp.ui.update.UpdateManager import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { private val mainViewModel: MainViewModel by viewModels() + + @Inject + lateinit var updateManager: UpdateManager + private val locationRequestLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> - if (result.resultCode == Activity.RESULT_OK) { + if (result.resultCode == RESULT_OK) { mainViewModel.processIntent(MainViewIntent.CheckLocationSettings(isEnabled = true)) } else { mainViewModel.processIntent(MainViewIntent.CheckLocationSettings(isEnabled = false)) @@ -46,10 +52,22 @@ class MainActivity : ComponentActivity() { mainViewModel.processIntent(MainViewIntent.GrantPermission(isGranted = isGranted)) } + private val updateRequestLauncher = + registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> + if (result.resultCode == RESULT_OK) { + // TODO Trigger a UI event + Log.d("MainActivity", "Update successful") + } else { + Log.e("MainActivity", "Update failed") + } + } + private lateinit var fusedLocationProviderClient: FusedLocationProviderClient override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + updateManager.checkForUpdates(activityResultLauncher = updateRequestLauncher) + createLocationRequest( activity = this@MainActivity, locationRequestLauncher = locationRequestLauncher @@ -115,5 +133,10 @@ class MainActivity : ComponentActivity() { else -> LoadingScreen() } } + + override fun onDestroy() { + super.onDestroy() + updateManager.unregisterListeners() + } } diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateAppException.kt b/app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateAppException.kt new file mode 100644 index 0000000..5e53b28 --- /dev/null +++ b/app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateAppException.kt @@ -0,0 +1,3 @@ +package com.github.odaridavid.weatherapp.ui.update + +data class UpdateAppException(val throwable: Throwable) : Exception() diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateManager.kt b/app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateManager.kt new file mode 100644 index 0000000..4ffc0c9 --- /dev/null +++ b/app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateManager.kt @@ -0,0 +1,69 @@ +package com.github.odaridavid.weatherapp.ui.update + +import android.content.Context +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.IntentSenderRequest +import com.github.odaridavid.weatherapp.core.api.Logger +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.appupdate.AppUpdateOptions +import com.google.android.play.core.install.model.AppUpdateType +import com.google.android.play.core.install.model.UpdateAvailability +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class UpdateManager @Inject constructor( + @ApplicationContext private val context: Context, + private val logger: Logger, + private val updateStateFactory: UpdateStateFactory, +) { + + private val appUpdateManager: AppUpdateManager by lazy { + AppUpdateManagerFactory.create(context) + } + + fun checkForUpdates( + activityResultLauncher: ActivityResultLauncher + ) { + val appUpdateInfoTask = appUpdateManager.appUpdateInfo + + appUpdateManager.registerListener(updateStateFactory.getUpdateStateListener( + onDownloaded = { + // TODO: Notify the user that the update is ready to be installed.Don't do it this way. + appUpdateManager.completeUpdate() + } + )) + + appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> + if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE + && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE) + ) { + update( + appUpdateManager = appUpdateManager, + appUpdateInfo = appUpdateInfo, + activityResultLauncher = activityResultLauncher, + ) + } + }.addOnFailureListener { exception -> + logger.logException(UpdateAppException(exception)) + } + } + + fun unregisterListeners() { + appUpdateManager.unregisterListener(updateStateFactory.getUpdateStateListener()) + } + + private fun update( + appUpdateManager: AppUpdateManager, + appUpdateInfo: AppUpdateInfo, + activityResultLauncher: ActivityResultLauncher + ) { + appUpdateManager.startUpdateFlowForResult( + appUpdateInfo, + activityResultLauncher, + AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build() + ) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateStateFactory.kt b/app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateStateFactory.kt new file mode 100644 index 0000000..04f2a8b --- /dev/null +++ b/app/src/main/java/com/github/odaridavid/weatherapp/ui/update/UpdateStateFactory.kt @@ -0,0 +1,43 @@ +package com.github.odaridavid.weatherapp.ui.update + +import com.google.android.play.core.install.InstallStateUpdatedListener +import com.google.android.play.core.install.model.InstallStatus +import javax.inject.Inject + +class UpdateStateFactory @Inject constructor() { + + fun getUpdateStateListener( + onDownloading: ((bytesDownloaded: Long, totalBytesToDownload: Long) -> Unit)? = null, + onDownloaded: (() -> Unit)? = null, + ) = InstallStateUpdatedListener { state -> + when (state.installStatus()) { + InstallStatus.DOWNLOADING -> { + val bytesDownloaded = state.bytesDownloaded() + val totalBytesToDownload = state.totalBytesToDownload() + if (onDownloading != null) { + onDownloading(bytesDownloaded, totalBytesToDownload) + } + // Show update progress bar. + } + InstallStatus.DOWNLOADED -> { + // Notify the user that the update is ready to be installed. + if (onDownloaded != null) { + onDownloaded() + } + + } + InstallStatus.INSTALLING, + InstallStatus.INSTALLED, + InstallStatus.FAILED, + InstallStatus.CANCELED, + InstallStatus.PENDING, + InstallStatus.UNKNOWN -> { + // No-op + } + else -> { + // No-op + } + } + } + +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 38eda52..dcceb71 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,6 +40,8 @@ retrofit = "2.9.0" truth = "1.4.2" turbine = "1.0.0" leakcanary = "3.0-alpha-1" +#InAppUpdate +inappupdate = "2.1.0" [libraries] activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } @@ -90,6 +92,8 @@ retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit truth = { module = "com.google.truth:truth", version.ref = "truth" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } +inapp-update = { module = "com.google.android.play:app-update", version.ref = "inappupdate" } +inapp-update-ktx = { module = "com.google.android.play:app-update-ktx", version.ref = "inappupdate" } [plugins] com-android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } @@ -120,3 +124,7 @@ firebase = [ "firebase-crashlytics", "firebase-perfomance-monitoring", ] +google-play = [ + "inapp-update", + "inapp-update-ktx", +]