Closed Minishlink closed 1 year ago
same issue here; spent almost 4 days debuging tons of issues raised from assignment of delegate to react bridge.
react-native-reanimated breaks as it expects the delegate to be an instance of UIResponder. react-native-firebase also broke
finally, I got push notifications, deep links to work; in-app messaging and content-cards not so much success
info:i use React native 0.71.4 & latest version here 3.0.0;
Hi all,
Thanks for raising this concern! We have filed a ticket internally to investigate how to refactor and improve the integration to support the use cases described and to update our public docs with the findings. One thing to note (if applicable for your situation) is that the Braze React Native SDK does not yet officially support the New Architecture - this is one of our next items prioritized in our roadmap.
In the meantime, if you figure out any workarounds or have specific suggestions on how to improve the iOS bridge integration, feel free to post on this thread.
We are experiencing the same problem, this is unrelated to the new architecture. Renimated, which is a very popular library, is incompatible with how the Bridge is initialised currently.
ERROR [Reanimated] Couldn't determine the version of the native part of Reanimated. Did you forget to re-build the app after upgrading react-native-reanimated? If you use Expo Go, you must use the exact version which is bundled into Expo SDK.
ERROR TypeError: Cannot read property 'installCoreFunctions' of undefined, js engine: hermes
@Minishlink what is the old installation method? is it a workaround or simply a suggestion to downgrade? thanks
Currently the Braze SDK doesn't make anything of the fact that it is a delegate of RCTBridge, it's just installation annoyance ;) So you can simply skip this step.
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
RCTAppSetupPrepareApp(application);
NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
NSString *brazeAPIKey = [info objectForKey:@"BrazeAPIKey"];
NSAssert(brazeAPIKey != nil, @"No Braze API key");
BRZConfiguration *configuration = [[BRZConfiguration alloc] initWithApiKey:brazeAPIKey
endpoint:@"sdk.fra-01.braze.eu"];
Braze *braze = [BrazeReactBridge initBraze:configuration];
braze.inAppMessagePresenter = [[BrazeInAppMessageUI alloc] init];
BRZGIFViewProvider.shared = [BRZGIFViewProvider sdWebImage];
AppDelegate.braze = braze;
[[BrazeReactUtils sharedInstance] populateInitialUrlFromLaunchOptions:launchOptions];
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:BRZNotifications.categories];
NSDictionary *pushDictionary = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
BOOL launchedFromBrazeInternalPush = pushDictionary && [BRZNotifications isInternalNotification: pushDictionary];
NSMutableDictionary *appProperties = [NSMutableDictionary dictionaryWithDictionary:[RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions]];
if (launchedFromBrazeInternalPush && [UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
appProperties[@"isHeadless"] = @([RCTConvert BOOL:@(YES)]);
}
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
[appProperties addEntriesFromDictionary: [self prepareInitialProps]];
UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"XXX", appProperties);
....
}
- (void) application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[AppDelegate.braze.notifications registerDeviceToken:deviceToken];
}
- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
(void)[AppDelegate.braze.notifications handleBackgroundNotificationWithUserInfo:userInfo
fetchCompletionHandler:completionHandler];
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
[[BrazeReactUtils sharedInstance] populateInitialUrlForCategories:response.notification.request.content.userInfo];
(void)[AppDelegate.braze.notifications handleUserNotificationWithResponse:response
withCompletionHandler:completionHandler];
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
if (@available(iOS 14.0, *)) {
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
} else {
completionHandler(UNNotificationPresentationOptionAlert);
}
}
#pragma mark - Braze
static Braze *_braze = nil;
+ (Braze *)braze {
return _braze;
}
+ (void)setBraze:(Braze *)braze {
_braze = braze;
}
// AppDelegate.h
#import <React/RCTBridgeDelegate.h>
#import <UserNotifications/UserNotifications.h>
#import <UIKit/UIKit.h>
#import <BrazeKit/BrazeKit-Swift.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>
@property (nonatomic, strong) UIWindow *window;
+ (Braze *)braze;
@end
for me, i also skipped the delegate step; it's working now with reanimated, firebase and reactnative 0.71.4 not sure if there will be any side effects; but so far everything seems okay.
#import <RCTAppDelegate.h>
#import <React/RCTBridge.h>
#import <UIKit/UIKit.h>
#import <UserNotifications/UNUserNotificationCenter.h>
@interface AppDelegate : RCTAppDelegate <UNUserNotificationCenterDelegate>
@end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.moduleName = appName;
// inject isHeadless Prop
self.initialProps = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];
// bootstrap Firebase using Firebase.json config
[FIRApp configure];
// change background
self.window.rootViewController.view.backgroundColor = [UIColor blackColor];
// Setup Braze
BRZConfiguration *configuration = [[BRZConfiguration alloc] initWithApiKey:apiKey endpoint:endpoint];
configuration.triggerMinimumTimeInterval = 1;
configuration.logger.level = BRZLoggerLevelDebug;
Braze *braze = [BrazeReactBridge initBraze:configuration];
AppDelegate.braze = braze;
[self registerForPushNotifications];
[[BrazeReactUtils sharedInstance] populateInitialUrlFromLaunchOptions:launchOptions];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
#pragma mark - Push Notifications
- (void)registerForPushNotifications {
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) {
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center setNotificationCategories:BRZNotifications.categories];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
(void)[AppDelegate.braze.notifications handleBackgroundNotificationWithUserInfo:userInfo
fetchCompletionHandler:completionHandler];
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler {
[[BrazeReactUtils sharedInstance] populateInitialUrlForCategories:response.notification.request.content.userInfo];
(void)[AppDelegate.braze.notifications handleUserNotificationWithResponse:response
withCompletionHandler:completionHandler];
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
}
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[FIRMessaging messaging].APNSToken = deviceToken;
[AppDelegate.braze.notifications registerDeviceToken:deviceToken];
}
- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken {
NSLog(@"FCM registration Renewed token: %@", fcmToken);
NSDictionary *dataDict = [NSDictionary dictionaryWithObject:fcmToken forKey:@"token"];
[[NSNotificationCenter defaultCenter] postNotificationName:
@"FCMToken" object:nil userInfo:dataDict];
// If necessary send token to application server.
// Note: This callback is fired at each app startup and whenever a new token is generated.
}
@alzalabany are you able to get content cards to show with your setup?
Any progress on this @hokstuff?
@Minishlink implementing your solution I still get Use of undeclared identifier 'BrazeInAppMessageUI'
and I can't seem to find the correct import for that variable
Use the following import #import <BrazeUI/BrazeUI-Swift.h>
(the BrazeInAppMessageUI part is only needed if you want to support in app messages with Braze standard UI)
@alzalabany are you able to get content cards to show with your setup?
Content cards support is very buggy right now, use the following patch https://github.com/braze-inc/braze-react-native-sdk/issues/201#issuecomment-1481545388 You will need to setup a Braze.Events.CONTENT_CARDS_UPDATED listener that sends you the cards as payload in order to have always up-to-date content cards when anything changes on the native side
@Minishlink looks like #import <BrazeUI/BrazeUI-Swift.h>
doesn't come with the rn sdk v3
I'd prefer to just let braze do it's thing. It's working fine on android for me but iOS isn't getting the in app message test I'm running right now
Hi @Minishlink @alzalabany and @CoryWritesCode,
Below is the diff of the proposed changes to resolve the issues on these two issues on the thread:
AppDelegate
.
subscribeToInAppMessage
.We are currently validating these changes and are aiming to release them in the near future. If you have any comments or if you run into issues when validating these changes in your apps, feel free to post on this thread prior to the next release.
diff --git a/public/BrazeProject/ios/BrazeProject/AppDelegate.mm b/public/BrazeProject/ios/BrazeProject/AppDelegate.mm
index 8ee2381..52dcc5b 100644
--- a/public/BrazeProject/ios/BrazeProject/AppDelegate.mm
+++ b/public/BrazeProject/ios/BrazeProject/AppDelegate.mm
@@ -24,12 +24,12 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
self.initialProps = @{};
// Setup Braze bridge
- id<RCTBridgeDelegate> moduleInitializer = [[BrazeReactBridge alloc] init];
- RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitializer
- launchOptions:launchOptions];
- RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
- moduleName:@"BrazeProject"
- initialProperties:nil];
+ NSURL *jsCodeLocation =
+ [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios"];
+ RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
+ moduleName:self.moduleName
+ initialProperties:self.initialProps
+ launchOptions:launchOptions];
self.bridge = rootView.bridge;
// Configure views in the application
diff --git a/public/iOS/BrazeReactBridge/BrazeReactBridge/BrazeReactBridge.h b/public/iOS/BrazeReactBridge/BrazeReactBridge/BrazeReactBridge.h
index 266e83a..32dd660 100644
--- a/public/iOS/BrazeReactBridge/BrazeReactBridge/BrazeReactBridge.h
+++ b/public/iOS/BrazeReactBridge/BrazeReactBridge/BrazeReactBridge.h
@@ -4,7 +4,7 @@
#import <BrazeKit/BrazeKit-Swift.h>
-@interface BrazeReactBridge : RCTEventEmitter <RCTBridgeModule, RCTBridgeDelegate>
+@interface BrazeReactBridge : RCTEventEmitter <RCTBridgeModule>
/// Intializes the Braze instance based on a configuration. This same instance is also used by the Braze bridge.
/// - Parameters:
diff --git a/public/iOS/BrazeReactBridge/BrazeReactBridge/BrazeReactBridge.m b/public/iOS/BrazeReactBridge/BrazeReactBridge/BrazeReactBridge.m
index c8fb5db..07908de 100644
--- a/public/iOS/BrazeReactBridge/BrazeReactBridge/BrazeReactBridge.m
+++ b/public/iOS/BrazeReactBridge/BrazeReactBridge/BrazeReactBridge.m
@@ -74,6 +74,10 @@ - (void)startObserving {
RCTLogInfo(@"Received News Feed cards via subscription of length: %lu", [cards count]);
[self sendEventWithName:kNewsFeedCardsUpdatedEvent body:nil];
}];
+
+ // Add default in-app message UI
+ BrazeInAppMessageUI *inAppMessageUI = [[BrazeInAppMessageUI alloc] init];
+ braze.inAppMessagePresenter = inAppMessageUI;
}
- (void)stopObserving {
@@ -754,12 +758,10 @@ - (void)braze:(Braze * _Nonnull)braze
RCT_EXPORT_METHOD(subscribeToInAppMessage:(BOOL)useBrazeUI)
{
useBrazeUIForInAppMessages = useBrazeUI;
-
- BrazeInAppMessageUI *inAppMessageUI = [[BrazeInAppMessageUI alloc] init];
- inAppMessageUI.delegate = self;
- braze.inAppMessagePresenter = inAppMessageUI;
+ ((BrazeInAppMessageUI *)braze.inAppMessagePresenter).delegate = self;
}
+/// `BrazeInAppMessageUIDelegate` method
- (enum BRZInAppMessageUIDisplayChoice)inAppMessage:(BrazeInAppMessageUI *)ui
displayChoiceForMessage:(BRZInAppMessageRaw *)message {
NSData *inAppMessageData = [message json];
@@ -819,12 +821,6 @@ - (BRZInAppMessageRaw *)getInAppMessageFromString:(NSString *)inAppMessageJSONSt
return nil;
}
-#pragma mark - RCTBridgeDelegate protocol
-
-- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
- return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
-}
-
RCT_EXPORT_MODULE();
@end
Note that our next release will also include fixes / requests from a few other open issues:
Thanks!
@hokstuff
This will occur automatically instead of needing to call subscribeToInAppMessage.
Since we are in context of react-native package, wouldn't it be better to actually have subscribeToInAppMessage
instead of requiring native code to assign BrazeInAppMessageUI
? This is what SDK does right now as far as I see. Moreover, the suggested approach requires integration of delegate once you decide to go with custom UI for in-app messages.
Also to post another problem that we face:
With v3, the SDK comes with a breaking change of storing in-app message images to local files (and providing file://
-like uri's to JS). While it could be an optimization for non-custom IAM integration, it requires an additional compatibility layer for apps using custom UI. Moreover, it's not compatible to Android, where the image comes with https://
path - so, again, requires writing platform-specific code, which is not really developer friendly in context of package for react-native. Can you please add a flag to control this? Or maybe document the case and provide an example of rendering images with file://
path?
Since we are in context of react-native package, wouldn't it be better to actually have
subscribeToInAppMessage
instead of requiring native code to assignBrazeInAppMessageUI
? This is what SDK does right now as far as I see. Moreover, the suggested approach requires integration of delegate once you decide to go with custom UI for in-app messages.
Actually I think it makes more sense for two reasons:
subscribeToInAppMessage
is not needed on AndroidsubscribeToInAppMessage
is a nonstandard function that has multiple responsibilities : 1) an in app message listener (typically it would be an addListener
subscription) 2) enabling Braze UI for in app messages on iOSAn optional ios_enableIAMBrazeUI
could be added though, for when the user is not confident in native code.
Since we are in context of react-native package, wouldn't it be better to actually have subscribeToInAppMessage instead of requiring native code to assign BrazeInAppMessageUI? This is what SDK does right now as far as I see. Moreover, the suggested approach requires integration of delegate once you decide to go with custom UI for in-app messages.
In the proposed change, it removes any need for iOS-native code to add BrazeInAppMessageUI
since that would be executed in startObserving
after the bridge is instantiated, and the BrazeBridge already implements the delegate method. This change would also match the behavior of subscribeToInAppMessages
more closely to Android, where its only jobs are:
((BrazeInAppMessageUI *)braze.inAppMessagePresenter).delegate = self;
in the patch above), while Android does it via adding a listeneruseBrazeUI
to determine whether or not to display via the default Braze UIAlso to post another problem that we face: With v3, the SDK comes with a breaking change of storing in-app message images to local files (and providing file://-like uri's to JS). While it could be an optimization for non-custom IAM integration, it requires an additional compatibility layer for apps using custom UI. Moreover, it's not compatible to Android, where the image comes with https:// path - so, again, requires writing platform-specific code, which is not really developer friendly in context of package for react-native. Can you please add a flag to control this? Or maybe document the case and provide an example of rendering images with file:// path?
@artyorsh Can you contact the Support team with more information on this issue so we can fully understand the current behavior, current workarounds, as well as any relevant context as to how to address the concern? We will need to game plan on whether to solve it in the native layer vs "wrapper" layer.
An optional ios_enableIAMBrazeUI could be added though, for when the user is not confident in native code.
Internally, we strive to make a consistent interface across our several Braze "wrappers" (React Native, Flutter, Unity, etc) and are trying to limit adhoc config options that may lead to bugs on a single platform. The hope of the proposed solution above is to simplify the configuration to lead to fewer integration permutations - but we will note this suggestion internally!
Hi @Minishlink @alzalabany @artyorsh and @CoryWritesCode,
We have released SDK version 4.0.0 which makes updates in the sample app as well as the iOS bridge to address the concern of overwriting the React bridge delegate. We will also update our public docs to reflect this updated recommended integration method on the iOS native layer.
If you continue to run into issues on this same topic, feel free to reopen this thread or else create a new thread if it is regarding a different issue.
Thank you all for your patience!
Hello @hokstuff, thank you for reacting quickly to these issues :) 👏
@hokstuff I believe this might still be an issue in 4.1.0.
Running 4.1.0 of this library, I still had to add the subscribeToInAppMessage
listener for the IAM content to show.
Braze.subscribeToInAppMessage(true, () => {});
In Xcode, I had a breakpoint on Line 84 in BrazeReactBridge BrazeInAppMessageUI *inAppMessageUI = [[BrazeInAppMessageUI alloc] init];
and noticed it wasn't triggered until I had added the listener above.
Once the listener was added, IAM were showing up and the breakpoint was triggered.
Hi @cam-shaw,
It sounds like the issue you are facing is different than the thread here. Can you contact support@braze.com with more information on your integration, including the relevant parts of your AppDelegate (didFinishLaunching and any other Braze-related code)? Also, can you provide more info on your integration, such as if you are using custom vs default UI?
Thanks!
In-App Messages also stopped working for us in 4.1.0
on iOS after migrating from the AppBoy SDK. They're working fine on Android.
Hi @sregg - could you provide some additional details around your implementation? Are you able to receive in-app messages on iOS after following the steps described here by setting the parameter to true
?
We don't have that listener as the docs explains it should work without
But that reminds me that we need to track IAM impressions for analytics purposes so I'll add that and see if that helps. Also, is that listener different than
Braze.addListener(Braze.Events.IN_APP_MESSAGE_RECEIVED, ...)
?
Yup it worked after adding:
Braze.subscribeToInAppMessage(true, () => {});
Also the callbacks in Braze.subscribeToInAppMessage()
and Braze.addListener(Braze.Events.IN_APP_MESSAGE_RECEIVED
are never triggered for some reason.
Even when the in-app message is clearly shown in the app.
Here's our code:
import Braze from '@braze/react-native-sdk';
import { useCallback } from 'react';
import { Events, useReporting } from '~/reporting';
export const useInAppMessages = () => {
const { track } = useReporting();
const trackInAppMessages = useCallback(
() =>
Braze.subscribeToInAppMessage(true, (event: { inAppMessage: string }) => {
const inAppMessage = new Braze.BrazeInAppMessage(event.inAppMessage);
track({
eventName: Events.IN_APP_MESSAGE_RECEIVED,
kind: 'analytics',
payload: {
type: inAppMessage.messageType,
message: inAppMessage.message,
header: inAppMessage.header,
},
});
}),
[track]
);
return { trackInAppMessages };
};
then in our RootNavigator.tsx
const { trackInAppMessages } = useInAppMessages();
useEffect(() => {
const inAppMessagesTrackingSub = trackInAppMessages();
return inAppMessagesTrackingSub?.remove;
}, [trackInAppMessages]);
a console.log()
above const inAppMessage = ...
won't be shown when receiving and showing an in-app message in the app.
Hi @sregg,
Would you be able to contact support@braze.com with this thread and attach the info above? It sounds like a tangential but separate issue than this closed post, and we'd like to keep the conversation on its own thread to cater towards your issue specifically.
From the public docs, the phrase
Native in-app messages display automatically on Android and iOS when using React Native.
refers to being able to present in-app messages using the out-of-the-box UI, with no customization at all. To add a custom UI or custom logic for metrics, you will need to use Braze.subscribeToInAppMessage
as you noted above, alongside any other possible customizations.
Regarding your code snippets here, please contact our Support team with steps to repro and verbose logs so we can continue the investigation.
Thank you!
That's right, we're not using custom UI. In fact, if we just add
Braze.subscribeToInAppMessage(true, () => {});
the in-app messages appear, whereas they don't if we don't add that line. I'll contact support.
Same issue and fix as I had. @hokstuff our team contacted Support about a separate issue and was told to follow the Github thread as it was with the dev team. Can I just confirm with you that contact Support is the best way to resolve this issue? I'd prefer not going in circles 😅
Hi @cam-shaw,
Can you post the Support case number so I could follow up on that thread? Going through Support instead of Github allows us to keep all the relevant info in one place, and that way you can attach files, code snippets, etc easier to associate in one place. For instance, this thread has multiple conversations happening at once for different host apps, so responses can get mixed up.
In addition to your case number, in your response to Support, can you include:
braze.inAppMessagePresenter
in the iOS layer of your application
subscribeToInAppMessage
and using a vanilla integration in our sample app with default UIThanks, Daniel
Which Platforms?
iOS
Which React Native Version?
0.70
Which @braze/react-native-sdk SDK version?
2.0.0 - 3.0.0
Description
Hello,
This is more a regression from v1 rather than a bug. The user here is not the end user but the developer.
Currently, the recommended way of installing Braze in v2+ suggests assigning Braze as delegate of the React Bridge.
This raises problems:
Fortunately:
sourceURLForBridge
I wonder whether this is simply an error when documenting the new Swift SDK.