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

A production-ready Flutter plugin for foreground/background detection, Android foreground services, background task scheduling, and lifecycle management.

example/lib/main.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:foreground_background_manager/foreground_background_manager.dart';

// ─── Top-level callback dispatcher (MUST be top-level) ────────────────────────
@pragma('vm:entry-point')
void callbackDispatcher() {
  ForegroundBackgroundManager.backgroundCallbackDispatcher();
}

// ─── Top-level background task (MUST be top-level) ────────────────────────────
@pragma('vm:entry-point')
Future<void> _syncCallback() async {
  debugPrint('[FBM Example] Background sync running...');
  await Future.delayed(const Duration(seconds: 1));
  debugPrint('[FBM Example] Background sync done.');
}

// ─── Main ──────────────────────────────────────────────────────────────────────
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await ForegroundBackgroundManager.initialize(
    callbackDispatcher: callbackDispatcher,
  );

  runApp(const FBMExampleApp());
}

// ─── App ───────────────────────────────────────────────────────────────────────
class FBMExampleApp extends StatelessWidget {
  const FBMExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FBM Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        brightness: Brightness.dark,
        scaffoldBackgroundColor: const Color(0xFF07071A),
        colorScheme: ColorScheme.dark(
          primary: const Color(0xFF7C3AED),
          secondary: const Color(0xFF4F46E5),
          surface: const Color(0xFF111132),
        ),
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

// ─── Home Screen ───────────────────────────────────────────────────────────────
class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});
  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {

  // State
  FBMLifecycleState _lifecycleState = FBMLifecycleState.resumed;
  bool _bgTaskRunning   = false;
  bool _fgSvcRunning    = false;
  bool _batteryIgnored  = false;
  final List<_Log> _logs = [];

  // Subscriptions
  final _subs = <StreamSubscription>[];

  // ─── Lifecycle ─────────────────────────────────────────────────────────
  @override
  void initState() {
    super.initState();
    _setupStreams();
    _checkBatteryStatus();
    _addLog('Plugin initialized ✅', LogLevel.success);
  }

  void _setupStreams() {
    _subs.add(ForegroundBackgroundManager.lifecycleStream.listen((s) {
      setState(() => _lifecycleState = s);
      _addLog('Lifecycle → ${s.label}', LogLevel.lifecycle);
    }));
    _subs.add(ForegroundBackgroundManager.onForeground.listen((_) {
      _addLog('App entered FOREGROUND 🌅', LogLevel.success);
    }));
    _subs.add(ForegroundBackgroundManager.onBackground.listen((_) {
      _addLog('App entered BACKGROUND 🌙', LogLevel.warning);
    }));
    _subs.add(ForegroundBackgroundManager.onAppKilled.listen((_) {
      _addLog('App KILLED — service will restart 🔄', LogLevel.warning);
    }));
    _subs.add(ForegroundBackgroundManager.onNotificationClick.listen((_) {
      _addLog('Notification tapped 🔔', LogLevel.info);
    }));
  }

  @override
  void dispose() {
    for (final s in _subs) { s.cancel(); }
    ForegroundBackgroundManager.dispose();
    super.dispose();
  }

  // ─── Actions ───────────────────────────────────────────────────────────
  Future<void> _startBgTask() async {
    try {
      await ForegroundBackgroundManager.registerBackgroundTask(
        taskId: 'sync_task',
        interval: const Duration(minutes: 15),
        callback: _syncCallback,
      );
      setState(() => _bgTaskRunning = true);
      _addLog('Background task registered 🔄', LogLevel.success);
    } catch (e) {
      _addLog('Register task failed: $e', LogLevel.error);
    }
  }

  Future<void> _stopBgTask() async {
    try {
      await ForegroundBackgroundManager.cancelBackgroundTask('sync_task');
      setState(() => _bgTaskRunning = false);
      _addLog('Background task cancelled ⛔', LogLevel.warning);
    } catch (e) {
      _addLog('Cancel task failed: $e', LogLevel.error);
    }
  }

  Future<void> _startFgService() async {
    try {
      // Request notification permission first (Android 13+)
      final status = await ForegroundBackgroundManager.permissions
          .requestNotificationPermission();
      _addLog('Notification permission: $status', LogLevel.info);
      if (status == PermissionStatus.denied ||
          status == PermissionStatus.permanentlyDenied) {
        _addLog('Permission denied — cannot start service', LogLevel.error);
        return;
      }

      await ForegroundBackgroundManager.startForegroundService(
        title: 'FBM Running',
        content: 'Service is active — even if app is killed',
        priority: NotificationPriority.low,
        autoRestart: true,
      );
      setState(() => _fgSvcRunning = true);
      _addLog('Foreground service started 🚀', LogLevel.success);
      _addLog('Kill the app — service will restart automatically!', LogLevel.info);
    } catch (e) {
      _addLog('Start service failed: $e', LogLevel.error);
    }
  }

  Future<void> _stopFgService() async {
    try {
      await ForegroundBackgroundManager.stopForegroundService();
      setState(() => _fgSvcRunning = false);
      _addLog('Foreground service stopped 🛑', LogLevel.warning);
    } catch (e) {
      _addLog('Stop service failed: $e', LogLevel.error);
    }
  }

  Future<void> _checkBatteryStatus() async {
    final ignored = await ForegroundBackgroundManager.permissions
        .isBatteryOptimizationIgnored();
    setState(() => _batteryIgnored = ignored);
  }

  Future<void> _requestBatteryIgnore() async {
    _addLog('Opening battery optimization settings...', LogLevel.info);
    await ForegroundBackgroundManager.permissions.requestIgnoreBatteryOptimization();
    await Future.delayed(const Duration(seconds: 1));
    await _checkBatteryStatus();
    if (_batteryIgnored) {
      _addLog('Battery optimization ignored ✅ — service more reliable!', LogLevel.success);
    } else {
      _addLog('Battery optimization still active ⚠️', LogLevel.warning);
    }
  }

  Future<void> _updateNotification() async {
    if (!_fgSvcRunning) {
      _addLog('Service not running', LogLevel.error);
      return;
    }
    await ForegroundBackgroundManager.updateForegroundNotification(
      title: 'FBM Updated',
      content: 'Notification updated at ${TimeOfDay.now().format(context)}',
    );
    _addLog('Notification updated ✏️', LogLevel.info);
  }

  void _clearLogs() => setState(() => _logs.clear());

  void _addLog(String msg, LogLevel lvl) {
    setState(() {
      _logs.insert(0, _Log(msg, lvl, DateTime.now()));
      if (_logs.length > 150) _logs.removeLast();
    });
  }

  // ─── Build ─────────────────────────────────────────────────────────────
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF07071A),
      body: SafeArea(
        child: CustomScrollView(
          physics: const BouncingScrollPhysics(),
          slivers: [
            SliverToBoxAdapter(child: _header()),
            SliverToBoxAdapter(child: _statusCard()),
            SliverToBoxAdapter(child: _batteryBanner()),
            SliverToBoxAdapter(child: _sectionLabel('Foreground Service')),
            SliverToBoxAdapter(child: _serviceControls()),
            SliverToBoxAdapter(child: _sectionLabel('Background Task')),
            SliverToBoxAdapter(child: _bgTaskControls()),
            SliverToBoxAdapter(child: _sectionLabel('Live Logs', extra: _logs.length)),
            SliverToBoxAdapter(child: _logConsole()),
            const SliverToBoxAdapter(child: SizedBox(height: 40)),
          ],
        ),
      ),
    );
  }

  // ─── Widget helpers ────────────────────────────────────────────────────

  Widget _header() => Padding(
    padding: const EdgeInsets.fromLTRB(20, 20, 20, 6),
    child: Row(
      children: [
        _gradientIcon(Icons.layers_rounded),
        const SizedBox(width: 12),
        Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
          const Text('ForegroundBackground', style: TextStyle(
              color: Colors.white, fontSize: 17, fontWeight: FontWeight.w700)),
          Text('Manager Demo', style: TextStyle(
              color: Colors.white.withOpacity(0.45), fontSize: 12)),
        ]),
        const Spacer(),
        _PulsingDot(active: _lifecycleState.isForegrounded),
      ],
    ),
  );

  Widget _statusCard() => Padding(
    padding: const EdgeInsets.fromLTRB(16, 8, 16, 4),
    child: _GlassCard(
      child: Row(children: [
        AnimatedContainer(
          duration: const Duration(milliseconds: 350),
          width: 50, height: 50,
          decoration: BoxDecoration(
            color: _lifecycleState.isForegrounded
                ? const Color(0xFF10B981).withOpacity(0.15)
                : const Color(0xFFF59E0B).withOpacity(0.15),
            borderRadius: BorderRadius.circular(14),
          ),
          child: Icon(
            _lifecycleState.isForegrounded ? Icons.visibility_rounded : Icons.bedtime_rounded,
            color: _lifecycleState.isForegrounded
                ? const Color(0xFF10B981) : const Color(0xFFF59E0B),
            size: 24,
          ),
        ),
        const SizedBox(width: 14),
        Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
          Text('App State', style: TextStyle(
              color: Colors.white.withOpacity(0.45), fontSize: 11, letterSpacing: 0.6)),
          const SizedBox(height: 3),
          AnimatedSwitcher(
            duration: const Duration(milliseconds: 250),
            child: Text(_lifecycleState.label, key: ValueKey(_lifecycleState),
                style: TextStyle(
                  color: _lifecycleState.isForegrounded
                      ? const Color(0xFF10B981) : const Color(0xFFF59E0B),
                  fontSize: 16, fontWeight: FontWeight.w700,
                )),
          ),
        ]),
        const Spacer(),
        Column(crossAxisAlignment: CrossAxisAlignment.end, children: [
          _Chip(label: 'BG Task', active: _bgTaskRunning, color: const Color(0xFF7C3AED)),
          const SizedBox(height: 6),
          _Chip(label: 'FG Service', active: _fgSvcRunning, color: const Color(0xFF4F46E5)),
        ]),
      ]),
    ),
  );

  Widget _batteryBanner() {
    if (_batteryIgnored) return const SizedBox.shrink();
    return Padding(
      padding: const EdgeInsets.fromLTRB(16, 4, 16, 4),
      child: GestureDetector(
        onTap: _requestBatteryIgnore,
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
          decoration: BoxDecoration(
            color: const Color(0xFFF59E0B).withOpacity(0.12),
            borderRadius: BorderRadius.circular(14),
            border: Border.all(color: const Color(0xFFF59E0B).withOpacity(0.4)),
          ),
          child: Row(children: [
            const Icon(Icons.battery_alert_rounded, color: Color(0xFFF59E0B), size: 20),
            const SizedBox(width: 10),
            Expanded(child: Text(
              'Tap to disable battery optimization — required for kill-proof service',
              style: const TextStyle(color: Color(0xFFF59E0B), fontSize: 12),
            )),
            const Icon(Icons.chevron_right_rounded, color: Color(0xFFF59E0B), size: 18),
          ]),
        ),
      ),
    );
  }

  Widget _sectionLabel(String label, {int? extra}) => Padding(
    padding: const EdgeInsets.fromLTRB(20, 16, 20, 6),
    child: Row(children: [
      Text(label.toUpperCase(), style: TextStyle(
          color: Colors.white.withOpacity(0.4), fontSize: 11,
          fontWeight: FontWeight.w700, letterSpacing: 1.3)),
      if (extra != null) ...[
        const SizedBox(width: 8),
        Container(
          padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 1),
          decoration: BoxDecoration(
            color: const Color(0xFF7C3AED).withOpacity(0.2),
            borderRadius: BorderRadius.circular(20),
          ),
          child: Text('$extra', style: const TextStyle(
              color: Color(0xFF7C3AED), fontSize: 10, fontWeight: FontWeight.w700)),
        ),
      ],
    ]),
  );

  Widget _serviceControls() => Padding(
    padding: const EdgeInsets.symmetric(horizontal: 16),
    child: _GlassCard(
      padding: const EdgeInsets.all(14),
      child: Column(children: [
        Row(children: [
          Expanded(child: _ActionBtn(
            label: _fgSvcRunning ? 'Stop Service' : 'Start Service',
            icon: _fgSvcRunning ? Icons.stop_circle_outlined : Icons.rocket_launch_rounded,
            color: const Color(0xFF4F46E5),
            active: _fgSvcRunning,
            onTap: _fgSvcRunning ? _stopFgService : _startFgService,
          )),
          const SizedBox(width: 10),
          Expanded(child: _ActionBtn(
            label: 'Update Text',
            icon: Icons.edit_notifications_rounded,
            color: const Color(0xFF0891B2),
            active: false,
            onTap: _updateNotification,
          )),
        ]),
        const SizedBox(height: 10),
        _InfoBox(
          icon: Icons.info_outline_rounded,
          text: _fgSvcRunning
              ? 'Service running — try minimizing or killing the app. It will restart!'
              : 'Start the service. It persists through minimize, background, and kill.',
          color: _fgSvcRunning ? const Color(0xFF10B981) : Colors.white38,
        ),
      ]),
    ),
  );

  Widget _bgTaskControls() => Padding(
    padding: const EdgeInsets.symmetric(horizontal: 16),
    child: _GlassCard(
      padding: const EdgeInsets.all(14),
      child: Column(children: [
        Row(children: [
          Expanded(child: _ActionBtn(
            label: _bgTaskRunning ? 'Stop Task' : 'Start Task',
            icon: _bgTaskRunning ? Icons.stop_circle_outlined : Icons.sync_rounded,
            color: const Color(0xFF7C3AED),
            active: _bgTaskRunning,
            onTap: _bgTaskRunning ? _stopBgTask : _startBgTask,
          )),
          const SizedBox(width: 10),
          Expanded(child: _ActionBtn(
            label: 'Clear Logs',
            icon: Icons.delete_sweep_rounded,
            color: const Color(0xFFDC2626),
            active: false,
            onTap: _clearLogs,
          )),
        ]),
        const SizedBox(height: 10),
        _InfoBox(
          icon: Icons.schedule_rounded,
          text: 'WorkManager (Android) / BGTaskScheduler (iOS). Minimum: 15 min.',
          color: Colors.white38,
        ),
      ]),
    ),
  );

  Widget _logConsole() => Padding(
    padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
    child: _GlassCard(
      padding: EdgeInsets.zero,
      child: _logs.isEmpty
          ? const Padding(
          padding: EdgeInsets.all(28),
          child: Center(child: Text('No logs yet...',
              style: TextStyle(color: Colors.white24, fontSize: 13,
                  fontStyle: FontStyle.italic))))
          : ConstrainedBox(
        constraints: const BoxConstraints(maxHeight: 340),
        child: ListView.separated(
          padding: const EdgeInsets.all(12),
          physics: const BouncingScrollPhysics(),
          shrinkWrap: true,
          itemCount: _logs.length,
          separatorBuilder: (_, __) =>
              Divider(color: Colors.white.withOpacity(0.05), height: 1),
          itemBuilder: (_, i) => _LogTile(entry: _logs[i]),
        ),
      ),
    ),
  );

  Widget _gradientIcon(IconData icon) => Container(
    width: 40, height: 40,
    decoration: BoxDecoration(
      gradient: const LinearGradient(
          colors: [Color(0xFF7C3AED), Color(0xFF4F46E5)]),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Icon(icon, color: Colors.white, size: 20),
  );
}

