Yogaruban Ganason
Yogaruban Ganason

Reputation: 11

Error In Flutter App : Bad notification for startForeground

Guys i have been working on flutter app, flutter background services to make the notification check for alert and the user gets notification if got alert every 15 seconds. The problem i facing is the app is crashing and I cant find the reason why. i hope you guys can help me. Below i will share the codes related to this part of the app.

The task is for the app to check for alert and notify the user. Currently in alert_my.dart i have made a notification that comes out only if the app is opened and the user openned alert page. Now the problem is for the part where im trying to make another notification comes every 15 seconds to check and alert the user if there is any alert.

background_services.dart

import 'dart:async';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_background_service_android/flutter_background_service_android.dart';

import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import '../../domain/entities/alert_entity.dart';
import 'alert_remote_data_source.dart';
import 'package:http/http.dart' as http;

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

Future<void> initializeService() async {
  final service = FlutterBackgroundService();

  await service.configure(
    androidConfiguration: AndroidConfiguration(
      onStart: onStart,
      autoStart: true,
      isForegroundMode: true, // Ensure foreground mode is enabled
      notificationChannelId: 'alert_channel',
      initialNotificationTitle: 'Monitoring in progress',
      initialNotificationContent: 'Fetching alerts in the background',
    ),
    iosConfiguration: IosConfiguration(
      onForeground: onStart,
    ),
  );

  service.startService();
}

@pragma('vm:entry-point')
Future<void> onStart(ServiceInstance service) async {
  // Add a log statement to verify `onStart` is called
  print('onStart called');

  // Set up notification channel for Android
  const AndroidNotificationChannel channel = AndroidNotificationChannel(
    'alert_channel',
    'Alerts',
    description: 'This channel is used for alert notifications.',
    importance: Importance.high,
  );

  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(channel);

  // Periodically check for alerts every 15 seconds
  Timer.periodic(Duration(seconds: 15), (timer) async {
    await checkForAlerts();
  });
}

// Periodically check for new alerts and show notification
Future<void> checkForAlerts() async {
  print('checkForAlerts called');
  final alertRemoteDataSource = AlertRemoteDataSource(http.Client());

  // Replace with actual userId and isPersonalFarm parameters
  const String userId = '7251424466327183360'; // Replace with real user ID
  const bool isPersonalFarm = true; // Set based on requirement

  try {
    final alerts =
        await alertRemoteDataSource.fetchAlertData(userId, isPersonalFarm);
    print('Fetched ${alerts.length} alerts');

    // Send notification if there are any alerts
    for (AlertEntity alert in alerts) {
       print('Processing alert: ${alert.farmName}');
      showNotification(alert);
    }
  } catch (error) {
    print('Error fetching alerts: $error');
  }
}

void showNotification(AlertEntity alert) {
  // Using a static ID (e.g., 888) for the notification
  flutterLocalNotificationsPlugin.show(
    888,
    'New Alert: ${alert.farmName}',
    'Pond ${alert.pondName}: ${alert.alert}',
    const NotificationDetails(
      android: AndroidNotificationDetails(
        'alert_channel',
        'Alerts',
        importance: Importance.high,
        priority: Priority.high,
        icon: '@drawable/ic_notification', // Replace with your app icon
      ),
    ),
  );
}

alert_my.dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kms/feature/page_settings/presentation/pages/settings_page.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../../authentication/presentation/blocs/auth_cubit.dart';
import '../../../live/presentation/pages/live_my.dart';
import '../blocs/alert_cubit.dart';
import '../blocs/alert_state.dart';
import '../widgets/farm_card.dart';
import '../widgets/user_info_widget.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class AlertMyPage extends StatefulWidget {
  @override
  _AlertMyPageState createState() => _AlertMyPageState();
}

class _AlertMyPageState extends State<AlertMyPage> {
  int _currentIndex = 0;
  bool _isPersonalFarm = true;
  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  @override
  void initState() {
    super.initState();
    _initializeNotifications(); // Initialize notifications
    _loadAlertData();
  }

  // Initialize notificatio

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

  //   const AndroidInitializationSettings initializationSettingsAndroid =
  //       AndroidInitializationSettings('@mipmap/ic_launcher');

  //   const InitializationSettings initializationSettings =
  //       InitializationSettings(
  //     android: initializationSettingsAndroid,
  //   );

  //   await flutterLocalNotificationsPlugin.initialize(initializationSettings);
  // }

