Skip to content

React Native Rehberi (React Native Guide)

📌 Ne Zaman Kullanılır?

✅ Kullan: JavaScript/React bilen ekip, hızlı cross-platform MVP, kod paylaşımı (web+mobil) ⚠️ Dikkat: Yoğun native özellik gerekiyorsa bridge overhead olabilir ❌ Kullanma: Oyun, AR/VR, çok yoğun animasyon (native veya Flutter daha iyi) Önerilen stack: React Native + TypeScript + Expo + React Navigation + Zustand

React Native ile iOS ve Android için tek kod tabanından mobil uygulama geliştirme rehberi. (A guide to building mobile apps for iOS and Android from a single codebase with React Native.)


1) React Native Nedir? (What is React Native?)

React Native, Facebook tarafından geliştirilen, JavaScript ve React kullanarak native mobil uygulamalar oluşturmayı sağlayan açık kaynak bir framework'tür. (React Native is an open-source framework developed by Facebook that allows building native mobile applications using JavaScript and React.)

Temel özellikler (Key features):

  • Cross-platform — Tek kod tabanıyla iOS ve Android uygulaması (Single codebase for iOS and Android)
  • Native bileşenler — WebView değil, gerçek native UI bileşenleri render eder (Renders real native UI components, not WebView)
  • Hot Reload — Kod değişiklikleri anında yansır, hızlı geliştirme döngüsü (Instant code reflection, fast development cycle)
  • JavaScript ekosistemi — npm/yarn paketleri, React bilgisi transfer edilir (npm/yarn packages, React knowledge transfers)
  • Hermes engine — Meta'nın özel JS motoru, daha hızlı başlatma ve düşük bellek kullanımı (Meta's custom JS engine, faster startup and lower memory)

React Native vs Alternatifler (vs Alternatives)

Özellik (Feature)React NativeFlutterNative (Swift/Kotlin)
Dil (Language)JavaScript/TypeScriptDartSwift / Kotlin
UI RenderNative bileşenlerKendi render motoru (Skia)Native
Performansİyi (Good)Çok iyi (Very good)En iyi (Best)
Öğrenme eğrisi (Learning curve)Düşük (React bilenler için)OrtaYüksek (2 platform)
Kod paylaşımı (Code sharing)~90-95%~95%0%
EkosistemÇok geniş (Very large)Büyüyen (Growing)Platform-specific
Hot ReloadSınırlı (Limited)
Web desteğiReact Native WebFlutter Web

Expo vs Bare Workflow Karşılaştırma (Expo vs Bare Workflow Comparison)

Özellik (Feature)Expo (Managed)Expo (Dev Build)Bare Workflow
Kurulum zorluğu (Setup difficulty)Çok kolayKolayOrta-Zor
Native modül erişimiSınırlı (Expo SDK)Tam (Full)Tam (Full)
OTA güncellemeler (OTA updates)✅ EAS Update✅ EAS UpdateManuel (expo-updates)
Build süreciEAS Build (bulut)EAS Build / LokalLokal (Xcode/Android Studio)
Xcode/Android Studio gerekli mi?❌ (EAS ile)✅ (lokal build)
EjectingArtık gerekli değil
Custom native kod✅ Config plugins✅ Direkt erişim
Tavsiye edilen durumPrototip, MVP, basit uygulamaÜretim uygulamalarıÇok özel native ihtiyaçlar

Tavsiye (Recommendation): Expo (Dev Build) ile başlayın. %95+ kullanım senaryosunu karşılar. (Start with Expo Dev Build. It covers 95%+ of use cases.)


2) Kurulum (Installation)

Expo CLI ile Kurulum (Setup with Expo CLI)

bash
# Node.js 18+ gerekli (Node.js 18+ required)
# npx ile Expo projesi oluştur (Create an Expo project with npx)
npx create-expo-app@latest my-app --template blank-typescript
cd my-app

# Geliştirme sunucusunu başlat (Start development server)
npx expo start

# Expo Go uygulamasını telefonuna indir ve QR kodu tara
# (Download Expo Go app on your phone and scan QR code)
bash
# EAS CLI kur (Install EAS CLI)
npm install -g eas-cli

# EAS ile giriş yap (Login with EAS)
eas login

# Dev build yapılandır (Configure dev build)
eas build:configure

# iOS dev build (simulator)
eas build --profile development --platform ios

# Android dev build (emulator)
eas build --profile development --platform android

# Dev client ile başlat (Start with dev client)
npx expo start --dev-client

React Native CLI ile Kurulum (Setup with React Native CLI)

bash
# React Native CLI projesi oluştur (Create React Native CLI project)
npx @react-native-community/cli init MyApp --template react-native-template-typescript
cd MyApp

# iOS bağımlılıkları (iOS dependencies — macOS only)
cd ios && pod install && cd ..

# Uygulamayı çalıştır (Run the app)
npx react-native run-android   # Android
npx react-native run-ios       # iOS (sadece macOS)

Android Studio Kurulumu (Android Studio Setup)

bash
# 1. Android Studio indir: https://developer.android.com/studio
# 2. SDK Manager'dan kur (Install from SDK Manager):
#    - Android SDK Platform 34
#    - Android SDK Build-Tools 34
#    - Android Emulator
#    - Android SDK Platform-Tools

# 3. Ortam değişkenleri (~/.bashrc veya ~/.zshrc)
export ANDROID_HOME=$HOME/Android/Sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin

# 4. Emulator oluştur (Create emulator)
# Android Studio → Virtual Device Manager → Create Device
# Pixel 7 → System Image: API 34 → Finish

# 5. Emulator başlat (Start emulator)
emulator -avd Pixel_7_API_34

Xcode Kurulumu (Xcode Setup — macOS Only)

bash
# 1. App Store'dan Xcode indir (Download Xcode from App Store)
# 2. Command Line Tools kur (Install Command Line Tools)
xcode-select --install

# 3. CocoaPods kur (Install CocoaPods)
sudo gem install cocoapods

# 4. iOS Simulator açmak için (To open iOS Simulator):
# Xcode → Open Developer Tool → Simulator
# veya terminal:
open -a Simulator

TypeScript Yapılandırması (TypeScript Configuration)

json
// tsconfig.json
{
  "extends": "expo/tsconfig.base",
  "compilerOptions": {
    "strict": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@screens/*": ["src/screens/*"],
      "@hooks/*": ["src/hooks/*"],
      "@utils/*": ["src/utils/*"],
      "@services/*": ["src/services/*"],
      "@types/*": ["src/types/*"],
      "@assets/*": ["assets/*"]
    }
  },
  "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
}

3) Proje Yapısı (Project Structure)

my-app/
├── app.json                  # Expo yapılandırması (Expo config)
├── App.tsx                   # Giriş noktası (Entry point)
├── tsconfig.json             # TypeScript ayarları
├── babel.config.js           # Babel yapılandırması
├── eas.json                  # EAS Build yapılandırması
├── assets/                   # Statik dosyalar (Static files)
│   ├── images/
│   ├── fonts/
│   └── icons/
├── src/
│   ├── components/           # Yeniden kullanılabilir bileşenler (Reusable components)
│   │   ├── ui/               # Genel UI bileşenleri (General UI)
│   │   │   ├── Button.tsx
│   │   │   ├── Input.tsx
│   │   │   ├── Card.tsx
│   │   │   └── Loading.tsx
│   │   └── shared/           # Paylaşılan bileşenler (Shared)
│   │       ├── Header.tsx
│   │       └── Avatar.tsx
│   ├── screens/              # Ekranlar (Screens)
│   │   ├── auth/
│   │   │   ├── LoginScreen.tsx
│   │   │   └── RegisterScreen.tsx
│   │   ├── home/
│   │   │   └── HomeScreen.tsx
│   │   └── profile/
│   │       └── ProfileScreen.tsx
│   ├── navigation/           # Navigasyon yapılandırması (Navigation config)
│   │   ├── AppNavigator.tsx
│   │   ├── AuthNavigator.tsx
│   │   └── types.ts
│   ├── hooks/                # Custom hook'lar
│   │   ├── useAuth.ts
│   │   ├── useTheme.ts
│   │   └── useApi.ts
│   ├── services/             # API servisleri (API services)
│   │   ├── api.ts
│   │   ├── authService.ts
│   │   └── userService.ts
│   ├── store/                # State management
│   │   ├── useAuthStore.ts
│   │   └── useAppStore.ts
│   ├── utils/                # Yardımcı fonksiyonlar (Helper functions)
│   │   ├── formatters.ts
│   │   ├── validators.ts
│   │   └── constants.ts
│   ├── types/                # TypeScript tipleri (TypeScript types)
│   │   ├── api.ts
│   │   ├── navigation.ts
│   │   └── models.ts
│   └── theme/                # Tema yapılandırması (Theme config)
│       ├── colors.ts
│       ├── spacing.ts
│       └── typography.ts
└── __tests__/                # Testler (Tests)
    ├── components/
    └── screens/

app.json Yapılandırması (app.json Configuration)

json
{
  "expo": {
    "name": "MyApp",
    "slug": "my-app",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./assets/icon.png",
    "userInterfaceStyle": "automatic",
    "splash": {
      "image": "./assets/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "assetBundlePatterns": ["**/*"],
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "com.yourcompany.myapp",
      "buildNumber": "1"
    },
    "android": {
      "adaptiveIcon": {
        "foregroundImage": "./assets/adaptive-icon.png",
        "backgroundColor": "#ffffff"
      },
      "package": "com.yourcompany.myapp",
      "versionCode": 1
    },
    "plugins": [
      "expo-router",
      "expo-secure-store",
      [
        "expo-camera",
        { "cameraPermission": "Fotoğraf çekmek için kamera erişimi gerekli." }
      ]
    ]
  }
}

4) Temel Bileşenler (Core Components)

View — Temel Konteyner (Basic Container)

tsx
import { View, StyleSheet } from 'react-native';

// View = web'deki <div> karşılığı (View is the equivalent of <div> on web)
function CardContainer({ children }: { children: React.ReactNode }) {
  return <View style={styles.card}>{children}</View>;
}

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 16,
    marginVertical: 8,
    // Gölge (Shadow) — Platform-specific
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3, // Android gölge (Android shadow)
  },
});

Text — Metin Gösterimi (Text Display)

tsx
import { Text, StyleSheet } from 'react-native';

// ⚠️ React Native'de tüm metinler <Text> içinde olmalı
// (All text must be inside <Text> in React Native)
function Typography() {
  return (
    <View>
      <Text style={styles.title}>Başlık (Title)</Text>
      <Text style={styles.body}>
        Normal metin. <Text style={styles.bold}>Kalın metin.</Text>
      </Text>
      <Text style={styles.body} numberOfLines={2} ellipsizeMode="tail">
        Çok uzun metin burada kesilir... (Long text will be truncated here...)
      </Text>
      <Text style={styles.link} onPress={() => console.log('tıklandı')}>
        Tıklanabilir metin (Clickable text)
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  title: { fontSize: 24, fontWeight: 'bold', color: '#1a1a1a', marginBottom: 8 },
  body: { fontSize: 16, color: '#333', lineHeight: 24 },
  bold: { fontWeight: '700' },
  link: { fontSize: 16, color: '#007AFF', textDecorationLine: 'underline' },
});

