我有一个 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 组件吗?
事实证明,问题是我的承诺设置未正确返回,返回不隐藏 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,
}
});