aws-amplify / amplify-flutter

A declarative library with an easy-to-use interface for building Flutter applications on AWS.
https://docs.amplify.aws
Apache License 2.0
1.31k stars 240 forks source link

Amplify.Notifications.Push.launchNotification returns null #3710

Open szymondobrzanski opened 10 months ago

szymondobrzanski commented 10 months ago

Description

Hello, Amplify.Notifications.Push.launchNotification returns null each time I sent push notification to terminated app. Both foreground and background notifications are delivered and read properly. I tested this behavior on both Android and iOS devices (in release mode for iOS). App is killed (by swipe - not from editor), I sent push notification, it is delivered to phone, after clicking on it app is opened but launchNotification returns null. Here is log example from Android device in release mode:

W/FlutterJNI(29654): FlutterJNI.loadLibrary called more than once
W/FlutterJNI(29654): FlutterJNI.init called more than once
I/flutter (29654): 🚀 launchNotificationFromTerminated: null

I checked and launchNotification is not consumed anywhere before.

I attach code of my configuration and notifications service:

Configuration:

Future<void> main() async {
  await configureApp();
  runMain();
}

void runMain() {
  runApp(MyApp());
}

Future<void> configureApp() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  const environment =
      String.fromEnvironment('ENVIRONMENT_F', defaultValue: Environment.prod);
  if (kDebugMode) {
    await firebaseCrashlyticsInstance.setCrashlyticsCollectionEnabled(false);
  }
  FlutterError.onError = (errorDetails) {
    firebaseCrashlyticsInstance.recordFlutterFatalError(errorDetails);
  };
  PlatformDispatcher.instance.onError = (error, stack) {
    firebaseCrashlyticsInstance.recordError(error, stack, fatal: true);
    return true;
  };
  await configureInjection(environment);
  try {
    if (Amplify.isConfigured) {
      safePrint('Amplify is already configured. Skipping configuration.');
    } else {
      final auth = AmplifyAuthCognito();
      final dataStorePlugin =
          AmplifyDataStore(modelProvider: ModelProvider.instance);
      final api = AmplifyAPI(modelProvider: ModelProvider.instance);
      final pushPlugin = AmplifyPushNotificationsPinpoint();
      PushNotificationsService.initalize(getIt<INotificationsRepository>());
      pushPlugin.onNotificationReceivedInBackground(
          PushNotificationsService.backgroundNotificationReceivedHandler);
      await Amplify.addPlugins([dataStorePlugin, api, auth, pushPlugin]);
      await Amplify.configure(amplifyconfig);
      PushNotificationsService.launchNotificationFromTerminated();
PushNotificationsService.startListeningToForeground();
          PushNotificationsService.onNotificationOpened();
    }
  } catch (e) {
    safePrint('An error occurred while configuring Amplify: $e');
    reportError(
        exception: 'Aws Amplify framework could not be initialised',
        reason: 'An error occurred while configuring Amplify: $e');
  }
}

Service:

@injectable
@pragma('vm:entry-point')
class PushNotificationsService {
  static late final PushNotificationsService _self;
  static late final INotificationsRepository _notificationsRepository;
  static late final StreamSubscription<PushNotificationMessage>
      _foregroundStream;
  static late final StreamSubscription<PushNotificationMessage>
      _notificationsOpenedStream;
  static late final StreamSubscription<String> _tokenStream;

  PushNotificationsService._internal(
      INotificationsRepository notificationsRepository) {
    _notificationsRepository = notificationsRepository;
  }

  @factoryMethod
  static PushNotificationsService initalize(
      INotificationsRepository notificationsRepository) {
    _self = PushNotificationsService._internal(notificationsRepository);
    return _self;
  }

  static Future<bool> requestPermission() async {
    final status = await Amplify.Notifications.Push.getPermissionStatus();
    if (status == PushNotificationPermissionStatus.granted) {
      return true;
    }
    if (status == PushNotificationPermissionStatus.shouldRequest ||
        status == PushNotificationPermissionStatus.shouldExplainThenRequest) {
      final status = await Amplify.Notifications.Push.requestPermissions();

      await _notificationsRepository.saveNotifications(status);
      return status;
    }
    return false;
  }

  static void startListeningToForeground() {
    _foregroundStream = Amplify
        .Notifications.Push.onNotificationReceivedInForeground
        .listen(_foregroundNotificationReceivedHandler);
  }

  static void onNotificationOpened() {
    _notificationsOpenedStream = Amplify.Notifications.Push.onNotificationOpened
        .listen(_notificationOpenedHandler);
  }

  ///Used to get device id in order to test notifications
  static void onTokenReceived() {
    _tokenStream = Amplify.Notifications.Push.onTokenReceived
        .listen(_tokenReceivedHandler);
  }

  static void launchNotificationFromTerminated() {
    final notification = Amplify.Notifications.Push.launchNotification;
    print('🚀 launchNotificationFromTerminated: $notification');
    if (notification != null) {
       //do sth
    }
  }

  static void _foregroundNotificationReceivedHandler(
      PushNotificationMessage notification) {
    print('🚀 foregroundNotificationReceivedHandler: $notification');
    if (notification.title != null && notification.body != null) {
      //display local notification
    }
  }

