├── .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 | [](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 |
--------------------------------------------------------------------------------