Expo Cheatsheet¶
Expo - Der schnellste Weg zum Aufbau von React Native Apps
Expo ist eine Open-Source-Plattform für die Herstellung von universellen nativen Apps für Android, iOS und das Web mit JavaScript und React. Es bietet eine Reihe von Werkzeugen und Dienstleistungen rund um React Native und native Plattformen gebaut. < p>
Inhaltsverzeichnis¶
- [Installation](#installation
- (#getting-started)
- [Projektstruktur](#project-structure_
- [Expo CLI](LINK_3__
- (#development-workflow_)
- Core Components
- [Navigation](LINK_6__
- [State Management](#state-management
- [Styling](LINK_8__
- APIs und Services
- (#push-notifications_)
- [Dateisystem](LINK_11_
- [Kamera und Medien](#camera-and-media
- (#location-services_)
- [Autorisierung](LINK_14__
- (#building-and-publishing_)
- [EAS (Expo Application Services)](#eas-expo-application-services
- Beste Praktiken
Installation¶
Voraussetzungen¶
# Install Node.js (version 16 or later)
# Download from nodejs.org
# Verify Node.js installation
node --version
npm --version
# Install Yarn (optional but recommended)
npm install -g yarn
```_
### Expo CLI Installation
```bash
# Install Expo CLI globally
npm install -g @expo/cli
# Or with Yarn
yarn global add @expo/cli
# Verify installation
expo --version
# Login to Expo account (optional)
expo login
# Check current user
expo whoami
```_
### Expo Go App
```bash
# Download Expo Go from app stores:
# iOS: App Store - search "Expo Go"
# Android: Google Play Store - search "Expo Go"
# Alternative: Use development build
# For custom native code or third-party libraries
```_
## Erste Schritte
### Neues Projekt erstellen
```bash
# Create new Expo project
expo init MyApp
# Choose template:
# - blank: minimal app with just the essentials
# - blank (TypeScript): minimal app with TypeScript
# - tabs (TypeScript): several example screens and tabs
# - minimal: bare minimum files
# Navigate to project directory
cd MyApp
# Start development server
expo start
# Alternative: Create with specific template
expo init MyApp --template blank
expo init MyApp --template blank-typescript
expo init MyApp --template tabs
```_
### Projektvorlagen
```bash
# Blank template
expo init MyApp --template blank
# TypeScript template
expo init MyApp --template blank-typescript
# Navigation template
expo init MyApp --template tabs
# Bare workflow (advanced)
expo init MyApp --template bare-minimum
# Create with npm
npx create-expo-app MyApp
# Create with specific SDK version
expo init MyApp --sdk-version 49.0.0
```_
## Projektstruktur
### Grundstruktur
### Beispiel
```javascript
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
```_
### App.json Konfiguration
```json
{
"expo": {
"name": "MyApp",
"slug": "myapp",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.yourcompany.myapp"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
},
"package": "com.yourcompany.myapp"
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
```_
## Risikopositionen
### Entwicklungskommandos
```bash
# Start development server
expo start
# Start with specific options
expo start --clear # Clear cache
expo start --offline # Work offline
expo start --tunnel # Use tunnel connection
expo start --lan # Use LAN connection
expo start --localhost # Use localhost
# Platform-specific starts
expo start --ios # Open iOS simulator
expo start --android # Open Android emulator
expo start --web # Open web browser
# Install dependencies
expo install package-name
# Install specific version
expo install package-name@version
# Install multiple packages
expo install package1 package2 package3
```_
### Projektleitung
```bash
# Check project status
expo doctor
# Upgrade Expo SDK
expo upgrade
# Eject from managed workflow (deprecated)
expo eject
# Prebuild (generate native code)
expo prebuild
# Clean project
expo start --clear
# Run on device
expo start --tunnel
# Generate app icons and splash screens
expo install expo-splash-screen
expo install @expo/vector-icons
```_
### Befehle erstellen
```bash
# Build for iOS (legacy)
expo build:ios
# Build for Android (legacy)
expo build:android
# Build for web
expo build:web
# Check build status
expo build:status
# Download build
expo build:download
# Modern EAS Build
eas build --platform ios
eas build --platform android
eas build --platform all
```_
## Entwicklungs-Workflow
### Laufen auf Gerät
```bash
# Method 1: Expo Go app
# 1. Install Expo Go on your device
# 2. Run: expo start
# 3. Scan QR code with Expo Go (Android) or Camera (iOS)
# Method 2: Development build
# 1. Create development build: eas build --profile development
# 2. Install build on device
# 3. Run: expo start --dev-client
# Method 3: Simulator/Emulator
expo start --ios # iOS Simulator
expo start --android # Android Emulator
```_
### Hot Reloading
```javascript
// Automatic hot reloading is enabled by default
// Changes to JavaScript files trigger automatic reload
// Disable hot reloading in app.json
{
"expo": {
"packagerOpts": {
"config": "metro.config.js"
}
}
}
// Manual reload
// Shake device or press Cmd+R (iOS) / Cmd+M (Android)
```_
### Debugging
```bash
# Enable remote debugging
# Shake device > Debug Remote JS
# Use Flipper for debugging
npm install --save-dev react-native-flipper
# Debug with VS Code
# Install React Native Tools extension
# Console logging
console.log('Debug message');
console.warn('Warning message');
console.error('Error message');
# React Native Debugger
# Download from GitHub releases
# Start with: expo start
# Open React Native Debugger
```_
## Kernkomponenten
### Grundkomponenten
```javascript
import React from 'react';
import {
View,
Text,
Image,
ScrollView,
TouchableOpacity,
TextInput,
StyleSheet
} from 'react-native';
const BasicComponents = () => {
const [text, setText] = React.useState('');
return (
<ScrollView style={styles.container}>
<View style={styles.section}>
<Text style={styles.title}>Hello, Expo!</Text>
<Image
source={{ uri: 'https://example.com/image.jpg' }}
style={styles.image}
/>
<TextInput
style={styles.input}
placeholder="Enter text here"
value={text}
onChangeText={setText}
/>
<TouchableOpacity
style={styles.button}
onPress={() => alert('Button pressed!')}
>
<Text style={styles.buttonText}>Press Me</Text>
</TouchableOpacity>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
section: {
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
image: {
width: 200,
height: 200,
marginBottom: 20,
},
input: {
borderWidth: 1,
borderColor: '#ccc',
padding: 10,
marginBottom: 20,
borderRadius: 5,
},
button: {
backgroundColor: '#007AFF',
padding: 15,
borderRadius: 5,
alignItems: 'center',
},
buttonText: {
color: 'white',
fontWeight: 'bold',
},
});
export default BasicComponents;
```_
### Listen und Daten
```javascript
import React from 'react';
import {
FlatList,
SectionList,
Text,
View,
StyleSheet
} from 'react-native';
const ListExamples = () => {
const data = [
{ id: '1', title: 'Item 1' },
{ id: '2', title: 'Item 2' },
{ id: '3', title: 'Item 3' },
];
const sectionData = [
{
title: 'Section 1',
data: ['Item 1', 'Item 2'],
},
{
title: 'Section 2',
data: ['Item 3', 'Item 4'],
},
];
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text>{item.title}</Text>
</View>
);
const renderSectionItem = ({ item }) => (
<View style={styles.item}>
<Text>{item}</Text>
</View>
);
const renderSectionHeader = ({ section: { title } }) => (
<View style={styles.header}>
<Text style={styles.headerText}>{title}</Text>
</View>
);
return (
<View style={styles.container}>
<Text style={styles.title}>FlatList Example</Text>
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
style={styles.list}
/>
<Text style={styles.title}>SectionList Example</Text>
<SectionList
sections={sectionData}
renderItem={renderSectionItem}
renderSectionHeader={renderSectionHeader}
keyExtractor={(item, index) => item + index}
style={styles.list}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginVertical: 10,
},
list: {
maxHeight: 200,
},
item: {
padding: 10,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
},
header: {
backgroundColor: '#f0f0f0',
padding: 10,
},
headerText: {
fontWeight: 'bold',
},
});
export default ListExamples;
```_
## Navigation
### React Navigation Setup
```bash
# Install React Navigation
npm install @react-navigation/native
# Install dependencies for Expo
expo install react-native-screens react-native-safe-area-context
# Install navigators
npm install @react-navigation/stack
npm install @react-navigation/bottom-tabs
npm install @react-navigation/drawer
```_
### Web-Screen-Navigation
```javascript
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { View, Text, Button } from 'react-native';
const Stack = createStackNavigator();
const HomeScreen = ({ navigation }) => (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => navigation.navigate('Details', { itemId: 86 })}
/>
</View>
);
const DetailsScreen = ({ route, navigation }) => {
const { itemId } = route.params;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Text>Item ID: {itemId}</Text>
<Button
title="Go back"
onPress={() => navigation.goBack()}
/>
</View>
);
};
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'My Home' }}
/>
<Stack.Screen
name="Details"
component={DetailsScreen}
options={({ route }) => ({ title: `Item ${route.params.itemId}` })}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
```_
### Tab Navigator
```javascript
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';
const Tab = createBottomTabNavigator();
const HomeScreen = () => (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Home!</Text>
</View>
);
const SettingsScreen = () => (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Settings!</Text>
</View>
);
const TabNavigator = () => {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === 'Home') {
iconName = focused ? 'home' : 'home-outline';
} else if (route.name === 'Settings') {
iconName = focused ? 'settings' : 'settings-outline';
}
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: 'tomato',
tabBarInactiveTintColor: 'gray',
})}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
);
};
export default TabNavigator;
```_
## Staatliche Verwaltung
### React Hooks
```javascript
import React, { useState, useEffect, useContext, useReducer } from 'react';
// useState Hook
const CounterComponent = () => {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
return (
<View>
<Text>Count: {count}</Text>
<Button title="+" onPress={() => setCount(count + 1)} />
<Button title="-" onPress={() => setCount(count - 1)} />
<TextInput
value={name}
onChangeText={setName}
placeholder="Enter name"
/>
</View>
);
};
// useEffect Hook
const DataComponent = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
};
if (loading) {
return <Text>Loading...</Text>;
}
return (
<View>
<Text>{JSON.stringify(data)}</Text>
</View>
);
};
// useContext Hook
const ThemeContext = React.createContext();
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
const ThemedComponent = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<View style={{ backgroundColor: theme === 'light' ? '#fff' : '#333' }}>
<Text style={{ color: theme === 'light' ? '#000' : '#fff' }}>
Current theme: {theme}
</Text>
<Button title="Toggle Theme" onPress={toggleTheme} />
</View>
);
};
```_
### Redux Setup
```bash
# Install Redux
npm install @reduxjs/toolkit react-redux
# Install Redux DevTools (optional)
npm install redux-devtools-extension
```_
```javascript
// store.js
import { configureStore, createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
});
// App.js
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store';
import Counter from './Counter';
const App = () => {
return (
<Provider store={store}>
<Counter />
</Provider>
);
};
// Counter.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from './store';
const Counter = () => {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<View>
<Text>Count: {count}</Text>
<Button title="+" onPress={() => dispatch(increment())} />
<Button title="-" onPress={() => dispatch(decrement())} />
<Button title="+5" onPress={() => dispatch(incrementByAmount(5))} />
</View>
);
};
```_
## Styling
### StyleSheet
```javascript
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const StyledComponent = () => {
return (
<View style={styles.container}>
<Text style={styles.title}>Styled Text</Text>
<View style={styles.box}>
<Text style={styles.boxText}>Box Content</Text>
</View>
<View style={[styles.box, styles.redBox]}>
<Text style={styles.boxText}>Red Box</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
marginBottom: 20,
textAlign: 'center',
},
box: {
backgroundColor: '#007AFF',
padding: 20,
marginVertical: 10,
borderRadius: 10,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
redBox: {
backgroundColor: '#FF3B30',
},
boxText: {
color: 'white',
fontSize: 16,
textAlign: 'center',
},
});
export default StyledComponent;
```_
### Responsive Design
```javascript
import React from 'react';
import { View, Text, Dimensions, StyleSheet } from 'react-native';
const { width, height } = Dimensions.get('window');
const ResponsiveComponent = () => {
return (
<View style={styles.container}>
<View style={styles.responsiveBox}>
<Text>Responsive Box</Text>
</View>
<View style={styles.flexContainer}>
<View style={styles.flexItem}>
<Text>Item 1</Text>
</View>
<View style={styles.flexItem}>
<Text>Item 2</Text>
</View>
<View style={styles.flexItem}>
<Text>Item 3</Text>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
responsiveBox: {
width: width * 0.8,
height: height * 0.2,
backgroundColor: '#007AFF',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 20,
},
flexContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
flexItem: {
flex: 1,
backgroundColor: '#34C759',
margin: 5,
padding: 20,
alignItems: 'center',
},
});
export default ResponsiveComponent;
```_
### Styled Components (Alternativ)
```bash
# Install styled-components
npm install styled-components
```_
```javascript
import React from 'react';
import styled from 'styled-components/native';
const Container = styled.View`
flex: 1;
padding: 20px;
background-color: #f5f5f5;
`;
const Title = styled.Text`
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 20px;
text-align: center;
`;
const Button = styled.TouchableOpacity`
background-color: ${props => props.primary ? '#007AFF' : '#34C759'};
padding: 15px;
border-radius: 10px;
margin-vertical: 10px;
`;
const ButtonText = styled.Text`
color: white;
text-align: center;
font-weight: bold;
`;
const StyledComponentExample = () => {
return (
<Container>
<Title>Styled Components</Title>
<Button primary onPress={() => alert('Primary button')}>
<ButtonText>Primary Button</ButtonText>
</Button>
<Button onPress={() => alert('Secondary button')}>
<ButtonText>Secondary Button</ButtonText>
</Button>
</Container>
);
};
export default StyledComponentExample;
```_
## APIs und Services
### Expo APIs
```javascript
import * as Device from 'expo-device';
import * as Battery from 'expo-battery';
import * as Network from 'expo-network';
import * as Application from 'expo-application';
const DeviceInfo = () => {
const [deviceInfo, setDeviceInfo] = useState({});
useEffect(() => {
getDeviceInfo();
}, []);
const getDeviceInfo = async () => {
const info = {
deviceName: Device.deviceName,
deviceType: Device.deviceType,
osName: Device.osName,
osVersion: Device.osVersion,
batteryLevel: await Battery.getBatteryLevelAsync(),
networkState: await Network.getNetworkStateAsync(),
appVersion: Application.nativeApplicationVersion,
};
setDeviceInfo(info);
};
return (
<View>
<Text>Device: {deviceInfo.deviceName}</Text>
<Text>OS: {deviceInfo.osName} {deviceInfo.osVersion}</Text>
<Text>Battery: {Math.round(deviceInfo.batteryLevel * 100)}%</Text>
<Text>Network: {deviceInfo.networkState?.type}</Text>
<Text>App Version: {deviceInfo.appVersion}</Text>
</View>
);
};
```_
### HTTP-Anfragen
```javascript
import React, { useState, useEffect } from 'react';
// Using fetch
const FetchExample = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
const postData = async (newPost) => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newPost),
});
const result = await response.json();
console.log('Posted:', result);
} catch (err) {
console.error('Error posting data:', err);
}
};
if (loading) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error}</Text>;
return (
<FlatList
data={data}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<View style={{ padding: 10 }}>
<Text style={{ fontWeight: 'bold' }}>{item.title}</Text>
<Text>{item.body}</Text>
</View>
)}
/>
);
};
// Using Axios (alternative)
// npm install axios
import axios from 'axios';
const AxiosExample = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchWithAxios();
}, []);
const fetchWithAxios = async () => {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
setData(response.data);
} catch (error) {
console.error('Axios error:', error);
}
};
const postWithAxios = async (newPost) => {
try {
const response = await axios.post('https://jsonplaceholder.typicode.com/posts', newPost);
console.log('Posted with Axios:', response.data);
} catch (error) {
console.error('Axios post error:', error);
}
};
return (
<View>
{/* Render data */}
</View>
);
};
```_
## Push-Benachrichtigungen
### Setup
```bash
# Install Expo Notifications
expo install expo-notifications
# For bare workflow, additional setup required
# See: https://docs.expo.dev/push-notifications/overview/
```_
### Grundangaben
```javascript
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import { Platform } from 'react-native';
// Configure notifications
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
});
const NotificationExample = () => {
const [expoPushToken, setExpoPushToken] = useState('');
const [notification, setNotification] = useState(false);
useEffect(() => {
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
const notificationListener = Notifications.addNotificationReceivedListener(notification => {
setNotification(notification);
});
const responseListener = Notifications.addNotificationResponseReceivedListener(response => {
console.log(response);
});
return () => {
Notifications.removeNotificationSubscription(notificationListener);
Notifications.removeNotificationSubscription(responseListener);
};
}, []);
const schedulePushNotification = async () => {
await Notifications.scheduleNotificationAsync({
content: {
title: "You've got mail! 📬",
body: 'Here is the notification body',
data: { data: 'goes here' },
},
trigger: { seconds: 2 },
});
};
const sendPushNotification = async (expoPushToken) => {
const message = {
to: expoPushToken,
sound: 'default',
title: 'Original Title',
body: 'And here is the body!',
data: { someData: 'goes here' },
};
await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
});
};
return (
<View>
<Text>Your expo push token: {expoPushToken}</Text>
<Button
title="Press to schedule a notification"
onPress={schedulePushNotification}
/>
<Button
title="Press to send a push notification"
onPress={() => sendPushNotification(expoPushToken)}
/>
</View>
);
};
async function registerForPushNotificationsAsync() {
let token;
if (Device.isDevice) {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
alert('Failed to get push token for push notification!');
return;
}
token = (await Notifications.getExpoPushTokenAsync()).data;
} else {
alert('Must use physical device for Push Notifications');
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
return token;
}
```_
## Dateisystem
### Dateioperationen
```bash
# Install FileSystem
expo install expo-file-system
```_
```javascript
import * as FileSystem from 'expo-file-system';
const FileSystemExample = () => {
const [fileContent, setFileContent] = useState('');
const writeFile = async () => {
const fileUri = FileSystem.documentDirectory + 'test.txt';
const content = 'Hello, Expo FileSystem!';
try {
await FileSystem.writeAsStringAsync(fileUri, content);
alert('File written successfully!');
} catch (error) {
console.error('Error writing file:', error);
}
};
const readFile = async () => {
const fileUri = FileSystem.documentDirectory + 'test.txt';
try {
const content = await FileSystem.readAsStringAsync(fileUri);
setFileContent(content);
} catch (error) {
console.error('Error reading file:', error);
}
};
const deleteFile = async () => {
const fileUri = FileSystem.documentDirectory + 'test.txt';
try {
await FileSystem.deleteAsync(fileUri);
alert('File deleted successfully!');
setFileContent('');
} catch (error) {
console.error('Error deleting file:', error);
}
};
const getFileInfo = async () => {
const fileUri = FileSystem.documentDirectory + 'test.txt';
try {
const info = await FileSystem.getInfoAsync(fileUri);
console.log('File info:', info);
} catch (error) {
console.error('Error getting file info:', error);
}
};
const downloadFile = async () => {
const fileUri = FileSystem.documentDirectory + 'downloaded.jpg';
const downloadUrl = 'https://example.com/image.jpg';
try {
const { uri } = await FileSystem.downloadAsync(downloadUrl, fileUri);
console.log('Downloaded to:', uri);
} catch (error) {
console.error('Error downloading file:', error);
}
};
return (
<View style={{ padding: 20 }}>
<Button title="Write File" onPress={writeFile} />
<Button title="Read File" onPress={readFile} />
<Button title="Delete File" onPress={deleteFile} />
<Button title="Get File Info" onPress={getFileInfo} />
<Button title="Download File" onPress={downloadFile} />
{fileContent ? (
<View style={{ marginTop: 20 }}>
<Text>File Content:</Text>
<Text>{fileContent}</Text>
</View>
) : null}
</View>
);
};
```_
## Kamera und Medien
### Kamera
```bash
# Install Camera
expo install expo-camera
```_
```javascript
import React, { useState, useEffect, useRef } from 'react';
import { Camera } from 'expo-camera';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
const CameraExample = () => {
const [hasPermission, setHasPermission] = useState(null);
const [type, setType] = useState(Camera.Constants.Type.back);
const cameraRef = useRef(null);
useEffect(() => {
(async () => {
const { status } = await Camera.requestCameraPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
const takePicture = async () => {
if (cameraRef.current) {
const photo = await cameraRef.current.takePictureAsync();
console.log('Photo taken:', photo.uri);
// Save or process the photo
}
};
const recordVideo = async () => {
if (cameraRef.current) {
const video = await cameraRef.current.recordAsync();
console.log('Video recorded:', video.uri);
}
};
const stopRecording = () => {
if (cameraRef.current) {
cameraRef.current.stopRecording();
}
};
if (hasPermission === null) {
return <View />;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
return (
<View style={styles.container}>
<Camera style={styles.camera} type={type} ref={cameraRef}>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.button}
onPress={() => {
setType(
type === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back
);
}}
>
<Text style={styles.text}>Flip</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={takePicture}>
<Text style={styles.text}>Take Photo</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={recordVideo}>
<Text style={styles.text}>Record</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={stopRecording}>
<Text style={styles.text}>Stop</Text>
</TouchableOpacity>
</View>
</Camera>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
camera: {
flex: 1,
},
buttonContainer: {
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
margin: 20,
},
button: {
flex: 0.1,
alignSelf: 'flex-end',
alignItems: 'center',
backgroundColor: 'rgba(0,0,0,0.5)',
padding: 10,
margin: 5,
},
text: {
fontSize: 18,
color: 'white',
},
});
export default CameraExample;
```_
### Image Picker
```bash
# Install Image Picker
expo install expo-image-picker
```_
```javascript
import * as ImagePicker from 'expo-image-picker';
import { Image } from 'react-native';
const ImagePickerExample = () => {
const [image, setImage] = useState(null);
const pickImage = async () => {
// Request permission
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
alert('Sorry, we need camera roll permissions to make this work!');
return;
}
// Pick image
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!result.cancelled) {
setImage(result.uri);
}
};
const takePhoto = async () => {
// Request permission
const { status } = await ImagePicker.requestCameraPermissionsAsync();
if (status !== 'granted') {
alert('Sorry, we need camera permissions to make this work!');
return;
}
// Take photo
const result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!result.cancelled) {
setImage(result.uri);
}
};
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button title="Pick an image from camera roll" onPress={pickImage} />
<Button title="Take a photo" onPress={takePhoto} />
{image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
</View>
);
};
```_
## Location Services
### Standort Setup
```bash
# Install Location
expo install expo-location
```_
```javascript
import * as Location from 'expo-location';
const LocationExample = () => {
const [location, setLocation] = useState(null);
const [errorMsg, setErrorMsg] = useState(null);
useEffect(() => {
getCurrentLocation();
}, []);
const getCurrentLocation = async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
return;
}
let location = await Location.getCurrentPositionAsync({});
setLocation(location);
};
const watchLocation = async () => {
let { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
setErrorMsg('Permission to access location was denied');
return;
}
const subscription = await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.High,
timeInterval: 1000,
distanceInterval: 1,
},
(location) => {
setLocation(location);
}
);
// Don't forget to unsubscribe when component unmounts
return () => subscription.remove();
};
const reverseGeocode = async () => {
if (location) {
const { latitude, longitude } = location.coords;
const address = await Location.reverseGeocodeAsync({
latitude,
longitude,
});
console.log('Address:', address);
}
};
const geocode = async (address) => {
const coords = await Location.geocodeAsync(address);
console.log('Coordinates:', coords);
};
let text = 'Waiting..';
if (errorMsg) {
text = errorMsg;
} else if (location) {
text = JSON.stringify(location);
}
return (
<View style={{ padding: 20 }}>
<Text>{text}</Text>
<Button title="Get Current Location" onPress={getCurrentLocation} />
<Button title="Watch Location" onPress={watchLocation} />
<Button title="Reverse Geocode" onPress={reverseGeocode} />
<Button
title="Geocode Address"
onPress={() => geocode('1600 Amphitheatre Parkway, Mountain View, CA')}
/>
</View>
);
};
```_
## Authentication
### Expo Authension
```bash
# Install AuthSession
expo install expo-auth-session expo-crypto
```_
```javascript
import * as AuthSession from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';
WebBrowser.maybeCompleteAuthSession();
const AuthExample = () => {
const [user, setUser] = useState(null);
// Google OAuth
const [request, response, promptAsync] = AuthSession.useAuthRequest(
{
clientId: 'YOUR_GOOGLE_CLIENT_ID',
scopes: ['openid', 'profile', 'email'],
redirectUri: AuthSession.makeRedirectUri({
useProxy: true,
}),
},
{
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
}
);
useEffect(() => {
if (response?.type === 'success') {
const { authentication } = response;
// Use the access token to get user info
fetchUserInfo(authentication.accessToken);
}
}, [response]);
const fetchUserInfo = async (token) => {
try {
const response = await fetch('https://www.googleapis.com/userinfo/v2/me', {
headers: { Authorization: `Bearer ${token}` },
});
const userInfo = await response.json();
setUser(userInfo);
} catch (error) {
console.error('Error fetching user info:', error);
}
};
const signIn = () => {
promptAsync();
};
const signOut = () => {
setUser(null);
};
return (
<View style={{ padding: 20 }}>
{user ? (
<View>
<Text>Welcome, {user.name}!</Text>
<Text>Email: {user.email}</Text>
<Button title="Sign Out" onPress={signOut} />
</View>
) : (
<Button
disabled={!request}
title="Sign in with Google"
onPress={signIn}
/>
)}
</View>
);
};
```_
### Firebase Authentication
```bash
# Install Firebase
npm install firebase
```_
```javascript
import { initializeApp } from 'firebase/app';
import {
getAuth,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signOut,
onAuthStateChanged
} from 'firebase/auth';
const firebaseConfig = {
// Your Firebase config
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const FirebaseAuthExample = () => {
const [user, setUser] = useState(null);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user);
});
return unsubscribe;
}, []);
const signUp = async () => {
try {
await createUserWithEmailAndPassword(auth, email, password);
} catch (error) {
alert(error.message);
}
};
const signIn = async () => {
try {
await signInWithEmailAndPassword(auth, email, password);
} catch (error) {
alert(error.message);
}
};
const handleSignOut = async () => {
try {
await signOut(auth);
} catch (error) {
alert(error.message);
}
};
return (
<View style={{ padding: 20 }}>
{user ? (
<View>
<Text>Welcome, {user.email}!</Text>
<Button title="Sign Out" onPress={handleSignOut} />
</View>
) : (
<View>
<TextInput
placeholder="Email"
value={email}
onChangeText={setEmail}
style={{ borderWidth: 1, padding: 10, marginBottom: 10 }}
/>
<TextInput
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
style={{ borderWidth: 1, padding: 10, marginBottom: 10 }}
/>
<Button title="Sign In" onPress={signIn} />
<Button title="Sign Up" onPress={signUp} />
</View>
)}
</View>
);
};
```_
## Bau und Verlagswesen
### Expo Build (Legacy)
```bash
# Build for iOS
expo build:ios
# Build for Android
expo build:android
# Build for web
expo build:web
# Check build status
expo build:status
# Download build
expo build:download
# Publish to Expo
expo publish
# Set release channel
expo publish --release-channel production
```_
### Bereitstellung von App Store
```bash
# iOS App Store
# 1. Build IPA: expo build:ios
# 2. Download IPA
# 3. Upload to App Store Connect using Transporter or Xcode
# Google Play Store
# 1. Build APK/AAB: expo build:android
# 2. Download APK/AAB
# 3. Upload to Google Play Console
```_
## EAS (Expo Application Services)
### EAS Setup
```bash
# Install EAS CLI
npm install -g eas-cli
# Login to Expo account
eas login
# Initialize EAS in project
eas build:configure
# Create development build
eas build --profile development --platform ios
eas build --profile development --platform android
# Create production build
eas build --profile production --platform ios
eas build --profile production --platform android
# Build for all platforms
eas build --platform all
```_
### EAS Konfiguration
```json
// eas.json
{
"cli": {
"version": ">= 2.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"resourceClass": "m1-medium"
}
},
"preview": {
"distribution": "internal"
},
"production": {
"ios": {
"resourceClass": "m1-medium"
}
}
},
"submit": {
"production": {
"ios": {
"appleId": "your-apple-id@example.com",
"ascAppId": "1234567890",
"appleTeamId": "ABCDEF1234"
},
"android": {
"serviceAccountKeyPath": "../path/to/api-key.json",
"track": "internal"
}
}
}
}
```_
### EAS-Zulassung
```bash
# Submit to app stores
eas submit --platform ios
eas submit --platform android
# Submit specific build
eas submit --platform ios --id your-build-id
# Submit with custom configuration
eas submit --platform android --profile production
```_
### EAS Update
```bash
# Install EAS Update
expo install expo-updates
# Configure EAS Update
eas update:configure
# Publish update
eas update --branch main --message "Bug fixes"
# Publish to specific channel
eas update --channel production --message "Production update"
# View updates
eas update:list
# Delete update
eas update:delete --group-id your-group-id
```_
## Best Practices
### Projektstruktur
Leistungsoptimierung¶
```javascript
// Use React.memo for expensive components
const ExpensiveComponent = React.memo(({ data }) => {
return (
// Use useMemo for expensive calculations const ExpensiveCalculation = ({ items }) => { const expensiveValue = useMemo(() => { return items.reduce((acc, item) => acc + item.value, 0); }, [items]);
return
// Use useCallback for event handlers const ListComponent = ({ items, onItemPress }) => { const handlePress = useCallback((item) => { onItemPress(item); }, [onItemPress]);
return (
// Optimize images
const OptimizedImage = ({ source, style }) => {
return (
Fehlerbehebung¶
```javascript // Error Boundary class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; }
static getDerivedStateFromError(error) { return { hasError: true }; }
componentDidCatch(error, errorInfo) { console.error('Error caught by boundary:', error, errorInfo); }
render() {
if (this.state.hasError) {
return (
return this.props.children;
} }
// Global error handler const setGlobalErrorHandler = () => { const defaultHandler = ErrorUtils.getGlobalHandler();
ErrorUtils.setGlobalHandler((error, isFatal) => { console.error('Global error:', error); // Log to crash reporting service defaultHandler(error, isFatal); }); };
// Async error handling const safeAsyncFunction = async () => { try { const result = await riskyAsyncOperation(); return result; } catch (error) { console.error('Async error:', error); // Handle error appropriately throw error; } }; ```_
Sicherheit Best Practices¶
```javascript // Secure storage import * as SecureStore from 'expo-secure-store';
const storeSecureData = async (key, value) => { await SecureStore.setItemAsync(key, value); };
const getSecureData = async (key) => { return await SecureStore.getItemAsync(key); };
// API key protection // Never store API keys in code // Use environment variables or secure storage
// Input validation const validateEmail = (email) => { const emailRegex = /[\s@]+@[\s@]+.[\s@]+$/; return emailRegex.test(email); };
const sanitizeInput = (input) => { return input.trim().replace(/[<>]/g, ''); };
// Network security const secureApiCall = async (url, options = {}) => { const defaultOptions = { headers: { 'Content-Type': 'application/json', // Add authentication headers }, ...options, };
try {
const response = await fetch(url, defaultOptions);
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status});
}
return await response.json();
} catch (error) {
console.error('API call failed:', error);
throw error;
}
};
```_
--
Zusammenfassung¶
Expo ist eine leistungsstarke Plattform, die vereinfacht React Native Entwicklung durch Bereitstellung:
- Rapid Development: Schneller Projektaufbau und Entwicklungsarbeit
- **Rich API Access*: Umfassende Set nativer APIs ohne nativen Code schreiben
- **Universal Apps*: Erstellen Sie für iOS, Android und Web aus einer einzigen Codebasis
- **Over-the-Air Updates*: Push-Updates ohne App Store-Zulassung
- **Entwicklungswerkzeuge*: Hervorragende Debugging- und Testwerkzeuge
- **Build Services*: Cloud-basierte Gebäude und Bereitstellung mit EAS
- Gemeinschaft: Großes Ökosystem und aktive Gemeinschaft
Expo ist ideal für Rapid Prototyping, MVPs und Produktionsapps, die keine umfangreichen nativen Anpassungen erfordern. Für Apps, die benutzerdefinierte native Module benötigen, bietet Expo einen einfachen Workflow und Entwicklung baut auf, um Flexibilität zu erhalten, während die Entwicklererfahrung reibungslos bleibt.
<= <= <= <================================================================================= Funktion copyToClipboard() {\cHFFFF} const commands = document.querySelectorAll('code'); alle Befehle = ''; Befehle. Für jede(cmd) => alle Befehle += cmd.textContent + '\n'); navigator.clipboard.writeText (allCommands); Alarm ('Alle Befehle, die in die Zwischenablage kopiert werden!'); }
Funktion generierenPDF() { Fenster.print(); }