update_checker_plus 1.0.0 copy "update_checker_plus: ^1.0.0" to clipboard
update_checker_plus: ^1.0.0 copied to clipboard

The most powerful, developer-friendly app update checker for Flutter. Supports Play Store, App Store, Firebase Remote Config, Supabase, Appwrite, force updates, maintenance mode, beautiful adaptive di [...]

example/lib/main.dart

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

void main() {
  // ── Global configuration (call once before runApp) ──────────────────────
  UpdateChecker.configure(
    UpdateConfig(
      // Replace with your actual package name / App Store ID.
      androidPackageName: 'com.example.myapp',
      iosAppId: '123456789',
      debugMode: true,
      // Check at most once per day.
      checkInterval: const Duration(hours: 24),
    ),
  );

  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'UpdateCheckerPlus Demo',
      theme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        brightness: Brightness.dark,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  UpdateResult? _lastResult;
  bool _isChecking = false;

  @override
  void initState() {
    super.initState();
    // Auto-check on startup.
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _checkDefault();
    });
  }

  // ── 1. Minimal one-liner ──────────────────────────────────────────────────
  Future<void> _checkDefault() async {
    setState(() => _isChecking = true);
    final result = await UpdateChecker.check(context: context);
    if (mounted) setState(() {
      _lastResult = result;
      _isChecking = false;
    });
  }

  // ── 2. HTTP Remote Config ─────────────────────────────────────────────────
  Future<void> _checkHttpRemoteConfig() async {
    setState(() => _isChecking = true);
    final result = await UpdateChecker.check(
      context: context,
      config: UpdateConfig(
        remoteConfigSource: HttpRemoteConfigSource(
          url: 'https://yourapi.com/app-update-config.json',
        ),
        debugMode: true,
        onUpdateAvailable: (info) async {
          debugPrint('Update available: ${info.latestVersion}');
          return true; // Show default UI.
        },
        onUpdateAccepted: (info) => debugPrint('User accepted update'),
        onUpdateIgnored: (info) => debugPrint('User ignored update'),
      ),
    );
    if (mounted) setState(() {
      _lastResult = result;
      _isChecking = false;
    });
  }

  // ── 3. Force update simulation ────────────────────────────────────────────
  Future<void> _simulateForceUpdate() async {
    setState(() => _isChecking = true);

    // We build a mock UpdateInfo directly for demo purposes.
    // In production, your remote config drives this automatically.
    final info = UpdateInfo(
      latestVersion: '9.9.9',
      currentVersion: '1.0.0',
      updateType: UpdateType.force,
      releaseNotes: '• Critical security patch\n• Performance improvements\n• New dashboard',
      storeUrl: 'https://play.google.com/store',
      canDismiss: false,
    );

    await showUpdateDialog(
      context: context,
      info: info,
      strings: const UpdateStrings(),
    );

    if (mounted) setState(() => _isChecking = false);
  }

  // ── 4. Soft update — bottom sheet style ───────────────────────────────────
  Future<void> _simulateSoftUpdateSheet() async {
    setState(() => _isChecking = true);

    final info = UpdateInfo(
      latestVersion: '2.5.0',
      currentVersion: '2.3.1',
      updateType: UpdateType.soft,
      releaseNotes: '• Brand-new onboarding flow\n• Dark mode fixes\n• 40% faster startup',
      storeUrl: 'https://play.google.com/store',
    );

    await showUpdateBottomSheet(
      context: context,
      info: info,
      strings: const UpdateStrings(),
      theme: UpdateTheme.branded(Theme.of(context).colorScheme.primary),
    );

    if (mounted) setState(() => _isChecking = false);
  }

  // ── 5. Maintenance mode ───────────────────────────────────────────────────
  Future<void> _simulateMaintenance() async {
    setState(() => _isChecking = true);

    final info = UpdateInfo(
      latestVersion: '2.0.0',
      currentVersion: '2.0.0',
      updateType: UpdateType.maintenance,
      maintenanceMessage:
          'We are performing scheduled maintenance.\nWe\'ll be back in ~30 minutes.',
      canDismiss: false,
    );

    await showUpdateDialog(
      context: context,
      info: info,
      strings: const UpdateStrings(),
    );

    if (mounted) setState(() => _isChecking = false);
  }

  // ── 6. Custom UI ──────────────────────────────────────────────────────────
  Future<void> _checkWithCustomUi() async {
    setState(() => _isChecking = true);

    final result = await UpdateChecker.check(
      context: context,
      config: UpdateConfig(
        androidPackageName: 'com.example.myapp',
        customUiBuilder: UpdateUiBuilder(
          builder: (ctx, info, onUpdate, onDismiss) {
            return _CustomUpdateCard(
              info: info,
              onUpdate: onUpdate,
              onDismiss: onDismiss,
            );
          },
        ),
      ),
    );

    if (mounted) setState(() {
      _lastResult = result;
      _isChecking = false;
    });
  }

  // ── 7. Dark theme ─────────────────────────────────────────────────────────
  Future<void> _simulateDarkTheme() async {
    setState(() => _isChecking = true);

    final info = UpdateInfo(
      latestVersion: '3.0.0',
      currentVersion: '2.8.0',
      updateType: UpdateType.soft,
      releaseNotes: '• Redesigned home screen\n• New widgets\n• Stability improvements',
      storeUrl: 'https://apps.apple.com/app/id123',
    );

    await showUpdateDialog(
      context: context,
      info: info,
      strings: const UpdateStrings(),
      theme: UpdateTheme.dark(),
    );

    if (mounted) setState(() => _isChecking = false);
  }

  // ── 8. Silent check ───────────────────────────────────────────────────────
  Future<void> _silentCheck() async {
    setState(() => _isChecking = true);
    final result = await UpdateChecker.checkSilently(
      config: UpdateConfig(
        androidPackageName: 'com.example.myapp',
        debugMode: true,
      ),
    );
    if (mounted) setState(() {
      _lastResult = result;
      _isChecking = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('UpdateCheckerPlus Demo'),
        centerTitle: true,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // Status card
          if (_lastResult != null)
            _StatusCard(result: _lastResult!),
          const SizedBox(height: 16),

          _Section(
            title: '🚀 Quick Start',
            children: [
              _DemoTile(
                title: 'One-liner check (default)',
                subtitle: 'UpdateChecker.check(context: context)',
                onTap: _isChecking ? null : _checkDefault,
              ),
              _DemoTile(
                title: 'Silent check (no UI)',
                subtitle: 'UpdateChecker.checkSilently()',
                onTap: _isChecking ? null : _silentCheck,
              ),
            ],
          ),

          _Section(
            title: '🌐 Remote Config',
            children: [
              _DemoTile(
                title: 'HTTP endpoint',
                subtitle: 'HttpRemoteConfigSource',
                onTap: _isChecking ? null : _checkHttpRemoteConfig,
              ),
            ],
          ),

          _Section(
            title: '🎨 UI Styles',
            children: [
              _DemoTile(
                title: 'Force update dialog',
                subtitle: 'Material 3, non-dismissible',
                onTap: _isChecking ? null : _simulateForceUpdate,
              ),
              _DemoTile(
                title: 'Soft update — bottom sheet',
                subtitle: 'Branded theme',
                onTap: _isChecking ? null : _simulateSoftUpdateSheet,
              ),
              _DemoTile(
                title: 'Maintenance mode',
                subtitle: 'Non-dismissible dialog',
                onTap: _isChecking ? null : _simulateMaintenance,
              ),
              _DemoTile(
                title: 'Dark theme dialog',
                subtitle: 'UpdateTheme.dark()',
                onTap: _isChecking ? null : _simulateDarkTheme,
              ),
              _DemoTile(
                title: 'Custom UI builder',
                subtitle: 'Your own widget',
                onTap: _isChecking ? null : _checkWithCustomUi,
              ),
            ],
          ),

          _Section(
            title: '🔧 Utilities',
            children: [
              _DemoTile(
                title: 'Clear skipped version',
                subtitle: 'Resets "Don\'t ask again" state',
                onTap: () async {
                  await UpdateChecker.clearSkippedVersion();
                  if (context.mounted) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      const SnackBar(content: Text('Skipped version cleared.')),
                    );
                  }
                },
              ),
            ],
          ),

          if (_isChecking)
            const Padding(
              padding: EdgeInsets.all(24),
              child: Center(child: CircularProgressIndicator()),
            ),
        ],
      ),
    );
  }
}