ScrollView — Kaydırılabilir Alan (Scrollable Area)

tsx
import { ScrollView, RefreshControl } from 'react-native';

function ScrollableContent() {
  const [refreshing, setRefreshing] = useState(false);

  const onRefresh = async () => {
    setRefreshing(true);
    await fetchData();
    setRefreshing(false);
  };

  return (
    <ScrollView
      style={{ flex: 1 }}
      contentContainerStyle={{ padding: 16 }}
      showsVerticalScrollIndicator={false}
      keyboardShouldPersistTaps="handled" // Klavye açıkken dokunma (Touch while keyboard open)
      refreshControl={
        <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
      }
    >
      {/* İçerik buraya (Content here) */}
    </ScrollView>
  );
}

// ⚠️ ScrollView tüm içeriği render eder — uzun listeler için FlatList kullan
// (ScrollView renders all content — use FlatList for long lists)

FlatList — Performanslı Liste (Performant List)

tsx
import { FlatList, View, Text, StyleSheet } from 'react-native';

interface User {
  id: string;
  name: string;
  email: string;
}

function UserList() {
  const [users, setUsers] = useState<User[]>([]);
  const [refreshing, setRefreshing] = useState(false);

  const renderItem = ({ item, index }: { item: User; index: number }) => (
    <View style={styles.userCard}>
      <Text style={styles.userName}>{item.name}</Text>
      <Text style={styles.userEmail}>{item.email}</Text>
    </View>
  );

  const renderEmpty = () => (
    <View style={styles.emptyContainer}>
      <Text style={styles.emptyText}>Kullanıcı bulunamadı (No users found)</Text>
    </View>
  );

  const renderSeparator = () => <View style={styles.separator} />;

  const renderHeader = () => (
    <Text style={styles.headerText}>Kullanıcılar (Users)</Text>
  );

  const renderFooter = () => (
    <Text style={styles.footerText}>{users.length} kullanıcı (users)</Text>
  );

  return (
    <FlatList
      data={users}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
      ListEmptyComponent={renderEmpty}
      ItemSeparatorComponent={renderSeparator}
      ListHeaderComponent={renderHeader}
      ListFooterComponent={renderFooter}
      refreshing={refreshing}
      onRefresh={() => { /* yenileme mantığı (refresh logic) */ }}
      onEndReached={() => { /* daha fazla yükle (load more) */ }}
      onEndReachedThreshold={0.5}
      showsVerticalScrollIndicator={false}
      initialNumToRender={10}
      maxToRenderPerBatch={5}
      windowSize={5}
    />
  );
}

const styles = StyleSheet.create({
  userCard: { padding: 16, backgroundColor: '#fff' },
  userName: { fontSize: 16, fontWeight: '600' },
  userEmail: { fontSize: 14, color: '#666', marginTop: 4 },
  separator: { height: 1, backgroundColor: '#e0e0e0' },
  emptyContainer: { padding: 40, alignItems: 'center' },
  emptyText: { fontSize: 16, color: '#999' },
  headerText: { fontSize: 20, fontWeight: 'bold', padding: 16 },
  footerText: { padding: 16, textAlign: 'center', color: '#999' },
});

SectionList — Gruplu Liste (Grouped List)

tsx
import { SectionList } from 'react-native';

const DATA = [
  { title: 'Frontend', data: ['React', 'Vue', 'Angular'] },
  { title: 'Backend', data: ['Node.js', 'Laravel', 'Django'] },
  { title: 'Mobile', data: ['React Native', 'Flutter', 'Swift'] },
];

function GroupedList() {
  return (
    <SectionList
      sections={DATA}
      keyExtractor={(item, index) => item + index}
      renderItem={({ item }) => <Text style={styles.item}>{item}</Text>}
      renderSectionHeader={({ section: { title } }) => (
        <Text style={styles.sectionHeader}>{title}</Text>
      )}
    />
  );
}

TouchableOpacity & Pressable — Dokunmatik Alanlar (Touchable Areas)

tsx
import { TouchableOpacity, Pressable, StyleSheet } from 'react-native';

// TouchableOpacity — basit dokunma efekti (simple touch effect)
function SimpleButton({ title, onPress }: { title: string; onPress: () => void }) {
  return (
    <TouchableOpacity
      style={styles.button}
      onPress={onPress}
      activeOpacity={0.7}
      disabled={false}
    >
      <Text style={styles.buttonText}>{title}</Text>
    </TouchableOpacity>
  );
}

// Pressable — daha gelişmiş dokunma olayları (more advanced touch events)
function AdvancedButton({ title, onPress }: { title: string; onPress: () => void }) {
  return (
    <Pressable
      style={({ pressed }) => [
        styles.button,
        pressed && styles.buttonPressed,
      ]}
      onPress={onPress}
      onLongPress={() => console.log('Uzun basıldı (Long pressed)')}
      delayLongPress={500}
      hitSlop={10} // Dokunma alanını genişlet (Expand touch area)
    >
      {({ pressed }) => (
        <Text style={[styles.buttonText, pressed && { opacity: 0.7 }]}>
          {title}
        </Text>
      )}
    </Pressable>
  );
}

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
    alignItems: 'center',
  },
  buttonPressed: { backgroundColor: '#0056b3' },
  buttonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
});

Image — Görsel (Image)

tsx
import { Image, ImageBackground, StyleSheet } from 'react-native';

function ImageExamples() {
  return (
    <View>
      {/* Yerel görsel (Local image) */}
      <Image source={require('../assets/images/logo.png')} style={styles.logo} />

      {/* Uzak görsel (Remote image) — boyut zorunlu (dimensions required) */}
      <Image
        source={{ uri: 'https://example.com/photo.jpg' }}
        style={styles.remoteImage}
        resizeMode="cover"         // cover | contain | stretch | center
        defaultSource={require('../assets/placeholder.png')} // iOS placeholder
        onError={(e) => console.log('Görsel yüklenemedi (Image load failed)')}
      />

      {/* Arka plan görseli (Background image) */}
      <ImageBackground
        source={require('../assets/bg.png')}
        style={styles.backgroundImage}
        imageStyle={{ borderRadius: 12 }}
      >
        <Text style={{ color: '#fff' }}>Arka plan üzerinde metin</Text>
      </ImageBackground>
    </View>
  );
}

// Performanslı görsel kütüphanesi (Performant image library):
// expo-image veya react-native-fast-image önerilir
// (expo-image or react-native-fast-image recommended)
import { Image as ExpoImage } from 'expo-image';

function OptimizedImage() {
  return (
    <ExpoImage
      source="https://example.com/photo.jpg"
      style={{ width: 200, height: 200 }}
      contentFit="cover"
      placeholder={blurhash}           // Blurhash placeholder
      transition={300}                  // Geçiş animasyonu (Transition animation)
      cachePolicy="memory-disk"         // Önbellek politikası (Cache policy)
    />
  );
}

const styles = StyleSheet.create({
  logo: { width: 100, height: 100 },
  remoteImage: { width: '100%', height: 200, borderRadius: 8 },
  backgroundImage: { width: '100%', height: 200, justifyContent: 'center', alignItems: 'center' },
});

TextInput — Metin Girişi (Text Input)

tsx
import { TextInput, StyleSheet } from 'react-native';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const passwordRef = useRef<TextInput>(null);

  return (
    <View>
      <TextInput
        style={styles.input}
        placeholder="E-posta (Email)"
        placeholderTextColor="#999"
        value={email}
        onChangeText={setEmail}
        keyboardType="email-address"
        autoCapitalize="none"
        autoCorrect={false}
        returnKeyType="next"
        onSubmitEditing={() => passwordRef.current?.focus()}
        textContentType="emailAddress"   // iOS otomatik doldurma (iOS autofill)
        autoComplete="email"             // Android otomatik doldurma
      />

      <TextInput
        ref={passwordRef}
        style={styles.input}
        placeholder="Şifre (Password)"
        placeholderTextColor="#999"
        value={password}
        onChangeText={setPassword}
        secureTextEntry                  // Şifre gizleme (Password hiding)
        returnKeyType="done"
        textContentType="password"
        autoComplete="password"
      />

      {/* Çok satırlı giriş (Multiline input) */}
      <TextInput
        style={[styles.input, styles.textArea]}
        placeholder="Açıklama (Description)"
        multiline
        numberOfLines={4}
        maxLength={500}
        textAlignVertical="top"          // Android için (For Android)
      />
    </View>
  );
}

const styles = StyleSheet.create({
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
    marginBottom: 12,
    backgroundColor: '#fff',
  },
  textArea: { height: 120 },
});
tsx
import { Modal, View, Text, Pressable, StyleSheet } from 'react-native';

function ConfirmDialog({ visible, onClose, onConfirm, message }: {
  visible: boolean;
  onClose: () => void;
  onConfirm: () => void;
  message: string;
}) {
  return (
    <Modal
      visible={visible}
      transparent
      animationType="fade"        // none | slide | fade
      onRequestClose={onClose}    // Android geri tuşu (Android back button)
    >
      <View style={styles.overlay}>
        <View style={styles.modalContent}>
          <Text style={styles.modalMessage}>{message}</Text>
          <View style={styles.modalActions}>
            <Pressable style={styles.cancelBtn} onPress={onClose}>
              <Text style={styles.cancelText}>İptal (Cancel)</Text>
            </Pressable>
            <Pressable style={styles.confirmBtn} onPress={onConfirm}>
              <Text style={styles.confirmText}>Onayla (Confirm)</Text>
            </Pressable>
          </View>
        </View>
      </View>
    </Modal>
  );
}

const styles = StyleSheet.create({
  overlay: {
    flex: 1, backgroundColor: 'rgba(0,0,0,0.5)',
    justifyContent: 'center', alignItems: 'center',
  },
  modalContent: {
    backgroundColor: '#fff', borderRadius: 16,
    padding: 24, width: '85%', maxWidth: 400,
  },
  modalMessage: { fontSize: 16, marginBottom: 24, textAlign: 'center' },
  modalActions: { flexDirection: 'row', justifyContent: 'flex-end', gap: 12 },
  cancelBtn: { paddingHorizontal: 20, paddingVertical: 10 },
  confirmBtn: {
    paddingHorizontal: 20, paddingVertical: 10,
    backgroundColor: '#007AFF', borderRadius: 8,
  },
  cancelText: { color: '#666', fontSize: 16 },
  confirmText: { color: '#fff', fontSize: 16, fontWeight: '600' },
});

ActivityIndicator — Yükleme Göstergesi (Loading Indicator)

tsx
import { ActivityIndicator, View, StyleSheet } from 'react-native';

// Basit kullanım (Simple usage)
function LoadingSpinner() {
  return <ActivityIndicator size="large" color="#007AFF" />;
}

// Tam ekran yükleme (Full screen loading)
function FullScreenLoader({ visible }: { visible: boolean }) {
  if (!visible) return null;
  return (
    <View style={styles.loaderOverlay}>
      <View style={styles.loaderBox}>
        <ActivityIndicator size="large" color="#007AFF" />
        <Text style={styles.loaderText}>Yükleniyor... (Loading...)</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  loaderOverlay: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'rgba(0,0,0,0.3)',
    justifyContent: 'center', alignItems: 'center', zIndex: 999,
  },
  loaderBox: {
    backgroundColor: '#fff', borderRadius: 12, padding: 24, alignItems: 'center',
  },
  loaderText: { marginTop: 12, fontSize: 14, color: '#666' },
});

