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.66k stars 2.21k forks source link

[🐛] Bug Report Title - Receiving multiple otp sms in iOS device for phone authentication #7522

Closed akhilsanker closed 6 months ago

akhilsanker commented 9 months ago

Issue

We are using @react-native-firebase/auth v11.5.0 in our react native (v0.66.5) app for phone authentication. But frequently in iOS devices, users are getting multiple otp sms at the same time.

B8505052-1A87-4DD2-9157-BCA5E8DB3B89


Project Files

Javascript

Click To Expand

#### `package.json`: ```json { "name": "", "version": "0.0.1", "private": true, "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", "start": "react-native start", "test": "jest", "lint": "eslint ." }, "dependencies": { "@appbaseio/reactivesearch-native": "0.12.0", "@codler/react-native-keyboard-aware-scroll-view": "2.0.0", "@ptomasroos/react-native-multi-slider": "2.2.2", "@react-native-camera-roll/camera-roll": "5.4.0", "@react-native-clipboard/clipboard": "1.9.0", "@react-native-community/art": "1.2.0", "@react-native-community/async-storage": "1.9.0", "@react-native-community/datetimepicker": "2.6.2", "@react-native-community/geolocation": "2.0.2", "@react-native-community/masked-view": "0.1.11", "@react-native-community/netinfo": "5.9.0", "@react-native-community/viewpager": "4.2.2", "@react-native-firebase/app": "11.5.0", "@react-native-firebase/auth": "11.5.0", "@react-native-firebase/firestore": "11.5.0", "@react-native-google-signin/google-signin": "9.0.2", "@react-native-picker/picker": "1.16.8", "@react-native-twitter-signin/twitter-signin": "1.2.0", "@react-navigation/bottom-tabs": "5.11.15", "@react-navigation/core": "5.16.1", "@react-navigation/drawer": "5.12.9", "@react-navigation/material-bottom-tabs": "5.3.19", "@react-navigation/material-top-tabs": "5.3.19", "@react-navigation/native": "5.9.8", "@react-navigation/stack": "5.14.9", "@segment/analytics-react-native": "1.5.3", "@segment/analytics-react-native-firebase": "1.5.0", "@segment/analytics-react-native-plugin-facebook-app-events": "0.2.2", "@types/i18n-js": "3.8.2", "@types/jest": "25.1.4", "@types/lodash": "4.14.176", "@types/react": "16.14.20", "@types/react-native": "0.66.5", "@types/react-native-datepicker": "1.7.1", "@types/react-native-snap-carousel": "3.8.5", "@types/react-native-text-input-mask": "0.7.6", "@types/react-redux": "7.1.20", "@types/react-test-renderer": "16.9.2", "@viro-community/react-viro": "^2.23.0", "appcenter": "4.4.3", "appcenter-analytics": "4.4.3", "appcenter-crashes": "4.4.3", "axios": "0.27.2", "axios-hooks": "3.1.0", "bodybuilder": "2.2.21", "credit-card-type": "9.1.0", "cross-fetch": "3.0.6", "i18n-js": "3.5.1", "isomorphic-fetch": "3.0.0", "lodash": "4.17.15", "moment": "2.26.0", "prop-types": "15.7.2", "react": "17.0.2", "react-countup": "4.4.0", "react-hook-form": "6.15.8", "react-native": "0.66.5", "react-native-animated-ellipsis": "2.0.0", "react-native-appsflyer": "6.10.2", "react-native-arkit": "^0.8.0", "react-native-autocomplete-dropdown": "2.0.7", "react-native-autocomplete-input": "4.1.0", "react-native-calendars": "1.1255.0", "react-native-code-push": "7.0.4", "react-native-countdown-circle-timer": "2.5.4", "react-native-date-picker": "4.1.1", "react-native-datepicker": "1.7.2", "react-native-device-info": "8.4.5", "react-native-dropdown-picker": "5.2.3", "react-native-elements": "2.3.2", "react-native-fast-image": "8.5.11", "react-native-fbsdk-next": "8.0.5", "react-native-geolocation-service": "5.0.0", "react-native-gesture-handler": "1.6.0", "react-native-gifted-chat": "2.1.0", "react-native-google-places-autocomplete": "2.4.1", "react-native-image-crop-picker": "0.39.0", "react-native-image-progress": "1.2.0", "react-native-image-resizer": "1.4.5", "react-native-input-scroll-view": "1.11.0", "react-native-klarna-inapp-sdk": "2.1.9", "react-native-linear-gradient": "2.5.6", "react-native-localize": "1.4.3", "react-native-maps": "0.27.1", "react-native-modal": "12.1.0", "react-native-normalize": "1.0.1", "react-native-onesignal": "4.5.0", "react-native-paper": "4.12.2", "react-native-permissions": "3.6.1", "react-native-pie-chart": "2.1.1", "react-native-pinchable": "0.2.1", "react-native-popup-menu": "0.15.11", "react-native-progress": "5.0.0", "react-native-reanimated": "1.7.0", "react-native-render-html": "5.1.1", "react-native-responsive-dimensions": "3.1.1", "react-native-restart": "0.0.17", "react-native-root-toast": "3.3.0", "react-native-safe-area-context": "0.7.3", "react-native-screens": "2.2.0", "react-native-share": "7.3.7", "react-native-skeleton-placeholder": "2.0.7", "react-native-snap-carousel": "4.0.0-beta.6", "react-native-stripe-checkout-webview": "0.0.12", "react-native-svg": "12.1.1", "react-native-svg-transformer": "1.0.0", "react-native-swipe-list-view": "3.2.9", "react-native-swiper": "1.6.0-rc.3", "react-native-tab-view": "2.14.4", "react-native-text-input-mask": "3.1.4", "react-native-tracking-transparency": "0.1.1", "react-native-typescript-transformer": "1.2.13", "react-native-ux-cam": "5.4.0", "react-native-vector-icons": "8.1.0", "react-native-webview": "11.22.7", "react-redux": "7.2.6", "recyclerlistview": "3.1.0-beta.6", "redux": "4.1.2", "redux-logger": "3.0.6", "redux-thunk": "2.4.0", "rn-fetch-blob": "0.12.0", "rn-swipe-button": "1.3.6", "typescript": "3.9.10" }, "devDependencies": { "@babel/core": "7.12.9", "@babel/runtime": "7.12.5", "@react-native-community/eslint-config": "2.0.0", "@types/metro-config": "0.66.0", "@types/react-native-calendars": "1.1264.3", "@types/react-native-dotenv": "0.2.0", "@types/react-native-restart": "0.0.0", "@typescript-eslint/eslint-plugin": "2.24.0", "babel-jest": "26.6.3", "eslint": "7.32.0", "eslint-config-airbnb-typescript": "7.2.0", "eslint-config-prettier": "6.10.0", "eslint-detailed-reporter": "0.8.0", "eslint-plugin-import": "2.25.2", "eslint-plugin-jsx-a11y": "6.2.3", "eslint-plugin-prettier": "3.4.1", "eslint-plugin-promise": "4.2.1", "eslint-plugin-react": "7.26.1", "eslint-plugin-react-hooks": "2.5.0", "jest": "26.6.3", "metro-react-native-babel-preset": "0.66.2", "prettier": "1.19.1", "react-native-dotenv": "0.2.0", "react-test-renderer": "17.0.2" }, "jest": { "preset": "react-native" } } ``` #### `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' platform :ios, '12.1' target '' do config = use_native_modules! use_react_native!( :path => config[:reactNativePath], # to enable hermes on iOS, change `false` to `true` and then install pods :hermes_enabled => false ) permissions_path = '../node_modules/react-native-permissions/ios' pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text', :modular_headers => true pod 'ReactNativeART', :path => '../node_modules/@react-native-community/art' pod 'Permission-PhotoLibrary', :path => "#{permissions_path}/PhotoLibrary" pod 'Permission-PhotoLibraryAddOnly', :path => "#{permissions_path}/PhotoLibraryAddOnly" pod 'boost', :podspec => '../node_modules/react-native/third-party-podspecs/boost.podspec' # target 'inckdTests' do # inherit! :complete # # Pods for testing # end # Enables Flipper. # # Note that if you have use_frameworks! enabled, Flipper will not work and # you should disable the next line. use_flipper!({ "Flipper-DoubleConversion" => "1.1.7" }) # post_install do |installer| # react_native_post_install(installer) # end post_install do |installer| __apply_Xcode_12_5_M1_post_install_workaround(installer) installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO' end end installer.pods_project.targets.each do |target| target.build_configurations.each do |config| # Force CocoaPods targets to always build for x86_64 config.build_settings['ARCHS[sdk=iphonesimulator*]'] = 'x86_64' end end end end target 'OneSignalNotificationServiceExtension' do pod 'OneSignalXCFramework', '>= 3.0', '< 4.0' end # post_install do |pi| # pi.pods_project.targets.each do |t| # t.build_configurations.each do |config| # config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.1' # end # end # end # post_install do |installer| # installer.pods_project.targets.each do |target| # target.build_configurations.each do |config| # config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO' # end # end # end ``` #### `AppDelegate.m`: ```objc #import "AppDelegate.h" #import #import #import #import #import #import #import #import #import #ifdef FB_SONARKIT_ENABLED #import #import #import #import #import #import static void InitializeFlipper(UIApplication *application) { FlipperClient *client = [FlipperClient sharedClient]; SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]]; [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]]; [client addPlugin:[FlipperKitReactPlugin new]]; [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]]; [client start]; } #endif @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { if (@available(iOS 14, *)) { UIDatePicker *picker = [UIDatePicker appearance]; picker.preferredDatePickerStyle = UIDatePickerStyleWheels; } #ifdef FB_SONARKIT_ENABLED InitializeFlipper(application); #endif if ([FIRApp defaultApp] == nil) { [FIRApp configure]; } RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"" initialProperties:nil]; 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]; [[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions]; return YES; } - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { #if DEBUG return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; #else return [CodePush bundleURL]; #endif } - (void)applicationWillResignActive:(UIApplication *)application { if (self.taskIdentifier != UIBackgroundTaskInvalid) { [application endBackgroundTask:self.taskIdentifier]; self.taskIdentifier = UIBackgroundTaskInvalid; } __weak typeof(self) weakSelf = self; self.taskIdentifier = [application beginBackgroundTaskWithName:nil expirationHandler:^{ [application endBackgroundTask:weakSelf.taskIdentifier]; weakSelf.taskIdentifier = UIBackgroundTaskInvalid; }]; } - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { return [RCTLinkingManager application:application openURL:url options:options]; } - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; } @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 // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext { buildToolsVersion = "30.0.2" minSdkVersion = 21 compileSdkVersion = 33 targetSdkVersion = 33 ndkVersion = "21.4.7075529" googlePlayServicesVersion = "17.0.0"//added for react-native-geolocation-service modules androidMapsUtilsVersion="2.2.0" androidXCore = "1.6.0" kotlinVersion = "1.6.0" googlePlayServicesAuthVersion = "19.2.0" // <--- use version 19.2.0 or newer for social media login } repositories { google() mavenCentral() } dependencies { classpath("com.android.tools.build:gradle:4.2.2") // <--- use version 4.2.2 or newer for social media login classpath 'com.google.gms:google-services:4.3.10' // <--- use version 4.3.10 or newer for social media login classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { mavenCentral() mavenLocal() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url("$rootDir/../node_modules/react-native/android") } maven { // Android JSC is installed from npm url("$rootDir/../node_modules/jsc-android/dist") } google() jcenter() maven { url 'https://www.jitpack.io' } maven { url 'https://maven.google.com' } maven { url 'https://x.klarnacdn.net/mobile-sdk/' } } configurations.all { resolutionStrategy { dependencySubstitution { substitute module("com.redmadrobot:input-mask-android:6.0.0") using module('com.github.RedMadRobot:input-mask-android:6.0.0') } } } } ``` #### `android/settings.gradle`: ```groovy rootProject.name = '' include ':react-native-vector-icons' project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android') include ':react-native-linear-gradient' project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android') include ':react-native-localize' project(':react-native-localize').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-localize/android') apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app', ':react-native-code-push' project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app') include ':app' ``` #### `MainApplication.java`: ```java package xxx.xxxx.xxxx; import android.app.Application; import android.content.Context; import com.facebook.react.PackageList; import com.facebook.react.ReactApplication; import com.facebook.react.ReactInstanceManager; import com.oblador.vectoricons.VectorIconsPackage; import com.reactcommunity.rnlocalize.RNLocalizePackage; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.soloader.SoLoader; import com.BV.LinearGradient.LinearGradientPackage; import com.reactnative.ivpusic.imagepicker.PickerPackage; import java.lang.reflect.InvocationTargetException; import java.util.List; import com.microsoft.codepush.react.CodePush; public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List packages = new PackageList(this).getPackages(); // Packages that cannot be autolinked yet can be added manually here, for example: // packages.add(new MyReactNativePackage()); packages.add(new LinearGradientPackage()); return packages; } @Override protected String getJSMainModuleName() { return "index"; } @Override protected String getJSBundleFile() { return CodePush.getJSBundleFile(); } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } @Override public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); } /** * Loads Flipper in React Native templates. Call this in the onCreate method with something like * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); * * @param context */ private static void initializeFlipper( Context context, ReactInstanceManager reactInstanceManager) { if (BuildConfig.DEBUG) { try { /* We use reflection here to pick up the class that initializes Flipper, since Flipper library is not available in release mode */ Class aClass = Class.forName("com.xxx.ReactNativeFlipper"); aClass .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) .invoke(null, context, reactInstanceManager); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } } ``` #### `AndroidManifest.xml`: ```xml ```