// ---------------------------------------------------------------------------
// Supporting widgets
// ---------------------------------------------------------------------------

class _Section extends StatelessWidget {
  final String title;
  final List<Widget> children;

  const _Section({required this.title, required this.children});

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 8),
          child: Text(
            title,
            style: Theme.of(context).textTheme.titleMedium?.copyWith(
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        Card(
          clipBehavior: Clip.antiAlias,
          child: Column(children: children),
        ),
        const SizedBox(height: 16),
      ],
    );
  }
}

class _DemoTile extends StatelessWidget {
  final String title;
  final String subtitle;
  final VoidCallback? onTap;

  const _DemoTile({
    required this.title,
    required this.subtitle,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(title),
      subtitle: Text(
        subtitle,
        style: TextStyle(
          fontFamily: 'monospace',
          fontSize: 11,
          color: Theme.of(context).colorScheme.outline,
        ),
      ),
      trailing: const Icon(Icons.chevron_right),
      onTap: onTap,
    );
  }
}

class _StatusCard extends StatelessWidget {
  final UpdateResult result;

  const _StatusCard({required this.result});

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final isError = result.status == UpdateStatus.error;
    final color = isError ? cs.errorContainer : cs.tertiaryContainer;
    final textColor = isError ? cs.onErrorContainer : cs.onTertiaryContainer;

    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: color,
        borderRadius: BorderRadius.circular(12),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            'Last check result',
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: textColor,
            ),
          ),
          const SizedBox(height: 4),
          Text(
            'Status: ${result.status.name}',
            style: TextStyle(color: textColor, fontSize: 13),
          ),
          if (result.updateInfo != null)
            Text(
              '${result.updateInfo!.currentVersion} → ${result.updateInfo!.latestVersion}',
              style: TextStyle(
                fontFamily: 'monospace',
                color: textColor,
                fontSize: 13,
              ),
            ),
          if (result.error != null)
            Text(
              '${result.error}',
              style: TextStyle(color: textColor, fontSize: 12),
            ),
        ],
      ),
    );
  }
}

