Skip to content

Mobile-Specific Network Considerations

Mobile networking should not be designed like desktop web networking. Connectivity changes, radios consume battery, apps move to background, and users care about data plans.

Quick Decision

SituationApproachWatch Out
Weak connectivityOffline cache + retryShow status to the user
Network changes oftenIdempotent requestDuplicate results must be prevented
Limited data planCompression + paginationQuality reduction must be managed
Background syncOS schedulerTiming is not guaranteed

Production Checklist

  • Problem: Which mobile network property breaks the flow?
  • Solution: Are timeout, retry, cache, offline state, metered network, and cancellation clear?
  • Trade-off: A more resilient network layer adds more state and testing cost.
  • Failure mode: Captive portals, network switches, airplane mode, timeout storms, and partial responses should be handled.
  • Measurement: Track network error rate, retry count, data usage, request cancellation, and offline duration.
  • Security/cost: Assume public Wi-Fi and require TLS; unnecessary transfer costs users money.

Battery Optimization

Network Efficiency

Battery life is a critical factor in mobile devices, and network operations are one of the biggest causes of battery drain.

Request Coalescing

javascript
class RequestCoalescer {
  constructor(windowSize = 100) {
    this.windowSize = windowSize;
    this.pendingRequests = [];
    this.coalesceTimer = null;
  }

  addRequest(request) {
    this.pendingRequests.push(request);
    
    if (!this.coalesceTimer) {
      this.coalesceTimer = setTimeout(() => {
        this.processBatchedRequests();
      }, this.windowSize);
    }
  }

  processBatchedRequests() {
    if (this.pendingRequests.length === 0) return;
    
    // Group similar requests
    const groupedRequests = this.groupSimilarRequests(this.pendingRequests);
    
    // Send batch request for each group
    groupedRequests.forEach(group => {
      this.sendBatchRequest(group);
    });
    
    this.pendingRequests = [];
    this.coalesceTimer = null;
  }

  groupSimilarRequests(requests) {
    const groups = new Map();
    
    requests.forEach(request => {
      const key = `${request.method}-${request.baseUrl}`;
      if (!groups.has(key)) {
        groups.set(key, []);
      }
      groups.get(key).push(request);
    });
    
    return Array.from(groups.values());
  }

  async sendBatchRequest(requests) {
    if (requests.length === 1) {
      return this.sendSingleRequest(requests[0]);
    }

    const batchPayload = {
      requests: requests.map(req => ({
        id: req.id,
        method: req.method,
        path: req.path,
        body: req.body,
        headers: req.headers
      }))
    };

    try {
      const response = await fetch(`${requests[0].baseUrl}/batch`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(batchPayload)
      });

      const results = await response.json();
      
      // Distribute results to individual request callbacks
      results.forEach(result => {
        const originalRequest = requests.find(req => req.id === result.id);
        if (originalRequest && originalRequest.callback) {
          originalRequest.callback(result.error, result.data);
        }
      });
    } catch (error) {
      // Fallback to individual requests
      requests.forEach(request => {
        this.sendSingleRequest(request);
      });
    }
  }

  async sendSingleRequest(request) {
    try {
      const response = await fetch(`${request.baseUrl}${request.path}`, {
        method: request.method,
        headers: request.headers,
        body: request.body
      });

      const data = await response.json();
      
      if (request.callback) {
        request.callback(null, data);
      }
    } catch (error) {
      if (request.callback) {
        request.callback(error, null);
      }
    }
  }
}

Radio State Management