5) Stil (Styling)

StyleSheet API

tsx
import { StyleSheet, View, Text } from 'react-native';

// ✅ StyleSheet.create kullan — performans optimizasyonu sağlar
// (Use StyleSheet.create — provides performance optimization)
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  // Birden fazla stil birleştirme (Combining multiple styles)
  // <View style={[styles.container, styles.centered]} />
  centered: {
    justifyContent: 'center',
    alignItems: 'center',
  },
});

// Dinamik stil (Dynamic style)
function DynamicStyle({ isActive }: { isActive: boolean }) {
  return (
    <View style={[
      styles.container,
      { backgroundColor: isActive ? '#e0f0ff' : '#f5f5f5' },
    ]}>
      <Text>Dinamik stil örneği (Dynamic style example)</Text>
    </View>
  );
}

Flexbox — Web'den Farklar (Differences from Web)

tsx
// ⚠️ React Native Flexbox varsayılanları web'den farklı!
// (React Native Flexbox defaults differ from web!)

const flexExamples = StyleSheet.create({
  // 1. flexDirection varsayılanı 'column' (web'de 'row')
  //    (Default flexDirection is 'column', in web it's 'row')
  column: {
    flexDirection: 'column',  // ← varsayılan (default)
  },
  row: {
    flexDirection: 'row',
  },

  // 2. flex sadece sayı alır, 'flex: 1 0 auto' yok
  //    (flex only takes a number, no 'flex: 1 0 auto')
  fillSpace: {
    flex: 1,
  },

  // 3. position varsayılanı 'relative' (web ile aynı)
  //    position: 'absolute' var, ama 'fixed' yok
  //    (position: 'absolute' exists, but no 'fixed')
  absolutePosition: {
    position: 'absolute',
    top: 0, left: 0, right: 0, bottom: 0,
  },

  // 4. gap desteği var (gap support is available)
  withGap: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 8,
    rowGap: 12,
    columnGap: 8,
  },

  // 5. Yüzde değerleri string olarak (Percentage values as strings)
  halfWidth: {
    width: '50%',
    height: '100%',
  },
});

Yaygın Düzen Kalıpları (Common Layout Patterns)

tsx
const layoutPatterns = StyleSheet.create({
  // Dikey ortalama (Vertical centering)
  centerVertical: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },

  // Üst-alt sabitleme (Top-bottom pinning)
  spaceBetween: {
    flex: 1,
    justifyContent: 'space-between',
  },

  // Yatay satır, ortala (Horizontal row, centered)
  rowCenter: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },

  // Kart düzeni (Card layout)
  card: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 16,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8,
    elevation: 4,
  },

  // SafeArea — çentikli cihazlar için (for notched devices)
  safeArea: {
    flex: 1,
    paddingTop: 0, // SafeAreaView kullan (use SafeAreaView)
  },
});

Platform.select — Platforma Özel Stil (Platform-Specific Styles)

tsx
import { Platform, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    ...Platform.select({
      ios: {
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.1,
        shadowRadius: 4,
      },
      android: {
        elevation: 4,
      },
    }),
  },
  text: {
    fontFamily: Platform.select({
      ios: 'System',
      android: 'Roboto',
    }),
    fontSize: 16,
    // Platform kontrolü (Platform check)
    paddingTop: Platform.OS === 'ios' ? 20 : 0,
  },
});

// Platform versiyonu kontrolü (Platform version check)
if (Platform.OS === 'android' && Platform.Version >= 33) {
  // Android 13+ özel mantık (Android 13+ specific logic)
}

Boyutlar ve Responsive Tasarım (Dimensions and Responsive Design)

tsx
import { Dimensions, useWindowDimensions, PixelRatio } from 'react-native';

function ResponsiveComponent() {
  // ✅ Hook — ekran boyutu değiştiğinde güncellenir (updates on screen resize)
  const { width, height, fontScale } = useWindowDimensions();
  const isTablet = width >= 768;

  return (
    <View style={{
      flexDirection: isTablet ? 'row' : 'column',
      padding: isTablet ? 32 : 16,
    }}>
      <Text style={{ fontSize: 16 / fontScale }}>Responsive metin</Text>
    </View>
  );
}

// Piksel yoğunluğu (Pixel density)
const pixelDensity = PixelRatio.get();        // 1, 1.5, 2, 3, 3.5
const fontScale = PixelRatio.getFontScale();   // Yazı tipi ölçeği
const dp = PixelRatio.roundToNearestPixel(18.3); // En yakın piksele yuvarla

6) Navigasyon (Navigation)

Kurulum (Installation)

bash
# React Navigation temel paketleri (core packages)
npm install @react-navigation/native
npm install @react-navigation/native-stack
npm install @react-navigation/bottom-tabs
npm install @react-navigation/drawer

# Expo bağımlılıkları (Expo dependencies)
npx expo install react-native-screens react-native-safe-area-context

# Bare workflow bağımlılıkları (Bare workflow dependencies)
npm install react-native-screens react-native-safe-area-context
# iOS: cd ios && pod install
tsx
// src/navigation/types.ts
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import type { CompositeScreenProps } from '@react-navigation/native';

// Stack navigasyon parametreleri (Stack navigation params)
export type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
  Settings: undefined;
  ProductDetail: { productId: string; productName: string };
};

// Tab navigasyon parametreleri (Tab navigation params)
export type MainTabParamList = {
  HomeTab: undefined;
  SearchTab: undefined;
  CartTab: undefined;
  ProfileTab: undefined;
};

// Ekran prop tipleri (Screen prop types)
export type HomeScreenProps = NativeStackScreenProps<RootStackParamList, 'Home'>;
export type ProfileScreenProps = NativeStackScreenProps<RootStackParamList, 'Profile'>;

// İç içe navigasyon prop tipi (Nested navigation prop type)
export type HomeTabProps = CompositeScreenProps<
  BottomTabScreenProps<MainTabParamList, 'HomeTab'>,
  NativeStackScreenProps<RootStackParamList>
>;

Stack Navigator

tsx
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { RootStackParamList } from './types';

const Stack = createNativeStackNavigator<RootStackParamList>();

function AppStack() {
  return (
    <Stack.Navigator
      initialRouteName="Home"
      screenOptions={{
        headerStyle: { backgroundColor: '#007AFF' },
        headerTintColor: '#fff',
        headerTitleStyle: { fontWeight: 'bold' },
        animation: 'slide_from_right',
        headerBackTitle: 'Geri',  // iOS geri butonu metni (iOS back button text)
      }}
    >
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{ title: 'Ana Sayfa', headerShown: false }}
      />
      <Stack.Screen
        name="Profile"
        component={ProfileScreen}
        options={({ route }) => ({ title: `Profil: ${route.params.userId}` })}
      />
      <Stack.Screen
        name="ProductDetail"
        component={ProductDetailScreen}
        options={{
          presentation: 'modal',        // Modal görünüm (Modal presentation)
          animation: 'slide_from_bottom',
        }}
      />
    </Stack.Navigator>
  );
}

Bottom Tab Navigator

tsx
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';

const Tab = createBottomTabNavigator<MainTabParamList>();

function MainTabs() {
  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: ({ focused, color, size }) => {
          const icons: Record<string, string> = {
            HomeTab: focused ? 'home' : 'home-outline',
            SearchTab: focused ? 'search' : 'search-outline',
            CartTab: focused ? 'cart' : 'cart-outline',
            ProfileTab: focused ? 'person' : 'person-outline',
          };
          return <Ionicons name={icons[route.name] as any} size={size} color={color} />;
        },
        tabBarActiveTintColor: '#007AFF',
        tabBarInactiveTintColor: '#999',
        headerShown: false,
        tabBarStyle: {
          borderTopWidth: 0,
          elevation: 10,
          shadowOpacity: 0.1,
          paddingBottom: 4,
          height: 60,
        },
      })}
    >
      <Tab.Screen name="HomeTab" component={HomeScreen} options={{ title: 'Ana Sayfa' }} />
      <Tab.Screen name="SearchTab" component={SearchScreen} options={{ title: 'Arama' }} />
      <Tab.Screen
        name="CartTab"
        component={CartScreen}
        options={{
          title: 'Sepet',
          tabBarBadge: 3,  // Bildirim rozeti (Notification badge)
          tabBarBadgeStyle: { backgroundColor: '#FF3B30' },
        }}
      />
      <Tab.Screen name="ProfileTab" component={ProfileScreen} options={{ title: 'Profil' }} />
    </Tab.Navigator>
  );
}

Drawer Navigator

tsx
import { createDrawerNavigator } from '@react-navigation/drawer';

const Drawer = createDrawerNavigator();

function DrawerNavigation() {
  return (
    <Drawer.Navigator
      screenOptions={{
        drawerActiveBackgroundColor: '#e0f0ff',
        drawerActiveTintColor: '#007AFF',
        drawerLabelStyle: { fontSize: 16 },
        headerShown: true,
      }}
      drawerContent={(props) => <CustomDrawerContent {...props} />}
    >
      <Drawer.Screen name="Home" component={HomeScreen} />
      <Drawer.Screen name="Settings" component={SettingsScreen} />
    </Drawer.Navigator>
  );
}

İç İçe Navigasyon (Nested Navigation)

tsx
import { NavigationContainer } from '@react-navigation/native';

function AppNavigator() {
  const { isAuthenticated } = useAuth();

  return (
    <NavigationContainer>
      {isAuthenticated ? (
        <Stack.Navigator screenOptions={{ headerShown: false }}>
          <Stack.Screen name="MainTabs" component={MainTabs} />
          <Stack.Screen name="ProductDetail" component={ProductDetailScreen} />
          <Stack.Screen name="Checkout" component={CheckoutScreen} />
        </Stack.Navigator>
      ) : (
        <Stack.Navigator screenOptions={{ headerShown: false }}>
          <Stack.Screen name="Login" component={LoginScreen} />
          <Stack.Screen name="Register" component={RegisterScreen} />
          <Stack.Screen name="ForgotPassword" component={ForgotPasswordScreen} />
        </Stack.Navigator>
      )}
    </NavigationContainer>
  );
}
tsx
import { useNavigation, useRoute } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';

function HomeScreen() {
  const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();

  return (
    <View>
      {/* Ekrana git (Navigate to screen) */}
      <Button title="Profil" onPress={() => navigation.navigate('Profile', { userId: '123' })} />

      {/* Yığına ekle — her seferinde yeni ekran (Push — new screen each time) */}
      <Button title="Push" onPress={() => navigation.push('Profile', { userId: '456' })} />

      {/* Geri git (Go back) */}
      <Button title="Geri" onPress={() => navigation.goBack()} />

      {/* Kök ekrana git (Go to root) */}
      <Button title="Köke git" onPress={() => navigation.popToTop()} />

      {/* Parametreleri oku (Read params) */}
      {/* Başka ekranda: route.params.userId */}

      {/* Stack'i sıfırla — geri gidemez (Reset stack — can't go back) */}
      <Button title="Sıfırla" onPress={() => {
        navigation.reset({
          index: 0,
          routes: [{ name: 'Home' }],
        });
      }} />
    </View>
  );
}

Deep Linking

