JIT
debugging fcm token issue in react native app navigation setup
import React, {useState, useEffect, useCallback, useRef} from 'react'; import { CommonActions, NavigationContainer, useNavigationContainerRef, } from '@react-navigation/native'; import {createNativeStackNavigator} from '@react-navigation/native-stack'; import { ActivityIndicator, View, StyleSheet, Alert, Text, Dimensions, } from 'react-native'; import Toast from 'react-native-toast-message'; import {LogBox} from 'react-native'; // Contexts and Utils import AsyncStorageManager from '../utils/AsyncStorageManager'; // Adjust path if needed import {UserProvider, useUser} from '../context/UserContext'; // Adjust path if needed import { MqttProvider, useMqtt, MqttConnectionState, } from '../context/MqttContext'; // Adjust path if needed // Screens and Navigators import LoginScreen from '../screens/LoginScreen'; // Adjust path if needed import MainTabNavigator from './MainTabNavigator'; // Adjust path if needed import CommunityItemDetailScreen from '../screens/CommunityItemDetailScreen'; // <<< Import the detail screen import GalleryScreen from '../screens/GalleryScreen'; import FrontCameraScreen from '../screens/FrontCameraScreen'; import DeviceScreen from '../screens/DeviceScreen'; import ZoneScreen from '../screens/ZoneScreen'; import AlarmsScreen from '../screens/AlarmsScreen'; import DeviceItemScreen from '../screens/DeviceItemScreen'; import GiveAccessScreen from '../screens/GiveAccessScreen'; import ViewAccessScreen from '../screens/AccessTabs/ViewAccessScreen'; // Notification Service import NotificationService, { ACTION_ACCEPT, ACTION_DECLINE, ACTION_SILENCE, } from '../services/NotificationService'; // Adjust path if needed import CCTVScreen from '../screens/CameraTabs/CCTVScreen'; import ZoneItemScreen from '../screens/ZoneItemScreen'; import AmenityScreen from '../screens/AmenityScreen'; import EventScreen from '../screens/EventScreen'; import CommunityScreen from '../screens/CommunityScreen'; import AmenityDetailsScreen from '../screens/AmenityDetailsScreen'; import EventDetailsScreen from '../screens/EventDetailsScreen'; import BookingScreen from '../screens/BookingTabs/BookingScreen'; import InviteGuestScreen from '../screens/InviteGuestScreen'; import messaging from '@react-native-firebase/messaging'; const Stack = createNativeStackNavigator(); const {height: screenHeight} = Dimensions.get('window'); // --- Notification Handling Logic (Keep as before) --- const handleNotificationOpen = (navigationRef, data, action) => { console.log('Handling Notification Open/Action:', {data, action}); if (!data) { console.warn('No data found in notification to handle.'); return; } const targetScreen = data.screen; const notificationId = data.localNotificationId; // Assuming you pass this in userInfo // Example action handling (customize as needed) if (action) { console.log( `Action '${action}' taken on notification ID: ${notificationId}.`, ); // Handle specific actions like ACCEPT, DECLINE, SILENCE if (action === ACTION_ACCEPT) { console.log(`Handling ACCEPT action.`); // Navigate or perform accept logic if (targetScreen && navigationRef.isReady()) { navigationRef.navigate(targetScreen, { notificationData: data, actionTaken: action, }); } } else if (action === ACTION_DECLINE) { console.log(`Handling DECLINE action.`); // Perform decline logic } else if (action === ACTION_SILENCE) { console.log(`Handling SILENCE action.`); // Perform silence logic } // Optionally cancel the notification after handling the action // notificationService.current?.cancelNotificationById(notificationId); // Need access to service instance } else { // --- Notification Tapped --- console.log(`Handling TAP action for notification ID: ${notificationId}`); if (targetScreen && navigationRef.isReady()) { console.log(`Navigating to screen: ${targetScreen}`); // Ensure navigation happens correctly, potentially checking if already on the screen // or using reset/push depending on desired behavior. // Check if the target screen is the detail screen if (targetScreen === 'CommunityItemDetail') { // Ensure data contains the necessary 'item' for the detail screen if (data.item) { navigationRef.navigate(targetScreen, {item: data.item}); } else { console.warn( 'Notification tapped for CommunityItemDetail but missing item data.', ); // Optionally navigate to a default state or show an error } } else { // Handle navigation for other screens navigationRef.navigate(targetScreen, {notificationData: data}); } } else if (!targetScreen) { console.log('No target screen specified in notification data.'); } else { console.warn('Navigation not ready when notification was opened.'); // Optionally queue the navigation action until ready } } }; // --- Main Content Component --- const AppNavigationContent = () => { const [isLoading, setIsLoading] = useState(true); const [isLoggedIn, setIsLoggedIn] = useState(false); let fcmToken = ''; const {setUserDetails, clearUserDetails, updateUserDetails} = useUser(); // Added updateUserDetails const { connect: connectMqtt, disconnect: disconnectMqtt, connectionState: mqttState, publish, } = useMqtt(); const shownConnectedToastRef = useRef(false); const navigationRef = useNavigationContainerRef(); const notificationService = useRef(null); const [notificationInitialized, setNotificationInitialized] = useState(false); // Initialize Notification Service Ref (runs only once) useEffect(() => { LogBox.ignoreAllLogs(true); const onRegisterToken = token => { console.log('AppNavigator received FCM Token:', token); fcmToken = token; }; const onNotificationOpened = (data, action) => { // Ensure navigationRef is available and ready if needed handleNotificationOpen(navigationRef, data, action); }; console.log('AppNavigator: Creating NotificationService instance...'); // Pass the callbacks when creating the instance notificationService.current = new NotificationService( onNotificationOpened, onRegisterToken, ); // Don't initialize immediately here, wait for login status }, []); // Empty dependency array ensures this runs only once // Effect to initialize notifications AFTER login status is confirmed useEffect(() => { // Only initialize if logged in AND not already initialized if (isLoggedIn && notificationService.current && !notificationInitialized) { console.log( 'User logged in, initializing Notification Service (will request permissions)...', ); // Call initialize() which now includes requesting permissions notificationService.current .initialize() .then(() => { console.log('Notification Service initialization attempt complete.'); setNotificationInitialized(true); // Mark as initialized (or attempted) }) .catch(error => { console.error( 'Error during Notification Service initialization:', error, ); setNotificationInitialized(true); // Mark as initialized even on error to prevent retries }); } else if (!isLoggedIn && notificationInitialized) { // Optional: Reset initialization status if user logs out console.log( 'User logged out, resetting notification initialization status.', ); setNotificationInitialized(false); } }, [isLoggedIn, notificationInitialized]); // Depend on login status and initialization status // MQTT Connection Toast (Keep as before) useEffect(() => { if (mqttState === MqttConnectionState.CONNECTED) { if (!shownConnectedToastRef.current) { Toast.show({ type: 'success', text1: 'MQTT Status', text2: 'Successfully connected! 👋', position: 'bottom', visibilityTime: 3000, }); shownConnectedToastRef.current = true; } } else if ( mqttState !== MqttConnectionState.CONNECTING && mqttState !== MqttConnectionState.RECONNECTING ) { if (shownConnectedToastRef.current) { shownConnectedToastRef.current = false; } } }, [mqttState]); useEffect(() => { let isMounted = true; const checkLoginStatus = async () => { setIsLoading(true); // Start loading indicator try { const storedUserData = await AsyncStorageManager.getItem('userData'); console.log('isMounted', isMounted); console.log('storedUserData', storedUserData); if (isMounted) { if (storedUserData && storedUserData.token) { // Check only for token existence console.log('storedUserData after ismount', storedUserData); console.log('User session found.'); setUserDetails(storedUserData); setIsLoggedIn(true); } else { console.log('No user session found.'); setIsLoggedIn(false); clearUserDetails(); } } } catch (error) { console.error('AppNavigator: Failed to load user data:', error); if (isMounted) { Alert.alert('Error', 'Failed to load session data.'); setIsLoggedIn(false); clearUserDetails(); } } finally { if (isMounted) setIsLoading(false); // Session check complete } }; checkLoginStatus(); return () => { isMounted = false; }; }, [setUserDetails, clearUserDetails]); // Removed isLoggedIn from dependency array // Connect/Disconnect MQTT based on Login Status (Keep as before) useEffect(() => { if (isLoggedIn) { if ( mqttState === MqttConnectionState.DISCONNECTED || mqttState === MqttConnectionState.CLOSED || mqttState === MqttConnectionState.ERROR ) { console.log('User logged in, connecting MQTT...'); connectMqtt(); } } else { if ( mqttState === MqttConnectionState.CONNECTED || mqttState === MqttConnectionState.CONNECTING || mqttState === MqttConnectionState.RECONNECTING ) { console.log('User logged out, disconnecting MQTT...'); disconnectMqtt(); } } }, [isLoggedIn, connectMqtt, disconnectMqtt, mqttState]); // Login Handler (Keep as before) const handleLoginSuccess = useCallback( loggedInUserData => { setIsLoading(true); // Show loading briefly while setting state console.log('loggedInUserData', loggedInUserData); if (loggedInUserData && loggedInUserData.profileDetails) { setUserDetails(loggedInUserData); setIsLoggedIn(true); console.log('fcmToken', loggedInUserData); messaging() .subscribeToTopic('advaint_global') .then(() => { console.log('Successfully subscribed to advaint_global topic'); }) .catch(err => { console.log('Error subscribing to advaint_global topic:', err); }); messaging() .subscribeToTopic( loggedInUserData.profileDetails.society_id.toString(), ) .then(() => { console.log('Successfully subscribed to society topic'); }) .catch(err => { console.log('Error subscribing to society topic:', err); }); const mqtt_firebase_token_flat = 'advaint/addfirebasetoken/cus_' + loggedInUserData.profileDetails.customer_id; const mqtt_firebase_token_society = 'advaint/addfirebasetoken/soc_' + loggedInUserData.profileDetails.society_id; const firebasebTokenMessage = { contact_id: loggedInUserData.profileDetails.flat_contact_id, email: loggedInUserData.profileDetails.email, firebase_token: fcmToken, }; const firebasebTokenMessageForSociety = { contact_id: loggedInUserData.profileDetails.flat_contact_id, email: loggedInUserData.profileDetails.email, firebase_token: fcmToken, }; console.log( 'firebasebTokenMessageForSociety', mqtt_firebase_token_society, firebasebTokenMessageForSociety, ); console.log( 'firebasebTokenMessage', mqtt_firebase_token_flat, firebasebTokenMessage, ); publish(mqtt_firebase_token_society, firebasebTokenMessageForSociety); publish(mqtt_firebase_token_flat, firebasebTokenMessage); } else { Alert.alert('Login Error', 'Failed to process login data.'); setIsLoggedIn(false); clearUserDetails(); } setIsLoading(false); }, [setUserDetails, clearUserDetails], ); const handleLogout = useCallback(async () => { try { // Clear AsyncStorage first await AsyncStorageManager.clearAll(); console.log('AsyncStorage cleared during logout.'); // Clear user details from context clearUserDetails(); console.log('User context cleared.'); // Update isLoggedIn state setIsLoggedIn(false); console.log('isLoggedIn state set to false.'); // Reset notification initialization state setNotificationInitialized(false); console.log('Notification initialization state reset.'); if (navigationRef.isReady()) { console.log('Resetting navigation stack to Login.'); navigationRef.dispatch( CommonActions.reset({ index: 0, routes: [{name: 'Login'}], }), ); } else { console.warn( 'Navigation not ready when attempting to reset after logout delay.', ); // Fallback: try resetting without delay if navigation wasn't ready initially navigationRef.dispatch( CommonActions.reset({ index: 0, routes: [{name: 'Login'}], }), ); } // 500ms delay } catch (error) { setTimeout(() => { console.error('Logout failed:', error); Alert.alert('Logout Error', 'An error occurred during logout.'); // Even if an error occurs during logout (e.g., AsyncStorage), // we still want to attempt to reset the UI state and navigation. clearUserDetails(); setIsLoggedIn(false); setNotificationInitialized(false); if (navigationRef.isReady()) { console.log('Resetting navigation stack to Login after error.'); navigationRef.dispatch( CommonActions.reset({ index: 0, routes: [{name: 'Login'}], }), ); } else { console.warn( 'Navigation not ready when attempting to reset after logout error delay.', ); navigationRef.dispatch( CommonActions.reset({ index: 0, routes: [{name: 'Login'}], }), ); } }, 500); // 500ms delay } }, [clearUserDetails, navigationRef]); // Loading State UI if (isLoading) { return ( <View style={styles.loadingContainer}> <ActivityIndicator size="large" color="#0000ff" /> <Text style={styles.loadingText}>Loading session...</Text> </View> ); } // Navigation Rendering return ( <NavigationContainer ref={navigationRef}> {/* The main stack navigator now includes Login, MainApp (Tabs), and the Detail screen */} <Stack.Navigator // Default screen options to apply to all screens screenOptions={{ headerStyle: {height: screenHeight * 0.07}, // <- Global header height is set here }}> {isLoggedIn ? ( <> <Stack.Screen name="MainApp" // This holds the Tab Navigator options={{headerShown: false}} // Hide header for the Tab container itself > {props => ( <MainTabNavigator {...props} handleLogout={handleLogout} /> )} </Stack.Screen> {/* Add CommunityItemDetailScreen HERE, outside MainApp but within the logged-in group */} <Stack.Screen name="CommunityItemDetail" component={CommunityItemDetailScreen} options={({route}) => ({ title: route.params?.item?.title || 'Item Details', headerShown: true, // <<< Make sure header is shown for the detail screen headerBackTitleVisible: false, // Optional: hide back button text on iOS })} /> <Stack.Screen name="GalleryScreen" component={GalleryScreen} options={({route}) => ({ title: route.params?.item?.title || 'Gallery', headerShown: true, // <<< Make sure header is shown for the detail screen headerBackTitleVisible: false, // Optional: hide back button text on iOS })} /> <Stack.Screen name="FrontCameraScreen" component={FrontCameraScreen} options={({route}) => ({ title: 'FRONT CAMERA', headerTitleAlign: 'center', // Add this line to center the title headerShown: true, headerBackTitleVisible: false, // Optional: hide back button text on iOS })} /> <Stack.Screen name="DeviceScreen" component={DeviceScreen} options={({route}) => ({ title: 'DEVICES', headerTitleAlign: 'center', // Add this line to center the title headerShown: true, headerBackTitleVisible: false, // Optional: hide back button text on iOS })} /> <Stack.Screen name="ZoneScreen" component={ZoneScreen} options={({route}) => ({ title: 'ZONES', headerTitleAlign: 'center', // Add this line to center the title headerShown: true, headerBackTitleVisible: false, // Optional: hide back button text on iOS })} /> <Stack.Screen name="AlarmsScreen" component={AlarmsScreen} options={({route}) => ({ title: 'ALARMS', headerTitleAlign: 'center', // Add this line to center the title headerShown: true, headerBackTitleVisible: false, // Optional: hide back button text on iOS })} /> <Stack.Screen name="CCTVScreen" component={CCTVScreen} options={({route}) => ({ title: 'CAMERAS', headerTitleAlign: 'center', // Add this line to center the title headerShown: true, headerBackTitleVisible: false, // Optional: hide back button text on iOS })} /> <Stack.Screen name="DeviceItemScreen" component={DeviceItemScreen} options={({route}) => ({ title: route.params?.device_name || 'DEVICES', headerTitleAlign: 'center', headerShown: true, headerBackTitleVisible: false, })} /> <Stack.Screen name="ZoneItemScreen" component={ZoneItemScreen} options={({route}) => ({ title: route.params?.zone_name || 'DEVICES', headerTitleAlign: 'center', headerShown: true, headerBackTitleVisible: false, })} /> <Stack.Screen name="GiveAccessScreen" component={GiveAccessScreen} options={({route}) => ({ title: 'GIVE ACCESS', headerTitleAlign: 'center', headerShown: true, headerBackTitleVisible: false, })} /> <Stack.Screen name="InviteGuestScreen" component={InviteGuestScreen} options={({route}) => ({ title: 'GUEST', headerTitleAlign: 'center', headerShown: true, headerBackTitleVisible: false, })} /> <Stack.Screen name="ViewAccessScreen" component={ViewAccessScreen} options={({route}) => ({ title: 'ACCESS', headerTitleAlign: 'center', headerShown: true, headerBackTitleVisible: false, })} /> <Stack.Screen name="AmenityScreen" component={AmenityScreen} options={({route}) => ({ title: 'AMENITIES', headerTitleAlign: 'center', headerShown: true, headerBackTitleVisible: false, })} /> <Stack.Screen name="EventScreen" component={EventScreen} options={({route}) => ({ title: 'EVENTS', headerTitleAlign: 'center', headerShown: true, headerBackTitleVisible: false, })} /> <Stack.Screen name="CommunityScreen" component={CommunityScreen} options={({route}) => ({ title: 'COMMUNITY', headerTitleAlign: 'center', headerShown: true, headerBackTitleVisible: false, })} /> <Stack.Screen name="AmenityDetailsScreen" component={AmenityDetailsScreen} options={({route}) => ({ title: 'BOOKING', headerTitleAlign: 'center', headerShown: true, headerBackTitleVisible: false, })} /> <Stack.Screen name="EventDetailsScreen" component={EventDetailsScreen} options={({route}) => ({ title: 'BOOKING', headerTitleAlign: 'center', headerShown: true, headerBackTitleVisible: false, })} /> <Stack.Screen name="BookingScreen" component={BookingScreen} options={({route}) => ({ title: 'BOOKINGS', headerTitleAlign: 'center', headerShown: true, headerBackTitleVisible: false, })} /> {/* Add other globally accessible logged-in screens here (e.g., Settings, Profile) */} </> ) : ( // Logged-out screen(s) <Stack.Screen name="Login" options={{headerShown: false}} // Hide header for Login screen > {/* Pass fcmToken down to LoginScreen if needed for display/debug */} {props => ( <LoginScreen {...props} onLoginSuccess={handleLoginSuccess} fcmToken={fcmToken} /> )} </Stack.Screen> )} </Stack.Navigator> <Toast /> </NavigationContainer> ); }; // --- Main AppNavigator Component (Wraps Providers) --- const AppNavigator = () => { return ( <UserProvider> <MqttProvider> <AppNavigationContent /> </MqttProvider> </UserProvider> ); }; const styles = StyleSheet.create({ loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f5f5f5', }, loadingText: {marginTop: 10, fontSize: 14, color: '#666'}, }); export default AppNavigator; got log in console.log('AppNavigator received FCM Token:', token); but in const firebasebTokenMessageForSociety = { contact_id: loggedInUserData.profileDetails.flat_contact_id, email: loggedInUserData.profileDetails.email, firebase_token: fcmToken, }; console.log( 'firebasebTokenMessageForSociety', mqtt_firebase_token_society, firebasebTokenMessageForSociety, ); fcmToken is empty
Updated files
v1
what chnage has done?

