force_updater 1.1.0
force_updater: ^1.1.0 copied to clipboard
Smart app version manager for Flutter. Add force update walls, soft update banners, and maintenance screens in 2 lines of code. Features 6 premium Material 3 UI themes.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:force_updater/force_updater.dart';
/// ---------------------------------------------------------------------------
/// FORCE_UPDATER INTERACTIVE PLAYGROUND & EXAMPLE
/// ---------------------------------------------------------------------------
///
/// This example showcases how to implement and test the `force_updater` package.
/// It provides a dynamic interactive interface letting you test all 5 premium
/// visual styles, switch between inline simulator configs, and inspect how
/// the updater behaves in real-time.
///
/// To test the package:
/// 1. Run the local mock API server: `dart example_api.dart`
/// 2. Launch this app on your Emulator or Web browser.
/// 3. Toggle styles and configuration settings below to see immediate updates!
/// ---------------------------------------------------------------------------
// Global Notifiers to make the example highly interactive in real-time!
final ValueNotifier<UpdateDialogStyle> activeStyleNotifier =
ValueNotifier<UpdateDialogStyle>(UpdateDialogStyle.glassmorphism);
final ValueNotifier<bool> isUsingLocalMockApiNotifier = ValueNotifier<bool>(
true,
);
void main() {
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ForceUpdater Premium Showcase',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.deepPurple,
brightness: Brightness.light,
),
darkTheme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.deepPurple,
brightness: Brightness.dark,
),
// We wrap the entire home view with ValueListenableBuilders so that
// changes to the style or the config instantly rebuild the ForceUpdater!
home: ValueListenableBuilder<UpdateDialogStyle>(
valueListenable: activeStyleNotifier,
builder: (context, activeStyle, _) {
return ValueListenableBuilder<bool>(
valueListenable: isUsingLocalMockApiNotifier,
builder: (context, useLocalApi, _) {
return ForceUpdater(
// 1. Current App Version
// We set the current app version to v4.0.0 (or v1.0.0) so we can trigger updates
// whenever the latest version is greater than this current version.
currentVersion: '4.0.0',
// 2. Background Check Interval
// We poll the backend every 3 seconds so you can see live hot updates
// immediately when you change options in the 'example_api.dart' server terminal.
checkInterval: const Duration(seconds: 3),
// 3. UI Styling
// Set the style dynamically based on the user's dropdown choice!
dialogStyle: activeStyle,
// 4. Custom UI Notification Override (100% Flexibity!)
// - By default, this is true. ForceUpdater displays one of the 5 premium dialogs.
// - Set this to false to suppress the default dialogs and implement your own custom UI
// (e.g., custom bottom sheets, overlays, or snackbars) inside the onVersionResolved callback below!
showDefaultDialog: true,
// 5. Update Strategy Configuration
// Switch dynamically between the local Mock API server and a hardcoded inline simulator!
config: useLocalApi
? UpdateConfig.remote('http://10.0.2.2:3000/version.json')
: UpdateConfig.inline(
minVersion:
'1.5.0', // Users below v1.5.0 get locked forcefully
latestVersion:
'2.0.0', // Users between 1.5.0 and 2.0.0 get optional updates
forceUpdate: false, // Compulsory update toggle
maintenance:
false, // Global system maintenance screen toggle
changelog:
'• Beautiful new interactive dashboard\n• Toggle between 5 custom visual styles on the fly\n• Fixed emulator network routing issues\n• Added smooth transitions and rich micro-animations',
storeUrlAndroid: 'https://play.google.com/store',
storeUrlIos: 'https://apps.apple.com',
),
// Let's print logs to console for better debugging insight:
onVersionResolved: (info) {
debugPrint(
'[ForceUpdater Playground] Version check successful: ${info.latestVersion}',
);
// 💡 CUSTOM UI IMPLEMENTATION PLACE:
// If you set `showDefaultDialog: false`, this is the exact place where you write
// your own custom notification layout! For example:
//
// if (Semver.isLessThan('1.0.0', info.minVersion)) {
// showMyFullyCustomBlockingWall(context);
// } else if (Semver.isLessThan('1.0.0', info.latestVersion)) {
// showMyCustomBannerOverlay(context);
// }
},
onError: (err) {
debugPrint('[ForceUpdater Playground] Error occurred: $err');
},
child: const HomePage(),
);
},
);
},
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final cs = theme.colorScheme;
return Scaffold(
backgroundColor: cs.surface,
appBar: AppBar(
title: const Text(
'ForceUpdater Showcase',
style: TextStyle(fontWeight: FontWeight.bold),
),
centerTitle: true,
elevation: 0,
backgroundColor: Colors.transparent,
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Welcome Card
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
cs.primaryContainer,
cs.secondaryContainer.withValues(alpha: 0.5),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(24),
border: Border.all(color: cs.primary.withValues(alpha: 0.1)),
),
child: Column(
children: [
CircleAvatar(
radius: 36,
backgroundColor: cs.primary,
child: const Icon(
Icons.rocket_launch_rounded,
size: 36,
color: Colors.white,
),
),
const SizedBox(height: 16),
Text(
'Interactive Playground',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'Experience premium update overlays and responsive dialog templates tailored for standard, minimalist, and luxury branding.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: cs.onSurfaceVariant,
height: 1.45,
),
),
],
),
),
const SizedBox(height: 24),
// UI Style Selection Card
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 0,
color: cs.surfaceContainerLow,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.palette_outlined, color: cs.primary),
const SizedBox(width: 10),
Text(
'Choose Dialog Style',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 6),
Text(
'Switch styles below to instantly reload the update dialog in that specific layout.',
style: theme.textTheme.bodySmall?.copyWith(
color: cs.onSurfaceVariant,
),
),
const SizedBox(height: 16),
// Custom Dropdown for selecting one of the 5 premium dialog styles
ValueListenableBuilder<UpdateDialogStyle>(
valueListenable: activeStyleNotifier,
builder: (context, activeStyle, _) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: cs.outlineVariant),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<UpdateDialogStyle>(
value: activeStyle,
isExpanded: true,
icon: const Icon(
Icons.keyboard_arrow_down_rounded,
),
items: const [
DropdownMenuItem(
value: UpdateDialogStyle.glassmorphism,
child: Text(
'🧪 Glassmorphism (Frosted Glow)',
),
),
DropdownMenuItem(
value: UpdateDialogStyle.classic,
child: Text(
'🏛️ Classic (Standard Material 3)',
),
),
DropdownMenuItem(
value: UpdateDialogStyle.minimalist,
child: Text(
'📐 Minimalist (Sophisticated Outlines)',
),
),
DropdownMenuItem(
value: UpdateDialogStyle.gradient,
child: Text(
'🎨 Gradient Card (Vibrant Colors)',
),
),
DropdownMenuItem(
value: UpdateDialogStyle.bottomSheet,
child: Text(
'📱 Slide-up BottomSheet (Sleek Compact)',
),
),
],
onChanged: (val) {
if (val != null) {
activeStyleNotifier.value = val;
}
},
),
),
);
},
),
],
),
),
),
const SizedBox(height: 16),
// Configuration Controller Card
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 0,
color: cs.surfaceContainerLow,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.settings_outlined, color: cs.primary),
const SizedBox(width: 10),
Text(
'Configuration Method',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 6),
Text(
'Change the data source for version checks. Use Mock API server or test locally without a backend!',
style: theme.textTheme.bodySmall?.copyWith(
color: cs.onSurfaceVariant,
),
),
const SizedBox(height: 16),
ValueListenableBuilder<bool>(
valueListenable: isUsingLocalMockApiNotifier,
builder: (context, useLocalApi, _) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SegmentedButton<bool>(
segments: const [
ButtonSegment<bool>(
value: true,
label: Text('Mock API Server'),
icon: Icon(Icons.dns_rounded),
),
ButtonSegment<bool>(
value: false,
label: Text('Inline Simulator'),
icon: Icon(Icons.offline_bolt_rounded),
),
],
selected: {useLocalApi},
onSelectionChanged: (newSelection) {
isUsingLocalMockApiNotifier.value =
newSelection.first;
},
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: cs.outlineVariant.withValues(
alpha: 0.5,
),
),
),
child: Row(
children: [
Icon(
useLocalApi
? Icons.link_rounded
: Icons.info_outline_rounded,
size: 18,
color: cs.primary,
),
const SizedBox(width: 8),
Expanded(
child: Text(
useLocalApi
? 'Target: http://10.0.2.2:3000/version.json (Android) / localhost:3000 (Web/iOS)'
: 'Simulating update check using local hardcoded parameters (min v1.5.0, latest v2.0.0).',
style: theme.textTheme.bodySmall
?.copyWith(
color: cs.onSurfaceVariant,
height: 1.35,
),
),
),
],
),
),
],
);
},
),
],
),
),
),
const SizedBox(height: 24),
// Quick instructions
Text(
'How to test updates:',
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
_StepTile(
step: '1',
title: 'Start local server',
subtitle:
'Run "dart example_api.dart" in the root directory to start the test endpoint.',
),
_StepTile(
step: '2',
title: 'Configure device target',
subtitle:
'Android emulator resolves localhost as 10.0.2.2. Web/iOS use localhost. We support both automatically!',
),
_StepTile(
step: '3',
title: 'Perform simulated hot updates',
subtitle:
'Press [2] in server terminal to trigger force updates, [3] to toggle soft alerts, or [1] for maintenance.',
),
],
),
),
),
);
}
}
class _StepTile extends StatelessWidget {
final String step;
final String title;
final String subtitle;
const _StepTile({
required this.step,
required this.title,
required this.subtitle,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final cs = theme.colorScheme;
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 12,
backgroundColor: cs.secondaryContainer,
child: Text(
step,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: cs.onSecondaryContainer,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 2),
Text(
subtitle,
style: theme.textTheme.bodySmall?.copyWith(
color: cs.onSurfaceVariant,
height: 1.35,
),
),
],
),
),
],
),
);
}
}