tsx
// app.json (Expo)
{
  "expo": {
    "scheme": "myapp",
    "android": {
      "intentFilters": [{
        "action": "VIEW",
        "data": [{ "scheme": "myapp" }, { "scheme": "https", "host": "myapp.com" }],
        "category": ["BROWSABLE", "DEFAULT"]
      }]
    },
    "ios": {
      "associatedDomains": ["applinks:myapp.com"]
    }
  }
}

// NavigationContainer linking yapılandırması (linking config)
const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],
  config: {
    screens: {
      MainTabs: {
        screens: {
          HomeTab: 'home',
          ProfileTab: 'profile',
        },
      },
      ProductDetail: 'product/:productId',
      Checkout: 'checkout',
    },
  },
};

function App() {
  return (
    <NavigationContainer linking={linking} fallback={<LoadingSpinner />}>
      <AppNavigator />
    </NavigationContainer>
  );
}

// Kullanım (Usage): myapp://product/123 → ProductDetail ekranı açılır

7) State Management

useState — Yerel Durum (Local State)

tsx
function Counter() {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState<User | null>(null);
  const [items, setItems] = useState<string[]>([]);

  // Önceki değere göre güncelleme (Update based on previous value)
  const increment = () => setCount(prev => prev + 1);

  // Nesne güncelleme (Object update)
  const updateUser = (name: string) =>
    setUser(prev => prev ? { ...prev, name } : null);

  // Dizi güncelleme (Array update)
  const addItem = (item: string) => setItems(prev => [...prev, item]);
  const removeItem = (index: number) =>
    setItems(prev => prev.filter((_, i) => i !== index));

  return <Text>{count}</Text>;
}

useReducer — Karmaşık Durum (Complex State)

tsx
type State = { count: number; step: number; history: number[] };
type Action =
  | { type: 'INCREMENT' }
  | { type: 'DECREMENT' }
  | { type: 'SET_STEP'; payload: number }
  | { type: 'RESET' };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + state.step,
        history: [...state.history, state.count + state.step],
      };
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - state.step,
        history: [...state.history, state.count - state.step],
      };
    case 'SET_STEP':
      return { ...state, step: action.payload };
    case 'RESET':
      return { count: 0, step: 1, history: [] };
    default:
      return state;
  }
}

function CounterWithReducer() {
  const [state, dispatch] = useReducer(reducer, { count: 0, step: 1, history: [] });
  return (
    <View>
      <Text>Sayaç: {state.count}</Text>
      <Button title="+" onPress={() => dispatch({ type: 'INCREMENT' })} />
    </View>
  );
}

Context API — Global Durum (Global State)

tsx
// src/contexts/ThemeContext.tsx
type Theme = 'light' | 'dark';
interface ThemeContextType {
  theme: Theme;
  toggleTheme: () => void;
  colors: typeof lightColors;
}

const lightColors = { bg: '#fff', text: '#000', primary: '#007AFF' };
const darkColors = { bg: '#1a1a1a', text: '#fff', primary: '#0A84FF' };

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>('light');
  const toggleTheme = () => setTheme(prev => prev === 'light' ? 'dark' : 'light');
  const colors = theme === 'light' ? lightColors : darkColors;

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme, colors }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) throw new Error('useTheme must be used within ThemeProvider');
  return context;
}

Zustand — Hafif State Yönetimi (Lightweight State Management)

bash
npm install zustand
tsx
// src/store/useAuthStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';

interface User {
  id: string;
  name: string;
  email: string;
}

interface AuthState {
  user: User | null;
  token: string | null;
  isAuthenticated: boolean;
  login: (user: User, token: string) => void;
  logout: () => void;
  updateProfile: (updates: Partial<User>) => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      user: null,
      token: null,
      isAuthenticated: false,

      login: (user, token) => set({
        user,
        token,
        isAuthenticated: true,
      }),

      logout: () => set({
        user: null,
        token: null,
        isAuthenticated: false,
      }),

      updateProfile: (updates) => set((state) => ({
        user: state.user ? { ...state.user, ...updates } : null,
      })),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

// Kullanım (Usage)
function ProfileScreen() {
  const { user, logout } = useAuthStore();
  // Seçici kullanım — gereksiz render'ları önler
  // (Selector usage — prevents unnecessary re-renders)
  const userName = useAuthStore((state) => state.user?.name);

  return (
    <View>
      <Text>{userName}</Text>
      <Button title="Çıkış" onPress={logout} />
    </View>
  );
}

Redux Toolkit

bash
npm install @reduxjs/toolkit react-redux
tsx
// src/store/slices/cartSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';

interface CartItem { id: string; name: string; price: number; quantity: number; }
interface CartState { items: CartItem[]; loading: boolean; error: string | null; }

const initialState: CartState = { items: [], loading: false, error: null };

export const fetchCart = createAsyncThunk('cart/fetch', async (userId: string) => {
  const response = await fetch(`/api/cart/${userId}`);
  return response.json();
});

const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    addItem: (state, action: PayloadAction<CartItem>) => {
      const existing = state.items.find(item => item.id === action.payload.id);
      if (existing) {
        existing.quantity += 1;
      } else {
        state.items.push({ ...action.payload, quantity: 1 });
      }
    },
    removeItem: (state, action: PayloadAction<string>) => {
      state.items = state.items.filter(item => item.id !== action.payload);
    },
    clearCart: (state) => { state.items = []; },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchCart.pending, (state) => { state.loading = true; })
      .addCase(fetchCart.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      })
      .addCase(fetchCart.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message ?? 'Hata oluştu';
      });
  },
});

export const { addItem, removeItem, clearCart } = cartSlice.actions;
export default cartSlice.reducer;

8) API Çağrıları (API Calls)

Fetch API

tsx
// src/services/api.ts
const BASE_URL = 'https://api.example.com';

async function request<T>(endpoint: string, options?: RequestInit): Promise<T> {
  const token = useAuthStore.getState().token;

  const response = await fetch(`${BASE_URL}${endpoint}`, {
    headers: {
      'Content-Type': 'application/json',
      ...(token && { Authorization: `Bearer ${token}` }),
      ...options?.headers,
    },
    ...options,
  });

  if (response.status === 401) {
    useAuthStore.getState().logout();
    throw new Error('Oturum süresi doldu (Session expired)');
  }

  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    throw new Error(error.message || `HTTP ${response.status} hatası`);
  }

  return response.json();
}

export const api = {
  get: <T>(endpoint: string) => request<T>(endpoint),
  post: <T>(endpoint: string, data: unknown) =>
    request<T>(endpoint, { method: 'POST', body: JSON.stringify(data) }),
  put: <T>(endpoint: string, data: unknown) =>
    request<T>(endpoint, { method: 'PUT', body: JSON.stringify(data) }),
  delete: <T>(endpoint: string) =>
    request<T>(endpoint, { method: 'DELETE' }),
};

Axios ile API İstemcisi (API Client with Axios)

bash
npm install axios
tsx
// src/services/axiosClient.ts
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';

const axiosClient = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 15000,
  headers: { 'Content-Type': 'application/json' },
});

// İstek interceptor'ı (Request interceptor)
axiosClient.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const token = useAuthStore.getState().token;
    if (token) config.headers.Authorization = `Bearer ${token}`;
    return config;
  },
  (error) => Promise.reject(error)
);

// Yanıt interceptor'ı (Response interceptor)
axiosClient.interceptors.response.use(
  (response) => response,
  async (error: AxiosError) => {
    if (error.response?.status === 401) {
      useAuthStore.getState().logout();
    }
    return Promise.reject(error);
  }
);

export default axiosClient;

TanStack Query (React Query)

bash
npm install @tanstack/react-query
tsx
// src/providers/QueryProvider.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 2,
      staleTime: 5 * 60 * 1000,          // 5 dakika (5 minutes)
      gcTime: 10 * 60 * 1000,             // 10 dakika (10 minutes)
      refetchOnWindowFocus: false,         // Mobilde gereksiz (unnecessary on mobile)
      refetchOnReconnect: true,            // Bağlantı geri gelince yenile
    },
  },
});

export function QueryProvider({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
}

// Hook kullanımı (Hook usage)
import { useQuery, useMutation, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';

function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: () => api.get<User[]>('/users'),
  });
}

function useUser(id: string) {
  return useQuery({
    queryKey: ['users', id],
    queryFn: () => api.get<User>(`/users/${id}`),
    enabled: !!id,  // id yoksa sorgu çalışmaz (query won't run if no id)
  });
}

function useCreateUser() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (data: CreateUserDTO) => api.post<User>('/users', data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
    onError: (error) => {
      Alert.alert('Hata', error.message);
    },
  });
}

// Sonsuz kaydırma (Infinite scroll)
function useInfiniteUsers() {
  return useInfiniteQuery({
    queryKey: ['users', 'infinite'],
    queryFn: ({ pageParam = 1 }) => api.get<UserPage>(`/users?page=${pageParam}`),
    getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
    initialPageParam: 1,
  });
}

Yükleme ve Hata Durumları (Loading and Error States)

tsx
function UserListScreen() {
  const { data: users, isLoading, error, refetch, isRefetching } = useUsers();

  if (isLoading) return <FullScreenLoader visible />;

  if (error) {
    return (
      <View style={styles.errorContainer}>
        <Text style={styles.errorText}>
          Hata: {error.message} (Error: {error.message})
        </Text>
        <Button title="Tekrar Dene (Retry)" onPress={() => refetch()} />
      </View>
    );
  }

  return (
    <FlatList
      data={users}
      renderItem={({ item }) => <UserCard user={item} />}
      keyExtractor={(item) => item.id}
      refreshing={isRefetching}
      onRefresh={refetch}
      ListEmptyComponent={
        <Text style={styles.empty}>Kullanıcı bulunamadı (No users found)</Text>
      }
    />
  );
}

9) Veri Saklama (Data Storage)

AsyncStorage — Basit Anahtar-Değer (Simple Key-Value)

bash
npm install @react-native-async-storage/async-storage
tsx
import AsyncStorage from '@react-native-async-storage/async-storage';

// Kaydet (Store)
await AsyncStorage.setItem('username', 'fahri');
await AsyncStorage.setItem('settings', JSON.stringify({ theme: 'dark', lang: 'tr' }));

// Oku (Read)
const username = await AsyncStorage.getItem('username');
const settings = JSON.parse(await AsyncStorage.getItem('settings') || '{}');

// Sil (Remove)
await AsyncStorage.removeItem('username');

// Çoklu işlem (Multi operation)
await AsyncStorage.multiSet([['key1', 'val1'], ['key2', 'val2']]);
const values = await AsyncStorage.multiGet(['key1', 'key2']);

// Tüm anahtarlar (All keys)
const allKeys = await AsyncStorage.getAllKeys();

// Tümünü temizle (Clear all)
await AsyncStorage.clear();

SecureStore — Güvenli Saklama (Secure Storage)

bash
npx expo install expo-secure-store
tsx
import * as SecureStore from 'expo-secure-store';

// ✅ Token, şifre, hassas veri için kullan (Use for tokens, passwords, sensitive data)
// iOS: Keychain, Android: EncryptedSharedPreferences

// Kaydet (Store)
await SecureStore.setItemAsync('authToken', 'eyJhbGciOiJIUzI1NiIs...');

// Oku (Read)
const token = await SecureStore.getItemAsync('authToken');

