app_update_check 1.0.0 copy "app_update_check: ^1.0.0" to clipboard
app_update_check: ^1.0.0 copied to clipboard

Check the App Store / Google Play for a newer version, show an optional or forced update dialog, and detect TestFlight builds. Zero dependencies.

app_update_check

Know when your Flutter app is out of date — and whether it's running from TestFlight.
App Store & Google Play version check, install-source detection and a ready-made update dialog.
Zero dependencies. Zero native setup. Never throws.

pub.flutter-io.cn version pub points pub likes pub downloads per month MIT license Platform: iOS 13+, Android 7+ Flutter 3.22+ Zero dependencies

Quick start · Status object · TestFlight · Forced updates · Dialog · Sources · Configuration · FAQ · API docs


app_update_check answers the three questions every production app eventually asks: Is there a newer build in the store? Should I force the user to update? and Is this build running from TestFlight? It talks to the iTunes Lookup API and the Google Play listing directly, reads the install receipt natively, and ships an adaptive update dialog — without pulling package_info_plus, url_launcher, http or any other package into your dependency tree.

await AppUpdate.showUpdateDialogIfNeeded(context);   // check + dialog, one line
final bool testFlight = await AppUpdate.isTestFlight(); // native, no network

✨ Why app_update_check? #

  • 📦 Zero dependencies. The only dependency is flutter. Nothing to conflict with the versions of package_info_plus, url_launcher or http your app already uses.
  • 🔧 Zero native setup. No AppDelegate edits, no manifest changes — the plugin registers itself and the INTERNET permission is merged automatically.
  • ✈️ TestFlight & install-source detection. Sandbox receipt and provisioning-profile heuristics on iOS, installer package on Android. isTestBuild gives you one answer on both platforms.
  • ⛔ Optional or forced updates. Pass a minimumVersion (from Remote Config, your API, anywhere) and the dialog becomes blocking; or use force: true to make every update mandatory.
  • 💬 Adaptive dialog in one call. Material on Android, Cupertino on iOS, localizable texts, release notes, or bring your own widget.
  • 🛡️ Never throws. Offline? Store markup changed? You get status.error and isUpdateAvailable == false; your app keeps running.
  • ⚡ Cached and de-duplicated. One store request per 30 minutes; concurrent callers share the same in-flight request.
  • 🔌 Pluggable sources. App Store, Play Store, your own backend, Firebase Remote Config, or a static value for tests.
  • 🧪 Built for testing. Swap AppUpdate.instance, inject a fake platform bridge or HTTP function — no method-channel mocking required.

🚀 Quick start #

1. Install #

flutter pub add app_update_check

Or add it to pubspec.yaml:

dependencies:
  app_update_check: ^1.0.0

2. Requirements #

Requirement Value
Flutter >= 3.22.0
Dart ^3.8.0
iOS 13.0+ (Swift, CocoaPods and Swift Package Manager)
Android minSdk 24 (Android 7.0), Kotlin
Third-party packages none

3. Use it #

import 'package:app_update_check/app_update_check.dart';

void main() {
  // Optional. Without it, your bundle id / applicationId is used.
  // A numeric iosId gives you a store link even when the lookup fails.
  AppUpdate.init(
    iosId: '6762586391',
    androidId: 'com.example.app',
  );
  runApp(const MyApp());
}

class _HomePageState extends State<HomePage> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      // Shows the dialog only when the store has a newer version.
      AppUpdate.showUpdateDialogIfNeeded(context);
    });
  }
  // ...
}

Need more control? Every building block is one call away:

final AppUpdateStatus status = await AppUpdate.check();
if (status.isUpdateAvailable) {
  debugPrint('${status.localVersion} → ${status.storeVersion}');
}

final bool testFlight = await AppUpdate.isTestFlight();   // iOS, no network
final AppInstallSource source = await AppUpdate.getInstallSource();
await AppUpdate.openStore();                              // Play / App Store app

That's it — no Swift, no Kotlin, no extra packages.

📦 What you get back #

AppUpdate.check() returns an AppUpdateStatus:

Member Type Meaning
localVersion / buildNumber String The running build (1.1.4, 12).
storeVersion String? Version published in the store, null if the lookup failed.
isUpdateAvailable bool storeVersion > localVersion.
isUpdateRequired bool localVersion < minimumVersion — a forced update.
updateType AppUpdateType none · optional · required.
shouldUpdate bool updateType != none.
isUpToDate bool Store reachable and nothing newer.
isAheadOfStore bool Running a build newer than the store (testing tracks, debug builds).
isTestFlight bool iOS build installed through TestFlight.
isTestBuild bool isTestFlight || isAheadOfStore — works on Android too.
installSource AppInstallSource appStore · testFlight · playStore · sideload · development · simulator · unknown.
storeUrl String? Link to the store listing.
releaseNotes String? "What's new" text when the store provides it.
error Object? Why the store lookup failed, if it did. Nothing is thrown.