// ─── Reusable Widgets ──────────────────────────────────────────────────────────

class _GlassCard extends StatelessWidget {
  final Widget child;
  final EdgeInsetsGeometry? padding;
  const _GlassCard({required this.child, this.padding});

  @override
  Widget build(BuildContext context) => Container(
    padding: padding ?? const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: Colors.white.withOpacity(0.04),
      borderRadius: BorderRadius.circular(20),
      border: Border.all(color: Colors.white.withOpacity(0.08)),
      boxShadow: [BoxShadow(
          color: Colors.black.withOpacity(0.25),
          blurRadius: 16, offset: const Offset(0, 4))],
    ),
    child: child,
  );
}

class _ActionBtn extends StatelessWidget {
  final String label;
  final IconData icon;
  final Color color;
  final bool active;
  final VoidCallback onTap;
  const _ActionBtn({required this.label, required this.icon,
    required this.color, required this.active, required this.onTap});

  @override
  Widget build(BuildContext context) => GestureDetector(
    onTap: onTap,
    child: AnimatedContainer(
      duration: const Duration(milliseconds: 250),
      padding: const EdgeInsets.symmetric(vertical: 14),
      decoration: BoxDecoration(
        color: active ? color.withOpacity(0.22) : color.withOpacity(0.09),
        borderRadius: BorderRadius.circular(14),
        border: Border.all(color: active
            ? color.withOpacity(0.55) : color.withOpacity(0.2)),
      ),
      child: Column(children: [
        Icon(icon, color: color, size: 22),
        const SizedBox(height: 6),
        Text(label, textAlign: TextAlign.center,
            style: TextStyle(color: color, fontSize: 12, fontWeight: FontWeight.w600)),
      ]),
    ),
  );
}

