dart_patch_updater 0.1.0 copy "dart_patch_updater: ^0.1.0" to clipboard
dart_patch_updater: ^0.1.0 copied to clipboard

In-app updates for Flutter without the App Store. Update business logic, feature flags, and configs from your own server or GitHub Releases. Includes SHA-256 checksum + RSA signature verification with [...]

example/lib/main.dart

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

/// Example app demonstrating dart_patch_updater usage
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize update manager
  final updateManager = UpdateManager(
    config: const UpdateConfig(
      serverUrl: 'https://updates.example.com',
      appId: 'com.example.myapp',
      appVersion: '2.1.0',
      // Your RSA public key for signature verification
      publicKey: '''
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
''',
      checkOnLaunch: true,
      autoDownload: false,
      autoApply: false,
      maxBackupVersions: 3,
    ),
  );

  // Initialize and load existing patches
  await updateManager.initialize();

  runApp(MyApp(updateManager: updateManager));
}

class MyApp extends StatelessWidget {
  final UpdateManager updateManager;

  const MyApp({super.key, required this.updateManager});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Patch Updater Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: HomePage(updateManager: updateManager),
    );
  }
}

class HomePage extends StatefulWidget {
  final UpdateManager updateManager;

  const HomePage({super.key, required this.updateManager});

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

class _HomePageState extends State<HomePage> {
  UpdateState _state = UpdateState.initial();
  String _statusMessage = 'Ready';

  @override
  void initState() {
    super.initState();
    // Listen to state changes
    widget.updateManager.stateStream.listen((state) {
      setState(() => _state = state);
    });
  }

  Future<void> _checkForUpdates() async {
    setState(() => _statusMessage = 'Checking for updates...');

    final result = await widget.updateManager.checkForUpdates();

    if (result.hasError) {
      setState(() => _statusMessage = 'Error: ${result.error}');
    } else if (result.updateAvailable) {
      setState(
        () => _statusMessage = 'Update available: ${result.patch?.version}',
      );
      _showUpdateDialog(result.patch);
    } else {
      setState(() => _statusMessage = 'No updates available');
    }
  }

  Future<void> _downloadAndApply() async {
    setState(() => _statusMessage = 'Downloading...');

    final result = await widget.updateManager.downloadAndApply(
      onProgress: (progress) {
        setState(() => _statusMessage = progress.progressString);
      },
    );

    if (result.success) {
      setState(() => _statusMessage = 'Updated to ${result.newVersion}');

      if (result.requiresRestart) {
        _showRestartDialog();
      }
    } else {
      setState(() {
        _statusMessage = result.rolledBack
            ? 'Update failed, rolled back: ${result.error}'
            : 'Update failed: ${result.error}';
      });
    }
  }

  void _showUpdateDialog(dynamic patch) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Update Available'),
        content: Text(
          'Version ${patch?.version} is available.\n\n'
          '${patch?.releaseNotes ?? "No release notes"}',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Later'),
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.pop(context);
              _downloadAndApply();
            },
            child: const Text('Update Now'),
          ),
        ],
      ),
    );
  }

  void _showRestartDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Restart Required'),
        content: const Text(
          'The update has been applied. Please restart the app to use the new features.',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Later'),
          ),
          ElevatedButton(
            onPressed: () => widget.updateManager.restartApp(),
            child: const Text('Restart Now'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Patch Updater Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Status card
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Status',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    Text(
                      'Current Patch: ${_state.currentPatchVersion ?? "None"}',
                    ),
                    Text('Status: ${_state.status.name}'),
                    Text(_statusMessage),
                    if (_state.downloadProgress != null)
                      LinearProgressIndicator(value: _state.downloadProgress),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 16),

            // Action buttons
            ElevatedButton.icon(
              onPressed: _state.isBusy ? null : _checkForUpdates,
              icon: const Icon(Icons.refresh),
              label: const Text('Check for Updates'),
            ),

            const SizedBox(height: 8),

            if (_state.hasUpdate)
              ElevatedButton.icon(
                onPressed: _state.isBusy ? null : _downloadAndApply,
                icon: const Icon(Icons.download),
                label: const Text('Download & Apply'),
              ),

            const Spacer(),

            // Business logic demo
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Patchable Business Logic Demo',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    _buildFeatureFlagsDemo(),
                    const SizedBox(height: 8),
                    _buildPricingDemo(),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildFeatureFlagsDemo() {
    // Read feature flags from patched modules
    final isDarkModeEnabled =
        widget.updateManager.getModuleData('feature_flags')?['dark_mode'] ??
            false;
    final isPremiumEnabled = widget.updateManager.getModuleData(
          'feature_flags',
        )?['premium_features'] ??
        false;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('Feature Flags:'),
        Text('  • Dark Mode: ${isDarkModeEnabled ? "ON" : "OFF"}'),
        Text('  • Premium Features: ${isPremiumEnabled ? "ON" : "OFF"}'),
      ],
    );
  }

  Widget _buildPricingDemo() {
    // Read pricing rules from patched modules
    final pricingRules = widget.updateManager.getModuleData('pricing_rules');
    final discount = pricingRules?['default_discount'] ?? 0;
    final taxRate = pricingRules?['tax_rate'] ?? 0.1;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('Pricing Rules:'),
        Text('  • Default Discount: ${(discount * 100).toStringAsFixed(0)}%'),
        Text('  • Tax Rate: ${(taxRate * 100).toStringAsFixed(0)}%'),
      ],
    );
  }
}
0
likes
140
points
125
downloads

Publisher

unverified uploader

Weekly Downloads

In-app updates for Flutter without the App Store. Update business logic, feature flags, and configs from your own server or GitHub Releases. Includes SHA-256 checksum + RSA signature verification with auto-rollback.

Repository (GitHub)
View/report issues

Topics

#update #in-app-update #ota #hot-update #feature-flags

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

archive, crypto, dio, flutter, json_annotation, path, path_provider, pointycastle, rxdart

More

Packages that depend on dart_patch_updater