The main change was replacing the fcmToken variable with a useRef called fcmTokenRef.

Here's why and what was done:

  1. Problem: The fcmToken variable was a regular variable in the component's scope. The onRegisterToken callback (which receives the token asynchronously) was updating this variable. However, the handleLoginSuccess callback (which uses the token) might have been using a stale value of fcmToken captured when the useCallback was initially created, or the token might not have been received yet when login succeeded.
  2. Solution: A useRef (fcmTokenRef) was introduced. useRef provides a mutable object (.current) that persists across renders and whose value can be accessed and updated directly from any callback without issues related to stale closures or asynchronous timing.
  3. Implementation:
    • let fcmToken = ''; was removed.
    • const fcmTokenRef = useRef(''); was added.
    • Inside the onRegisterToken callback, fcmTokenRef.current = token; was added to store the received token in the ref.
    • Inside the handleLoginSuccess callback, const currentFcmToken = fcmTokenRef.current; was added to get the current value of the token from the ref just before using it.
    • The MQTT messages in handleLoginSuccess now use currentFcmToken.
    • publish was added to the dependency array of handleLoginSuccess.
    • fcmTokenRef.current = ''; was added to the handleLogout function to clear the token on logout.

This ensures that when handleLoginSuccess is called, it accesses the most up-to-date FCM token stored in the fcmTokenRef.current.*

