iws_version_manager 2.0.0
iws_version_manager: ^2.0.0 copied to clipboard
IWS Version Manager makes it easy to notify new app versions and block versions that are no longer supported.
IWS Version Manager #
IWS Version Manager makes it easy to notify users about new app versions, manage version compatibility, and block versions that are no longer supported. This package provides comprehensive version management with automatic notifications, device tracking, and flexible UI components.
Features #
- Automatic Version Checking: Automatically check for new versions on app startup
- Multiple Notification Types: Support for mandatory and optional updates
- Rich UI Components: Pre-built widgets for different notification styles
- Release Notes: Display formatted release notes with markdown support
- Device Tracking: Track device usage and user analytics
- Platform Support: Cross-platform support with web-specific features
- Custom Release Note Rendering: Customizable release note display
- Connection Error Handling: Graceful handling of network issues
- BLoC State Management: Built-in state management for version status
Getting Started #
1. Environment Setup #
Add the IWS API key and Version Code as DART variables at compile time:
flutter run --dart-define=IWS_API_KEY=your_api_key_here --dart-define=APP_VERSION_CODE=1
Or add them to your launch.json for VS Code:
{
"configurations": [
{
"name": "Flutter",
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
"args": [
"--dart-define=IWS_API_KEY=your_api_key_here",
"--dart-define=APP_VERSION_CODE=1"
]
}
]
}
2. Initialize Version Manager #
Initialize the version checking in your app startup:
void main() {
runApp(MyApp());
// Initialize version manager
if (!IwsVersionManager().widgetsInitialized()) {
IwsVersionManager(
deviceId: 'unique-device-id',
userIdentifier: 'user-identifier'
).initializeWidgets();
}
}
3. Configure Notification & Platform Behavior (Optional) #
You can customize on which platforms version notices appear and how notices behave when there is no download URL:
IwsVersionManager(
// Filter which platforms show version notifications/dialogs (null = all platforms enabled)
enabledNotificationPlatforms: {
PlatformName.android,
PlatformName.ios,
},
// Behavior when there is no download URL (never by default)
noDownloadUrlNoticeMode: NoDownloadUrlNoticeMode.never,
).initializeWidgets();
Available Notice Modes (NoDownloadUrlNoticeMode):
NoDownloadUrlNoticeMode.never: (Default) Does not show version banners or dialogs when no download URL is available.NoDownloadUrlNoticeMode.mandatoryOnly: Shows version notices without a download URL only when the update is mandatory (mandatoryUpdate: true).NoDownloadUrlNoticeMode.always: Always shows version notices even when no download URL is available.
Platform Filtering (enabledNotificationPlatforms):
Pass a Set<PlatformName> to specify the allowed platforms (e.g. {PlatformName.android, PlatformName.ios}). If the app runs on a platform not included in the set, version widgets and notifications will not be displayed.
4. Configure Web Server Cache Headers (Required for Web) #
For Flutter Web apps to properly update, you should configure your web server to avoid caching the app shell and bootstrap entry points too aggressively. Flutter's official FAQ explains that browsers and CDNs can keep serving stale files after deployment, and recommends cache-busting or filename changes for static resources.
Official reference: Why doesn't my app update immediately after it's deployed?
These files change with every build and should be fetched fresh:
| File | Cache Header |
|---|---|
index.html |
Cache-Control: no-cache, no-store, must-revalidate |
flutter_service_worker.js |
Cache-Control: no-cache, no-store, must-revalidate |
flutter.js or flutter_bootstrap.js |
Cache-Control: no-cache, no-store, must-revalidate |
Firebase Hosting
Add a firebase.json configuration file to your project root:
{
"hosting": {
"public": "build/web",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"headers": [
{
"source": "index.html",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache, no-store, must-revalidate"
}
]
},
{
"source": "flutter_service_worker.js",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache, no-store, must-revalidate"
}
]
},
{
"source": "flutter.js",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache, no-store, must-revalidate"
}
]
}
]
}
}
Shared Hosting (Apache - .htaccess)
Create or modify the .htaccess file in your build/web directory:
<Files "index.html">
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "0"
</Files>
<Files "flutter_service_worker.js">
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "0"
</Files>
<Files "flutter.js">
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "0"
</Files>
Shared Hosting (Nginx - nginx.conf)
Configure your server block in nginx.conf:
server {
listen 80;
server_name your-domain.com;
root /var/www/build/web;
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
}
location = /flutter_service_worker.js {
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
}
location = /flutter.js {
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
}
}
Other Hosting Providers
For other hosting providers, check their documentation for setting custom HTTP headers. Common ways include:
- Control panel/Dashboard headers configuration
- Custom headers in deployment configuration files
- .htaccess or web.config files
- Hosting provider's API for header management
⚠️ Warning: Implications of No-Cache Headers
Setting
no-cache, no-store, must-revalidateheaders on these files has important implications:
Bandwidth Impact: These files will be downloaded on every page load, even for users who recently visited. This increases bandwidth usage compared to cached versions.
Slower Load Times: Users may experience slightly slower page loads if they don't have a local browser cache, as the files must be fetched from the server every time.
Server Load: Increased server requests may result in higher server load, especially with high traffic volumes.
Browser Storage: The
must-revalidatedirective ensures that even expired cached versions are revalidated with the server, preventing stale versions from being served.User Bandwidth: Users with metered connections (mobile data) may experience higher data usage.
These tradeoffs are necessary for proper update functionality. Without these headers, users may not receive critical updates and could experience inconsistent behavior with different versions of your app running simultaneously. The benefits of reliable updates outweigh the performance considerations in most cases.
Optimization Tip: You can cache other assets normally when they are versioned or fingerprinted by your build process. The main goal is to avoid stale app-shell files and to use cache-busting for resources that must change immediately after deployment.
Usage Examples #
1. Automatic Scaffold Messages (Modern Responsive Notifications) #
The easiest way to show version notifications is using IwsVersionScaffoldMessage. It provides a modern, responsive, zero-dependency notification overlay that automatically adapts to Mobile (Android, iOS) and Desktop/Web (Windows, macOS, Linux, Web):
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return IwsVersionScaffoldMessage(
showConnectionErrors: true, // Show connection error notifications
notificationStyle: IwsNotificationStyle.adaptive, // adaptive, floatingToast, snackBar, dialog, banner
position: IwsNotificationPosition.adaptive, // adaptive (topRight on Desktop/Web, top on Mobile)
autoDismissDuration: const Duration(seconds: 8),
child: Scaffold(
appBar: AppBar(title: Text('My App')),
body: YourContent(),
),
);
}
}
Notification Styles (IwsNotificationStyle):
IwsNotificationStyle.adaptive: (Default & Recommended) Renders a floating responsive card in the top-right corner on Web/Desktop, a top floating card on Mobile, and a blocking modal dialog for mandatory updates.IwsNotificationStyle.floatingToast: Floating animated card with auto-dismiss and swipe-to-dismiss support.IwsNotificationStyle.snackBar: Uses standard Material 3ScaffoldMessenger.of(context).showSnackBar.IwsNotificationStyle.dialog: Shows anAlertDialogfor all version updates.IwsNotificationStyle.banner: In-app top banner bar.
Theme Customization (IwsNotificationTheme):
You can customize colors, borders, typography, and button labels:
IwsVersionScaffoldMessage(
theme: const IwsNotificationTheme(
downloadButtonText: 'ACTUALIZAR AHORA',
releaseNotesButtonText: 'Ver cambios',
elevation: 6.0,
borderRadius: BorderRadius.all(Radius.circular(16.0)),
),
child: Scaffold(...),
);
2. Version Icon Button #
Add a version status icon to your app bar:
class MyAppBar extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
return AppBar(
title: Text('My App'),
actions: [
IwsVersionIconButton(
showConnectionError: true, // Show icon for connection errors
),
],
);
}
@override
Size get preferredSize => Size.fromHeight(kToolbarHeight);
}
3. Custom Version Listener #
Handle version events with custom logic:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return IwsVersionListener(
onUpdateAvailable: (lastVersion, downloadLink) {
// Handle optional update
print('Update available: ${lastVersion?.versionName}');
// Show custom UI or trigger custom logic
},
onMandatoryUpdate: (lastVersion, downloadLink) {
// Handle mandatory update
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text('Update Required'),
content: Text('You must update to continue using the app.'),
actions: [
TextButton(
onPressed: () => _launchUpdate(downloadLink),
child: Text('Update Now'),
),
],
),
);
},
onConnectionError: () {
// Handle connection errors
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Connection error')),
);
},
onPlatformNotSupported: () {
// Handle unsupported platform
print('Platform not supported');
},
child: YourAppContent(),
);
}
void _launchUpdate(String? downloadLink) {
if (downloadLink != null) {
// Launch download URL
}
}
}
4. Custom Version Builder #
Build different UI based on version status:
class VersionAwareWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return IwsVersionBuilder(
updateAvailable: (lastVersion) {
return Card(
color: Colors.green.shade100,
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
Icon(Icons.new_releases, color: Colors.green),
Text('Version ${lastVersion?.versionName} available!'),
ElevatedButton(
onPressed: () => _downloadUpdate(),
child: Text('Download'),
),
],
),
),
);
},
mandatoryUpdate: (lastVersion) {
return Container(
color: Colors.orange.shade100,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.warning, size: 64, color: Colors.orange),
Text('Mandatory Update Required'),
Text('Version: ${lastVersion?.versionName}'),
ElevatedButton(
onPressed: () => _downloadUpdate(),
child: Text('Update Now'),
),
],
),
),
);
},
connectionError: () {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.wifi_off, size: 64, color: Colors.red),
Text('Connection Error'),
ElevatedButton(
onPressed: () => _retryConnection(),
child: Text('Retry'),
),
],
),
);
},
platformNotSupported: () {
return Center(
child: Text('Platform not supported'),
);
},
child: YourNormalContent(),
);
}
void _downloadUpdate() {
// Handle download logic
}
void _retryConnection() {
IwsVersionManager().versionBloc.add(CheckAppVersion());
}
}
5. Manual Version Checking #
Check for versions programmatically:
class VersionService {
static Future<void> checkForUpdates() async {
try {
final versionStatus = await IwsVersionManager(
deviceId: 'device-id',
userIdentifier: 'user-id',
).checkPlatformVersion();
if (versionStatus.newVersionAvailable) {
print('New version available: ${versionStatus.lastVersion?.versionName}');
if (versionStatus.mandatoryUpdate) {
print('This is a mandatory update!');
// Force user to update
} else {
print('Optional update available');
// Show optional update notification
}
// Access version details
final version = versionStatus.lastVersion;
if (version != null) {
print('Version Code: ${version.versionCode}');
print('Version Name: ${version.versionName}');
print('Download Link: ${version.downloadLink}');
print('Release Notes: ${version.rawReleaseNotes}');
print('Published At: ${version.publishedAt}');
print('Minimal Version: ${version.minimalVersion}');
}
}
if (!versionStatus.supportedPlatform) {
print('Current platform is not supported');
}
} catch (e) {
if (e is SdkNotConfigureException) {
print('SDK not configured: ${e.message}');
} else {
print('Error checking version: $e');
}
}
}
}
6. Custom Release Note Rendering #
Customize how release notes are displayed:
void setupCustomReleaseNotes() {
IwsVersionManager().releaseNoteBuilder = (String plainText, dynamic richText) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'What\'s New',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: BorderRadius.circular(8),
),
child: MarkdownBody(data: plainText),
),
],
);
};
}
7. BLoC Integration #
Access the version BLoC directly for advanced use cases:
class CustomVersionWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<VersionBloc, VersionState>(
bloc: IwsVersionManager().versionBloc,
builder: (context, state) {
switch (state.status) {
case VersionStatus.starting:
return CircularProgressIndicator();
case VersionStatus.updated:
return Text('App is up to date');
case VersionStatus.updateAvailable:
return _buildUpdateAvailable(state);
case VersionStatus.mandatoryUpdate:
return _buildMandatoryUpdate(state);
case VersionStatus.connectionError:
return _buildConnectionError();
case VersionStatus.platformNotSupported:
return _buildPlatformNotSupported();
}
},
);
}
Widget _buildUpdateAvailable(VersionState state) {
return Card(
child: ListTile(
leading: Icon(Icons.new_releases, color: Colors.green),
title: Text('Update Available'),
subtitle: Text('Version ${state.lastVersion?.versionName}'),
trailing: TextButton(
onPressed: () => _downloadUpdate(state.downloadLink),
child: Text('Download'),
),
),
);
}
Widget _buildMandatoryUpdate(VersionState state) {
return Card(
color: Colors.orange.shade100,
child: ListTile(
leading: Icon(Icons.warning, color: Colors.orange),
title: Text('Mandatory Update'),
subtitle: Text('Update required to continue'),
trailing: ElevatedButton(
onPressed: () => _downloadUpdate(state.downloadLink),
child: Text('Update'),
),
),
);
}
Widget _buildConnectionError() {
return Card(
child: ListTile(
leading: Icon(Icons.error, color: Colors.red),
title: Text('Connection Error'),
trailing: IconButton(
onPressed: () => _retryCheck(),
icon: Icon(Icons.refresh),
),
),
);
}
Widget _buildPlatformNotSupported() {
return Card(
child: ListTile(
leading: Icon(Icons.block, color: Colors.grey),
title: Text('Platform Not Supported'),
subtitle: Text('This platform is not supported'),
),
);
}
void _downloadUpdate(String? downloadLink) {
// Implementation
}
void _retryCheck() {
IwsVersionManager().versionBloc.add(CheckAppVersion());
}
}
Configuration Options #
Version Status Types #
The package provides several version status types:
VersionStatus.starting: Initial state, checking in progressVersionStatus.updated: App is up to dateVersionStatus.updateAvailable: Optional update availableVersionStatus.mandatoryUpdate: Required update availableVersionStatus.connectionError: Network connection errorVersionStatus.platformNotSupported: Platform not supported
Widget Configuration #
Each widget supports various configuration options:
IwsVersionScaffoldMessage
showConnectionErrors: Show notifications for connection errorsmessageHeight: Height of notification messagesnoDownloadUrlNoticeMode: Override notice mode for when there is no download URLenabledPlatforms: Override allowed platforms for displaying scaffold notificationscustomNoDownloadUrlMessage: Optional custom text to display when there is no download link
IwsVersionIconButton
showConnectionError: Show icon for connection errorsnoDownloadUrlNoticeMode: Override notice mode for when there is no download URLenabledPlatforms: Override allowed platforms for displaying version iconcustomNoDownloadUrlMessage: Optional custom text to display when there is no download link
Error Handling #
The package includes built-in error handling:
try {
final status = await IwsVersionManager().checkPlatformVersion();
// Handle status
} on SdkNotConfigureException catch (e) {
// Handle configuration errors
print('Configuration error: ${e.message}');
} on FetchDataException catch (e) {
// Handle network errors
print('Network error: ${e.httpCode}');
} catch (e) {
// Handle other errors
print('Unknown error: $e');
}
Data Models #
ApplicationVersion #
class ApplicationVersion {
int versionCode; // Numeric version code
String versionName; // Human-readable version name
dynamic releaseNotes; // Rich text release notes
String? rawReleaseNotes; // Plain text release notes
String? downloadLink; // Direct download URL
DateTime? publishedAt; // Publication date
int minimalVersion; // Minimum supported version
String? platformDownloadLink; // Platform-specific download URL
}
AppVersionStatus #
class AppVersionStatus {
bool newVersionAvailable; // New version is available
bool mandatoryUpdate; // Update is mandatory
bool supportedPlatform; // Platform is supported
ApplicationVersion? lastVersion; // Latest version information
}
Web Support #
The package provides unified version handling on Flutter Web:
- Download URL Support: If a download URL (
downloadLink/platformDownloadLink) is configured, a download action is presented to the user. - Configurable Notices: When no download URL exists,
NoDownloadUrlNoticeModecontrols whether notices are shown (hidden by default). - Custom Messages: Support for custom descriptions or instructions via
customNoDownloadUrlMessage.
Best Practices #
- Initialize Early: Call
initializeWidgets()as early as possible in your app lifecycle - Handle Errors: Always implement error handling for network issues
- Test Updates: Use the dry-run publish option to test your update flow
- User Experience: Provide clear messaging about update requirements
- Graceful Degradation: Ensure your app works even when version checking fails
Advanced Usage #
Custom BLoC Events #
Trigger version checks manually:
// Trigger a version check
IwsVersionManager().versionBloc.add(CheckAppVersion());
State Monitoring #
Monitor version state changes:
IwsVersionManager().versionBloc.stream.listen((state) {
print('Version status changed: ${state.status}');
});