  Future<void> _initializeNotifications() async {
    // Check if the permission request is in progress
    if (await Permission.notification.isPermanentlyDenied) {
      // If permission is permanently denied, you might want to show a dialog
// to direct the user to settings, or handle it accordingly.
      return;
    }

// Check if the permission is denied before requesting
    final status = await Permission.notification.status;
    if (status.isDenied) {
      await Permission.notification.request();
    }

    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('@drawable/ic_notification');

    const InitializationSettings initializationSettings =
        InitializationSettings(
      android: initializationSettingsAndroid,
    );

    await flutterLocalNotificationsPlugin.initialize(initializationSettings);
  }

  // Future<void> _requestNotificationPermission() async {
  //   final status = await Permission.notification.status;

  //   if (status.isDenied) {
  //     // Prompt the user to allow notifications
  //     final newStatus = await Permission.notification.request();

  //     if (newStatus.isGranted) {
  //       print('Notification permission granted');
  //     } else {
  //       print('Notification permission denied');
  //     }
  //   }
  // }

  // // Show notification when an alert is received
  // Future<void> _showNotification(String alertTitle, String alertMessage) async {
  //   const AndroidNotificationDetails androidPlatformChannelSpecifics =
  //       AndroidNotificationDetails('alert_channel_id', 'Alert Notifications',
  //           channelDescription: 'Channel for alert notifications',
  //           importance: Importance.max,
  //           priority: Priority.high,
  //           ticker: 'ticker');

  //   const NotificationDetails platformChannelSpecifics = NotificationDetails(
  //     android: androidPlatformChannelSpecifics,
  //   );

  //   await flutterLocalNotificationsPlugin.show(
  //     0,
  //     alertTitle,
  //     alertMessage,
  //     platformChannelSpecifics,
  //     payload: 'alert_payload',
  //   );
  // }
  // Updated show notification method to loop sound
  Future<void> _showNotification(String alertTitle, String alertMessage) async {
    // Define custom looping sound (assuming sound file is added to res/raw directory)
    const AndroidNotificationDetails androidPlatformChannelSpecifics =
        AndroidNotificationDetails(
      'alert_channel_id',
      'Alert Notifications',
      channelDescription: 'Channel for alert notifications',
      importance: Importance.max,
      priority: Priority.high,
      ticker: 'ticker',
      sound: RawResourceAndroidNotificationSound(
          'alert_sound'), // Custom sound file in res/raw
      playSound: true,
      enableVibration: true,
      ongoing: true, // Keeps the notification active
    );

    const NotificationDetails platformChannelSpecifics = NotificationDetails(
      android: androidPlatformChannelSpecifics,
    );

    await flutterLocalNotificationsPlugin.show(
      0,
      alertTitle,
      alertMessage,
      platformChannelSpecifics,
      payload: 'alert_payload',
    );
  }

