Expo AppLoading 在 TestFlight 中加载应用程序时立即隐藏 SplashScreen

问题描述 投票:0回答:1

我有一个 Expo Managed React Native 应用程序。我有一些在主应用程序屏幕渲染之前调用的异步函数。它在开发模式、生产开发模式和 Expo Go 上运行得很好,但在 Test Flight 上,应用程序立即隐藏了启动屏幕。

总而言之,我有一个状态钩子,当 AppLoading 异步方法完成时,它会发生变化。在此异步期间,我更新了 Context API 以包含从 SQLite 数据库获取的数据。这是我的代码:

import React, { useContext, useEffect, useState } from 'react';
import AppLoading from 'expo-app-loading';
import {
  StyleSheet,
  FlatList,
  View,
  Text,
} from 'react-native';
import { Icon } from 'react-native-elements';
import Card from '../components/Card';
import Colors from '../constants/Colors';
import DatabaseService from '../services/database.service';

import { AppContext } from '../contexts/appContext';

export default () => {
  /**
   * Vars
   */
  const databaseService = DatabaseService.getService();

  /**
   * State
   */
  const [appInitialized, setAppInitialized] = useState(false);
  const [appInitializedFail, setAppInitializedFail] = useState(false);

  /**
   * Contexts
   */
  const { cardsArray, dispatchEvent } = useContext(AppContext);

  /**
   * On mount initialize the database and query for all cards
   */
  useEffect(() => {
    initialize()
      .then(didInitialize => {
        if (didInitialize) {
          setAppInitialized(true);
        } else {
          setAppInitialized(true);
          setAppInitializedFail(true);
        }
      })
      .catch(err => {
        setAppInitialized(true);
        setAppInitializedFail(true);

        console.error(err);
      });
  }, []);

  /**
   * Prepare the app by loading async data
   *
   * @returns {Promise}
   */
  const initialize = async () => {
    const _initialTime = new Date();

    return databaseService.init()
      .then(async database => {
        return getAllCards(database)
          .then(async createdData => {
            const [createdArray, createdMap] = createdData;

            await dispatchEvent('UPDATE_CARDS_MAP', createdMap);
            await dispatchEvent('UPDATE_CARDS_ARRAY', createdArray);

            console.debug(
              `\nFetched all ${createdArray.length} results in ${(new Date() - _initialTime) / 1000}s.`
            );

            return true;
          })
          .catch(err => {
            console.error(err);

            return false;
          });
      })
      .catch(err => {
        console.error(err);

        return false;
      });
  };

  /**
   * Query the database for all cards
   *
   * @param {Class} database Service to utilize our database queries on
   * @returns {Promise}
   */
  const getAllCards = (database) => {
    return new Promise((resolve, reject) => {
      database.db.transaction(
        (tx) => {
          tx.executeSql(
            `SELECT cards.* FROM cards ORDER BY RANDOM()`,
            [],
            (_, res) => {
              const cards = createCardsArray(res.rows._array);

              resolve(cards);
            },
            (_, err) => {
              console.error(err);

              reject(err);
            }
          );
        },
        (err) => {
          console.error(err);

          reject(err);
        }
      );
    });
  };

  /**
   * Create cards array based on a set of cards passed through
   *
   * @param {Object} cards Raw cards array before we manipulate the data
   * @returns {Array}
   */
  const createCardsArray = (cards) => {
    const createdMap = {};
    const createdArray = cards.map(card => {
      const { uuid, otherFaceIds, scryfallId, layout, side } = card;
      const url = `https://api.scryfall.com/cards/${scryfallId}?format=image&version=`;

      let isTransform = undefined;
      let isFlip = undefined;
      let isMeld = undefined;
      let isSplit = undefined;
      let isDual = undefined;
      let imageUrl = `${url}normal`;
      let cropUrl = `${url}art_crop`;
      let thumbUrl = `${url}small`;

      if (otherFaceIds) {
        isDual = true;

        // Cards with two faces
        if (layout.includes('transform') || layout.includes('modal_dfc')) {
          isTransform = true;

          if (side === 'b') {
            imageUrl = imageUrl + `&face=back`;
            cropUrl = cropUrl + `&face=back`;
          }
          // Cards with two sets of data but one side of a card
        } else if (layout.includes('flip')) {
          isFlip = true;
        } else if (layout.includes('meld')) {
          isMeld = true;
        } else if (layout.includes('split')) {
          isSplit = true;
        }
      }

      const newCard = {
        ...card,
        imageUrl,
        cropUrl,
        thumbUrl,
        isDual,
        isTransform,
        isSplit,
        isFlip,
        isMeld,
      };

      createdMap[uuid] = newCard;

      return newCard;
    });

    return [createdArray, createdMap];
  };

  /**
   * Render the loading view
   *
   * @returns {JSX}
   */
  if (!appInitialized) {
    return (
      <AppLoading />
    );
  }

  /**
   * Render the error view
   *
   * @returns {JSX}
   */
  if (appInitializedFail) {
    return (
      <View style={styles.screenView}>
        <Icon color={Colors.redColor} size={40} name="warning-outline" type="ionicon" />
        <Text style={styles.errorText}>The database failed to load!</Text>
        <Text style={[styles.errorText, styles.errorTextSmall]}>You can try restarting the application, restarting your device or reinstalling the application to resolve this issue.</Text>
      </View>
    );
  }

  /**
   * Render the successful view
   *
   * @returns {JSX}
   */
  return (
    <View style={styles.screenView}>
      <FlatList
        data={cardsArray}
        renderItem={({ item: card }) => <Card
          card={card}
          hideFavoriteButton={false}
        />}
        keyExtractor={item => item.id}
      />
    </View>
  );
};