not working just fcmToken from login screen to appnavigator import React, {useState, useRef, useEffect} from 'react'; import { View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator, Alert, KeyboardAvoidingView, Platform, ScrollView, LogBox, } from 'react-native'; import {useNavigation} from '@react-navigation/native'; import ApiManager from '../services/ApiManager'; import AsyncStorageManager from '../utils/AsyncStorageManager'; import {useMqtt, MqttConnectionState} from '../context/MqttContext'; import messaging from '@react-native-firebase/messaging'; // Receive fcmToken as a prop const LoginScreen = ({onLoginSuccess}) => { const [email, setEmail] = useState('rushikeshp@advaintai.com'); const [password, setPassword] = useState('Pass@1234'); const [isLoading, setIsLoading] = useState(false); const [isWaitingForMqtt, setIsWaitingForMqtt] = useState(false); const [fcmToken, setFcmToken] = useState(''); const navigation = useNavigation(); const passwordInputRef = useRef(null); const {connectionState: mqttState, error: mqttError} = useMqtt(); // Effect to watch MQTT state after login attempt starts waiting (Remains the same) useEffect(() => { LogBox.ignoreAllLogs(true); requestPermission(); if (!isWaitingForMqtt) return; console.log('LoginScreen: Waiting for MQTT, current state:', mqttState); if (mqttState === MqttConnectionState.CONNECTED) { console.log('LoginScreen: MQTT Connected! Navigating to MainApp.'); setIsWaitingForMqtt(false); // Navigation is handled by AppNavigator's isLoggedIn state change now // navigation.replace('MainApp'); // Remove direct navigation } else if (mqttState === MqttConnectionState.ERROR) { console.error( 'LoginScreen: MQTT connection failed while waiting.', mqttError, ); Alert.alert( 'Connection Error', `Could not connect to messaging service: ${ mqttError?.message || 'Unknown error' }. Please try logging in again later.`, [{text: 'OK', onPress: () => setIsWaitingForMqtt(false)}], ); } else if ( mqttState === MqttConnectionState.CLOSED || mqttState === MqttConnectionState.DISCONNECTED ) { console.warn( 'LoginScreen: MQTT connection closed or disconnected while waiting.', ); Alert.alert( 'Connection Issue', 'Messaging service connection was lost. Please try logging in again.', [{text: 'OK', onPress: () => setIsWaitingForMqtt(false)}], ); } }, [mqttState, isWaitingForMqtt, navigation, mqttError]); const requestPermission = async () => { try { const authorizationStatus = await messaging().requestPermission(); if ( authorizationStatus === messaging.AuthorizationStatus.AUTHORIZED || authorizationStatus === messaging.AuthorizationStatus.PROVISIONAL ) { const token = await messaging().getToken(); setFcmToken(token); setIsLoading(false); } else { console.log('Permission denied'); } } catch (error) { console.error('Error getting FCM token:', error.message || error); } }; const handleLogin = async () => { const trimmedEmail = email.trim(); const trimmedPassword = password.trim(); if (!trimmedEmail || !trimmedPassword) { Alert.alert('Error', 'Please enter both email and password.'); return; } if (!/\S+@\S+\.\S+/.test(trimmedEmail)) { Alert.alert('Error', 'Please enter a valid email address.'); return; } // <<< Check if FCM token is available >>> if (!fcmToken) { Alert.alert( 'Initialization Error', 'Could not retrieve notification token. Please restart the app or check network connection.', ); return; } setIsLoading(true); setIsWaitingForMqtt(false); try { // <<< Pass fcmToken to ApiManager.login >>> const loginResponse = await ApiManager.login( trimmedEmail, trimmedPassword, fcmToken, ); await AsyncStorageManager.setItem('userData', loginResponse); if (onLoginSuccess) { console.log('loginResponse', loginResponse); onLoginSuccess(loginResponse); // This sets isLoggedIn=true in AppNavigator } else { console.error('LoginScreen: onLoginSuccess callback is missing!'); setIsLoading(false); return; } console.log( 'LoginScreen: API Login successful. Waiting for MQTT connection (triggered by AppNavigator)...', ); setIsLoading(false); setIsWaitingForMqtt(true); // Start waiting for MQTT connection state change } catch (error) { Alert.alert( 'Login Failed', error.message || 'An unexpected error occurred.', ); setIsLoading(false); setIsWaitingForMqtt(false); } }; // Determine the text for the loading state const loadingText = isLoading ? 'Logging in...' : isWaitingForMqtt ? 'Connecting...' : 'Login'; // Disable button if loading, waiting for MQTT, OR if fcmToken is not yet available const isButtonDisabled = isLoading || isWaitingForMqtt || !fcmToken; const showLoadingIndicator = isLoading || isWaitingForMqtt; return ( <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={styles.container}> <ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled"> <View style={styles.innerContainer}> <Text style={styles.title}>Welcome Back!</Text> <Text style={styles.subtitle}>Login to continue</Text> <TextInput style={styles.input} placeholder="Email Address" value={email} onChangeText={setEmail} autoCapitalize="none" placeholderTextColor="#888" keyboardType="email-address" returnKeyType="next" onSubmitEditing={() => passwordInputRef.current?.focus()} blurOnSubmit={false} editable={!showLoadingIndicator} /> <TextInput ref={passwordInputRef} style={styles.input} placeholder="Password" value={password} onChangeText={setPassword} secureTextEntry placeholderTextColor="#888" returnKeyType="done" onSubmitEditing={handleLogin} editable={!showLoadingIndicator} /> {/* Show message if token is not ready */} {isLoading == true && ( <Text style={styles.tokenWarning}> Initializing notifications... </Text> )} <TouchableOpacity style={[styles.button, isButtonDisabled && styles.buttonDisabled]} onPress={handleLogin} disabled={isButtonDisabled} activeOpacity={0.7}> {showLoadingIndicator ? ( <ActivityIndicator size="small" color="#ffffff" /> ) : ( <Text style={styles.buttonText}>{loadingText}</Text> )} </TouchableOpacity> {isWaitingForMqtt && ( <Text style={styles.waitingText}> Establishing secure connection... </Text> )} </View> </ScrollView> </KeyboardAvoidingView> ); }; // Styles remain mostly the same, added tokenWarning style const styles = StyleSheet.create({ container: {flex: 1, backgroundColor: '#f0f2f5'}, scrollContainer: {flexGrow: 1, justifyContent: 'center', paddingBottom: 20}, innerContainer: { justifyContent: 'center', alignItems: 'center', paddingHorizontal: 30, paddingVertical: 40, }, title: { fontSize: 28, fontWeight: 'bold', marginBottom: 10, color: '#1c1e21', textAlign: 'center', }, subtitle: { fontSize: 16, color: '#606770', marginBottom: 40, textAlign: 'center', }, input: { width: '100%', maxWidth: 400, height: 50, backgroundColor: '#fff', borderWidth: 1, borderColor: '#dddfe2', borderRadius: 8, paddingHorizontal: 15, marginBottom: 15, fontSize: 16, color: '#1c1e21', }, button: { width: '100%', maxWidth: 400, height: 50, backgroundColor: '#1877f2', justifyContent: 'center', alignItems: 'center', borderRadius: 8, marginTop: 10, shadowColor: '#000', shadowOffset: {width: 0, height: 2}, shadowOpacity: 0.2, shadowRadius: 2.62, elevation: 4, }, buttonDisabled: {backgroundColor: '#a0caff', elevation: 0}, buttonText: {color: '#ffffff', fontSize: 18, fontWeight: 'bold'}, waitingText: {marginTop: 15, fontSize: 14, color: '#606770'}, tokenWarning: {marginBottom: 10, fontSize: 13, color: '#ff9800'}, // Style for token warning }); export default LoginScreen;

