How to monitor the app state like running in background or forground ?
#state#lifecycle#background#foreground#app-state#monitoring
Answer
Overview
Monitoring app lifecycle states (background/foreground) is crucial for managing resources, saving user progress, pausing/resuming tasks, and optimizing battery consumption. Flutter provides multiple approaches to detect when your app transitions between these states.
App Lifecycle States
| State | Description | When It Occurs |
|---|---|---|
| Resumed | App is visible and interactive | User is actively using the app |
| Inactive | App is visible but not receiving input | Incoming call, system dialog, app switcher |
| Paused | App is not visible but running | App moved to background |
| Detached | App is still in memory but not visible | About to terminate or suspended |
| Hidden | App is hidden from view | iOS specific - app in background |
Method 1: WidgetsBindingObserver (Recommended)
Basic Implementation
dartimport 'package:flutter/material.dart'; class AppLifecycleObserver extends StatefulWidget { final Widget child; const AppLifecycleObserver({required this.child}); _AppLifecycleObserverState createState() => _AppLifecycleObserverState(); } class _AppLifecycleObserverState extends State<AppLifecycleObserver> with WidgetsBindingObserver { AppLifecycleState? _currentState; void initState() { super.initState(); // Register observer WidgetsBinding.instance.addObserver(this); _currentState = WidgetsBinding.instance.lifecycleState; } void dispose() { // Unregister observer WidgetsBinding.instance.removeObserver(this); super.dispose(); } void didChangeAppLifecycleState(AppLifecycleState state) { setState(() { _currentState = state; }); switch (state) { case AppLifecycleState.resumed: print('App is in foreground'); _onAppResumed(); break; case AppLifecycleState.inactive: print('App is inactive'); _onAppInactive(); break; case AppLifecycleState.paused: print('App is in background'); _onAppPaused(); break; case AppLifecycleState.detached: print('App is detached'); _onAppDetached(); break; case AppLifecycleState.hidden: print('App is hidden'); break; } } void _onAppResumed() { // Resume video playback // Refresh data // Resume animations } void _onAppInactive() { // Pause ongoing operations } void _onAppPaused() { // Save user progress // Pause music/video // Cancel network requests // Stop location updates } void _onAppDetached() { // Clean up resources } Widget build(BuildContext context) { return widget.child; } } // Usage in main app void main() { runApp( AppLifecycleObserver( child: MyApp(), ), ); }
Advanced Example with Use Cases
dartclass VideoPlayerScreen extends StatefulWidget { _VideoPlayerScreenState createState() => _VideoPlayerScreenState(); } class _VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindingObserver { VideoPlayerController? _controller; bool _isPlaying = false; void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _initializePlayer(); } void dispose() { WidgetsBinding.instance.removeObserver(this); _controller?.dispose(); super.dispose(); } void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { // App moved to background - pause video if (_isPlaying) { _controller?.pause(); _isPlaying = false; } } else if (state == AppLifecycleState.resumed) { // App returned to foreground - resume if was playing if (!_isPlaying && _controller != null) { _controller?.play(); _isPlaying = true; } } } void _initializePlayer() { _controller = VideoPlayerController.network( 'https://example.com/video.mp4 )..initialize().then((_) { setState(() {}); _controller?.play(); _isPlaying = true; }); } Widget build(BuildContext context) { return Scaffold( body: _controller != null && _controller!.value.isInitialized ? VideoPlayer(_controller!) : CircularProgressIndicator(), ); } }
Method 2: AppLifecycleListener (Flutter 3.13+)
dartimport 'package:flutter/widgets.dart'; class ModernLifecycleObserver extends StatefulWidget { State<ModernLifecycleObserver> createState() => _ModernLifecycleObserverState(); } class _ModernLifecycleObserverState extends State<ModernLifecycleObserver> { late final AppLifecycleListener _listener; void initState() { super.initState(); _listener = AppLifecycleListener( onStateChange: _onStateChanged, onResume: _onResume, onInactive: _onInactive, onHide: _onHide, onShow: _onShow, onPause: _onPause, onRestart: _onRestart, onDetach: _onDetach, ); } void dispose() { _listener.dispose(); super.dispose(); } void _onStateChanged(AppLifecycleState state) { print('State changed to: $state'); } void _onResume() { print('App resumed (foreground)'); } void _onInactive() { print('App inactive'); } void _onHide() { print('App hidden'); } void _onShow() { print('App shown'); } void _onPause() { print('App paused (background)'); } void _onRestart() { print('App restarted'); } void _onDetach() { print('App detached'); } Widget build(BuildContext context) { return Container(); } }
Method 3: Using flutter_fgbg Package
Installation
yamldependencies: flutter_fgbg: ^0.3.0
Implementation
dartimport 'package:flutter_fgbg/flutter_fgbg.dart'; class FGBGExample extends StatefulWidget { _FGBGExampleState createState() => _FGBGExampleState(); } class _FGBGExampleState extends State<FGBGExample> { StreamSubscription? _subscription; String _state = 'Unknown'; void initState() { super.initState(); _subscription = FGBGEvents.stream.listen((event) { setState(() { _state = event == FGBGType.foreground ? 'Foreground' : 'Background'; }); if (event == FGBGType.foreground) { print('App came to foreground'); _handleForeground(); } else { print('App went to background'); _handleBackground(); } }); } void dispose() { _subscription?.cancel(); super.dispose(); } void _handleForeground() { // Refresh data // Resume operations } void _handleBackground() { // Save state // Pause operations } Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('App State: $_state')), body: Center(child: Text('Current state: $_state')), ); } }
Practical Use Cases
1. Auto-Save User Progress
dartclass AutoSaveManager with WidgetsBindingObserver { final UserDataService _dataService = UserDataService(); void initialize() { WidgetsBinding.instance.addObserver(this); } void dispose() { WidgetsBinding.instance.removeObserver(this); } void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { // Save user data when app goes to background _saveUserProgress(); } } Future<void> _saveUserProgress() async { try { await _dataService.saveProgress(); print('User progress saved'); } catch (e) { print('Failed to save progress: $e'); } } }
2. Pause/Resume Audio
dartclass AudioManager with WidgetsBindingObserver { final AudioPlayer _audioPlayer = AudioPlayer(); bool _wasPlaying = false; void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { _wasPlaying = _audioPlayer.playing; if (_wasPlaying) { _audioPlayer.pause(); } } else if (state == AppLifecycleState.resumed) { if (_wasPlaying) { _audioPlayer.play(); } } } }
3. Stop Location Tracking
dartclass LocationManager with WidgetsBindingObserver { StreamSubscription<Position>? _positionStream; void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { // Stop location updates to save battery _positionStream?.pause(); } else if (state == AppLifecycleState.resumed) { // Resume location updates _positionStream?.resume(); } } }
4. Refresh Data on Foreground
dartclass DataRefreshManager with WidgetsBindingObserver { final ApiService _apiService = ApiService(); DateTime? _lastRefresh; void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { _refreshDataIfNeeded(); } } Future<void> _refreshDataIfNeeded() async { final now = DateTime.now(); // Refresh if more than 5 minutes since last refresh if (_lastRefresh == null || now.difference(_lastRefresh!).inMinutes > 5) { await _apiService.fetchLatestData(); _lastRefresh = now; print('Data refreshed'); } } }
5. Analytics Tracking
dartimport 'package:firebase_analytics/firebase_analytics.dart'; class AnalyticsManager with WidgetsBindingObserver { final FirebaseAnalytics _analytics = FirebaseAnalytics.instance; DateTime? _backgroundTime; void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.paused) { _backgroundTime = DateTime.now(); _analytics.logEvent( name: 'app_backgrounded', parameters: {'timestamp': _backgroundTime.toString()}, ); } else if (state == AppLifecycleState.resumed) { if (_backgroundTime != null) { final duration = DateTime.now().difference(_backgroundTime!); _analytics.logEvent( name: 'app_resumed', parameters: { 'background_duration_seconds': duration.inSeconds, }, ); } } } }
Comparison of Methods
| Method | Pros | Cons | Best For |
|---|---|---|---|
| WidgetsBindingObserver | Built-in, no dependencies | Manual registration/disposal | Most apps |
| AppLifecycleListener | Modern API, cleaner code | Requires Flutter 3.13+ | New projects |
| flutter_fgbg | Simple foreground/background only | External dependency | Simple use cases |
Best Practices
Resource Management:
- Pause heavy operations when app goes to background
- Stop timers, animations, and network requests
- Cancel location tracking to save battery
- Clear sensitive data from memory
User Experience:
- Save user progress before backgrounding
- Resume seamlessly when returning to foreground
- Refresh data if it might be stale
- Show loading indicators during refresh
Performance:
- Avoid unnecessary work in background
- Use background tasks for long-running operations
- Implement debouncing for state changes
- Clean up resources in dispose()
Testing:
- Test all lifecycle transitions
- Verify state persistence
- Check memory leaks with DevTools
- Test on both iOS and Android
Common Pitfalls
Don't forget to unregister:
dart// ❌ BAD: Memory leak void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } // Forgot dispose! // ✅ GOOD void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); }
Handle null states:
dart// ❌ BAD void didChangeAppLifecycleState(AppLifecycleState state) { _controller.pause(); // Might be null! } // ✅ GOOD void didChangeAppLifecycleState(AppLifecycleState state) { _controller?.pause(); }
Learning Resources
- AppLifecycleListener Documentation
- Flutter App Lifecycle Guide
- Managing App Lifecycle in Flutter
- flutter_fgbg Package
Pro Tip: Always test lifecycle transitions thoroughly, especially on iOS where apps can be terminated more aggressively than on Android. Use background tasks for operations that must complete even when the app is backgrounded, and always clean up resources properly to avoid memory leaks.