Environment

Click To Expand

**`react-native info` output:** ``` System: OS: macOS 13.0.1 CPU: (16) x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz Memory: 74.70 MB / 32.00 GB Shell: 5.8.1 - /bin/zsh Binaries: Node: 18.3.0 - /usr/local/bin/node Yarn: Not Found npm: 8.11.0 - /usr/local/bin/npm Watchman: Not Found Managers: CocoaPods: 1.11.3 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: DriverKit 22.2, iOS 16.2, macOS 13.1, tvOS 16.1, watchOS 9.1 Android SDK: Not Found IDEs: Android Studio: 2021.2 AI-212.5712.43.2112.8609683 Xcode: 14.2/14C18 - /usr/bin/xcodebuild Languages: Java: 11.0.12 - /usr/bin/javac npmPackages: @react-native-community/cli: Not Found react: 17.0.2 => 17.0.2 react-native: 0.66.5 => 0.66.5 react-native-macos: Not Found npmGlobalPackages: *react-native*: Not Found ``` - **Platform that you're experiencing the issue on**: - [ ] iOS - [ ] Android - [x] **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:** - `@react-native-firebase/auth": "11.5.0"` - **Are you using `TypeScript`?** - `Y` & `"typescript": "3.9.10"`


mikehardy commented 9 months ago