Version strings are compared numerically (1.10.0 > 1.9.9, 1.2 == 1.2.0); pre-release and build metadata are ignored.

✈️ TestFlight & install source #

// iOS only, no network involved:
final bool testFlight = await AppUpdate.isTestFlight();

// Cross-platform "is this a test build?" (TestFlight, Play internal testing, debug):
final bool testBuild = (await AppUpdate.check()).isTestBuild;

A common pattern is serving a different remote configuration to testers:

final bool tester = (await AppUpdate.check()).isTestBuild;
final String key = tester ? 'APP_CONFIG_TESTFLIGHT' : 'APP_CONFIG';
final String config = remoteConfig.getString(key);

How the install source is determined:

Platform Signal installSource
iOS Compiled for the simulator simulator
iOS Bundle contains embedded.mobileprovision (Xcode, Ad Hoc, Enterprise) development
iOS Receipt file is sandboxReceipt testFlight
iOS Receipt file is receipt appStore
Android Installer is com.android.vending (every Play track) playStore
Android No installer at all (adb install, IDE run) development
Android Any other installer (system package installer, third-party store) sideload

Google Play does not distinguish testing tracks from production, which is why isAheadOfStore / isTestBuild exist.

⛔ Forced updates #

An update is required when the installed version is below minimumVersion. The dialog then has no "Later" button and cannot be dismissed with the back gesture or by tapping outside; it stays open even after the store is opened.

// 1. Per call — e.g. read from Firebase Remote Config
final String minVersion = remoteConfig.getString('MIN_SUPPORTED_VERSION'); // "1.2.0"
await AppUpdate.showUpdateDialogIfNeeded(context, minimumVersion: minVersion);

// 2. Once, at startup
AppUpdate.init(minimumVersion: '1.2.0');

// 3. From your own version source (see below)
AppUpdate.init(
  source: VersionSource.fromCallback((local) async => StoreVersionInfo(
    version: remoteConfig.getString('LATEST_VERSION'),
    minimumVersion: remoteConfig.getString('MIN_SUPPORTED_VERSION'),
  )),
);

// Or simply make every update mandatory
await AppUpdate.showUpdateDialogIfNeeded(context, force: true);

🎨 Customizing the dialog #

// Localized texts and release notes
await AppUpdate.showUpdateDialogIfNeeded(
  context,
  texts: AppUpdateDialogTexts(
    title: l10n.updateTitle,
    message: l10n.updateMessage,
    updateButton: l10n.update,
    laterButton: l10n.later,
  ),
  showReleaseNotes: true,
);

// Your own widget — `mandatory` tells you whether it must be blocking,
// `openStore` opens the listing.
await AppUpdate.showUpdateDialogIfNeeded(
  context,
  builder: (context, status, mandatory, openStore) => PopScope(
    canPop: !mandatory,
    child: MyUpdateSheet(
      version: status.storeVersion,
      notes: status.releaseNotes,
      onUpdate: openStore,
    ),
  ),
);

// Or use the default dialog widget directly
showDialog(
  context: context,
  barrierDismissible: false,
  builder: (_) => AppUpdateDialog(
    status: status,
    mandatory: true,
    onUpdate: AppUpdate.openStore,
  ),
);

The default dialog is AlertDialog.adaptive: Material on Android, Cupertino on iOS. showUpdateDialogIfNeeded never opens a second dialog while one is visible, so it is safe to call from several places.

🔌 Version sources #

Source Where the version comes from
StoreVersionSource (default) App Store on iOS, Play Store on Android.
AppStoreVersionSource iTunes Lookup API, by numeric id or bundle id, optional storefront country.
PlayStoreVersionSource Google Play listing page, optional locale.
CallbackVersionSource / VersionSource.fromCallback Any async function — Remote Config, your backend, feature flags.
StaticVersionSource A fixed value, ideal for tests and demos.

Implement VersionSource for anything else:

class MyApiVersionSource extends VersionSource {
  const MyApiVersionSource();

  @override
  Future<StoreVersionInfo?> fetch(LocalAppInfo local) async {
    final json = await api.get('/app-version?platform=${local.packageName}');
    return StoreVersionInfo(
      version: json['latest'],
      minimumVersion: json['minimum'],
      storeUrl: json['storeUrl'],
      releaseNotes: json['notes'],
    );
  }
}

AppUpdate.init(source: const MyApiVersionSource());

⚙️ Configuration reference #

All parameters are shared by AppUpdate.init(...) and AppUpdateChecker(...).

Parameter Default Description
androidId applicationId Play Store package id.
iosId bundle id Numeric App Store id (recommended) or bundle id.
iosCountry null (US) Two-letter storefront (vn, jp, …) if the app is not on the US store.
androidLocale en_US hl parameter for the Play Store page.
minimumVersion null Versions below it are reported as required.
forceStoreVersion null Pretend the store has this version — development only.
cacheDuration 30 min How long a store answer is reused.
timeout 10 s Network timeout per request.
source store lookup Custom VersionSource.
platform method channel AppUpdateCheckPlatform bridge, replaceable in tests.
httpGet dart:io HttpGet function, replaceable in tests.

