in_app_update_kit
Google Play In-App Updates for DartNative apps.
It keeps the Flutter in_app_update
API, so code written for it ports by changing one import:
// import 'package:in_app_update/in_app_update.dart';
import 'package:in_app_update_kit/in_app_update_kit.dart';
Calls go over FFI and JNI to Play's AppUpdateManager; there are no platform
channels. Android only. On iOS every call is a safe no-op (see below).
Install
dependencies:
in_app_update_kit:
hosted: https://dartpub.dev
version: ^0.1.0
dn pub get # regenerates lib/dartnative_plugin_registrant.dart
DartNativePluginRegistrant.registerAll() in main() loads the plugin. No
other setup is needed.
Use
Future<void> updateIfNeeded({required bool forced}) async {
try {
final info = await InAppUpdate.checkForUpdate();
if (info.updateAvailability != UpdateAvailability.updateAvailable) return;
if (forced) {
await InAppUpdate.performImmediateUpdate();
} else if (await InAppUpdate.startFlexibleUpdate() ==
AppUpdateResult.success) {
// Downloaded. Install when it suits the user; Play restarts the app.
await InAppUpdate.completeFlexibleUpdate();
}
} on InAppUpdateException catch (e) {
// e.isUnavailable: Play can't update this install (sideloaded build,
// no Play Store). Fall back to opening the store listing.
}
}
| Call | Result |
|---|---|
checkForUpdate() |
AppUpdateInfo. Must run before a start. |
performImmediateUpdate() |
Play's full-screen flow. success, userDeniedUpdate or inAppUpdateFailed; Play usually restarts the app first. |
startFlexibleUpdate() |
Consent dialog, then a background download. success once the download finished; userDeniedUpdate if the dialog was declined or the download canceled. |
completeFlexibleUpdate() |
Installs the downloaded update. |
installUpdateListener |
Stream<InstallStatus> of Play's install states. |
Platform setup
Android
Nothing to add to the app. The plugin's Gradle module depends on
com.google.android.play:app-update:2.1.0, which pulls in
com.google.android.play:core-common and the Play services basement and
tasks libraries. Gradle fetches them from Google's Maven repository; this
package doesn't bundle or modify them. They stay under their own terms: the
Play Core Software Development Kit Terms of Service
and the Android Software Development Kit License.
core-common merges Play's transparent PlayCoreDialogWrapperActivity into
your manifest. No permissions are added. R8 rules ship with the plugin.
In-app updates only work for builds installed from Google Play. A debug build
from dn run makes checkForUpdate() throw TASK_FAILURE with
installErrorCode -10 (ERROR_APP_NOT_OWNED), or return
updateNotAvailable, depending on the Play Store version. To test the real
flow, use Play's internal app sharing or an internal test track.
iOS
iOS has no in-app update API, and the package ships no iOS code, pod or symbols. The calls still work and never throw:
checkForUpdate()returnsupdateAvailability: unknown, nothing allowed.performImmediateUpdate()andstartFlexibleUpdate()returninAppUpdateFailedand show nothing.completeFlexibleUpdate()does nothing;installUpdateListenernever emits.
Send iOS users to the App Store yourself.
Errors
Failures throw InAppUpdateException (Flutter's PlatformException doesn't
exist in DartNative). code keeps in_app_update's strings:
code |
When |
|---|---|
TASK_FAILURE |
A Play task failed; installErrorCode holds Play's InstallErrorCode. |
REQUIRE_CHECK_FOR_UPDATE |
A start or complete before checkForUpdate(). |
REQUIRE_FOREGROUND_ACTIVITY |
No Activity in the foreground. |
INSTALL_ERROR |
The flexible download failed. |
UPDATE_IN_PROGRESS |
A start while another start is pending. |
NATIVE_UNAVAILABLE |
The native library didn't load. |
isUnavailable is true for install error codes -3, -9 and -10, where Play
can't serve updates to this install at all.
Differences from in_app_update
InAppUpdateExceptioninstead ofPlatformException, with the same codes.- Unknown Play values (such as
InstallStatus10,REQUIRES_UI_INTENT) decode asunknowninstead of throwing aStateError. AppUpdateInfo ==compares the precondition lists by value.- A flexible download canceled from the notification completes
startFlexibleUpdate()withuserDeniedUpdate; upstream never completed. Upstream also added a new install listener on every start. - A second start while one is pending throws
UPDATE_IN_PROGRESSinstead of orphaning the first call. - On iOS the calls are no-ops instead of throwing
MissingPluginException.
Testing
package:in_app_update_kit/testing.dart has two seams:
debugSetInAppUpdateBackend(backend)routesInAppUpdatethrough a fakeInAppUpdateBackendin pure-Dart tests.debugUseFakeAppUpdateManager()(Android debug builds) swaps in Play'sFakeAppUpdateManager, so the flexible flow can be walked on an emulator. The example app has buttons for it.
cd example && dn run -d <android-device>
Credits & license
The API, the enums and AppUpdateInfo, their documentation and the Play call
sequence are adapted from in_app_update
5.0.0 by Victor Choueiri (MIT). The method and event channels were replaced by
an FFI/JNI bridge, and the flexible flow's listener leak and cancel hang were
fixed. See THIRD_PARTY_NOTICES.
This package is MIT, see LICENSE.