kotlin
// Android - Kotlin
class RadioStateManager(private val context: Context) {
    private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) 
        as ConnectivityManager
    private val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) 
        as TelephonyManager
    
    private var isHighPowerMode = false
    private val stateTransitionDelay = 2000L // 2 seconds
    private var lastActivityTime = 0L

    fun optimizeRadioUsage() {
        // Coalesce network requests
        scheduleRequestBatch()
        
        // Monitor radio state transitions
        monitorRadioState()
        
        // Implement intelligent wake-up scheduling
        schedulePeriodicTasks()
    }

    private fun scheduleRequestBatch() {
        val handler = Handler(Looper.getMainLooper())
        handler.postDelayed({
            if (hasPendingRequests()) {
                enterHighPowerMode()
                processPendingRequests()
                scheduleIdleTransition()
            }
        }, stateTransitionDelay)
    }

    private fun enterHighPowerMode() {
        if (!isHighPowerMode) {
            isHighPowerMode = true
            lastActivityTime = System.currentTimeMillis()
            
            // Configure for high-performance networking
            setNetworkPreferences(true)
        }
    }

    private fun enterIdleMode() {
        if (isHighPowerMode) {
            isHighPowerMode = false
            
            // Configure for power-efficient networking
            setNetworkPreferences(false)
        }
    }

    private fun setNetworkPreferences(highPerformance: Boolean) {
        val builder = NetworkRequest.Builder()
        
        if (highPerformance) {
            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
        } else {
            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
        }
        
        val networkRequest = builder.build()
        connectivityManager.requestNetwork(networkRequest, networkCallback)
    }

    private fun scheduleIdleTransition() {
        val handler = Handler(Looper.getMainLooper())
        handler.postDelayed({
            if (System.currentTimeMillis() - lastActivityTime > stateTransitionDelay) {
                enterIdleMode()
            }
        }, stateTransitionDelay)
    }

    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            // Network is available for use
        }

        override fun onLost(network: Network) {
            // Handle network loss
        }
    }

    private fun monitorRadioState() {
        telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_DATA_ACTIVITY)
    }

    private val phoneStateListener = object : PhoneStateListener() {
        override fun onDataActivity(direction: Int) {
            when (direction) {
                TelephonyManager.DATA_ACTIVITY_IN,
                TelephonyManager.DATA_ACTIVITY_OUT,
                TelephonyManager.DATA_ACTIVITY_INOUT -> {
                    lastActivityTime = System.currentTimeMillis()
                }
                TelephonyManager.DATA_ACTIVITY_NONE -> {
                    // No data activity
                }
            }
        }
    }

    private fun hasPendingRequests(): Boolean {
        // Check if there are pending network requests
        return NetworkRequestQueue.instance.hasPendingRequests()
    }

    private fun processPendingRequests() {
        NetworkRequestQueue.instance.processAll()
    }
}
swift
// iOS - Swift
class RadioStateManager {
    private var isHighPowerMode = false
    private let stateTransitionDelay: TimeInterval = 2.0
    private var lastActivityTime: TimeInterval = 0
    private let requestQueue = NetworkRequestQueue()
    
    func optimizeRadioUsage() {
        // Coalesce network requests
        scheduleRequestBatch()
        
        // Monitor radio state
        monitorRadioState()
        
        // Schedule periodic tasks efficiently
        schedulePeriodicTasks()
    }
    
    private func scheduleRequestBatch() {
        DispatchQueue.main.asyncAfter(deadline: .now() + stateTransitionDelay) {
            if self.requestQueue.hasPendingRequests() {
                self.enterHighPowerMode()
                self.requestQueue.processAll()
                self.scheduleIdleTransition()
            }
        }
    }
    
    private func enterHighPowerMode() {
        guard !isHighPowerMode else { return }
        
        isHighPowerMode = true
        lastActivityTime = Date().timeIntervalSince1970
        
        // Configure URLSession for high performance
        configureURLSession(highPerformance: true)
    }
    
    private func enterIdleMode() {
        guard isHighPowerMode else { return }
        
        isHighPowerMode = false
        
        // Configure URLSession for power efficiency
        configureURLSession(highPerformance: false)
    }
    
    private func configureURLSession(highPerformance: Bool) {
        let configuration = URLSessionConfiguration.default
        
        if highPerformance {
            configuration.timeoutIntervalForRequest = 30
            configuration.timeoutIntervalForResource = 60
            configuration.httpMaximumConnectionsPerHost = 6
        } else {
            configuration.timeoutIntervalForRequest = 60
            configuration.timeoutIntervalForResource = 300
            configuration.httpMaximumConnectionsPerHost = 2
        }
        
        configuration.allowsCellularAccess = true
        configuration.waitsForConnectivity = !highPerformance
    }
    
    private func scheduleIdleTransition() {
        DispatchQueue.main.asyncAfter(deadline: .now() + stateTransitionDelay) {
            let currentTime = Date().timeIntervalSince1970
            if currentTime - self.lastActivityTime > self.stateTransitionDelay {
                self.enterIdleMode()
            }
        }
    }
    
