invertase / react-native-firebase

πŸ”₯ A well-tested feature-rich modular Firebase implementation for React Native. Supports both iOS & Android platforms for all Firebase services.
https://rnfirebase.io
Other
11.7k stars 2.21k forks source link

[πŸ›] IOS AB testing doesn't work correctly with Targeting `app` criteria #7461

Closed cheuchheang closed 11 months ago

cheuchheang commented 11 months ago

Issue

First, thank you in advance for your support! I have 2 apps in my console: Universal app and Specific app. These 2 apps have the same version number 6.0.7 and different build number 898 899. I want to run an AB test for only Universal app but faced the issue when Targeting with app behave incorrectly. Here what I set for targeting:

App: Universal app
Version: 6.0.7

Expect

affect only Universal

Actual

affect both apps


My temporary solution

I need to add the specific build number as another criteria to get the result I expect:

App: Universal app
Version: 6.0.7
build number: 898

Question

What is the root cause for this? Using only app and version as targeting criteria should be fine, right?


Project Files

Javascript

Click To Expand

#### `package.json`: ```json "@react-native-firebase/analytics": "^14.0.1", "@react-native-firebase/app": "^14.0.1", "@react-native-firebase/auth": "^14.0.1", "@react-native-firebase/crashlytics": "^14.0.1", "@react-native-firebase/database": "^14.0.1", "@react-native-firebase/functions": "^14.5.1", "@react-native-firebase/perf": "14.12.0", "@react-native-firebase/remote-config": "^14.11.0", "@react-native-firebase/storage": "^14.7.1", ``` #### `firebase.json` for react-native-firebase v6: ```json # N/A ```

iOS

Click To Expand

