flutter_chen_updater 2.1.0 copy "flutter_chen_updater: ^2.1.0" to clipboard
flutter_chen_updater: ^2.1.0 copied to clipboard

A comprehensive Flutter app update library supporting background downloads and iOS App Store redirects.

Flutter Chen Updater #

Language: English | 中文

A powerful Flutter in-app update plugin that supports Android app store redirection, APK automatic download and installation, plus iOS App Store redirection.

✨ Features #

  • Cross-platform Support: Android app store redirection / APK auto-update, iOS App Store redirection
  • Smart Download: Support for resumable downloads, file verification, multiple fallback options
  • Permission Management: Automatic handling of Android installation permissions and storage permissions
  • Force Update: Support for optional and mandatory update modes
  • Progress Monitoring: Real-time download progress callbacks
  • File Verification: MD5 file integrity validation
  • Lifecycle Management: Smart handling of app foreground/background switching
  • Custom UI: Support for custom update dialogs

Preview #

Preview

📦 Installation #

Add the dependency to your pubspec.yaml file:

dependencies:
  flutter_chen_updater: ^1.0.0

Then run:

flutter pub get

⚙️ Permission Configuration #

Android #

Add necessary permissions in android/app/src/main/AndroidManifest.xml:

<!-- Internet permission -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Storage permissions (Android 9 and below) -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" 
    android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" 
    android:maxSdkVersion="28" />

<!-- Install permission -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

<!-- Network state permission -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

🚀 Basic Usage #

1. Simple Update Check #

import 'package:flutter_chen_updater/flutter_chen_updater.dart';

void checkForUpdate() {
  final updateInfo = UpdateInfo(
    version: '1.1.0',
    downloadUrl: 'https://example.com/app-v1.1.0.apk',
    androidMarketUrl: 'market://details?id=com.example.app',
    iosUrl: 'https://apps.apple.com/app/id123456789',
    description: '1. Fixed known issues\n2. Improved user experience\n3. Added new features',
    isForceUpdate: false,
  );

  Updater.checkAndUpdate(
    context,
    updateInfo,
    onAlreadyLatest: () => print('Already latest version'),
    onConfirm: () => print('User confirmed update'),
    onCancel: () => print('User cancelled update'),
  );
}

2. Update with Progress Monitoring #

void checkForUpdateWithProgress() {
  final updateInfo = UpdateInfo(
    version: '1.1.0',
    downloadUrl: 'https://example.com/app-v1.1.0.apk',
    description: 'Update description',
    fileHash: 'abc123def456', // Optional: MD5 file verification
    hashAlgorithm: 'md5',
    fileSize: 15 * 1024 * 1024, // 15MB
  );

  Updater.checkAndUpdate(
    context,
    updateInfo,
    onProgress: (progress) {
      print('Download progress: ${(progress.progress * 100).toStringAsFixed(1)}%');
      print('Downloaded: ${progress.downloaded} / ${progress.total}');
    },
    onConfirm: () => print('Started download update'),
  );
}

3. Force Update Example #

void forceUpdate() {
  final updateInfo = UpdateInfo(
    version: '2.1.0',
    description: 'Important security update, please upgrade immediately',
    downloadUrl: 'https://example.com/app-v2.1.0.apk',
    isForceUpdate: true, // Force update, user cannot cancel
  );

  Updater.checkAndUpdate(context, updateInfo);
}

3.1 Prefer Android App Store Update #

If androidMarketUrl is provided, the plugin opens the Android app store directly. If the store link cannot be opened, the update is treated as failed. If you want APK installation, provide only downloadUrl.

void updateWithAndroidMarket() {
  final updateInfo = UpdateInfo(
    version: '2.1.0',
    androidMarketUrl: 'market://details?id=com.example.app',
    description: 'Open the Android app store directly',
  );

  Updater.checkAndUpdate(context, updateInfo);
}

4. Custom Dialog #

4.1 Using dialogBuilder for Customization

Future<bool> customDialog(BuildContext context, UpdateInfo updateInfo) {
  return showDialog<bool>(
    context: context,
    barrierDismissible: !updateInfo.isForceUpdate,
    builder: (context) => AlertDialog(
      title: Text('New Version ${updateInfo.version} Available'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(updateInfo.description),
          if (updateInfo.fileSize != null)
            Padding(
              padding: const EdgeInsets.only(top: 8.0),
              child: Text('Size: ${(updateInfo.fileSize! / 1024 / 1024).toStringAsFixed(1)}MB'),
            ),
        ],
      ),
      actions: [
        if (!updateInfo.isForceUpdate)
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: const Text('Later'),
          ),
        ElevatedButton(
          onPressed: () => Navigator.pop(context, true),
          child: const Text('Update Now'),
        ),
      ],
    ),
  ) ?? false;
}