// Sil (Remove)
await SecureStore.deleteItemAsync('authToken');

// Seçenekler (Options)
await SecureStore.setItemAsync('biometric_key', 'secret', {
  requireAuthentication: true,  // Biyometrik doğrulama gerektir (Require biometric auth)
});

// Kullanılabilirlik kontrolü (Availability check)
const available = await SecureStore.isAvailableAsync();

MMKV — Yüksek Performanslı Saklama (High-Performance Storage)

bash
npm install react-native-mmkv
tsx
import { MMKV } from 'react-native-mmkv';

// MMKV örneği oluştur (Create MMKV instance)
const storage = new MMKV({ id: 'app-storage' });

// Senkron işlemler — AsyncStorage'dan çok daha hızlı
// (Synchronous operations — much faster than AsyncStorage)
storage.set('username', 'fahri');
storage.set('count', 42);
storage.set('isLoggedIn', true);

const username = storage.getString('username');   // string | undefined
const count = storage.getNumber('count');           // number | undefined
const isLoggedIn = storage.getBoolean('isLoggedIn'); // boolean | undefined

storage.delete('username');
storage.clearAll();

// Tüm anahtarlar (All keys)
const keys = storage.getAllKeys();

// Zustand ile MMKV persist (MMKV persist with Zustand)
import { StateStorage } from 'zustand/middleware';

const zustandStorage: StateStorage = {
  getItem: (name) => storage.getString(name) ?? null,
  setItem: (name, value) => storage.set(name, value),
  removeItem: (name) => storage.delete(name),
};

Depolama Karşılaştırması (Storage Comparison)

Özellik (Feature)AsyncStorageSecureStoreMMKV
PerformansYavaş (Slow)Orta (Medium)Çok hızlı (Very fast)
Şifreleme (Encryption)✅ (OS-level)İsteğe bağlı (Optional)
Senkron (Synchronous)❌ (async)❌ (async)
Boyut sınırı (Size limit)~6MB (Android)~2KB/öğeSınırsız (Unlimited)
Kullanım alanı (Use case)Ayarlar, önbellekToken, şifreYüksek frekanslı veri
Expo desteği❌ (dev build gerekli)

10) İzinler ve Cihaz Erişimi (Permissions & Device Access)

İzin Yönetimi (Permission Management)

bash
# Expo ile (With Expo)
npx expo install expo-camera expo-location expo-notifications expo-media-library

# Bare workflow — react-native-permissions
npm install react-native-permissions

İzin Tablosu (Permission Table)

İzin (Permission)iOS (Info.plist)Android (AndroidManifest.xml)Expo Plugin
Kamera (Camera)NSCameraUsageDescriptionCAMERAexpo-camera
Galeri (Photo Library)NSPhotoLibraryUsageDescriptionREAD_MEDIA_IMAGESexpo-media-library
Konum (Location)NSLocationWhenInUseUsageDescriptionACCESS_FINE_LOCATIONexpo-location
Bildirim (Notifications)Otomatik (Automatic)POST_NOTIFICATIONS (API 33+)expo-notifications
Mikrofon (Microphone)NSMicrophoneUsageDescriptionRECORD_AUDIOexpo-av
Kişiler (Contacts)NSContactsUsageDescriptionREAD_CONTACTSexpo-contacts
Takvim (Calendar)NSCalendarsUsageDescriptionREAD_CALENDARexpo-calendar
Biyometrik (Biometric)NSFaceIDUsageDescriptionUSE_BIOMETRICexpo-local-authentication

Kamera Erişimi (Camera Access)

tsx
import { CameraView, useCameraPermissions } from 'expo-camera';

function CameraScreen() {
  const [permission, requestPermission] = useCameraPermissions();
  const [facing, setFacing] = useState<'front' | 'back'>('back');
  const cameraRef = useRef<CameraView>(null);

  if (!permission) return <View />;

  if (!permission.granted) {
    return (
      <View style={styles.container}>
        <Text>Kamera erişimi gerekli (Camera access required)</Text>
        <Button title="İzin Ver (Grant Permission)" onPress={requestPermission} />
      </View>
    );
  }

  const takePicture = async () => {
    if (cameraRef.current) {
      const photo = await cameraRef.current.takePictureAsync({
        quality: 0.8,
        base64: false,
      });
      console.log('Fotoğraf URI:', photo?.uri);
    }
  };

  return (
    <View style={{ flex: 1 }}>
      <CameraView ref={cameraRef} style={{ flex: 1 }} facing={facing}>
        <View style={styles.cameraControls}>
          <Pressable onPress={() => setFacing(f => f === 'back' ? 'front' : 'back')}>
            <Text style={styles.controlText}>Çevir (Flip)</Text>
          </Pressable>
          <Pressable onPress={takePicture}>
            <View style={styles.captureButton} />
          </Pressable>
        </View>
      </CameraView>
    </View>
  );
}

Konum Erişimi (Location Access)

tsx
import * as Location from 'expo-location';

function LocationScreen() {
  const [location, setLocation] = useState<Location.LocationObject | null>(null);

  useEffect(() => {
    (async () => {
      // İzin iste (Request permission)
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        Alert.alert('Hata', 'Konum izni reddedildi (Location permission denied)');
        return;
      }

      // Mevcut konum (Current location)
      const currentLocation = await Location.getCurrentPositionAsync({
        accuracy: Location.Accuracy.High,
      });
      setLocation(currentLocation);
    })();
  }, []);

  // Konum takibi (Location tracking)
  const startTracking = async () => {
    await Location.watchPositionAsync(
      { accuracy: Location.Accuracy.High, distanceInterval: 10 },
      (newLocation) => setLocation(newLocation)
    );
  };

  return (
    <View>
      {location && (
        <Text>
          Enlem: {location.coords.latitude}, Boylam: {location.coords.longitude}
        </Text>
      )}
    </View>
  );
}

Bildirimler (Notifications)

tsx
import * as Notifications from 'expo-notifications';
import { useEffect, useRef } from 'react';

// Bildirim yapılandırması (Notification configuration)
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: true,
  }),
});

function useNotifications() {
  const notificationListener = useRef<Notifications.Subscription>();

  useEffect(() => {
    registerForPushNotifications();

    // Bildirim dinleyicisi (Notification listener)
    notificationListener.current = Notifications.addNotificationReceivedListener(
      (notification) => {
        console.log('Bildirim alındı:', notification.request.content);
      }
    );

    return () => notificationListener.current?.remove();
  }, []);

  const registerForPushNotifications = async () => {
    const { status } = await Notifications.requestPermissionsAsync();
    if (status !== 'granted') return;

    const token = (await Notifications.getExpoPushTokenAsync()).data;
    console.log('Push token:', token);
    // Token'ı backend'e gönder (Send token to backend)
  };

  // Yerel bildirim gönder (Send local notification)
  const sendLocalNotification = async () => {
    await Notifications.scheduleNotificationAsync({
      content: {
        title: 'Hatırlatma (Reminder)',
        body: 'Uygulamayı kontrol etmeyi unutma! (Don\'t forget to check the app!)',
        data: { screen: 'Home' },
      },
      trigger: { seconds: 5 },
    });
  };

  return { sendLocalNotification };
}

Info.plist ve AndroidManifest Yapılandırması (Configuration)

xml
<!-- ios/MyApp/Info.plist — Expo app.json plugins ile otomatik eklenir -->
<key>NSCameraUsageDescription</key>
<string>Fotoğraf çekmek için kamera erişimi gereklidir.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Yakındaki yerleri göstermek için konum erişimi gereklidir.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Fotoğraf seçmek için galeri erişimi gereklidir.</string>
xml
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

11) Animasyon (Animation)

Animated API — Temel Animasyonlar (Basic Animations)

tsx
import { Animated, Easing } from 'react-native';

function FadeInView({ children }: { children: React.ReactNode }) {
  const fadeAnim = useRef(new Animated.Value(0)).current;
  const slideAnim = useRef(new Animated.Value(50)).current;

  useEffect(() => {
    Animated.parallel([
      Animated.timing(fadeAnim, {
        toValue: 1,
        duration: 500,
        useNativeDriver: true,     // ✅ Her zaman true kullan (Always use true)
      }),
      Animated.timing(slideAnim, {
        toValue: 0,
        duration: 500,
        easing: Easing.out(Easing.cubic),
        useNativeDriver: true,
      }),
    ]).start();
  }, []);

  return (
    <Animated.View style={{
      opacity: fadeAnim,
      transform: [{ translateY: slideAnim }],
    }}>
      {children}
    </Animated.View>
  );
}

// Döngüsel animasyon (Looping animation)
function SpinningLoader() {
  const spinValue = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.loop(
      Animated.timing(spinValue, {
        toValue: 1,
        duration: 1000,
        easing: Easing.linear,
        useNativeDriver: true,
      })
    ).start();
  }, []);

  const spin = spinValue.interpolate({
    inputRange: [0, 1],
    outputRange: ['0deg', '360deg'],
  });

  return (
    <Animated.View style={{ transform: [{ rotate: spin }] }}>
      <Text style={{ fontSize: 32 }}>⟳</Text>
    </Animated.View>
  );
}

// Yay animasyonu (Spring animation)
function BounceButton({ onPress }: { onPress: () => void }) {
  const scale = useRef(new Animated.Value(1)).current;

  const handlePress = () => {
    Animated.sequence([
      Animated.spring(scale, { toValue: 0.9, useNativeDriver: true }),
      Animated.spring(scale, { toValue: 1, friction: 3, useNativeDriver: true }),
    ]).start(() => onPress());
  };

  return (
    <Animated.View style={{ transform: [{ scale }] }}>
      <Pressable onPress={handlePress}>
        <Text style={styles.buttonText}>Bas (Press)</Text>
      </Pressable>
    </Animated.View>
  );
}

Reanimated — Gelişmiş Animasyonlar (Advanced Animations)

bash
npx expo install react-native-reanimated
# babel.config.js'e plugin ekle (add plugin to babel.config.js):
# plugins: ['react-native-reanimated/plugin']
tsx
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  withSpring,
  withRepeat,
  withSequence,
  interpolate,
  Easing,
  runOnJS,
} from 'react-native-reanimated';

function ReanimatedExample() {
  const offset = useSharedValue(0);
  const opacity = useSharedValue(1);

  const animatedStyles = useAnimatedStyle(() => ({
    transform: [{ translateX: offset.value }],
    opacity: opacity.value,
  }));

  const handlePress = () => {
    // Zamanlama animasyonu (Timing animation)
    offset.value = withTiming(200, {
      duration: 500,
      easing: Easing.bezier(0.25, 0.1, 0.25, 1),
    });

    // Yay animasyonu (Spring animation)
    offset.value = withSpring(200, {
      damping: 10,
      stiffness: 100,
    });

    // Tekrarlayan animasyon (Repeating animation)
    offset.value = withRepeat(
      withTiming(200, { duration: 500 }),
      -1,     // Sonsuz tekrar (Infinite repeat)
      true    // Ters yön (Reverse)
    );

    // Sıralı animasyon (Sequential animation)
    offset.value = withSequence(
      withTiming(100, { duration: 200 }),
      withTiming(-100, { duration: 200 }),
      withSpring(0)
    );
  };

  return (
    <Animated.View style={[styles.box, animatedStyles]}>
      <Pressable onPress={handlePress}>
        <Text>Animate</Text>
      </Pressable>
    </Animated.View>
  );
}

