App Updater

GitHub stars pub package

A comprehensive Flutter package to check for app updates and display platform-native dialogs. Supports iOS, Android, macOS, Windows, and Linux with adaptive UI for each platform.

Platform Dialog Style
iOS iOS Dialog
Android Android Dialog
macOS macOS Dialog
Windows Windows Dialog
Linux Linux Dialog

Features

Core Features

  • Platform-Specific Dialogs: Native UI for each platform (Material, Cupertino, Fluent, Adwaita)
  • Persistent Dialogs: Force users to update with non-dismissible dialogs
  • Skip Version: Let users skip specific versions
  • Do Not Ask Again: Remember user's preference to not show update dialogs
  • Custom Endpoints: Support for custom XML/JSON version endpoints

New in v2.0.0

  • Release Notes Display: Show what's new in each update
  • Update Frequency Control: Only check for updates every X days
  • Silent/Background Checking: Check updates periodically without blocking UI
  • Force Update by Minimum Version: Define minimum required version to force updates
  • Analytics/Callbacks: Track update impressions, dismissals, and completions
  • TestFlight Support: Support beta updates via TestFlight on iOS
  • GitHub Releases Support: Check for updates from GitHub releases
  • Firebase Remote Config: Integrate with Firebase for remote version control
  • Localization/i18n: Built-in translations for dialog text
  • Update Urgency Levels: Critical, high, medium, low update types
  • Changelog Fetching: Auto-fetch and display changelogs from stores

Supported Platforms

Platform Store Dialog Style
iOS App Store Cupertino
Android Play Store Material Design 3
macOS Mac App Store Cupertino
Windows Microsoft Store Fluent Design
Linux Snap Store / Flathub Adwaita (GNOME)

Installation

Add app_updater to your pubspec.yaml:

dependencies:
  app_updater: ^2.0.0

Quick Start

import 'package:app_updater/app_updater.dart';

// Create an AppUpdater instance
final appUpdater = AppUpdater.configure(
  iosAppId: '123456789',
  macAppId: '987654321',
  microsoftProductId: '9NBLGGH4NNS1',
  snapName: 'my-app',
  flathubAppId: 'com.example.myapp',
);

// Check for updates and show dialog
final updateInfo = await appUpdater.checkAndShowUpdateDialog(context);

if (updateInfo.updateAvailable) {
  print('New version available: ${updateInfo.latestVersion}');
  print('Release notes: ${updateInfo.releaseNotes}');
}

Usage

Basic Usage

// Create AppUpdater with configuration
final appUpdater = AppUpdater.configure(
  iosAppId: '123456789',
  // androidPackageName is auto-detected!
);

// Check and show dialog if update available
await appUpdater.checkAndShowUpdateDialog(context);

All Platforms

final appUpdater = AppUpdater.configure(
  // Mobile
  iosAppId: '123456789',
  androidPackageName: 'com.example.app', // Optional - auto-detected
  // Desktop
  macAppId: '987654321',
  microsoftProductId: '9NBLGGH4NNS1',
  snapName: 'my-app',
  flathubAppId: 'com.example.myapp',
  linuxStoreType: LinuxStoreType.snap, // or LinuxStoreType.flathub
);

Dialog Styles

The package provides 5 dialog styles that match each platform's design language:

await appUpdater.showUpdateDialog(
  context,
  dialogStyle: UpdateDialogStyle.adaptive, // Auto-selects based on platform
);

// Or force a specific style:
// UpdateDialogStyle.material   - Material Design 3 (Android)
// UpdateDialogStyle.cupertino  - iOS/macOS native
// UpdateDialogStyle.fluent     - Windows Fluent Design
// UpdateDialogStyle.adwaita    - Linux GNOME/Adwaita

Persistent (Forced) Updates

For critical updates that users must install:

await appUpdater.checkAndShowUpdateDialog(
  context,
  isPersistent: true,  // Cannot be dismissed
  isDismissible: false,
  title: 'Critical Update Required',
  message: 'Please update to continue using the app.',
);

Skip Version & Do Not Ask Again

Let users control update notifications:

await appUpdater.checkAndShowUpdateDialog(
  context,
  showSkipVersion: true,    // Shows "Skip this version" option
  showDoNotAskAgain: true,  // Shows "Don't remind me again" option
);

Managing Preferences

// Check if user skipped a version
final isSkipped = await UpdatePreferences.isVersionSkipped('2.0.0');