We are using @react-native-firebase/auth v11.5.0 in our react native (v0.66.5) app for phone authentication.

This is software from years ago, when logging issues it needs to be on current versions to make sure we're not chasing ghosts with limited time

Please let me know if you reproduce on current version of react-native-firebase

Also note that if you do reproduce, we'll need a reproducible example that is minimal, just a simple index.js / App.js that demonstrates the problem https://stackoverflow.com/help/minimal-reproducible-example as it is most likely a project-specific error with (for example) multiple instances of hooks or code running when you thought they would run only once

akhilsanker commented 9 months ago

Hi @mikehardy , Thanks for the reply.

We are currently running our project in the above mentioned versions. There are lesser chance to upgrade the react native version. However, we will try upgrading the firebase auth version to latest version and will give an update about the issue

Thanks.

mikehardy commented 9 months ago

I'm not sure react-native 0.66.5 will even compile on Xcode 15 FWIW. I believe you need at least react-native 0.68 for that. Believe it or not, upgrading from 0.66 to 0.73 is not that hard anymore, as the new architecture stuff that came in (requiring lots of native changes as you upgraded) were also refined quite a bit and mostly removed (also requiring a lot of native changes...) leaving the upgrade process to no longer require that many changes :-). Perhaps waiting so long has benefited you.

https://react-native-community.github.io/upgrade-helper/?from=0.66.5&to=0.73.1

Either way, you are asking for support, and you are doing so on outdated versions, and it is important to realize that there is a high risk that you are possibly mal-investing your time by doing so as it is always possible for bugs to be fixed already, meaning all time spent investigating problems on the old version were wasted - while the alternative - upgrading - even react-native - is something you know you will have to do eventually, so at least to me it always seems best to go ahead and get everything up to date, then try to reproduce, then ask for support

akhilsanker commented 8 months ago

hi @mikehardy ,

We upgraded react native and @react-native-firebase/auth, but getting the same in iOS. Receiving multiple otp at same time.

"@react-native-firebase/app": "18.7.3",
"@react-native-firebase/auth": "18.7.3",
"@react-native-firebase/firestore": "18.7.3",
"react-native": "0.71.14",

Updated to stable version of react native.

Code snippet

Phone input screen

import React, { useEffect, useRef, useState } from 'react';
import {
  Animated,
  Keyboard,
  ScrollView,
  StyleSheet,
  TouchableOpacity,
  View
} from 'react-native';
import { AccessToken, LoginManager } from 'react-native-fbsdk-next';
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import { useDispatch, useSelector } from 'react-redux';
import auth, { FirebaseAuthTypes } from '@react-native-firebase/auth';
import OneSignal from 'react-native-onesignal';

import {
  useAxiosGet,
  useAxiosPost,
  useAxiosPut,
  useBackHandler,
  useBasicAnimation,
  useKeyboardHideShow
} from '@hook';

import { ASYNC_STORE, storeData } from '@storage';
import {
  Button,
  FontIcon,
  HeaderBarTitled,
  Modal,
  OverlayModalViewRevamp,
  ProgressView,
  RowBar,
  Text
} from '@components';
import { ChooseCountryModalView } from '@components/commonComponents';
import { I18n, isAndroid, isIPhoneX } from '@lib';
import { logOut, setPhone, setProfile } from '@store/modules/auth/actions';
import { ModalPropsType } from '@lib/types';
import { setSearchFilter } from '@store/modules/search/actions';
import { setSystemLanguage, setUserProcessAndRole, showNotification } from '@store/modules/system/actions';
import { theme } from '@styles';
import ErrorDisplayComponent from '@modules/registration/components/ErrorDisplayComponent';
import InputBox from '@modules/registration/components/InputBox';
import StepperCountText from '@modules/registration/components/StepperCountText';
import { SystemState } from '@store/modules/system/types';

