Building Enterprise Mobile Apps with Expo and React Native

Enterprise mobile app development with Expo and React Native: EAS Build, push notifications, offline support, deep linking, authentication, and App Store submission.

E
ECOSIRE Research and Development Team
|19 Mart 20269 dk okuma2.0k Kelime|

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-notifications hem ö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-authentication ile 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-resolver aracılığıyla çalışır
  • Hassas verileri hiçbir zaman AsyncStorage içinde saklamayın — belirteçler için expo-secure-store kullanı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.

E

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.

WhatsApp'ta Sohbet Et