Okay, I understand. The issue is that the LoginScreen is trying to fetch the FCM token itself and pass it to ApiManager.login, while the AppNavigator is also trying to get the token via the NotificationService and store it in a ref. This creates a race condition and confusion about where the single source of truth for the FCM token is.

The AppNavigator is the better place to manage the NotificationService and the FCM token lifecycle, as it lives longer than the LoginScreen component and is responsible for the overall app state (logged in/out).

Here's the corrected approach:

  1. AppNavigator will continue to initialize NotificationService and store the token in fcmTokenRef.
  2. AppNavigator will pass the current value of fcmTokenRef.current down to the LoginScreen as a prop.
  3. LoginScreen will receive the FCM token via this prop and use it when calling ApiManager.login.
  4. LoginScreen will disable the login button until the fcmToken prop is received (i.e., not empty).
  5. AppNavigator's handleLoginSuccess will still use fcmTokenRef.current for the MQTT publish, ensuring it uses the token managed by the navigator's notification service.

Let's apply these changes to your provided code.

Updated files
v2
what chnages has done?

Here are the changes made:

In src/navigation/AppNavigator.js:

  1. The fcmTokenRef.current value is now explicitly passed as the fcmToken prop to the LoginScreen component within the <Stack.Screen name="Login">.
  2. Added mqttState to the dependency array of the handleLoginSuccess useCallback hook.
  3. Added a check (if (publish && mqttState === MqttConnectionState.CONNECTED)) before publishing the FCM token via MQTT in handleLoginSuccess to ensure the MQTT client is connected and the publish function is available. Added a console warning if publishing is skipped.