import {
  LoginEmailBold,
  LoginFBIcon,
  LoginGoogleIcon
} from '@assets';
import { countrySelectionData, getAPI, icons, responsiveScale } from '@constants';
import { SignInKeys } from '@constants/constants';
import {
  AsyncBoolean,
  IdentifyTypes,
  NotificationType,
  SocialMediaLoginVariants,
  UserProcess,
  UserRole
} from '@constants/enums';

interface PhoneValidationScreenProps {
  navigation: Record<string, Function>;
}

const styles: Record<string, object> = StyleSheet.create({
  phoneCodeText: {
    letterSpacing: 0.3,
    lineHeight: responsiveScale(20)
  },
  phoneCodeWrapper: {
    alignItems: 'center',
    backgroundColor: theme.colors.background_light_grey,
    borderRadius: responsiveScale(4),
    height: responsiveScale(50),
    justifyContent: 'center',
    marginRight: responsiveScale(16),
    paddingHorizontal: responsiveScale(15)
  },
  scrollContainer: {
    backgroundColor: theme.colors.light,
    flexGrow: 1,
    justifyContent: 'space-between',
    paddingHorizontal: responsiveScale(16),
    paddingTop: responsiveScale(20)
  },
  textWrapper: {
    borderRadius: responsiveScale(4),
    borderWidth: responsiveScale(1),
    width: responsiveScale(248)
  },
  phoneNumberText: {
    fontFamily: `${theme.fontFamily.figtree}-Regular`,
    fontSize: responsiveScale(16),
    letterSpacing: 0.3
  },
  progressRail: {
    backgroundColor: theme.colors.background_light_grey,
    height: responsiveScale(4)
  },
  acceptButtonWrapper: {
    borderRadius: responsiveScale(4),
    borderWidth: 0,
    height: responsiveScale(48),
    marginVertical: responsiveScale(8),
    width: '100%'
  },
  viewDivider: {
    backgroundColor: theme.colors.border_primary,
    flex: 1,
    height: responsiveScale(1)
  },
  otherSignUpWrapper: {
    alignItems: 'center',
    borderColor: theme.colors.button_secondary_100,
    borderRadius: responsiveScale(4),
    borderWidth: responsiveScale(1),
    flexDirection: 'row',
    height: responsiveScale(40),
    justifyContent: 'center',
    marginBottom: responsiveScale(10),
    width: '100%'
  }
});

