skip to Main Content

I have implemented fcm and added flutter local notification with action button for getting incomming call notification and it works on debug mode perfectly but it is not working in release mode. I have added all necessary permission. And I know most of os will kill background task but I need to know how to tackle those issues.Somebody please help me and I experianced similar issue in a live tracking app some os have killed the task after some time and navigation stopped

when a push notification comes Ineed to get call notification with ringtone with action button either with flutter local notification or flutter call kit.

Also Im doing a live tracking app I need to track continuosly. In that app I used geolocator , flutter background service etc the issue is navigation is getting stopped after some time in some phones

 import 'dart:convert';
import 'dart:developer';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:hope/core/services/local_storage.dart';
import 'package:hope/core/utils/colors.dart';
import 'package:hope/core/view_model.dart/widgetprovider/bottomnav_provider.dart';
import 'package:hope/views/screens/matches/call_view.dart';
import 'package:hope/views/screens/matches/incomming_call.dart';
import 'package:hope/views/screens/notification/notification_view.dart';
import 'package:just_audio/just_audio.dart';

@pragma('vm:entry-point')
void onSelectNotification(NotificationResponse notificationResponse) async {
  final payload = notificationResponse.payload;

  if (payload != null && payload.isNotEmpty) {
    Map<String, dynamic> data = Map<String, dynamic>.from(jsonDecode(payload));

    log("Notification payload: ${data.toString()}");

    String messageType = data['type'];

    if (notificationResponse.actionId == 'accept_call') {
      // Stop the audio first
      await audioPlayer.stop();
      audioPlayer.dispose();

      // Cancel the notification
      if (notificationId != null) {
        flutterLocalNotificationsPlugin.cancel(notificationId!);
      }

      // Navigate to the call view
      navigatorKey.currentState?.push(
        MaterialPageRoute(
          builder: (_) => CallView(
            channelId: data['channelId'],
            agoraToken: data['agora_token'],
            contactName: data['caller_name'],
          ),
        ),
      );

      return; // Early return to prevent further execution
    } else if (notificationResponse.actionId == 'reject_call') {
      // Stop the audio first
      await audioPlayer.stop();
      audioPlayer.dispose();

      // Cancel the notification
      if (notificationId != null) {
        flutterLocalNotificationsPlugin.cancel(notificationId!);
      }

      // Since this is a reject action, do not navigate to any page
      log('Call Rejected');

      return; // Early return to prevent further navigation
    }

    // Handle other message types based on 'messageType'
    if (messageType == 'missed_call') {
      BottomNavProvider().setPage(3);
    } else if (messageType == 'chat') {
      navigatorKey.currentState?.push(
        MaterialPageRoute(
          builder: (_) => IncomingCallView(
            channelId: data['channelId'],
            agoraToken: data['agora_token'],
            contactName: data['channelId'],
          ),
        ),
      );
    } else if (messageType == 'new_interest') {
      navigatorKey.currentState?.push(
        MaterialPageRoute(
          builder: (_) => IncomingCallView(
            channelId: data['channelId'],
            agoraToken: data['agora_token'],
            contactName: data['caller_name'],
          ),
        ),
      );
    } else if (messageType == 'notification') {
      navigatorKey.currentState?.push(
        MaterialPageRoute(
          builder: (_) =>
              NotificationView(), // Navigate to the notification screen
        ),
      );
    }
  } else {
    log("Notification payload is null or empty.");
  }
}

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
AudioPlayer audioPlayer = AudioPlayer();
int? notificationId;

class PushNoti {
  String? _fcmToken;
  final FirebaseMessaging _fcm = FirebaseMessaging.instance;

  Future<void> requestNotificationPermission() async {
    await _fcm.requestPermission(
      alert: true,
      announcement: false,
      badge: true,
      carPlay: false,
      criticalAlert: false,
      provisional: false,
      sound: true,
    );
    _fcmToken = await _fcm.getToken();
    log('device FCM token is $_fcmToken');

    LocalStorage.setString({'fcmToken': _fcmToken.toString()});
    _fcmToken = await _fcm.getToken();
  }

