app_lifecycle_executor 0.0.3 copy "app_lifecycle_executor: ^0.0.3" to clipboard
app_lifecycle_executor: ^0.0.3 copied to clipboard

A Flutter package for executing lifecycle callbacks on app install and version upgrades across all platforms.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:app_lifecycle_executor/app_lifecycle_executor.dart';
// Version class is exported by app_lifecycle_executor, no need to import separately

// Example: Using enum for action IDs
enum ActionId {
  showV2Banner,
  initNewFeature,
  showNotification,
  manualTrigger;

  String get value => name;
}

// Example: Custom implementation of OneTimeAction
class ShowBannerAction implements OneTimeAction {
  @override
  String get id => ActionId.showV2Banner.value;

  @override
  Future<void> callback() async {
    print('πŸŽ‰ Showing banner for v2.0.0 features (from custom class)');
    await Future.delayed(Duration(milliseconds: 100));
  }
}

// Example: Another custom implementation
class InitFeatureAction implements OneTimeAction {
  @override
  String get id => ActionId.initNewFeature.value;

  @override
  Future<void> callback() async {
    print('πŸš€ Initializing new feature (from custom class)');
    await Future.delayed(Duration(milliseconds: 100));
  }
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Option 1: Register callbacks before init
  AppLifecycleExecutor.onFirstInstall(() async {
    print('πŸŽ‰ App installed for the first time!');
    // Example: Initialize default settings
    // Example: Show onboarding screens
    // Example: Create initial database
    await Future.delayed(Duration(milliseconds: 100));
    print('βœ… First install setup completed');
  });

  AppLifecycleExecutor.onVersionChanged((oldVersion, newVersion) async {
    print('πŸ”„ App upgraded from $oldVersion to $newVersion');

    // Example: Handle version-specific migrations using Version comparison
    if (oldVersion < Version(2, 0, 0) && newVersion >= Version(2, 0, 0)) {
      print('Migrating to major version 2.x...');
      await migrateDatabase();
    }

    // Example: Check specific version changes
    if (oldVersion.major == 1 && newVersion.major == 2) {
      print('Major version upgrade from 1.x to 2.x');
    }

    // Example: Show "What's New" dialog
    print('βœ… Migration completed');
  });

  AppLifecycleExecutor.onError((error, stackTrace) {
    print('❌ Error during lifecycle callback: $error');
    // In production, you might want to log this to your error tracking service
  });

  // Register one-time actions that should run once per version upgrade
  AppLifecycleExecutor.runOnce([
    // Using custom implementations with enum-based IDs
    ShowBannerAction(),
    InitFeatureAction(),

    // Using SimpleOneTimeAction for inline actions
    SimpleOneTimeAction(
      id: ActionId.showNotification.value,
      callback: () async {
        print('πŸ“’ Showing notification for v3.0.0 major update');
        await Future.delayed(Duration(milliseconds: 100));
      },
    ),
  ]);

  // Initialize the app lifecycle executor
  await AppLifecycleExecutor.init();

  // Option 2: Pass callbacks directly to init (overrides registered callbacks)
  // await AppLifecycleExecutor.init(
  //   firstInstallCallback: () async {
  //     print('πŸŽ‰ App installed for the first time!');
  //   },
  //   onVersionChanged: (oldVersion, newVersion) async {
  //     print('πŸ”„ App upgraded from $oldVersion to $newVersion');
  //   },
  //   onError: (error, stackTrace) {
  //     print('❌ Error during lifecycle callback: $error');
  //   },
  // );

  runApp(const MyApp());
}