const PhoneValidationScreen: React.FC<PhoneValidationScreenProps> = ({ navigation }) => {
  const [country, setCountry] = useState<Record<string, any>>(countrySelectionData.DE);
  const [errorText, setErrorText] = useState('');
  const [firebaseToken, setFirebaseToken] = useState('');
  const [isKeyboardActive, setIsKeyboardActive] = useState(false);
  const [isSocialMediaLogin, setIsSocialMediaLogin] = useState(false);
  const [phoneNumber, setPhoneNumber] = useState<string>('');
  const [phoneNumberFormatted, setPhoneNumberFormatted] = useState<string>('');
  const [socialMediaAccessTypeSelected, setSocialMediaAccessTypeSelected] = useState<string>('');
  const { systemLanguage } = useSelector((state: Record<string, SystemState>) => state.system);

  // const { RNTwitterSignIn } = NativeModules;
  GoogleSignin.configure({ webClientId: SignInKeys.google.WEB_CLIENT_ID });
  // RNTwitterSignIn.init(
  //   SignInKeys.twitter.TWITTER_CONSUMER_KEY, SignInKeys.twitter.TWITTER_CONSUMER_SECRET
  // );

  const [, isKeyboardVisible] = useKeyboardHideShow();
  const countrySelectionModalRef = useRef<ModalPropsType>();
  const dispatch = useDispatch();
  const footerAnimation = useBasicAnimation({
    doAnimation: isKeyboardVisible && isAndroid(),
    useNativeDriver: true
  });
  const profileNotFoundModalRef = useRef<ModalPropsType>();

  const firebaseAuthStatus = async (): Promise<void> => {
    if (auth().currentUser) {
      await auth().signOut();
    }
  };

  const handleSuccess = (): void => {
    dispatch(setPhone(country.countryCode.toString(), phoneNumber));
    firebaseAuthStatus();
    navigation.navigate('PrimaryAuth', {
      screen: 'PhoneOtpVerificationScreen',
      params: {
        formattedPhnNumber: phoneNumberFormatted
      }
    });
  };

  const [{ loading: validatingNumber }, triggerValidatePhone] = useAxiosPost(
    getAPI.PHONE_VALIDATION,
    {
      onSuccess: () => handleSuccess(),
      onError: () => setErrorText(I18n.t('screen.login.phn_number.err'))
    }
  );

  const isDisabled = !(phoneNumber.length >= country.phoneNumberMinLength)
    || errorText.length > 0
    || validatingNumber;

  const clearSavedDetails = async (): Promise<void> => {
    LoginManager.logOut();
    dispatch(logOut());
    await GoogleSignin.revokeAccess();
    await GoogleSignin.signOut();
  };

  useEffect(() => {
    clearSavedDetails();
  }, []);

  const keyboardDidShow = (): void => {
    setIsKeyboardActive(true);
  };

  const keyboardDidHide = (): void => {
    setIsKeyboardActive(false);
  };

  useEffect(() => {
    Keyboard.addListener('keyboardDidShow', keyboardDidShow);
    Keyboard.addListener('keyboardDidHide', keyboardDidHide);

    // cleanup function
    return () => {
      Keyboard.addListener('keyboardDidShow', keyboardDidShow).remove();
      Keyboard.addListener('keyboardDidHide', keyboardDidHide).remove();
    };
  }, [isKeyboardActive]);

  const goBack = (): void => {
    if (navigation.canGoBack()) {
      navigation.goBack();
    }
  };

  useBackHandler(goBack);

  useEffect(() => {
    setPhoneNumber('');
    setPhoneNumberFormatted('');
    dispatch(setSearchFilter(null));
  }, [country]);

  const onPressStudioOwnerLogin = (): void => {
    setIsSocialMediaLogin(false);
    navigation.navigate('StudioOwnerEmailValidationScreen');
  };

  const onPressNext = (): void => {
    Keyboard.dismiss();
    setIsSocialMediaLogin(false);
    triggerValidatePhone({
      data: {
        phone_code: country.countryCode.toString(),
        phone_number: phoneNumber,
        process: UserProcess.LOGIN
      }
    });
  };

  useEffect(() => {
    if (phoneNumber.length >= country.phoneNumberLength) Keyboard.dismiss();
  }, [phoneNumber]);

  const onPressEmailLogin = (): void => {
    setIsSocialMediaLogin(false);
    navigation.navigate('EmailValidationScreen');
  };

  const handleAppLanguageSuccess = (res: Record<string, Record<string, string>>): void => {
    const { data } = res;
    I18n.locale = data.app_language;
    dispatch(setSystemLanguage(data.app_language));
    storeData(ASYNC_STORE.CURRENT_LANGUAGE, data.app_language);
  };

  const [, setAppLanguage] = useAxiosPut(
    getAPI.GET_APP_LANGUAGE,
    {
      onError: () => dispatch(showNotification(I18n.t('general.something_went_wrong'), NotificationType.WARNING)),
      onSuccess: (res: Record<string, Record<string, string>>) => handleAppLanguageSuccess(res)
    }
  );

  const setLanguage = async (): Promise<void> => {
    setAppLanguage({
      data: {
        app_language: systemLanguage
      }
    });
  };

  const handleLoginResponse = async (res: any): Promise<void> => {
    setLanguage();
    await storeData(
      ASYNC_STORE.LOGIN_DETAILS.ACCESS_TOKEN,
      res.data.login_details.access_token
    );
    await storeData(
      ASYNC_STORE.LOGIN_DETAILS.REFRESH_TOKEN,
      res.data.login_details.refresh_token
    );

    const { role } = res.data?.basic_details;

    dispatch(setUserProcessAndRole(UserProcess.LOGIN, role));
    dispatch(setProfile(res, IdentifyTypes.LOGIN));
    if (role === UserRole.CUSTOMER) {
      OneSignal.setEmail(res?.data?.basic_details?.email);
      OneSignal.sendTags({
        is_customer: AsyncBoolean.TRUE,
        first_name: res.data.basic_details.first_name
      });
    }
  };

  const [
    { loading: validatingSocialLogin },
    triggerSignInWithSocialMedia
  ] = useAxiosGet(`${getAPI.SOCIAL_MEDIA_ACCESS}?social_media=${socialMediaAccessTypeSelected}`, {
    headers: { Authorization: `FirebaseToken ${firebaseToken}` },
    onError: (error) => {
      if (error?.response?.status === 401) {
        profileNotFoundModalRef?.current?.showModal();
      } else {
        dispatch(showNotification(I18n.t('general.something_went_wrong'), NotificationType.WARNING));
      }
    },
    onSuccess: async (res: any) => handleLoginResponse(res)
  });

  navigation.setOptions({
    header: () => (
      <HeaderBarTitled
        color="light"
        containerStyle={{ paddingRight: responsiveScale(10) }}
        headerRightComponent={(
          <StepperCountText
            currentCount={1}
            totalTaskCount={2}
          />
        )}
        leftIconComponent={(
          <FontIcon name={icons.ArrowLeft} size={24} color={theme.colors.button_secondary_100} />
        )}
        leftIconPress={() => goBack()}
        title={I18n.t('general.login')}
        titleColor="text_primary"
        titleFontFamily="figtree"
        titleSize={18}
        titleWeight="semiBold"
      />
    )
  });

  useEffect(() => {
    if (firebaseToken !== '' && isSocialMediaLogin) {
      triggerSignInWithSocialMedia();
    }
  }, [firebaseToken]);

  useEffect(() => {
    const subscriber = auth().onAuthStateChanged((userData: FirebaseAuthTypes.User | null) => {
      if (userData) {
        userData.getIdToken().then((tokenValue: string) => {
          setFirebaseToken(tokenValue);
        });
      }
    });
    return subscriber; // unsubscribe on unmount
  }, []);

  return (
    <>
      <RowBar
        style={styles.progressRail}
        withBottomGap={false}
      >
        <View
          style={[styles.progressRail, {
            backgroundColor: theme.colors.icon_light,
            width: '50%'
          }]}
        />
      </RowBar>
      <ProgressView
        isLoading={validatingSocialLogin}
      />
      <ScrollView
        bounces={false}
        contentContainerStyle={styles.scrollContainer}
        keyboardShouldPersistTaps="handled"
      >
        <View>
          <Text
            color="text_secondary"
            fontFamily="figtree"
            size={16}
            style={{
              paddingBottom: responsiveScale(8)
            }}
          >
            {I18n.t('screen.phone_number_signup_heading')}
          </Text>
          <View>
            <RowBar
              style={{
                paddingVertical: responsiveScale(16)
              }}
              withBottomGap={false}
            >
              <TouchableOpacity
                activeOpacity={0.7}
                onPress={() => {
                  countrySelectionModalRef?.current?.showModal();
                  setErrorText('');
                }}
                style={styles.phoneCodeWrapper}
              >
                <Text
                  color="text_primary"
                  fontFamily="figtree"
                  size={16}
                  style={styles.phoneCodeText}
                >
                  {`${typeof country.countryGoogleId === 'string' && country.countryGoogleId.toLocaleUpperCase()} ${country.countryCode}`}
                </Text>
              </TouchableOpacity>
              <InputBox
                keyboardType="numeric"
                maskDetails={{
                  textFormat: country.textFormat.toString()
                }}
                maxLength={typeof country.phoneNumberLength === 'string'
                  ? +country.phoneNumberLength
                  : country.phoneNumberLength}
                onChangeValue={(phone: string, phoneFormatted: string) => {
                  setPhoneNumber(phone);
                  setPhoneNumberFormatted(phoneFormatted);
                }}
                onFocus={() => setErrorText('')}
                placeholderTextColor="text_placeholder"
                selectionColor={theme.colors.text_placeholder}
                textColor="text_primary"
                textStyle={styles.phoneNumberText}
                textWrapperStyle={{
                  ...styles.textWrapper,
                  borderColor: theme.colors[isKeyboardVisible ? 'button_secondary_100' : 'border_primary']
                }}
                useMaskedInput
                value={phoneNumberFormatted}
              />
            </RowBar>
            {errorText.length > 0
              && (
                <View
                  style={{
                    paddingBottom: responsiveScale(10)
                  }}
                >
                  <ErrorDisplayComponent
                    errorText={errorText}
                  />
                </View>
              )}
            <Button
              color={isDisabled ? 'background_light_grey' : 'button_secondary_100'}
              disabled={isDisabled}
              fontFamily="figtree"
              fontWeight="semiBold"
              loading={validatingNumber}
              onPress={onPressNext}
              style={styles.acceptButtonWrapper}
              subColor={isDisabled ? 'text_disabled' : 'light'}
              textSize={16}
              title={I18n.t('general.login')}
            />
          </View>
        </View>
        <Animated.View
          style={{
            transform: [
              {
                translateY: footerAnimation.interpolate({
                  inputRange: [0, 1],
                  outputRange: [0, responsiveScale(300)]
                })
              }
            ]
          }}
        >
          <RowBar
            style={{
              paddingVertical: responsiveScale(16)
            }}
            withBottomGap={false}
          >
            <View style={styles.viewDivider} />
            <Text
              color="button_secondary_50"
              fontFamily="figtree"
              style={{
                paddingHorizontal: responsiveScale(10)
              }}
              size={14}
            >
              {I18n.t('screen.other_signin_option')}
            </Text>
            <View style={styles.viewDivider} />
          </RowBar>
          <View
            style={{
              paddingBottom: responsiveScale(isIPhoneX() ? 10 : 6),
              paddingTop: responsiveScale(16)
            }}
          >
            <TouchableOpacity
              style={styles.otherSignUpWrapper}
              onPress={onPressStudioOwnerLogin}
            >
              <FontIcon
                name={icons.Studio}
                size={20}
                color={theme.colors.primary_400}
                style={{
                  position: 'absolute',
                  left: responsiveScale(16)
                }}
              />
              <Text
                color="button_secondary_100"
                fontFamily="figtree"
                size={14}
                weight="semiBold"
              >
                {I18n.t('screen.login.phn_number.sign_in_as_studio_owner')}
              </Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={styles.otherSignUpWrapper}
              onPress={onPressEmailLogin}
            >
              <View
                style={{
                  position: 'absolute',
                  left: responsiveScale(8)
                }}
              >
                <LoginEmailBold />
              </View>
              <Text
                color="button_secondary_100"
                fontFamily="figtree"
                size={14}
                weight="semiBold"
              >
                {I18n.t('screen.login.phn_number.sign_with_email')}
              </Text>
            </TouchableOpacity>
          </View>
        </Animated.View>
      </ScrollView>
      <Modal
        backDropColor={theme.colors.shadow_primary}
        onBackButtonPress={() => countrySelectionModalRef?.current?.hideModal()}
        onBackdropPress={() => countrySelectionModalRef?.current?.hideModal()}
        ref={countrySelectionModalRef}
        screen={(
          <ChooseCountryModalView
            displayPhnCode
            onPressContinue={(value: Record<string, string | number>) => {
              setCountry(value);
              countrySelectionModalRef?.current?.hideModal();
            }}
            value={country.countryGoogleId.toLowerCase()}
          />
        )}
        withRef
      />
      <Modal
        backDropColor={theme.colors.shadow_primary}
        onBackButtonPress={() => profileNotFoundModalRef?.current?.hideModal()}
        onBackdropPress={() => profileNotFoundModalRef?.current?.hideModal()}
        ref={profileNotFoundModalRef}
        screen={(
          <OverlayModalViewRevamp
            cancel={I18n.t('button.ok')}
            cancelHandle={() => profileNotFoundModalRef?.current?.hideModal()}
            subtitle={I18n.t('screen.social_media_login.account_not_found_desc')}
            subTitleTextStyle={{
              paddingRight: responsiveScale(50)
            }}
            cancelButtonStyle={{
              height: responsiveScale(40),
              width: responsiveScale(90)
            }}
            title={I18n.t('screen.social_media_login.account_not_found')}
            titleColor="dark_black_light"
            titleFont="secondary"
            titleSize={18}
            titleWeight="bold"
          />
        )}
        style={{
          justifyContent: 'flex-end'
        }}
        withRef
      />
    </>
  );
};

