flutter_chen_updater 1.2.0
flutter_chen_updater: ^1.2.0 copied to clipboard
A comprehensive Flutter app update library supporting background downloads and iOS App Store redirects.
Flutter Chen Updater #
A powerful Flutter in-app update plugin that supports Android APK automatic download, installation, and iOS App Store redirection.
✨ Features #
- ✅ Cross-platform Support: Android 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 #
📦 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',
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);
}
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 #
void checkVersionUpdate() {
final currentVersion = '1.0.0';
final newVersion = '1.1.0';
final needUpdate = Updater.needUpdate(currentVersion, newVersion);
print('Need update: $needUpdate');
}
📚 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? 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 applicationdownload()
- Pure download method (no auto-installation)installApk()
- Install APK fileneedUpdate()
- Version comparisoncancelDownload()
- Cancel downloaddispose()
- 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:
- Storage permission (Android 9 and below)
- Installation permission (Android 8+)
- 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 #
- Version Check: Recommend checking for updates on app startup
- Network Check: Check network status before checking for updates
- User Experience: Avoid showing update prompts during critical operations
- Resource Cleanup: Call
Updater.dispose()
when app exits - 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.
⚠️ Important Notes #
-
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
-
File Security:
- Recommend using HTTPS download links
- Strongly recommend setting fileHash for file integrity verification
- Downloaded APK files will be automatically validated
-
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