Future<void> migrateDatabase() async {
  // Simulate database migration
  await Future.delayed(Duration(milliseconds: 200));
  print('Database migration completed');
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'App Lifecycle Executor Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}

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

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

class _HomePageState extends State<HomePage> {
  String _currentVersion = 'Loading...';
  String? _lastVersion = 'Loading...';
  bool _firstInstallExecuted = false;
  List<String> _executedActionIds = [];

  @override
  void initState() {
    super.initState();
    _loadLifecycleInfo();
  }

  Future<void> _loadLifecycleInfo() async {
    final currentVersion = await AppLifecycleExecutor.getCurrentVersion();
    final lastVersion = await AppLifecycleExecutor.getLastVersion();
    final firstInstallExecuted =
        await AppLifecycleExecutor.hasFirstInstallExecuted();
    final executedActionIds = await AppLifecycleExecutor.getExecutedActionIds();

    setState(() {
      _currentVersion = currentVersion;
      _lastVersion = lastVersion ?? 'None (first install)';
      _firstInstallExecuted = firstInstallExecuted;
      _executedActionIds = executedActionIds;
    });
  }

  Future<void> _resetLifecycle() async {
    await AppLifecycleExecutor.reset();
    await _loadLifecycleInfo();

    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Lifecycle state reset!')),
      );
    }
  }

  Future<void> _triggerOneTimeAction() async {
    final wasExecuted = await AppLifecycleExecutor.runOnceNow(
      SimpleOneTimeAction(
        id: ActionId.manualTrigger.value,
        callback: () async {
          print('🎯 Manual one-time action triggered!');
          await Future.delayed(Duration(milliseconds: 100));
        },
      ),
    );

    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(
            wasExecuted
                ? 'βœ… Action executed successfully!'
                : 'ℹ️ Action already executed (skipped)',
          ),
          backgroundColor: wasExecuted ? Colors.green : Colors.orange,
        ),
      );
    }

    // Refresh the UI to show updated executed actions
    await _loadLifecycleInfo();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('App Lifecycle Executor Demo'),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(24.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'Lifecycle Information',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 24),
              _InfoRow(
                label: 'Current Version:',
                value: _currentVersion,
              ),
              const SizedBox(height: 12),
              _InfoRow(
                label: 'Last Stored Version:',
                value: _lastVersion ?? 'None',
              ),
              const SizedBox(height: 12),
              _InfoRow(
                label: 'First Install Executed:',
                value: _firstInstallExecuted ? 'Yes' : 'No',
              ),
              const SizedBox(height: 12),
              _InfoRow(
                label: 'Is First Install (sync):',
                value: AppLifecycleExecutor.isFirstInstall ? 'Yes' : 'No',
                highlight: AppLifecycleExecutor.isFirstInstall,
              ),
              const SizedBox(height: 12),
              _InfoRow(
                label: 'Is Initialized:',
                value: AppLifecycleExecutor.isInitialized ? 'Yes' : 'No',
              ),
              const SizedBox(height: 24),
              const Text(
                'One-Time Actions',
                style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 12),
              _InfoRow(
                label: 'Executed Actions:',
                value: _executedActionIds.isEmpty
                    ? 'None'
                    : _executedActionIds.join(', '),
              ),
              const SizedBox(height: 24),
              // Example: Conditional widget based on first install status
              if (AppLifecycleExecutor.isFirstInstall)
                Container(
                  padding: const EdgeInsets.all(16),
                  decoration: BoxDecoration(
                    color: Colors.blue.shade50,
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(color: Colors.blue.shade200),
                  ),
                  child: Row(
                    children: [
                      Icon(Icons.celebration, color: Colors.blue.shade700),
                      const SizedBox(width: 12),
                      Expanded(
                        child: Text(
                          'Welcome! This is your first time using the app.',
                          style: TextStyle(
                            color: Colors.blue.shade900,
                            fontWeight: FontWeight.w500,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              const SizedBox(height: 32),
              Center(
                child: ElevatedButton.icon(
                  onPressed: _triggerOneTimeAction,
                  icon: const Icon(Icons.play_arrow),
                  label: const Text('Run One-Time Action'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 24,
                      vertical: 12,
                    ),
                  ),
                ),
              ),
              const SizedBox(height: 16),
              Center(
                child: ElevatedButton(
                  onPressed: _resetLifecycle,
                  child: const Text('Reset Lifecycle State (for testing)'),
                ),
              ),
              const SizedBox(height: 16),
              const Center(
                child: Text(
                  'After resetting, restart the app to trigger\nfirst install callback again.',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 12, color: Colors.grey),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _InfoRow extends StatelessWidget {
  final String label;
  final String value;
  final bool highlight;

  const _InfoRow({
    required this.label,
    required this.value,
    this.highlight = false,
  });

  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SizedBox(
          width: 180,
          child: Text(
            label,
            style: const TextStyle(
              fontWeight: FontWeight.bold,
              fontSize: 16,
            ),
          ),
        ),
        Expanded(
          child: Text(
            value,
            style: TextStyle(
              fontSize: 16,
              color: highlight ? Colors.blue.shade700 : null,
              fontWeight: highlight ? FontWeight.bold : null,
            ),
          ),
        ),
      ],
    );
  }
}
2
likes
160
points
15
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for executing lifecycle callbacks on app install and version upgrades across all platforms.

Homepage

Documentation

API reference

License

MIT (license)

Dependencies

flutter, package_info_plus, shared_preferences, version

More

Packages that depend on app_lifecycle_executor