Dreaded SplashScreen Flicker on Android - UGH

Please provide the following:

  1. SDK Version: 35
  2. Platforms(Android/iOS/web/all):Android

I’m using the SplashScreen.preventAutoHide() and the SplashScreen.hide() so that I can load my assets as well has have an animated type splash screen for the user. As per the other countless threads I’ve read, iOS works as it should, both on the emulator/device expo apps and on the standalone app. But on android …

  1. emulator/device w/ expo - on every other load, there’s a white flicker between the splash screen and the actual app.
  2. on the standalone app - every load shows the white flicker but the flicker isn’t that long.

Here’s my source code for app.js

import React from 'react'
import { AppState, StatusBar, Platform } from 'react-native'
import { SplashScreen } from 'expo'
import { Block, GalioProvider } from 'galio-framework'
import { PersistGate } from 'redux-persist/integration/react'
import AppContainer from './src/navigation/Screens'
import { argonTheme } from './src/constants'
import { store, persistor } from './src/store/store'
import { Provider } from 'react-redux'
import { ApolloProvider } from 'react-apollo'
import { AppearanceProvider } from 'react-native-appearance'

import client from './src/data/apolloClient'
import {
  setTopLevelNavigator,
  navigate,
} from './src/navigation/NavigationService'
import {
  listenForNotifications,
  setUpChannel,
  checkPermissions,
} from './src/data/notifications'
import AppLoader from './src/components/AppLoader'
import { setNotificationPermission } from './src/store/slices/deviceInfoSlice'

StatusBar.setBarStyle('dark-content')

if (Platform.OS === 'android') {
  StatusBar.setTranslucent(true)
  StatusBar.setBackgroundColor('transparent')
}

class App extends React.Component {
  constructor(props) {
    super(props)
    SplashScreen.preventAutoHide()
  }
  state = {
    appState: AppState.currentState,
  }

  componentDidMount() {
    listenForNotifications(this.newPostNavigation)
    setUpChannel()
    AppState.addEventListener('change', this._handleAppStateChange)
  }

  componentWillUnmount() {
    AppState.removeEventListener('change', this._handleAppStateChange)
  }

  newPostNavigation = (id, location) => {
    navigate(location, { id })
  }

  checkNotificationStatus = async () => {
    checkPermissions().then((results) => {
      const reduxState = store.getState()
      const { notificationPermission } = reduxState.deviceInfo
      if (notificationPermission !== results) {
        store.dispatch(setNotificationPermission(results))
      }
    })
  }

  // Sets a top level navigator to access navigation in root component
  navigateWithUid = (navigatorRef) => {
    setTopLevelNavigator(navigatorRef)
    const reduxState = store.getState()

    // Comment this conditional to disable auto navigation from onboarding screens
    if (reduxState.deviceInfo && !reduxState.deviceInfo.deviceUid) {
      navigate('OnBoarding', {})
    }
  }

  //if application is in the background and user changes notification settings in their phone settings,
  //this will catch the change and update the app
  _handleAppStateChange = (nextAppState) => {
    if (
      this.state.appState.match(/inactive|background/) &&
      nextAppState === 'active'
    ) {
      this.checkNotificationStatus()
    }
    this.setState({ appState: nextAppState })
  }

  render() {
    return (
      <ApolloProvider client={client}>
        <Provider store={store}>
          <PersistGate loading={null} persistor={persistor}>
            <GalioProvider theme={argonTheme}>
              <AppLoader>
                <AppearanceProvider>
                  <Block flex>
                    <AppContainer
                      ref={(navigatorRef) => this.navigateWithUid(navigatorRef)}
                    />
                  </Block>
                </AppearanceProvider>
              </AppLoader>
            </GalioProvider>
          </PersistGate>
        </Provider>
      </ApolloProvider>
    )
  }
}

export default App

and here’s my source code for AppLoader that does the caching