  Future<void> initPushNotification() async {
    _fcmToken = await _fcm.getToken();
    log('device FCM token is $_fcmToken');

    LocalStorage.setString({'fcmToken': _fcmToken.toString()});

    // Handle incoming messages when the app is in the foreground
    FirebaseMessaging.onMessage.listen((RemoteMessage? message) async {
      // showNotification(message!);
      // If it's a call type message, show the incoming call screen
      if (message!.data.isNotEmpty && message!.data['type'] == 'call') {
        audioPlayer.play();
        navigatorKey.currentState
            ?.push(
          MaterialPageRoute(
            builder: (_) => IncomingCallView(
              channelId: message!.data['channelId'],
              agoraToken: message.data['agora_token'],
              contactName: message.data['caller_name'],
            ),
          ),
        )
            .then((_) {
          if (notificationId != null) {
            audioPlayer.stop();
            flutterLocalNotificationsPlugin.cancel(notificationId!);
          }
        });
      } else {
        showNotification(message);
      }
    });

    // Handle notification taps when the app is in the background or terminated
    FirebaseMessaging.onMessageOpenedApp
        .listen((RemoteMessage? message) async {});

    // Check if the app was opened from a terminated state due to a notification tap
    _fcm.getInitialMessage().then((RemoteMessage? message) {});
  }
}

void showNotification(RemoteMessage message) async {
  RemoteNotification? notification = message.notification;
  AndroidNotification? android = message.notification?.android;

  if (message.data['type'] == 'call') {
    await audioPlayer.setAsset(
        'assets/ringtone/ringtone.mp3'); // Load the ringtone from assets
    audioPlayer.play();

    log('hello world tttt ${message.data.toString()}');
  }
  var platformChannelSpecifics = NotificationDetails(
    android: message.data['type'] == 'call'
        ? AndroidNotificationDetails(
            'call_channel', // For call notifications
            'Incoming Call',

            importance: Importance.max,
            priority: Priority.max,
            // sound: RawResourceAndroidNotificationSound('isa_63861'), // Custom sound for calls (optional)
            playSound: true,
            color: UiColor.primary,
            actions: <AndroidNotificationAction>[
              AndroidNotificationAction(
                'accept_call',
                'Accept',
                // icon: DrawableResourceAndroidBitmap("ic_accept"),
                showsUserInterface: true,
              ),
              AndroidNotificationAction(
                'reject_call',
                'Reject',
                // icon: DrawableResourceAndroidBitmap("ic_reject"),
                showsUserInterface: true,
              ),
            ],
          )
        : AndroidNotificationDetails(
            'important_notifications', // For other notifications
            'Important Notifications',
            importance: Importance.high,
            color: UiColor.primary,
            // sound: RawResourceAndroidNotificationSound("notification_sound"), // Optional custom sound
            actions: [], // No actions for other notifications (can be added dynamically)
          ),
    iOS: DarwinNotificationDetails(
      sound: 'default',
      presentAlert: true,
      presentBadge: true,
      presentSound: true,
    ),
  );

  if (notification != null && android != null) {
    notificationId = notification.hashCode;

    flutterLocalNotificationsPlugin.show(notification.hashCode,
        notification.title, notification.body, platformChannelSpecifics,
        payload: jsonEncode(message.data));
  }
}

Future<void> initLocalNotifications() async {
  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('mipmap/ic_launcher');

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

  await flutterLocalNotificationsPlugin.initialize(initializationSettings,
      onDidReceiveBackgroundNotificationResponse:
          (NotificationResponse notificationResponse) async {
    // Handle background notification response here
    log('Background notification received from local notoifction: ${notificationResponse.payload}');
  }, onDidReceiveNotificationResponse: onSelectNotification);
}

Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  // Show the notification first
  showNotification(message);

  // Check if the message type is 'call'
  if (message.data['type'] == 'call') {
    // Wait for 25 seconds
    await Future.delayed(Duration(seconds: 25));

    // Remove the notification after 25 seconds
    flutterLocalNotificationsPlugin.cancel(message.notification.hashCode);
    log('Notification removed after 25 seconds for call type.');
  }
}

Future<void> firebaseMessagingForegroundHandler(RemoteMessage message) async {
  if (message.data['type'] == 'call') {
    await audioPlayer.setAsset(
        'assets/ringtone/ringtone.mp3'); // Load the ringtone from assets
    audioPlayer.play();
  } else {
    showNotification(message);
  }
}

2

Answers


  1. Did you add these lines of code?

    You need to configure a top level or static method which will handle the action:

    @pragma('vm:entry-point')
    void notificationTapBackground(NotificationResponse notificationResponse) {
      // handle action
    }
    

    Specify this function as a parameter in the initialize method of this plugin:

    await flutterLocalNotificationsPlugin.initialize(
        initializationSettings,
        onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) async {
            // ...
        },
        onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
    );
    
    Login or Signup to reply.
  2. For me its resolved by unrestricting battery optimsation in app battery setting which can be find in devices setting-> apps -> [‘app name’]->battery and turn off toggole for (cancel permission when app in unused) . After changing this setup its start working

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search