How to enable notifications in Flutter when the app is completely closed (even removed from recent apps)?

I am developing a Flutter app that needs to send notifications even when the app is completely closed, including when it is removed from the recent apps list. I am using the following setup:

Packages:

flutter_local_notifications for notifications. battery_plus for detecting battery state changes (e.g., charger plugged in or disconnected). Goal:

Trigger a notification when the charger is plugged in or disconnected, even if the app is not running in the background. Current Issue:

Notifications work when the app is running or in the background but stop when the app is killed (removed from recent apps).

Getting notification when app is in recents…when clear not getting notification

main.dart:
import 'package:flutter/material.dart';
import 'package:battery_plus/battery_plus.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Charger Notification',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final Battery _battery = Battery();
  final FlutterLocalNotificationsPlugin _notificationsPlugin =
  FlutterLocalNotificationsPlugin();

  BatteryState? _batteryState;

  @override
  void initState() {
    super.initState();
    _initializeNotifications();
    _listenToBatteryState();
  }

  Future<void> _initializeNotifications() async {
    // Request notification permission
    if (await Permission.notification.isDenied) {
      await Permission.notification.request();
    }

    const InitializationSettings initializationSettings = InitializationSettings(
      android: AndroidInitializationSettings('@mipmap/ic_launcher'),
    );

    await _notificationsPlugin.initialize(initializationSettings);
  }

  void _showNotification(String title, String body) async {
    const AndroidNotificationDetails androidNotificationDetails =
    AndroidNotificationDetails(
      'charger_channel',
      'Charger Notifications',
      channelDescription: 'Notifications when charger is plugged or unplugged',
      importance: Importance.high,
      priority: Priority.high,
    );

    const NotificationDetails notificationDetails =
    NotificationDetails(android: androidNotificationDetails);

    await _notificationsPlugin.show(
      0,
      title,
      body,
      notificationDetails,
    );
  }

  void _listenToBatteryState() {
    _battery.onBatteryStateChanged.listen((BatteryState state) {
      if (_batteryState != state) {
        setState(() {
          _batteryState = state;
        });

        if (state == BatteryState.charging) {
          _showNotification('Charger Connected', 'Your device is now charging.');
        } else if (state == BatteryState.discharging) {
          _showNotification(
              'Charger Disconnected', 'Your device is no longer charging.');
        }
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Charger Notification App'),
      ),
      body: Center(
        child: Text(
          _batteryState == BatteryState.charging
              ? 'Device is Charging'
              : _batteryState == BatteryState.connectedNotCharging
              ? 'Device is Not Charging'
              : 'Unknown State',
          style: const TextStyle(fontSize: 20),
        ),
      ),
    );
  }
}
manifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.BATTERY_STATS" />
    <application
        android:label="testbattery"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:taskAffinity=""
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
    <!-- Required to query activities that can process text, see:
         https://developer.android.com/training/package-visibility and
         https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.

In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin.

    <queries>
        <intent>
            <action android:name="android.intent.action.PROCESS_TEXT"/>
            <data android:mimeType="text/plain"/>
        </intent>
    </queries>
</manifest>

2 Likes

Have you tried background services packages?

flutter_background_service
WorkManager

1 Like

Tried but couldn’t achieve

If I remember well, depending on the Android OS version, notifications are suspended for your app when it is killed by the user, until the user starts it again.

1 Like

If I enable background service also couldn’t achieve

Hey,

I’ve worked a lot on notifications and this is a unique usecase you have there. Well I don’t think it can be possible in newer Android versions as they’ve pushed battery optimisations very aggressively. So in this case where a service with a Broadcast receiver (intercepting the correct intent) would’ve been your best bet cannot be used as Android will shut it down if its been running for a long time.
Maybe you can still try with a Foreground service but in this case you’ll be showing to the user that your application is running in the background. In this case as well Android may choose to kill your notifications in some scenarios.

You can read more about battery optimisations interfering with notification here - https://dontkillmyapp.com/

Thanks

Please try this one: workmanager | Flutter package