-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Closed
Labels
platform: Androidtype: accepted/buguser: requires reproductionThis issue requires a reproduction before maintainers can work on itThis issue requires a reproduction before maintainers can work on it
Description
What happened?
During app launch, our app crashes on android from time to time. This had started after we upgraded to the version 8.*, with RN 0.77.3 with new arch enabled
The current version we use is the latest – 8.4.2
This is our current App.tsx:
import { applyMiddleware, type Store } from 'redux'
import ReduxThunk from 'redux-thunk'
import promiseMiddleware from 'redux-promise-middleware'
import {
initializeApp,
logOut,
performNeededActionsOnAppBackgrounding,
type RenewSessionCompletionData,
renewUserSessionIfNeeded
} from './actions/AppInitializationActions'
import 'react-native-reanimated'
// prevent a crash on gesture-handler component after react-native moved to inline requires
import 'react-native-gesture-handler'
import type { BobState } from './reducers'
import reducers from './reducers'
import {
APP_LAUNCHER_LOADER,
PEOPLE_LIST_SCREEN_ID,
APP_MENU_TAB_SCREEN,
HOME_SCREEN_ID,
LOGIN_SCREEN_ID,
SCREEN_TITLE_COMPONENT_ID,
STORYBOOK_SCREEN_ID
} from './screens/ScreensIds'
import { registerScreens } from './screens/screens'
import {
UNDETERMINED_LOGIN_STATE,
type UNDETERMINED_LOGIN_STATE_CASE,
NOT_LOGGED_IN_STATE,
LOGGED_IN_STATE,
type NOT_LOGGED_IN_STATE_CASE,
type LOGGED_IN_STATE_CASE,
HOMEPAGE_WHOSOUT,
HOMEPAGE_NEWJOINERS,
HOMEPAGE_BIRTHDAYS,
HOMEPAGE_ANNIVERSARIES,
HOMEPAGE_SHOUTOUTS,
DIRECTORY_FEATURE,
ORG_CHART_FEATURE,
TRY_DEMO_PREFIX,
type BobDispatchReturn,
HIBOB_COMPANY_ID
} from './consts'
import { screenWidth, getSafeTheme, clearThemeCache } from './services/AppearanceService'
import { AppState, type AppStateStatus, Platform, LogBox, Linking, InteractionManager, Appearance } from 'react-native'
import SoundsService from './services/SoundsService'
import MainStoreService from './services/MainStoreService'
import AppService from './services/AppService'
import TimeServiceHelper from './components/TimeOff/TimeServiceHelper'
import CallKitExtensionService from './services/CallKitExtensionService'
import AppTabs from './screens/AppTabs'
import ShoutoutHelper from './components/Shoutouts/ShoutoutHelper'
import {
Navigation,
type LayoutTabsChildren,
type LayoutRoot,
type AnimationOptions,
type Options
} from 'react-native-navigation'
import { initPushNotifications } from './services/PushNotificationsService'
import { getValueOfParamInURL, IS_IOS } from './services/BobUtils'
import { translate, TranslationPaths } from '@b-localizationService'
import { BobColorsService, ColorRole } from '@b-colors'
import { GOTHAM_BOOK } from '@b-typography'
import * as Sentry from '@sentry/react-native'
import UserHelper from './models/UserHelper'
import EnvironmentService from './services/EnvironmentService'
import ErrorReportService from './services/ErrorReportService'
import MAMConfigurationService from './services/MAMConfigurationService'
import { get } from '@b-lodash'
import { dismissHUD, showLoadingHUD, showResultHUD } from './components/common/Components/BobBasicComponents'
import selectIsStageEnvironment from './duckers/debugMode/selectors/selectIsStageEnvironment'
import type { ErrorEvent, EventHint } from '@sentry/browser'
import './unistyles'
import { UnistylesRuntime, type UnistylesTheme } from 'react-native-unistyles'
import {
initPropsWhichNeedsRootComponent,
getNavigationBarOptions,
topBarTitleStyle,
topBarSubtitleStyle,
backButtonPayload
} from './services/AppearanceService'
import EnvironmentConsts from './shared/constants/EnvironmentConsts'
import DeviceConstant from './shared/constants/DeviceConstant'
if (__DEV__) {
LogBox.ignoreAllLogs(true)
}
const navigationAnimationOptions: AnimationOptions =
Platform.OS === 'ios'
? {}
: {
push: {
content: {
translationX: {
from: screenWidth(),
to: 0,
duration: 300
}
}
},
pop: {
content: {
translationX: {
from: 0,
to: screenWidth(),
duration: 300
}
}
}
}
function buildNavigationDefaultOptions(theme: UnistylesTheme): Options {
return {
layout: { orientation: ['portrait'] },
topBar: {
title: topBarTitleStyle(undefined, theme),
subtitle: topBarSubtitleStyle(undefined, theme),
backButton: backButtonPayload(theme)
},
animations: navigationAnimationOptions,
statusBar: {
drawBehind: true,
backgroundColor: 'transparent',
style: BobColorsService.isDarkMode() ? 'light' : 'dark'
},
navigationBar: getNavigationBarOptions()
}
}
let warnedDefaultOptionsError = false
function setNavigationDefaultOptions() {
try {
if (!Navigation || typeof Navigation.setDefaultOptions !== 'function') {
if (__DEV__ && !warnedDefaultOptionsError) {
warnedDefaultOptionsError = true
console.warn('Navigation.setDefaultOptions is not available yet.')
}
return
}
const theme = getSafeTheme()
Navigation.setDefaultOptions(buildNavigationDefaultOptions(theme))
} catch (error) {
if (__DEV__ && !warnedDefaultOptionsError) {
warnedDefaultOptionsError = true
console.warn('Failed to set default navigation options:', error)
}
}
}
// Public function to reapply navigation defaults when theme changes
export function reapplyNavigationDefaults() {
setNavigationDefaultOptions()
}
export const FIRST_APP_LAUNCH_KEY: string = 'FIRST_APP_LAUNCH_KEY'
type AppRootViewType = NOT_LOGGED_IN_STATE_CASE | LOGGED_IN_STATE_CASE | UNDETERMINED_LOGIN_STATE_CASE
const middlewares = [promiseMiddleware]
export const kStore: Store<BobState> = MainStoreService.initialize(
reducers,
applyMiddleware(...middlewares, ReduxThunk)
)
registerScreens(kStore)
let kAppState: AppStateStatus
export default class App {
currentRoot: AppRootViewType
isBridgeInitialled = false
firstTime = true
companyId: number | null | undefined = null // needed for Sentry, see "beforeSend" below
theme: UnistylesTheme | undefined
private static savedBottomTabs: LayoutTabsChildren[] = []
private static activeComponentIds: Set<string> = new Set<string>()
private static getScreenSpecificOptions(
screenId: string | undefined,
companyName: string | undefined,
theme: UnistylesTheme
): Options {
const baseOptions: Options = {
layout: {
backgroundColor: theme.colors[ColorRole.backgroundGeneral]
},
statusBar: {
backgroundColor: theme.colors[ColorRole.backgroundGeneral]
},
navigationBar: getNavigationBarOptions(),
topBar: {
background: {
color: theme.colors[ColorRole.backgroundGeneral]
},
borderColor: theme.colors[ColorRole.border300],
title: {
color: theme.colors[ColorRole.text800]
},
backButton: {
color: theme.colors[ColorRole.text700]
}
}
}
switch (screenId) {
case HOME_SCREEN_ID:
return {
...baseOptions,
topBar: {
...baseOptions.topBar,
leftButtons: [
{
id: SCREEN_TITLE_COMPONENT_ID,
component: {
id: SCREEN_TITLE_COMPONENT_ID + HOME_SCREEN_ID,
name: SCREEN_TITLE_COMPONENT_ID,
passProps: {
text: companyName
}
}
}
]
}
}
case PEOPLE_LIST_SCREEN_ID:
return {
...baseOptions,
layout: {
...baseOptions.layout,
orientation: IS_IOS ? ['portrait', 'landscape'] : undefined
},
topBar: {
...baseOptions.topBar,
leftButtons: [
{
id: SCREEN_TITLE_COMPONENT_ID,
component: {
id: SCREEN_TITLE_COMPONENT_ID + PEOPLE_LIST_SCREEN_ID,
name: SCREEN_TITLE_COMPONENT_ID,
passProps: {
text: translate(TranslationPaths.peopleTabTitle)
}
}
}
],
noBorder: true,
scrollEdgeAppearance: {
active: true,
noBorder: true
},
borderColor: theme.colors[ColorRole.backgroundGeneral],
elevation: -1
}
}
case APP_MENU_TAB_SCREEN:
return {
layout: {
backgroundColor: theme.colors[ColorRole.grey100BackgroundOfWhite]
},
statusBar: {
backgroundColor: theme.colors[ColorRole.grey100BackgroundOfWhite]
},
navigationBar: getNavigationBarOptions(),
topBar: {
background: {
color: theme.colors[ColorRole.grey100BackgroundOfWhite]
},
leftButtons: [
{
id: SCREEN_TITLE_COMPONENT_ID,
component: {
id: SCREEN_TITLE_COMPONENT_ID + APP_MENU_TAB_SCREEN,
name: SCREEN_TITLE_COMPONENT_ID,
passProps: {
text: translate(TranslationPaths.appMenuScreen_title)
}
}
}
],
noBorder: true,
scrollEdgeAppearance: {
active: true,
noBorder: true
},
borderColor: theme.colors[ColorRole.divider200],
elevation: -1
}
}
default:
return baseOptions
}
}
private static getBottomTabOptions(screenId: string | undefined, theme: UnistylesTheme) {
const baseOptions = {
fontFamily: GOTHAM_BOOK,
fontSize: 12,
selectedFontSize: 12,
textColor: theme.colors[ColorRole.text800],
selectedTextColor: theme.colors[ColorRole.text800],
selectedIconColor: null,
iconColor: theme.colors[ColorRole.text800]
}
switch (screenId) {
case HOME_SCREEN_ID:
return {
...baseOptions,
icon: require('../images/homeTabIcon.png'),
selectedIcon: BobColorsService.isDarkMode()
? require('../images/selectedHomeTabIconDark.png')
: require('../images/selectedHomeTabIcon.png'),
text: translate(TranslationPaths.homeTabTitle)
}
case PEOPLE_LIST_SCREEN_ID:
return {
...baseOptions,
icon: require('../images/peopleTabIcon.png'),
selectedIcon: BobColorsService.isDarkMode()
? require('../images/selectedPeopleTabIconDark.png')
: require('../images/selectedPeopleTabIcon.png'),
text: translate(TranslationPaths.peopleTabTitle)
}
case APP_MENU_TAB_SCREEN:
return {
...baseOptions,
icon: require('../images/appMenuTabIcon.png'),
selectedIcon: BobColorsService.isDarkMode()
? require('../images/selectedAppMenuTabIconDark.png')
: require('../images/selectedAppMenuTabIcon.png'),
text: translate(TranslationPaths.appMenuTabTitle)
}
default:
return baseOptions
}
}
private static bottomTabs(
_userDisplayName: string,
companyName: string | undefined,
shouldShowHomeTab: boolean,
shouldDisplayPeopleTab: boolean,
passProps: object
): LayoutTabsChildren[] {
const children: LayoutTabsChildren[] = []
let currentTabIndex = -1
const theme = UnistylesRuntime.getTheme() as UnistylesTheme
if (shouldShowHomeTab) {
currentTabIndex++
children.push({
stack: {
children: [
{
component: {
id: HOME_SCREEN_ID,
name: HOME_SCREEN_ID,
options: App.getScreenSpecificOptions(HOME_SCREEN_ID, companyName, theme),
passProps: passProps
}
}
],
options: {
bottomTab: App.getBottomTabOptions(HOME_SCREEN_ID, theme)
}
}
})
}
if (shouldDisplayPeopleTab) {
currentTabIndex++
children.push({
stack: {
children: [
{
component: {
id: PEOPLE_LIST_SCREEN_ID,
name: PEOPLE_LIST_SCREEN_ID,
options: App.getScreenSpecificOptions(PEOPLE_LIST_SCREEN_ID, companyName, theme),
passProps: passProps
}
}
],
options: {
bottomTab: App.getBottomTabOptions(PEOPLE_LIST_SCREEN_ID, theme)
}
}
})
}
currentTabIndex++
AppTabs.TAB_BAR_APP_MENU_INDEX = currentTabIndex
children.push({
stack: {
children: [
{
component: {
id: APP_MENU_TAB_SCREEN,
name: APP_MENU_TAB_SCREEN,
options: App.getScreenSpecificOptions(APP_MENU_TAB_SCREEN, companyName, theme),
passProps: passProps
}
}
],
options: {
bottomTab: App.getBottomTabOptions(APP_MENU_TAB_SCREEN, theme)
}
}
})
return children
}
constructor() {
App.activeComponentIds = new Set<string>()
Navigation.events().registerComponentDidAppearListener(({ componentId }) => {
App.activeComponentIds.add(componentId)
})
BobColorsService.initTheme()
// Set up theme change listener to handle navigation updates
Appearance.addChangeListener(({ colorScheme }) => {
try {
BobColorsService.setUserTheme(colorScheme)
// Clear theme cache to ensure fresh theme data on next access
clearThemeCache()
// Reapply navigation defaults first to ensure consistency
reapplyNavigationDefaults()
// Then refresh all screen-specific navigation elements
App.refreshUserTopBarAndBottomBar()
} catch (_error) {
// Fallback: ensure navigation defaults are applied even if other updates fail
try {
clearThemeCache()
reapplyNavigationDefaults()
} catch (_fallbackError) {
// Silent fail in extreme cases to prevent crash
}
}
})
EnvironmentService.initialize()
this.initSentry()
// CRITICAL FIX for HIBOB-MOBILE-51J: Apply MAM configuration early in app lifecycle
// This ensures enrollment cancellation tracking is configured before any MSAL operations
MAMConfigurationService.applyConfigurationToNativeModules()
Navigation.events().registerAppLaunchedListener(() => {
this.isBridgeInitialled = true
// Apply defaults first to avoid setRoot using old defaults
setNavigationDefaultOptions()
initPropsWhichNeedsRootComponent()
const forceResetRoot = true
this.onStoreUpdate(forceResetRoot)
})
AppService.initialize()
const store = MainStoreService.store()
// since react-redux only works on components, we need to subscribe this class manually
store.subscribe(this.onStoreUpdate.bind(this))
store.dispatch(initializeApp(this.onAppInitializationCompletion))
SoundsService.initialize()
ShoutoutHelper.initialize(store)
TimeServiceHelper.initialize(store)
CallKitExtensionService.initialize().catch((error) => {
ErrorReportService.logNonFatalErrorWithContext(error, 'Failed to initialize CallKitExtensionService')
})
kAppState = AppState.currentState
if (Platform.OS === 'android') {
// android bug – getting false changes on start
setTimeout(() => {
kAppState = AppState.currentState
AppState.addEventListener('change', this.onAppStateChange)
}, 5000)
} else {
AppState.addEventListener('change', this.onAppStateChange)
}
}
private onDeepLinkingLaunch = (urlOrEvent?: string | { url: string }) => {
const url = typeof urlOrEvent === 'string' ? urlOrEvent : get(urlOrEvent, 'url')
const isOpenApp = typeof urlOrEvent === 'string'
this.handleDeepLinking(url, isOpenApp)
}
private showLoadingHUD = (url: string, shouldWaitNavigation: boolean = false) => {
const companyName = getValueOfParamInURL(url, 'companyName')
if (shouldWaitNavigation) {
const screenEventListener = Navigation.events().registerComponentDidAppearListener(({ componentName }) => {
if (componentName === LOGIN_SCREEN_ID) {
setTimeout(() => {
showLoadingHUD(
translate(TranslationPaths.try_out_environment_hub_loading_message, {
companyName: companyName
})
)
}, 0)
screenEventListener.remove()
}
})
} else {
InteractionManager.runAfterInteractions(() => {
showLoadingHUD(
translate(TranslationPaths.try_out_environment_hub_loading_message, {
companyName: companyName
})
)
})
}
}
private setEnvironment = (url: string) => {
const domain = getValueOfParamInURL(url, 'domain')
if (!domain) {
return
}
EnvironmentService.bobDomain = domain
setTimeout(() => {
dismissHUD()
showResultHUD(true, translate(TranslationPaths.try_out_environment_hub_success_message))
}, 3000)
}
private handleDeepLinking = (url: string | undefined | null, isOpenApp: boolean) => {
if (!url) {
return
}
if (url.startsWith(TRY_DEMO_PREFIX)) {
const domain = getValueOfParamInURL(url, 'domain')
if (!domain) {
showResultHUD(false, translate(TranslationPaths.try_out_environment_hub_error_message))
return
}
const storeState = kStore.getState() as BobState
const root: AppRootViewType = storeState.app.root
if (LOGGED_IN_STATE === root) {
const dispatch = kStore.dispatch as BobDispatchReturn
this.showLoadingHUD(url, true)
dispatch(
logOut(() => {
this.setEnvironment(url)
})
)
} else {
this.showLoadingHUD(url, isOpenApp)
this.setEnvironment(url)
}
}
}
private setupDeepLinkingHandling = () => {
Linking.getInitialURL()
.then((url) => {
if (url) {
this.onDeepLinkingLaunch(url)
}
})
.catch((error) => {
ErrorReportService.logNonFatalError(error)
})
Linking.addEventListener('url', this.onDeepLinkingLaunch)
}
private initSentry() {
Sentry.init({
dsn: 'https://a7fb0a5844e4a0509273455aaf5c30d6@o4507759802712064.ingest.de.sentry.io/4507759853568080',
tracesSampleRate: 0.1,
profilesSampleRate: 0.1,
enableAppStartTracking: true,
enableNativeFramesTracking: true,
enableStallTracking: true,
enableUserInteractionTracing: true,
enableAutoSessionTracking: true,
autoInitializeNativeSdk: !EnvironmentConsts.isLocal || DeviceConstant.isIOS,
enableLogs: true,
integrations: [Sentry.reactNativeNavigationIntegration({ navigation: Navigation })],
beforeSend: (event: ErrorEvent, _hint: EventHint) => {
if (__DEV__) {
return null
}
let retVal: ErrorEvent | null = null
const shouldAlwaysSend = this.companyId === HIBOB_COMPANY_ID
if (shouldAlwaysSend) {
retVal = event
} else {
retVal = Math.random() > 0.9 ? event : null
}
return retVal
}
})
}
private onStoreUpdate(forceResetRoot?: boolean) {
if (!this.isBridgeInitialled) {
return
}
const storeState = kStore.getState() as BobState
const root: AppRootViewType = storeState.app.root
if (this.firstTime && root !== UNDETERMINED_LOGIN_STATE) {
this.setupDeepLinkingHandling()
this.firstTime = false
}
if (this.currentRoot !== root || forceResetRoot) {
this.currentRoot = root
this.startApp(root)
}
}
private startApp(root: AppRootViewType) {
switch (root) {
case UNDETERMINED_LOGIN_STATE:
this.handleUndeterminedLoginState()
break
case LOGGED_IN_STATE:
this.handleLoggedInState()
break
case NOT_LOGGED_IN_STATE:
this.handleNotLoggedInState()
break
default:
console.error('Unknown app root')
}
}
private handleUndeterminedLoginState() {
const loadingRoot: LayoutRoot = {
root: {
component: {
name: APP_LAUNCHER_LOADER
}
}
}
if (IS_IOS) {
Navigation.setDefaultOptions({
animations: {
setRoot: {
waitForRender: true
}
}
})
}
Navigation.setRoot(loadingRoot)
}
private handleNotLoggedInState() {
this.companyId = null
const loginRoot: LayoutRoot = {
root: {
component: {
name: LOGIN_SCREEN_ID,
options: {
topBar: {
visible: false
}
},
passProps: {
onInitialLoginCompletion: this.onInitialLoginCompletion
}
}
}
}
Navigation.setRoot(loginRoot)
}
private handleLoggedInState() {
initPushNotifications(AppService.onReceivedPushNotification)
const passProps = {
addMethodToCallWhenRefreshDataNeeded: AppService.addMethodToCallWhenRefreshDataNeeded,
removeMethodToCallWhenRefreshDataNeeded: AppService.removeMethodToCallWhenRefreshDataNeeded,
addMethodToCallWhenAppReturnedFromBackground: AppService.addMethodToCallWhenAppReturnedFromBackground,
removeMethodToCallWhenAppReturnedFromBackground: AppService.removeMethodToCallWhenAppReturnedFromBackground,
addMethodToCallOnPushNotificationReceived: AppService.addMethodToCallWhenReceivedPushNotification,
removeMethodToCallOnPushNotificationReceived: AppService.removeMethodToCallWhenReceivedPushNotification
}
const storeState = kStore.getState() as BobState
const permissions = storeState.permissions.permissions
const userDisplayName = UserHelper.getDisplayName(storeState.auth.user)
this.companyId = storeState.auth.user?.companyId
this.theme = UnistylesRuntime.getTheme() as UnistylesTheme
const companyName = storeState.auth.user?.companyName
const companyFeatures = storeState.app.companyFeatures
const shouldShowHomeTab = Boolean(
companyFeatures?.has(HOMEPAGE_WHOSOUT) ||
companyFeatures?.has(HOMEPAGE_NEWJOINERS) ||
companyFeatures?.has(HOMEPAGE_BIRTHDAYS) ||
companyFeatures?.has(HOMEPAGE_ANNIVERSARIES) ||
companyFeatures?.has(HOMEPAGE_SHOUTOUTS)
)
const shouldDisplayPeopleTab = Boolean(
(permissions?.shouldDisplayPeopleDirectory && companyFeatures?.has(DIRECTORY_FEATURE)) ||
(permissions?.shouldDisplayOrgChart && companyFeatures?.has(ORG_CHART_FEATURE))
)
App.savedBottomTabs = App.bottomTabs(
userDisplayName,
companyName,
shouldShowHomeTab,
shouldDisplayPeopleTab,
passProps
)
const state = kStore.getState()
const isStageEnvironment = selectIsStageEnvironment(state)
const mainRoot: LayoutRoot = {
root: {
bottomTabs: {
id: 'BOTTOM_TABS_COMPONENT_ID',
children: App.savedBottomTabs,
options: {
bottomTabs: {
backgroundColor: isStageEnvironment
? (UnistylesRuntime.getTheme() as UnistylesTheme).colors[ColorRole.infoBackground]
: (UnistylesRuntime.getTheme() as UnistylesTheme).colors[ColorRole.backgroundGeneral]
}
}
}
}
}
Navigation.setRoot(mainRoot)
}
private onAppStateChange = (nextAppState: AppStateStatus) => {
if (kAppState) {
if (kAppState === 'background' && nextAppState === 'active') {
kStore.dispatch(renewUserSessionIfNeeded(AppService.onRenewSessionCompletion))
AppService.refreshInstalledEmailApps()
// Check for pending MSAL authentication that may have been interrupted
if (Platform.OS === 'android') {
this.checkPendingMSALAuth()
}
CallKitExtensionService.handleAppForeground(kStore.dispatch).catch((error) => {
ErrorReportService.logNonFatalErrorWithContext(error, 'Failed to handle CallKit foreground state')
})
} else if (kAppState === 'active' && nextAppState === 'inactive') {
kStore.dispatch(performNeededActionsOnAppBackgrounding())
CallKitExtensionService.storeStatusOnBackground().catch((error) => {
ErrorReportService.logNonFatalErrorWithContext(error, 'Failed to store CallKit background state')
})
}
}
kAppState = nextAppState
}
private async checkPendingMSALAuth() {
try {
const { NativeModules } = require('react-native')
if (NativeModules.MSALNativeModule?.checkPendingAuth) {
const recovered = await NativeModules.MSALNativeModule.checkPendingAuth()
if (recovered) {
ErrorReportService.logWithLevel('Successfully recovered pending MSAL authentication', 'info')
}
}
} catch (error) {
// Log the error for diagnostic purposes, but do not interrupt flow
ErrorReportService.logNonFatalErrorWithContext(error, 'Failed to recover pending MSAL authentication')
}
}
// tslint:disable-next-line:no-empty
private onAppInitializationCompletion(_result: RenewSessionCompletionData | null | undefined) {
const tabScreenIds = App.savedBottomTabs.map((tab) => tab.stack?.children?.[0]?.component?.id)
const iconColor = (UnistylesRuntime.getTheme() as UnistylesTheme).colors[ColorRole.bottomTabIconColor]
tabScreenIds.forEach((screenId) => {
Navigation.mergeOptions(screenId as string, {
bottomTab: {
text: (() => {
switch (screenId) {
case HOME_SCREEN_ID:
return translate(TranslationPaths.homeTabTitle)
case PEOPLE_LIST_SCREEN_ID:
return translate(TranslationPaths.peopleTabTitle)
case APP_MENU_TAB_SCREEN:
return translate(TranslationPaths.appMenuTabTitle)
default:
return ''
}
})(),
iconColor: iconColor
}
})
})
}
// tslint:disable-next-line:no-empty
private onInitialLoginCompletion(_result: boolean, _error: Error) {}
public static refreshUserTopBarAndBottomBar() {
const tabScreenIds = App.savedBottomTabs.map((tab) => tab.stack?.children?.[0]?.component?.id)
setNavigationDefaultOptions()
const theme = UnistylesRuntime.getTheme() as UnistylesTheme
const storeState = kStore.getState() as BobState
const companyName = storeState.auth.user?.companyName ?? ''
tabScreenIds.forEach((screenId) => {
const screenOptions = App.getScreenSpecificOptions(screenId, companyName, theme)
const bottomTabOptions = App.getBottomTabOptions(screenId, theme)
Navigation.mergeOptions(screenId as string, {
...screenOptions,
bottomTab: bottomTabOptions
})
})
App.activeComponentIds.forEach((componentId) => {
const screenOptions = App.getScreenSpecificOptions(componentId, companyName, theme)
Navigation.mergeOptions(componentId, {
...screenOptions
})
})
// Update the bottomTabs background color as well
const isStageEnvironment = selectIsStageEnvironment(storeState)
Navigation.mergeOptions('BOTTOM_TABS_COMPONENT_ID', {
bottomTabs: {
backgroundColor: isStageEnvironment
? theme.colors[ColorRole.infoBackground]
: theme.colors[ColorRole.backgroundGeneral]
}
})
}
public static initializeStorybook() {
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({
root: {
stack: {
id: 'storybook-root',
children: [
{
component: {
name: STORYBOOK_SCREEN_ID,
options: {
topBar: {
visible: false
}
}
}
}
]
}
}
})
})
}
}
What was the expected behaviour?
To launch normally without a crash
Was it tested on latest react-native-navigation?
- I have tested this issue on the latest react-native-navigation release and it still reproduces.
Help us reproduce this issue!
No response
In what environment did this happen?
React Native Navigation version: 8.4.2
React Native version: 0.77.3
Has Fabric (React Native's new rendering system) enabled: yes
Node version: 22.11.0
Device model: OnePlus 8T
Android version: 14
gosha212
Metadata
Metadata
Assignees
Labels
platform: Androidtype: accepted/buguser: requires reproductionThis issue requires a reproduction before maintainers can work on itThis issue requires a reproduction before maintainers can work on it