/// A fully custom update card for the custom UI builder demo.
class _CustomUpdateCard extends StatelessWidget {
  final UpdateInfo info;
  final VoidCallback onUpdate;
  final void Function(String?) onDismiss;

  const _CustomUpdateCard({
    required this.info,
    required this.onUpdate,
    required this.onDismiss,
  });

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return Dialog(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  colors: [cs.primary, cs.tertiary],
                ),
                shape: BoxShape.circle,
            ),
              child: const Icon(Icons.rocket_launch_rounded,
                  color: Colors.white, size: 40),
            ),
            const SizedBox(height: 20),
            Text(
              '✨ v${info.latestVersion} is here!',
              style: Theme.of(context)
                  .textTheme
                  .headlineSmall
                  ?.copyWith(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            Text(
              info.releaseNotes ?? 'New features and bug fixes.',
              textAlign: TextAlign.center,
              style: Theme.of(context).textTheme.bodyMedium,
            ),
            const SizedBox(height: 24),
            ElevatedButton.icon(
              onPressed: onUpdate,
              icon: const Icon(Icons.download_rounded),
              label: const Text('Update Now'),
              style: ElevatedButton.styleFrom(
                minimumSize: const Size.fromHeight(48),
                shape: const StadiumBorder(),
              ),
            ),
            TextButton(
              onPressed: () => onDismiss(null),
              child: const Text('Maybe later'),
            ),
          ],
        ),
      ),
    );
  }
}
5
likes
140
points
17
downloads

Documentation

API reference

Publisher

verified publisherhadiapp.me

Weekly Downloads

The most powerful, developer-friendly app update checker for Flutter. Supports Play Store, App Store, Firebase Remote Config, Supabase, Appwrite, force updates, maintenance mode, beautiful adaptive dialogs, in-app updates, and much more — with a one-line API.

Repository (GitHub)
View/report issues

Topics

#update #in-app-update #version-check #remote-config #play-store

License

MIT (license)

Dependencies

connectivity_plus, flutter, http, package_info_plus, shared_preferences, url_launcher

More

Packages that depend on update_checker_plus