In src/screens/LoginScreen.js:

  1. Removed the internal fcmToken state (const [fcmToken, setFcmToken] = useState('');).
  2. Removed the requestPermission function and its call within the useEffect.
  3. Removed the messaging import as token fetching is no longer handled here.
  4. The component now receives fcmToken as a prop (const LoginScreen = ({onLoginSuccess, fcmToken}) => { ... }).
  5. The handleLogin function now uses the fcmToken prop when calling ApiManager.login.
  6. The login button's disabled state (isButtonDisabled) now checks if the fcmToken prop is available (!fcmToken).
  7. Updated the conditional text displayed below the inputs to reflect the state, including a message when waiting for the notification token prop.
im geneatting token in login screen just pass it to app navigator

Okay, let's adjust the flow so the FCM token is generated in the LoginScreen and passed up to the AppNavigator upon successful login.

Updated files
v3
what chage has done

Here are the changes made:

In src/navigation/AppNavigator.js:

  1. Removed the fcmTokenRef as the token is now managed by the LoginScreen.
  2. Removed the onRegisterToken callback from the NotificationService constructor. A placeholder comment was added to indicate that the NotificationService might still receive a token internally, but it's not the one used for the MQTT publish in handleLoginSuccess.
  3. Modified the handleLoginSuccess callback to accept the fcmToken as a second argument ((loggedInUserData, fcmToken)).
  4. The MQTT publish logic within handleLoginSuccess now uses the fcmToken argument received from the LoginScreen.
  5. Removed the fcmTokenRef.current = ''; line from handleLogout as the ref no longer exists.
  6. The LoginScreen component rendered within the <Stack.Screen name="Login"> no longer receives the fcmToken prop.