    private func monitorRadioState() {
        // Monitor network reachability
        let monitor = NWPathMonitor()
        let queue = DispatchQueue(label: "NetworkMonitor")
        
        monitor.pathUpdateHandler = { [weak self] path in
            if path.status == .satisfied {
                self?.lastActivityTime = Date().timeIntervalSince1970
            }
        }
        
        monitor.start(queue: queue)
    }
    
    private func schedulePeriodicTasks() {
        // Use background app refresh efficiently
        let identifier = "com.app.network-sync"
        
        let request = BGAppRefreshTaskRequest(identifier: identifier)
        request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15 minutes
        
        try? BGTaskScheduler.shared.submit(request)
    }
}

class NetworkRequestQueue {
    private var pendingRequests: [NetworkRequest] = []
    private let queue = DispatchQueue(label: "NetworkRequestQueue")
    
    func addRequest(_ request: NetworkRequest) {
        queue.async {
            self.pendingRequests.append(request)
        }
    }
    
    func hasPendingRequests() -> Bool {
        return queue.sync {
            return !pendingRequests.isEmpty
        }
    }
    
    func processAll() {
        queue.async {
            let requests = self.pendingRequests
            self.pendingRequests.removeAll()
            
            // Process requests in batch
            self.processBatch(requests)
        }
    }
    
    private func processBatch(_ requests: [NetworkRequest]) {
        // Group similar requests
        let groupedRequests = Dictionary(grouping: requests) { request in
            return "\(request.method)-\(request.baseURL.host ?? "")"
        }
        
        // Process each group
        groupedRequests.values.forEach { group in
            if group.count == 1 {
                self.processSingleRequest(group[0])
            } else {
                self.processBatchRequest(group)
            }
        }
    }
    
    private func processSingleRequest(_ request: NetworkRequest) {
        // Process individual request
        URLSession.shared.dataTask(with: request.urlRequest) { data, response, error in
            request.completion?(data, response, error)
        }.resume()
    }
    
    private func processBatchRequest(_ requests: [NetworkRequest]) {
        // Process batch request
        // Implementation depends on server-side batch API
    }
}

struct NetworkRequest {
    let method: String
    let baseURL: URL
    let path: String
    let parameters: [String: Any]?
    let completion: ((Data?, URLResponse?, Error?) -> Void)?
    
    var urlRequest: URLRequest {
        let url = baseURL.appendingPathComponent(path)
        var request = URLRequest(url: url)
        request.httpMethod = method
        
        if let parameters = parameters {
            request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }
        
        return request
    }
}

Background Sync

Efficient background synchronization minimizes battery drain.

dart
// Flutter - Dart
class BackgroundSyncManager {
  static const String _taskName = 'background_sync';
  final SharedPreferences _prefs;
  final List<SyncTask> _pendingTasks = [];
  Timer? _syncTimer;
  
  BackgroundSyncManager(this._prefs);
  
  void initialize() {
    // Register background task
    Workmanager().initialize(callbackDispatcher);
    
    // Schedule periodic sync
    schedulePeriodicSync();
    
    // Handle app lifecycle changes
    _handleAppLifecycle();
  }
  
  void schedulePeriodicSync() {
    Workmanager().registerPeriodicTask(
      _taskName,
      _taskName,
      frequency: const Duration(minutes: 15),
      constraints: Constraints(
        networkType: NetworkType.connected,
        requiresBatteryNotLow: true,
      ),
    );
  }
  
