Reputation: 11
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
Reputation: 1
Try to change importance: Importance.high
to importance: Importance.low
,
Please review this doc: AndroidForeground
Upvotes: 0