🧪 Testing #

Try the update flow before anything is published:

AppUpdate.init(forceStoreVersion: '99.0.0'); // dev builds only

Unit-test your own code without touching the network or a method channel:

AppUpdate.instance = AppUpdateChecker(
  platform: FakeAppPlatform(),                         // implements AppUpdateCheckPlatform
  source: const StaticVersionSource(version: '9.9.9'), // or minimumVersion: ...
);

AppUpdateChecker is a plain class — create several instances if you need different configurations.

🔬 How it works #

AppUpdate.check()
 ├─ AppUpdateCheckPlatform.getAppInfo()          MethodChannel "app_update_check"
 │    version · build · packageName · installSource      (Swift / Kotlin, cached)
 └─ VersionSource.fetch(local)                  cached 30 min, de-duplicated
      ├─ iOS      → https://itunes.apple.com/lookup?id=…            (JSON)
      ├─ Android  → https://play.google.com/store/apps/details?id=…  (HTML)
      └─ custom   → your API / Remote Config
 → AppUpdateStatus  →  showUpdateDialogIfNeeded()  →  openStore()
                                                        market:// → https (Android)
                                                        itms-apps:// → https (iOS)
  • iOS reads CFBundleShortVersionString / CFBundleVersion, checks for an embedded provisioning profile and inspects appStoreReceiptURL (the same heuristic used by most production apps).
  • Android reads PackageInfo and the installing package (getInstallSourceInfo on API 30+, getInstallerPackageName below).
  • Opening the store tries the store app first and falls back to the browser. No LSApplicationQueriesSchemes or <queries> entries are needed.

📱 Platform behavior #

Platform Store lookup isTestFlight installSource Open store
iOS 13+ iTunes Lookup API ✅ native App Store app → Safari
Android 7+ Play Store listing always false Play Store app → browser
Web · desktop status.error is set false unknown false

The package never crashes on unsupported platforms; every call degrades to "no update".

🧪 Example app #

The example/ app shows the local build info, the install source, the store status, and lets you trigger the optional and forced dialogs. Flip Simulate a newer store version to see the dialog without publishing anything.

cd example
flutter run

❓ FAQ #

Why not new_version_plus / upgrader? Those packages bring their own transitive dependencies (package_info_plus, http, url_launcher, …) that regularly conflict with the versions an app already pins. app_update_check has none, adds TestFlight / install-source detection, first-class forced updates, caching and a test-friendly API.
Is reading the Play Store page reliable? Google offers no public API for the listed version, so the page is scanned for the version string — the same approach the community has relied on for years. If Google changes the markup, check() reports the failure through status.error and your app keeps working. For guaranteed results, serve the version yourself with VersionSource.fromCallback.
The App Store lookup returns nothing for my new app. The iTunes Lookup API can lag a few hours behind a first release. Pass a numeric iosId so the store link works regardless, and set iosCountry if the app is not available on the US storefront.
Does TestFlight detection need network access? No. AppUpdate.isTestFlight() only inspects the app bundle and returns instantly. Only the store comparison goes online.
Does it work on the iOS simulator or in debug builds? Yes. The simulator reports installSource == simulator, Xcode / Ad Hoc / Enterprise builds report development; neither is mistaken for TestFlight.
What about Google Play In-App Updates? Play's in-app update flow is a different feature (it downloads the update inside your app and needs the Play Core library). This package is store-agnostic and dependency-free; use it to detect and prompt, and add Play In-App Updates on top if you want the in-app download experience.
Do I need permissions or plist entries? No. The Android INTERNET permission is merged from the plugin manifest, and the store is opened without LSApplicationQueriesSchemes / <queries> declarations.
How are versions compared? Only the leading numeric components count: 1.2.3, 1.2, 1.2.3.4 and v1.2.3-beta+7 all parse, missing parts are 0 (1.2 == 1.2.0) and comparison is numeric (1.10 > 1.9). The parser is exposed as VersionNumber.

🤝 Contributing #

Issues and pull requests are welcome! If this package saved you time, a ⭐ on GitHub and a 👍 on pub.flutter-io.cn help others discover it.

flutter pub get && flutter analyze && flutter test   # 66 tests, runs in ~2 s

📄 License #

MIT © winterzxzz. See LICENSE.


Keywords: flutter app version check, flutter update dialog, force update flutter, flutter testflight detection, flutter new version, app store version flutter, play store version flutter, flutter in app update prompt, flutter minimum version, flutter version checker plugin, new_version_plus alternative, upgrader alternative.

0
likes
160
points
66
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Check the App Store / Google Play for a newer version, show an optional or forced update dialog, and detect TestFlight builds. Zero dependencies.

Repository (GitHub)
View/report issues

Topics

#version #update #testflight #app-store #play-store

License

MIT (license)

Dependencies

flutter

More

Packages that depend on app_update_check

Packages that implement app_update_check