Force Update in Flutter (Part 2): The Upgrader Package
Force Update series: 1. Why you need it · 2. Upgrader · 3. Remote config intro · 4. force_update_helper · 5. GitHub Gist · 6. Firebase Remote Config · 7. Dart Shelf API
In Part 1 we covered why force update exists. This part is the fastest path to a real prompt: the upgrader package.
It compares the installed app version to what is published on the App Store / Play Store and shows an alert when something newer is live.
When upgrader is the right tool
Use it when:
- You want users on the latest store build most of the time
- You do not need a remote "minimum version" kill switch
- The app is already (or will be) published — store listing is the source of truth
Skip it when you need to retire an API while a newer store build is still rolling out, or when you want to force only on critical releases. That is remote config territory.
Install
dart pub add upgrader package_info_plus url_launcher
flutter pub get
Minimal example
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Upgrader Example',
home: UpgradeAlert(
child: Scaffold(
appBar: AppBar(title: const Text('Upgrader Example')),
body: const Center(child: Text('Checking…')),
),
),
);
}
}
Fine for demos. Real apps rarely use home alone.
Named routes and GoRouter
If you use onGenerateRoute or GoRouter, put UpgradeAlert in MaterialApp.builder and pass a root navigator key so the dialog can present:
final rootNavigatorKey = GlobalKey<NavigatorState>();
class MainApp extends ConsumerWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
navigatorKey: rootNavigatorKey,
builder: (context, child) {
return UpgradeAlert(
navigatorKey: rootNavigatorKey,
child: child ?? const SizedBox.shrink(),
);
},
onGenerateRoute: onGenerateRoute,
);
}
}
GoRouter variant:
final rootNavigatorKey = GlobalKey<NavigatorState>();
final goRouter = GoRouter(
navigatorKey: rootNavigatorKey,
routes: [ /* … */ ],
);
MaterialApp.router(
routerConfig: goRouter,
builder: (context, child) {
return UpgradeAlert(
navigatorKey: goRouter.routerDelegate.navigatorKey,
child: child ?? const SizedBox.shrink(),
);
},
);
Two rules I do not break:
- builder — alert is an ancestor of every route
- navigatorKey — alert knows which navigator owns the overlay
Material vs Cupertino dialog
builder: (context, child) {
final dialogStyle =
defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.macOS
? UpgradeDialogStyle.cupertino
: UpgradeDialogStyle.material;
return UpgradeAlert(
navigatorKey: rootNavigatorKey,
dialogStyle: dialogStyle,
child: child ?? const SizedBox.shrink(),
);
},
Soft vs hard with upgrader flags
| Goal | Knobs |
|---|---|
| Soft nudge | default / allow Later + Ignore |
| Hard force | showIgnore: false, showLater: false, barrierDismissible: false |
Hard-blocking every store release is rude. I keep upgrader soft unless the release is critical — and for critical work I often switch to a remote min version instead.
How to test
upgrader compares installed version (from the running app / pubspec packaging) to the store listing.
- App must be published (or use a published package id you control)
- Set
version:inpubspec.yamlbelow the store version - Run on a real device for the full "Update now → store" path
- Dev-only: clear cached "already prompted" state
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// DEV ONLY — remove before release
await Upgrader.clearSavedSettings();
runApp(const MainApp());
}
Limits I hit in production
- No remote control of when a version dies — only "is there something newer on the store?"
- You cannot keep supporting 1.4 while 1.5 is live without users getting nagged
- Backend deprecation schedules need a required_version you own
That is exactly why Part 3 introduces remote config force update.
Summary
- upgrader = store version comparison + dialog, fast to ship
- Wire it via
builder+navigatorKeyin real navigation setups - Prefer soft prompts; reserve hard for true emergencies
- For kill-switch control, move on to remote minimum version
← Previous Part 1: Why you need force update
Next → Part 3: Remote config approach