#### `ios/Podfile`: - [ ] I'm not using Pods - [x] I'm using Pods and my Podfile looks like: ```ruby require_relative "../node_modules/react-native/scripts/react_native_pods" require_relative "../node_modules/@react-native-community/cli-platform-ios/native_modules" source 'https://cdn.cocoapods.org/' platform :ios, '12.4' install! 'cocoapods', :deterministic_uuids => false production = ENV["PRODUCTION"] == "1" # use_frameworks! target "Ling Universal" do config = use_native_modules! # Flags change depending on the env values. flags = get_default_flags() use_react_native!( :path => config[:reactNativePath], # to enable hermes on iOS, change `false` to `true` and then install pods :production => production, :hermes_enabled => false, :fabric_enabled => flags[:fabric_enabled], :flipper_configuration => FlipperConfiguration.disabled, # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/.." ) $RNFirebaseAsStaticFramework = true permissions_path = "../node_modules/react-native-permissions/ios" pod "OneSignalXCFramework", ">= 3.0", "< 4.0" pod "Permission-Microphone", :path => "#{permissions_path}/Microphone" pod "Permission-SpeechRecognition", :path => "#{permissions_path}/SpeechRecognition" pod "RNVectorIcons", :path => "../node_modules/react-native-vector-icons" pod "RNSound", :path => "../node_modules/react-native-sound" pod "react-native-in-app-review", :path => "../node_modules/react-native-in-app-review" pod "react-native-mail", :path => "../node_modules/react-native-mail" pod "react-native-config", :path => "../node_modules/react-native-config" end target "Ling Thai" do config = use_native_modules! # Flags change depending on the env values. flags = get_default_flags() use_react_native!( :path => config[:reactNativePath], # to enable hermes on iOS, change `false` to `true` and then install pods :production => production, :hermes_enabled => false, :fabric_enabled => flags[:fabric_enabled], :flipper_configuration => FlipperConfiguration.disabled, # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/.." ) $RNFirebaseAsStaticFramework = true permissions_path = "../node_modules/react-native-permissions/ios" pod "OneSignalXCFramework", ">= 3.0", "< 4.0" pod "Permission-Microphone", :path => "#{permissions_path}/Microphone" pod "Permission-SpeechRecognition", :path => "#{permissions_path}/SpeechRecognition" pod "RNVectorIcons", :path => "../node_modules/react-native-vector-icons" pod "RNSound", :path => "../node_modules/react-native-sound" pod "react-native-in-app-review", :path => "../node_modules/react-native-in-app-review" pod "react-native-mail", :path => "../node_modules/react-native-mail" pod "react-native-config", :path => "../node_modules/react-native-config" end target "OneSignalNotificationServiceExtension" do pod "OneSignalXCFramework", ">= 3.0", "< 4.0" end target "OneSignalNotificationServiceExtension2" do pod "OneSignalXCFramework", ">= 3.0", "< 4.0" end post_install do |installer| react_native_post_install(installer) __apply_Xcode_12_5_M1_post_install_workaround(installer) `sed -i -e $'s/__IPHONE_10_0/__IPHONE_14_0/' #{installer.sandbox.root}/RCT-Folly/folly/portability/Time.h` installer.pods_project.targets.each do |target| target.build_configurations.each do |config| if config.build_settings["WRAPPER_EXTENSION"] == "bundle" config.build_settings["DEVELOPMENT_TEAM"] = "ZBXRY8DKL4" end end end end ``` #### `AppDelegate.m`: ```objc #import "AppDelegate.h" #import #import #import #import #import #import #import #import #import #import #import #import #import "ling-Swift.h" #import "RNSplashScreen.h" #if RCT_NEW_ARCH_ENABLED #import #import #import #import #import #import #import static NSString *const kRNConcurrentRoot = @"concurrentRoot"; @interface AppDelegate () { RCTTurboModuleManager *_turboModuleManager; RCTSurfacePresenterBridgeAdapter *_bridgeAdapter; std::shared_ptr _reactNativeConfig; facebook::react::ContextContainer::Shared _contextContainer; } @end #endif @implementation AppDelegate - (BOOL)application:(UIApplication *)application openURL:(nonnull NSURL *)url options:(nonnull NSDictionary *)options { return [[FBSDKApplicationDelegate sharedInstance] application:application openURL:url options:options] || [RNGoogleSignin application:application openURL:url options:options]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [Bugsnag start]; RCTAppSetupPrepareApp(application); RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; #if RCT_NEW_ARCH_ENABLED _contextContainer = std::make_shared(); _reactNativeConfig = std::make_shared(); _contextContainer->insert("ReactNativeConfig", _reactNativeConfig); _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer]; bridge.surfacePresenter = _bridgeAdapter.surfacePresenter; #endif NSDictionary *initProps = [self prepareInitialProps]; UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"ling", 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]; [FIRApp configure]; [FBSDKApplicationDelegate.sharedInstance initializeSDK]; 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 [CodePush bundleURL]; #endif } - (void)applicationWillEnterForeground:(UIApplication *)application { /* Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. */ if (@available(iOS 14.0, *)) { [WidgetKitHelper reloadAllWidgets]; } else { // Fallback on earlier versions } } - (void)applicationDidEnterBackground:(UIApplication *)application { if (@available(iOS 14.0, *)) { [WidgetKitHelper reloadAllWidgets]; } else { // Fallback on earlier versions } } // Push Notification //Called when a notification is delivered to a foreground app. -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge); } // Required for the register event. - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } // Required for the notification event. You must call the completion handler after handling the remote notification. - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; } // Required for the registrationError event. - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error]; } // Required for localNotification event - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { [RNCPushNotificationIOS didReceiveNotificationResponse:response]; } #if RCT_NEW_ARCH_ENABLED #pragma mark - RCTCxxBridgeDelegate - (std::unique_ptr)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)getTurboModule:(const std::string &)name jsInvoker:(std::shared_ptr)jsInvoker { return nullptr; } - (std::shared_ptr)getTurboModule:(const std::string &)name initParams: (const facebook::react::ObjCTurboModule::InitParams &)params { return nullptr; } - (id)getModuleInstanceFromClass:(Class)moduleClass { return RCTAppSetupDefaultModuleFromClass(moduleClass); } #endif @end ```


