Задача приложения состоит в том, чтобы проверить наличие предупреждений и уведомить пользователя. В настоящее время в alert_my.dart я сделал уведомление, которое появляется только в том случае, если приложение открыто и пользователь открыл страницу оповещения. Теперь проблема в том, что я пытаюсь сделать еще одно уведомление, которое приходит каждые 15 секунд, чтобы проверить и предупредить пользователя, если есть какое-либо предупреждение.
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 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 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 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 {
int _currentIndex = 0;
bool _isPersonalFarm = true;
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
@override
void initState() {
super.initState();
_initializeNotifications(); // Initialize notifications
_loadAlertData();
}
// Initialize notificatio
// Future _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 _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 _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 _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 _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().state.userId;
context.read().getAlerts('$userId', _isPersonalFarm);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.logout),
onPressed: () {
context.read().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(
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 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
Ошибка журнала консоли
/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.
Подробнее здесь: https://stackoverflow.com/questions/791 ... foreground