// Check if user chose "do not ask again"
final doNotAsk = await UpdatePreferences.isDoNotAskAgain();

// Reset all preferences
await UpdatePreferences.clearAll();

// Reset only skipped version
await UpdatePreferences.clearSkippedVersion();

// Get analytics data
final impressions = await UpdatePreferences.getUpdateImpressions();
final dismissals = await UpdatePreferences.getUpdateDismissals();

New Features

Release Notes Display

Show what's new in the update dialog:

await appUpdater.checkAndShowUpdateDialog(
  context,
  showReleaseNotes: true, // Enabled by default
);

// Release notes are automatically fetched from:
// - App Store (iOS/macOS)
// - Flathub (Linux)
// - GitHub Releases
// - Custom JSON/XML endpoints

Update Frequency Control

Prevent checking for updates too frequently:

final appUpdater = AppUpdater.configure(
  iosAppId: '123456789',
  checkFrequency: Duration(days: 1), // Only check once per day
);

// Manual check respecting frequency
final updateInfo = await appUpdater.checkForUpdate();

// Force check ignoring frequency
final updateInfo = await appUpdater.checkForUpdate(respectFrequency: false);

Background/Silent Checking

Check for updates in the background without blocking the UI:

// Start periodic background checking
appUpdater.startBackgroundChecking(Duration(hours: 6));

// Listen for updates
appUpdater.updateStream.listen((updateInfo) {
  if (updateInfo.updateAvailable) {
    // Show notification or update badge
    showNotification('Update available: ${updateInfo.latestVersion}');
  }
});

// Stop background checking when done
appUpdater.stopBackgroundChecking();

// Don't forget to dispose
@override
void dispose() {
  appUpdater.dispose();
  super.dispose();
}

Force Update by Minimum Version

Force updates when users are below a minimum version:

final appUpdater = AppUpdater.configure(
  iosAppId: '123456789',
  minimumVersion: '2.0.0', // Users below this MUST update
);

// Or via custom JSON endpoint:
// { "version": "2.5.0", "minimumVersion": "2.0.0" }

// Check if force update is required
final updateInfo = await appUpdater.checkForUpdate();
if (updateInfo.requiresForceUpdate) {
  // Show non-dismissible dialog
  await appUpdater.showUpdateDialog(context, isPersistent: true);
}

Update Urgency Levels

Communicate update importance to users:

// Urgency levels: low, medium, high, critical
// Set via custom endpoints or Firebase Remote Config

// Custom JSON endpoint example:
// { "version": "2.5.0", "urgency": "critical" }

// The dialog icon and color change based on urgency
final updateInfo = await appUpdater.checkForUpdate();
print('Urgency: ${updateInfo.urgency}'); // UpdateUrgency.critical

Analytics & Callbacks

Track user interactions with update dialogs:

final appUpdater = AppUpdater.configure(
  iosAppId: '123456789',
  onAnalyticsEvent: (event) {
    // Send to your analytics service
    analytics.logEvent(
      name: event.eventName,
      parameters: event.toMap(),
    );
  },
);

// Available events:
// - update_dialog_shown
// - update_accepted
// - update_declined
// - update_version_skipped
// - update_do_not_ask_again
// - update_check_started
// - update_check_completed
// - update_check_failed
// - update_store_opened

GitHub Releases Support

Check for updates from GitHub releases:

final appUpdater = AppUpdater.configure(
  githubOwner: 'mycompany',
  githubRepo: 'myapp',
  githubIncludePrereleases: false, // Set to true for beta builds
);

// Automatically fetches:
// - Latest version from tag name
// - Release notes from body
// - Download URL from assets

TestFlight Support (iOS)

Support beta updates via TestFlight:

final appUpdater = AppUpdater.configure(
  iosAppId: '123456789',
  testFlightEnabled: true,
  testFlightUrl: 'https://testflight.apple.com/join/ABC123', // Optional custom URL
);

// Open TestFlight for beta testing
await appUpdater.openTestFlight();

Firebase Remote Config Integration

Use Firebase Remote Config for dynamic version control:

final appUpdater = AppUpdater.configure(
  firebaseRemoteConfigEnabled: true,
  firebaseSettings: FirebaseRemoteConfigSettings(
    minimumVersionKey: 'minimum_app_version',
    latestVersionKey: 'latest_app_version',
    updateUrlKey: 'app_update_url',
    releaseNotesKey: 'release_notes',
    urgencyKey: 'update_urgency',
    mandatoryKey: 'mandatory_update',
  ),
  firebaseConfigFetcher: () async {
    // Fetch from your Firebase Remote Config instance
    await FirebaseRemoteConfig.instance.fetchAndActivate();
    return {
      'minimum_app_version': FirebaseRemoteConfig.instance.getString('minimum_app_version'),
      'latest_app_version': FirebaseRemoteConfig.instance.getString('latest_app_version'),
      'app_update_url': FirebaseRemoteConfig.instance.getString('app_update_url'),
      'release_notes': FirebaseRemoteConfig.instance.getString('release_notes'),
      'update_urgency': FirebaseRemoteConfig.instance.getString('update_urgency'),
      'mandatory_update': FirebaseRemoteConfig.instance.getBool('mandatory_update'),
    };
  },
);

Localization / i18n

Provide translations for all dialog text:

// Create localized strings
final frenchStrings = UpdateStrings(
  updateAvailableTitle: 'Mise à jour disponible',
  updateAvailableMessage: 'Une nouvelle version ({latestVersion}) est disponible. Vous avez actuellement la version {currentVersion}.',
  updateButton: 'Mettre à jour',
  laterButton: 'Plus tard',
  skipVersionButton: 'Ignorer cette version',
  doNotAskAgainButton: 'Ne plus me rappeler',
  criticalUpdateTitle: 'Mise à jour critique requise',
  criticalUpdateMessage: 'Cette mise à jour contient des correctifs importants.',
  releaseNotesTitle: 'Nouveautés',
  loadingText: 'Vérification des mises à jour...',
  errorText: 'Impossible de vérifier les mises à jour',
  upToDateText: 'Votre application est à jour !',
);

final appUpdater = AppUpdater.configure(
  iosAppId: '123456789',
  strings: frenchStrings,
);

Custom Update Servers

Custom JSON Endpoint

Host your own version endpoint:

{
  "version": "2.0.0",
  "url": "https://example.com/download",
  "releaseNotes": "- New feature X\n- Bug fix Y",
  "minimumVersion": "1.5.0",
  "urgency": "high",
  "mandatory": false
}
final appUpdater = AppUpdater.configure(
  customJsonUrl: 'https://example.com/version.json',
);

Custom XML Endpoint

<?xml version="1.0" encoding="UTF-8"?>
<app>
  <version>2.0.0</version>
  <url>https://example.com/download</url>
  <releaseNotes>New features and improvements</releaseNotes>
  <minimumVersion>1.5.0</minimumVersion>
  <urgency>high</urgency>
  <mandatory>false</mandatory>
</app>
final appUpdater = AppUpdater.configure(
  customXmlUrl: 'https://example.com/version.xml',
);

Additional Methods

Check Without Dialog

final updateInfo = await appUpdater.checkForUpdate();

print('Current: ${updateInfo.currentVersion}');
print('Latest: ${updateInfo.latestVersion}');
print('Update available: ${updateInfo.updateAvailable}');
print('Store URL: ${updateInfo.updateUrl}');
print('Release notes: ${updateInfo.releaseNotes}');
print('Urgency: ${updateInfo.urgency}');
print('Is mandatory: ${updateInfo.isMandatory}');
print('Requires force update: ${updateInfo.requiresForceUpdate}');
print('Update size: ${updateInfo.formattedUpdateSize}');

Open Store

// Open the appropriate store for the current platform
await appUpdater.openStore();

// Or use the convenience function
await openAppStore(
  iosAppId: '123456789',
  microsoftProductId: '9NBLGGH4NNS1',
);

Callbacks

await appUpdater.checkAndShowUpdateDialog(
  context,
  onNoUpdate: () {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('App is up to date!')),
    );
  },
  onUpdate: () {
    print('User chose to update');
  },
  onCancel: () {
    print('User cancelled');
  },
);

Custom Dialog

await appUpdater.showUpdateDialog(
  context,
  customDialog: AlertDialog(
    title: const Text('Custom Update Dialog'),
    content: const Text('A new version is available!'),
    actions: [
      TextButton(
        onPressed: () => Navigator.pop(context),
        child: const Text('Later'),
      ),
      FilledButton(
        onPressed: () {
          Navigator.pop(context);
          appUpdater.openStore();
        },
        child: const Text('Update'),
      ),
    ],
  ),
);

API Reference

AppUpdater