  static Future<void> backgroundNotificationReceivedHandler(
      PushNotificationMessage notification) async {
    print('🚀 backgroundNotificationReceivedHandler: $notification');

    ///onNotificationOpened won't work on iOS because of flutter_local_notifications config
    ///https://github.com/aws-amplify/amplify-flutter/issues/3273 -> that's why navigation is done here
    if (Platform.isIOS) {
        //do sth
    }
  }

  static void _notificationOpenedHandler(PushNotificationMessage notification) {
    ///Works only on Android as per comment above
    print('🚀 _notificationOpenedHandler: $notification');
  }

  static void cancelStreams() {
    _foregroundStream.cancel();
    _notificationsOpenedStream.cancel();
    _tokenStream.cancel();
  }
}

Categories

Steps to Reproduce

No response

Screenshots

No response

Platforms

Flutter Version

3.13.0

Amplify Flutter Version

1.4.0

Deployment Method

Amplify CLI

Schema

No response

HuiSF commented 10 months ago

Hi @szymondobrzanski

For iOS, if you enabled the background mode for your app, when a PN arrives on the phone after the app is killed, it will launch the app in the background. In this case, the message will be emitted via onNotificationReceivedInBackground. And then, when you click on the PN to open the app, as the app has already launched in the background, the message will be emitted via onNotificationOpened. If the background mode is not enabled, when you click the PN to launch the app, the message should be accessible via Amplify.Notifications.Push.launchNotification.

For android, while you are seeing Amplify.Notifications.Push.launchNotification returns null, do you receive any message event from other channels (onNotificationReceivedInBackground etc.)?

szymondobrzanski commented 10 months ago

Hi @HuiSF, nope the only log I'm getting is from Amplify.Notifications.Push.launchNotification as stated above, other channels don't invoke any notifications.

szymondobrzanski commented 10 months ago

@HuiSF any update from your side? Did you try to reproduce it?

szymondobrzanski commented 10 months ago

Hey @HuiSF @dnys1 any update?

cwomack commented 9 months ago

@szymondobrzanski, apologies on the delayed response. Can you share details of the android environment that you're using? Is it an emulator or a real, physical device? There was a similar issue (#3143) that we couldn't reproduce, and has since been closed tied to Android behavior .

On the iOS side, I believe it's expected behavior if the you have enabled background mode. We're still investigating this, so any further context/answers would be helpful! Thank you.

szymondobrzanski commented 9 months ago

Hey @cwomack , thanks for response. I was using Android physical device with API Level 26. Why returning null after opening app from terminated state in iOS device would be expected behavior? Enabling background modes for iOS is one of configuration steps in Amplify documentation and there is none information that getLaunchNotification will return null for iOS by default.

chrispypatt commented 7 months ago

@cwomack I am experiencing the same behavior on iOS. I have tried with both with background notification mode on and off based on your comment above. I have my notification code register with onNotificationOpened and check Amplify.Notifications.Push.launchNotification for notifications after the app has initialized with no luck.

Foreground notifications and background notifications both work as expected 100% of the time but when I kill the app trigger a notification, tap it to launch the app, it is not received by the launchNotification or onNotificationOpened.

rest950 commented 5 months ago

Same issue on iOS, launchNotification and onNotificationOpened are both not trigged from terminated state.

bitsplash-andy commented 5 months ago

@rest950 Do you have firebase messaging as part of the project?

If you are using firebase_messaging in any way in the project, iOS will stop behaving as expected, the two libraries are currently incompatible.

see #4237

bitsplash-andy commented 5 months ago

Just to add to this, I am also experiencing this issue, but only on Android. I receive tokens, foreground and background notifications as expected but am unable to get access to the notification that launched the app. iOS works fine providing the firebase_messaging library isn't used in conjunction with Amplify. Frustratingly, I didn't experience this issue using Firebase, so we've swapped one problem for another.

rest950 commented 5 months ago

@bitsplash-andy

My project no longer utilizes Firebase Messaging; it only relies on other Firebase services. However, on iOS in the terminated state, I am unable to trigger launchNotification or onNotificationOpened.

Regarding Android's ability to obtain launchNotification, in my tests, I found that it can only be trigged in a release build; it is not trigged in a debug build. Please give it a try and see if you encounter the same issue.

HuiSF commented 5 months ago

Hi @rest950

However, on iOS in the terminated state, I am unable to trigger launchNotification

Have you enabled the background mode for you iOS app?

If you have enabled background mode for your iOS app, when a push notification arrives on the device and your app is in the terminated state - the notification wakes your app up in the background. In this case, the notification will be sent to the callback of onNotificationReceivedInBackground - so there won't be a launchNotification.

I am unable to trigger onNotificationOpened.

Is this happening when you tap on a notification in the notification center or the PN banner to open the app?

chrispypatt commented 5 months ago

I am still seeing this on iOS and we do not use any Firebase dependencies.

bitsplash-andy commented 5 months ago

In our case this didn’t work on Android because we were using google-services 4.4.1 in our build.gradle.

Downgrading google-services to 4.3.15 resolved the issue. Not sure if the library is incompatible with 4.4.x versions at this time?

rest950 commented 4 months ago

I am unable to trigger onNotificationOpened.

Is this happening when you tap on a notification in the notification center or the PN banner to open the app?

@HuiSF

After my attempts, I found that tap from notification center successfully triggered onNotificationOpened. However, tap from the PN Banner did not trigger onNotificationOpened. Is this the expected behavior?

Jordan-Nelson commented 2 months ago

Apologies for the delay. Thank you for the additional info. We will attempt to reproduce this with the additional info provided.