  void addSyncTask(SyncTask task) {
    _pendingTasks.add(task);
    _persistPendingTasks();
    
    // If app is active, sync immediately
    if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
      _syncImmediately();
    }
  }
  
  void _syncImmediately() {
    _syncTimer?.cancel();
    _syncTimer = Timer(const Duration(milliseconds: 500), () {
      _performSync();
    });
  }
  
  Future<void> _performSync() async {
    if (_pendingTasks.isEmpty) return;
    
    final tasks = List<SyncTask>.from(_pendingTasks);
    _pendingTasks.clear();
    
    // Group tasks by priority and type
    final groupedTasks = _groupTasks(tasks);
    
    // Process each group
    for (final group in groupedTasks) {
      await _processSyncGroup(group);
    }
    
    _persistPendingTasks();
  }
  
  List<List<SyncTask>> _groupTasks(List<SyncTask> tasks) {
    // Sort by priority
    tasks.sort((a, b) => b.priority.compareTo(a.priority));
    
    final groups = <List<SyncTask>>[];
    var currentGroup = <SyncTask>[];
    String? currentType;
    
    for (final task in tasks) {
      if (currentType != task.type || currentGroup.length >= 10) {
        if (currentGroup.isNotEmpty) {
          groups.add(currentGroup);
        }
        currentGroup = <SyncTask>[task];
        currentType = task.type;
      } else {
        currentGroup.add(task);
      }
    }
    
    if (currentGroup.isNotEmpty) {
      groups.add(currentGroup);
    }
    
    return groups;
  }
  
  Future<void> _processSyncGroup(List<SyncTask> group) async {
    if (group.isEmpty) return;
    
    try {
      if (group.length == 1) {
        await _processSingleTask(group.first);
      } else {
        await _processBatchTasks(group);
      }
    } catch (e) {
      // Handle sync errors
      _handleSyncError(group, e);
    }
  }
  
  Future<void> _processSingleTask(SyncTask task) async {
    final dio = Dio();
    
    final response = await dio.request(
      task.endpoint,
      data: task.data,
      options: Options(method: task.method),
    );
    
    if (response.statusCode == 200) {
      // Task completed successfully
      _onTaskCompleted(task);
    } else {
      throw DioError(
        requestOptions: response.requestOptions,
        response: response,
      );
    }
  }
  
  Future<void> _processBatchTasks(List<SyncTask> tasks) async {
    final dio = Dio();
    
    final batchData = {
      'tasks': tasks.map((task) => {
        'id': task.id,
        'type': task.type,
        'data': task.data,
        'endpoint': task.endpoint,
        'method': task.method,
      }).toList(),
    };
    
    final response = await dio.post('/api/batch-sync', data: batchData);
    
    if (response.statusCode == 200) {
      final results = response.data['results'] as List;
      
      for (final result in results) {
        final taskId = result['id'];
        final success = result['success'] == true;
        final task = tasks.firstWhere((t) => t.id == taskId);
        
        if (success) {
          _onTaskCompleted(task);
        } else {
          _onTaskFailed(task, result['error']);
        }
      }
    }
  }
  
  void _handleSyncError(List<SyncTask> tasks, dynamic error) {
    for (final task in tasks) {
      if (task.retryCount < task.maxRetries) {
        task.retryCount++;
        task.nextRetryTime = DateTime.now().add(
          Duration(seconds: math.pow(2, task.retryCount).toInt() * 30)
        );
        _pendingTasks.add(task);
      } else {
        _onTaskFailed(task, error);
      }
    }
  }
  
  void _onTaskCompleted(SyncTask task) {
    // Notify listeners or update UI
    print('Task completed: ${task.id}');
  }
  
  void _onTaskFailed(SyncTask task, dynamic error) {
    // Handle permanent task failure
    print('Task failed permanently: ${task.id} - $error');
  }
  
  void _persistPendingTasks() {
    final tasksJson = _pendingTasks.map((task) => task.toJson()).toList();
    _prefs.setString('pending_sync_tasks', jsonEncode(tasksJson));
  }
  
  void _loadPendingTasks() {
    final tasksJson = _prefs.getString('pending_sync_tasks');
    if (tasksJson != null) {
      final tasksList = jsonDecode(tasksJson) as List;
      _pendingTasks.addAll(
        tasksList.map((json) => SyncTask.fromJson(json))
      );
    }
  }
  
  void _handleAppLifecycle() {
    WidgetsBinding.instance.didChangeAppLifecycleState = (state) {
      switch (state) {
        case AppLifecycleState.paused:
          // App going to background, prepare for background sync
          _persistPendingTasks();
          break;
        case AppLifecycleState.resumed:
          // App returning to foreground, sync immediately if needed
          _loadPendingTasks();
          if (_pendingTasks.isNotEmpty) {
            _syncImmediately();
          }
          break;
        default:
          break;
      }
    };
  }
}