export default PhoneValidationScreen;

Simulator Screen Shot - iPhone 11 Pro - 2024-01-12 at 09 53 53

Otp screen:

import React, { useEffect, useState } from 'react';
import {
  Keyboard,
  ScrollView,
  StyleSheet,
  TouchableOpacity,
  View
} from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import auth, { FirebaseAuthTypes } from '@react-native-firebase/auth';

import { useAxiosPost, useAxiosPut } from '@hook';

import { ArtistState } from '@store/modules/artist/types';
import { ASYNC_STORE, storeData } from '@storage';
import { AuthState } from '@store/modules/auth/types';
import { Button, FontIcon, HeaderBarTitled, RowBar, Text } from '@components';
import { I18n } from '@lib';
import { setPhoneOtp } from '@store/modules/registration/actions';
import { setProfile } from '@store/modules/auth/actions';
import { setSystemLanguage, setUserProcessAndRole, showNotification } from '@store/modules/system/actions';
import { SystemState } from '@store/modules/system/types';
import { theme } from '@styles';
import ErrorDisplayComponent from '@modules/registration/components/ErrorDisplayComponent';
import OtpInputPhone from '@modules/registration/components/OtpInputPhone';
import StepperCountText from '@modules/registration/components/StepperCountText';

import { getAPI, icons, REGEX, responsiveScale } from '@constants';
import { IdentifyTypes, NotificationType, SystemLanguage, UserProcess } from '@constants/enums';