// Kaydırma bazlı animasyon (Scroll-based animation)
import Animated, { useAnimatedScrollHandler, useAnimatedStyle } from 'react-native-reanimated';

function AnimatedHeader() {
  const scrollY = useSharedValue(0);

  const scrollHandler = useAnimatedScrollHandler({
    onScroll: (event) => {
      scrollY.value = event.contentOffset.y;
    },
  });

  const headerStyle = useAnimatedStyle(() => ({
    height: interpolate(scrollY.value, [0, 100], [200, 60], 'clamp'),
    opacity: interpolate(scrollY.value, [0, 100], [1, 0.8], 'clamp'),
  }));

  return (
    <View style={{ flex: 1 }}>
      <Animated.View style={[styles.header, headerStyle]} />
      <Animated.ScrollView onScroll={scrollHandler} scrollEventThrottle={16}>
        {/* İçerik (Content) */}
      </Animated.ScrollView>
    </View>
  );
}

Lottie — JSON Animasyonları (JSON Animations)

bash
npx expo install lottie-react-native
tsx
import LottieView from 'lottie-react-native';

function SuccessAnimation() {
  const animationRef = useRef<LottieView>(null);

  return (
    <LottieView
      ref={animationRef}
      source={require('../assets/animations/success.json')}
      autoPlay
      loop={false}
      style={{ width: 200, height: 200 }}
      speed={1.5}
      onAnimationFinish={() => console.log('Animasyon bitti (Animation finished)')}
    />
  );
}

// Kontrollü animasyon (Controlled animation)
function ControlledLottie() {
  const progress = useSharedValue(0);

  return (
    <LottieView
      source={require('../assets/animations/loading.json')}
      progress={progress.value}
      style={{ width: 150, height: 150 }}
    />
  );
}

Shared Element Transition

bash
npm install react-native-shared-element react-navigation-shared-element
tsx
// Expo Router ile shared transition (Shared transition with Expo Router)
import { SharedTransition, withLayoutAnimation } from 'react-native-reanimated';

// Liste ekranı (List screen)
function ListScreen() {
  return (
    <FlatList
      data={items}
      renderItem={({ item }) => (
        <Link href={`/detail/${item.id}`} asChild>
          <Pressable>
            <Animated.Image
              sharedTransitionTag={`image-${item.id}`}
              source={{ uri: item.image }}
              style={{ width: 100, height: 100 }}
            />
          </Pressable>
        </Link>
      )}
    />
  );
}

// Detay ekranı (Detail screen)
function DetailScreen({ id }: { id: string }) {
  return (
    <Animated.Image
      sharedTransitionTag={`image-${id}`}
      source={{ uri: item.image }}
      style={{ width: '100%', height: 300 }}
    />
  );
}

12) Gesture — Dokunma Hareketleri (Touch Gestures)

Kurulum (Installation)

bash
npx expo install react-native-gesture-handler

Temel Gesture'lar (Basic Gestures)

tsx
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';

// Sürükle-bırak (Drag and drop)
function DraggableBox() {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const savedX = useSharedValue(0);
  const savedY = useSharedValue(0);

  const panGesture = Gesture.Pan()
    .onStart(() => {
      savedX.value = translateX.value;
      savedY.value = translateY.value;
    })
    .onUpdate((event) => {
      translateX.value = savedX.value + event.translationX;
      translateY.value = savedY.value + event.translationY;
    })
    .onEnd(() => {
      // Yerine geri dön (Snap back) — isteğe bağlı
      // translateX.value = withSpring(0);
      // translateY.value = withSpring(0);
    });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      { translateX: translateX.value },
      { translateY: translateY.value },
    ],
  }));

  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View style={[styles.box, animatedStyle]} />
    </GestureDetector>
  );
}

// Yakınlaştırma (Pinch to zoom)
function PinchableImage() {
  const scale = useSharedValue(1);
  const savedScale = useSharedValue(1);

  const pinchGesture = Gesture.Pinch()
    .onUpdate((event) => {
      scale.value = savedScale.value * event.scale;
    })
    .onEnd(() => {
      savedScale.value = scale.value;
      if (scale.value < 1) {
        scale.value = withSpring(1);
        savedScale.value = 1;
      }
    });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }));

  return (
    <GestureDetector gesture={pinchGesture}>
      <Animated.Image
        source={{ uri: 'https://example.com/photo.jpg' }}
        style={[{ width: 300, height: 300 }, animatedStyle]}
      />
    </GestureDetector>
  );
}

Swipe Aksiyonları (Swipe Actions)

tsx
import { Swipeable } from 'react-native-gesture-handler';

function SwipeableRow({ item, onDelete }: { item: Item; onDelete: () => void }) {
  const renderRightActions = (progress: Animated.AnimatedInterpolation<number>) => {
    const translateX = progress.interpolate({
      inputRange: [0, 1],
      outputRange: [100, 0],
    });

    return (
      <Animated.View style={[styles.deleteAction, { transform: [{ translateX }] }]}>
        <Pressable onPress={onDelete} style={styles.deleteButton}>
          <Text style={styles.deleteText}>Sil (Delete)</Text>
        </Pressable>
      </Animated.View>
    );
  };

  return (
    <Swipeable renderRightActions={renderRightActions} overshootRight={false}>
      <View style={styles.row}>
        <Text>{item.name}</Text>
      </View>
    </Swipeable>
  );
}

Bottom Sheet

bash
npm install @gorhom/bottom-sheet
tsx
import BottomSheet, { BottomSheetView, BottomSheetBackdrop } from '@gorhom/bottom-sheet';

function BottomSheetExample() {
  const bottomSheetRef = useRef<BottomSheet>(null);
  const snapPoints = useMemo(() => ['25%', '50%', '90%'], []);

  return (
    <View style={{ flex: 1 }}>
      <Button
        title="Sheet Aç (Open Sheet)"
        onPress={() => bottomSheetRef.current?.expand()}
      />

      <BottomSheet
        ref={bottomSheetRef}
        index={-1}                          // Başlangıçta kapalı (Initially closed)
        snapPoints={snapPoints}
        enablePanDownToClose
        backdropComponent={(props) => (
          <BottomSheetBackdrop {...props} disappearsOnIndex={-1} appearsOnIndex={0} />
        )}
        handleIndicatorStyle={{ backgroundColor: '#ccc' }}
      >
        <BottomSheetView style={{ padding: 16 }}>
          <Text style={styles.sheetTitle}>Seçenekler (Options)</Text>
          {/* İçerik (Content) */}
        </BottomSheetView>
      </BottomSheet>
    </View>
  );
}

Pull-to-Refresh

tsx
// FlatList ile zaten yerleşik (Already built-in with FlatList)
function PullToRefreshList() {
  const [refreshing, setRefreshing] = useState(false);

  const onRefresh = useCallback(async () => {
    setRefreshing(true);
    await fetchData();
    setRefreshing(false);
  }, []);

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      refreshing={refreshing}
      onRefresh={onRefresh}
      // Özel refresh göstergesi rengi (Custom refresh indicator color)
      refreshControl={
        <RefreshControl
          refreshing={refreshing}
          onRefresh={onRefresh}
          tintColor="#007AFF"           // iOS
          colors={['#007AFF']}         // Android
          progressBackgroundColor="#fff" // Android arka plan
        />
      }
    />
  );
}

13) Native Modüller (Native Modules)

Kamera — expo-camera (Camera)

tsx
import { CameraView, CameraType, useCameraPermissions } from 'expo-camera';

function CameraModule() {
  const [permission, requestPermission] = useCameraPermissions();
  const [facing, setFacing] = useState<CameraType>('back');
  const cameraRef = useRef<CameraView>(null);

  const takePicture = async () => {
    const photo = await cameraRef.current?.takePictureAsync({
      quality: 0.7,
      exif: true,
    });
    if (photo) {
      // Fotoğrafı galeriye kaydet (Save photo to gallery)
      await MediaLibrary.saveToLibraryAsync(photo.uri);
    }
  };

  const recordVideo = async () => {
    const video = await cameraRef.current?.recordAsync({ maxDuration: 30 });
    console.log('Video URI:', video?.uri);
  };

  if (!permission?.granted) {
    return <Button title="Kamera İzni Ver" onPress={requestPermission} />;
  }

  return (
    <CameraView ref={cameraRef} style={{ flex: 1 }} facing={facing}>
      <View style={styles.controls}>
        <Pressable onPress={takePicture}><Text>Çek (Capture)</Text></Pressable>
        <Pressable onPress={() => setFacing(f => f === 'back' ? 'front' : 'back')}>
          <Text>Çevir (Flip)</Text>
        </Pressable>
      </View>
    </CameraView>
  );
}

Biyometrik Kimlik Doğrulama (Biometric Authentication)

bash
npx expo install expo-local-authentication
tsx
import * as LocalAuthentication from 'expo-local-authentication';

async function authenticateWithBiometrics(): Promise<boolean> {
  // Donanım kontrolü (Hardware check)
  const hasHardware = await LocalAuthentication.hasHardwareAsync();
  if (!hasHardware) {
    Alert.alert('Hata', 'Biyometrik donanım bulunamadı (No biometric hardware found)');
    return false;
  }

  // Kayıtlı biyometrik var mı? (Is biometric enrolled?)
  const isEnrolled = await LocalAuthentication.isEnrolledAsync();
  if (!isEnrolled) {
    Alert.alert('Hata', 'Biyometrik kayıt bulunamadı (No biometric enrolled)');
    return false;
  }

  // Desteklenen tipler (Supported types)
  const supportedTypes = await LocalAuthentication.supportedAuthenticationTypesAsync();
  // 1: Parmak izi (Fingerprint), 2: Yüz tanıma (Facial recognition), 3: Iris

  // Kimlik doğrulama (Authenticate)
  const result = await LocalAuthentication.authenticateAsync({
    promptMessage: 'Kimliğinizi doğrulayın (Verify your identity)',
    cancelLabel: 'İptal (Cancel)',
    disableDeviceFallback: false,  // Şifre ile giriş seçeneği (PIN fallback option)
    fallbackLabel: 'Şifre Kullan (Use Password)',
  });

  return result.success;
}

// Kullanım (Usage)
function SecureScreen() {
  const [authenticated, setAuthenticated] = useState(false);

  useEffect(() => {
    authenticateWithBiometrics().then(setAuthenticated);
  }, []);

  if (!authenticated) return <Text>Kimlik doğrulama gerekli (Auth required)</Text>;
  return <Text>Güvenli içerik (Secure content)</Text>;
}

Görsel Seçici (Image Picker)

bash
npx expo install expo-image-picker
tsx
import * as ImagePicker from 'expo-image-picker';

async function pickImage() {
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsEditing: true,
    aspect: [1, 1],
    quality: 0.8,
  });

  if (!result.canceled) {
    const uri = result.assets[0].uri;
    // Görseli yükle (Upload image)
    await uploadImage(uri);
  }
}

async function takePhoto() {
  const { status } = await ImagePicker.requestCameraPermissionsAsync();
  if (status !== 'granted') return;

  const result = await ImagePicker.launchCameraAsync({
    allowsEditing: true,
    quality: 0.8,
  });

  if (!result.canceled) {
    console.log('Fotoğraf URI:', result.assets[0].uri);
  }
}

