firebase_update
Flutter force update, maintenance mode, patch notes, and custom update UI driven by Firebase Remote Config.
Updated , by A Few Good Coders
Published 13 sectionsFlutter Package
- pub points
- 150/160
- likes
- 17
- downloads, 30 days
- 11
- latest version
- 1.0.7+1
pub.dev, as of Verify on pub.dev (opens in a new tab)BSD-3-Clause licence (opens in a new tab)
Start here: release policy, not an app installer
Your first working integration
- Install and configure Firebase
- Initialize with a mounted navigator
- Publish the release policy
- Test policy safely
- Plan rollout and rollback
Already using this in production? Start with the troubleshooting and migration or release-checklist sections below. Work in a development project first, and keep the reviewed package version visible when comparing these examples with your lockfile.
This guide covers firebase_update 1.0.7+1, verified against its published archive and repository on 13 October 2026. The package reads one JSON-valued Firebase Remote Config parameter, compares the running app version, and exposes update or maintenance state with optional built-in UI.
It does not publish a store release, install an update, or replace server authorization. A blocking dialog is a user-interface gate, not a security boundary. Release the new binary to the intended users before requiring it, and keep a rollback-ready Remote Config template.
Start with a staging Firebase project and an optional update. Verify store links, offline behavior, and resume behavior before trying a forced update.
1. Add dependencies and configure Firebase
Run from the Flutter app directory. Add firebase_core directly because the bootstrap imports it. Use FlutterFire to register each supported target and generate lib/firebase_options.dart; confirm you selected the intended Firebase project and bundle/application IDs.
The package declares Dart ^3.2.0 and Flutter >=1.17.0, but those declarations alone are not a reliable working toolchain minimum: its Firebase and package_info_plus dependencies require newer SDKs. Use a current compatible Flutter stable toolchain and let flutter pub get report the effective constraints. Do not force incompatible dependencies with overrides.
The published manifest permits firebase_core >=4.5.0 <6.0.0 and firebase_remote_config >=6.2.0 <9.0.0. If combining it with another Firebase package, ensure their ranges overlap. Native store launch and Remote Config real-time support vary by platform; validate the platforms you actually ship.
flutter --version
flutter pub add firebase_update:^1.0.7+1 firebase_core
dart pub global activate flutterfire_cli
flutterfire configure
flutter pub get2. Bootstrap with a mounted navigator
This complete main.dart uses the generated Firebase options. It mounts the app before initializing update UI, so the shared navigator is ready and a Remote Config fetch does not hold the first frame. This is a usability choice—not an offline enforcement guarantee.
Replace the placeholder store URLs with your real listings before publishing. Do not create a second navigator key during rebuilds. With a router-based app, give the package the root navigator key used by that router rather than a disconnected key.
If your business requires a startup gate while policy is loading, design a separate loading/offline state. Do not infer that an unreachable Remote Config service means the app is safe or blocked.
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_update/firebase_update.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
final navigatorKey = GlobalKey<NavigatorState>();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(MaterialApp(
navigatorKey: navigatorKey,
home: const Scaffold(body: Center(child: Text('Update demo'))),
));
WidgetsBinding.instance.addPostFrameCallback((_) async {
try {
await FirebaseUpdate.instance.initialize(
navigatorKey: navigatorKey,
config: const FirebaseUpdateConfig(
remoteConfigKey: 'firebase_update_config',
fallbackStoreUrls: FirebaseUpdateStoreUrls(
android: 'https://play.google.com/store/apps/details?id=com.example.app',
ios: 'https://apps.apple.com/app/id000000000',
),
),
);
} catch (error) {
debugPrint('Update initialization failed: $error');
// Apply your product-defined retry/offline policy here.
}
});
}3. Publish the Remote Config parameter
In Firebase Console → Remote Config, create firebase_update_config. Its value must be a JSON object serialized into the parameter—not separate parameters named min_version and latest_version. Publish the template after saving changes.
For a staging app running version 1.0.0, this example offers 1.1.0 without forcing it. Keep platform conditions separate if iOS and Android releases become available at different times. Replace store URLs with live listings.
The parser accepts min_version, latest_version, general update_title / update_message, force/optional-specific title/message fields, maintenance_title, maintenance_message, patch_notes, patch_notes_format, and store_url_android, store_url_ios, store_url_macos, store_url_windows, store_url_linux, store_url_web. Empty strings are treated as absent. Only html selects HTML patch notes; otherwise notes are plain text.
Snooze is configured in Dart. allow_snooze and snooze_duration_hours are not recognized JSON fields in this release.
{
"min_version": "1.0.0",
"latest_version": "1.1.0",
"optional_update_title": "A new version is ready",
"optional_update_message": "Update when convenient to get the latest improvements.",
"force_update_title": "Update required",
"force_update_message": "Install the supported version to continue.",
"maintenance_message": "",
"patch_notes": "Improved navigation\nFixed sign-in errors",
"patch_notes_format": "text",
"store_url_android": "https://play.google.com/store/apps/details?id=com.example.app",
"store_url_ios": "https://apps.apple.com/app/id000000000"
}Understand precedence and version comparison
If no current version is available, the resolver produces an idle state. Normally package_info_plus provides the application version; FirebaseUpdateConfig.currentVersion is a test override. Remove that override from production unless you intentionally own version resolution.
The comparator splits dot-separated components and parses integers; it is not a full Semantic Versioning parser. Use plain numeric versions such as 1.2.3. Do not rely on prerelease labels or +build suffixes to order releases. Build-number-only upgrades need an explicit, tested versioning strategy.
An up-to-date state is the result of the available policy, not proof the device contacted the server just now. A store-version source is optional and does not replace the Remote Config policy.
Roll out and roll back safely
- Confirm the new binary is available to the users targeted by the Remote Config condition, including staged rollout and regional availability.
- Publish an optional update by raising
latest_versionwhile leavingmin_versionat the oldest supported release. - Test the store button on actual devices and confirm the promoted version can be installed.
- Raise
min_versiononly after the compatibility and availability decision is approved. - To roll back a mistaken block, restore the previous minimum and clear
maintenance_message, then publish. Verify a connected device receives the revision; disconnected devices cannot receive an immediate rollback.
Keep a safe default template and a record of which versions each platform supports. Maintenance is enabled by a non-empty message, not a separate boolean. HTML notes should be curated release content, not untrusted user input. Remote Config values are client-readable; never place secrets in them.
Fetching, real-time changes, and offline behavior
Defaults are a 60-second fetch timeout, a 12-hour minimum fetch interval, and real-time listening enabled. checkNow() requests a check but follows the configured fetch policy; it is not a promise to bypass all caches. For staging only, a short interval can make experiments easier. Avoid repeated zero-interval production polling.
Real-time updates depend on connectivity and the target's Firebase SDK support. The source retries stream failures with backoff, but no client-side system guarantees instantaneous propagation. Set recheckInterval if you want a periodic safety net. storeVersionSource and checkStoreVersionOnResume are opt-in; store lookup can trigger a fresh Remote Config fetch when the store is ahead.
The controller re-presents active blocking states on resume. Test returning from the store without installing an update. Own and cancel any subscription you add to FirebaseUpdate.instance.stream; do not reinitialize on every widget rebuild.
Optional updates: Later, snooze, and skip
By default, Later dismisses the optional prompt for the current session. snoozeDuration persists a time-based deferral across app restarts; showSkipVersion enables skipping the offered version. These are product decisions, not universal defaults.
Use these fields in the same initialization configuration shown above. For custom UI, the public methods include snoozeOptionalUpdate, dismissOptionalUpdateForSession, and version-skip support; follow their API signatures for the resolved release. Force-update and maintenance gates should not be treated as optional-update dismissal flows.
allowedFlavors checks the FLAVOR Dart define. Passing only Flutter's --flavor flag does not supply that value: use --dart-define=FLAVOR=production when matching a configured production allowlist.
const config = FirebaseUpdateConfig(
showSkipVersion: true,
snoozeDuration: Duration(hours: 24),
recheckInterval: Duration(hours: 6),
allowedFlavors: ['production'],
);Custom presentation without unsupported arguments
Use FirebaseUpdateCard() for a built-in reactive inline card. It reads the singleton's state itself: it does not accept latestVersion. FirebaseUpdateBuilder is the lower-level option for your own widget tree.
Set enableDefaultPresentation: false if your application owns presentation entirely; otherwise an inline card and automatic modal may both appear. If you disable automatic UI, you also own the behavior of mandatory states—an informational card alone does not block navigation.
forceUpdateWidget, optionalUpdateWidget, and maintenanceWidget receive presentation data and wired callbacks. Preserve those actions when replacing the visual design. Use onDialogShown, onDialogDismissed, onSnoozed, and onVersionSkipped for your own analytics; the package does not automatically make these Firebase Analytics events.
// Place inside an existing screen after initialization:
const FirebaseUpdateCard();
// Or build your own status view:
FirebaseUpdateBuilder(
builder: (context, state) => Text('Update state: ${state.kind.name}'),
);
// In initialize(...), for fully app-owned presentation:
// config: const FirebaseUpdateConfig(enableDefaultPresentation: false),Store launch and optional patch integrations
A store launch opens a listing; it does not confirm an update was installed. Provide tested fallback URLs, especially for iOS and non-mobile targets. onStoreLaunch overrides the normal launch flow; use it only if your callback implements the replacement behavior. onForceUpdateTap and onOptionalUpdateTap observe actions without replacing the launch flow.
Shorebird integration is an extension point through FirebaseUpdatePatchSource, not automatic bundled patch infrastructure. Supply and test a patch source if your app uses it. The package considers patch availability when the app is otherwise up to date; an applied patch may require a restart according to your patch runtime. Do not promise hot replacement of every app change.
Test policy without changing production Firebase
First test the resolver through applyPayload() after initialization in a staging/test harness. This changes the local controller state, not the remote Firebase template. The example simulates an optional update for 1.0.0; then test minimum version, maintenance, and clearing the policy.
For end-to-end tests, use a separate Firebase project. Repository scripts under test/firebase_config/ can write Remote Config; inspect their target and credentials before running them. They are not needed just to install the package, and should never be pointed at production by accident.
Test: fresh install; offline launch; optional Later and restart; timed snooze; skip then a newer release; forced update then store/resume without installation; maintenance on/off; malformed JSON; missing current version; invalid store URL; nested navigation; each flavor and target platform.
final result = await FirebaseUpdate.instance.applyPayload(
{'min_version': '1.0.0', 'latest_version': '1.1.0'},
currentVersion: '1.0.0',
);
assert(result.kind == FirebaseUpdateKind.optionalUpdate);
await FirebaseUpdate.instance.applyPayload(
{'maintenance_message': 'Staging maintenance test'},
currentVersion: '1.0.0',
);
await FirebaseUpdate.instance.applyPayload(
{'min_version': '1.0.0', 'latest_version': '1.0.0'},
currentVersion: '1.0.0',
);Troubleshooting checklist
Include the resolved package/Flutter versions, target platform, a redacted JSON policy, current app version, and reproduction steps when filing an issue. Never include a service-account key.
Source trail
Release: pub.dev 1.0.7+1 (opens in a new tab). Repository snapshot: e11e680 (opens in a new tab).
This guide was checked against the payload parser and resolver (opens in a new tab), configuration, controller, and card implementation—not only the README. The published archive contains store-version source files absent from this repository snapshot; use the pub.dev release for installation rather than assuming a Git dependency has identical contents. Consult the versioned API reference (opens in a new tab) for extension interfaces.