/**
 * Styles
 */
const styles = StyleSheet.create({
  screenView: {
    ...StyleSheet.absoluteFill,
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: Colors.darkColor,
    paddingLeft: 20,
    paddingRight: 20,
  },
  errorText: {
    textAlign: 'center',
    color: Colors.redColor,
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  errorTextSmall: {
    fontSize: 16,
    color: Colors.lightColor,
  }
});

任何人都可以破译这里发生了什么导致 TestFlight 不尊重 AppLoading 组件吗?

react-native expo
1个回答
0
投票

事实证明,问题是我的承诺设置未正确返回,返回不隐藏 AppLoading 组件的承诺的正确方法是使用 Expo Splash Screen 将我的代码更改为此,并像这样更改我的承诺:

import React, { useContext, useEffect, useState } from 'react';
import * as SplashScreen from 'expo-splash-screen';
import {
  StyleSheet,
  FlatList,
  View,
  Text,
} from 'react-native';
import { Icon } from 'react-native-elements';
import * as Analytics from 'expo-firebase-analytics';
import Card from '../components/Card';
import Colors from '../constants/Colors';
import DatabaseService from '../services/database.service';

import { AppContext } from '../contexts/appContext';

SplashScreen.preventAutoHideAsync();

export default () => {
  /**
   * Vars
   */
  const databaseService = DatabaseService.getService();

  /**
   * State
   */
  const [appInitializedFail, setAppInitializedFail] = useState(false);
  const [errorHasBeenSet, setErrorHasBeenSet] = useState(false);
  const [errorCode, setErrorCode] = useState(null);

  /**
   * Contexts
   */
  const { cardsArray, dispatchEvent } = useContext(AppContext);

  /**
   * On mount initialize the database and query for all cards
   */
  useEffect(() => {
    initialize()
      .then(didInitialize => {
        if (!!didInitialize) {
          SplashScreen.hideAsync();
        } else {
          setErrorViewState('Error: App initialized with no data.');
        }
      })
      .catch(err => {
        setErrorViewState('Error: Failed to initialize application.');

        console.error(err);
      });
  }, []);

  /**
   * Prepare the app by loading async data
   *
   * @returns {Promise}
   */
  const initialize = async () => {
    const _initialTime = new Date();

    return databaseService.init(setErrorViewState)
      .then(async database => {
        return await getAllCards(database)
          .then(results => {
            if(results){
              const [createdArray, createdMap] = results;

              dispatchEvent('UPDATE_CARDS_MAP', createdMap);
              dispatchEvent('UPDATE_CARDS_ARRAY', createdArray);

              console.debug(
                `\nFetched all ${createdArray.length} results in ${(new Date() - _initialTime) / 1000}s.`
              );

              return true;
            } else {
              setErrorViewState('Error: No database data.');

              return false;
            }
          })
          .catch(err => {
            setErrorViewState('Error: Failed to get database data.');

            return err;
          });
      })
      .catch(err => {
        setErrorViewState('Error: Failed to initialize the database.');

        return err;
      });
  };

  /**
   * Query the database for all cards
   *
   * @param {Class} database Service to utilize our database queries on
   * @returns {Promise}
   */
  const getAllCards = (database) => {
    return new Promise(async (resolve, reject) => {
      return await database.db.transaction(
        (tx) => {
          tx.executeSql(
            `SELECT cards.* FROM cards ORDER BY RANDOM()`,
            [],
            (_, res) => {
              const cards = createCardsArray(res.rows._array);

              resolve(cards);
            },
            (_, err) => {
              setErrorViewState('Error: Failed to execute query.');

              reject(err);
            }
          );
        },
        (err) => {
          setErrorViewState('Error: Failed to execute transaction.');

          reject(err);
        }
      );
    });
  };

  /**
   * Create cards array based on a set of cards passed through
   *
   * @param {Object} cards Raw cards array before we manipulate the data
   * @returns {Array}
   */
  const createCardsArray = (cards) => {
    const createdMap = {};
    const createdArray = cards
      .map(card => {
        const { uuid, otherFaceIds, scryfallId, layout, side } = card;
        const url = `https://api.scryfall.com/cards/${scryfallId}?format=image&version=`;

        let isTransform = undefined;
        let isFlip = undefined;
        let isMeld = undefined;
        let isSplit = undefined;
        let isDual = undefined;
        let imageUrl = `${url}normal`;
        let cropUrl = `${url}art_crop`;
        let thumbUrl = `${url}small`;

        if (otherFaceIds) {
          isDual = true;

          // Cards with two faces
          if (layout.includes('transform') || layout.includes('modal_dfc')) {
            isTransform = true;

            if (side === 'b') {
              imageUrl = imageUrl + `&face=back`;
              cropUrl = cropUrl + `&face=back`;
            }
            // Cards with two sets of data but one side of a card
          } else if (layout.includes('flip')) {
            isFlip = true;
          } else if (layout.includes('meld')) {
            isMeld = true;
          } else if (layout.includes('split')) {
            isSplit = true;
          }
        }

        const newCard = {
          ...card,
          imageUrl,
          cropUrl,
          thumbUrl,
          isDual,
          isTransform,
          isSplit,
          isFlip,
          isMeld,
        };

        createdMap[uuid] = newCard;

        return newCard;
      })
      // Filter out other sides so we can see the main face first and swap them later
      .filter(card => card.side === 'a' || !card.side);

    return [createdArray, createdMap];
  };

  /**
   * Set state to show our error message
   */
  const setErrorViewState = (errorCode = null) => {
    // Ensure we establish the first caught error
    if(!errorHasBeenSet){
      setErrorHasBeenSet(true);
      setAppInitializedFail(true);
      setErrorCode(errorCode);

      SplashScreen.hideAsync();

      Analytics.logEvent('app_failed_initialization', {
        contentType: 'text',
        itemId: errorCode || 'Error: Unknown',
        method: 'app load'
      });
    }
  }

  /**
   * Render the error view
   *
   * @returns {JSX}
   */
  if (appInitializedFail) {
    return (
      <View style={styles.screenView}>
        <Icon color={Colors.redColor} size={40} name="warning-outline" type="ionicon" />
        <Text style={styles.errorText}>There was a problem</Text>
        {errorCode && <Text style={[styles.errorText, styles.errorCode]}>{errorCode}</Text>}
        <Text style={[styles.errorText, styles.errorTextSmall]}>You can try restarting the application, restarting your device and reinstalling the application to resolve this issue.</Text>
        <Text style={[styles.errorText, styles.errorTextSmall]}>If that does not work, please take a screenshot and report this to the developer.</Text>
      </View>
    );
  }

  /**
   * Render the successful view
   *
   * @returns {JSX}
   */
  return (
    <View style={styles.screenView}>
      <FlatList
        data={cardsArray}
        keyExtractor={item => item.id}
        showsVerticalScrollIndicator={false}
        renderItem={({ item: card }) => <Card
          card={card}
          hideFavoriteButton={false}
        />}
      />
    </View>
  );
};

/**
 * Styles
 */
const styles = StyleSheet.create({
  screenView: {
    ...StyleSheet.absoluteFill,
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: Colors.darkColor,
  },
  errorText: {
    textAlign: 'center',
    color: Colors.redColor,
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  errorCode: {
    fontSize: 18,
  },
  errorTextSmall: {
    fontSize: 16,
    color: Colors.lightColor,
  }
});
© www.soinside.com 2019 - 2024. All rights reserved.