Force Update in Flutter (Part 4): force_update_helper
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
Part 3 defined the remote min-version policy. This part is the packaging: force_update_helper so you do not re-implement dialogs and store URLs every project.
What the package is for
You still own:
- Where
required_versionlives (Gist / Firebase / API) - When to raise it
The package owns:
- Calling your
fetchRequiredVersioncallback - Comparing with the installed version
- Showing the update UI
- Opening App Store / Play with the right ids
Dependencies
dependencies:
force_update_helper: ^0.3.0
package_info_plus: ^9.0.0
url_launcher: ^6.3.2
dio: ^5.9.0 # if you fetch via HTTP
Android store links need a query intent in AndroidManifest.xml:
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
Without it, url_launcher can fail silently on modern Android.
Client shape
ForceUpdateClient(
fetchRequiredVersion: () async {
// Part 5/6/7 plug in here — Gist, Firebase, or Shelf
final response = await dio.get('https://example.com/required_version');
return response.data as String; // e.g. "1.4.2"
},
iosAppStoreId: Env.appStoreId, // numeric App Store id
);
Android package name usually comes from the running app; iOS needs the numeric App Store ID (not the bundle id).
Gate the app once
I mount the check at startup — after bindings / env load, before authenticated home:
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) {
return ForceUpdateWidget(
// package API names vary by version — see README
client: forceUpdateClient,
child: child ?? const SizedBox.shrink(),
);
},
home: const HomeScreen(),
);
}
}
Read the package README for the exact widget name and parameters for the version you pin — APIs evolve; the architecture above stays stable.
What I customize
- Copy — "Update required" + one sentence of why (security / data / login)
- Hard only when
current < required— no Later button for true force - Analytics —
force_update_shown,force_update_opened_store - Timeout — do not spin forever on a dead endpoint
Testing checklist
- Lower
required_versionremote value → app opens normally - Raise above installed version → hard prompt
- Tap Update → correct store listing
- Airplane mode with empty cache → fail open (or cached policy)
- iOS and Android both open the right listing
Summary
force_update_helperis the glue; policy and remote source are still your job- Inject different fetch implementations per environment
- Fix Android queries + iOS App Store id before blaming the package
← Previous Part 3: Remote config intro
Next → Part 5: GitHub Gist as remote config