14) Platform-Specific Kod (Platform-Specific Code)

Platform.OS ile Koşullu Kod (Conditional Code with Platform.OS)

tsx
import { Platform } from 'react-native';

function PlatformSpecificComponent() {
  return (
    <View>
      {Platform.OS === 'ios' ? (
        <Text>iOS cihazdasınız (You are on iOS)</Text>
      ) : (
        <Text>Android cihazdasınız (You are on Android)</Text>
      )}

      <Text style={{
        fontFamily: Platform.OS === 'ios' ? 'Helvetica' : 'Roboto',
        paddingTop: Platform.OS === 'ios' ? 44 : 0,
      }}>
        Platform bazlı stil (Platform-based style)
      </Text>
    </View>
  );
}

// Platform.select — temiz yaklaşım (clean approach)
const styles = StyleSheet.create({
  header: {
    ...Platform.select({
      ios: { paddingTop: 44, borderBottomWidth: 0.5, borderBottomColor: '#ccc' },
      android: { paddingTop: 0, elevation: 4 },
      default: { paddingTop: 0 },  // Web veya diğer
    }),
  },
});

Dosya Bazlı Platform Kodu (File-Based Platform Code)

src/components/
  Button.tsx           # Paylaşılan mantık (Shared logic)
  Button.ios.tsx       # iOS'a özel (iOS-specific)
  Button.android.tsx   # Android'e özel (Android-specific)

# React Native otomatik doğru dosyayı seçer
# (React Native automatically picks the correct file)
# import Button from './Button'; → iOS'ta .ios.tsx, Android'de .android.tsx
tsx
// DatePicker.ios.tsx
import DateTimePicker from '@react-native-community/datetimepicker';

export function DatePicker({ value, onChange }: DatePickerProps) {
  return (
    <DateTimePicker
      value={value}
      mode="date"
      display="spinner"         // iOS tarzı (iOS style)
      onChange={(_, date) => date && onChange(date)}
    />
  );
}

// DatePicker.android.tsx
import DateTimePicker from '@react-native-community/datetimepicker';

export function DatePicker({ value, onChange }: DatePickerProps) {
  const [show, setShow] = useState(false);
  return (
    <View>
      <Pressable onPress={() => setShow(true)}>
        <Text>{value.toLocaleDateString('tr-TR')}</Text>
      </Pressable>
      {show && (
        <DateTimePicker
          value={value}
          mode="date"
          display="default"       // Android tarzı (Android style)
          onChange={(_, date) => { setShow(false); date && onChange(date); }}
        />
      )}
    </View>
  );
}

15) CLI Komutları Tablosu (CLI Commands Table)

Expo CLI Komutları (Expo CLI Commands)

Komut (Command)Açıklama (Description)
npx create-expo-app my-appYeni Expo projesi oluştur (Create new Expo project)
npx expo startGeliştirme sunucusu başlat (Start dev server)
npx expo start --clearÖnbelleği temizleyerek başlat (Start with cache clear)
npx expo start --dev-clientDev client ile başlat (Start with dev client)
npx expo start --tunnelTunnel modunda başlat (Start in tunnel mode)
npx expo install <paket>Uyumlu paket kur (Install compatible package)
npx expo prebuildNative projeler oluştur (Generate native projects)
npx expo prebuild --cleanTemiz native proje oluştur (Clean native project)
npx expo run:iosiOS'ta çalıştır (Run on iOS)
npx expo run:androidAndroid'de çalıştır (Run on Android)
npx expo exportWeb/embedded export (Web/embedded export)
npx expo customizeYapılandırma dosyaları oluştur (Generate config files)

EAS CLI Komutları (EAS CLI Commands)

Komut (Command)Açıklama (Description)
eas build --platform iosiOS build oluştur (Create iOS build)
eas build --platform androidAndroid build oluştur (Create Android build)
eas build --profile previewPreview build oluştur (Create preview build)
eas submit --platform iosApp Store'a gönder (Submit to App Store)
eas submit --platform androidPlay Store'a gönder (Submit to Play Store)
eas updateOTA güncelleme yayınla (Publish OTA update)
eas device:createTest cihazı kaydet (Register test device)
eas credentialsKimlik bilgilerini yönet (Manage credentials)

React Native CLI Komutları (React Native CLI Commands)

Komut (Command)Açıklama (Description)
npx react-native run-androidAndroid'de çalıştır (Run on Android)
npx react-native run-iosiOS'te çalıştır (Run on iOS)
npx react-native startMetro bundler başlat (Start Metro bundler)
npx react-native start --reset-cacheÖnbellek sıfırla (Reset cache)
npx react-native log-androidAndroid loglarını göster (Show Android logs)
npx react-native log-iosiOS loglarını göster (Show iOS logs)
npx react-native bundleJS bundle oluştur (Create JS bundle)
npx react-native infoOrtam bilgisi göster (Show environment info)

Android & Gradle Komutları (Android & Gradle Commands)

Komut (Command)Açıklama (Description)
cd android && ./gradlew assembleReleaseRelease APK oluştur (Build release APK)
cd android && ./gradlew bundleReleaseRelease AAB oluştur (Build release AAB)
cd android && ./gradlew cleanBuild önbelleğini temizle (Clean build cache)
adb devicesBağlı cihazları listele (List connected devices)
adb install app.apkAPK kur (Install APK)
adb reverse tcp:8081 tcp:8081Port yönlendirme (Port forwarding)
adb logcat *:EHata logları (Error logs)
adb shell input keyevent 82Dev menüsü aç (Open dev menu)

16) Guvenlik (Security)

Token Saklama (Token Storage)

tsx
// ❌ YANLIŞ — AsyncStorage şifrelenmez (WRONG — AsyncStorage is not encrypted)
await AsyncStorage.setItem('token', jwt);

// ✅ DOĞRU — SecureStore kullan (CORRECT — use SecureStore)
await SecureStore.setItemAsync('token', jwt);

// ✅ DOĞRU — MMKV şifreleme ile (CORRECT — MMKV with encryption)
const secureStorage = new MMKV({
  id: 'secure-storage',
  encryptionKey: 'your-encryption-key',
});

SSL Pinning

bash
# react-native-ssl-pinning ile (with react-native-ssl-pinning)
npm install react-native-ssl-pinning
tsx
import { fetch as sslFetch } from 'react-native-ssl-pinning';

const response = await sslFetch('https://api.example.com/data', {
  method: 'GET',
  headers: { 'Content-Type': 'application/json' },
  sslPinning: {
    certs: ['server_cert'],  // DER formatında sertifika (Certificate in DER format)
  },
  timeoutInterval: 10000,
});

ProGuard / R8 — Android Kod Karıştırma (Android Code Obfuscation)

# android/app/proguard-rules.pro
# React Native kuralları (React Native rules)
-keep class com.facebook.hermes.unicode.** { *; }
-keep class com.facebook.jni.** { *; }

# Uygulama modelleri koru (Keep app models)
-keep class com.yourapp.models.** { *; }

# Reanimated
-keep class com.swmansion.reanimated.** { *; }
groovy
// android/app/build.gradle
android {
    buildTypes {
        release {
            minifyEnabled true          // Kod küçültme (Code minification)
            shrinkResources true        // Kaynak küçültme (Resource shrinking)
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
                         'proguard-rules.pro'
        }
    }
}

Hermes Engine Güvenliği (Hermes Engine Security)

tsx
// Hermes bytecode derler — kaynak kod APK'da görünmez
// (Hermes compiles bytecode — source code not visible in APK)

// Hermes aktif mi kontrol et (Check if Hermes is active)
const isHermes = () => !!(global as any).HermesInternal;
console.log('Hermes aktif:', isHermes());

// app.json Hermes yapılandırması (Hermes configuration)
// Expo SDK 49+ varsayılan olarak Hermes kullanır
// (Expo SDK 49+ uses Hermes by default)

Genel Güvenlik Kontrol Listesi (General Security Checklist)

Kontrol (Check)Açıklama (Description)
SecureStore/Keychain kullanHassas veriler için (For sensitive data)
SSL Pinning uygulaMITM saldırılarına karşı (Against MITM attacks)
ProGuard/R8 etkinleştirKod karıştırma (Code obfuscation)
Hermes kullanBytecode derleme (Bytecode compilation)
Root/Jailbreak tespitiGüvenli olmayan cihaz kontrolü (Compromised device check)
Ekran görüntüsü engellemeFLAG_SECURE (Android) (Screenshot prevention)
Ağ güvenliği yapılandırmasınetwork_security_config.xml (Network security config)
Hassas veri loglamaÜretimde console.log kaldır (Remove console.log in production)

17) Tips — İpuçları ve Performans (Tips & Performance)

FlatList Optimizasyonu (FlatList Optimization)