const styles: Record<string, object> = StyleSheet.create({
  infoLineWrapper: {
    flexDirection: 'row',
    justifyContent: 'center',
    paddingBottom: responsiveScale(20)
  },
  infoText: {
    letterSpacing: 0.03,
    lineHeight: responsiveScale(16),
    paddingLeft: responsiveScale(2)
  },
  inputContainer: {
    flex: 1,
    marginVertical: responsiveScale(10)
  },
  scrollContainer: {
    backgroundColor: theme.colors.light,
    flexGrow: 1,
    justifyContent: 'space-between',
    paddingHorizontal: responsiveScale(16),
    paddingTop: responsiveScale(20)
  },
  acceptButtonWrapper: {
    borderRadius: responsiveScale(4),
    borderWidth: 0,
    height: responsiveScale(48),
    marginVertical: responsiveScale(10),
    width: '100%'
  },
  progressRail: {
    backgroundColor: theme.colors.background_light_grey,
    height: responsiveScale(4)
  }
});

const secondsToRetry = 15;

const PhoneOtpVerificationScreen: React.FC<{
  navigation: Record<string, Function>;
  route: {
    params: {
      formattedPhnNumber?: string;
    };
  };
}> = ({ navigation, route = { params: { formattedPhnNumber: '' } } }) => {
  const { basicDetails } = useSelector((state: Record<string, ArtistState>) => state.artist);
  const { systemLanguage } = useSelector((state: Record<string, SystemState>) => state.system);
  const authState = useSelector((state: Record<string, AuthState>) => state.auth);
  const dispatch = useDispatch();
  const isConnected = useSelector((state: Record<string, SystemState>) => state.system.isConnected);

  const [
    confirmation, setConfirmation
  ] = useState<FirebaseAuthTypes.ConfirmationResult | null>(null);
  const [enableButton, setEnableButton] = useState<boolean>(false);
  const [firebaseToken, setFirebaseToken] = useState('');
  const [isActive, setIsActive] = useState(false);
  const [isInvalidOtp, setIsInvalidOtp] = useState(false);
  const [isValidating, setIsValidating] = useState(false);
  const [otp, setOtp] = useState('');
  const [seconds, setSeconds] = useState(secondsToRetry);

  const { formattedPhnNumber } = route.params;
  const { phoneCountryCode, phoneNumber } = basicDetails;
  const phoneNum = `${phoneCountryCode}${phoneNumber}`;
  const formattedPhoneNum = `${phoneCountryCode} ${formattedPhnNumber && formattedPhnNumber.length > 0
    ? formattedPhnNumber
    : phoneNumber}`;
  const isButtonDisabled = confirmation === null || isValidating;
  const selectedLanguage = systemLanguage === 'DE' ? SystemLanguage.GERMAN : SystemLanguage.ENGLISH;

  const noInternet = (): void => {
    if (!isConnected) {
      navigation.navigate('InternetNotify');
    }
  };

  const handleAppLanguageSuccess = (res: Record<string, Record<string, string>>): void => {
    const { data } = res;
    I18n.locale = data.app_language;
    dispatch(setSystemLanguage(data.app_language));
    storeData(ASYNC_STORE.CURRENT_LANGUAGE, data.app_language);
  };

  const [, setAppLanguage] = useAxiosPut(
    getAPI.GET_APP_LANGUAGE,
    {
      onError: () => dispatch(showNotification(I18n.t('general.something_went_wrong'), NotificationType.WARNING)),
      onSuccess: (res: Record<string, Record<string, string>>) => handleAppLanguageSuccess(res)
    }
  );

  const setLanguage = async (): Promise<void> => {
    setAppLanguage({
      data: {
        app_language: systemLanguage
      }
    });
  };

  const [, triggerSignInWithPhoneNumber] = useAxiosPost(getAPI.USER_LOGIN, {
    headers: { Authorization: `FirebaseToken ${firebaseToken}`, 'Accept-Language': selectedLanguage },
    onError: () => {
    },
    onSuccess: async (res: any) => {
      setLanguage();
      await storeData(
        ASYNC_STORE.LOGIN_DETAILS.ACCESS_TOKEN,
        res.data.login_details.access_token
      );
      await storeData(
        ASYNC_STORE.LOGIN_DETAILS.REFRESH_TOKEN,
        res.data.login_details.refresh_token
      );

      const { role } = res.data?.basic_details;

      dispatch(setUserProcessAndRole(UserProcess.LOGIN, role));
      dispatch(setProfile(res, IdentifyTypes.LOGIN));
    }
  });

  useEffect(() => {
    if (firebaseToken !== '' || firebaseToken.length > 0) {
      const data = {
        phone_code: authState.phoneCountryCode,
        phone_no: authState.phoneNumber,
        process: UserProcess.LOGIN
      };
      triggerSignInWithPhoneNumber({ data });
    }
  }, [firebaseToken]);

  const triggerFirebaseSignIn = (): void => {
    noInternet();
    auth()
      .signInWithPhoneNumber(phoneNum, true)
      .then((confirm) => {
        setConfirmation(confirm);
      });
  };

  useEffect(() => {
    if (confirmation === null) {
      triggerFirebaseSignIn();
    }
  });

  const phoneNumberSignIn = (): void => {
    if (!isConnected) navigation.navigate('InternetNotify');

    auth().signInWithPhoneNumber(phoneNum, true).then((confirm) => {
      setConfirmation(confirm);
    });
  };

  useEffect(() => {
    phoneNumberSignIn();

    const unsubscribe = auth().onAuthStateChanged((user: FirebaseAuthTypes.User | null) => {
      if (user) {
        user.getIdToken().then((tokenValue: string) => {
          setFirebaseToken(tokenValue);
        });
      }
    });

    return unsubscribe;
  }, []);

  useEffect(() => {
    let interval: any;
    if (seconds <= 0) {
      setIsActive(true);
    }
    if (!isActive) {
      interval = setInterval(() => {
        setSeconds((second) => second - 1);
      }, 1000);
    } else if (isActive && seconds !== 0) {
      clearInterval(interval);
    }
    return () => clearInterval(interval);
  }, [isActive, seconds]);

  const onResend = (): void => {
    phoneNumberSignIn();
    if (isActive) {
      dispatch(setPhoneOtp([]));
      // navigation.goBack();
    }
  };

  const validateOtp = (): void => {
    Keyboard.dismiss();
    if (isConnected) {
      setIsValidating(true);
      if (confirmation) {
        if (REGEX.NUMBER.test(otp)) {
          confirmation.confirm(otp).catch(() => {
            setIsInvalidOtp(true);
            setIsValidating(false);
          });
        } else {
          setIsInvalidOtp(true);
          setIsValidating(false);
        }
      }
    } else {
      navigation.navigate('InternetNotify');
      setIsValidating(false);
    }
  };

  return (
    <>
      <HeaderBarTitled
        color="light"
        containerStyle={{ paddingRight: responsiveScale(10) }}
        headerRightComponent={(
          <StepperCountText
            currentCount={2}
            totalTaskCount={2}
          />
        )}
        leftIconComponent={(
          <FontIcon name={icons.ArrowLeft} size={24} color={theme.colors.button_secondary_100} />
        )}
        leftIconPress={() => {
          navigation.goBack();
          dispatch(setPhoneOtp([]));
        }}
        title={I18n.t('screen.otp.header')}
        titleColor="text_primary"
        titleFontFamily="figtree"
        titleSize={18}
        titleWeight="semiBold"
      />
      <RowBar
        style={styles.progressRail}
        withBottomGap={false}
      >
        <View
          style={[styles.progressRail, {
            backgroundColor: theme.colors.icon_light,
            width: '100%'
          }]}
        />
      </RowBar>
      <ScrollView
        bounces={false}
        contentContainerStyle={styles.scrollContainer}
        keyboardShouldPersistTaps="handled"
      >
        <View>
          <View
            style={{
              paddingBottom: responsiveScale(8)
            }}
          >
            <Text
              color="text_secondary"
              fontFamily="figtree"
              size={16}
              style={{ lineHeight: responsiveScale(24) }}
            >
              {I18n.t('screen.otp.phone_header_1')}
              <Text
                color="text_primary"
                fontFamily="figtree"
                size={16}
                style={{ lineHeight: responsiveScale(24) }}
                weight="semiBold"
              >
                {` ${formattedPhoneNum} `}
              </Text>
              <Text
                color="text_secondary"
                fontFamily="figtree"
                size={16}
                style={{ lineHeight: responsiveScale(24) }}
              >
                {I18n.t('screen.otp.phone_header_2')}
              </Text>
            </Text>
          </View>
          <View
            style={styles.inputContainer}
          >
            <OtpInputPhone
              isError={isInvalidOtp}
              onCodechange={(value: Array<string>) => setEnableButton(value.length === 6)}
              onCodeComplete={(code: string) => {
                setOtp(code);
                Keyboard.dismiss();
              }}
              onFocus={() => {
                if (isInvalidOtp) dispatch(setPhoneOtp([])); setIsInvalidOtp(false);
              }}
            />
          </View>
          {
            isInvalidOtp
              ? (
                <View
                  style={{
                    paddingBottom: responsiveScale(10)
                  }}
                >
                  <ErrorDisplayComponent
                    errorText={I18n.t('screen.otp_invalid_otp')}
                  />
                </View>
              ) : null
          }
          <Button
            color={(!enableButton || isButtonDisabled) && !isValidating ? 'background_light_grey' : 'button_secondary_100'}
            disabled={!enableButton || isButtonDisabled}
            fontFamily="figtree"
            fontWeight="semiBold"
            loading={isValidating}
            onPress={() => validateOtp()}
            style={styles.acceptButtonWrapper}
            textSize={16}
            subColor={!enableButton || isButtonDisabled ? 'text_disabled' : 'light'}
            title={I18n.t('screen.otp.verifyButton')}
          />
        </View>
        <View
          style={styles.infoLineWrapper}
        >
          <Text
            color="button_secondary_50"
            fontFamily="figtree"
            size={14}
            style={styles.infoText}
          >
            {I18n.t('screen.otp.didnot_receive')}
          </Text>
          <TouchableOpacity
            disabled={!isActive}
            onPress={() => onResend()}
          >
            <Text
              color="button_secondary_100"
              fontFamily="figtree"
              size={14}
              style={[styles.infoText, {
                opacity: isActive ? 1 : 0.5
              }]}
              weight="semiBold"
            >
              {` ${I18n.t('screen.otp.resend')}`}
            </Text>
          </TouchableOpacity>
        </View>
      </ScrollView>
    </>
  );
};