Android

Click To Expand

#### Have you converted to AndroidX? - [ ] my application is an AndroidX application? - [ ] I am using `android/gradle.settings` `jetifier=true` for Android compatibility? - [ ] I am using the NPM package `jetifier` for react-native compatibility? #### `android/build.gradle`: ```groovy // N/A ``` #### `android/app/build.gradle`: ```groovy // N/A ``` #### `android/settings.gradle`: ```groovy // N/A ``` #### `MainApplication.java`: ```java // N/A ``` #### `AndroidManifest.xml`: ```xml ```


Environment

Click To Expand

**`react-native info` output:** ``` OUTPUT GOES HERE ``` - **Platform that you're experiencing the issue on**: - [ ] iOS - [ ] Android - [ ] **iOS** but have not tested behavior on Android - [ ] **Android** but have not tested behavior on iOS - [ ] Both - **`react-native-firebase` version you're using that has this issue:** - `e.g. 5.4.3` - **`Firebase` module(s) you're using that has the issue:** - `e.g. Instance ID` - **Are you using `TypeScript`?** - `Y/N` & `VERSION`


mikehardy commented 11 months ago

I'm not sure exactly, but I can say that with versions this old we cannot offer support:

 "@react-native-firebase/analytics": "^14.0.1",
    "@react-native-firebase/app": "^14.0.1",
    "@react-native-firebase/auth": "^14.0.1",
    "@react-native-firebase/crashlytics": "^14.0.1",
    "@react-native-firebase/database": "^14.0.1",
    "@react-native-firebase/functions": "^14.5.1",
    "@react-native-firebase/perf": "14.12.0",
    "@react-native-firebase/remote-config": "^14.11.0",
    "@react-native-firebase/storage": "^14.7.1",

(additionally, you have version skew within @react-native-firebase packages, which is not something you should do - they should always all be the same version - easy to think about - https://invertase.io/blog/react-native-firebase-versioning)

I cannot say that upgrading will fix your issue, but I can say that it will fix lots of other issues and may fix this one so we cannot really proceed until we're sure that everything is on current versions including all fixes to date

cheuchheang commented 11 months ago

@mikehardy Thank you for your quick support! I have another doubt. Currently, I see only GoogleService-Info.plist in my project which uses BUNDLE_ID of Universal app. Should it be the root cause that the remote config works improperly? If yes, how do I fix it? (please share me the doc!)

image
mikehardy commented 11 months ago

If they don't have separate projects in firebase it may be difficult to separate them but I'm unsure, have not seen this use case before

Not aware of an actual doc to send, I encourage you to pursue firebase support on stackoverlow with appropriate firebase tags

cheuchheang commented 11 months ago

@mikehardy We have 2 projects separately like this. Does it mean I need to add 2 GoogleService-Info.plist as well? image

mikehardy commented 11 months ago

Possibly? With apologies this is a pretty specific area that is different for each team developing solutions. I personally have 4 projects (to separate the backends off my different promotion environments - dev / qa / staging / prod) but most people just have one 🀷 . It appears you have two - for whatever reasons, the specifics are not important - but they are important to you so you have two

I'm guessing you will need use different plist files yes, and this page may be interesting https://firebase.google.com/docs/projects/multiprojects

That's how I do it, with the plist files included or not based on target membership of the files, and I build different "Xcode targets" to build the different apps connected to different firebase projects / data backends

cheuchheang commented 11 months ago

@mikehardy That's really helpful and appreciate for your quick support. Yes, you're right. Now, the issue is fixed after I added another GoogleService-Info.plist for my specific app. I would like to close this discussion since I have no more issue. Thank you so much! πŸ™ŒπŸŽ‰

mikehardy commented 11 months ago

Glad that helped! Good luck with your project