  // Load data without waiting for return value, let Bloc handle the state
  void _loadAlertData() {
    final userId = context.read<AuthCubit>().state.userId;
    context.read<AlertCubit>().getAlerts('$userId', _isPersonalFarm);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: Icon(Icons.logout),
            onPressed: () {
              context.read<AuthCubit>().logout();
              Navigator.pushReplacementNamed(context, '/login');
            },
          ),
        ],
      ),
      body: Column(
        children: [
          if (_currentIndex == 0) ...[
            UserInfoWidget(),
            _buildToggleButtons(),
            const SizedBox(height: 10),
          ],
          Expanded(
            child: _currentIndex == 0
                ? RefreshIndicator(
                    onRefresh: () async => _loadAlertData(),
                    child: BlocBuilder<AlertCubit, AlertState>(
                      builder: (context, state) {
                        print('Current state: $state');
                        if (state is AlertLoading) {
                          return Center(child: CircularProgressIndicator());
                        } else if (state is AlertError) {
                          return Center(
                              child: Text(
                                  'No alerts available for community farms'));
                        } else if (state is AlertLoaded) {
                          final alerts = state.alerts;
                          print('Alerts loaded: $alerts');

                          // Trigger notification when alerts are loaded
                          if (alerts != null && alerts.isNotEmpty) {
                            for (var alert in alerts) {
                              _showNotification(
                                "Alert 🚨",
                                "${alert.farmName} " +
                                    "\nAlert: ${alert.alert}",
                              );
                            }
                          }

                          return alerts == null || alerts.isEmpty
                              ? Center(child: Text('No alerts available'))
                              : ListView.builder(
                                  itemCount: alerts.length,
                                  itemBuilder: (context, index) {
                                    final alert = alerts[index];
                                    return FarmCard(
                                      farmName: alert.farmName,
                                      pondName: alert.pondName,
                                      alert: alert.alert,
                                    );
                                  },
                                );
                        }
                        return SizedBox.shrink();
                      },
                    ),
                  )
                : _currentIndex == 1
                    ? LiveMyPage()
                    : SettingsPage(),
          ),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
            if (_currentIndex == 0) {
              _loadAlertData();
            }
          });
        },
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.warning), label: 'Alert'),
          BottomNavigationBarItem(icon: Icon(Icons.cell_tower), label: 'Live'),
          BottomNavigationBarItem(
              icon: Icon(Icons.settings), label: 'Settings'),
        ],
      ),
    );
  }

  Widget _buildToggleButtons() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 30.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          _buildToggleButton(
            label: 'My Aqua Farms',
            icon: Icons.person,
            isSelected: _isPersonalFarm,
            onTap: () {
              setState(() {
                _isPersonalFarm = true;
                _loadAlertData();
              });
            },
          ),
          _buildToggleButton(
            label: 'Community Farms',
            icon: Icons.group,
            isSelected: !_isPersonalFarm,
            onTap: () {
              setState(() {
                _isPersonalFarm = false;
                _loadAlertData();
              });
            },
          ),
        ],
      ),
    );
  }

  Widget _buildToggleButton({
    required String label,
    required IconData icon,
    required bool isSelected,
    required VoidCallback onTap,
  }) {
    return Expanded(
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          foregroundColor: isSelected ? Colors.white : Colors.black,
          backgroundColor:
              isSelected ? const Color(0xFF2B479A) : Colors.grey[200],
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(10),
          ),
        ),
        onPressed: onTap,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(icon),
            const SizedBox(width: 8),
            Text(label, style: const TextStyle(fontSize: 10)),
          ],
        ),
      ),
    );
  }
}

main.dart

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize notifications settings
  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('@drawable/ic_notification');
  const DarwinInitializationSettings initializationSettingsDarwin =
      DarwinInitializationSettings();
  const InitializationSettings initializationSettings = InitializationSettings(
    android: initializationSettingsAndroid,
    iOS: initializationSettingsDarwin,
  );
  await flutterLocalNotificationsPlugin.initialize(initializationSettings);

  // Initialize the background service
  await initializeService();

  runApp(MyApp());
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>


    <application
    android:requestLegacyExternalStorage="true"
        android:label="Kelah Monitoring System"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">

        <receiver android:name="com.dexterous.flutterlocalnotifications.notifications.receivers.ActionReceiver"/>
        
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <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>

        <service
            android:name="id.flutter.flutter_background_service.BackgroundService"
            android:permission="android.permission.FOREGROUND_SERVICE"
            android:foregroundServiceType="dataSync"
            android:exported="true"
            tools:replace="android:exported" />
            

        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
    <queries>
        <intent>
            <action android:name="android.intent.action.PROCESS_TEXT"/>
            <data android:mimeType="text/plain"/>
        </intent>
    </queries>
</manifest>

The console log error

/AndroidRuntime( 8663): android.app.RemoteServiceException$CannotPostForegroundServiceNotificationException: Bad notification for startForeground
E/AndroidRuntime( 8663):    at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:2078)
E/AndroidRuntime( 8663):    at android.app.ActivityThread.-$$Nest$mthrowRemoteServiceException(Unknown Source:0)
E/AndroidRuntime( 8663):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2369)
E/AndroidRuntime( 8663):    at android.os.Handler.dispatchMessage(Handler.java:106)
E/AndroidRuntime( 8663):    at android.os.Looper.loopOnce(Looper.java:205)
E/AndroidRuntime( 8663):    at android.os.Looper.loop(Looper.java:294)
E/AndroidRuntime( 8663):    at android.app.ActivityThread.main(ActivityThread.java:8177)
E/AndroidRuntime( 8663):    at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime( 8663):    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
E/AndroidRuntime( 8663):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
I/Process ( 8663): Sending signal. PID: 8663 SIG: 9
Lost connection to device.

Upvotes: 0

Views: 162

Answers (1)

AzPsu
AzPsu

Reputation: 1

Try to change importance: Importance.high to importance: Importance.low,

Please review this doc: AndroidForeground

Upvotes: 0

Related Questions