Expo Standalone with Cloud Firestore (web SDK) Responses Take a Really Long time

I’m running into a weird issue only on standalone apps when it comes to the response time for Cloud Firestore issues. Reproduction steps:

  • Launch app, verify auth, and setup an onSnapshot listener that watches the user
  • Change something on the user and it immediately fires an action to re-render with the new data and everything works - yay! :tada:
  • Lock the phone
  • Update something on the user
  • Reopen the phone with the app now foregrounded (still the same running instance of the app)
  • It takes around 30 seconds for the onSnapshot to happen to trigger the re-render and I literally can’t figure it out :sob:

For what it’s worth, it looks like it always take the request a long time to complete, regardless of if the app was launched for the first time or has been running for a while. It’s just when the app is launched for the first time the data is magically there. :thinking_face:

When I run the app with expo start the Firestore requests fire immediately, and they’re super fast. Everything works as expected until I try the standalone app. I’ve tried different SDKs and upgrading Cloud Firestore, but it’s the same issue regardless.

Has anyone dealt with this or have tips on how to debug the standalone app? Since there’s no actual errors, Sentry doesn’t give me anything.

package.json

{
  "name": "its-a-date",
  "version": "0.1.4",
  "private": true,
  "devDependencies": {
    "jest-expo": "^32.0.0",
    "react-test-renderer": "16.3.1",
    "babel-preset-expo": "^5.0.0"
  },
  "main": "./node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "firebase use its-a-date-development && expo start --config ./config/dev-app.json -i",
  },
  "jest": {
    "preset": "jest-expo"
  },
  "dependencies": {
    "@expo/vector-icons": "^9.0.0",
    "@ptomasroos/react-native-multi-slider": "^1.0.0",
    "axios": "^0.18.0",
    "dlv": "^1.1.2",
    "expo": "^32.0.0",
    "firebase": "^5.8.0",
    "geolib": "^2.0.24",
    "lodash": "^4.17.10",
    "moment": "^2.22.2",
    "react": "16.5.0",
    "react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
    "react-native-elements": "^0.19.1",
    "react-native-gifted-chat": "^0.6.0",
    "react-native-popup-dialog": "^0.15.0",
    "react-native-responsive-screen": "^1.1.6",
    "react-native-snap-carousel": "^3.7.2",
    "react-navigation": "^3.0.9",
    "react-redux": "^5.0.7",
    "redux": "^4.0.0",
    "redux-thunk": "^2.3.0",
    "sentry-expo": "^1.9.0",
    "shortid": "^2.2.12",
    "styled-components": "^3.4.0"
  }
}

config

{
  "expo": {
    "sdkVersion": "32.0.0",
    "name": "It's a Date",
    "description": "The dating app that actually involves dates.",
    "icon": "./assets/images/iosAppIcon.png",
    "slug": "its-a-date",
    "version": "1.0.0",
    "orientation": "portrait",
    "splash": {
      "image": "./assets/images/splash.png",
      "resizeMode": "cover"
    },
    "ios": {
      "bundleIdentifier": "xxx"
    },
    "privacy": "public",
    "platforms": ["ios"],
    "scheme": "xxx",
    "hooks": {
      "postPublish": [
        {
          "file": "sentry-expo/upload-sourcemaps",
          "config": {
            "organization": "caldera",
            "project": "its-a-date",
            "authToken": "xxxx"
          }
        }
      ]
    }
  }
}

If you’d like to see the query and stuff I can add that to this issue. Thanks!

For anyone that runs into this issue, this is my very horrible (but functional) workaround. First, create an app state listener on your component like so:

export default class App extends Component {
  state = {
    appState: AppState.currentState
  };

  store = store;

  componentDidMount() {
    AppState.addEventListener('change', this._handleAppStateChange);
    firebase.initializeApp(FIREBASE_DEVELOPMENT_CONFIG);

    firebase.auth().onAuthStateChanged(user => {

      if (!_.isNull(user)) {
        this.user = user;
        return this.store.dispatch(userChangedListener(user));
      }

      return this.store.dispatch(noUser());
    });
  }

  _handleAppStateChange = nextAppState => {
    if (
      this.state.appState.match(/inactive|background/) &&
      nextAppState === 'active'
    ) {
      this.store.dispatch(getUser());
    }
    this.setState({ appState: nextAppState });
  };

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

  render() {
    return (
      <Provider store={this.store}>
        <ThemeProvider theme={theme}>
          <TopLevelNavigator />
        </ThemeProvider>
      </Provider>
    );
  }
}

Then spin up a cloud function or express instance that gets the query you are looking for once.
index.ts

...
import { getUser } from './getUser';

app.get('/get-user', getUser);

getUser.ts (server side)

import { db } from './utils/store';

export const getUser = async (req, res) => {
  const { user_id } = req.user;

  const userRef = db.collection('users').doc(user_id);
  const userQuery = await userRef.get();
  const user = userQuery.data();

  res.send(user);
};

Then you need to call that in a Redux action creator

export const getUser = user => async (dispatch, getState) => {
  dispatch({
    type: SHOW_LOADING,
    loading: true
  });

  try {
    let token = await firebase
      .auth()
      .currentUser.getIdToken(/* forceRefresh */ true);

    try {
      const config = {
        headers: {
          Authorization: `Bearer ${token}`,
          Accept: 'application/json'
        }
      };

      let response = await axios.get(`${FIREBASE_API_URL}/get-user`, config);

      dispatch({
        type: USER_CHANGED_LISTENER,
        userLoggedIn: true,
        payload: response.data
      });

      dispatch({
        type: HIDE_LOADING,
        loading: false
      });
    } catch (error) {
      dispatch(
        createErrorAction(CLOUD_FUNCTIONS_ERROR, error.response, 'high')
      );
    }
  } catch (error) {
    dispatch(createErrorAction(UNKNOWN_ERROR, error, 'high'));
  }
};

Dispatch to the same reducer so that way when someone opens your app the state is immediately re-rendering from the one time Express request. Then, about 30 seconds later your onSnapshot listener will start working again! :upside_down_face:

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.