Method Description
AppUpdater.configure(...) Create instance with individual parameters
AppUpdater(config) Create instance with AppUpdaterConfig
checkForUpdate() Check for updates, returns UpdateInfo
showUpdateDialog(context, ...) Show update dialog
checkAndShowUpdateDialog(context, ...) Check and show dialog if update available
openStore() Open the appropriate app store
openTestFlight() Open TestFlight (iOS only)
getStoreUrl() Get store URL for current platform
getTestFlightUrl() Get TestFlight URL (iOS only)
startBackgroundChecking(interval) Start periodic background checks
stopBackgroundChecking() Stop background checks
performBackgroundCheck() Perform single background check
dispose() Clean up resources

AppUpdaterConfig

Parameter Type Description
iosAppId String? iOS App Store app ID
macAppId String? macOS App Store app ID
androidPackageName String? Android package name (auto-detected)
microsoftProductId String? Microsoft Store product ID
snapName String? Snap Store package name
flathubAppId String? Flathub app ID
customXmlUrl String? Custom XML endpoint URL
customJsonUrl String? Custom JSON endpoint URL
linuxStoreType LinuxStoreType snap or flathub
githubOwner String? GitHub repository owner
githubRepo String? GitHub repository name
githubIncludePrereleases bool Include prereleases (default: false)
testFlightEnabled bool Enable TestFlight (default: false)
testFlightUrl String? Custom TestFlight URL
firebaseRemoteConfigEnabled bool Enable Firebase (default: false)
firebaseSettings FirebaseRemoteConfigSettings? Firebase config keys
firebaseConfigFetcher Function? Callback to fetch Firebase values
checkFrequency Duration? Minimum time between checks
minimumVersion String? Minimum required version
onAnalyticsEvent Function? Analytics callback
strings UpdateStrings Localized strings

UpdateDialogStyle

Style Platform Description
adaptive All Auto-selects based on platform
material Android Material Design 3
cupertino iOS/macOS Native Apple design
fluent Windows Microsoft Fluent Design
adwaita Linux GNOME/Adwaita style

UpdateUrgency

Level Description
low Minor improvements, can be skipped
medium Regular updates with new features
high Important updates, strongly recommended
critical Security fixes or breaking changes

UpdateInfo

Property Type Description
currentVersion String Current app version
latestVersion String? Latest available version
updateUrl String? Store/download URL
updateAvailable bool Whether update is available
releaseNotes String? Release notes/changelog
urgency UpdateUrgency Update urgency level
minimumVersion String? Minimum required version
isMandatory bool Whether update is mandatory
releaseDate DateTime? Release date
updateSizeBytes int? Update size in bytes
requiresForceUpdate bool Whether force update is required
formattedUpdateSize String? Human-readable size (e.g., "15.2 MB")

UpdatePreferences

Method Description
isVersionSkipped(version) Check if version is skipped
skipVersion(version) Skip a specific version
isDoNotAskAgain() Check "do not ask again" preference
setDoNotAskAgain(value) Set "do not ask again" preference
getLastCheckTime() Get last update check time
setLastCheckTime(time) Set last update check time
shouldCheckForUpdate(frequency) Check if enough time has passed
getUpdateImpressions() Get dialog impression count
getUpdateDismissals() Get dismissal count
clearAll() Clear all preferences
clearSkippedVersion() Clear skipped version only

UpdateAnalyticsEvent

Property Type Description
eventName String Event name
currentVersion String Current app version
latestVersion String? Latest available version
urgency UpdateUrgency? Update urgency
platform String Platform name
timestamp DateTime Event timestamp
toMap() Map Convert to map for analytics

UpdateStrings

Property Type Description
updateAvailableTitle String Dialog title
updateAvailableMessage String Dialog message (supports placeholders)
updateButton String Update button text
laterButton String Later button text
skipVersionButton String Skip version button text
doNotAskAgainButton String Do not ask again button text
criticalUpdateTitle String Critical update title
criticalUpdateMessage String Critical update message
releaseNotesTitle String Release notes section title
loadingText String Loading text
errorText String Error text
upToDateText String Up to date text

Notes

  • Update dialogs won't work on iOS Simulator (no App Store)
  • Android package name is auto-detected if not provided
  • Custom endpoints take priority over store checks
  • Firebase Remote Config takes highest priority when enabled
  • GitHub releases are checked before platform stores
  • Release notes are automatically fetched from supported sources
  • Background checking requires calling dispose() when done

Credits

Author

Libraries

app_updater