firebase_update 1.0.4
firebase_update: ^1.0.4 copied to clipboard
Flutter force update, maintenance mode & patch notes via Firebase Remote Config. Real-time, server-controlled, with built-in UI and full customization.
firebase_update #
Flutter force update, maintenance mode, and real-time update prompts with Firebase Remote Config.
One package to handle forced updates, optional updates, maintenance mode, and patch notes — with built-in UI, real-time Remote Config listening, and full customization hooks.
Full documentation → qoder.in/resources/firebase-update
Index #
- Features
- Installation
- Quick Start
- Remote Config Schema
- Payload Examples
- Update States
- Configuration
- Custom UI
- Reactive Widget
- API Reference
- Testing
Features #
- Force update — blocks app usage when a breaking release is required
- Optional update — encourages upgrade via a dismissible dialog or bottom sheet
- Maintenance mode — instantly gates the app without shipping a build
- Patch notes — plain text or HTML, shown inline in the update UI
- Real-time updates — reacts to Remote Config changes without an app restart
- Built-in UI — default dialog and bottom sheet, no setup beyond a
navigatorKey - Custom UI — replace any surface with your own widget builders
FirebaseUpdateBuilder— reactive widget for building your own in-screen update surfaces
Installation #
dependencies:
firebase_update: ^1.0.0
This package requires Firebase to already be set up in your app. If you haven't done that yet, follow the FlutterFire setup guide.
Quick Start #
1. Initialize #
Call initialize() once during app bootstrap, after Firebase.initializeApp():
import 'package:firebase_update/firebase_update.dart';
final navigatorKey = GlobalKey<NavigatorState>();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await FirebaseUpdate.instance.initialize(
navigatorKey: navigatorKey,
config: const FirebaseUpdateConfig(),
);
runApp(MyApp(navigatorKey: navigatorKey));
}
Pass the same navigatorKey to your MaterialApp:
MaterialApp(
navigatorKey: navigatorKey,
home: const HomeScreen(),
)
That's it. The package now listens for Remote Config changes and automatically presents the appropriate UI when an update or maintenance state is detected.
Remote Config Schema #
Create a parameter named firebase_update_config in the Firebase console (or use a custom name via remoteConfigKey). Its value must be a JSON string:
{
"min_version": "2.0.0",
"latest_version": "2.3.1",
"maintenance_message": "",
"patch_notes": "• Bug fixes\n• Performance improvements",
"patch_notes_format": "text"
}
| Field | Type | Description |
|---|---|---|
min_version |
string | Minimum supported version. Below this → force update (blocking). |
latest_version |
string | Latest available version. Below this → optional update. |
maintenance_title |
string | Title shown on the maintenance screen. |
maintenance_message |
string | Non-empty string activates maintenance mode (blocking). |
force_update_title |
string | Override title for the force update screen. |
force_update_message |
string | Override body for the force update screen. |
optional_update_title |
string | Override title for the optional update prompt. |
optional_update_message |
string | Override body for the optional update prompt. |
patch_notes |
string | Release notes shown alongside the update prompt. |
patch_notes_format |
string | "text" (default) or "html". |
Priority: maintenance (if maintenance_message is non-empty) → force update (if current < min_version) → optional update (if current < latest_version). Only one surface is shown at a time; the package dismisses the previous modal before showing a new one.
Payload Examples #
These are the three default states most teams care about first. The screenshots below were generated from scripts/take_screenshots.sh, so the README reflects the actual packaged UI.
Optional Update #
Use this when you want to encourage upgrades without blocking the app.
{
"min_version": "2.0.0",
"latest_version": "2.6.0",
"optional_update_title": "Update available",
"optional_update_message": "Version 2.6.0 is ready with a smoother experience.",
"patch_notes": "Faster startup · Cleaner onboarding · Bug fixes.",
"patch_notes_format": "text"
}
Force Update #
Use this when the installed app version is no longer safe or compatible.
{
"min_version": "2.5.0",
"latest_version": "2.6.0",
"force_update_message": "This release contains required security fixes.",
"patch_notes": "<ul><li>Critical security patches</li><li>Required backend compatibility</li></ul>",
"patch_notes_format": "html"
}
Maintenance Mode #
Use this when you need to temporarily gate the app without shipping a new build.
{
"maintenance_title": "Scheduled maintenance",
"maintenance_message": "We're upgrading our servers. We'll be back shortly."
}
If you want to go beyond the defaults, the next sections cover state handling, presentation controls, and fully custom surfaces.
Update States #
FirebaseUpdateState.kind is one of:
| Kind | Meaning |
|---|---|
idle |
Not yet initialized. |
upToDate |
App version meets the minimum requirement. |
optionalUpdate |
A newer version is available, but the app is usable. |
forceUpdate |
App version is below the minimum. Usage is blocked. |
maintenance |
Maintenance mode is active. Usage is blocked. |
state.isBlocking is true for forceUpdate and maintenance.
Configuration #
FirebaseUpdateConfig(
// remoteConfigKey defaults to 'firebase_update_config'
currentVersion: '2.1.0', // Override auto-detected version
packageName: 'com.example.app', // Override auto-detected package name
fetchTimeout: Duration(seconds: 60),
minimumFetchInterval: Duration(hours: 12),
listenToRealtimeUpdates: true, // React to RC changes without restart
enableDefaultPresentation: true, // Set false to fully own the UI
useBottomSheetForOptionalUpdate: true, // false = dialog instead
fallbackStoreUrls: FirebaseUpdateStoreUrls(
android: 'https://play.google.com/store/apps/details?id=com.example.app',
ios: 'https://apps.apple.com/app/id000000000',
),
presentation: FirebaseUpdatePresentation(...), // Theme / alignment / icon
)
Custom UI #
Override any surface directly on FirebaseUpdateConfig — replace one, two, or all three independently:
// Just override maintenance — everything else stays default
FirebaseUpdateConfig(
maintenanceWidget: (context, data) => MyMaintenanceScreen(data: data),
)
// Mix and match
FirebaseUpdateConfig(
forceUpdateWidget: (context, data) => MyForceUpdateDialog(data: data),
optionalUpdateWidget: (context, data) => MyUpdateSheet(data: data),
maintenanceWidget: (context, data) => MyMaintenanceScreen(data: data),
)
Each builder receives FirebaseUpdatePresentationData with the resolved title, state, primary/secondary action labels, and tap callbacks wired to the correct package behavior — your widget doesn't need to re-implement any of that logic.
For optionalUpdateWidget, the modal type (dialog vs bottom sheet) is still controlled by useBottomSheetForOptionalUpdate — your widget is the content regardless of which container is used.
Theming the default UI #
FirebaseUpdatePresentation(
theme: FirebaseUpdatePresentationTheme(
accentColor: Colors.indigo,
accentForegroundColor: Colors.white,
surfaceColor: Colors.white,
heroGradient: LinearGradient(
colors: [Colors.indigo.shade800, Colors.indigo.shade400],
),
dialogBorderRadius: BorderRadius.circular(24),
),
)
Reactive Widget #
Use FirebaseUpdateBuilder to build your own in-screen update surfaces — a settings row, a banner, or anything else that should react to update state:
FirebaseUpdateBuilder(
builder: (context, state) {
if (state.kind == FirebaseUpdateKind.optionalUpdate) {
return UpdateBanner(version: state.latestVersion);
}
return const SizedBox.shrink();
},
)
API Reference #
FirebaseUpdate #
The singleton controller exposed as FirebaseUpdate.instance.
await FirebaseUpdate.instance.initialize(
navigatorKey: navigatorKey,
config: config,
);
Key members:
| Member | Type | What it does |
|---|---|---|
initialize({required navigatorKey, required config}) |
Future<void> |
Boots the package, fetches Remote Config, restores skip/snooze state, and starts real-time listening when enabled. |
checkNow() |
Future<void> |
Forces an immediate Remote Config fetch and re-evaluates the current state. |
applyPayload(Map<String, dynamic>? rawPayload, {String? currentVersion}) |
Future<FirebaseUpdateState> |
Resolves state from a raw payload without going through Firebase. Useful for tests and custom sources. |
stream |
Stream<FirebaseUpdateState> |
Broadcast stream of update-state changes. |
currentState |
FirebaseUpdateState |
Last resolved state, synchronously readable. |
navigatorKey |
GlobalKey<NavigatorState>? |
Navigator key registered during initialization. |
config |
FirebaseUpdateConfig? |
Active config object currently in use. |
snoozeOptionalUpdate([duration]) |
Future<void> |
Snoozes an optional update using the passed duration or config.snoozeDuration. |
dismissOptionalUpdateForSession() |
void |
Hides the current optional prompt only for the current app session. |
skipVersion(version) |
Future<void> |
Permanently skips prompts for one specific version. |
clearSnooze() |
Future<void> |
Clears any persisted snooze window. |
clearSkippedVersion() |
Future<void> |
Clears any persisted skipped version. |
FirebaseUpdateState #
Resolved state emitted by the package.
| Property | Type | Notes |
|---|---|---|
kind |
FirebaseUpdateKind |
idle, upToDate, optionalUpdate, forceUpdate, maintenance, shorebirdPatch |
isInitialized |
bool |
true after initialize() has completed |
title |
String? |
Resolved title used for default presentation |
message |
String? |
Resolved message body |
currentVersion |
String? |
Current app version |
minimumVersion |
String? |
Required minimum version |
latestVersion |
String? |
Latest available version |
patchNotes |
String? |
Raw patch notes content |
patchNotesFormat |
FirebaseUpdatePatchNotesFormat |
plainText or html |
maintenanceTitle |
String? |
Maintenance title from payload |
maintenanceMessage |
String? |
Maintenance message from payload |
storeUrls |
FirebaseUpdateStoreUrls? |
Store URLs from Remote Config payload |
isBlocking |
bool |
Convenience getter for force update and maintenance |
FirebaseUpdateConfig #
Main configuration object passed to initialize().
Core setup:
| Property | Type | Purpose |
|---|---|---|
remoteConfigKey |
String |
Remote Config parameter key. Default: firebase_update_config |
currentVersion |
String? |
Manual version override |
packageName |
String? |
Manual app identifier override for store launch |
fallbackStoreUrls |
FirebaseUpdateStoreUrls |
Per-platform store fallback URLs |
fetchTimeout |
Duration |
Remote Config fetch timeout |
minimumFetchInterval |
Duration |
Remote Config fetch throttle interval |
listenToRealtimeUpdates |
bool |
Enables real-time RC subscription |
enableDefaultPresentation |
bool |
Turns package-managed dialogs/sheets on or off |
Presentation control:
| Property | Type | Purpose |
|---|---|---|
useBottomSheetForOptionalUpdate |
bool? |
Optional update as sheet instead of dialog |
useBottomSheetForForceUpdate |
bool |
Force update as sheet |
useBottomSheetForMaintenance |
bool |
Maintenance as sheet |
presentation |
FirebaseUpdatePresentation |
Theme, labels, typography, alignment, icon builder |
forceUpdateWidget |
FirebaseUpdateViewBuilder? |
Replaces default force UI |
optionalUpdateWidget |
FirebaseUpdateViewBuilder? |
Replaces default optional UI |
maintenanceWidget |
FirebaseUpdateViewBuilder? |
Replaces default maintenance UI |
shorebirdPatchWidget |
FirebaseUpdateViewBuilder? |
Replaces default patch-ready UI |
Behavior and hooks:
| Property | Type | Purpose |
|---|---|---|
onStoreLaunch |
VoidCallback? |
Fully overrides default store-launch behavior |
onForceUpdateTap |
VoidCallback? |
Analytics or side effects for force CTA |
onOptionalUpdateTap |
VoidCallback? |
Analytics or side effects for optional CTA |
onOptionalLaterTap |
VoidCallback? |
Analytics or side effects for dismiss CTA |
onDialogShown |
void Function(FirebaseUpdateState)? |
Fires when package UI is presented |
onDialogDismissed |
void Function(FirebaseUpdateState)? |
Fires when package UI is dismissed |
onSnoozed |
void Function(String, Duration)? |
Fires when optional prompt is snoozed |
onVersionSkipped |
void Function(String)? |
Fires when a version is skipped |
allowedFlavors |
List<String>? |
Whitelist build flavors using --dart-define=FLAVOR=... |
showSkipVersion |
bool |
Shows “Skip this version” on optional prompts |
snoozeDuration |
Duration? |
Default snooze duration |
preferencesStore |
FirebaseUpdatePreferencesStore? |
Custom persistence backend for skip/snooze |
Patch support:
| Property | Type | Purpose |
|---|---|---|
patchSource |
FirebaseUpdatePatchSource? |
Integrates Shorebird or another patch provider |
onPatchApplied |
VoidCallback? |
Called after a patch has been applied |
FirebaseUpdatePresentation #
Controls the built-in UI system.
| Member | Type | Purpose |
|---|---|---|
useBottomSheetForOptionalUpdate |
bool |
Global default for optional updates |
contentAlignment |
FirebaseUpdateContentAlignment? |
Aligns icon, title, and body |
patchNotesAlignment |
FirebaseUpdateContentAlignment? |
Aligns the patch notes block independently |
typography |
FirebaseUpdateTypography |
Fine-grained text-style overrides |
labels |
FirebaseUpdateLabels |
Override every static string in the default UI |
theme |
FirebaseUpdatePresentationTheme |
Colors, border radius, blur, gradient, layout tokens |
iconBuilder |
FirebaseUpdateIconBuilder? |
Replaces the default top icon |
Supporting presentation objects:
| Object | What it controls |
|---|---|
FirebaseUpdatePresentationTheme |
Accent colors, surface colors, outline, barrier, blur, gradients, radii, max dialog width |
FirebaseUpdateTypography |
Title, body, release-notes, read-more, and button text styles |
FirebaseUpdateLabels |
Titles, CTA text, release-notes labels, skip-version text, patch-ready copy |
FirebaseUpdateContentAlignment |
start, center, end |
Widgets #
| Widget | Purpose |
|---|---|
FirebaseUpdateBuilder |
Rebuilds on every FirebaseUpdateState change so you can render custom inline UI |
FirebaseUpdateCard |
Ready-made inline card for optional update, force update, maintenance, and patch states |
Example:
FirebaseUpdateBuilder(
builder: (context, state) {
if (state.kind == FirebaseUpdateKind.optionalUpdate) {
return FirebaseUpdateCard();
}
return const SizedBox.shrink();
},
)
Supporting Types #
| Type | Purpose |
|---|---|
FirebaseUpdateKind |
Enum for idle, upToDate, optionalUpdate, forceUpdate, maintenance, shorebirdPatch |
FirebaseUpdatePatchNotesFormat |
Enum for plainText and html patch notes rendering |
FirebaseUpdateStoreUrls |
Per-platform fallback URLs: android, ios, macos, windows, linux, web |
FirebaseUpdatePatchSource |
Interface for code-push patch providers such as Shorebird |
FirebaseUpdatePreferencesStore |
Interface for skip/snooze persistence |
SharedPreferencesFirebaseUpdateStore |
Default shared_preferences implementation of the persistence store |
Testing #
Unit + widget tests #
flutter test
Real Remote Config integration (requires service account) #
A Dart CLI tool in test/firebase_config/ pushes predefined scenarios directly to Firebase Remote Config, leaving all other parameters untouched. The running app reacts in real time via onConfigUpdated.
cd test/firebase_config
# One-time dependency install
dart pub get
# Push a scenario
dart run update_remote_config.dart optional # optional update bottom sheet / dialog
dart run update_remote_config.dart force # force update (blocking)
dart run update_remote_config.dart maintenance # maintenance mode (blocking)
dart run update_remote_config.dart clear # back to up-to-date
# Custom Remote Config key
dart run update_remote_config.dart optional --key my_update_key
Requires test/firebase_config/service-account.json with firebaseremoteconfig write permissions.
Keywords #
flutter force update · flutter maintenance mode · flutter in-app update · firebase remote config update · flutter app update prompt · flutter update dialog · flutter update wall · flutter app gate · real-time update control · no-code update control
License #
BSD-3-Clause © Qoder