import React, { Component } from 'react'
import { SplashScreen, AppLoading } from 'expo'
import { Asset } from 'expo-asset'
import {
  Animated,
  Platform,
  StatusBar,
  StyleSheet,
  View,
  Image,
} from 'react-native'
import * as Font from 'expo-font'
import { Images, Gifs } from '../constants'

const assetImages = [
  Images.GoAnnualHorizontal,
  Images.LogoBlueStacked,
  Images.splashWhite,
]

const fonts = [
  { 'open-sans-regular': require('../../assets/font/OpenSans-Regular.ttf') },
  { 'open-sans-light': require('../../assets/font/OpenSans-Light.ttf') },
  { 'open-sans-bold': require('../../assets/font/OpenSans-Bold.ttf') },
]

function cacheFonts(fonts) {
  return fonts.map((font) => Font.loadAsync(font))
}

function cacheImages(images) {
  return images.map((image) => {
    if (typeof image === 'string') {
      return Image.prefetch(image)
    } else {
      return Asset.fromModule(image).downloadAsync()
    }
  })
}

export default class AppLoader extends Component {
  constructor(props) {
    super(props)
  }

  state = {
    isReady: false,
    splashAnimation: new Animated.Value(0),
    splashAnimationComplete: false,
  }

  componentDidMount() {
    this._loadResourcesAsync()
      .then(() => this.setState({ isReady: true }))
      .catch((error) =>
        console.error(`Unexpected error thrown when loading: ${error}`)
      )
  }

  _loadResourcesAsync = async () => {
    const imageAssets = cacheImages([...assetImages])
    const fontAssets = cacheFonts([...fonts])
    return await Promise.all([...fontAssets, ...imageAssets])
  }

  render() {
    const { isReady } = this.state

    if (!isReady)
      return (
        <View style={{ flex: 1 }}>
          <Image
            source={require('../../assets/splash.png')}
            style={{
              width: undefined,
              height: undefined,
              position: 'absolute',
              top: 0,
              left: 0,
              bottom: 0,
              right: 0,
              resizeMode: 'cover',
            }}
            fadeDuration={0}
          />
        </View>
      )

    return (
      <View style={styles.container}>
        {Platform.OS === 'ios' && <StatusBar barStyle='default' />}
        {this.props.children}
        {this._maybeRenderLoadingImage()}
      </View>
    )
  }
  _maybeRenderLoadingImage = () => {
    if (this.state.splashAnimationComplete) {
      return null
    }

    return (
      <Animated.View
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          bottom: 0,
          right: 0,
          alignItems: 'center',
          justifyContent: 'center',
          backgroundColor: '#fff',
          opacity: this.state.splashAnimation.interpolate({
            inputRange: [0, 1],
            outputRange: [1, 0],
          }),
        }}
      >
        <Animated.Image
          source={require('../../assets/splash.png')}
          style={{
            width: undefined,
            height: undefined,
            position: 'absolute',
            top: 0,
            left: 0,
            bottom: 0,
            right: 0,
            resizeMode: 'cover',
          }}
          onLoadEnd={this._animateOut}
          fadeDuration={0}
        />
      </Animated.View>
    )
  }

  _animateOut = () => {
    SplashScreen.hide()
    Animated.timing(this.state.splashAnimation, {
      toValue: 1,
      delay: 100,
      duration: 500,
      useNativeDriver: true,
    }).start(() => {
      this.setState({ splashAnimationComplete: true })
    })
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
})

Any help would be greatly appreciated!

Hey @jacobjohn90 did you ever get to the bottom of this?

My very simple app where I am not doing any preloading and not calling SplashScreen.preventAutoHide() or SplashScreen.hide() suddenly started doing this right after I upgraded from SDK 35 to SDK 36.

There was no flashing on SDK 35, but on SDK 36 there are one or two flashes when loading the standalone app on an Android phone.

I also have the flash and it’s quite ugly, I have not found a way to remove it