class SyncTask {
  final String id;
  final String type;
  final String endpoint;
  final String method;
  final Map<String, dynamic> data;
  final int priority;
  final int maxRetries;
  int retryCount;
  DateTime? nextRetryTime;
  
  SyncTask({
    required this.id,
    required this.type,
    required this.endpoint,
    required this.method,
    required this.data,
    this.priority = 0,
    this.maxRetries = 3,
    this.retryCount = 0,
    this.nextRetryTime,
  });
  
  Map<String, dynamic> toJson() => {
    'id': id,
    'type': type,
    'endpoint': endpoint,
    'method': method,
    'data': data,
    'priority': priority,
    'maxRetries': maxRetries,
    'retryCount': retryCount,
    'nextRetryTime': nextRetryTime?.toIso8601String(),
  };
  
  static SyncTask fromJson(Map<String, dynamic> json) => SyncTask(
    id: json['id'],
    type: json['type'],
    endpoint: json['endpoint'],
    method: json['method'],
    data: json['data'],
    priority: json['priority'] ?? 0,
    maxRetries: json['maxRetries'] ?? 3,
    retryCount: json['retryCount'] ?? 0,
    nextRetryTime: json['nextRetryTime'] != null 
        ? DateTime.parse(json['nextRetryTime'])
        : null,
  );
}

void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) async {
    final syncManager = BackgroundSyncManager(
      await SharedPreferences.getInstance()
    );
    
    // Perform background sync
    await syncManager._performSync();
    
    return Future.value(true);
  });
}

Offline Support

Data Synchronization

Robust offline capabilities are essential for mobile applications.

javascript
class OfflineDataManager {
  constructor() {
    this.db = null;
    this.syncQueue = [];
    this.isOnline = navigator.onLine;
    this.initializeDatabase();
    this.setupNetworkListeners();
  }

  async initializeDatabase() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open('OfflineDB', 1);
      
      request.onerror = () => reject(request.error);
      request.onsuccess = () => {
        this.db = request.result;
        resolve();
      };
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        
        // Create stores
        if (!db.objectStoreNames.contains('data')) {
          const dataStore = db.createObjectStore('data', { keyPath: 'id' });
          dataStore.createIndex('timestamp', 'timestamp');
          dataStore.createIndex('type', 'type');
        }
        
        if (!db.objectStoreNames.contains('sync_queue')) {
          const syncStore = db.createObjectStore('sync_queue', { keyPath: 'id' });
          syncStore.createIndex('priority', 'priority');
          syncStore.createIndex('timestamp', 'timestamp');
        }
        
