amplify-js: iOS: onNotificationReceivedInForeground is always triggered when Push Notification is clicked
Before opening, please confirm:
- I have searched for duplicate or closed issues and discussions.
- I have read the guide for submitting bug reports.
- I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
JavaScript Framework
React Native
Amplify APIs
Authentication, GraphQL API, Push Notifications
Amplify Categories
auth, function, api, notifications
Environment information
System:
OS: macOS 12.6.2
CPU: (8) x64 Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
Memory: 34.70 MB / 16.00 GB
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 18.16.0 - ~/.nvm/versions/node/v18.16.0/bin/node
Yarn: 1.22.19 - /usr/local/bin/yarn
npm: 9.5.1 - ~/.nvm/versions/node/v18.16.0/bin/npm
Watchman: 2023.02.13.00 - /usr/local/bin/watchman
Browsers:
Chrome: 112.0.5615.137
Safari: 15.6.1
Safari Technology Preview: 14.2
npmPackages:
@apollo/client: ^3.7.7 => 3.7.13
@apollo/client/cache: undefined ()
@apollo/client/core: undefined ()
@apollo/client/errors: undefined ()
@apollo/client/link/batch: undefined ()
@apollo/client/link/batch-http: undefined ()
@apollo/client/link/context: undefined ()
@apollo/client/link/core: undefined ()
@apollo/client/link/error: undefined ()
@apollo/client/link/http: undefined ()
@apollo/client/link/persisted-queries: undefined ()
@apollo/client/link/retry: undefined ()
@apollo/client/link/schema: undefined ()
@apollo/client/link/subscriptions: undefined ()
@apollo/client/link/utils: undefined ()
@apollo/client/link/ws: undefined ()
@apollo/client/react: undefined ()
@apollo/client/react/components: undefined ()
@apollo/client/react/context: undefined ()
@apollo/client/react/hoc: undefined ()
@apollo/client/react/hooks: undefined ()
@apollo/client/react/parser: undefined ()
@apollo/client/react/ssr: undefined ()
@apollo/client/testing: undefined ()
@apollo/client/testing/core: undefined ()
@apollo/client/utilities: undefined ()
@apollo/client/utilities/globals: undefined ()
@aws-amplify/rtn-push-notification: ^1.1.1 => 1.1.1
@aws-amplify/ui-react-native: ^1.2.14 => 1.2.14
@aws-sdk/client-lex-runtime-v2: ^3.262.0 => 3.321.1 (3.186.1)
@babel/core: ^7.12.9 => 7.21.5
@babel/runtime: ^7.12.5 => 7.21.5
@formatjs/intl-datetimeformat: ^6.5.1 => 6.7.0
@formatjs/intl-getcanonicallocales: ^2.1.0 => 2.1.0
@formatjs/intl-locale: ^3.1.1 => 3.2.1
@formatjs/intl-numberformat: ^8.3.5 => 8.4.1
@formatjs/intl-pluralrules: ^5.1.10 => 5.2.1
@react-native-async-storage/async-storage: ^1.18.1 => 1.18.1
@react-native-community/eslint-config: ^2.0.0 => 2.0.0
@react-native-community/netinfo: ^9.3.9 => 9.3.9
@react-native-masked-view/masked-view: ^0.2.8 => 0.2.9
@react-native-picker/picker: ^2.4.8 => 2.4.10
@react-navigation/bottom-tabs: ^6.5.5 => 6.5.7
@react-navigation/native: ^6.0.14 => 6.1.6
@react-navigation/native-stack: ^6.9.2 => 6.9.12
@react-navigation/stack: ^6.3.13 => 6.3.16
@reduxjs/toolkit: ^1.9.3 => 1.9.5
@reduxjs/toolkit-query: 1.0.0
@reduxjs/toolkit-query-react: 1.0.0
@rneui/base: ^4.0.0-rc.7 => 4.0.0-rc.7
@rneui/themed: ^4.0.0-rc.7 => 4.0.0-rc.7
@rtk-query/graphql-request-base-query: ^2.2.0 => 2.2.0
@tsconfig/react-native: ^2.0.2 => 2.0.3
@types/jest: ^26.0.23 => 26.0.24
@types/lodash: ^4.14.191 => 4.14.194
@types/react: ^18.0.21 => 18.2.0
@types/react-native: ^0.70.6 => 0.70.13
@types/react-native-vector-icons: ^6.4.13 => 6.4.13
@types/react-test-renderer: ^18.0.0 => 18.0.0
@types/styled-components: ^5.1.26 => 5.1.26
@types/styled-components-react-native: ^5.2.1 => 5.2.1
@types/uuid: ^9.0.0 => 9.0.1
@typescript-eslint/eslint-plugin: ^5.37.0 => 5.59.1 (3.10.1)
@typescript-eslint/parser: ^5.37.0 => 5.59.1 (3.10.1)
HelloWorld: 0.0.1
amazon-cognito-identity-js: ^6.2.0 => 6.2.0
apollo3-cache-persist: ^0.14.1 => 0.14.1
aws-amplify: ^5.1.4 => 5.1.4
babel-jest: ^26.6.3 => 26.6.3
babel-plugin-module-resolver: ^5.0.0 => 5.0.0
date-fns: ^2.29.3 => 2.30.0
eslint: ^7.32.0 => 7.32.0
eslint-config-prettier: ^8.6.0 => 8.8.0 (6.15.0)
eslint-plugin-import: ^2.27.5 => 2.27.5
eslint-plugin-prettier: ^4.2.1 => 4.2.1 (3.1.2)
eslint-plugin-react-hooks: ^4.6.0 => 4.6.0
example: 0.0.1
graphql: ^16.6.0 => 16.6.0 (15.8.0)
hermes-inspector-msggen: 1.0.0
husky: ^8.0.0 => 8.0.3
jest: ^26.6.3 => 26.6.3
lint-staged: ^13.1.0 => 13.2.2
lodash: ^4.17.21 => 4.17.21
metro-react-native-babel-preset: 0.72.3 => 0.72.3
prettier: ^2.8.3 => 2.8.8
react: ^18.2.0 => 18.2.0
react-intl: ^6.2.8 => 6.4.1
react-native: 0.70.6 => 0.70.6
react-native-autoheight-webview: ^1.6.5 => 1.6.5
react-native-config: ^1.5.0 => 1.5.0
react-native-device-info: ^10.4.0 => 10.6.0
react-native-flash-message: ^0.4.0 => 0.4.1
react-native-gesture-handler: ^2.9.0 => 2.9.0
react-native-get-random-values: ^1.8.0 => 1.8.0
react-native-gifted-chat: ^2.0.1 => 2.0.1
react-native-iap: ^12.10.0 => 12.10.5
react-native-linear-gradient: ^2.6.2 => 2.6.2
react-native-localize: ^2.2.6 => 2.2.6
react-native-render-html: ^6.3.4 => 6.3.4
react-native-safe-area-context: ^4.5.0 => 4.5.2
react-native-screens: ^3.18.2 => 3.20.0
react-native-secure-key-store: ^2.0.10 => 2.0.10
react-native-splash-screen: ^3.3.0 => 3.3.0
react-native-svg: ^13.8.0 => 13.9.0
react-native-svg-transformer: ^1.0.0 => 1.0.0
react-native-url-polyfill: ^1.3.0 => 1.3.0
react-native-vector-icons: ^9.2.0 => 9.2.0
react-native-webview: ^11.26.1 => 11.26.1
react-redux: ^8.0.5 => 8.0.5
react-test-renderer: 18.1.0 => 18.1.0
reactotron-react-native: ^5.0.3 => 5.0.3
redux-persist: ^6.0.0 => 6.0.0
redux-persist/integration/react: undefined ()
styled-components: ^5.3.9 => 5.3.10
styled-components/macro: undefined ()
styled-components/native: undefined ()
styled-components/primitives: undefined ()
typescript: ^4.8.3 => 4.9.5
uuid: ^9.0.0 => 9.0.0 (3.4.0, 8.3.2, 7.0.3)
npmGlobalPackages:
corepack: 0.17.0
npm: 9.5.1
Describe the bug
onNotificationReceivedInForeground is triggered not only for Push notifications received in foreground iOS app.
But also when:
- Push notification is clicked when app is in background (
onNotificationOpenedalso works) - Push notification is clicked when app is terminated (
Notifications.Push.getLaunchNotificationalso works)
So I only need onNotificationReceivedInForeground if app is foreground, moreover, schemas in documentation doesn’t contains trigger of this listener for background or terminated states.
Is it expected that onNotificationReceivedInForeground triggers always?
Expected behavior
onNotificationReceivedInForeground triggers only when push notification comes when app is in foreground mode
Reproduction steps
- Check Additional configuration for the high-level code
- Bug is reproduced using AWS Pinpoint
Test messagingfeature - This migration guide is used: https://docs.amplify.aws/lib/push-notifications/migrate-from-previous-version/q/platform/react-native/
aws-exports.js
/* eslint-disable */
// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.
const awsmobile = {
"aws_project_region": "us-east-1",
"aws_mobile_analytics_app_id": "xxx",
"aws_mobile_analytics_app_region": "us-east-1",
"Analytics": {
"AWSPinpoint": {
"appId": "xxx",
"region": "us-east-1"
}
},
"Notifications": {
"Push": {
"AWSPinpoint": {
"appId": "xxx",
"region": "us-east-1"
}
}
},
"aws_appsync_graphqlEndpoint": "https://xxx/graphql",
"aws_appsync_region": "us-east-1",
"aws_appsync_authenticationType": "API_KEY",
"aws_appsync_apiKey": "xxx",
"aws_cognito_identity_pool_id": "xxx",
"aws_cognito_region": "us-east-1",
"aws_user_pools_id": "xxx",
"aws_user_pools_web_client_id": "xxx"",
"oauth": {},
"aws_cognito_username_attributes": [
"EMAIL"
],
"aws_cognito_social_providers": [],
"aws_cognito_signup_attributes": [
"NAME",
"FAMILY_NAME"
],
"aws_cognito_mfa_configuration": "OFF",
"aws_cognito_mfa_types": [
"SMS"
],
"aws_cognito_password_protection_settings": {
"passwordPolicyMinLength": 8,
"passwordPolicyCharacters": []
},
"aws_cognito_verification_mechanisms": [
"EMAIL"
]
};
export default awsmobile;
Manual configuration
No response
Additional configuration
AppDelegate.h
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>
@property(nonatomic, strong) UIWindow *window;
@end
AppDelegate.mm
#import "AppDelegate.h"
#import "AmplifyPushNotification.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "RNSplashScreen.h"
#import <React/RCTAppSetupUtils.h>
#import <React/RCTLinkingManager.h>
#if RCT_NEW_ARCH_ENABLED
#import <React/CoreModulesPlugins.h>
#import <React/RCTCxxBridgeDelegate.h>
#import <React/RCTFabricSurfaceHostingProxyRootView.h>
#import <React/RCTSurfacePresenter.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <ReactCommon/RCTTurboModuleManager.h>
#import <react/config/ReactNativeConfig.h>
static NSString *const kRNConcurrentRoot = @"concurrentRoot";
@interface AppDelegate () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate> {
RCTTurboModuleManager *_turboModuleManager;
RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
std::shared_ptr<const facebook::react::ReactNativeConfig> _reactNativeConfig;
facebook::react::ContextContainer::Shared _contextContainer;
}
@end
#endif
@implementation AppDelegate
// Required for deep linking
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
return [RCTLinkingManager application:application openURL:url options:options];
}
// Required for Push Notifications
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[AmplifyPushNotification didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
[AmplifyPushNotification didReceiveRemoteNotification:userInfo withCompletionHandler:completionHandler];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
RCTAppSetupPrepareApp(application);
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
#if RCT_NEW_ARCH_ENABLED
_contextContainer = std::make_shared<facebook::react::ContextContainer const>();
_reactNativeConfig = std::make_shared<facebook::react::EmptyReactNativeConfig const>();
_contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
_bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer];
bridge.surfacePresenter = _bridgeAdapter.surfacePresenter;
#endif
NSDictionary *initProps = [self prepareInitialProps];
UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"AstuvetMobile", initProps);
if (@available(iOS 13.0, *)) {
rootView.backgroundColor = [UIColor systemBackgroundColor];
} else {
rootView.backgroundColor = [UIColor whiteColor];
}
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
[RNSplashScreen show];
return YES;
}
/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
///
/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
/// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it returns `false`.
- (BOOL)concurrentRootEnabled
{
// Switch this bool to turn on and off the concurrent root
return true;
}
- (NSDictionary *)prepareInitialProps
{
NSMutableDictionary *initProps = [NSMutableDictionary new];
#ifdef RCT_NEW_ARCH_ENABLED
initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]);
#endif
return initProps;
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
#if RCT_NEW_ARCH_ENABLED
#pragma mark - RCTCxxBridgeDelegate
- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
_turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
delegate:self
jsInvoker:bridge.jsCallInvoker];
return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager);
}
#pragma mark RCTTurboModuleManagerDelegate
- (Class)getModuleClassFromName:(const char *)name
{
return RCTCoreModulesClassProvider(name);
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
return nullptr;
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
initParams:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return nullptr;
}
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
return RCTAppSetupDefaultModuleFromClass(moduleClass);
}
#endif
@end
Podfile
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
platform :ios, '13.0'
install! 'cocoapods', :deterministic_uuids => false
target 'AstuvetMobile' do
config = use_native_modules!
# Flags change depending on the env values.
flags = get_default_flags()
use_react_native!(
:path => config[:reactNativePath],
# Hermes is now enabled by default. Disable by setting this flag to false.
# Upcoming versions of React Native may rely on get_default_flags(), but
# we make it explicit here to aid in the React Native upgrade process.
:hermes_enabled => false, # false is set for debug purpose: https://github.com/facebook/react-native/issues/34624
:fabric_enabled => flags[:fabric_enabled],
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
:flipper_configuration => FlipperConfiguration.enabled,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
target 'AstuvetMobileTests' do
inherit! :complete
# Pods for testing
end
post_install do |installer|
react_native_post_install(
installer,
# Set `mac_catalyst_enabled` to `true` in order to apply patches
# necessary for Mac Catalyst builds
:mac_catalyst_enabled => false
)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
end
end
index.js
import 'react-native-gesture-handler';
import 'react-native-get-random-values';
import 'react-native-url-polyfill/auto';
import { AppRegistry } from 'react-native';
import { Amplify, Notifications } from 'aws-amplify';
import App from './src/modules/App/App.tsx';
import { name as appName } from './app.json';
import awsconfig from './src/aws-exports';
Amplify.configure(awsconfig);
Notifications.Push.enable();
AppRegistry.registerComponent(appName, () => App);
App.tsx
const App = () => {
useRequestNotificationsPermissions();
useBackgroundAndForegroundNotifications();
const launchNotificationState = useGetLaunchNotification();
return (
<...>
);
};
export const useBackgroundAndForegroundNotifications = () => {
const tokenListener = useRef<any>();
const foregroundListener = useRef<any>();
const notificationOpenedListener = useRef<any>();
useEffect(() => {
tokenListener.current =
Notifications.Push.onTokenReceived(handleTokenReceived);
foregroundListener.current =
Notifications.Push.onNotificationReceivedInForeground(
handleNotificationReceivedInForeground,
);
notificationOpenedListener.current =
Notifications.Push.onNotificationOpened(handleNotificationOpened);
return () => {
tokenListener.current?.remove();
foregroundListener.current?.remove();
notificationOpenedListener.current?.remove();
};
}, []);
};
export const useRequestNotificationsPermissions = () => {
useAsyncEffect(async () => {
checkAndRequestPushNotificationsPermissions();
}, []);
};
const checkAndRequestPushNotificationsPermissions = async () => {
const status = await Notifications.Push.getPermissionStatus();
if (status === 'GRANTED') {
return;
}
if (status === 'DENIED') {
// further attempts to request permissions will no longer do anything
return;
}
if (status === 'SHOULD_REQUEST') {
await Notifications.Push.requestPermissions();
}
if (status === 'SHOULD_EXPLAIN_THEN_REQUEST') {
// you should display some explanation to your user before requesting permissions
// TODO explain
// then request permissions
await Notifications.Push.requestPermissions();
}
};
export const useGetLaunchNotification = (): LaunchNotificationState => {
const { data: launchNotification, loading: loadingLaunchNotification } =
useQuery(Notifications.Push.getLaunchNotification);
return {
launchNotification,
loadingLaunchNotification,
};
};
export default App;
Mobile Device
iPhone 14
Mobile Operating System
iOS 16.4.1
Additional information and screenshots
This video presents the following case:
- App is terminated
- User clicks notification and app is launched
getLaunchNotificationis working because the first screen is a screen opened by deep link url- But
onNotificationReceivedInForegroundtriggers as well, because inner notification is visible
About this issue
- Original URL
- State: closed
- Created a year ago
- Comments: 18 (7 by maintainers)
Not sure if it can help, but react-native-splash-screen library was preventing
Notifications.Push.onNotificationOpenedandNotifications.Push.getLaunchNotificationfrom being triggered when a push notification was pressed.Without that library it works fine… now gotta find an alternative to the splash screen 😮💨
@tyndria, thanks for quick the reply. If you get a chance to properly test everything for Amplify Push Notifications then let us know if the issue persists. I’m curious to hear if replacing (or removing) the
react-native-splash-screenlibrary also resolves any issues you saw on the Amplify side.@cwomack hi!
sorry for the late response, but this is what I’ve done:
Then I did the same with amplify push notifications and I stopped receiving additional inner notifications (but launch notifications weren’t working which could be just sth else, because I was in a hurry) So it seems that ‘react-native-splash-screen’ could be a problem, but I haven’t tested everything properly for amplify notifications