class _Chip extends StatelessWidget {
  final String label;
  final bool active;
  final Color color;
  const _Chip({required this.label, required this.active, required this.color});

  @override
  Widget build(BuildContext context) => AnimatedContainer(
    duration: const Duration(milliseconds: 300),
    padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 4),
    decoration: BoxDecoration(
      color: active ? color.withOpacity(0.18) : Colors.white.withOpacity(0.05),
      borderRadius: BorderRadius.circular(20),
      border: Border.all(color: active ? color.withOpacity(0.45) : Colors.white.withOpacity(0.1)),
    ),
    child: Row(mainAxisSize: MainAxisSize.min, children: [
      Container(width: 6, height: 6,
          decoration: BoxDecoration(
              color: active ? color : Colors.white24, shape: BoxShape.circle)),
      const SizedBox(width: 5),
      Text(label, style: TextStyle(
          color: active ? color : Colors.white38, fontSize: 10, fontWeight: FontWeight.w600)),
    ]),
  );
}

class _InfoBox extends StatelessWidget {
  final IconData icon;
  final String text;
  final Color color;
  const _InfoBox({required this.icon, required this.text, required this.color});

  @override
  Widget build(BuildContext context) => Row(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Icon(icon, size: 14, color: color),
      const SizedBox(width: 6),
      Expanded(child: Text(text, style: TextStyle(color: color, fontSize: 11))),
    ],
  );
}

