Expo ve React Native ile Kurumsal Mobil Uygulamalar Geliştirme
Expo, hızlı bir prototip oluşturma aracından meşru bir kurumsal platforma dönüştü. Expo SDK 52 ve yeni mimari (Fabric oluşturucu + JSI) ile, React web uygulamanızla kodu paylaşırken ve tek bir TypeScript kod tabanından hem iOS hem de Android'e gönderirken, yerel uygulamaların performansına ve hissine uyan platformlar arası mobil uygulamalar oluşturabilirsiniz.
Bu kılavuz, bir hobi projesini bir kurumsal üretim uygulamasından ayıran kalıpları kapsar: EAS Build yapılandırması, anında bildirim altyapısı, çevrimdışı öncelikli veri senkronizasyonu, biyometrik kimlik doğrulama ve App Store gönderim iş akışları.
Önemli Çıkarımlar
- Expo Go demolar içindir; EAS Build üretim içindir; ilk günden itibaren EAS ile başlayın
- Yeni mimari (Fabric + JSI) isteğe bağlıdır ancak ölçülebilir performans kazanımları sağlar
expo-notificationshem ön planda hem de arka planda işleme için dikkatli kurulum gerektirir- Çevrimdışı destek, senkronizasyon mantığına sahip yerel bir veritabanına (WatermelonDB veya SQLite) ihtiyaç duyar
expo-local-authenticationile biyometrik kimlik doğrulama birkaç satırlık koddan oluşur- Derin bağlantı, hem app.json'da hem de kimlik doğrulama sağlayıcınızın geri çağırma URL'lerinde yapılandırma gerektirir
- TypeScript yolu takma adları Expo'da
babel-plugin-module-resolveraracılığıyla çalışır- Hassas verileri hiçbir zaman
AsyncStorageiçinde saklamayın — belirteçler içinexpo-secure-storekullanın
Proje Kurulumu
Herhangi bir yerel modül gereksinimi öngörüyorsanız, her Expo projesini Çıplak iş akışıyla (Yönetilen değil) başlatın. Yönetilen iş akışı kullanışlıdır ancak sizi sınırlar; daha sonra geçiş yapmak, çıkarma işlemini gerektirir ve bu da işleri aksatır.
# 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
expo-router dosya tabanlı yönlendirme sistemi modern bir yaklaşımdır; Next.js'nin Uygulama Yönlendirici kalıplarını yansıtır ve web ile mobil arasında bağlam geçişini çok daha kolay hale getirir.
app.json Yapılandırması
Kurumsal uygulamalar, başlangıçtan itibaren dikkatli bir app.json yapılandırmasına ihtiyaç duyar:
{
"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 Derleme Yapılandırması
EAS (Expo Uygulama Hizmetleri), uygulamanızı oluşturmayı, göndermeyi ve güncellemeyi yönetir. eas.json ile yapılandırın:
{
"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
Güvenli Kimlik Doğrulama
Kurumsal uygulamalar kimlik doğrulama belirteçlerini hiçbir zaman AsyncStorage içinde saklamamalıdır; bu kod şifrelenmemiştir ve root erişimli cihazlardaki diğer uygulamalar tarafından erişilebilir. Anahtar Zinciri (iOS) ve Anahtar Deposunu (Android) kullanan expo-secure-store kullanın:
// 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),
]);
},
};
Biyometrik Kimlik Doğrulama
Face ID / Touch ID / parmak izi kimlik doğrulaması ekleme:
// 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();
}
Anında Bildirimler
Anlık bildirimler arka uç altyapısı ve dikkatli izin yönetimi gerektirir:
// 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();
};
}, []);
}
WatermelonDB ile Çevrimdışı Destek
Kurumsal uygulamaların çevrimdışı çalışması gerekir. WatermelonDB, React Native için en performanslı yerel veritabanıdır:
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,
});
}
API Verileri için TanStack Sorgusu
TanStack Query, React Native'de tam olarak React web uygulamalarında olduğu gibi çalışır:
// 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'] });
},
});
}
Fuar güncellemeleriyle Kablosuz Güncellemeler
EAS Güncellemesi, App Store incelemesi olmadan JavaScript paketi güncellemelerini sunar; hata düzeltmeleri için kritik öneme sahiptir:
// 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();
}, []);
}
Sıkça Sorulan Sorular
Ne zaman çıplak React Native yerine Expo'yu seçmeliyim?
Kurumsal uygulamaların büyük çoğunluğu için Expo'yu seçin. EAS ekosistemi, biyometrik kimlik doğrulama, anında bildirimler, derin bağlantı, kamera, dosya sistemi, güvenli depolama gibi kurumsal gereksinimlerin %95'ini kapsar. Yalnızca Expo'da bulunmayan özel yerel modüllere ihtiyacınız varsa veya eski yerel kodla entegrasyon yapıyorsanız React Native'i çıplak olarak kullanın. Expo ile geliştirme deneyimi önemli ölçüde daha iyi ve EAS Build, yerel yapıların karmaşıklığını ele alıyor.
Next.js web uygulaması ile Expo mobil uygulaması arasında kodu nasıl paylaşırım?
İş mantığı, türler ve API istemcileri için paylaşılan paketlerle bir Turborepo monorepo kullanın. React Native, tüm tarayıcı/Node.js API'lerini desteklemez, bu nedenle paylaşılan paketlerde neler olduğuna dikkat edin. İyi çalışan bir model: türleri, doğrulama şemalarını (Zod) ve API istemci işlevlerini paylaşın, ancak kullanıcı arayüzü bileşenlerini ayrı tutun. Next.js 16'nın Uygulama Yönlendiricisi ve Expo Yönlendiricisi, benzer zihinsel modellerle dosya tabanlı yönlendirmeyi kullanır ve paralel uygulamaların sürdürülmesini kolaylaştırır.
Kimlik doğrulama geri aramaları için derin bağlantıyı nasıl hallederim?
URI şemanızı scheme altında app.json'da yapılandırın (örneğin, "scheme": "ecosire"). Kimlik doğrulama sağlayıcınızda ecosire://auth/callback'ü yönlendirme URI'si olarak kaydedin. Uygulamanızda gelen URL'leri işlemek için expo-linking kullanın. Gerçek URL'ler gibi çalışan evrensel bağlantılar (iOS) ve uygulama bağlantıları (Android) için, alanınızdan sunulan apple-app-site-association ve assetlinks.json dosyaları gibi ek alan adı doğrulamasına ihtiyacınız vardır.
EAS'te uygulama sürümü oluşturmayı yönetmenin en iyi yolu nedir?
eas.json'de "appVersionSource": "remote"'ı ayarlayın ve üretim yapıları için "autoIncrement": true'yi etkinleştirin. Bu, EAS'nin yapı numaralarını otomatik olarak yönetmesine olanak tanıyarak, manuel olarak artırmayı unutmak gibi yaygın bir hatayı önler. İnsanların okuyabileceği sürümünüzü (1.2.3) app.json içinde tutun ve yapı numarasını (iOS buildNumber, Android versionCode) EAS'nin yönetmesine izin verin.
Geliştirme aşamasında anında bildirimleri nasıl test ederim?
Cihazınızın Expo push jetonuyla Expo'nun https://expo.dev/notifications adresindeki push bildirimi test aracını kullanın. Yerel geliştirme için expo-notifications, yerel bildirimleri arka uç olmadan tetikleyen bir scheduleNotificationAsync işlevi sağlar. Uçtan uca test için eas build --profile development ile bir geliştirme istemcisi oluşturun ve fiziksel bir cihaz kullanın.
Sonraki Adımlar
Güvenilir, emniyetli ve uygun ölçekte bakımı yapılabilir bir kurumsal mobil uygulama oluşturmak, temelin doğru atılmasını gerektirir; EAS Build yapılandırması, güvenli belirteç depolama, anında bildirim altyapısı ve çevrimdışı senkronizasyon, ilk günden itibaren dikkatli kurulum gerektirir.
ECOSIRE, Expo'yu kullanarak 11 ekranlı, sonsuz kaydırmalı, anında bildirimli ve EAS dağıtım yapılandırmalı mobil üretim uygulamaları geliştirmiştir. Mobil geliştirme uzmanlığına ihtiyacınız varsa geliştirme hizmetlerimizi keşfedin.
Yazan
ECOSIRE Research and Development Team
ECOSIRE'da kurumsal düzeyde dijital ürünler geliştiriyor. Odoo entegrasyonları, e-ticaret otomasyonu ve yapay zeka destekli iş çözümleri hakkında içgörüler paylaşıyor.
İlgili Makaleler
Data Mesh Architecture: Decentralized Data for Enterprise
A comprehensive guide to data mesh architecture—principles, implementation patterns, organizational requirements, and how it enables scalable, domain-driven data ownership.
ECOSIRE vs Big 4 Consultancies: Enterprise Quality, Startup Speed
How ECOSIRE delivers enterprise-grade ERP and digital transformation outcomes without Big 4 pricing, overhead, or timeline bloat. A direct comparison.
Generative AI in Enterprise Applications: Beyond Chatbots
Discover how generative AI is transforming enterprise applications beyond chatbots—from code generation to synthetic data, document intelligence, and process automation.