in_app_update_flutter 2.0.0 copy "in_app_update_flutter: ^2.0.0" to clipboard
in_app_update_flutter: ^2.0.0 copied to clipboard

A Flutter plugin to show an in-app update prompt using the native App Store product page on iOS, keeping users inside your app.

example/lib/main.dart

import 'dart:io';

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

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Platform.isAndroid
          ? const AndroidUpdateExample()
          : const IosUpdateExample(),
    );
  }
}

// =============================================================================
// iOS Example
// =============================================================================

/// iOS: Shows the App Store product page overlay via StoreKit.
///
/// On iOS there is no "check for update" API — you simply present the
/// App Store page and let the user decide.
class IosUpdateExample extends StatelessWidget {
  const IosUpdateExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('In-App Update (iOS)')),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            try {
              await InAppUpdateFlutter()
                  .showUpdateForIos(appStoreId: '544007664');
            } catch (e) {
              if (context.mounted) {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('Error: $e')),
                );
              }
            }
          },
          child: const Text('Show App Store Update'),
        ),
      ),
    );
  }
}

// =============================================================================
// Android Example
// =============================================================================

/// Android: Demonstrates the full Play Core In-App Updates flow.
///
/// The recommended flow is:
///
/// 1. **Check** — Call [checkUpdateAndroid] to query Play Core for update info.
/// 2. **Decide** — Inspect [AppUpdateInfoAndroid] to determine if an update is
///    available, what types are allowed, and whether priority/staleness warrant
///    prompting the user.
/// 3. **Start** — Call [startImmediateUpdateAndroid] or [startFlexibleUpdateAndroid]
///    based on the update info.
/// 4. **Monitor** (flexible only) — Listen to [installStateStreamAndroid] for
///    download progress.
/// 5. **Complete** (flexible only) — Call [completeUpdateAndroid] once the
///    download reaches [InstallStatusAndroid.downloaded] to trigger an app restart.
class AndroidUpdateExample extends StatefulWidget {
  const AndroidUpdateExample({super.key});

  @override
  State<AndroidUpdateExample> createState() => _AndroidUpdateExampleState();
}

class _AndroidUpdateExampleState extends State<AndroidUpdateExample> {
  final _plugin = InAppUpdateFlutter();
  AppUpdateInfoAndroid? _updateInfo;
  String _status = 'Press "Check for Update" to start.';

  // ---------------------------------------------------------------------------
  // Step 1: Check for update
  // ---------------------------------------------------------------------------

  /// Always start here. This queries Play Core and returns metadata about the
  /// available update (if any). No UI is shown to the user at this point.
  Future<void> _checkForUpdate() async {
    setState(() => _status = 'Checking for update...');
    try {
      final info = await _plugin.checkUpdateAndroid();
      setState(() {
        _updateInfo = info;
        _status =
            info.updateAvailability == UpdateAvailabilityAndroid.updateAvailable
                ? 'Update available! Choose an update type below.'
                : 'No update available (${info.updateAvailability.name}).';
      });
    } catch (e) {
      setState(() {
        _updateInfo = null;
        _status = 'Check failed: $e';
      });
    }
  }

  // ---------------------------------------------------------------------------
  // Step 2 & 3: Start the update (only after checking)
  // ---------------------------------------------------------------------------

  /// Immediate update: full-screen, blocking UI managed by Google Play.
  /// The user must accept or cancel. The app restarts automatically on success.
  Future<void> _startImmediateUpdate() async {
    setState(() => _status = 'Starting immediate update...');
    try {
      final result = await _plugin.startImmediateUpdateAndroid();
      setState(() => _status = 'Immediate update result: ${result.name}');
    } catch (e) {
      setState(() => _status = 'Immediate update error: $e');
    }
  }

  /// Flexible update: downloads in the background while the user keeps using
  /// the app. You must listen to [installStateStreamAndroid] and call
  /// [completeUpdateAndroid] when the download finishes.
  Future<void> _startFlexibleUpdate() async {
    setState(() => _status = 'Starting flexible update...');
    try {
      final result = await _plugin.startFlexibleUpdateAndroid();
      setState(() => _status = result == UpdateResultAndroid.success
          ? 'Flexible update started — monitor progress below.'
          : 'Flexible update result: ${result.name}');
    } catch (e) {
      setState(() => _status = 'Flexible update error: $e');
    }
  }

  // ---------------------------------------------------------------------------
  // Step 4 & 5: Complete a flexible update after download
  // ---------------------------------------------------------------------------

