fl_updater 0.0.2 copy "fl_updater: ^0.0.2" to clipboard
fl_updater: ^0.0.2 copied to clipboard

Firebase Remote Config-driven app update dialog with automatic wrapper, snoozable soft updates, and native App/Play Store opening.

fl_updater #

pub package pub points license: MIT

A lightweight, cost-conscious Flutter plugin for Firebase Remote Config-driven app updates. Supports automatic launch checks, soft updates with per-version snoozing, non-dismissible force updates, and native App Store / Google Play Store redirection.


✨ Features #

  • 🚀 Declarative Wrapper: Wrap your MaterialApp with FlUpdaterWrapper for zero-boilerplate launch checks.
  • Imperative API: Use FlUpdater().checkForUpdate() or FlUpdater().showUpdateDialog() for manual checks (e.g. from a settings screen).
  • 🔄 Soft & Force Updates:
    • Soft updates: Optional update prompt with a "Later" button.
    • Force updates: Mandatory blocking dialog (canPop: false) when the installed version is below fl_updater_min_version.
  • Smart Snoozing: Dismissing a soft update snoozes it for a configurable duration (default: 3 days). Snooze is scoped per version, so releasing a newer update immediately prompts the user again.
  • 💰 Cost-Conscious Architecture: Designed for Firebase Remote Config usage-based pricing:
    • Debug mode disabled by default: Prevents development hot restarts from consuming Remote Config quotas.
    • Cached fetches: Configurable minimumFetchInterval (default: 1 hour) ensures throttled network requests.
  • 🏬 Native Store Redirection: Opens the platform's native store page (Apple App Store on iOS, Google Play Store on Android).
  • 🎨 Fully Customizable UI: Style the built-in Material dialog with FlUpdaterDialogStyle, or supply your own custom UI via dialogBuilder.

📦 Installation #

Add fl_updater and firebase_core to your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  fl_updater: ^0.0.1
  firebase_core: ^4.13.0 # or latest

Then ensure Firebase is initialized in your main() method:

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(const MyApp());
}

🔧 Firebase Remote Config Setup #

Configure update parameters in your Firebase Console → Remote Config.

1. Parameters #

Create the following two parameters:

Parameter Key Type Description Default Value
fl_updater_latest_version String The latest published version available in stores. "0.0.0"
fl_updater_min_version String The minimum supported version below which an update is forced. "0.0.0" (or Use in-app default)

2. Platform Conditional Values (fl_updater_android & fl_updater_ios) #

In Firebase Console, you can define targeting conditions:

  • fl_updater_android: Condition rule: device.os == 'android'
  • fl_updater_ios: Condition rule: device.os == 'ios'

Then add conditional values to fl_updater_latest_version and fl_updater_min_version:

  • For Android (fl_updater_android): e.g., latest version "2.5.0", min version "2.0.0"
  • For iOS (fl_updater_ios): e.g., latest version "2.4.0", min version "2.1.0"

Firebase Remote Config automatically evaluates these conditions per device on fetch and serves the appropriate values to fl_updater. All other keys are ignored.

3. Store Redirection Identifiers #

Store identifiers are configured directly in Dart code (via the wrapper or API call):

  • iOS (iosAppId): Numeric Apple App Store ID (e.g., '123456789').
  • Android (androidPackageId): Package name (e.g., 'com.example.app'). Defaults to the host app package name if omitted.

🚀 Usage #

Wrap your MaterialApp with FlUpdaterWrapper inside the builder callback:

import 'package:flutter/material.dart';
import 'package:fl_updater/fl_updater.dart';

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: (context, child) => FlUpdaterWrapper(
        iosAppId: '123456789',
        androidPackageId: 'com.example.app', // Optional: defaults to host package
        child: child!,
      ),
      home: const HomePage(),
    );
  }
}

This checks Remote Config once when the app is launched and displays the update dialog if an update is available and not currently snoozed.


2. Imperative / Manual Usage #

Trigger an update check manually, such as from an "About" or "Settings" screen:

final updater = FlUpdater();

// Check and show dialog if an update is available:
await updater.showUpdateDialog(
  context,
  iosAppId: '123456789',
  androidPackageId: 'com.example.app',
);

Or check status without displaying a UI:

final updater = FlUpdater();
final info = await updater.checkForUpdate(
  iosAppId: '123456789',
);

print('Current: ${info.currentVersion}');
print('Latest: ${info.latestVersion}');
print('Status: ${info.status}'); // UpdateStatus.none, soft, or force

🎨 Customization #

Styling the Default Dialog #

Customize colors, typography, buttons, shapes, and icons using FlUpdaterDialogStyle:

FlUpdaterWrapper(
  iosAppId: '123456789',
  title: 'Exciting New Update!',
  message: 'We added new features and performance improvements.',
  updateButtonText: 'Update Now',
  laterButtonText: 'Not Now',
  style: FlUpdaterDialogStyle(
    backgroundColor: Colors.white,
    titleStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
    messageStyle: const TextStyle(color: Colors.black87),
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
    icon: const Icon(Icons.system_update, size: 40, color: Colors.blue),
  ),
  child: child!,
)

Providing a Custom Update UI (dialogBuilder) #

Replace the built-in dialog entirely with your own custom widget or bottom sheet:

FlUpdaterWrapper(
  iosAppId: '123456789',
  dialogBuilder: (context, info, onUpdate, onLater) {
    final isForce = info.status == UpdateStatus.force;
    return AlertDialog(
      title: Text('Version ${info.latestVersion} Available'),
      content: Text('You are on ${info.currentVersion}. Please update to continue.'),
      actions: [
        if (!isForce)
          TextButton(
            onPressed: onLater, // Snoozes the update and dismisses dialog
            child: const Text('Remind me later'),
          ),
        ElevatedButton(
          onPressed: onUpdate, // Redirects to App/Play Store
          child: const Text('Update'),
        ),
      ],
    );
  },
  child: child!,
)

⏰ Snoozing Behavior #

When a soft (optional) update is available, tapping the "Later" button snoozes update prompts for snoozeDuration (default: 3 days).

  • The snooze is persisted locally via SharedPreferences.
  • Snoozes are scoped to the latest version. When you publish a newer version in Remote Config, the active snooze is automatically invalidated.
  • Force updates always bypass snooze and cannot be dismissed.
FlUpdaterWrapper(
  snoozeDuration: const Duration(days: 7), // Snooze for 1 week
  child: child!,
)

Resetting Snooze (For Debugging & Testing) #

You can automatically clear the snooze store on every app launch during development:

FlUpdaterWrapper(
  enableInDebugMode: true,
  clearSnoozeInDebugMode: true, // Clears previous snoozes on app launch in debug mode
  child: child!,
)

Or reset it manually via code:

// Globally clear active snooze state:
await FlUpdater.clearSnoozeStore();

// Or on an instance:
final updater = FlUpdater();
await updater.clearSnooze();

💰 Fetch Behavior & Quota Optimization #

To safeguard your Firebase Remote Config quota and avoid unintended billing:

  1. Disabled in Debug Mode by Default: kDebugMode disables Remote Config fetching entirely so that frequent hot restarts do not burn quotas. To test in debug mode or staging, pass enableInDebugMode: true:
    FlUpdaterWrapper(
      enableInDebugMode: true, // Opt-in for debug/staging builds
      child: child!,
    )
    
  2. Fetch Interval Throttling: The minimumFetchInterval (default: 1 hour) prevents frequent network queries. Repeated checks within this duration use the Firebase cached values.
    FlUpdaterWrapper(
      minimumFetchInterval: const Duration(minutes: 30),
      child: child!,
    )
    

⚡ Real-Time Remote Config Updates #

fl_updater listens to Firebase Remote Config updates in real time via onConfigUpdated:

  • When you publish changes to fl_updater_latest_version or fl_updater_min_version in the Firebase Console, the new config is activated immediately.
  • The update status is evaluated without waiting for minimumFetchInterval to expire.
  • Active snoozes are automatically cleared so users are prompted for the newly published version right away.
  • If the new version requires an update, the update dialog appears instantly for active users.

Real-time updates are enabled by default (listenForRealtimeUpdates: true). You can disable them if needed:

FlUpdaterWrapper(
  listenForRealtimeUpdates: false, // Only check on app launch
  child: child!,
)

🪵 Diagnostic Logging #

Logging is disabled by default to keep console and production outputs clean. You can enable diagnostic logging in several ways:

1. Globally #

void main() {
  FlUpdater.enableLogging = true;
  runApp(const MyApp());
}

2. Per Wrapper or Method Call #

FlUpdaterWrapper(
  enableLogging: true,
  child: child!,
)

📖 API Reference #

FlUpdaterWrapper & FlUpdater.showUpdateDialog #

Property Type Default Description
iosAppId String? null Numeric Apple App Store ID (required for iOS).
androidPackageId String? null Google Play Store package name (defaults to host app).
navigatorKey GlobalKey<NavigatorState>? null Optional explicit key for the root Navigator.
snoozeDuration Duration Duration(days: 3) How long to snooze soft updates when dismissed.
minimumFetchInterval Duration Duration(hours: 1) Throttling interval for Firebase Remote Config fetches.
enableInDebugMode bool false Enable checks in kDebugMode.
clearSnoozeInDebugMode bool false Automatically clear saved snooze state on launch in debug mode.
listenForRealtimeUpdates bool true Instantly activate and check updates on Remote Config publish.
enableLogging bool? null Enable diagnostic console logs for troubleshooting.
title String? 'Update available' Dialog title text.
message String? null Dialog message body text.
updateButtonText String? 'Update' Label for the update button.
laterButtonText String? 'Later' Label for the later/snooze button.
style FlUpdaterDialogStyle? null Style configuration for the default dialog.
dialogBuilder FlUpdaterDialogBuilder? null Custom builder to provide your own dialog UI.

📱 Example App #

Check out the example directory for a complete sample app demonstrating both automatic wrapper and manual checking with Firebase Remote Config.

To run the example app:

cd example
flutter run

📄 License #

This project is licensed under the MIT License - see the LICENSE file for details.

0
likes
160
points
290
downloads

Documentation

API reference

Publisher

verified publisherkishormainali.com

Weekly Downloads

Firebase Remote Config-driven app update dialog with automatic wrapper, snoozable soft updates, and native App/Play Store opening.

Repository (GitHub)
View/report issues

Topics

#app-update #remote-config #firebase #in-app-update #updater

License

MIT (license)

Dependencies

firebase_remote_config, flutter, package_info_plus, plugin_platform_interface, shared_preferences

More

Packages that depend on fl_updater

Packages that implement fl_updater