react-native-firebase
react-native-firebase copied to clipboard
[🐛] Bug Report Title - Receiving multiple otp sms in iOS device for phone authentication
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.
Project Files
Javascript
Click To Expand
package.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:
# N/A
iOS
Click To Expand
ios/Podfile
:
- [ ] I'm not using Pods
- [x] I'm using Pods and my Podfile looks like:
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
:
#import "AppDelegate.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <Firebase.h>
#import <CodePush/CodePush.h>
#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <TwitterKit/TWTRKit.h>
#import <React/RCTLinkingManager.h>
#import <RNGoogleSignin/RNGoogleSignin.h>
#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
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<UIApplicationOpenURLOptionsKey,id> *)options
{
return [RCTLinkingManager application:application openURL:url options:options];
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _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
:
// N/A
android/app/build.gradle
:
// 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
:
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
:
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<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> 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
:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="xxxx"
android:exported="true"
>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:usesCleartextTraffic="true"
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:allowBackup="false"
android:theme="@style/AppTheme"
android:largeHeap="true"
android:requestLegacyExternalStorage="true"
android:networkSecurityConfig="@xml/network_security_config"
>
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:screenOrientation="portrait"
android:exported="true"
>
<intent-filter
android:exported="false"
>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter
android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="xxx" />
<data android:pathPattern=".*" />
</intent-filter>
</activity>
<activity
android:name="com.facebook.react.devsupport.DevSettingsActivity"
android:exported="true"
/>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyCkXEvPUDC09JqbSgBF-uW0gecKocNbSW8"/>
<meta-data
android:name="com.onesignal.NotificationAccentColor.DEFAULT"
android:value="#8C62DB"
/>
<meta-data
android:name="com.google.android.gms.wallet.api.enabled"
android:value="true" />
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
<meta-data android:name="com.facebook.sdk.ClientToken" android:value="@string/facebook_client_token"/>
<!-- You will also only need to add this uses-library tag -->
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
</application>
</manifest>
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"
-
- 👉 Check out
React Native Firebase
andInvertase
on Twitter for updates on the library.
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
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.
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
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;
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;
Is anything I'm missing here. Please note there is no issue in Android.
Thanks.
@mikehardy , Any updates on this Thanks
@mikehardy , Any updates on this.
Thanks.
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
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.