        if (!db.objectStoreNames.contains('metadata')) {
          db.createObjectStore('metadata', { keyPath: 'key' });
        }
      };
    });
  }

  setupNetworkListeners() {
    window.addEventListener('online', () => {
      this.isOnline = true;
      this.processOfflineQueue();
    });
    
    window.addEventListener('offline', () => {
      this.isOnline = false;
    });
  }

  async saveData(data, options = {}) {
    const record = {
      id: data.id || this.generateId(),
      ...data,
      timestamp: Date.now(),
      synced: this.isOnline,
      offline: !this.isOnline
    };

    // Save to local database
    await this.storeLocally('data', record);

    if (this.isOnline) {
      // Try to sync immediately
      try {
        await this.syncToServer(record);
        record.synced = true;
        await this.storeLocally('data', record);
      } catch (error) {
        // Add to sync queue for later
        await this.addToSyncQueue(record, 'CREATE');
      }
    } else {
      // Add to sync queue
      await this.addToSyncQueue(record, 'CREATE');
    }

    return record;
  }

  async getData(id) {
    return this.getFromStore('data', id);
  }

  async getAllData(options = {}) {
    const { type, limit, offset } = options;
    
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(['data'], 'readonly');
      const store = transaction.objectStore('data');
      
      let request;
      if (type) {
        const index = store.index('type');
        request = index.getAll(type);
      } else {
        request = store.getAll();
      }
      
      request.onsuccess = () => {
        let results = request.result;
        
        // Apply pagination
        if (offset) results = results.slice(offset);
        if (limit) results = results.slice(0, limit);
        
        resolve(results);
      };
      
      request.onerror = () => reject(request.error);
    });
  }

  async updateData(id, updates) {
    const existing = await this.getData(id);
    if (!existing) throw new Error('Record not found');

    const updated = {
      ...existing,
      ...updates,
      timestamp: Date.now(),
      synced: false
    };

    await this.storeLocally('data', updated);

    if (this.isOnline) {
      try {
        await this.syncUpdateToServer(updated);
        updated.synced = true;
        await this.storeLocally('data', updated);
      } catch (error) {
        await this.addToSyncQueue(updated, 'UPDATE');
      }
    } else {
      await this.addToSyncQueue(updated, 'UPDATE');
    }

    return updated;
  }

  async deleteData(id) {
    const existing = await this.getData(id);
    if (!existing) return;

    // Mark as deleted locally
    const deleted = {
      ...existing,
      deleted: true,
      timestamp: Date.now(),
      synced: false
    };

    await this.storeLocally('data', deleted);

    if (this.isOnline) {
      try {
        await this.syncDeleteToServer(id);
        // Remove from local store after successful sync
        await this.removeFromStore('data', id);
      } catch (error) {
        await this.addToSyncQueue(deleted, 'DELETE');
      }
    } else {
      await this.addToSyncQueue(deleted, 'DELETE');
    }
  }

  async addToSyncQueue(record, operation) {
    const queueItem = {
      id: this.generateId(),
      recordId: record.id,
      operation,
      data: record,
      timestamp: Date.now(),
      retries: 0,
      priority: this.getSyncPriority(operation)
    };

    await this.storeLocally('sync_queue', queueItem);
  }

  getSyncPriority(operation) {
    switch (operation) {
      case 'DELETE': return 3;
      case 'UPDATE': return 2;
      case 'CREATE': return 1;
      default: return 0;
    }
  }

  async processOfflineQueue() {
    if (!this.isOnline) return;

    const queueItems = await this.getAllFromStore('sync_queue');
    
    // Sort by priority and timestamp
    queueItems.sort((a, b) => {
      if (a.priority !== b.priority) return b.priority - a.priority;
      return a.timestamp - b.timestamp;
    });

    for (const item of queueItems) {
      try {
        await this.processSyncItem(item);
        await this.removeFromStore('sync_queue', item.id);
      } catch (error) {
        console.error('Sync failed for item:', item.id, error);
        
        // Increment retry count
        item.retries++;
        if (item.retries < 5) {
          item.timestamp = Date.now() + (item.retries * 30000); // Exponential backoff
          await this.storeLocally('sync_queue', item);
        } else {
          // Max retries reached, handle permanent failure
          await this.handleSyncFailure(item);
          await this.removeFromStore('sync_queue', item.id);
        }
      }
    }
  }

  async processSyncItem(item) {
    switch (item.operation) {
      case 'CREATE':
        await this.syncToServer(item.data);
        break;
      case 'UPDATE':
        await this.syncUpdateToServer(item.data);
        break;
      case 'DELETE':
        await this.syncDeleteToServer(item.recordId);
        await this.removeFromStore('data', item.recordId);
        break;
    }

    // Mark as synced
    if (item.operation !== 'DELETE') {
      const record = await this.getData(item.recordId);
      if (record) {
        record.synced = true;
        await this.storeLocally('data', record);
      }
    }
  }

  async syncToServer(record) {
    const response = await fetch('/api/data', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(record)
    });

    if (!response.ok) {
      throw new Error(`Sync failed: ${response.statusText}`);
    }

    return response.json();
  }

  async syncUpdateToServer(record) {
    const response = await fetch(`/api/data/${record.id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(record)
    });

    if (!response.ok) {
      throw new Error(`Update sync failed: ${response.statusText}`);
    }

    return response.json();
  }

  async syncDeleteToServer(recordId) {
    const response = await fetch(`/api/data/${recordId}`, {
      method: 'DELETE'
    });

    if (!response.ok) {
      throw new Error(`Delete sync failed: ${response.statusText}`);
    }
  }

  async handleSyncFailure(item) {
    console.error('Permanent sync failure for item:', item);
    // Could notify user, log to analytics, etc.
  }

  // Utility methods
  async storeLocally(storeName, data) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readwrite');
      const store = transaction.objectStore(storeName);
      const request = store.put(data);
      
      request.onsuccess = () => resolve();
      request.onerror = () => reject(request.error);
    });
  }

  async getFromStore(storeName, id) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readonly');
      const store = transaction.objectStore(storeName);
      const request = store.get(id);
      
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async getAllFromStore(storeName) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readonly');
      const store = transaction.objectStore(storeName);
      const request = store.getAll();
      
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async removeFromStore(storeName, id) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], 'readwrite');
      const store = transaction.objectStore(storeName);
      const request = store.delete(id);
      
      request.onsuccess = () => resolve();
      request.onerror = () => reject(request.error);
    });
  }

  generateId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
  }
}

Cross-Platform Networking

Universal HTTP Client

A consistent networking interface across all platforms.

javascript
class UniversalHttpClient {
  constructor(config = {}) {
    this.baseURL = config.baseURL || '';
    this.timeout = config.timeout || 30000;
    this.headers = config.headers || {};
    this.interceptors = {
      request: [],
      response: []
    };
    
    // Platform detection
    this.platform = this.detectPlatform();
    this.adapter = this.createAdapter();
  }

  detectPlatform() {
    if (typeof window !== 'undefined') {
      return 'web';
    } else if (typeof global !== 'undefined' && global.process) {
      return 'node';
    } else if (typeof self !== 'undefined') {
      return 'worker';
    }
    return 'unknown';
  }

  createAdapter() {
    switch (this.platform) {
      case 'web':
        return new WebNetworkAdapter();
      case 'node':
        return new NodeNetworkAdapter();
      default:
        return new NativeNetworkAdapter();
    }
  }

  async request(config) {
    // Apply request interceptors
    let finalConfig = { ...config };
    for (const interceptor of this.interceptors.request) {
      finalConfig = await interceptor(finalConfig);
    }

    // Merge with instance config
    finalConfig = {
      ...finalConfig,
      url: this.resolveURL(finalConfig.url),
      headers: { ...this.headers, ...finalConfig.headers },
      timeout: finalConfig.timeout || this.timeout
    };

    try {
      let response = await this.adapter.request(finalConfig);
      
      // Apply response interceptors
      for (const interceptor of this.interceptors.response) {
        response = await interceptor(response);
      }
      
      return response;
    } catch (error) {
      // Apply error interceptors
      for (const interceptor of this.interceptors.response) {
        if (interceptor.length > 1) {
          error = await interceptor(error);
        }
      }
      throw error;
    }
  }

  resolveURL(url) {
    if (url.startsWith('http')) return url;
    return this.baseURL.replace(/\/$/, '') + '/' + url.replace(/^\//, '');
  }

  // Convenience methods
  async get(url, config = {}) {
    return this.request({ ...config, url, method: 'GET' });
  }

  async post(url, data, config = {}) {
    return this.request({ ...config, url, method: 'POST', data });
  }

  async put(url, data, config = {}) {
    return this.request({ ...config, url, method: 'PUT', data });
  }

  async delete(url, config = {}) {
    return this.request({ ...config, url, method: 'DELETE' });
  }
}

// Platform-specific adapters
class WebNetworkAdapter {
  async request(config) {
    const response = await fetch(config.url, {
      method: config.method,
      headers: config.headers,
      body: config.data ? JSON.stringify(config.data) : undefined
    });
    
    return this.normalizeResponse(response);
  }

  async normalizeResponse(response) {
    return {
      status: response.status,
      statusText: response.statusText,
      headers: Object.fromEntries(response.headers.entries()),
      data: await response.json()
    };
  }
}

class NativeNetworkAdapter {
  async request(config) {
    // React Native or native platform implementation
    const response = await fetch(config.url, {
      method: config.method,
      headers: config.headers,
      body: config.data ? JSON.stringify(config.data) : undefined
    });
    
    return this.normalizeResponse(response);
  }

  async normalizeResponse(response) {
    return {
      status: response.status,
      statusText: response.statusText,
      headers: response.headers,
      data: await response.json()
    };
  }
}

These mobile-specific considerations are critical for optimizing network performance and user experience in modern mobile applications. Battery optimization, offline support, and cross-platform compatibility are fundamental requirements for a successful mobile application.

Created by Eren Demir.