transistorsoft / flutter_background_fetch

Periodic callbacks in the background for both IOS and Android. Includes Android Headless mechanism
MIT License
573 stars 167 forks source link

background fetch not running when app is closed #382

Open zsrour1991 opened 1 month ago

zsrour1991 commented 1 month ago

import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:ess_application/appservices_lib.dart'; import 'package:ess_core/esscore_lib.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:get_it/get_it.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:background_fetch/background_fetch.dart';

class BackGroundHelper { late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; late Timer _timer;

BackGroundHelper() { initializeNotifications(); initBackgroundFetch(); //registration(); startTimer(); // Start the timer here } static late NotificationService notificationService = locator(); // Declare NotificationService

static GetIt sl = GetIt.instance;

Future initializeNotifications() async { print("initializeNotifications"); flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

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

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

await flutterLocalNotificationsPlugin.initialize(initializationSettings,
    onDidReceiveNotificationResponse: (NotificationResponse response) {
  print("Notification tapped: ${response.payload}");
});

// Create notification channel
_createNotificationChannel();

// For iOS, request permission
if (Platform.isIOS) {
  final permissionGranted = await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
          IOSFlutterLocalNotificationsPlugin>()
      ?.requestPermissions(
        alert: true,
        badge: true,
        sound: true,
      );
  print('Permission granted: $permissionGranted');
}

}

Future _createNotificationChannel() async { const AndroidNotificationChannel channel = AndroidNotificationChannel( 'your_channel_id', // Unique ID for the channel 'your_channel_name', // User-visible name for the channel description: 'your_channel_description', // Description for the channel importance: Importance.max, // Importance level for notifications playSound: true, // Play sound for notifications ); await requestPermissions(); flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(channel); }

Future showNotification() async { print("Showing notification..."); var androidPlatformChannelSpecifics = AndroidNotificationDetails( 'your_channel_id', // Ensure this matches the channel ID used in _createNotificationChannel 'your_channel_name', channelDescription: 'your_channel_description', importance: Importance.max, priority: Priority.high, playSound: true, // Ensure this is set if you want sound ); var platformChannelSpecifics = NotificationDetails( android: androidPlatformChannelSpecifics, iOS: DarwinNotificationDetails(), );

try {
  print("fetch notification");
  SharedPreferences sp = await SharedPreferences.getInstance();

  if (sp.getInt('companyId') != null) {
    print("fetch notification");
    print(sp.getInt('companyId'));
    await notificationService.fetch().then((value) async {
      print("Fetching notifications");
      print(notificationService.state.notificationsUnseen.length);
      if (notificationService.state.notificationsUnseen.isNotEmpty) {
        print("----------------------");
        // if(sp.getInt("notificationId")!=notificationService.state.notificationsUnseen[0].id!){
        sp.setInt("notificationId",
            notificationService.state.notificationsUnseen[0].id!);
        await flutterLocalNotificationsPlugin.show(
          notificationService.state.notificationsUnseen[0].id!,
          "${changeTime(notificationService.state.notificationsUnseen[0].dateCreated!, 6, true)} ${notificationService.state.notificationsUnseen[0].companyName!}",
          notificationService.state.notificationsUnseen[0].text!,
          const NotificationDetails(
            android: AndroidNotificationDetails(
                'your_channel_id', 'your_channel_name',
                channelDescription: 'your_channel_description',
                importance: Importance.max,
                icon: '@mipmap/ic_launcher'),
            iOS: DarwinNotificationDetails(),
          ),
          payload: jsonEncode({
            'id': notificationService.state.notificationsUnseen[0].id,
            'companyName': notificationService
                .state.notificationsUnseen[0].companyName,
          }),
        ); //}
      }
    });
  }

  print("Notification displayed successfully.");
} catch (e) {
  print("Error displaying notification: $e");
}

}

static Future requestPermissions() async { if (Platform.isAndroid) { var status = await Permission.notification.status; if (!status.isGranted) { await Permission.notification.request(); } } else if (Platform.isIOS) { var status = await Permission.notification.isGranted; if (!status) { await Permission.notification.request(); } } }

@pragma('vm:entry-point') void backgroundFetchHeadlessTask(HeadlessTask task) async { print("Headless Task Triggered: ${task.taskId}"); try { await BackGroundHelper().showNotification(); } catch (e) { print("Error in headless task: $e"); } BackgroundFetch.finish(task.taskId); }

void initBackgroundFetch() { print("initBackgroundFetch"); BackgroundFetch.configure( BackgroundFetchConfig( minimumFetchInterval: 15, stopOnTerminate: false, enableHeadless: true, startOnBoot: true, ), (String taskId) async { print("[BackgroundFetch] Event received: $taskId"); await showNotification(); BackgroundFetch.finish(taskId); }, ).then((int status) { print("[BackgroundFetch] configure success: $status"); }).catchError((e) { print("[BackgroundFetch] configure ERROR: $e"); }); }

void startTimer() { _timer = Timer.periodic(Duration(seconds: 15), (timer) { showNotification(); }); }

void stopTimer() { _timer.cancel(); }

void ini() { BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask); } }

christocracy commented 1 month ago

The issue template is required, not optional;

Your Environment

To Reproduce Steps to reproduce the behavior:

Debug logs

Additional context Add any other context about the problem here.

coskuncay commented 1 month ago

hey @christocracy ,

When I trigger background fetch through Xcode, it works, but if I don't do that or leave my real device idle, it doesn't work at all. What can I do?

christocracy commented 1 month ago

It can take days before the OS machine-learning model begins firing regular events. This issue has been posted hundreds of times over the last 10 years.

There is no way to control when the OS decides to fire events.

just periodically bring your app to the foreground throughout the day, to simulate regular user behaviour.

iOS will halt firing events if the user manually terminates the app.

if simulated events work, your job is done. Now you wait.

github-actions[bot] commented 1 week ago

This issue is stale because it has been open for 30 days with no activity.