class _PulsingDot extends StatefulWidget {
  final bool active;
  const _PulsingDot({required this.active});
  @override
  State<_PulsingDot> createState() => _PulsingDotState();
}

class _PulsingDotState extends State<_PulsingDot> with SingleTickerProviderStateMixin {
  late final AnimationController _ctrl = AnimationController(
      vsync: this, duration: const Duration(milliseconds: 1100))..repeat(reverse: true);
  late final Animation<double> _anim =
  Tween(begin: 0.35, end: 1.0).animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeInOut));

  @override
  void dispose() { _ctrl.dispose(); super.dispose(); }

  @override
  Widget build(BuildContext context) {
    final c = widget.active ? const Color(0xFF10B981) : const Color(0xFFF59E0B);
    return FadeTransition(
      opacity: _anim,
      child: Container(width: 9, height: 9,
          decoration: BoxDecoration(color: c, shape: BoxShape.circle)),
    );
  }
}

// ─── Log models ────────────────────────────────────────────────────────────────

enum LogLevel { info, success, warning, error, lifecycle }

class _Log {
  final String msg;
  final LogLevel level;
  final DateTime time;
  _Log(this.msg, this.level, this.time);
}

class _LogTile extends StatelessWidget {
  final _Log entry;
  const _LogTile({required this.entry});