tsx
// ✅ Optimizasyon ipuçları (Optimization tips)
<FlatList
  data={data}
  renderItem={renderItem}
  keyExtractor={(item) => item.id}           // ✅ Benzersiz key (Unique key)
  getItemLayout={(_, index) => ({             // ✅ Sabit yükseklik (Fixed height)
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
  initialNumToRender={10}                     // ✅ İlk render sayısı
  maxToRenderPerBatch={5}                     // ✅ Batch render sayısı
  windowSize={5}                              // ✅ Görünür pencere boyutu
  removeClippedSubviews={true}                // ✅ Görünmeyen öğeleri kaldır
  updateCellsBatchingPeriod={50}              // ✅ Batch güncelleme süresi (ms)
/>

// ✅ renderItem'ı memoize et (Memoize renderItem)
const renderItem = useCallback(({ item }: { item: Item }) => (
  <MemoizedItemComponent item={item} />
), []);

// ✅ Bileşeni memo ile sar (Wrap component with memo)
const MemoizedItemComponent = React.memo(({ item }: { item: Item }) => (
  <View style={styles.item}>
    <Text>{item.name}</Text>
  </View>
));

FlashList — FlatList Alternatifi (FlatList Alternative)

bash
npm install @shopify/flash-list
tsx
import { FlashList } from '@shopify/flash-list';

// FlatList'ten 5-10x daha performanslı (5-10x more performant than FlatList)
<FlashList
  data={data}
  renderItem={renderItem}
  estimatedItemSize={80}     // ✅ Tahmini öğe yüksekliği (Estimated item height)
  keyExtractor={(item) => item.id}
/>

Hermes Engine

json
// app.json — Expo'da varsayılan olarak aktif (Active by default in Expo)
{
  "expo": {
    "jsEngine": "hermes"
  }
}

Hermes avantajları (Hermes benefits):

  • Daha hızlı uygulama başlatma (Faster app startup) — bytecode ön derleme
  • Daha düşük bellek kullanımı (Lower memory usage)
  • Daha küçük bundle boyutu (Smaller bundle size)
  • Garbage collector optimizasyonu (GC optimization)

Bundle Boyutu Küçültme (Bundle Size Reduction)

bash
# Bundle boyutu analizi (Bundle size analysis)
npx react-native-bundle-visualizer

# Expo ile bundle analizi
npx expo export --dump-sourcemap
Teknik (Technique)Açıklama (Description)
Hermes kullanBytecode derleme ile küçültme (Reduce with bytecode compilation)
Tree shakingKullanılmayan kodları kaldır (Remove unused code)
Lazy importReact.lazy() ile dinamik import
Görsel optimizasyonuWebP formatı, boyut sıkıştırma (WebP format, compression)
Gereksiz paketleri kaldırdepcheck ile kontrol et (Check with depcheck)
ProGuard/R8Android kod küçültme (Android code minification)
Moment.js yerinedate-fns veya dayjs kullan (Use date-fns or dayjs)
Lodash yerinelodash-es veya native yöntemler (lodash-es or native methods)

Performans İpuçları (Performance Tips)

tsx
// 1. ✅ useMemo — pahalı hesaplamaları önbelleğe al (Cache expensive computations)
const sortedItems = useMemo(
  () => items.sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

// 2. ✅ useCallback — fonksiyon referansını koru (Preserve function reference)
const handlePress = useCallback((id: string) => {
  navigation.navigate('Detail', { id });
}, [navigation]);

// 3. ✅ React.memo — gereksiz render'ları önle (Prevent unnecessary renders)
const ExpensiveComponent = React.memo(({ data }: { data: Data }) => {
  return <View>{/* Karmaşık UI */}</View>;
});

// 4. ✅ InteractionManager — ağır işlemleri geciktir (Defer heavy operations)
import { InteractionManager } from 'react-native';

useEffect(() => {
  const task = InteractionManager.runAfterInteractions(() => {
    // Animasyonlar bittikten sonra çalışır (Runs after animations complete)
    loadHeavyData();
  });
  return () => task.cancel();
}, []);

// 5. ✅ Görsel önbelleğe alma (Image caching)
// expo-image veya react-native-fast-image kullan
import { Image } from 'expo-image';
<Image source={uri} cachePolicy="memory-disk" />

// 6. ✅ console.log üretimde kaldır (Remove console.log in production)
// babel.config.js
module.exports = {
  plugins: [
    ['transform-remove-console', { exclude: ['error', 'warn'] }],
  ],
};

// 7. ✅ Hermes GC ayarlama — büyük listeler için
// (Hermes GC tuning — for large lists)
if (global.HermesInternal) {
  global.HermesInternal.enablePromiseRejectionTracker?.();
}

18) Test (Testing)

Jest — Birim Test (Unit Testing)

bash
# Expo projesinde Jest zaten kurulu (Jest is already installed in Expo)
# Ek test kütüphaneleri (Additional test libraries)
npm install --save-dev @testing-library/react-native @testing-library/jest-native
json
// package.json veya jest.config.js
{
  "jest": {
    "preset": "jest-expo",
    "setupFilesAfterSetup": ["@testing-library/jest-native/extend-expect"],
    "transformIgnorePatterns": [
      "node_modules/(?!(react-native|@react-native|expo|@expo|@react-navigation)/)"
    ]
  }
}
tsx
// __tests__/utils/formatters.test.ts
import { formatCurrency, formatDate } from '../../src/utils/formatters';

describe('formatCurrency', () => {
  it('TL formatında para birimi gösterir (displays currency in TL format)', () => {
    expect(formatCurrency(1234.5)).toBe('₺1.234,50');
  });

  it('sıfır değer için doğru çalışır (works correctly for zero)', () => {
    expect(formatCurrency(0)).toBe('₺0,00');
  });

  it('negatif değerleri işler (handles negative values)', () => {
    expect(formatCurrency(-50)).toBe('-₺50,00');
  });
});

describe('formatDate', () => {
  it('tarihi Türkçe formatta gösterir (displays date in Turkish format)', () => {
    const date = new Date('2024-03-15');
    expect(formatDate(date)).toBe('15 Mart 2024');
  });
});

React Native Testing Library — Bileşen Testi (Component Testing)

tsx
// __tests__/components/LoginForm.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native';
import { LoginForm } from '../../src/components/LoginForm';

describe('LoginForm', () => {
  const mockOnSubmit = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('giriş formunu render eder (renders login form)', () => {
    render(<LoginForm onSubmit={mockOnSubmit} />);

    expect(screen.getByPlaceholderText('E-posta')).toBeTruthy();
    expect(screen.getByPlaceholderText('Şifre')).toBeTruthy();
    expect(screen.getByText('Giriş Yap')).toBeTruthy();
  });

  it('form doğrulama hatalarını gösterir (shows form validation errors)', async () => {
    render(<LoginForm onSubmit={mockOnSubmit} />);

    fireEvent.press(screen.getByText('Giriş Yap'));

    await waitFor(() => {
      expect(screen.getByText('E-posta gerekli')).toBeTruthy();
      expect(screen.getByText('Şifre gerekli')).toBeTruthy();
    });

    expect(mockOnSubmit).not.toHaveBeenCalled();
  });

  it('geçerli form gönderiminde onSubmit çağrılır (onSubmit called on valid submission)', async () => {
    render(<LoginForm onSubmit={mockOnSubmit} />);

    fireEvent.changeText(screen.getByPlaceholderText('E-posta'), 'test@example.com');
    fireEvent.changeText(screen.getByPlaceholderText('Şifre'), 'password123');
    fireEvent.press(screen.getByText('Giriş Yap'));

    await waitFor(() => {
      expect(mockOnSubmit).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'password123',
      });
    });
  });

  it('yükleme durumunda butonu devre dışı bırakır (disables button during loading)', () => {
    render(<LoginForm onSubmit={mockOnSubmit} loading />);

    const button = screen.getByText('Giriş Yapılıyor...');
    expect(button).toBeDisabled();
  });
});

Hook Testi (Hook Testing)

tsx
// __tests__/hooks/useAuth.test.ts
import { renderHook, act } from '@testing-library/react-native';
import { useAuthStore } from '../../src/store/useAuthStore';

describe('useAuthStore', () => {
  beforeEach(() => {
    useAuthStore.setState({
      user: null, token: null, isAuthenticated: false,
    });
  });

  it('login kullanıcı bilgilerini günceller (login updates user info)', () => {
    const { result } = renderHook(() => useAuthStore());

    act(() => {
      result.current.login(
        { id: '1', name: 'Fahri', email: 'fahri@example.com' },
        'test-token'
      );
    });

    expect(result.current.isAuthenticated).toBe(true);
    expect(result.current.user?.name).toBe('Fahri');
    expect(result.current.token).toBe('test-token');
  });

  it('logout durumu sıfırlar (logout resets state)', () => {
    const { result } = renderHook(() => useAuthStore());

    act(() => {
      result.current.login(
        { id: '1', name: 'Fahri', email: 'fahri@example.com' },
        'test-token'
      );
      result.current.logout();
    });

    expect(result.current.isAuthenticated).toBe(false);
    expect(result.current.user).toBeNull();
  });
});

API Mock Testi (API Mock Testing)

tsx
// __tests__/services/userService.test.ts
import { api } from '../../src/services/api';
import { getUsers, createUser } from '../../src/services/userService';

jest.mock('../../src/services/api');
const mockedApi = api as jest.Mocked<typeof api>;

describe('userService', () => {
  it('kullanıcı listesini getirir (fetches user list)', async () => {
    const mockUsers = [
      { id: '1', name: 'Fahri', email: 'fahri@example.com' },
    ];
    mockedApi.get.mockResolvedValueOnce(mockUsers);

    const users = await getUsers();

    expect(mockedApi.get).toHaveBeenCalledWith('/users');
    expect(users).toEqual(mockUsers);
  });

  it('API hatası fırlatır (throws API error)', async () => {
    mockedApi.get.mockRejectedValueOnce(new Error('Network error'));

    await expect(getUsers()).rejects.toThrow('Network error');
  });
});

Detox — E2E Test (End-to-End Testing)

bash
# Detox kurulumu (Detox installation)
npm install --save-dev detox @types/detox
npm install --save-dev jest-circus

# iOS build (macOS only)
detox build --configuration ios.sim.debug

# E2E testleri çalıştır (Run E2E tests)
detox test --configuration ios.sim.debug
tsx
// e2e/login.test.ts
describe('Login Flow', () => {
  beforeAll(async () => {
    await device.launchApp();
  });

  beforeEach(async () => {
    await device.reloadReactNative();
  });

  it('başarılı giriş akışı (successful login flow)', async () => {
    // E-posta gir (Enter email)
    await element(by.id('email-input')).typeText('test@example.com');

    // Şifre gir (Enter password)
    await element(by.id('password-input')).typeText('password123');

    // Klavyeyi kapat (Dismiss keyboard)
    await element(by.id('password-input')).tapReturnKey();

    // Giriş butonuna bas (Press login button)
    await element(by.id('login-button')).tap();

    // Ana sayfa görünür mü kontrol et (Check if home screen is visible)
    await waitFor(element(by.id('home-screen')))
      .toBeVisible()
      .withTimeout(5000);

    // Hoş geldiniz mesajı (Welcome message)
    await expect(element(by.text('Hoş Geldiniz'))).toBeVisible();
  });

  it('hatalı giriş durumu (invalid login scenario)', async () => {
    await element(by.id('email-input')).typeText('wrong@example.com');
    await element(by.id('password-input')).typeText('wrongpassword');
    await element(by.id('login-button')).tap();

    // Hata mesajı görünür mü (Is error message visible)
    await waitFor(element(by.text('Geçersiz kimlik bilgileri')))
      .toBeVisible()
      .withTimeout(3000);
  });

  it('şifremi unuttum akışı (forgot password flow)', async () => {
    await element(by.text('Şifremi Unuttum')).tap();

    await waitFor(element(by.id('forgot-password-screen')))
      .toBeVisible()
      .withTimeout(2000);

    await element(by.id('reset-email-input')).typeText('test@example.com');
    await element(by.text('Sıfırlama Linki Gönder')).tap();

    await waitFor(element(by.text('E-posta gönderildi')))
      .toBeVisible()
      .withTimeout(3000);
  });
});

Test Kapsamı (Test Coverage)

bash
# Kapsam raporu ile test çalıştır (Run tests with coverage report)
npx jest --coverage

# Belirli dosya veya klasör (Specific file or folder)
npx jest --coverage --collectCoverageFrom='src/**/*.{ts,tsx}'

# İzleme modunda (Watch mode)
npx jest --watch
json
// jest.config.js — kapsam eşikleri (coverage thresholds)
{
  "coverageThreshold": {
    "global": {
      "branches": 70,
      "functions": 80,
      "lines": 80,
      "statements": 80
    }
  }
}

Rehber (Guide)Açıklama (Description)
React RehberiReact temelleri, Hooks, JSX (React fundamentals)
TypeScript RehberiTypeScript tip sistemi (TypeScript type system)
JavaScript/TypeScript RehberiJS/TS dil özellikleri (Language features)
CSS & Tailwind RehberiFlexbox temelleri (Flexbox basics)
Web Performans RehberiPerformans optimizasyonu (Performance optimization)
Node.js RehberiBackend API geliştirme (Backend API development)
API Geliştirme RehberiREST/GraphQL API tasarımı (API design)
Docker RehberiCI/CD pipeline (CI/CD pipeline)
VS Code RehberiReact Native eklentileri (RN extensions)

Son güncelleme (Last updated): 2026-04 Bu rehber React Native 0.76+, Expo SDK 52+ ve React Navigation 7 baz alınarak hazırlanmıştır. (This guide is based on React Native 0.76+, Expo SDK 52+, and React Navigation 7.)

Developer Guides & Technical References