void checkWithCustomDialog() {
  final updateInfo = UpdateInfo(
    version: '1.1.0',
    downloadUrl: 'https://example.com/app.apk',
    description: 'Important update description',
  );

  Updater.checkAndUpdate(
    context,
    updateInfo,
    dialogBuilder: customDialog,
  );
}

4.2 Using UpdateDialog for Flexible Configuration

void checkWithFlexibleDialog() {
  final updateInfo = UpdateInfo(
    version: '1.2.0',
    downloadUrl: 'https://example.com/app.apk',
    description: '• Fixed important security vulnerabilities\n• Optimized startup speed\n• Added night mode',
    fileSize: 25 * 1024 * 1024, // 25MB
  );

  showDialog<bool>(
    context: context,
    barrierDismissible: !updateInfo.isForceUpdate,
    builder: (context) => UpdateDialog(
      updateInfo: updateInfo,
      // Custom title
      title: Container(
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.blue, Colors.purple],
          ),
        ),
        child: Row(
          children: [
            Icon(Icons.system_update, color: Colors.white),
            const SizedBox(width: 8),
            Text(
              'Important Update V${updateInfo.version}',
              style: const TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
      ),
      // Custom content
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Update Content',
                  style: Theme.of(context).textTheme.titleMedium?.copyWith(
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 8),
                Text(updateInfo.description),
                const SizedBox(height: 12),
                if (updateInfo.fileSize != null)
                  Container(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 12,
                      vertical: 6,
                    ),
                    decoration: BoxDecoration(
                      color: Colors.grey.shade100,
                      borderRadius: BorderRadius.circular(16),
                    ),
                    child: Text(
                      'Package size: ${(updateInfo.fileSize! / 1024 / 1024).toStringAsFixed(1)}MB',
                      style: Theme.of(context).textTheme.bodySmall,
                    ),
                  ),
              ],
            ),
          ),
        ],
      ),
      // Custom footer
      footer: Container(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            if (!updateInfo.isForceUpdate) ...[
              Expanded(
                child: OutlinedButton(
                  onPressed: () => Navigator.pop(context, false),
                  child: const Text('Remind Later'),
                ),
              ),
              const SizedBox(width: 12),
            ],
            Expanded(
              flex: updateInfo.isForceUpdate ? 1 : 1,
              child: ElevatedButton.icon(
                onPressed: () => Navigator.pop(context, true),
                icon: const Icon(Icons.download),
                label: const Text('Update Now'),
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

5. Download Only Function #

void downloadOnly() {
  final updateInfo = UpdateInfo(
    version: '1.1.0',
    downloadUrl: 'https://example.com/app.apk',
    description: 'Update package',
  );

  Updater.download(updateInfo).listen((progress) {
    if (progress.isCompleted && progress.filePath != null) {
      print('Download completed: ${progress.filePath}');
      // Install later
      Updater.installApk(
        progress.filePath!,
        onSuccess: () => print('Installation successful'),
        onError: (error) => print('Installation failed: $error'),
      );
    } else if (progress.isFailed) {
      print('Download failed: ${progress.error}');
    }
  });
}

6. Version Comparison #

The plugin supports comparing both version numbers and build numbers (Flutter standard format):

void checkVersionUpdate() {
  // Pure version number comparison
  final needUpdate1 = Updater.needUpdate('1.0.0', '1.1.0');
  print('Need update: $needUpdate1'); // true

  // Version with build number comparison
  final needUpdate2 = Updater.needUpdate('1.0.0+1', '1.0.0+2');
  print('Need update: $needUpdate2'); // true (same version, build number increased)

  // Mixed comparison (one has build number, one doesn't)
  final needUpdate3 = Updater.needUpdate('1.0.0+100', '1.0.1');
  print('Need update: $needUpdate3'); // true (version number increased)
}

Version Comparison Rules:

  1. Version number takes priority (e.g., 1.2.3)
  2. If version numbers are the same, compare build numbers
  3. Format: version+buildNumber (e.g., 1.0.0+100)
  4. The plugin automatically retrieves the current app's version+buildNumber from native code

📚 API Documentation #

UpdateInfo Class #

Update information configuration class:

class UpdateInfo {
  final String version;           // New version number (required)
  final String? downloadUrl;      // Android APK download link
  final String? androidMarketUrl; // Android app store link (optional)
  final String? iosUrl;          // iOS App Store link
  final String description;       // Update description (required)
  final bool isForceUpdate;      // Whether to force update
  final String? fileHash;        // File hash value (optional)
  final String? hashAlgorithm;   // Hash algorithm (default: 'md5')
  final int? fileSize;           // File size (bytes)
  final Map<String, dynamic>? extra; // Extra data
}

Updater Class #

Main updater class:

Static Methods

  • checkAndUpdate() - Check and update application
  • download() - Pure download method (no auto-installation)
  • installApk() - Install APK file
  • needUpdate() - Version comparison
  • cancelDownload() - Cancel download
  • dispose() - Clean up resources

DownloadProgress Class #

Download progress information:

class DownloadProgress {
  final int downloaded;          // Downloaded bytes
  final int total;              // Total bytes
  final double progress;        // Progress (0.0 - 1.0)
  final DownloadStatus status;  // Download status
  final String? error;          // Error message
  final String? filePath;       // File path
}

DownloadStatus Enum #

enum DownloadStatus {
  idle,         // Idle
  downloading,  // Downloading
  paused,       // Paused
  completed,    // Completed
  failed,       // Failed
  cancelled,    // Cancelled
  installing    // Installing
}

🔧 Advanced Features #

File Verification #

The plugin supports MD5 file integrity verification:

final updateInfo = UpdateInfo(
  version: '1.1.0',
  downloadUrl: 'https://example.com/app.apk',
  description: 'Security update',
  fileHash: 'a1b2c3d4e5f6...',
  hashAlgorithm: 'md5',
);

Permission Handling #

The plugin automatically handles the following permissions:

  1. Storage permission (Android 9 and below)
  2. Installation permission (Android 8+)
  3. Network permission

For missing permissions, the plugin will:

  • Automatically request permissions
  • Guide users to system settings page
  • Monitor app lifecycle and auto-retry

Lifecycle Management #

The plugin intelligently handles app foreground/background switching:

  • Auto-retry installation when user returns from permission settings
  • Keep background download tasks active
  • Auto-cleanup resources when app exits

💡 Error Handling #

The plugin provides comprehensive error handling mechanisms:

Updater.checkAndUpdate(
  context,
  updateInfo,
  onProgress: (progress) {
    if (progress.isFailed) {
      // Handle download failure
      showDialog(
        context: context,
        builder: (_) => AlertDialog(
          title: const Text('Download Failed'),
          content: Text(progress.error ?? 'Unknown error'),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('OK'),
            ),
          ],
        ),
      );
    }
  },
);

🚦 Best Practices #

  1. Version Check: Recommend checking for updates on app startup
  2. Network Check: Check network status before checking for updates
  3. User Experience: Avoid showing update prompts during critical operations
  4. Resource Cleanup: Call Updater.dispose() when app exits
  5. Error Logging: Log error information during update process for debugging

❓ FAQ #

Q: What to do if Android installation fails? #

A: Check if "Install unknown apps" permission is granted. The plugin will automatically guide users to settings.

Q: What to do if download speed is slow? #

A: The plugin uses Android system download manager and HTTP fallback options to ensure optimal download experience.

Q: How to support incremental updates? #

A: Current version doesn't support incremental updates. Recommend using file verification to ensure integrity.

Q: How does iOS implement automatic updates? #

A: iOS can only redirect to App Store due to system restrictions, cannot implement APK-style automatic updates.

Q: If androidMarketUrl is configured but invalid, will the plugin fall back automatically? #

A: No. The plugin will treat the update as failed. If you want APK installation, configure downloadUrl alone instead of mixing both modes.

⚠️ Important Notes #

  1. Android Permissions:

    • Android 6.0+ requires runtime storage permission requests
    • Android 8.0+ requires permission to install from unknown sources
    • Android 10+ uses scoped storage, no storage permission needed
  2. File Security:

    • Recommend using HTTPS download links
    • Strongly recommend setting fileHash for file integrity verification
    • Downloaded APK files will be automatically validated
  3. User Experience:

    • Avoid showing update prompts during important user operations
    • Use force updates cautiously, only for security updates
    • Provide clear update descriptions and file size information

📄 License #

MIT License

3
likes
150
points
163
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A comprehensive Flutter app update library supporting background downloads and iOS App Store redirects.

Repository (GitHub)
View/report issues

Topics

#updater

License

MIT (license)

Dependencies

convert, crypto, flutter, path_provider, permission_handler, url_launcher

More

Packages that depend on flutter_chen_updater

Packages that implement flutter_chen_updater