Mobil Mimari Rehberi (Mobile Architecture Guide)
Bu Rehber Nedir? (What Is This Guide?)
Mobil uygulamalarda sürdürülebilir, test edilebilir ve ölçeklenebilir mimari kurmak için kapsamlı bir rehber. React Native ve Flutter örnekleriyle mimari yaklaşımlar, offline-first stratejiler, güvenlik ve erişilebilirlik konularını kapsar.
1) Mimari Yaklaşımlar (Architectural Approaches)
Mobil uygulamalarda doğru mimari seçimi; bakım kolaylığı, test edilebilirlik ve ekip verimliliğini doğrudan etkiler.
Karşılaştırma Tablosu (Comparison Table)
| Özellik (Feature) | MVC | MVP | MVVM | Clean Architecture |
|---|---|---|---|---|
| Tam Adı | Model-View-Controller | Model-View-Presenter | Model-View-ViewModel | Uncle Bob's Clean Arch |
| Katman Sayısı (Layers) | 3 | 3 | 3 | 3-4 (iç içe daireler) |
| View-Logic Ayrımı | Zayıf — Controller şişer | İyi — Presenter aracı | Çok iyi — data binding | Mükemmel — katı sınırlar |
| Test Edilebilirlik (Testability) | Düşük | Yüksek | Yüksek | Çok yüksek |
| Öğrenme Eğrisi (Learning Curve) | Düşük | Orta | Orta | Yüksek |
| Bağımlılık Yönü (Dependency Direction) | Karışık | View → Presenter → Model | View → ViewModel → Model | Dıştan içe (outer → inner) |
| Reaktif Destek (Reactive Support) | Manuel | Manuel | Doğal (Observable/Stream) | Framework bağımsız |
| Küçük Proje İçin (Small Projects) | Uygun | Fazla boilerplate | Uygun | Fazla karmaşık |
| Büyük Proje İçin (Large Projects) | Yetersiz | Uygun | Çok uygun | En uygun |
| Mobil Kullanım (Mobile Usage) | iOS (UIKit eski stil) | Android (eski Java projeleri) | RN + Flutter (yaygın) | RN + Flutter (kurumsal) |
2) MVVM Detaylı (MVVM in Detail)
MVVM, mobil uygulamalarda en yaygın kullanılan mimari kalıptır. View ile iş mantığını ayırarak test edilebilirlik ve bakım kolaylığı sağlar.
MVVM Katmanları (MVVM Layers)
┌─────────────────────────────────────────────┐
│ VIEW │
│ (Ekran / Bileşen — Screen / Component) │
│ Kullanıcı etkileşimi, UI render │
│ ViewModel'i gözlemler (observes) │
└──────────────────┬──────────────────────────┘
│ data binding / subscribe
┌──────────────────▼──────────────────────────┐
│ VIEW MODEL │
│ (Durum yönetimi — State management) │
│ İş mantığı, UI state, validasyon │
│ Model katmanını çağırır │
└──────────────────┬──────────────────────────┘
│ repository / service call
┌──────────────────▼──────────────────────────┐
│ MODEL │
│ (Veri katmanı — Data layer) │
│ API, veritabanı, cache │
└─────────────────────────────────────────────┘React Native'de MVVM
// ── Model (types + repository) ──
interface User {
id: string;
name: string;
email: string;
}
class UserRepository {
async getUser(id: string): Promise<User> {
const response = await api.get(`/users/${id}`);
return response.data;
}
async updateUser(id: string, data: Partial<User>): Promise<User> {
const response = await api.put(`/users/${id}`, data);
return response.data;
}
}
// ── ViewModel (custom hook) ──
function useUserViewModel(userId: string) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const repository = useMemo(() => new UserRepository(), []);
const fetchUser = useCallback(async () => {
setLoading(true);
setError(null);
try {
const data = await repository.getUser(userId);
setUser(data);
} catch (err) {
setError('Kullanıcı yüklenemedi');
} finally {
setLoading(false);
}
}, [userId, repository]);
const updateName = useCallback(async (name: string) => {
if (!user) return;
try {
const updated = await repository.updateUser(user.id, { name });
setUser(updated);
} catch (err) {
setError('Güncelleme başarısız');
}
}, [user, repository]);
useEffect(() => { fetchUser(); }, [fetchUser]);
return { user, loading, error, updateName, retry: fetchUser };
}
// ── View (component) ──
function UserProfileScreen({ userId }: { userId: string }) {
const { user, loading, error, updateName, retry } = useUserViewModel(userId);
if (loading) return <LoadingSpinner />;
if (error) return <ErrorView message={error} onRetry={retry} />;
return (
<View>
<Text>{user?.name}</Text>
<Button title="İsmi Güncelle" onPress={() => updateName('Yeni İsim')} />
</View>
);
}Flutter'da MVVM
// ── Model ──
class User {
final String id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) => User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
// ── Repository ──
class UserRepository {
final ApiClient _api;
UserRepository(this._api);
Future<User> getUser(String id) async {
final response = await _api.get('/users/$id');
return User.fromJson(response.data);
}
}
// ── ViewModel (ChangeNotifier) ──
class UserViewModel extends ChangeNotifier {
final UserRepository _repository;
User? _user;
bool _loading = false;
String? _error;
User? get user => _user;
bool get loading => _loading;
String? get error => _error;
UserViewModel(this._repository);
Future<void> fetchUser(String id) async {
_loading = true;
_error = null;
notifyListeners();
try {
_user = await _repository.getUser(id);
} catch (e) {
_error = 'Kullanıcı yüklenemedi';
} finally {
_loading = false;
notifyListeners();
}
}
}
// ── View (Widget) ──
class UserProfileScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<UserViewModel>(
builder: (context, vm, child) {
if (vm.loading) return const CircularProgressIndicator();
if (vm.error != null) return ErrorWidget(message: vm.error!);
return Column(
children: [
Text(vm.user?.name ?? ''),
ElevatedButton(
onPressed: () => vm.fetchUser(vm.user!.id),
child: const Text('Yenile'),
),
],
);
},
);
}
}3) Clean Architecture (Temiz Mimari)
Clean Architecture, iş mantığını framework ve altyapı detaylarından tamamen izole eder. Uncle Bob (Robert C. Martin) tarafından tanımlanmıştır.
Katman Yapısı (Layer Structure)
┌─────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ (UI, Screens, Widgets, ViewModels, Controllers) │
│ Framework'e bağımlı (Framework-dependent) │
├─────────────────────────────────────────────────┤
│ DOMAIN LAYER │
│ (Entities, Use Cases, Repository Interfaces) │
│ Hiçbir şeye bağımlı değil (No dependencies) │
├─────────────────────────────────────────────────┤
│ DATA LAYER │
│ (Repository Impl, API, Local DB, DTO, Mapper) │
│ Domain'deki interface'leri implemente eder │
└─────────────────────────────────────────────────┘Bağımlılık Kuralı (Dependency Rule)
Presentation ──depends on──> Domain <──depends on── Data
▲
│
İş mantığı burada yaşar
(Business logic lives here)- Domain katmanı hiçbir katmana bağımlı değildir (Domain depends on nothing)
- Presentation yalnızca Domain'e bağımlıdır (Presentation depends only on Domain)
- Data yalnızca Domain'deki interface'leri implemente eder (Data implements Domain interfaces)
Use Case Örneği (Use Case Example)
// ── Domain: Entity ──
interface Product {
id: string;
name: string;
price: number;
stock: number;
}
// ── Domain: Repository Interface ──
interface ProductRepository {
getProducts(): Promise<Product[]>;
getProductById(id: string): Promise<Product>;
searchProducts(query: string): Promise<Product[]>;
}
// ── Domain: Use Case ──
class GetProductsUseCase {
constructor(private repository: ProductRepository) {}
async execute(query?: string): Promise<Product[]> {
if (query && query.length >= 2) {
return this.repository.searchProducts(query);
}
return this.repository.getProducts();
}
}
// ── Data: Repository Implementation ──
class ProductRepositoryImpl implements ProductRepository {
constructor(
private api: ApiClient,
private localDb: LocalDatabase
) {}
async getProducts(): Promise<Product[]> {
try {
const products = await this.api.get('/products');
await this.localDb.saveProducts(products);
return products;
} catch {
return this.localDb.getProducts(); // offline fallback
}
}
async getProductById(id: string): Promise<Product> {
return this.api.get(`/products/${id}`);
}
async searchProducts(query: string): Promise<Product[]> {
return this.api.get(`/products?q=${query}`);
}
}4) Repository Pattern (API + Local Cache)
Repository pattern, veri kaynağını soyutlayarak API ve local cache arasında şeffaf geçiş sağlar.
Strateji Akışı (Strategy Flow)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ ViewModel │────>│ Repository │────>│ API Client │
│ │ │ │ │ (Remote) │
└──────────────┘ │ │ └──────────────┘
│ │
│ │────>┌──────────────┐
│ │ │ Local DB │
└──────────────┘ │ (SQLite/ │
│ Hive/MMKV) │
└──────────────┘Cache Stratejileri (Cache Strategies)
| Strateji (Strategy) | Açıklama (Description) | Kullanım Alanı (Use Case) |
|---|---|---|
| Cache-First | Önce cache'e bak, yoksa API'ye git | Sık değişmeyen veriler (profil, ayarlar) |
| Network-First | Önce API'ye git, hata olursa cache'e düş | Güncel veri önemli (feed, mesajlar) |
| Cache-then-Network | Hemen cache'i göster, arka planda API'den güncelle | En iyi UX — anında yükleme + güncel veri |
| Network-Only | Sadece API kullan | Hassas veriler (ödeme, OTP) |
| Cache-Only | Sadece cache kullan | Tamamen offline senaryolar |
Cache-then-Network Örneği (React Native)
class ProductRepository {
constructor(
private api: ApiClient,
private cache: CacheStorage,
private ttl: number = 5 * 60 * 1000 // 5 dakika (5 minutes)
) {}
async getProducts(forceRefresh = false): Promise<{
data: Product[];
source: 'cache' | 'network';
}> {
// 1. Cache'i kontrol et (Check cache)
if (!forceRefresh) {
const cached = await this.cache.get<Product[]>('products');
if (cached && !this.isExpired(cached.timestamp)) {
// Arka planda güncelle (Update in background)
this.refreshInBackground();
return { data: cached.data, source: 'cache' };
}
}
// 2. API'den al (Fetch from API)
try {
const products = await this.api.get<Product[]>('/products');
await this.cache.set('products', products);
return { data: products, source: 'network' };
} catch (error) {
// 3. Offline fallback
const cached = await this.cache.get<Product[]>('products');
if (cached) return { data: cached.data, source: 'cache' };
throw error;
}
}
private isExpired(timestamp: number): boolean {
return Date.now() - timestamp > this.ttl;
}
private async refreshInBackground(): Promise<void> {
try {
const products = await this.api.get<Product[]>('/products');
await this.cache.set('products', products);
} catch {
// Sessizce başarısız ol (Silently fail)
}
}
}5) State Management Mimarileri Karşılaştırma (State Management Comparison)
React Native
| Çözüm (Solution) | Yaklaşım (Approach) | Öğrenme Eğrisi | Boilerplate | Performans | İdeal Kullanım |
|---|---|---|---|---|---|
| Context API | React yerleşik | Düşük | Düşük | Düşük (gereksiz render) | Küçük uygulamalar, tema/dil |
| Zustand | Minimal store | Düşük | Çok düşük | Yüksek | Orta ölçekli projeler |
| Redux Toolkit | Flux mimarisi | Orta | Orta | Yüksek | Büyük, karmaşık state |
| Jotai | Atomic state | Düşük | Düşük | Yüksek | Bağımsız state parçaları |
| MobX | Observable/Reactive | Orta | Düşük | Yüksek | Reaktif veri akışları |
| React Query | Server state | Düşük | Düşük | Yüksek | API veri yönetimi |
Flutter
| Çözüm (Solution) | Yaklaşım (Approach) | Öğrenme Eğrisi | Boilerplate | Performans | İdeal Kullanım |
|---|---|---|---|---|---|
| setState | Flutter yerleşik | Düşük | Düşük | Düşük (tüm widget rebuild) | Widget-lokal state |
| Provider | InheritedWidget wrapper | Düşük | Düşük | İyi | Küçük-orta projeler |
| Riverpod | Provider 2.0, derleme güvenli | Orta | Orta | Çok iyi | Orta-büyük projeler |
| Bloc/Cubit | Event-driven, stream tabanlı | Yüksek | Yüksek | Çok iyi | Kurumsal, büyük projeler |
| GetX | Hepsi bir arada | Düşük | Çok düşük | İyi | Hızlı prototip |
| Signals | Reactive primitives | Düşük | Düşük | Çok iyi | Modern Flutter projeleri |
6) Proje Yapısı (Project Structure)
Feature-Based (Özellik Bazlı) vs Layer-Based (Katman Bazlı)
| Yaklaşım | Avantaj (Advantage) | Dezavantaj (Disadvantage) |
|---|---|---|
| Feature-Based | Özellik izolasyonu, ekip paralelliği, ölçeklenebilir | Paylaşılan kodun yönetimi zor |
| Layer-Based | Anlaşılması kolay, küçük projelere uygun | Büyük projelerde dosya kalabalığı |
React Native — Feature-Based Yapı
src/
├── app/ # Uygulama konfigürasyonu
│ ├── App.tsx
│ ├── navigation/
│ │ ├── RootNavigator.tsx
│ │ ├── AuthNavigator.tsx
│ │ └── MainTabNavigator.tsx
│ └── providers/
│ └── AppProviders.tsx
├── features/ # Özellik modülleri
│ ├── auth/
│ │ ├── screens/
│ │ │ ├── LoginScreen.tsx
│ │ │ └── RegisterScreen.tsx
│ │ ├── hooks/
│ │ │ └── useAuthViewModel.ts
│ │ ├── services/
│ │ │ └── AuthService.ts
│ │ ├── types/
│ │ │ └── auth.types.ts
│ │ └── components/
│ │ └── LoginForm.tsx
│ ├── products/
│ │ ├── screens/
│ │ ├── hooks/
│ │ ├── services/
│ │ └── components/
│ └── profile/
│ ├── screens/
│ ├── hooks/
│ ├── services/
│ └── components/
├── shared/ # Paylaşılan kod
│ ├── components/
│ │ ├── Button.tsx
│ │ ├── Input.tsx
│ │ └── ErrorBoundary.tsx
│ ├── hooks/
│ │ ├── useNetwork.ts
│ │ └── useTheme.ts
│ ├── services/
│ │ ├── ApiClient.ts
│ │ └── StorageService.ts
│ ├── utils/
│ │ ├── formatters.ts
│ │ └── validators.ts
│ └── constants/
│ ├── colors.ts
│ └── endpoints.ts
└── types/ # Global tipler
└── global.d.tsFlutter — Feature-Based Yapı
lib/
├── app/ # Uygulama konfigürasyonu
│ ├── app.dart
│ ├── router/
│ │ └── app_router.dart
│ └── theme/
│ ├── app_theme.dart
│ └── app_colors.dart
├── features/ # Özellik modülleri
│ ├── auth/
│ │ ├── presentation/
│ │ │ ├── screens/
│ │ │ │ ├── login_screen.dart
│ │ │ │ └── register_screen.dart
│ │ │ ├── widgets/
│ │ │ │ └── login_form.dart
│ │ │ └── viewmodels/
│ │ │ └── auth_viewmodel.dart
│ │ ├── domain/
│ │ │ ├── entities/
│ │ │ │ └── user.dart
│ │ │ ├── repositories/
│ │ │ │ └── auth_repository.dart
│ │ │ └── usecases/
│ │ │ └── login_usecase.dart
│ │ └── data/
│ │ ├── repositories/
│ │ │ └── auth_repository_impl.dart
│ │ ├── datasources/
│ │ │ ├── auth_remote_source.dart
│ │ │ └── auth_local_source.dart
│ │ └── models/
│ │ └── user_model.dart
│ ├── products/
│ └── profile/
├── core/ # Paylaşılan çekirdek
│ ├── network/
│ │ ├── api_client.dart
│ │ └── interceptors.dart
│ ├── storage/
│ │ └── local_storage.dart
│ ├── utils/
│ │ └── validators.dart
│ └── widgets/
│ ├── custom_button.dart
│ └── loading_indicator.dart
└── main.dart7) Offline-First & Data Sync (Çevrimdışı Öncelikli & Veri Senkronizasyonu)
Offline-First Stratejisi (Offline-First Strategy)
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Kullanıcı │───>│ Local DB │───>│ Sync Queue │
│ Eylemi │ │ (Önce yaz) │ │ (Sıraya al)│
│ (User Act) │ │ (Write first)│ │ (Enqueue) │
└─────────────┘ └──────────────┘ └──────┬──────┘
│
┌───────────────────▼──────┐
│ Bağlantı var mı? │
│ (Is connected?) │
├──── Evet ───> API Sync │
├──── Hayır ──> Bekle │
└──────────────────────────┘Optimistic vs Pessimistic UI
| Yaklaşım (Approach) | Açıklama (Description) | Avantaj | Dezavantaj | Kullanım Alanı |
|---|---|---|---|---|
| Optimistic UI | Sunucu yanıtı beklenmeden UI güncellenir | Anlık hissettirir, hızlı UX | Hata durumunda geri alma gerekir | Beğeni, yorum, sepet ekleme |
| Pessimistic UI | Sunucu yanıtı beklenip sonra UI güncellenir | Tutarlı veri, hata yok | Loading gösterilir, yavaş hissettirir | Ödeme, sipariş, transfer |
Queue-Based Sync Örneği
interface SyncQueueItem {
id: string;
action: 'CREATE' | 'UPDATE' | 'DELETE';
endpoint: string;
payload: any;
timestamp: number;
retryCount: number;
maxRetries: number;
}
class SyncManager {
private queue: SyncQueueItem[] = [];
private isProcessing = false;
async enqueue(item: Omit<SyncQueueItem, 'id' | 'timestamp' | 'retryCount'>): Promise<void> {
const queueItem: SyncQueueItem = {
...item,
id: generateUUID(),
timestamp: Date.now(),
retryCount: 0,
};
this.queue.push(queueItem);
await this.persistQueue();
this.processQueue();
}
private async processQueue(): Promise<void> {
if (this.isProcessing || this.queue.length === 0) return;
if (!await isNetworkAvailable()) return;
this.isProcessing = true;
const item = this.queue[0];
try {
await this.executeSync(item);
this.queue.shift(); // Başarılı — kuyruktan çıkar
await this.persistQueue();
} catch (error) {
item.retryCount++;
if (item.retryCount >= item.maxRetries) {
this.queue.shift();
await this.handleFailedItem(item);
}
} finally {
this.isProcessing = false;
if (this.queue.length > 0) this.processQueue();
}
}
private async executeSync(item: SyncQueueItem): Promise<void> {
switch (item.action) {
case 'CREATE': await api.post(item.endpoint, item.payload); break;
case 'UPDATE': await api.put(item.endpoint, item.payload); break;
case 'DELETE': await api.delete(item.endpoint); break;
}
}
private async persistQueue(): Promise<void> {
await AsyncStorage.setItem('sync_queue', JSON.stringify(this.queue));
}
private async handleFailedItem(item: SyncQueueItem): Promise<void> {
// Başarısız öğeyi logla veya kullanıcıya bildir
console.error(`Senkronizasyon başarısız: ${item.endpoint}`, item);
}
}Yerel Veritabanı Seçenekleri (Local Database Options)
| Veritabanı (Database) | Platform | Tür (Type) | İdeal Kullanım |
|---|---|---|---|
| SQLite (Expo SQLite) | RN | İlişkisel (Relational) | Karmaşık sorgular, büyük veri |
| MMKV | RN | Key-Value | Basit ayarlar, token, tercihler |
| WatermelonDB | RN | İlişkisel (reactive) | Offline-first, büyük veri setleri |
| Hive | Flutter | NoSQL Key-Value | Hızlı okuma/yazma, basit veriler |
| Isar | Flutter | NoSQL | Büyük veri, full-text arama |
| Drift (Moor) | Flutter | İlişkisel (Relational) | Karmaşık sorgular, tip güvenliği |
8) Navigation Patterns (Navigasyon Kalıpları)
Navigasyon Türleri (Navigation Types)
| Tür (Type) | Açıklama (Description) | Kullanım Alanı |
|---|---|---|
| Stack | Ekranlar üst üste yığılır, geri tuşu ile dönüş | Detay sayfaları, form akışları |
| Tab | Alt/üst sekmeler ile paralel ekranlar | Ana ekranlar (Home, Search, Profile) |
| Drawer | Yan menüden açılan navigasyon | Ayarlar, kategori listesi |
| Modal | Mevcut ekranın üzerine açılan panel | Filtre, onay, hızlı işlem |
| Deep Link | URL ile doğrudan belirli ekrana yönlendirme | Bildirim, paylaşım, marketing |
Auth Flow Navigasyon Yapısı (Auth Flow Structure)
┌─────────────────────────────────────────────┐
│ Root Navigator │
│ │
│ ┌─── Token var mı? (Has token?) ─────┐ │
│ │ │ │
│ │ Hayır (No) Evet (Yes) │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ Auth Stack Main Tab Nav │ │
│ │ ├── Splash ├── Home Stack │ │
│ │ ├── Login ├── Search │ │
│ │ ├── Register ├── Cart │ │
│ │ └── ForgotPass └── Profile Stack│ │
│ │ ├── Profile │ │
│ │ ├── Settings│ │
│ │ └── Orders │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘Deep Linking Yapılandırması (Deep Linking Configuration)
// React Native — React Navigation deep linking
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
MainTab: {
screens: {
Home: 'home',
ProductDetail: 'products/:id',
Profile: 'profile',
},
},
Auth: {
screens: {
Login: 'login',
ResetPassword: 'reset-password/:token',
},
},
},
},
};// Flutter — GoRouter deep linking
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'products/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
return ProductDetailScreen(productId: id);
},
),
],
),
GoRoute(
path: '/login',
builder: (context, state) => const LoginScreen(),
),
],
redirect: (context, state) {
final isLoggedIn = authNotifier.isLoggedIn;
final isLoginRoute = state.matchedLocation == '/login';
if (!isLoggedIn && !isLoginRoute) return '/login';
if (isLoggedIn && isLoginRoute) return '/';
return null;
},
);9) API & Network Layer (API ve Ağ Katmanı)
Client Abstraction (İstemci Soyutlaması)
// React Native — API Client with Axios
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
class ApiClient {
private client: AxiosInstance;
constructor(baseURL: string) {
this.client = axios.create({
baseURL,
timeout: 15000,
headers: { 'Content-Type': 'application/json' },
});
this.setupInterceptors();
}
private setupInterceptors(): void {
// Request interceptor — token ekleme (attach token)
this.client.interceptors.request.use(async (config) => {
const token = await SecureStore.getItemAsync('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Response interceptor — token yenileme (refresh token)
this.client.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const newToken = await this.refreshToken();
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return this.client(originalRequest);
} catch {
// Oturumu sonlandır (Force logout)
await this.forceLogout();
}
}
return Promise.reject(error);
}
);
}
async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
const response = await this.client.get<T>(url, config);
return response.data;
}
async post<T>(url: string, data?: any): Promise<T> {
const response = await this.client.post<T>(url, data);
return response.data;
}
async put<T>(url: string, data?: any): Promise<T> {
const response = await this.client.put<T>(url, data);
return response.data;
}
async delete<T>(url: string): Promise<T> {
const response = await this.client.delete<T>(url);
return response.data;
}
private async refreshToken(): Promise<string> {
const refreshToken = await SecureStore.getItemAsync('refresh_token');
const response = await axios.post(`${this.client.defaults.baseURL}/auth/refresh`, {
refresh_token: refreshToken,
});
const { access_token } = response.data;
await SecureStore.setItemAsync('access_token', access_token);
return access_token;
}
private async forceLogout(): Promise<void> {
await SecureStore.deleteItemAsync('access_token');
await SecureStore.deleteItemAsync('refresh_token');
// Navigate to login
}
}Retry Stratejisi (Retry Strategy)
async function withRetry<T>(
fn: () => Promise<T>,
options: {
maxRetries?: number;
baseDelay?: number;
maxDelay?: number;
retryableStatuses?: number[];
} = {}
): Promise<T> {
const {
maxRetries = 3,
baseDelay = 1000,
maxDelay = 10000,
retryableStatuses = [408, 429, 500, 502, 503, 504],
} = options;
let lastError: any;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error: any) {
lastError = error;
const status = error?.response?.status;
const isRetryable = !status || retryableStatuses.includes(status);
if (attempt === maxRetries || !isRetryable) throw error;
// Exponential backoff with jitter
const delay = Math.min(
baseDelay * Math.pow(2, attempt) + Math.random() * 1000,
maxDelay
);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
throw lastError;
}10) Responsive UI & Adaptif Tasarım (Responsive UI & Adaptive Design)
Ekran Boyutu Sınıflandırması (Screen Size Classification)
| Kategori (Category) | Genişlik (Width) | Örnek Cihaz (Example Device) |
|---|---|---|
| Küçük Telefon (Small Phone) | < 360 dp | iPhone SE, eski Android |
| Normal Telefon (Phone) | 360-414 dp | iPhone 14, Pixel 7 |
| Büyük Telefon (Phablet) | 414-600 dp | iPhone 14 Pro Max, Galaxy Ultra |
| Küçük Tablet (Small Tablet) | 600-840 dp | iPad Mini, Galaxy Tab |
| Tablet | 840-1200 dp | iPad Pro 11" |
| Büyük Tablet / Masaüstü | > 1200 dp | iPad Pro 12.9", Masaüstü |
Responsive Tasarım Kuralları (Responsive Design Rules)
// React Native — Responsive utility
import { Dimensions, PixelRatio, Platform, StatusBar } from 'react-native';
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
// Referans tasarım boyutu (Reference design size)
const BASE_WIDTH = 375; // iPhone 14 genişliği
const BASE_HEIGHT = 812;
// Ölçekleme fonksiyonları (Scaling functions)
export const scale = (size: number): number =>
(SCREEN_WIDTH / BASE_WIDTH) * size;
export const verticalScale = (size: number): number =>
(SCREEN_HEIGHT / BASE_HEIGHT) * size;
export const moderateScale = (size: number, factor = 0.5): number =>
size + (scale(size) - size) * factor;
// Cihaz tipi belirleme (Device type detection)
export const isTablet = (): boolean => {
const ratio = PixelRatio.get();
const adjustedWidth = SCREEN_WIDTH * ratio;
return adjustedWidth >= 1000;
};
// Safe area değerleri (Safe area values)
export const getStatusBarHeight = (): number => {
if (Platform.OS === 'ios') return 44;
return StatusBar.currentHeight ?? 24;
};Orientation Desteği (Orientation Support)
// React Native — Orientation hook
import { useWindowDimensions } from 'react-native';
function useOrientation() {
const { width, height } = useWindowDimensions();
const isPortrait = height >= width;
const isLandscape = width > height;
return {
isPortrait,
isLandscape,
screenWidth: width,
screenHeight: height,
columns: isTablet() ? (isLandscape ? 4 : 3) : (isLandscape ? 3 : 2),
};
}Tablet Layout Stratejisi (Tablet Layout Strategy)
Telefon (Phone): Tablet:
┌──────────────┐ ┌─────────┬──────────────┐
│ Liste │ │ Liste │ Detay │
│ (List) │ ──────> │ (Master)│ (Detail) │
│ │ │ │ │
│ │ │ │ │
│ │ │ 1/3 │ 2/3 │
└──────────────┘ └─────────┴──────────────┘11) Accessibility — Erişilebilirlik
Erişilebilirlik, uygulamanızı engelli kullanıcılar dahil herkes için kullanılabilir kılmaktır. Hem etik bir sorumluluk hem de birçok ülkede yasal gerekliliktir.
Screen Reader Desteği (Screen Reader Support)
// React Native — Erişilebilir bileşen
<TouchableOpacity
accessible={true}
accessibilityLabel="Sepete ekle butonu"
accessibilityHint="Bu ürünü alışveriş sepetinize ekler"
accessibilityRole="button"
accessibilityState={{ disabled: outOfStock }}
onPress={handleAddToCart}
>
<Text>Sepete Ekle</Text>
</TouchableOpacity>
// Canlı duyuru (Live announcement)
import { AccessibilityInfo } from 'react-native';
AccessibilityInfo.announceForAccessibility('Ürün sepete eklendi');// Flutter — Erişilebilir bileşen
Semantics(
label: 'Sepete ekle butonu',
hint: 'Bu ürünü alışveriş sepetinize ekler',
button: true,
enabled: !outOfStock,
child: ElevatedButton(
onPressed: outOfStock ? null : handleAddToCart,
child: const Text('Sepete Ekle'),
),
)Erişilebilirlik Kontrol Listesi (Accessibility Checklist)
| Kriter (Criteria) | Açıklama (Description) | Minimum Standart |
|---|---|---|
| Renk Kontrastı (Color Contrast) | Metin ile arka plan arasındaki kontrast oranı | WCAG AA: 4.5:1 (normal metin), 3:1 (büyük metin) |
| Dokunma Alanı (Touch Target) | Dokunulabilir öğelerin minimum boyutu | 44x44 pt (iOS), 48x48 dp (Android) |
| Büyük Font Desteği (Large Font) | Sistem font boyutu ayarına uyum | Metin 200%'e kadar ölçeklenmeli |
| Screen Reader Etiketleri | Tüm etkileşimli öğelerde accessibilityLabel | Her buton, giriş, resim için zorunlu |
| Odak Sırası (Focus Order) | Tab/swipe ile mantıksal gezinme sırası | Soldan sağa, yukarıdan aşağıya |
| Hareket Azaltma (Reduce Motion) | Animasyonları devre dışı bırakma seçeneği | prefers-reduced-motion desteği |
| Renk Körlüğü (Color Blindness) | Bilgiyi sadece renkle iletmeme | İkon, metin veya desen eklenmeli |
Test Araçları (Testing Tools)
| Araç (Tool) | Platform | Açıklama (Description) |
|---|---|---|
| Accessibility Inspector | iOS | Xcode'da erişilebilirlik denetleyicisi |
| Accessibility Scanner | Android | Google'ın erişilebilirlik tarama aracı |
| VoiceOver | iOS | Apple ekran okuyucu ile manuel test |
| TalkBack | Android | Google ekran okuyucu ile manuel test |
| axe / eslint-plugin-jsx-a11y | RN | Otomatik erişilebilirlik lintleme |
| Semantics Debugger | Flutter | showSemanticsDebugger: true ile görsel denetim |
12) App Size Optimization (Uygulama Boyutu Optimizasyonu)
Android Optimizasyonları
| Teknik (Technique) | Açıklama (Description) | Kazanım (Savings) |
|---|---|---|
| AAB Kullanımı | APK yerine App Bundle — cihaza özel dağıtım | %15-25 daha küçük |
| ProGuard / R8 | Kullanılmayan kod ve sınıfları temizleme | %10-30 kod küçültme |
| Resource Shrinking | Kullanılmayan kaynakları (resim, string) temizleme | Değişken |
| WebP Formatı | PNG/JPEG yerine WebP kullanımı | %25-35 daha küçük resimler |
| Dynamic Feature Modules | İsteğe bağlı modülleri gerektiğinde indirme | İlk yükleme boyutu azalır |
React Native Özel Optimizasyonlar
| Teknik (Technique) | Açıklama (Description) |
|---|---|
| Hermes Engine | Bytecode derleme ile daha küçük bundle ve hızlı başlatma |
| RAM Bundles | Sadece gerekli modülleri yükleme (lazy loading) |
| Asset Sıkıştırma | Resimleri derleme öncesi optimize etme (sharp, imagemin) |
| Kullanılmayan Import'ları Temizleme | Tree shaking ve dead code elimination |
Flutter Özel Optimizasyonlar
| Teknik (Technique) | Açıklama (Description) |
|---|---|
--split-debug-info | Debug sembollerini ayırma — APK boyutunu azaltır |
--obfuscate | Kod obfuscation ile boyut küçültme |
| Deferred Components | Bileşenleri gerektiğinde indirme |
--tree-shake-icons | Kullanılmayan Material ikonlarını temizleme |
iOS Optimizasyonları (App Thinning)
| Teknik (Technique) | Açıklama (Description) |
|---|---|
| App Slicing | Cihaza özel kaynak dağıtımı (1x, 2x, 3x) |
| Bitcode | Apple sunucularında yeniden optimizasyon |
| On-Demand Resources | Kaynakları gerektiğinde indirme (oyun seviyeleri vb.) |
13) Güvenlik Mimarisi (Security Architecture)
Token Saklama (Token Storage)
| Yöntem (Method) | Platform | Güvenlik Seviyesi | Açıklama |
|---|---|---|---|
| Keychain | iOS | Yüksek | Donanım şifrelemeli güvenli depo |
| Keystore / EncryptedSharedPreferences | Android | Yüksek | Donanım destekli şifreleme |
| expo-secure-store | RN (Expo) | Yüksek | Keychain/Keystore wrapper |
| react-native-keychain | RN (CLI) | Yüksek | Native Keychain/Keystore erişimi |
| flutter_secure_storage | Flutter | Yüksek | Keychain/Keystore wrapper |
| AsyncStorage / SharedPreferences | RN / Flutter | Düşük | Şifrelenmemiş — token için KULLANMAYIN |
Token Saklama Kuralı (Token Storage Rule)
Access token ve refresh token asla AsyncStorage, SharedPreferences veya MMKV gibi şifrelenmemiş depolarda saklanmamalıdır. Her zaman platform'un güvenli depolama mekanizmasını kullanın.
Certificate Pinning (Sertifika Sabitleme)
// React Native — SSL Pinning (react-native-ssl-pinning)
import { fetch } from 'react-native-ssl-pinning';
const response = await fetch('https://api.example.com/data', {
method: 'GET',
sslPinning: {
certs: ['my_server_cert'], // .cer dosyası assets içinde
},
headers: {
Authorization: `Bearer ${token}`,
},
});// Flutter — Certificate Pinning (Dio + dio_http2_adapter)
final dio = Dio();
(dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
final client = HttpClient();
client.badCertificateCallback = (cert, host, port) {
// Sertifika parmak izini doğrula (Verify certificate fingerprint)
final expectedFingerprint = 'AB:CD:EF:...';
final certFingerprint = sha256(cert.der);
return certFingerprint == expectedFingerprint;
};
return client;
};Biyometrik Kimlik Doğrulama (Biometric Authentication)
// React Native — expo-local-authentication
import * as LocalAuthentication from 'expo-local-authentication';
async function authenticateWithBiometrics(): Promise<boolean> {
// 1. Donanım desteğini kontrol et (Check hardware support)
const hasHardware = await LocalAuthentication.hasHardwareAsync();
if (!hasHardware) return false;
// 2. Kayıtlı biyometri var mı? (Is biometric enrolled?)
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
if (!isEnrolled) return false;
// 3. Kimlik doğrulama (Authenticate)
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Kimliğinizi doğrulayın',
cancelLabel: 'İptal',
fallbackLabel: 'PIN Kullan',
disableDeviceFallback: false,
});
return result.success;
}Root/Jailbreak Tespiti (Root/Jailbreak Detection)
| Kontrol (Check) | iOS (Jailbreak) | Android (Root) |
|---|---|---|
| Dosya Varlığı | /Applications/Cydia.app, /private/var/stash | /system/app/Superuser.apk, /sbin/su |
| Yazma Testi | Sistem dizinine yazma denemesi | /system dizinine yazma denemesi |
| Kütüphane Kontrolü | MobileSubstrate varlığı | su binary erişimi |
| Paket Yöneticisi | apt, dpkg varlığı | Magisk, SuperSU varlığı |
| Kütüphane | IOSSecuritySuite | rootbeer, RNRootDetection |
Kod Obfuscation (Kod Karartma)
// Android — build.gradle (ProGuard/R8)
android {
buildTypes {
release {
minifyEnabled true // Kod küçültme (Code shrinking)
shrinkResources true // Kaynak küçültme (Resource shrinking)
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
}# Flutter — Obfuscation ile build
flutter build apk \
--obfuscate \
--split-debug-info=build/debug-info \
--tree-shake-iconsGüvenlik Kontrol Listesi (Security Checklist)
| Kontrol (Check) | Açıklama (Description) | Öncelik (Priority) |
|---|---|---|
| Güvenli Token Saklama | Keychain / Keystore kullanımı | Kritik |
| Certificate Pinning | API iletişiminde sertifika sabitleme | Yüksek |
| Kod Obfuscation | Release build'de kod karartma | Yüksek |
| Root/Jailbreak Tespiti | Cihaz güvenlik kontrolü | Orta |
| Biyometrik Auth | Hassas işlemler için biyometrik doğrulama | Orta |
| Network Security Config | Cleartext traffic engelleme (HTTP yasak) | Yüksek |
| Clipboard Temizleme | Hassas verileri clipboard'dan silme | Orta |
| Screenshot Engelleme | Hassas ekranlarda ekran görüntüsü engeli | Orta |
| Debug Tespiti | Debugger bağlantısını tespit etme | Düşük |
| Secure Logging | Production'da hassas verileri loglamamak | Yüksek |
14) İlgili Rehberler (Related Guides)
- Mobil Geliştirme Genel Bakış — Framework karşılaştırma ve terim sözlüğü
- React Native Rehberi — Kurulumdan production'a React Native
- Flutter Rehberi — Dart ve Flutter ile mobil geliştirme
- Uygulama Yayınlama Rehberi — Store yayınlama, sertifikalar, CI/CD
- Mobil Araçlar Rehberi — IDE, emülatör, debug, profiling araçları
- Yazılım Mimarisi Rehberi — Backend mimari prensipleri
- API Geliştirme Rehberi — RESTful API tasarımı
- Docker Rehberi — Mobil backend containerization