  @override
  Widget build(BuildContext context) {
    final color = _color(entry.level);
    final t = entry.time;
    final ts = '${t.hour.toString().padLeft(2,'0')}:'
        '${t.minute.toString().padLeft(2,'0')}:'
        '${t.second.toString().padLeft(2,'0')}';
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 5),
      child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
        Text(_icon(entry.level), style: const TextStyle(fontSize: 13)),
        const SizedBox(width: 7),
        Expanded(child: Text(entry.msg, style: TextStyle(
            color: color, fontSize: 11.5, fontWeight: FontWeight.w500))),
        Text(ts, style: const TextStyle(
            color: Colors.white24, fontSize: 10, fontFamily: 'monospace')),
      ]),
    );
  }

  Color _color(LogLevel l) {
    switch (l) {
      case LogLevel.info:      return Colors.white60;
      case LogLevel.success:   return const Color(0xFF10B981);
      case LogLevel.warning:   return const Color(0xFFF59E0B);
      case LogLevel.error:     return const Color(0xFFEF4444);
      case LogLevel.lifecycle: return const Color(0xFF818CF8);
    }
  }

  String _icon(LogLevel l) {
    switch (l) {
      case LogLevel.info:      return 'ℹ️';
      case LogLevel.success:   return '✅';
      case LogLevel.warning:   return '⚠️';
      case LogLevel.error:     return '❌';
      case LogLevel.lifecycle: return '🔄';
    }
  }
}
1
likes
140
points
21
downloads

Documentation

API reference

Publisher

verified publishersanjaysharma.info

Weekly Downloads

A production-ready Flutter plugin for foreground/background detection, Android foreground services, background task scheduling, and lifecycle management.

Homepage

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on foreground_background_manager

Packages that implement foreground_background_manager