In src/screens/LoginScreen.js:

  1. Re-added the fcmToken state (const [fcmToken, setFcmToken] = useState('');).
  2. Re-added the messaging import.
  3. Re-added the requestPermission function to fetch the FCM token.
  4. Added a useEffect hook to call requestPermission when the component mounts ([] dependency array).
  5. The handleLogin function now uses the fcmToken from its local state when calling ApiManager.login.
  6. The onLoginSuccess callback received from AppNavigator is now called with two arguments: the loginResponse and the local fcmToken (onLoginSuccess(loginResponse, fcmToken)).
  7. The login button's disabled state (isButtonDisabled) now checks the local fcmToken state (!fcmToken).
  8. Updated the text displayed below the inputs to reflect the state based on the local fcmToken.

In essence, the responsibility for fetching and holding the FCM token was moved back to the LoginScreen, and the LoginScreen is now responsible for passing that token up to the AppNavigator via the onLoginSuccess callback when the user successfully logs in.

provide updated src folder
Updated files
v4
what changes has done and where?

Here are the changes made:

In src/navigation/AppNavigator.js:

  • Removed the fcmTokenRef state variable.
  • Removed the onRegisterToken callback from the NotificationService constructor.
  • Modified the handleLoginSuccess function to accept fcmToken as a parameter from the LoginScreen.
  • Updated the MQTT publish messages within handleLoginSuccess to use the fcmToken parameter received from LoginScreen.
  • Removed the line that cleared fcmTokenRef.current in the handleLogout function.
  • Removed the fcmToken prop being passed to the LoginScreen component in the Stack Navigator.

In src/screens/LoginScreen.js:

  • Re-added the local fcmToken state variable (const [fcmToken, setFcmToken] = useState('');).
  • Re-added the import messaging from '@react-native-firebase/messaging';.
  • Re-added the requestPermission function to fetch the FCM token.
  • Added a useEffect hook to call requestPermission when the component mounts.
  • Modified the handleLogin function to use the local fcmToken state when calling ApiManager.login.
  • Modified the call to the onLoginSuccess prop to pass the local fcmToken as the second argument.
  • Updated the logic for disabling the login button (isButtonDisabled) to check the local fcmToken state.
  • Adjusted the conditional text displayed below the inputs to reflect the status of the local fcmToken.