export default PhoneOtpVerificationScreen;

Simulator Screen Shot - iPhone 11 Pro - 2024-01-12 at 09 54 26

Is anything I'm missing here. Please note there is no issue in Android.

Thanks.

akhilsanker commented 8 months ago

@mikehardy , Any updates on this Thanks

akhilsanker commented 8 months ago

@mikehardy , Any updates on this.

Thanks.

mikehardy commented 8 months ago

With apologies, this is very very far from a minimal reproduction.

My initial response is that you have two cases where you try to do a phone OTP:


  const triggerFirebaseSignIn = (): void => {
    noInternet();
    auth()
      .signInWithPhoneNumber(phoneNum, true)
      .then((confirm) => {
        setConfirmation(confirm);
      });
  };

  useEffect(() => {
    if (confirmation === null) {
      triggerFirebaseSignIn();
    }
  });

  const phoneNumberSignIn = (): void => {
    if (!isConnected) navigation.navigate('InternetNotify');

    auth().signInWithPhoneNumber(phoneNum, true).then((confirm) => {
      setConfirmation(confirm);
    });
  };

And they are triggered by state changes in some cases. They are likely both being called, perhaps in response to some unexpected state change.

This is a commonly used flow (Phone OTP) and no one else is reporting this, while you have pretty complicated code that is causing a problem. My working hypothesis is that the library is working fine but you have an error in your code.

If you were to boil it down to a simple page that did nothing but take a phone number, and call the phone verification API in response to a button press next to the phone number, I bet you would see one and only one OTP SMS every time --> https://stackoverflow.com/help/minimal-reproducible-example

If you can do that - create a single screen with nothing but a phone number input box that calls the react-native-firebase API - and you reproduce multiple SMS then we have an issue here

github-actions[bot] commented 7 months ago

Hello 👋, to help manage issues we automatically close stale issues.

This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.