braze-react-native-sdk
braze-react-native-sdk copied to clipboard
[Bug]: Push Notification doesn't redirect if App is killed and device has been inactive for a few minutes
Which Platforms?
iOS
Which React Native Version?
0.76.6
Which @braze/react-native-sdk SDK version?
13.1.1
Repro Rate
50%
Steps To Reproduce
- Make sure the app is killed and not in the background
- Start a push notification campaign with a deep link
- Sent test push notification
- Leave push notification
- Open push notification sometimes it doesn't deep link to the specific screen; sometimes
If I immediately click the notification right after it was sent, it works 100% of the time.
I also followed the sample app setup
Expected Behavior
The app should redirect to the right deep-linked screen even if the app is in a killed state and if the device has been inactive for a long duration.
Actual Incorrect Behavior
Sometimes, when you click on the link, it opens the app but doesn't redirect
Verbose Logs
Additional Information
Helper hook we use in our app entry file to initialize and listen for Braze push notifications.
import Braze from '@braze/react-native-sdk';
import { useEffect } from 'react';
import { BRAZE_DEEP_LINK } from '../constants/mmkv';
import { linking } from '../constants/routes';
import mmkvStorage from '../mmkvStorage';
import { linkTo } from '../navigation/RootNavigation';
export default ({ navigationReady }: { navigationReady: boolean }) => {
// Handle navigation ready state changes
useEffect(() => {
if (navigationReady) {
const storedUrl = mmkvStorage.getString(BRAZE_DEEP_LINK);
if (storedUrl && linking?.config) {
linkTo(storedUrl, linking.config);
}
}
}, [navigationReady]);
useEffect(() => {
Braze.requestPushPermission();
const handleUrl = (url: string) => {
if (linking?.config && navigationReady) {
linkTo(url, linking.config);
} else {
mmkvStorage.set(BRAZE_DEEP_LINK, url);
}
};
// Handle cold start
Braze.getInitialPushPayload((payload) => {
if (payload?.url) {
handleUrl(payload.url);
}
});
// Handle background/foreground notifications
const subscription = Braze.addListener('pushNotificationEvent', (payload) => {
if (payload?.url) {
handleUrl(payload.url);
}
});
return () => {
subscription.remove();
};
}, []);
};
React Navigation Linking Object
export const linking: LinkingOptions<RootStackParamList> | undefined = {
prefixes: [
'https://...'
],
async getInitialURL() {
const pause = async (timeMs: number) => new Promise((resolve) => setTimeout(resolve, timeMs));
const url = await Linking.getInitialURL();
if (checkIfUniversalShortlink(url)) {
const longUrl = await getLongUrl(url);
// Wait to make sure navigation is initialized
await pause(500);
return longUrl ?? url;
}
// Wait to make sure navigation is initialized
await pause(500);
return url;
},
// Custom function to subscribe to incoming links
subscribe(listener: (url: string) => void) {
// First, you may want to do the default deep link handling
const linkingSubscription = Linking.addEventListener('url', async ({ url }) => {
if (checkIfBiltShortlink(url)) {
const longUrl = await getLongUrl(url);
listener(longUrl ?? url);
} else {
listener(url);
}
});
return () => {
linkingSubscription.remove();
};
},
config,
};
// Braze
#import <BrazeKit/BrazeKit-Swift.h>
#import "BrazeReactBridge.h"
#import <BrazeReactUtils.h>
@implementation AppDelegate
static NSString *const brazeApiKey = @"...";
static NSString *const brazeEndpoint = @"...";
static NSString *const iOSPushAutoEnabledKey = @"iOSPushAutoEnabled";
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// ...
// Setup Braze
BRZConfiguration *configuration = [[BRZConfiguration alloc] initWithApiKey:brazeApiKey endpoint:brazeEndpoint];
// Default to automatically setting up push notifications
BOOL pushAutoEnabled = NO;
if ([[NSUserDefaults standardUserDefaults] objectForKey:iOSPushAutoEnabledKey]) {
pushAutoEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:iOSPushAutoEnabledKey];
}
if (pushAutoEnabled) {
NSLog(@"iOS Push Auto enabled.");
configuration.push.automation = [[BRZConfigurationPushAutomation alloc] initEnablingAllAutomations:YES];
}
Braze *braze = [BrazeReactBridge initBraze:configuration];
AppDelegate.braze = braze;
if (!pushAutoEnabled) {
// If the user explicitly disables Push Auto, register for push manually
NSLog(@"iOS Push Auto disabled - Registering for push manually.");
[self registerForPushNotifications];
}
[[BrazeReactUtils sharedInstance] populateInitialPayloadFromLaunchOptions:launchOptions];
[application registerForRemoteNotifications];
UNUserNotificationCenter *center = UNUserNotificationCenter.currentNotificationCenter;
[center setNotificationCategories:BRZNotifications.categories];
center.delegate = self;
UNAuthorizationOptions options = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
if (@available(iOS 12.0, *)) {
options = options | UNAuthorizationOptionProvisional;
}
[center requestAuthorizationWithOptions:options
completionHandler:^(BOOL granted, NSError *_Nullable error) {
NSLog(@"Notification authorization, granted: %d, error: %@)", granted, error);
if (granted) {
NSLog(@"Attempting to create bridge class: %@", bridgeClass);
// Store the bridge in the property instead of local variable
if (self.brazeBridge) {
if (@available(iOS 17.2, *)) {
[self.brazeBridge registerActivityType];
}
NSLog(@"Calling brazeBridge resumeActivities", self.brazeBridge);
if (@available(iOS 16.1, *)) {
[self.brazeBridge resumeActivities];
}
}
}
}];
// ...
return YES;
}
#pragma mark - Push Notifications (manual integration)
- (void)registerForPushNotifications {
UNUserNotificationCenter *center = UNUserNotificationCenter.currentNotificationCenter;
[center setNotificationCategories:BRZNotifications.categories];
center.delegate = self;
[UIApplication.sharedApplication registerForRemoteNotifications];
// Authorization is requested later in the JavaScript layer via `Braze.requestPushPermission`.
}
// application:didReceiveRemoteNotification:fetchCompletionHandler:
// [not deprecated, but is superseded by the UNUserNotificationCenterDelegate methods]
// docs: https://reactnative.dev/blog/2024/04/22/release-0.74
// - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// BOOL processedByBraze = AppDelegate.braze != nil && [AppDelegate.braze.notifications handleBackgroundNotificationWithUserInfo:userInfo fetchCompletionHandler:completionHandler];
// if (processedByBraze) {
// return;
// }
// completionHandler(UIBackgroundFetchResultNoData);
// }
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler {
[[BrazeReactUtils sharedInstance] populateInitialUrlForCategories:response.notification.request.content.userInfo];
BOOL processedByBraze = AppDelegate.braze != nil && [AppDelegate.braze.notifications handleUserNotificationWithResponse:response withCompletionHandler:completionHandler];
if (processedByBraze) {
return;
}
completionHandler();
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
if (@available(iOS 14.0, *)) {
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
} else {
completionHandler(UNNotificationPresentationOptionAlert);
}
}
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[AppDelegate.braze.notifications registerDeviceToken:deviceToken];
}
#pragma mark - AppDelegate.braze
static Braze *_braze = nil;
+ (Braze *)braze {
return _braze;
}
+ (void)setBraze:(Braze *)braze {
_braze = braze;
}