├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── de │ └── ffuf │ └── in_app_update │ └── InAppUpdatePlugin.kt ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── de │ │ │ │ │ └── ffuf │ │ │ │ │ └── in_app_update_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── lib │ └── main.dart └── pubspec.yaml ├── in_app_update.iml ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── InAppUpdatePlugin.h │ └── InAppUpdatePlugin.m └── in_app_update.podspec ├── lib └── in_app_update.dart ├── pubspec.lock └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | .flutter-plugins-dependencies 9 | 10 | **/.idea/** 11 | !**/.idea/runConfigurations/ 12 | !**/.idea/runConfigurations/* 13 | 14 | example/pubspec.lock 15 | pubspec.lock 16 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 198df796aa80073ef22bdf249e614e2ff33c6895 8 | channel: beta 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 4.2.3 2 | - Migrate from jcenter to maven central thanks to @xVemu 3 | 4 | ## 4.2.2 5 | - Match Kotlin jvm compatibility to java compatibility thanks to @ciriousjoker 6 | 7 | ## 4.2.1 8 | - Add namespace to support gradle 8 (#105) thanks to @Rexios80 9 | 10 | ## 4.2.0 11 | - added install update listener feature (#103) thanks to @ViniciusSossela 12 | 13 | ## 4.1.4 14 | * use kotlin 1.7.10, which is the oldest version supported by the play store update library. You will need to adjust this in your project if you use an older version of kotlin. 15 | 16 | ## 4.1.3 17 | * fix parsing of preconditions 18 | 19 | ## 4.1.1 20 | * fix data type for `immediateAllowedPreconditions` and `flexibleAllowedPreconditions` (fixes #99) 21 | 22 | ## 4.1.0 23 | * update Play In-App Update library to 2.10.1, which includes support for Android 14 24 | * introduce `immediateAllowedPreconditions` and `flexibleAllowedPreconditions` fields to `AppUpdateInfo` to check why an update may not be possible 25 | 26 | ## 4.0.1 27 | * improve documentation (thanks to @enzo-santos) 28 | 29 | ## 4.0.0 30 | * Update dependencies 31 | * InstallStatus and UpdateAvailability are now enums 32 | * update Dart to min 2.17 33 | * catch SendIntentException caused by the play store update library (fixes #46) 34 | 35 | ## 3.0.0 36 | * Update dependencies 37 | * Introduce `AppUpdateResult` to know if update is success, user denied or failed. 38 | * Change to return PlatformException with proper error code instead of message only. 39 | 40 | ## 2.0.0 41 | 42 | * Migrated to null safety (#60) 43 | * __Breaking__: Replaced `updateAvailable` with `updateAvailability`. 44 | * Expose more fields to access, added `updateAvailability`, `installStatus`, `clientVersionStalenessDays` and `updatePriority`. (#40) 45 | * Introduce `UpdateAvailability` and `InstallStatus` for constants. 46 | 47 | ## 1.1.15 48 | * fix android Gradle version (6.5.+) build error (#55). thanks for https://github.com/mig35 49 | 50 | ## 1.1.14 51 | * fix compilation error on iOS (#50) 52 | 53 | ## 1.1.13 54 | * fix MissingPluginException (#49) 55 | 56 | ## 1.1.12 57 | * fix #42 58 | * fix #28 (thanks @amaurycannesson) 59 | 60 | ## 1.1.11 61 | * Update Dependencies (possible fix for #31) 62 | * implement to new Flutter plugin format (#29, #27) 63 | 64 | ## 1.1.7 65 | * Fix Crashes (#12, #15, #19) 66 | 67 | ## 1.1.6 68 | * Handle cases where no foreground activity is available (#10) 69 | 70 | ## 1.1.5 71 | * Fix Result already submitted exception (#8) 72 | 73 | ## 1.1.4 74 | 75 | * Expose Available Version Code: https://developer.android.com/reference/com/google/android/play/core/appupdate/AppUpdateInfo.html#availableVersionCode() 76 | (fixes #7) 77 | 78 | ## 1.1.3 79 | 80 | * Fix Android build. 81 | 82 | ## 1.1.1 83 | 84 | * **Breaking change**: Renamed `InAppUpdateState` to `AppUpdateInfo` to mirror the Android SDK and 85 | the `updateType` property has been replaced by `immediateUpdateAllowed` & `flexibleUpdateAllowed`. 86 | The `updateType` property was previously broken. Consequently, refactoring is sensible. 87 | This also means that `UpdateType` has been removed. 88 | * Added support for resuming immediate updates that were cancelled. 89 | This is handled automatically and does not require any Flutter-side code. 90 | * Added documentation to the library. 91 | 92 | ## 1.0.2 93 | 94 | * Readme and example updates 95 | 96 | ## 1.0.0 97 | 98 | * First version released. 99 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | in_app_update 2 | 3 | MIT License 4 | 5 | Copyright (c) 2020 Victor Choueiri 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![pub package](https://img.shields.io/pub/v/in_app_update.svg)](https://pub.dev/packages/in_app_update) 2 | 3 | Maintained by [Jonas Bark](https://twitter.com/boni2k) 4 | 5 | # in_app_update 6 | 7 | Enables In App Updates on Android using the official Android APIs. 8 | 9 | https://developer.android.com/guide/app-bundle/in-app-updates 10 | 11 | 12 | 13 | ## Documentation 14 | 15 | The following methods are exposed: 16 | - `Future checkForUpdate()`: Checks if there's an update available 17 | - `Future performImmediateUpdate()`: Performs an immediate update (full-screen) 18 | - `Future startFlexibleUpdate()`: Starts a flexible update (background download) 19 | - `Future completeFlexibleUpdate()`: Actually installs an available flexible update 20 | 21 | Please have a look in the example app on how to use it! 22 | 23 | ### Android 24 | 25 | This plugin integrates the official Android APIs to perform in app updated that were released in 2019: 26 | https://developer.android.com/guide/app-bundle/in-app-updates 27 | 28 | ### iOS 29 | iOS does not offer such a functionality. You might want to look into e.g. https://pub.dev/packages/upgrader. 30 | If you call the methods above on a iOS device you'll run into a not-implemented exception. 31 | 32 | # Troubleshooting 33 | 34 | ## Getting ERROR_API_NOT_AVAILABLE error 35 | Be aware that this plugin cannot be tested locally. It must be installed via Google Play to work. 36 | Please check the official documentation about In App Updates from Google: 37 | 38 | https://developer.android.com/guide/playcore/in-app-updates/test 39 | 40 | ## Update does not work on old Android versions 41 | In App Updates are only available from API Versions >= 21, as mentioned [here](https://developer.android.com/guide/playcore/in-app-updates). 42 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'de.ffuf.in_app_update' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.7.10' 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:7.2.2' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | } 16 | 17 | rootProject.allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | apply plugin: 'com.android.library' 25 | apply plugin: 'kotlin-android' 26 | 27 | android { 28 | compileSdkVersion 31 29 | 30 | sourceSets { 31 | main.java.srcDirs += 'src/main/kotlin' 32 | } 33 | defaultConfig { 34 | minSdkVersion 16 35 | } 36 | lintOptions { 37 | disable 'InvalidPackage' 38 | } 39 | namespace "de.ffuf.in_app_update" 40 | 41 | compileOptions { 42 | sourceCompatibility JavaVersion.VERSION_1_8 43 | targetCompatibility JavaVersion.VERSION_1_8 44 | } 45 | 46 | kotlinOptions { 47 | jvmTarget = '1.8' 48 | } 49 | } 50 | 51 | dependencies { 52 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 53 | implementation 'com.google.android.play:app-update:2.1.0' 54 | 55 | // For Kotlin users, also add the Kotlin extensions library for Play In-App Update: 56 | implementation 'com.google.android.play:app-update-ktx:2.1.0' 57 | 58 | } 59 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 07 17:42:18 IST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'in_app_update' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/kotlin/de/ffuf/in_app_update/InAppUpdatePlugin.kt: -------------------------------------------------------------------------------- 1 | package de.ffuf.in_app_update 2 | 3 | import android.app.Activity 4 | import android.app.Activity.RESULT_CANCELED 5 | import android.app.Activity.RESULT_OK 6 | import android.app.Application 7 | import android.content.Intent 8 | import android.content.IntentSender.SendIntentException 9 | import android.os.Bundle 10 | import android.util.Log 11 | import com.google.android.play.core.appupdate.AppUpdateInfo 12 | import com.google.android.play.core.appupdate.AppUpdateManager 13 | import com.google.android.play.core.appupdate.AppUpdateManagerFactory 14 | import com.google.android.play.core.appupdate.AppUpdateOptions 15 | import com.google.android.play.core.install.InstallStateUpdatedListener 16 | import com.google.android.play.core.install.model.* 17 | import io.flutter.embedding.engine.plugins.FlutterPlugin 18 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 19 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 20 | import io.flutter.plugin.common.EventChannel 21 | import io.flutter.plugin.common.MethodCall 22 | import io.flutter.plugin.common.MethodChannel 23 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 24 | import io.flutter.plugin.common.MethodChannel.Result 25 | import io.flutter.plugin.common.PluginRegistry 26 | 27 | interface ActivityProvider { 28 | fun addActivityResultListener(callback: PluginRegistry.ActivityResultListener) 29 | fun activity(): Activity 30 | } 31 | 32 | class InAppUpdatePlugin : FlutterPlugin, MethodCallHandler, 33 | PluginRegistry.ActivityResultListener, Application.ActivityLifecycleCallbacks, ActivityAware, 34 | EventChannel.StreamHandler { 35 | 36 | companion object { 37 | private const val REQUEST_CODE_START_UPDATE = 1276 38 | } 39 | 40 | private lateinit var channel: MethodChannel 41 | private lateinit var event: EventChannel 42 | private lateinit var installStateUpdatedListener: InstallStateUpdatedListener 43 | private var installStateSink: EventChannel.EventSink? = null 44 | 45 | override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { 46 | installStateSink = events 47 | } 48 | 49 | override fun onCancel(arguments: Any?) { 50 | installStateSink = null 51 | } 52 | 53 | private fun addState(status: Int){ 54 | installStateSink?.success(status) 55 | } 56 | 57 | override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 58 | channel = MethodChannel(flutterPluginBinding.binaryMessenger, "de.ffuf.in_app_update/methods") 59 | channel.setMethodCallHandler(this) 60 | 61 | event = EventChannel(flutterPluginBinding.binaryMessenger,"de.ffuf.in_app_update/stateEvents" ) 62 | event.setStreamHandler(this) 63 | 64 | installStateUpdatedListener = InstallStateUpdatedListener { installState -> 65 | addState(installState.installStatus()) 66 | } 67 | appUpdateManager?.registerListener(installStateUpdatedListener) 68 | } 69 | 70 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { 71 | channel.setMethodCallHandler(null) 72 | event.setStreamHandler(null) 73 | appUpdateManager?.unregisterListener(installStateUpdatedListener) 74 | } 75 | 76 | private var activityProvider: ActivityProvider? = null 77 | 78 | private var updateResult: Result? = null 79 | private var appUpdateType: Int? = null 80 | private var appUpdateInfo: AppUpdateInfo? = null 81 | private var appUpdateManager: AppUpdateManager? = null 82 | 83 | override fun onMethodCall(call: MethodCall, result: Result) { 84 | when (call.method) { 85 | "checkForUpdate" -> checkForUpdate(result) 86 | "performImmediateUpdate" -> performImmediateUpdate(result) 87 | "startFlexibleUpdate" -> startFlexibleUpdate(result) 88 | "completeFlexibleUpdate" -> completeFlexibleUpdate(result) 89 | else -> result.notImplemented() 90 | } 91 | } 92 | 93 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { 94 | if (requestCode == REQUEST_CODE_START_UPDATE) { 95 | if (appUpdateType == AppUpdateType.IMMEDIATE) { 96 | when (resultCode) { 97 | RESULT_CANCELED -> { 98 | updateResult?.error("USER_DENIED_UPDATE", resultCode.toString(), null) 99 | } 100 | RESULT_OK -> { 101 | updateResult?.success(null) 102 | } 103 | ActivityResult.RESULT_IN_APP_UPDATE_FAILED -> { 104 | updateResult?.error("IN_APP_UPDATE_FAILED", "Some other error prevented either the user from providing consent or the update to proceed.", null) 105 | } 106 | } 107 | updateResult = null 108 | return true 109 | } else if (appUpdateType == AppUpdateType.FLEXIBLE) { 110 | when (resultCode) { 111 | RESULT_CANCELED -> { 112 | updateResult?.error("USER_DENIED_UPDATE", resultCode.toString(), null) 113 | updateResult = null 114 | } 115 | ActivityResult.RESULT_IN_APP_UPDATE_FAILED -> { 116 | updateResult?.error("IN_APP_UPDATE_FAILED", resultCode.toString(), null) 117 | updateResult = null 118 | } 119 | } 120 | return true 121 | } 122 | } 123 | return false 124 | } 125 | 126 | 127 | override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) { 128 | activityProvider = object : ActivityProvider { 129 | override fun addActivityResultListener(callback: PluginRegistry.ActivityResultListener) { 130 | activityPluginBinding.addActivityResultListener(callback) 131 | } 132 | 133 | override fun activity(): Activity { 134 | return activityPluginBinding.activity 135 | } 136 | } 137 | } 138 | 139 | override fun onDetachedFromActivityForConfigChanges() { 140 | activityProvider = null 141 | } 142 | 143 | override fun onReattachedToActivityForConfigChanges(activityPluginBinding: ActivityPluginBinding) { 144 | activityProvider = object : ActivityProvider { 145 | override fun addActivityResultListener(callback: PluginRegistry.ActivityResultListener) { 146 | activityPluginBinding.addActivityResultListener(callback) 147 | } 148 | 149 | override fun activity(): Activity { 150 | return activityPluginBinding.activity 151 | } 152 | } 153 | } 154 | 155 | override fun onDetachedFromActivity() { 156 | activityProvider = null 157 | } 158 | 159 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {} 160 | 161 | override fun onActivityPaused(activity: Activity) {} 162 | 163 | override fun onActivityStarted(activity: Activity) {} 164 | 165 | override fun onActivityDestroyed(activity: Activity) {} 166 | 167 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} 168 | 169 | override fun onActivityStopped(activity: Activity) {} 170 | 171 | override fun onActivityResumed(activity: Activity) { 172 | appUpdateManager 173 | ?.appUpdateInfo 174 | ?.addOnSuccessListener { appUpdateInfo -> 175 | if (appUpdateInfo.updateAvailability() 176 | == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS 177 | && appUpdateType == AppUpdateType.IMMEDIATE 178 | ) { 179 | try { 180 | appUpdateManager?.startUpdateFlowForResult( 181 | appUpdateInfo, 182 | AppUpdateType.IMMEDIATE, 183 | activity, 184 | REQUEST_CODE_START_UPDATE 185 | ) 186 | } catch (e: SendIntentException) { 187 | Log.e("in_app_update", "Could not start update flow", e) 188 | } 189 | } 190 | } 191 | } 192 | 193 | private fun performImmediateUpdate(result: Result) = checkAppState(result) { 194 | appUpdateType = AppUpdateType.IMMEDIATE 195 | updateResult = result 196 | 197 | appUpdateManager?.startUpdateFlowForResult( 198 | appUpdateInfo!!, 199 | activityProvider!!.activity(), 200 | AppUpdateOptions.defaultOptions(AppUpdateType.IMMEDIATE), 201 | REQUEST_CODE_START_UPDATE 202 | ) 203 | } 204 | 205 | private fun checkAppState(result: Result, block: () -> Unit) { 206 | requireNotNull(appUpdateInfo) { 207 | result.error("REQUIRE_CHECK_FOR_UPDATE", "Call checkForUpdate first!", null) 208 | } 209 | requireNotNull(activityProvider?.activity()) { 210 | result.error("REQUIRE_FOREGROUND_ACTIVITY", "in_app_update requires a foreground activity", null) 211 | } 212 | requireNotNull(appUpdateManager) { 213 | result.error("REQUIRE_CHECK_FOR_UPDATE", "Call checkForUpdate first!", null) 214 | } 215 | block() 216 | } 217 | 218 | private fun startFlexibleUpdate(result: Result) = checkAppState(result) { 219 | appUpdateType = AppUpdateType.FLEXIBLE 220 | updateResult = result 221 | appUpdateManager?.startUpdateFlowForResult( 222 | appUpdateInfo!!, 223 | activityProvider!!.activity(), 224 | AppUpdateOptions.defaultOptions(AppUpdateType.FLEXIBLE), 225 | REQUEST_CODE_START_UPDATE 226 | ) 227 | 228 | appUpdateManager?.registerListener { state -> 229 | addState(state.installStatus()) 230 | if (state.installStatus() == InstallStatus.DOWNLOADED) { 231 | updateResult?.success(null) 232 | updateResult = null 233 | } else if (state.installErrorCode() != InstallErrorCode.NO_ERROR) { 234 | updateResult?.error( 235 | "Error during installation", 236 | state.installErrorCode().toString(), 237 | null 238 | ) 239 | updateResult = null 240 | } 241 | } 242 | } 243 | 244 | private fun completeFlexibleUpdate(result: Result) = checkAppState(result) { 245 | appUpdateManager?.completeUpdate() 246 | } 247 | 248 | private fun checkForUpdate(result: Result) { 249 | requireNotNull(activityProvider?.activity()) { 250 | result.error("REQUIRE_FOREGROUND_ACTIVITY", "in_app_update requires a foreground activity", null) 251 | } 252 | 253 | activityProvider?.addActivityResultListener(this) 254 | activityProvider?.activity()?.application?.registerActivityLifecycleCallbacks(this) 255 | 256 | appUpdateManager = AppUpdateManagerFactory.create(activityProvider!!.activity()) 257 | 258 | // Returns an intent object that you use to check for an update. 259 | val appUpdateInfoTask = appUpdateManager!!.appUpdateInfo 260 | 261 | // Checks that the platform will allow the specified type of update. 262 | appUpdateInfoTask.addOnSuccessListener { info -> 263 | appUpdateInfo = info 264 | result.success( 265 | mapOf( 266 | "updateAvailability" to info.updateAvailability(), 267 | "immediateAllowed" to info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE), 268 | "immediateAllowedPreconditions" to info.getFailedUpdatePreconditions(AppUpdateOptions.defaultOptions(AppUpdateType.IMMEDIATE)).map { it.toInt() }.toList(), 269 | "flexibleAllowed" to info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE), 270 | "flexibleAllowedPreconditions" to info.getFailedUpdatePreconditions(AppUpdateOptions.defaultOptions(AppUpdateType.FLEXIBLE)).map { it.toInt() }.toList(), 271 | "availableVersionCode" to info.availableVersionCode(), //Nullable according to docs 272 | "installStatus" to info.installStatus(), 273 | "packageName" to info.packageName(), 274 | "clientVersionStalenessDays" to info.clientVersionStalenessDays(), //Nullable according to docs 275 | "updatePriority" to info.updatePriority() 276 | ) 277 | ) 278 | } 279 | appUpdateInfoTask.addOnFailureListener { 280 | result.error("TASK_FAILURE", it.message, null) 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 198df796aa80073ef22bdf249e614e2ff33c6895 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # in_app_update_example 2 | 3 | Demonstrates how to use the in_app_update plugin. 4 | 5 | ```dart 6 | RaisedButton( 7 | child: Text('Check for Update'), 8 | onPressed: () { 9 | InAppUpdate.checkForUpdate().then((state) { 10 | setState(() { 11 | _updateState = state; 12 | }); 13 | }).catchError((e) => _showError(e)); 14 | }, 15 | ), 16 | RaisedButton( 17 | child: Text('Perform immediate update'), 18 | onPressed: _updateInfo?.updateAvailability == 19 | UpdateAvailability.updateAvailable 20 | ? () { 21 | InAppUpdate.performImmediateUpdate() 22 | .catchError((e) => _showError(e)); 23 | } 24 | : null, 25 | ), 26 | RaisedButton( 27 | child: Text('Start flexible update'), 28 | onPressed: _updateInfo?.updateAvailability == 29 | UpdateAvailability.updateAvailable 30 | ? () { 31 | InAppUpdate.startFlexibleUpdate().then((_) { 32 | setState(() { 33 | _flexibleUpdateAvailable = true; 34 | }); 35 | }).catchError((e) => _showError(e)); 36 | } 37 | : null, 38 | ), 39 | RaisedButton( 40 | child: Text('Complete flexible update'), 41 | onPressed: !_flexibleUpdateAvailable 42 | ? null 43 | : () { 44 | InAppUpdate.completeFlexibleUpdate().then((_) { 45 | _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text('Success!'))); 46 | }).catchError((e) => _showError(e)); 47 | ; 48 | }, 49 | ) 50 | ``` -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 31 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "de.ffuf.in_app_update_example" 42 | minSdkVersion 16 43 | targetSdkVersion 31 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 14 | 18 | 22 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/de/ffuf/in_app_update_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package de.ffuf.in_app_update_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonasbark/flutter_in_app_update/67df3574c9b332cb87e04bf7fa3724d7a0b68500/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonasbark/flutter_in_app_update/67df3574c9b332cb87e04bf7fa3724d7a0b68500/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonasbark/flutter_in_app_update/67df3574c9b332cb87e04bf7fa3724d7a0b68500/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonasbark/flutter_in_app_update/67df3574c9b332cb87e04bf7fa3724d7a0b68500/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonasbark/flutter_in_app_update/67df3574c9b332cb87e04bf7fa3724d7a0b68500/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.2.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableR8=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 07 19:37:12 NOVT 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:in_app_update/in_app_update.dart'; 5 | 6 | void main() => runApp(MyApp()); 7 | 8 | class MyApp extends StatefulWidget { 9 | @override 10 | _MyAppState createState() => _MyAppState(); 11 | } 12 | 13 | class _MyAppState extends State { 14 | AppUpdateInfo? _updateInfo; 15 | 16 | GlobalKey _scaffoldKey = new GlobalKey(); 17 | 18 | bool _flexibleUpdateAvailable = false; 19 | 20 | // Platform messages are asynchronous, so we initialize in an async method. 21 | Future checkForUpdate() async { 22 | InAppUpdate.checkForUpdate().then((info) { 23 | setState(() { 24 | _updateInfo = info; 25 | }); 26 | }).catchError((e) { 27 | showSnack(e.toString()); 28 | }); 29 | } 30 | 31 | void showSnack(String text) { 32 | if (_scaffoldKey.currentContext != null) { 33 | ScaffoldMessenger.of(_scaffoldKey.currentContext!) 34 | .showSnackBar(SnackBar(content: Text(text))); 35 | } 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return MaterialApp( 41 | home: Scaffold( 42 | key: _scaffoldKey, 43 | appBar: AppBar( 44 | title: const Text('In App Update Example App'), 45 | ), 46 | body: Padding( 47 | padding: const EdgeInsets.all(8.0), 48 | child: Column( 49 | children: [ 50 | Center( 51 | child: Text('Update info: $_updateInfo'), 52 | ), 53 | ElevatedButton( 54 | child: Text('Check for Update'), 55 | onPressed: () => checkForUpdate(), 56 | ), 57 | ElevatedButton( 58 | child: Text('Perform immediate update'), 59 | onPressed: _updateInfo?.updateAvailability == 60 | UpdateAvailability.updateAvailable 61 | ? () { 62 | InAppUpdate.performImmediateUpdate() 63 | .catchError((e) { 64 | showSnack(e.toString()); 65 | return AppUpdateResult.inAppUpdateFailed; 66 | }); 67 | } 68 | : null, 69 | ), 70 | ElevatedButton( 71 | child: Text('Start flexible update'), 72 | onPressed: _updateInfo?.updateAvailability == 73 | UpdateAvailability.updateAvailable 74 | ? () { 75 | InAppUpdate.startFlexibleUpdate().then((_) { 76 | setState(() { 77 | _flexibleUpdateAvailable = true; 78 | }); 79 | }).catchError((e) { 80 | showSnack(e.toString()); 81 | }); 82 | } 83 | : null, 84 | ), 85 | ElevatedButton( 86 | child: Text('Complete flexible update'), 87 | onPressed: !_flexibleUpdateAvailable 88 | ? null 89 | : () { 90 | InAppUpdate.completeFlexibleUpdate().then((_) { 91 | showSnack("Success!"); 92 | }).catchError((e) { 93 | showSnack(e.toString()); 94 | }); 95 | }, 96 | ) 97 | ], 98 | ), 99 | ), 100 | ), 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: in_app_update_example 2 | description: Demonstrates how to use the in_app_update plugin. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | in_app_update: 16 | # When depending on this package from a real application you should use: 17 | # in_app_update: ^x.y.z 18 | # See https://dart.dev/tools/pub/dependencies#version-constraints 19 | # The example app is bundled with the plugin so we use a path dependency on 20 | # the parent directory to use the current plugin's version. 21 | path: ../ 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^1.0.0 26 | 27 | dev_dependencies: 28 | flutter_test: 29 | sdk: flutter 30 | 31 | # For information on the generic Dart part of this file, see the 32 | # following page: https://dart.dev/tools/pub/pubspec 33 | 34 | # The following section is specific to Flutter. 35 | flutter: 36 | 37 | # The following line ensures that the Material Icons font is 38 | # included with your application, so that you can use the icons in 39 | # the material Icons class. 40 | uses-material-design: true 41 | 42 | # To add assets to your application, add an assets section, like this: 43 | # assets: 44 | # - images/a_dot_burr.jpeg 45 | # - images/a_dot_ham.jpeg 46 | 47 | # An image asset can refer to one or more resolution-specific "variants", see 48 | # https://flutter.dev/assets-and-images/#resolution-aware. 49 | 50 | # For details regarding adding assets from package dependencies, see 51 | # https://flutter.dev/assets-and-images/#from-packages 52 | 53 | # To add custom fonts to your application, add a fonts section here, 54 | # in this "flutter" section. Each entry in this list should have a 55 | # "family" key with the font family name, and a "fonts" key with a 56 | # list giving the asset and other descriptors for the font. For 57 | # example: 58 | # fonts: 59 | # - family: Schyler 60 | # fonts: 61 | # - asset: fonts/Schyler-Regular.ttf 62 | # - asset: fonts/Schyler-Italic.ttf 63 | # style: italic 64 | # - family: Trajan Pro 65 | # fonts: 66 | # - asset: fonts/TrajanPro.ttf 67 | # - asset: fonts/TrajanPro_Bold.ttf 68 | # weight: 700 69 | # 70 | # For details regarding fonts from package dependencies, 71 | # see https://flutter.dev/custom-fonts/#from-packages 72 | -------------------------------------------------------------------------------- /in_app_update.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonasbark/flutter_in_app_update/67df3574c9b332cb87e04bf7fa3724d7a0b68500/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/InAppUpdatePlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface InAppUpdatePlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/InAppUpdatePlugin.m: -------------------------------------------------------------------------------- 1 | #import "InAppUpdatePlugin.h" 2 | 3 | @implementation InAppUpdatePlugin 4 | + (void)registerWithRegistrar:(NSObject*)registrar { 5 | FlutterMethodChannel* channel = [FlutterMethodChannel 6 | methodChannelWithName:@"in_app_update" 7 | binaryMessenger:[registrar messenger]]; 8 | InAppUpdatePlugin* instance = [[InAppUpdatePlugin alloc] init]; 9 | [registrar addMethodCallDelegate:instance channel:channel]; 10 | } 11 | 12 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 13 | result(FlutterMethodNotImplemented); 14 | } 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ios/in_app_update.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint in_app_update.podspec' to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'in_app_update' 7 | s.version = '0.0.1' 8 | s.summary = 'Enables In App Updates on Android using the official Android APIs.' 9 | s.description = <<-DESC 10 | Enables In App Updates on Android using the official Android APIs. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.public_header_files = 'Classes/**/*.h' 18 | s.dependency 'Flutter' 19 | s.platform = :ios, '8.0' 20 | 21 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. 22 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } 23 | end 24 | -------------------------------------------------------------------------------- /lib/in_app_update.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/services.dart'; 4 | 5 | /// Status of a download/install. 6 | /// 7 | /// For more information, see its corresponding page on 8 | /// [Android Developers](https://developer.android.com/reference/com/google/android/play/core/install/model/InstallStatus.html). 9 | enum InstallStatus { 10 | unknown(0), 11 | pending(1), 12 | downloading(2), 13 | installing(3), 14 | installed(4), 15 | failed(5), 16 | canceled(6), 17 | downloaded(11); 18 | 19 | const InstallStatus(this.value); 20 | final int value; 21 | } 22 | 23 | /// Availability of an update for the requested package. 24 | /// 25 | /// For more information, see its corresponding page on 26 | /// [Android Developers](https://developer.android.com/reference/com/google/android/play/core/install/model/UpdateAvailability.html). 27 | enum UpdateAvailability { 28 | unknown(0), 29 | updateNotAvailable(1), 30 | updateAvailable(2), 31 | developerTriggeredUpdateInProgress(3); 32 | 33 | const UpdateAvailability(this.value); 34 | final int value; 35 | } 36 | 37 | enum AppUpdateResult { 38 | /// The user has accepted the update. For immediate updates, you might not 39 | /// receive this callback because the update should already be completed by 40 | /// Google Play by the time the control is given back to your app. 41 | success, 42 | 43 | /// The user has denied or cancelled the update. 44 | userDeniedUpdate, 45 | 46 | /// Some other error prevented either the user from providing consent or the 47 | /// update to proceed. 48 | inAppUpdateFailed, 49 | } 50 | 51 | class InAppUpdate { 52 | static const MethodChannel _channel = 53 | const MethodChannel('de.ffuf.in_app_update/methods'); 54 | static const EventChannel _installListener = 55 | const EventChannel('de.ffuf.in_app_update/stateEvents'); 56 | 57 | /// Has to be called before being able to start any update. 58 | /// 59 | /// Returns [AppUpdateInfo], which can be used to decide if 60 | /// [startFlexibleUpdate] or [performImmediateUpdate] should be called. 61 | static Future checkForUpdate() async { 62 | final result = await _channel.invokeMethod('checkForUpdate'); 63 | 64 | return AppUpdateInfo( 65 | updateAvailability: UpdateAvailability.values.firstWhere( 66 | (element) => element.value == result['updateAvailability']), 67 | immediateUpdateAllowed: result['immediateAllowed'], 68 | immediateAllowedPreconditions: result['immediateAllowedPreconditions'] 69 | ?.map((e) => e as int) 70 | .toList(), 71 | flexibleUpdateAllowed: result['flexibleAllowed'], 72 | flexibleAllowedPreconditions: result['flexibleAllowedPreconditions'] 73 | ?.map((e) => e as int) 74 | .toList(), 75 | availableVersionCode: result['availableVersionCode'], 76 | installStatus: InstallStatus.values 77 | .firstWhere((element) => element.value == result['installStatus']), 78 | packageName: result['packageName'], 79 | clientVersionStalenessDays: result['clientVersionStalenessDays'], 80 | updatePriority: result['updatePriority'], 81 | ); 82 | } 83 | 84 | static Stream get installUpdateListener { 85 | return _installListener 86 | .receiveBroadcastStream() 87 | .cast() 88 | .map((int value) { 89 | switch (value) { 90 | case 0: 91 | return InstallStatus.unknown; 92 | case 1: 93 | return InstallStatus.pending; 94 | case 2: 95 | return InstallStatus.downloading; 96 | case 3: 97 | return InstallStatus.installing; 98 | case 4: 99 | return InstallStatus.installed; 100 | case 5: 101 | return InstallStatus.failed; 102 | case 6: 103 | return InstallStatus.canceled; 104 | case 11: 105 | return InstallStatus.downloaded; 106 | default: 107 | return InstallStatus.unknown; 108 | } 109 | }); 110 | } 111 | 112 | /// Performs an immediate update that is entirely handled by the Play API. 113 | /// 114 | /// [checkForUpdate] has to be called first to be able to run this. 115 | static Future performImmediateUpdate() async { 116 | try { 117 | await _channel.invokeMethod('performImmediateUpdate'); 118 | return AppUpdateResult.success; 119 | } on PlatformException catch (e) { 120 | if (e.code == 'USER_DENIED_UPDATE') { 121 | return AppUpdateResult.userDeniedUpdate; 122 | } else if (e.code == 'IN_APP_UPDATE_FAILED') { 123 | return AppUpdateResult.inAppUpdateFailed; 124 | } 125 | 126 | throw e; 127 | } 128 | } 129 | 130 | /// Starts the download of the app update. 131 | /// 132 | /// Throws a [PlatformException] if the download fails. 133 | /// When the returned [Future] is completed without any errors, 134 | /// [completeFlexibleUpdate] can be called to install the update. 135 | /// 136 | /// [checkForUpdate] has to be called first to be able to run this. 137 | static Future startFlexibleUpdate() async { 138 | try { 139 | await _channel.invokeMethod('startFlexibleUpdate'); 140 | return AppUpdateResult.success; 141 | } on PlatformException catch (e) { 142 | if (e.code == 'USER_DENIED_UPDATE') { 143 | return AppUpdateResult.userDeniedUpdate; 144 | } else if (e.code == 'IN_APP_UPDATE_FAILED') { 145 | return AppUpdateResult.inAppUpdateFailed; 146 | } 147 | 148 | throw e; 149 | } 150 | } 151 | 152 | /// Installs the update downloaded via [startFlexibleUpdate]. 153 | /// 154 | /// [startFlexibleUpdate] has to be completed successfully. 155 | static Future completeFlexibleUpdate() async { 156 | return await _channel.invokeMethod('completeFlexibleUpdate'); 157 | } 158 | } 159 | 160 | /// Contains information about the availability and progress of an app 161 | /// update. 162 | /// 163 | /// For more information, see its corresponding page on 164 | /// [Android Developers](https://developer.android.com/reference/com/google/android/play/core/appupdate/AppUpdateInfo). 165 | class AppUpdateInfo { 166 | /// Whether an update is available for the app. 167 | /// 168 | /// This is a value from [UpdateAvailability]. 169 | final UpdateAvailability updateAvailability; 170 | 171 | /// Whether an immediate update is allowed. 172 | final bool immediateUpdateAllowed; 173 | 174 | /// determine the reason why an update cannot be started 175 | final List? immediateAllowedPreconditions; 176 | 177 | /// Whether a flexible update is allowed. 178 | final bool flexibleUpdateAllowed; 179 | 180 | /// determine the reason why an update cannot be started 181 | final List? flexibleAllowedPreconditions; 182 | 183 | /// The version code of the update. 184 | /// 185 | /// If no updates are available, this is an arbitrary value. 186 | final int? availableVersionCode; 187 | 188 | /// The progress status of the update. 189 | /// 190 | /// This value is defined only if [updateAvailability] is 191 | /// [UpdateAvailability.developerTriggeredUpdateInProgress]. 192 | /// 193 | /// This is a value from [InstallStatus]. 194 | final InstallStatus installStatus; 195 | 196 | /// The package name for the app to be updated. 197 | final String packageName; 198 | 199 | /// The in-app update priority for this update, as defined by the developer 200 | /// in the Google Play Developer API. 201 | /// 202 | /// This value is defined only if [updateAvailability] is 203 | /// [UpdateAvailability.updateAvailable]. 204 | final int updatePriority; 205 | 206 | /// The number of days since the Google Play Store app on the user's device 207 | /// has learnt about an available update. 208 | /// 209 | /// If update is not available, or if staleness information is unavailable, 210 | /// this is null. 211 | final int? clientVersionStalenessDays; 212 | 213 | AppUpdateInfo({ 214 | required this.updateAvailability, 215 | required this.immediateUpdateAllowed, 216 | required this.immediateAllowedPreconditions, 217 | required this.flexibleUpdateAllowed, 218 | required this.flexibleAllowedPreconditions, 219 | required this.availableVersionCode, 220 | required this.installStatus, 221 | required this.packageName, 222 | required this.clientVersionStalenessDays, 223 | required this.updatePriority, 224 | }); 225 | 226 | @override 227 | bool operator ==(Object other) => 228 | identical(this, other) || 229 | other is AppUpdateInfo && 230 | runtimeType == other.runtimeType && 231 | updateAvailability == other.updateAvailability && 232 | immediateUpdateAllowed == other.immediateUpdateAllowed && 233 | immediateAllowedPreconditions == 234 | other.immediateAllowedPreconditions && 235 | flexibleUpdateAllowed == other.flexibleUpdateAllowed && 236 | flexibleAllowedPreconditions == other.flexibleAllowedPreconditions && 237 | availableVersionCode == other.availableVersionCode && 238 | installStatus == other.installStatus && 239 | packageName == other.packageName && 240 | clientVersionStalenessDays == other.clientVersionStalenessDays && 241 | updatePriority == other.updatePriority; 242 | 243 | @override 244 | int get hashCode => 245 | updateAvailability.hashCode ^ 246 | immediateUpdateAllowed.hashCode ^ 247 | immediateAllowedPreconditions.hashCode ^ 248 | flexibleUpdateAllowed.hashCode ^ 249 | flexibleAllowedPreconditions.hashCode ^ 250 | availableVersionCode.hashCode ^ 251 | installStatus.hashCode ^ 252 | packageName.hashCode ^ 253 | clientVersionStalenessDays.hashCode ^ 254 | updatePriority.hashCode; 255 | 256 | @override 257 | String toString() => 258 | 'InAppUpdateState{updateAvailability: $updateAvailability, ' 259 | 'immediateUpdateAllowed: $immediateUpdateAllowed, ' 260 | 'immediateAllowedPreconditions: $immediateAllowedPreconditions, ' 261 | 'flexibleUpdateAllowed: $flexibleUpdateAllowed, ' 262 | 'flexibleAllowedPreconditions: $flexibleAllowedPreconditions, ' 263 | 'availableVersionCode: $availableVersionCode, ' 264 | 'installStatus: $installStatus, ' 265 | 'packageName: $packageName, ' 266 | 'clientVersionStalenessDays: $clientVersionStalenessDays, ' 267 | 'updatePriority: $updatePriority}'; 268 | } 269 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.17.2" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.1" 52 | flutter: 53 | dependency: "direct main" 54 | description: flutter 55 | source: sdk 56 | version: "0.0.0" 57 | flutter_test: 58 | dependency: "direct dev" 59 | description: flutter 60 | source: sdk 61 | version: "0.0.0" 62 | matcher: 63 | dependency: transitive 64 | description: 65 | name: matcher 66 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" 67 | url: "https://pub.dev" 68 | source: hosted 69 | version: "0.12.16" 70 | material_color_utilities: 71 | dependency: transitive 72 | description: 73 | name: material_color_utilities 74 | sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" 75 | url: "https://pub.dev" 76 | source: hosted 77 | version: "0.5.0" 78 | meta: 79 | dependency: transitive 80 | description: 81 | name: meta 82 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "1.9.1" 86 | path: 87 | dependency: transitive 88 | description: 89 | name: path 90 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "1.8.3" 94 | sky_engine: 95 | dependency: transitive 96 | description: flutter 97 | source: sdk 98 | version: "0.0.99" 99 | source_span: 100 | dependency: transitive 101 | description: 102 | name: source_span 103 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 104 | url: "https://pub.dev" 105 | source: hosted 106 | version: "1.10.0" 107 | stack_trace: 108 | dependency: transitive 109 | description: 110 | name: stack_trace 111 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 112 | url: "https://pub.dev" 113 | source: hosted 114 | version: "1.11.0" 115 | stream_channel: 116 | dependency: transitive 117 | description: 118 | name: stream_channel 119 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 120 | url: "https://pub.dev" 121 | source: hosted 122 | version: "2.1.1" 123 | string_scanner: 124 | dependency: transitive 125 | description: 126 | name: string_scanner 127 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 128 | url: "https://pub.dev" 129 | source: hosted 130 | version: "1.2.0" 131 | term_glyph: 132 | dependency: transitive 133 | description: 134 | name: term_glyph 135 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 136 | url: "https://pub.dev" 137 | source: hosted 138 | version: "1.2.1" 139 | test_api: 140 | dependency: transitive 141 | description: 142 | name: test_api 143 | sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "0.6.0" 147 | vector_math: 148 | dependency: transitive 149 | description: 150 | name: vector_math 151 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "2.1.4" 155 | web: 156 | dependency: transitive 157 | description: 158 | name: web 159 | sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "0.1.4-beta" 163 | sdks: 164 | dart: ">=3.1.0-185.0.dev <4.0.0" 165 | flutter: ">=1.20.0" 166 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: in_app_update 2 | description: Enables In App Updates on Android using the official Android APIs. 3 | version: 4.2.3 4 | homepage: https://jonasbark.de 5 | repository: https://github.com/jonasbark/flutter_in_app_update 6 | 7 | environment: 8 | sdk: '>=2.17.0 <4.0.0' 9 | flutter: ">=1.20.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | flutter: 20 | plugin: 21 | platforms: 22 | android: 23 | package: de.ffuf.in_app_update 24 | pluginClass: InAppUpdatePlugin 25 | --------------------------------------------------------------------------------