Erstellen mobiler Unternehmensanwendungen mit Expo und React Native
Expo hat sich von einem Rapid-Prototyping-Tool zu einer legitimen Unternehmensplattform entwickelt. Mit Expo SDK 52 und der neuen Architektur (Fabric Renderer + JSI) können Sie plattformübergreifende mobile Apps erstellen, die der Leistung und dem Feeling nativer Apps entsprechen – während Sie Code mit Ihrer React-Webanwendung teilen und von einer einzigen TypeScript-Codebasis aus auf iOS und Android liefern.
In diesem Leitfaden werden die Muster behandelt, die ein Hobbyprojekt von einer produktiven Unternehmensanwendung unterscheiden: EAS-Build-Konfiguration, Push-Benachrichtigungsinfrastruktur, Offline-First-Datensynchronisierung, biometrische Authentifizierung und App-Store-Übermittlungsworkflows.
Wichtige Erkenntnisse
- Expo Go ist für Demos; EAS Build ist für die Produktion gedacht – beginnen Sie vom ersten Tag an mit EAS – Die neue Architektur (Fabric + JSI) ist optional, bietet aber messbare Leistungssteigerungen
expo-notificationserfordert eine sorgfältige Einrichtung sowohl für die Vordergrund- als auch für die Hintergrundverarbeitung – Offline-Unterstützung erfordert eine lokale Datenbank (WatermelonDB oder SQLite) mit Synchronisierungslogik- Die biometrische Authentifizierung mit
expo-local-authenticationumfasst einige Codezeilen – Deep Linking erfordert eine Konfiguration sowohl in app.json als auch in den Rückruf-URLs Ihres Authentifizierungsanbieters – TypeScript-Pfadaliase funktionieren in Expo überbabel-plugin-module-resolver– Speichern Sie niemals vertrauliche Daten inAsyncStorage– verwenden Sieexpo-secure-storefür Token
Projekt-Setup
Starten Sie jedes Expo-Projekt mit dem Bare-Workflow (nicht Managed), wenn Sie mit nativen Modulanforderungen rechnen. Der verwaltete Workflow ist praktisch, schränkt Sie jedoch ein – ein späterer Wechsel erfordert ein Auswerfen, was störend ist.
# Create new Expo project
npx create-expo-app@latest MyApp --template
# Or with TypeScript template
npx create-expo-app@latest MyApp -t expo-template-blank-typescript
# Install core enterprise dependencies
npx expo install \
expo-router \
expo-secure-store \
expo-local-authentication \
expo-notifications \
expo-updates \
expo-constants \
@react-navigation/native \
@react-navigation/bottom-tabs \
@tanstack/react-query
Das dateibasierte Routing-System expo-router ist der moderne Ansatz – es spiegelt die App-Router-Muster von Next.js wider und erleichtert so den Kontextwechsel zwischen Web und Mobilgeräten erheblich.
app.json-Konfiguration
Unternehmensanwendungen erfordern von Anfang an eine sorgfältige app.json-Konfiguration:
{
"expo": {
"name": "ECOSIRE",
"slug": "ecosire-mobile",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#0a0a0a"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.ecosire.mobile",
"buildNumber": "1",
"infoPlist": {
"NSFaceIDUsageDescription": "Use Face ID to securely sign in",
"NSCameraUsageDescription": "Take photos for your profile"
}
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#0a0a0a"
},
"package": "com.ecosire.mobile",
"versionCode": 1,
"permissions": [
"USE_BIOMETRIC",
"USE_FINGERPRINT",
"RECEIVE_BOOT_COMPLETED",
"VIBRATE"
]
},
"plugins": [
[
"expo-notifications",
{
"icon": "./assets/notification-icon.png",
"color": "#f59e0b",
"sounds": ["./assets/notification.wav"]
}
],
"expo-router",
"expo-secure-store",
"expo-local-authentication"
],
"updates": {
"url": "https://u.expo.dev/YOUR-PROJECT-ID"
},
"runtimeVersion": {
"policy": "sdkVersion"
},
"extra": {
"eas": {
"projectId": "YOUR-PROJECT-ID"
}
}
}
}
EAS-Build-Konfiguration
EAS (Expo Application Services) kümmert sich um die Erstellung, Übermittlung und Aktualisierung Ihrer App. Konfigurieren Sie es mit eas.json:
{
"cli": {
"version": ">= 10.0.0",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
},
"android": {
"buildType": "apk"
},
"env": {
"API_URL": "http://localhost:3001",
"NODE_ENV": "development"
}
},
"preview": {
"distribution": "internal",
"channel": "preview",
"env": {
"API_URL": "https://staging-api.ecosire.com",
"NODE_ENV": "staging"
}
},
"production": {
"channel": "production",
"autoIncrement": true,
"env": {
"API_URL": "https://api.ecosire.com",
"NODE_ENV": "production"
},
"ios": {
"credentialsSource": "remote"
},
"android": {
"credentialsSource": "remote"
}
}
},
"submit": {
"production": {
"ios": {
"appleId": "[email protected]",
"ascAppId": "YOUR-APP-STORE-CONNECT-APP-ID",
"appleTeamId": "YOUR-TEAM-ID"
},
"android": {
"serviceAccountKeyPath": "./google-service-account.json",
"track": "internal"
}
}
}
}
# Build for different environments
eas build --profile development --platform ios
eas build --profile preview --platform all
eas build --profile production --platform all
# Submit to stores
eas submit --profile production --platform ios
eas submit --profile production --platform android
Sichere Authentifizierung
Unternehmensanwendungen dürfen Authentifizierungstoken niemals in AsyncStorage speichern – sie sind unverschlüsselt und für andere Anwendungen auf gerooteten Geräten zugänglich. Verwenden Sie expo-secure-store, das den Schlüsselbund (iOS) und den Keystore (Android) verwendet:
// lib/auth/token-storage.ts
import * as SecureStore from 'expo-secure-store';
const ACCESS_TOKEN_KEY = 'ecosire_access_token';
const REFRESH_TOKEN_KEY = 'ecosire_refresh_token';
export const tokenStorage = {
async saveTokens(accessToken: string, refreshToken: string) {
await Promise.all([
SecureStore.setItemAsync(ACCESS_TOKEN_KEY, accessToken, {
keychainService: 'com.ecosire.mobile.auth',
keychainAccessible: SecureStore.WHEN_UNLOCKED,
}),
SecureStore.setItemAsync(REFRESH_TOKEN_KEY, refreshToken, {
keychainService: 'com.ecosire.mobile.auth',
keychainAccessible: SecureStore.WHEN_UNLOCKED,
}),
]);
},
async getAccessToken(): Promise<string | null> {
return SecureStore.getItemAsync(ACCESS_TOKEN_KEY, {
keychainService: 'com.ecosire.mobile.auth',
});
},
async getRefreshToken(): Promise<string | null> {
return SecureStore.getItemAsync(REFRESH_TOKEN_KEY, {
keychainService: 'com.ecosire.mobile.auth',
});
},
async clearTokens() {
await Promise.all([
SecureStore.deleteItemAsync(ACCESS_TOKEN_KEY),
SecureStore.deleteItemAsync(REFRESH_TOKEN_KEY),
]);
},
};
Biometrische Authentifizierung
Hinzufügen von Face ID/Touch ID/Fingerabdruck-Authentifizierung:
// lib/auth/biometric.ts
import * as LocalAuthentication from 'expo-local-authentication';
export async function isBiometricAvailable(): Promise<boolean> {
const compatible = await LocalAuthentication.hasHardwareAsync();
if (!compatible) return false;
const enrolled = await LocalAuthentication.isEnrolledAsync();
return enrolled;
}
export async function authenticateWithBiometrics(
reason = 'Confirm your identity'
): Promise<boolean> {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: reason,
fallbackLabel: 'Use passcode',
disableDeviceFallback: false,
cancelLabel: 'Cancel',
});
return result.success;
}
// Usage in a component
async function handleSensitiveAction() {
const available = await isBiometricAvailable();
if (available) {
const authenticated = await authenticateWithBiometrics(
'Confirm this transaction'
);
if (!authenticated) return;
}
// Proceed with the action
await processSensitiveOperation();
}
Push-Benachrichtigungen
Push-Benachrichtigungen erfordern eine Backend-Infrastruktur und einen sorgfältigen Umgang mit Berechtigungen:
// lib/notifications/setup.ts
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import Constants from 'expo-constants';
import { Platform } from 'react-native';
// Configure how notifications appear when app is in foreground
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
export async function registerForPushNotifications(): Promise<string | null> {
// Push notifications don't work on simulators
if (!Device.isDevice) {
console.warn('Push notifications require a physical device');
return null;
}
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
return null; // User denied permissions
}
// Android requires a notification channel
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'Default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#f59e0b',
});
}
const projectId = Constants.expoConfig?.extra?.eas?.projectId;
const token = await Notifications.getExpoPushTokenAsync({ projectId });
return token.data;
}
// hooks/use-push-notifications.ts
import { useEffect, useRef } from 'react';
import { AppState } from 'react-native';
import * as Notifications from 'expo-notifications';
import { registerForPushNotifications } from '@/lib/notifications/setup';
import { updatePushToken } from '@/lib/api/notifications';
export function usePushNotifications() {
const notificationListener = useRef<Notifications.Subscription>();
const responseListener = useRef<Notifications.Subscription>();
useEffect(() => {
// Register and save token to backend
registerForPushNotifications().then((token) => {
if (token) {
updatePushToken(token).catch(console.error);
}
});
// Handle notifications received while app is open
notificationListener.current =
Notifications.addNotificationReceivedListener((notification) => {
console.log('Notification received:', notification);
});
// Handle notification tap
responseListener.current =
Notifications.addNotificationResponseReceivedListener((response) => {
const data = response.notification.request.content.data;
// Navigate based on notification data
handleNotificationNavigation(data);
});
return () => {
notificationListener.current?.remove();
responseListener.current?.remove();
};
}, []);
}
Offline-Unterstützung mit WatermelonDB
Unternehmensanwendungen müssen offline funktionieren. WatermelonDB ist die leistungsstärkste lokale Datenbank für React Native:
npx expo install @nozbe/watermelondb @nozbe/with-observables
// db/schema.ts
import { appSchema, tableSchema } from '@nozbe/watermelondb';
export const schema = appSchema({
version: 1,
tables: [
tableSchema({
name: 'contacts',
columns: [
{ name: 'server_id', type: 'string', isOptional: true },
{ name: 'name', type: 'string' },
{ name: 'email', type: 'string', isOptional: true },
{ name: 'organization_id', type: 'string' },
{ name: 'synced_at', type: 'number', isOptional: true },
{ name: 'is_deleted', type: 'boolean' },
{ name: 'created_at', type: 'number' },
{ name: 'updated_at', type: 'number' },
],
}),
],
});
// lib/sync/contacts-sync.ts
import { synchronize } from '@nozbe/watermelondb/sync';
import { database } from '@/db';
export async function syncContacts(authToken: string) {
await synchronize({
database,
pullChanges: async ({ lastPulledAt }) => {
const response = await fetch(
`${API_URL}/sync/contacts?since=${lastPulledAt}`,
{
headers: { Authorization: `Bearer ${authToken}` },
}
);
const { changes, timestamp } = await response.json();
return { changes, timestamp };
},
pushChanges: async ({ changes }) => {
await fetch(`${API_URL}/sync/contacts`, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ changes }),
});
},
migrationsEnabledAtVersion: 1,
});
}
TanStack-Abfrage für API-Daten
TanStack Query funktioniert in React Native genauso wie in React-Web-Apps:
// lib/api/contacts.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { tokenStorage } from '@/lib/auth/token-storage';
async function apiRequest<T>(path: string, options?: RequestInit): Promise<T> {
const token = await tokenStorage.getAccessToken();
const response = await fetch(`${process.env.API_URL}${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options?.headers,
},
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
export function useContacts() {
return useQuery({
queryKey: ['contacts'],
queryFn: () => apiRequest<Contact[]>('/contacts'),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
export function useCreateContact() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateContactDto) =>
apiRequest<Contact>('/contacts', {
method: 'POST',
body: JSON.stringify(data),
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['contacts'] });
},
});
}
Over-the-Air-Updates mit Messe-Updates
EAS Update liefert JavaScript-Bundle-Updates ohne Überprüfung im App Store – entscheidend für Fehlerbehebungen:
// hooks/use-app-updates.ts
import { useEffect } from 'react';
import * as Updates from 'expo-updates';
import { Alert } from 'react-native';
export function useAppUpdates() {
useEffect(() => {
async function checkForUpdates() {
if (__DEV__) return; // Skip in development
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
Alert.alert(
'Update Available',
'A new version of the app is ready. Restart to apply.',
[
{ text: 'Later', style: 'cancel' },
{
text: 'Restart Now',
onPress: () => Updates.reloadAsync(),
},
]
);
}
} catch (error) {
// Update check failed — app continues running current version
console.warn('Update check failed:', error);
}
}
checkForUpdates();
}, []);
}
Häufig gestellte Fragen
Wann sollte ich Expo gegenüber bloßem React Native wählen?
Wählen Sie Expo für die überwiegende Mehrheit der Unternehmensanwendungen. Das EAS-Ökosystem deckt 95 % der Unternehmensanforderungen ab – biometrische Authentifizierung, Push-Benachrichtigungen, Deep Linking, Kamera, Dateisystem, sichere Speicherung. Nutzen Sie React Native nur dann, wenn Sie benutzerdefinierte native Module benötigen, die in Expo nicht verfügbar sind, oder wenn Sie eine Integration mit älterem nativen Code durchführen. Die Entwicklungserfahrung mit Expo ist deutlich besser und EAS Build bewältigt die Komplexität nativer Builds.
Wie teile ich Code zwischen einer Next.js-Web-App und einer mobilen Expo-App?
Verwenden Sie ein Turborepo-Monorepo mit gemeinsam genutzten Paketen für Geschäftslogik, Typen und API-Clients. React Native unterstützt nicht alle Browser-/Node.js-APIs. Seien Sie daher vorsichtig, was in gemeinsam genutzten Paketen enthalten ist. Ein Muster, das gut funktioniert: Gemeinsame Nutzung von Typen, Validierungsschemata (Zod) und API-Client-Funktionen, aber getrennte UI-Komponenten. Sowohl der App Router als auch der Expo Router von Next.js 16 verwenden dateibasiertes Routing mit ähnlichen mentalen Modellen, was die Aufrechterhaltung paralleler Implementierungen erleichtert.
Wie gehe ich mit Deep-Linking für Authentifizierungsrückrufe um?
Konfigurieren Sie Ihr URI-Schema in app.json unter scheme (z. B. "scheme": "ecosire"). Registrieren Sie ecosire://auth/callback als Weiterleitungs-URI bei Ihrem Authentifizierungsanbieter. Verwenden Sie expo-linking, um eingehende URLs in Ihrer App zu verarbeiten. Für universelle Links (iOS) und App-Links (Android), die wie echte URLs funktionieren, benötigen Sie eine zusätzliche Domain-Verifizierung – apple-app-site-association- und assetlinks.json-Dateien, die von Ihrer Domain bereitgestellt werden.
Wie gehandhabt man die App-Versionierung in EAS am besten?
Legen Sie "appVersionSource": "remote" in eas.json fest und aktivieren Sie "autoIncrement": true für Produktionsbuilds. Dadurch kann EAS Build-Nummern automatisch verwalten und verhindert so den häufigen Fehler, zu vergessen, die Nummer manuell zu erhöhen. Behalten Sie Ihre für Menschen lesbare Version (1.2.3) in app.json und überlassen Sie EAS die Verarbeitung der Build-Nummer (iOS buildNumber, Android versionCode).
Wie teste ich Push-Benachrichtigungen in der Entwicklung?
Verwenden Sie das Push-Benachrichtigungstesttool von Expo unter https://expo.dev/notifications mit dem Expo-Push-Token Ihres Geräts. Für die lokale Entwicklung stellt expo-notifications eine scheduleNotificationAsync-Funktion bereit, die lokale Benachrichtigungen ohne Backend auslöst. Erstellen Sie für End-to-End-Tests einen Entwicklungsclient mit eas build --profile development und verwenden Sie ein physisches Gerät.
Nächste Schritte
Der Aufbau einer mobilen Unternehmensanwendung, die zuverlässig, sicher und in großem Maßstab wartbar ist, erfordert die richtige Grundlage: EAS Build-Konfiguration, sicherer Token-Speicher, Push-Benachrichtigungs-Infrastruktur und Offline-Synchronisierung müssen vom ersten Tag an sorgfältig eingerichtet werden.
ECOSIRE hat mithilfe von Expo mobile Produktionsanwendungen mit 11 Bildschirmen, unendlichem Scrollen, Push-Benachrichtigungen und EAS-Bereitstellungskonfiguration erstellt. Wenn Sie Fachwissen in der mobilen Entwicklung benötigen, entdecken Sie unsere Entwicklungsdienste.
Geschrieben von
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Transformieren Sie Ihr Unternehmen mit Odoo ERP
Kompetente Odoo-Implementierung, Anpassung und Support zur Optimierung Ihrer Abläufe.
Verwandte Artikel
Der vollständige Leitfaden zu Odoo ERP im Jahr 2026: Alles, was Sie wissen müssen
Umfassender Odoo ERP-Leitfaden zu Modulen, Preisen, Implementierung, Anpassung und Integration. Erfahren Sie, warum sich im Jahr 2026 mehr als 12 Millionen Benutzer für Odoo entscheiden.
Migration von Microsoft Dynamics 365 zu Odoo: Unternehmenshandbuch
Unternehmensleitfaden für die Migration von Microsoft Dynamics 365 zu Odoo. Moduläquivalente, Datenextraktion, Anpassungsprüfung und Parallellaufstrategie.
ERP vs. CRM: Was ist der Unterschied und was benötigen Sie?
ERP- und CRM-Vergleich mit Erläuterung der Kernfunktionen, wann Sie jedes System benötigen, wann Sie beide benötigen, Integrationsvorteile und wie Odoo sie vereinheitlicht.