  /// Call this only after [installStateStreamAndroid] reports
  /// [InstallStatusAndroid.downloaded]. This triggers an app restart to
  /// install the update.
  Future<void> _completeUpdate() async {
    setState(() => _status = 'Completing update — app will restart...');
    try {
      await _plugin.completeUpdateAndroid();
    } catch (e) {
      setState(() => _status = 'Complete update error: $e');
    }
  }

  // ---------------------------------------------------------------------------
  // UI
  // ---------------------------------------------------------------------------

  @override
  Widget build(BuildContext context) {
    final hasChecked = _updateInfo != null;
    final isUpdateAvailable = _updateInfo?.updateAvailability ==
        UpdateAvailabilityAndroid.updateAvailable;

    return Scaffold(
      appBar: AppBar(title: const Text('In-App Update (Android)')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // Status
          Text(_status, style: Theme.of(context).textTheme.bodyLarge),
          const SizedBox(height: 16),

          // Update info (shown after check)
          if (hasChecked) ...[
            _InfoCard(info: _updateInfo!),
            const SizedBox(height: 16),
          ],

          // Step 1: Check
          _StepButton(
            step: 1,
            label: 'Check for Update',
            onPressed: _checkForUpdate,
          ),

          // Step 2: Immediate (enabled only if update is available & allowed)
          _StepButton(
            step: 2,
            label: 'Start Immediate Update',
            onPressed: isUpdateAvailable &&
                    (_updateInfo?.isImmediateUpdateAllowed ?? false)
                ? _startImmediateUpdate
                : null,
          ),

          // Step 3: Flexible (enabled only if update is available & allowed)
          _StepButton(
            step: 3,
            label: 'Start Flexible Update',
            onPressed: isUpdateAvailable &&
                    (_updateInfo?.isFlexibleUpdateAllowed ?? false)
                ? _startFlexibleUpdate
                : null,
          ),

          // Step 4: Monitor flexible progress
          const SizedBox(height: 16),
          Text(
            'Step 4 — Flexible Update Progress',
            style: Theme.of(context).textTheme.titleSmall,
          ),
          const SizedBox(height: 8),
          _FlexibleProgressWidget(plugin: _plugin),

          // Step 5: Complete
          const SizedBox(height: 8),
          _StepButton(
            step: 5,
            label: 'Complete Update (restart app)',
            onPressed: _completeUpdate,
          ),
        ],
      ),
    );
  }
}

// =============================================================================
// Helper widgets
// =============================================================================

class _StepButton extends StatelessWidget {
  const _StepButton({
    required this.step,
    required this.label,
    required this.onPressed,
  });

  final int step;
  final String label;
  final VoidCallback? onPressed;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: ElevatedButton(
        onPressed: onPressed,
        child: Text('Step $step: $label'),
      ),
    );
  }
}

class _InfoCard extends StatelessWidget {
  const _InfoCard({required this.info});

  final AppUpdateInfoAndroid info;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Update Info',
              style: Theme.of(context).textTheme.titleSmall,
            ),
            const SizedBox(height: 8),
            Text('Availability: ${info.updateAvailability.name}'),
            Text('Version code: ${info.availableVersionCode ?? "N/A"}'),
            Text('Priority: ${info.updatePriority}'),
            Text('Staleness: ${info.clientVersionStalenessDays ?? "N/A"} days'),
            Text('Immediate allowed: ${info.isImmediateUpdateAllowed}'),
            Text('Flexible allowed: ${info.isFlexibleUpdateAllowed}'),
            Text('Install status: ${info.installStatus.name}'),
          ],
        ),
      ),
    );
  }
}

class _FlexibleProgressWidget extends StatelessWidget {
  const _FlexibleProgressWidget({required this.plugin});

  final InAppUpdateFlutter plugin;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<InstallStateAndroid>(
      stream: plugin.installStateStreamAndroid,
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return const Text('No active download.');
        }
        final state = snapshot.data!;
        final progress = state.totalBytesToDownload > 0
            ? state.bytesDownloaded / state.totalBytesToDownload
            : 0.0;
        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Status: ${state.status.name}'),
            const SizedBox(height: 4),
            LinearProgressIndicator(value: progress),
            const SizedBox(height: 4),
            Text(
              '${state.bytesDownloaded} / ${state.totalBytesToDownload} bytes',
            ),
            if (state.status == InstallStatusAndroid.downloaded)
              const Padding(
                padding: EdgeInsets.only(top: 8),
                child: Text(
                  'Download complete — press "Complete Update" to install.',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
              ),
          ],
        );
      },
    );
  }
}
11
likes
160
points
279
downloads

Documentation

API reference

Publisher

verified publisheraxions.tech

Weekly Downloads

A Flutter plugin to show an in-app update prompt using the native App Store product page on iOS, keeping users inside your app.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on in_app_update_flutter

Packages that implement in_app_update_flutter