App Hub Upgrader
A Flutter package for checking app version updates from an API and displaying a customizable update dialog to users.
Table of Contents
- Features
- Installation
- Usage
- API Response Format
- API Configuration
- UpdateDialog Widget
- Models
- Error Handling
- Forced Updates
- Platform Support
- Versioning
- Example
- License
- Contributing
Features
- ✅ Check for app updates from API
- ✅ Automatic version detection using
package_info_plus - ✅ Customizable update dialog with changelog display
- ✅ Support for forced updates
- ✅ Platform-specific version checking (Android/iOS)
- ✅ Easy to integrate and use
Installation
From pub.dev (Recommended)
Add this to your package's pubspec.yaml file:
dependencies:
app_hub_upgrader: ^1.0.0
Then run:
flutter pub get
Note: When using git, make sure to specify a tag (e.g., v1.0.0) for stable versions, or use a branch name for development versions.
Usage
Quick Start
- Initialize the package in your
main.dart(optional - for .env support):
import 'package:flutter/material.dart';
import 'package:app_hub_upgrader/app_hub_upgrader.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize to load .env file (optional - will use defaults if not called)
await AppHubUpgrader.initialize();
runApp(MyApp());
}
- Use AppHubUpgrader in your app:
final upgrader = AppHubUpgrader(
appID: 'your-app-id', // Replace with your actual app ID
context: context,
useProduction: true, // Set to false for development API
);
// Check for updates
await upgrader.checkForUpdate();
That's it! The package will automatically check for updates and show a dialog if a new version is available.
Note: If you don't call initialize() or don't have a .env file, the package will use default API URLs.
Step-by-Step Integration
Step 1: Add to Your App's Entry Point
The most common approach is to check for updates when your app starts. Here's a complete example:
import 'package:app_hub_upgrader/app_hub_upgrader.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late AppHubUpgrader _upgrader;
@override
void initState() {
super.initState();
// Initialize the upgrader
_upgrader = AppHubUpgrader(
appID: 'your-app-id', // Get this from your backend/API
context: context,
useProduction: true,
);
// Check for updates after the first frame is rendered
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkForUpdates();
});
}
Future<void> _checkForUpdates() async {
try {
await _upgrader.checkForUpdate();
} catch (e) {
// Handle errors silently or show a message
debugPrint('Update check failed: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('My App')),
body: const Center(
child: Text('Welcome to My App'),
),
);
}
}
Step 2: Customize the Update Dialog (Optional)
You can customize the appearance and behavior of the update dialog:
final upgrader = AppHubUpgrader(
appID: 'your-app-id',
context: context,
useProduction: true,
dialogConfig: UpdateDialogConfig(
// Customize dialog text
title: 'New Version Available!',
updateButtonText: 'Update Now',
laterButtonText: 'Maybe Later',
// Customize colors
primaryColor: Colors.blue,
backgroundColor: Colors.white,
// Customize icon
customIcon: const Icon(
Icons.cloud_download,
size: 64,
color: Colors.blue,
),
// Add callbacks
onUpdate: () {
debugPrint('User clicked update button');
// You can add analytics tracking here
},
onLater: () {
debugPrint('User clicked later button');
// You can add analytics tracking here
},
),
);
await upgrader.checkForUpdate();
Common Use Cases
Use Case 1: Check on App Launch (Recommended)
Check for updates when the app starts:
@override
void initState() {
super.initState();
final upgrader = AppHubUpgrader(
appID: 'your-app-id',
context: context,
);
WidgetsBinding.instance.addPostFrameCallback((_) {
upgrader.checkForUpdate();
});
}
Use Case 2: Check on Button Tap
Allow users to manually check for updates:
ElevatedButton(
onPressed: () async {
final upgrader = AppHubUpgrader(
appID: 'your-app-id',
context: context,
);
await upgrader.checkForUpdate();
},
child: const Text('Check for Updates'),
)
Use Case 3: Check Periodically
Check for updates at regular intervals:
Timer.periodic(const Duration(hours: 24), (timer) async {
final upgrader = AppHubUpgrader(
appID: 'your-app-id',
context: context,
);
await upgrader.checkForUpdate();
});
Use Case 4: Check Without Auto-Dialog
Check for updates and handle the result manually:
final upgrader = AppHubUpgrader(
appID: 'your-app-id',
useProduction: true,
);
// Check without showing dialog automatically
final updateInfo = await upgrader.checkForUpdate(
context: context,
checkOnly: true, // Don't show dialog automatically
);
if (updateInfo?.hasUpdate == true) {
// Custom logic based on update info
if (updateInfo!.isForcedUpdate) {
// Show custom forced update UI
await upgrader.showUpdateDialog(context, updateInfo);
} else {
// Show custom optional update UI
_showCustomUpdateNotification(updateInfo);
}
} else {
// No update available
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('You are using the latest version!')),
);
}
Use Case 5: Manual Version and Platform Specification
Override automatic detection:
final updateInfo = await upgrader.checkForUpdate(
context: context,
currentVersion: '1.0.0', // Override auto-detected version
platform: 'android', // Override auto-detected platform
);
Advanced Configuration
Using Development API
Switch to development API for testing:
final upgrader = AppHubUpgrader(
appID: 'your-app-id',
context: context,
useProduction: false, // Use development API
);
Error Handling
Handle errors gracefully:
try {
await upgrader.checkForUpdate();
} on AppException catch (e) {
// Handle API errors
debugPrint('Update check failed: ${e.message} (${e.statusCode})');
if (e.statusCode == 404) {
// No update endpoint found
} else if (e.statusCode >= 500) {
// Server error
}
} catch (e) {
// Handle other errors
debugPrint('Unexpected error: $e');
}
Using UpdateDialog Independently
You can use the UpdateDialog widget directly if you have your own update checking logic:
// After getting UpdateInfo from your own API call
showDialog(
context: context,
barrierDismissible: !updateInfo.isForcedUpdate,
builder: (context) => UpdateDialog(
updateInfo: updateInfo,
title: 'Update Required',
updateButtonText: 'Update Now',
laterButtonText: 'Later',
primaryColor: Colors.blue,
),
);
API Response Format
The package expects the API to return a response in the following format:
{
"status_code": 200,
"is_success": true,
"message": "Success",
"data": {
"has_update": true,
"latest_version": "1.2.0",
"current_version": "1.1.0",
"is_forced_update": false,
"min_supported_version": "1.0.0",
"download_url": "https://play.google.com/store/apps/details?id=com.example.app",
"changelog": {
"fixes": [
"Fixed bug in login screen",
"Improved performance",
"Added new features"
]
},
"release_date": "2024-01-15"
}
}
The package will automatically fall back to these defaults if:
.envfile is not foundAppHubUpgrader.initialize()is not called- Environment variables are not set
Alternative: Modify api_config.dart
You can also modify the default URLs directly in lib/config/api_config.dart if you prefer not to use .env files.
API Parameters
The package sends the following query parameters to the API:
app_id: Your application IDcurrent_version: Current app version (auto-detected or manually provided)platform: Platform name (androidorios, auto-detected)
UpdateDialog Widget
The UpdateDialog widget can be used independently if needed. The isForcedUpdate property is automatically read from updateInfo.isForcedUpdate:
showDialog(
context: context,
barrierDismissible: !updateInfo.isForcedUpdate, // Prevent dismissal for forced updates
builder: (context) => UpdateDialog(
updateInfo: updateInfo, // isForcedUpdate is read from here
title: 'Update Required',
updateButtonText: 'Update Now',
laterButtonText: 'Later',
primaryColor: Colors.blue,
),
);
UpdateDialogConfig Properties
| Property | Type | Description |
|---|---|---|
title |
String? |
Custom title for the dialog |
updateButtonText |
String? |
Text for the update button |
laterButtonText |
String? |
Text for the later button (hidden if forced update) |
onUpdate |
VoidCallback? |
Callback when update button is clicked |
onLater |
VoidCallback? |
Callback when later button is clicked |
primaryColor |
Color? |
Primary color for buttons and icon |
backgroundColor |
Color? |
Background color of the dialog |
titleStyle |
TextStyle? |
Custom style for the title |
contentStyle |
TextStyle? |
Custom style for content text |
customIcon |
Widget? |
Custom icon widget |
Models
UpdateInfo
Contains information about available updates:
class UpdateInfo {
final bool hasUpdate;
final String? latestVersion;
final String? currentVersion;
final bool isForcedUpdate;
final String? minSupportedVersion;
final String? downloadUrl;
final Changelog? changelog;
final String? releaseDate;
}
Changelog
Contains changelog information:
class Changelog {
final List<String> fixes;
}
Error Handling
The package throws AppException when API calls fail. You can catch and handle these:
try {
await upgrader.checkForUpdate();
} on AppException catch (e) {
print('Error: ${e.message} (Status: ${e.statusCode})');
} catch (e) {
print('Unexpected error: $e');
}
Forced Updates
When isForcedUpdate is true:
- The dialog cannot be dismissed by tapping outside
- The "Later" button is hidden
- Users must update to continue using the app
Platform Support
- ✅ Android
- ✅ iOS
Dependencies
This package depends on the following:
http: ^1.6.0- For making API requestspackage_info_plus: ^9.0.0- For getting app version informationurl_launcher: ^6.3.1- For opening download URLs
All dependencies are automatically installed when you add this package to your project.
Example
See the example folder for a complete working example.
Versioning
This package follows Semantic Versioning. The current version is 1.0.0.
For version history, see CHANGELOG.md.
License
See the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Related Documentation
- Git Versioning Guide - How to version the package with git tags
- Quick Tag Guide - Quick reference for creating release tags