Saltar a contenido

PERF-002: Mobile Performance Specifications

Identificador: PERF-002 Version: 1.0.0 Fecha: 2025-12-08 Autor: SpecQueen Technical Division Estado: Aprobado


1. Tabla de Contenidos

  1. Introduccion
  2. iOS Performance
  3. Android Performance
  4. Cross-Platform Metrics
  5. Testing Methodology
  6. Optimization Strategies

2. Introduccion

2.1. Proposito

Este documento especifica los requisitos de rendimiento para las aplicaciones moviles iOS y Android de MedTime, incluyendo benchmarks especificos por plataforma, estrategias de optimizacion y metodologia de testing.

2.2. Referencias

Documento Relevancia
MOB-IOS-001-arquitectura.md Arquitectura iOS
MOB-AND-001-arquitectura.md Arquitectura Android
PERF-001-benchmarks.md SLAs globales
04-seguridad-cliente.md Cifrado E2E

2.3. Principios Mobile-First

┌────────────────────────────────────────────────────────────────┐
│                    MOBILE PERFORMANCE PRINCIPLES                │
├────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. BATTERY IS PRECIOUS                                        │
│     → Minimize background work, batch operations               │
│                                                                 │
│  2. NETWORK IS UNRELIABLE                                      │
│     → Offline-first, graceful degradation                      │
│                                                                 │
│  3. MEMORY IS LIMITED                                          │
│     → Aggressive cleanup, lazy loading                         │
│                                                                 │
│  4. USER ATTENTION IS SHORT                                    │
│     → Fast launch, responsive UI, progress feedback            │
│                                                                 │
│  5. DEVICES VARY WIDELY                                        │
│     → Target mid-range, test on low-end                        │
│                                                                 │
└────────────────────────────────────────────────────────────────┘

3. iOS Performance

3.1. App Lifecycle

3.1.1. Launch Performance

Metrica Target Warning Critical Medicion
Cold Start <2s 2-3s >3s Pre-main + post-main
Warm Start <500ms 500-800ms >1s Resume time
First Frame <1s 1-1.5s >2s First contentful paint
Interactive <2s 2-3s >4s User can tap
// iOS Launch Optimization Checklist

struct LaunchOptimization {

    // MARK: - Pre-main Optimizations

    static let preMainOptimizations = """
    1. Reduce dynamic library count (<6 custom frameworks)
    2. Use static linking where possible
    3. Minimize +load methods
    4. Remove unused code (tree shaking)
    5. Use DYLD_PRINT_STATISTICS to measure
    """

    // MARK: - Post-main Optimizations

    static func optimizedAppDelegate() {
        // Defer non-critical initialization

        // Phase 1: Critical (blocking)
        // - Initialize crash reporting
        // - Setup logging
        // - Load user session

        // Phase 2: Important (async)
        // - Initialize database
        // - Setup crypto manager
        // - Preload first screen data

        // Phase 3: Deferred (after first frame)
        // - Analytics initialization
        // - Push notification setup
        // - Background task registration
    }

    // MARK: - Measurement

    static func measureLaunch() {
        // Use MetricKit for production
        // Use Instruments for development
        let launchMetric = MXAppLaunchMetric()
        // Track: timeToFirstDraw, resumeTime
    }
}

3.1.2. Background Performance

Operacion Max Duration Frequency Battery Budget
Background Fetch 30s 15min 1% max
Background Sync 30s On significant change 0.5%
Silent Push Processing 30s As needed 0.1%
Background Task 3min Daily 2%
// iOS Background Task Configuration

class BackgroundTaskManager {

    // MARK: - Background Fetch

    func registerBackgroundTasks() {
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.medtime.sync",
            using: nil
        ) { task in
            self.handleSyncTask(task as! BGAppRefreshTask)
        }

        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.medtime.cleanup",
            using: nil
        ) { task in
            self.handleCleanupTask(task as! BGProcessingTask)
        }
    }

    // MARK: - Performance Budgets

    struct PerformanceBudget {
        static let syncTask = TaskBudget(
            maxDuration: .seconds(25),  // Leave 5s buffer
            maxMemory: 50_000_000,       // 50MB
            maxCPU: 0.8                  // 80% of one core
        )

        static let cleanupTask = TaskBudget(
            maxDuration: .minutes(2),
            maxMemory: 100_000_000,
            maxCPU: 0.5
        )
    }
}

3.2. SwiftUI Performance

3.2.1. View Rendering

Metrica Target Warning Critical
Frame Rate 60 FPS 45 FPS 30 FPS
View Update <16ms 16-33ms >33ms
List Scroll 0% dropped <5% >10%
Animation 60 FPS 50 FPS <45 FPS
// SwiftUI Performance Best Practices

struct PerformantMedicationList: View {

    @StateObject private var viewModel = MedicationListViewModel()

    var body: some View {
        // Use LazyVStack for large lists
        ScrollView {
            LazyVStack(spacing: 12) {
                ForEach(viewModel.medications) { medication in
                    MedicationRow(medication: medication)
                        .id(medication.id)  // Stable identity
                }
            }
        }
        // Avoid recomputing entire list
        .equatable()
    }
}

// MARK: - Optimization Patterns

struct OptimizationPatterns {

    // 1. Extract expensive views
    struct ExpensiveView: View, Equatable {
        let data: SomeData

        var body: some View {
            // Complex view
        }

        static func == (lhs: Self, rhs: Self) -> Bool {
            lhs.data.id == rhs.data.id
        }
    }

    // 2. Use @StateObject for view models
    // 3. Prefer value types
    // 4. Minimize @State changes
    // 5. Use .drawingGroup() for complex graphics
}

// MARK: - Debug Performance

extension View {
    func debugPerformance() -> some View {
        #if DEBUG
        Self._printChanges()  // Xcode 13+
        return self
        #else
        return self
        #endif
    }
}

3.2.2. Image Handling

Operacion Target Max
Thumbnail Load <50ms 100ms
Full Image Load <200ms 500ms
Image Decode <100ms 300ms
Memory per Image 5MB 20MB
// Efficient Image Loading

struct OptimizedAsyncImage: View {

    let url: URL?
    let size: CGSize

    var body: some View {
        AsyncImage(url: url) { phase in
            switch phase {
            case .empty:
                SkeletonView()
                    .frame(width: size.width, height: size.height)

            case .success(let image):
                image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(width: size.width, height: size.height)
                    .clipped()

            case .failure:
                PlaceholderImage()

            @unknown default:
                EmptyView()
            }
        }
    }
}

// MARK: - Image Cache Configuration

class ImageCacheManager {

    static let shared = ImageCacheManager()

    let cache: URLCache = {
        let cache = URLCache(
            memoryCapacity: 50_000_000,   // 50 MB memory
            diskCapacity: 100_000_000,    // 100 MB disk
            directory: .cachesDirectory
        )
        return cache
    }()

    // Prefetch visible items
    func prefetchImages(urls: [URL]) {
        let prefetcher = ImagePrefetcher(urls: urls)
        prefetcher.start()
    }
}

3.3. SwiftData Performance

3.3.1. Query Performance

Operacion Target Max Items
Single Fetch 5ms 20ms 1
List Fetch 20ms 50ms 50
Batch Fetch 50ms 150ms 500
Predicate Query 30ms 100ms Complex
Sort Operation 10ms 50ms 100 items
// SwiftData Query Optimization

@Model
class Medication {
    // MARK: - Indexed Properties

    @Attribute(.unique)
    var id: UUID

    // Index frequently queried fields
    @Attribute
    var name: String

    @Attribute
    var nextDoseTime: Date?

    @Attribute
    var isActive: Bool

    // MARK: - Relationships (Lazy by default)

    @Relationship(deleteRule: .cascade)
    var schedules: [Schedule]?

    // MARK: - Computed (not persisted)

    var upcomingDoses: [ScheduledDose] {
        // Compute on demand, don't persist
    }
}

// MARK: - Optimized Queries

class MedicationRepository {

    private let context: ModelContext

    // Fetch with predicate and sort (single query)
    func fetchActiveMedications() async throws -> [Medication] {
        let descriptor = FetchDescriptor<Medication>(
            predicate: #Predicate { $0.isActive == true },
            sortBy: [SortDescriptor(\.nextDoseTime)]
        )
        return try context.fetch(descriptor)
    }

    // Paginated fetch for large datasets
    func fetchMedications(page: Int, pageSize: Int = 50) async throws -> [Medication] {
        var descriptor = FetchDescriptor<Medication>(
            sortBy: [SortDescriptor(\.name)]
        )
        descriptor.fetchLimit = pageSize
        descriptor.fetchOffset = page * pageSize
        return try context.fetch(descriptor)
    }

    // Batch insert for sync
    func batchInsert(_ medications: [Medication]) async throws {
        // Use transaction for batch operations
        try context.transaction {
            for medication in medications {
                context.insert(medication)
            }
        }
    }
}

3.3.2. Migration Performance

Migracion Target Max
Lightweight <100ms 500ms
Custom (100 records) <1s 3s
Custom (1000 records) <5s 15s
// Migration Performance Configuration

enum MigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [SchemaV1.self, SchemaV2.self]
    }

    static var stages: [MigrationStage] {
        [migrateV1toV2]
    }

    // Lightweight migration (automatic)
    static let migrateV1toV2 = MigrationStage.lightweight(
        fromVersion: SchemaV1.self,
        toVersion: SchemaV2.self
    )

    // Custom migration with progress
    static func customMigration(
        context: ModelContext,
        progress: @escaping (Double) -> Void
    ) async throws {
        let batchSize = 100
        let total = try context.fetchCount(FetchDescriptor<OldModel>())
        var processed = 0

        while processed < total {
            // Process in batches
            var descriptor = FetchDescriptor<OldModel>()
            descriptor.fetchLimit = batchSize
            descriptor.fetchOffset = processed

            let batch = try context.fetch(descriptor)
            for item in batch {
                // Transform
                let newItem = NewModel(from: item)
                context.insert(newItem)
            }

            try context.save()
            processed += batch.count
            progress(Double(processed) / Double(total))
        }
    }
}

3.4. CryptoKit Performance

3.4.1. Encryption Benchmarks

Operacion Target Max Hardware
AES-GCM Encrypt (1KB) 3ms 10ms SE if available
AES-GCM Decrypt (1KB) 2ms 8ms SE if available
Key Generation 5ms 20ms Secure Enclave
Key Derivation (Argon2id) 200ms 500ms CPU (Swift-Argon2)
Keychain Read 5ms 30ms Secure Enclave

DV2-REMEDIACION: Argon2id unificado para ambas plataformas. Decision del Director: 2025-12-09. CryptoKit no soporta Argon2id nativamente, usar Swift-Argon2 library.

// CryptoKit Performance Implementation

class CryptoManager {

    private let symmetricKey: SymmetricKey

    // MARK: - Optimized Encryption

    func encrypt(_ data: Data) throws -> Data {
        // Use Secure Enclave when available
        let sealedBox = try AES.GCM.seal(
            data,
            using: symmetricKey,
            nonce: AES.GCM.Nonce()
        )
        return sealedBox.combined!
    }

    func decrypt(_ data: Data) throws -> Data {
        let sealedBox = try AES.GCM.SealedBox(combined: data)
        return try AES.GCM.open(sealedBox, using: symmetricKey)
    }

    // MARK: - Batch Operations

    func encryptBatch(_ items: [Data]) async throws -> [Data] {
        // Process in parallel for large batches
        try await withThrowingTaskGroup(of: (Int, Data).self) { group in
            for (index, item) in items.enumerated() {
                group.addTask {
                    (index, try self.encrypt(item))
                }
            }

            var results = [(Int, Data)]()
            for try await result in group {
                results.append(result)
            }

            return results.sorted { $0.0 < $1.0 }.map { $0.1 }
        }
    }

    // MARK: - Argon2id Key Derivation (DV2-REMEDIACION)
    // Usar Swift-Argon2 library: https://github.com/nicktrienenern/Swift-Argon2

    func deriveKey(password: String, salt: Data) throws -> SymmetricKey {
        // Argon2id parameters matching 04-seguridad-cliente.md
        let hash = try Argon2.hash(
            password: password,
            salt: salt,
            iterations: 3,          // t = 3
            memory: 64 * 1024,      // m = 64MB
            parallelism: 4,         // p = 4
            length: 32,             // 256-bit key
            variant: .id            // Argon2id
        )
        return SymmetricKey(data: hash)
    }

    // MARK: - Performance Measurement

    func measureEncryptionPerformance() {
        let testData = Data(repeating: 0, count: 1024)  // 1KB

        let start = CFAbsoluteTimeGetCurrent()
        for _ in 0..<1000 {
            _ = try? encrypt(testData)
        }
        let elapsed = CFAbsoluteTimeGetCurrent() - start

        print("1000 encryptions: \(elapsed)s")
        print("Average: \(elapsed / 1000 * 1000)ms")
    }
}

3.5. Network Performance

3.5.1. URLSession Optimization

Metrica Target Max
DNS Lookup <50ms 200ms
TCP Handshake <100ms 300ms
TLS Handshake <150ms 400ms
Time to First Byte <300ms 800ms
Total Request (1KB) <500ms 1s
// URLSession Configuration for Performance

class NetworkManager {

    static let shared = NetworkManager()

    private lazy var session: URLSession = {
        let config = URLSessionConfiguration.default

        // Connection pooling
        config.httpMaximumConnectionsPerHost = 4

        // Timeouts
        config.timeoutIntervalForRequest = 30
        config.timeoutIntervalForResource = 60

        // Caching
        config.urlCache = URLCache(
            memoryCapacity: 20_000_000,   // 20MB
            diskCapacity: 100_000_000,    // 100MB
            directory: nil
        )
        config.requestCachePolicy = .returnCacheDataElseLoad

        // HTTP/2 multiplexing
        config.httpShouldUsePipelining = true

        // Compression
        config.httpAdditionalHeaders = [
            "Accept-Encoding": "gzip, deflate"
        ]

        return URLSession(configuration: config)
    }()

    // MARK: - Request Batching

    func batchRequests<T: Decodable>(_ requests: [URLRequest]) async throws -> [T] {
        try await withThrowingTaskGroup(of: T.self) { group in
            for request in requests {
                group.addTask {
                    let (data, _) = try await self.session.data(for: request)
                    return try JSONDecoder().decode(T.self, from: data)
                }
            }

            var results = [T]()
            for try await result in group {
                results.append(result)
            }
            return results
        }
    }

    // MARK: - Retry with Backoff

    func requestWithRetry<T: Decodable>(
        _ request: URLRequest,
        maxRetries: Int = 3
    ) async throws -> T {
        var lastError: Error?
        var delay: UInt64 = 1_000_000_000  // 1 second

        for attempt in 0..<maxRetries {
            do {
                let (data, _) = try await session.data(for: request)
                return try JSONDecoder().decode(T.self, from: data)
            } catch {
                lastError = error
                if attempt < maxRetries - 1 {
                    try await Task.sleep(nanoseconds: delay)
                    delay *= 2  // Exponential backoff
                }
            }
        }

        throw lastError!
    }
}

3.6. Widget Performance (WidgetKit)

Metrica Target Max
Timeline Generation <100ms 500ms
View Rendering <50ms 200ms
Memory Usage <30MB 50MB
Refresh Frequency 15min min Configurable
// WidgetKit Performance Optimization

struct MedicationWidget: Widget {

    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: "medication",
            provider: MedicationTimelineProvider()
        ) { entry in
            MedicationWidgetView(entry: entry)
        }
        .configurationDisplayName("Next Medication")
        .description("Shows your next scheduled medication")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

struct MedicationTimelineProvider: TimelineProvider {

    // MARK: - Performance Optimizations

    func getTimeline(in context: Context, completion: @escaping (Timeline<MedicationEntry>) -> Void) {
        // Use background context
        Task.detached(priority: .utility) {
            let entries = await generateEntries()

            // Limit entries to reduce memory
            let limitedEntries = Array(entries.prefix(24))  // 24 hours

            let timeline = Timeline(
                entries: limitedEntries,
                policy: .after(Date().addingTimeInterval(900))  // 15 min
            )

            completion(timeline)
        }
    }

    private func generateEntries() async -> [MedicationEntry] {
        // Fetch only what's needed
        let medications = await fetchUpcomingMedications(limit: 10)

        return medications.map { medication in
            MedicationEntry(
                date: medication.nextDoseTime,
                medication: medication.lightweightCopy  // Minimize data
            )
        }
    }
}

// Lightweight data for widget
struct LightweightMedication: Codable {
    let id: UUID
    let name: String
    let dosage: String
    let time: Date
    // No relationships, no heavy data
}

3.7. WatchOS Performance

Metrica Target Max
Phone-Watch Sync <1s 3s
Complication Update <100ms 500ms
App Launch <1s 2s
Memory Usage <20MB 30MB
// WatchOS Performance Optimization

class WatchConnectivityManager: NSObject, WCSessionDelegate {

    // MARK: - Efficient Data Transfer

    func sendMedicationUpdate(_ medication: LightweightMedication) {
        guard WCSession.default.isReachable else {
            // Queue for later
            queueMessage(medication)
            return
        }

        // Use transferUserInfo for guaranteed delivery
        let data = try? JSONEncoder().encode(medication)
        WCSession.default.transferUserInfo([
            "medication": data as Any,
            "timestamp": Date()
        ])
    }

    // MARK: - Complication Updates

    func updateComplication() {
        let server = CLKComplicationServer.sharedInstance()
        guard let complications = server.activeComplications else { return }

        // Batch update
        for complication in complications {
            server.reloadTimeline(for: complication)
        }
    }

    // MARK: - Data Minimization

    struct WatchMedicationData: Codable {
        // Only essential fields for watch
        let name: String
        let dosage: String
        let nextDoseTime: Date
        let color: String  // For quick identification
        // Total: ~200 bytes vs 2KB full medication
    }
}

4. Android Performance

4.1. App Lifecycle

4.1.1. Launch Performance (DV2-P3)

PERF-BAJO-002: Android benchmarks no estan al nivel de iOS Remediacion: Agregar targets especificos para Android con justificacion

Metrica iOS Target Android Target Variance Medicion
Cold Start <2s <2.5s +25% TTID + TTFD
Warm Start <500ms <600ms +20% Resume time
Hot Start <100ms <200ms +100% onResume
First Frame <1s <1.2s +20% First draw

Justificacion de Diferencias Android/iOS:

Android_Performance_Justification:

  cold_start_overhead:
    factor: +25% vs iOS
    reasons:
      - "Dex/ART compilation overhead (vs iOS pre-compiled)"
      - "Fragmented ecosystem: must target older devices"
      - "GC initialization + memory management overhead"
      - "Broader device spectrum (low-end devices comunes)"
      - "SQLCipher overhead mayor en Android (JNI boundary)"
    mitigation:
      - "Baseline Profiles (R8 optimization)"
      - "Startup Profiler integration"
      - "Lazy initialization mas agresiva"

  warm_start_overhead:
    factor: +20% vs iOS
    reasons:
      - "Process recreation mas frecuente (memoria limitada)"
      - "Bundle restore overhead"
      - "GC puede triggerearse durante startup"
    mitigation:
      - "onSaveInstanceState optimization"
      - "ViewModel retention"

  hot_start_overhead:
    factor: +100% vs iOS
    reasons:
      - "Activity lifecycle mas complejo que SwiftUI"
      - "onResume callbacks pueden ser pesados"
      - "View inflation si no cached"
    mitigation:
      - "View binding caching"
      - "Deferred onResume work"

  acceptance_criteria:
    - "Targets Android reflejan realidad del ecosistema"
    - "Low-end devices (Samsung A13) deben cumplir max thresholds"
    - "High-end devices (Pixel 8) deben superar iOS targets"
    - "Optimization continua reduce gap iOS/Android"

Android-Specific Optimization Targets:

Optimization iOS Android Impact
Baseline Profile N/A Required -15% startup
R8 Full Mode N/A Required -20% app size
ProGuard Rules N/A Required -10% startup
Startup Tasks Phase 1-3 Phase 1-3 + WorkManager Parity
Memory Pressure Rare Common +defensive GC

4.1.2. Launch Performance Benchmarks (Device-Specific)

Android_Device_Benchmarks:

  low_end_samsung_a13:
    cold_start:
      target: 3.5s
      max: 4.5s
      justificacion: "2GB RAM, MediaTek Helio G80"
    warm_start:
      target: 800ms
      max: 1.2s
    memory_baseline:
      target: 100MB
      max: 150MB

  mid_range_pixel_6:
    cold_start:
      target: 2.5s
      max: 3.5s
      justificacion: "8GB RAM, Google Tensor"
    warm_start:
      target: 600ms
      max: 900ms
    memory_baseline:
      target: 80MB
      max: 120MB

  high_end_pixel_8_pro:
    cold_start:
      target: 1.8s  # Beat iOS target
      max: 2.5s
      justificacion: "12GB RAM, Tensor G3"
    warm_start:
      target: 400ms  # Beat iOS target
      max: 600ms
    memory_baseline:
      target: 80MB
      max: 100MB
// Android Launch Optimization

class MedTimeApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        // Phase 1: Critical (blocking, <500ms)
        initializeCrashReporting()
        initializeLogging()

        // Phase 2: Background (non-blocking)
        lifecycleScope.launch(Dispatchers.Default) {
            initializeDatabase()
            initializeCryptoManager()
        }

        // Phase 3: Deferred (after first frame)
        MainScope().launch {
            delay(1000)  // Wait for UI
            initializeAnalytics()
            initializePushNotifications()
        }
    }
}

// Measure with Android Vitals
object LaunchMetrics {

    fun reportLaunchTime() {
        val launchTime = System.currentTimeMillis() - processStartTime

        if (launchTime > 2500) {
            // Log slow start
            FirebasePerformance.getInstance()
                .newTrace("slow_cold_start")
                .putMetric("duration_ms", launchTime)
                .stop()
        }
    }

    // Use App Startup Library for initialization
    class DatabaseInitializer : Initializer<Database> {
        override fun create(context: Context): Database {
            return Room.databaseBuilder(
                context,
                AppDatabase::class.java,
                "medtime.db"
            ).build()
        }

        override fun dependencies(): List<Class<out Initializer<*>>> {
            return listOf(CryptoInitializer::class.java)
        }
    }
}

4.1.3. Background Performance

Operacion Max Duration Frequency Battery Budget
WorkManager Sync 10min 15min (flexible) 1% max
Foreground Service Unlimited As needed 2%/hour
Doze-compatible 30s window Maintenance 0.5%
Expedited Work 1min Critical only 0.3%
// WorkManager Configuration for Performance

class SyncWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        return withContext(Dispatchers.IO) {
            try {
                // Set progress for long operations
                setProgress(workDataOf("status" to "syncing"))

                val syncResult = syncManager.performSync()

                if (syncResult.success) {
                    Result.success()
                } else {
                    Result.retry()
                }
            } catch (e: Exception) {
                if (runAttemptCount < 3) {
                    Result.retry()
                } else {
                    Result.failure()
                }
            }
        }
    }

    companion object {
        fun schedule(context: Context) {
            val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .setRequiresBatteryNotLow(true)
                .build()

            val request = PeriodicWorkRequestBuilder<SyncWorker>(
                15, TimeUnit.MINUTES,
                5, TimeUnit.MINUTES  // Flex interval
            )
                .setConstraints(constraints)
                .setBackoffCriteria(
                    BackoffPolicy.EXPONENTIAL,
                    1, TimeUnit.MINUTES
                )
                .build()

            WorkManager.getInstance(context)
                .enqueueUniquePeriodicWork(
                    "sync",
                    ExistingPeriodicWorkPolicy.KEEP,
                    request
                )
        }
    }
}

4.2. Jetpack Compose Performance

4.2.1. Recomposition

Metrica Target Warning Critical
Recomposition Count Minimal +20% +50%
Recomposition Time <8ms 8-16ms >16ms
Skip Rate >90% 70-90% <70%
Frame Time <16ms 16-32ms >32ms
// Compose Performance Best Practices

@Composable
fun MedicationListScreen(
    viewModel: MedicationListViewModel = hiltViewModel()
) {
    // Collect as state (not collectAsStateWithLifecycle for lists)
    val medications by viewModel.medications.collectAsState()

    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp)
    ) {
        items(
            items = medications,
            key = { it.id }  // Stable keys prevent recomposition
        ) { medication ->
            MedicationItem(
                medication = medication,
                onClick = { viewModel.onMedicationClick(it) }
            )
        }
    }
}

// Stable data class
@Immutable
data class MedicationUiModel(
    val id: String,
    val name: String,
    val dosage: String,
    val nextDoseTime: String,
    val isOverdue: Boolean
)

// Skippable composable
@Composable
private fun MedicationItem(
    medication: MedicationUiModel,
    onClick: (String) -> Unit
) {
    // Use remember for lambdas
    val clickHandler = remember(medication.id) {
        { onClick(medication.id) }
    }

    Card(
        modifier = Modifier
            .fillMaxWidth()
            .clickable(onClick = clickHandler)
    ) {
        // Content
    }
}

// Debug recomposition
@Composable
fun RecompositionTracker(name: String) {
    val recompositions = remember { mutableIntStateOf(0) }
    SideEffect {
        recompositions.intValue++
        Log.d("Recomposition", "$name: ${recompositions.intValue}")
    }
}

4.2.2. LazyColumn Optimization

Metrica 50 items 500 items 5000 items
Initial Render 50ms 80ms 100ms
Scroll FPS 60 60 55-60
Memory 20MB 40MB 60MB
// LazyColumn Optimization

@Composable
fun OptimizedMedicationList(
    medications: List<MedicationUiModel>,
    onItemClick: (String) -> Unit
) {
    // Use rememberLazyListState for scroll position
    val listState = rememberLazyListState()

    // Prefetch visible items
    val visibleItems = remember(listState) {
        derivedStateOf {
            listState.layoutInfo.visibleItemsInfo.map { it.index }
        }
    }

    LazyColumn(
        state = listState,
        modifier = Modifier.fillMaxSize()
    ) {
        items(
            items = medications,
            key = { it.id },
            contentType = { "medication" }  // Same type = better recycling
        ) { medication ->
            // Use derivedStateOf for computed properties
            val isVisible by remember(visibleItems) {
                derivedStateOf { visibleItems.value.contains(medications.indexOf(medication)) }
            }

            MedicationRow(
                medication = medication,
                isVisible = isVisible,
                onClick = onItemClick
            )
        }
    }
}

// Image loading with Coil
@Composable
fun MedicationImage(
    url: String?,
    modifier: Modifier = Modifier
) {
    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data(url)
            .crossfade(true)
            .size(100, 100)  // Request specific size
            .memoryCachePolicy(CachePolicy.ENABLED)
            .diskCachePolicy(CachePolicy.ENABLED)
            .build(),
        contentDescription = null,
        modifier = modifier,
        placeholder = painterResource(R.drawable.placeholder)
    )
}

4.3. Room Performance

4.3.1. Query Performance

Operacion Target Max Items
Single Fetch 5ms 20ms 1
List Fetch 25ms 60ms 50
Batch Fetch 60ms 180ms 500
Complex Query 40ms 120ms With joins
Transaction 10ms/item 30ms/item Batch
// Room Query Optimization

@Dao
interface MedicationDao {

    // Indexed query
    @Query("SELECT * FROM medications WHERE id = :id")
    suspend fun getById(id: String): MedicationEntity?

    // Paginated query with Flow
    @Query("SELECT * FROM medications WHERE is_active = 1 ORDER BY name LIMIT :limit OFFSET :offset")
    fun getActivePaginated(limit: Int, offset: Int): Flow<List<MedicationEntity>>

    // Optimized for list display (only needed fields)
    @Query("""
        SELECT id, name, dosage, next_dose_time
        FROM medications
        WHERE is_active = 1
        ORDER BY next_dose_time
    """)
    fun getUpcomingDoses(): Flow<List<MedicationSummary>>

    // Batch insert with transaction
    @Transaction
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAll(medications: List<MedicationEntity>)

    // Batch update
    @Query("UPDATE medications SET sync_status = :status WHERE id IN (:ids)")
    suspend fun updateSyncStatus(ids: List<String>, status: String)
}

// Projection for list view (lighter)
data class MedicationSummary(
    val id: String,
    val name: String,
    val dosage: String,
    @ColumnInfo(name = "next_dose_time")
    val nextDoseTime: Long?
)

// Database with SQLCipher
@Database(
    entities = [MedicationEntity::class, ScheduleEntity::class],
    version = 1,
    exportSchema = true
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {

    companion object {
        fun create(context: Context, passphrase: ByteArray): AppDatabase {
            val factory = SupportFactory(passphrase)

            return Room.databaseBuilder(
                context,
                AppDatabase::class.java,
                "medtime_encrypted.db"
            )
                .openHelperFactory(factory)
                .setJournalMode(JournalMode.WRITE_AHEAD_LOGGING)  // Better performance
                .build()
        }
    }
}

4.4. Crypto Performance (argon2kt)

Operacion Target Max Notes
AES-GCM Encrypt (1KB) 5ms 20ms Hardware if available
AES-GCM Decrypt (1KB) 4ms 15ms Hardware if available
Argon2id 200ms 500ms Memory: 64MB
Keystore Read 15ms 50ms StrongBox if available
// Crypto Performance Implementation

class CryptoManager @Inject constructor(
    private val keyManager: KeyManager
) {

    private val cipher = Cipher.getInstance("AES/GCM/NoPadding")

    // MARK: - Encryption

    suspend fun encrypt(data: ByteArray): ByteArray = withContext(Dispatchers.Default) {
        val key = keyManager.getMasterKey()
        val iv = generateIV()

        cipher.init(Cipher.ENCRYPT_MODE, key, GCMParameterSpec(128, iv))
        val encrypted = cipher.doFinal(data)

        // Return IV + encrypted
        iv + encrypted
    }

    suspend fun decrypt(data: ByteArray): ByteArray = withContext(Dispatchers.Default) {
        val iv = data.sliceArray(0 until 12)
        val encrypted = data.sliceArray(12 until data.size)

        val key = keyManager.getMasterKey()
        cipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(128, iv))

        cipher.doFinal(encrypted)
    }

    // MARK: - Key Derivation

    suspend fun deriveKey(password: String, salt: ByteArray): ByteArray {
        return withContext(Dispatchers.Default) {
            // Argon2id parameters matching 04-seguridad-cliente.md
            val argon2 = Argon2Kt()
            val result = argon2.hash(
                mode = Argon2Mode.ARGON2_ID,
                password = password.toByteArray(),
                salt = salt,
                tCostInIterations = 3,
                mCostInKibibyte = 64 * 1024,  // 64 MiB
                parallelism = 4,
                hashLengthInBytes = 32
            )
            result.rawHashAsByteArray()
        }
    }

    // MARK: - Performance Measurement

    fun measureCryptoPerformance(): CryptoMetrics {
        val testData = ByteArray(1024) { it.toByte() }
        val iterations = 1000

        val encryptStart = System.nanoTime()
        repeat(iterations) {
            runBlocking { encrypt(testData) }
        }
        val encryptTime = (System.nanoTime() - encryptStart) / iterations / 1_000_000.0

        return CryptoMetrics(
            encryptMs = encryptTime,
            decryptMs = /* similar measurement */,
            keyDerivationMs = /* measure Argon2id */
        )
    }
}

4.5. Network Performance (Retrofit/OkHttp)

Metrica Target Max
Connection Pool 5 connections 10 max
Keep-Alive 5 minutes -
Timeout (connect) 10s -
Timeout (read) 30s -
Retry Count 3 5 max
// OkHttp Configuration

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            // Connection pooling
            .connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES))

            // Timeouts
            .connectTimeout(10, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)

            // Interceptors
            .addInterceptor(AuthInterceptor())
            .addInterceptor(RetryInterceptor(maxRetries = 3))
            .addInterceptor(GzipInterceptor())

            // Cache
            .cache(Cache(
                directory = File(context.cacheDir, "http_cache"),
                maxSize = 50L * 1024L * 1024L  // 50 MB
            ))

            // HTTP/2
            .protocols(listOf(Protocol.HTTP_2, Protocol.HTTP_1_1))

            .build()
    }

    // Retry with exponential backoff
    class RetryInterceptor(private val maxRetries: Int) : Interceptor {
        override fun intercept(chain: Interceptor.Chain): Response {
            var lastException: IOException? = null
            var delay = 1000L

            repeat(maxRetries) { attempt ->
                try {
                    return chain.proceed(chain.request())
                } catch (e: IOException) {
                    lastException = e
                    if (attempt < maxRetries - 1) {
                        Thread.sleep(delay)
                        delay *= 2
                    }
                }
            }

            throw lastException!!
        }
    }
}

4.6. Widget Performance (Glance)

Metrica Target Max
Widget Update <200ms 500ms
Memory Usage <30MB 50MB
Compose Render <100ms 300ms
// Glance Widget Optimization

class MedicationWidget : GlanceAppWidget() {

    override val stateDefinition = GlanceStateDefinition<MedicationWidgetState>()

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            val state = currentState<MedicationWidgetState>()
            MedicationWidgetContent(state)
        }
    }
}

@Composable
private fun MedicationWidgetContent(state: MedicationWidgetState) {
    // Use Glance composables (not regular Compose)
    Column(
        modifier = GlanceModifier
            .fillMaxSize()
            .background(ColorProvider(Color.White))
            .padding(12.dp)
    ) {
        Text(
            text = state.medicationName,
            style = TextStyle(fontWeight = FontWeight.Bold)
        )
        Text(
            text = "Next: ${state.nextDoseTime}",
            style = TextStyle(fontSize = 14.sp)
        )
        Button(
            text = "Take",
            onClick = actionRunCallback<TakeDoseAction>()
        )
    }
}

// Efficient state updates
class MedicationWidgetReceiver : GlanceAppWidgetReceiver() {

    override val glanceAppWidget = MedicationWidget()

    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)

        // Batch updates
        if (intent.action == "UPDATE_WIDGET") {
            MainScope().launch {
                val state = fetchWidgetState()
                updateAppWidgetState(context, glanceId, state)
                glanceAppWidget.update(context, glanceId)
            }
        }
    }
}

4.7. Wear OS Performance

Metrica Target Max
Data Sync <500ms 2s
Message Delivery <200ms 1s
Complication Update <100ms 500ms
Memory <25MB 40MB
// Wear OS Performance

class WearDataManager(
    private val dataClient: DataClient,
    private val messageClient: MessageClient
) {

    // Efficient data sync
    suspend fun syncMedications(medications: List<LightweightMedication>) {
        val dataMap = PutDataMapRequest.create("/medications").run {
            dataMap.putDataMapArrayList(
                "medications",
                medications.map { it.toDataMap() }.toArrayList()
            )
            dataMap.putLong("timestamp", System.currentTimeMillis())
            asPutDataRequest().setUrgent()
        }

        dataClient.putDataItem(dataMap).await()
    }

    // Lightweight medication for watch
    data class LightweightMedication(
        val id: String,
        val name: String,
        val dosage: String,
        val nextDoseTime: Long,
        val color: Int
    ) {
        fun toDataMap(): DataMap = DataMap().apply {
            putString("id", id)
            putString("name", name)
            putString("dosage", dosage)
            putLong("nextDoseTime", nextDoseTime)
            putInt("color", color)
        }
    }

    // Complication update
    fun updateComplication(context: Context) {
        val request = ComplicationDataSourceUpdateRequester.create(
            context,
            ComponentName(context, MedicationComplicationService::class.java)
        )
        request.requestUpdateAll()
    }
}

5. Cross-Platform Metrics

5.1. Feature Parity Matrix

Feature iOS Target Android Target Variance Allowed
Cold Start 2s 2.5s +25% Android
List Scroll 60 FPS 60 FPS 0%
Encryption (1KB) 3ms 5ms +67% Android
Sync Push 500ms 500ms 0%
Memory (baseline) 50MB 80MB +60% Android
Battery (foreground) 5%/hr 5%/hr 0%

5.2. Comparative Benchmarks

Cross-Platform Benchmarks:

  app_launch:
    measurement: "Cold start to interactive"
    ios:
      target: 2000ms
      low_end_device: iPhone SE 2nd
      high_end_device: iPhone 15 Pro
    android:
      target: 2500ms
      low_end_device: Samsung A13
      high_end_device: Pixel 8 Pro

  list_performance:
    measurement: "Scroll FPS with 500 items"
    ios:
      target: 60fps
      technology: SwiftUI LazyVStack
    android:
      target: 60fps
      technology: Compose LazyColumn

  encryption_throughput:
    measurement: "AES-GCM operations per second"
    ios:
      target: 333/s  # 3ms per operation
      technology: CryptoKit + Secure Enclave
    android:
      target: 200/s  # 5ms per operation
      technology: Cipher + StrongBox

  sync_performance:
    measurement: "100 items incremental sync"
    ios:
      target: 2s
      technology: URLSession + async/await
    android:
      target: 2s
      technology: Retrofit + Coroutines

6. Testing Methodology

6.1. Device Matrix

Category iOS Devices Android Devices
Low-end iPhone SE (2nd), iPhone 8 Samsung A13, Moto G Power
Mid-range iPhone 12, iPhone 13 Pixel 6, Samsung A54
High-end iPhone 15 Pro, iPhone 15 Pro Max Pixel 8 Pro, Samsung S24 Ultra

6.2. Screen Transition Performance (DV2-P3)

PERF-BAJO-001: Screen transitions > 300ms para algunas pantallas Remediacion: Documentar excepciones aceptables o targets especificos

6.2.1. Targets por Tipo de Transicion

Tipo de Transicion Target Max Excepciones Aceptables
Simple (navegacion) <150ms 300ms N/A
Lista → Detalle <200ms 300ms N/A
Modal overlay <100ms 200ms N/A
Tab switch <100ms 150ms N/A
Deep link navigation <300ms 500ms Incluye data loading
Initial app load → Home <500ms 1s Incluye crypto setup

6.2.2. Excepciones Documentadas (>300ms)

Excepciones_Aceptables:

  initial_home_load:
    target: 500ms
    max: 1000ms
    justificacion: >
      Primera carga incluye:
      - Key retrieval (Keychain/Keystore)
      - Database connection
      - Decryption inicial de datos
      - UI rendering
    mitigation:
      - Mostrar skeleton/loading state
      - Progressive reveal de UI
      - Cache en memoria para siguientes accesos

  deep_link_with_data:
    target: 300ms
    max: 500ms
    justificacion: >
      Deep link requiere:
      - Route parsing
      - Data fetch desde DB local
      - Potential decryption
    mitigation:
      - Optimistic UI
      - Placeholder mientras carga

  search_results_load:
    target: 300ms
    max: 500ms
    justificacion: >
      Busqueda con:
      - Query de DB compleja
      - Multiple decryptions
      - Rendering de lista grande
    mitigation:
      - Debounce (300ms)
      - Paginated results
      - Virtual scrolling

  offline_to_online_sync:
    target: 500ms
    max: 1000ms
    justificacion: >
      Transicion durante sync incluye:
      - Network reconnection
      - Pending operations flush
      - UI state reconciliation
    mitigation:
      - Non-blocking sync
      - Progress indicator
      - Allow navigation durante sync

6.2.3. Medicion de Transitions

// iOS: Measure transition time
class NavigationPerformanceMonitor {

    static func measureTransition(from: String, to: String) {
        let start = CFAbsoluteTimeGetCurrent()

        // On destination view appear
        let elapsed = (CFAbsoluteTimeGetCurrent() - start) * 1000

        if elapsed > 300 {
            print("SLOW TRANSITION: \(from)\(to): \(elapsed)ms")
        }
    }
}
// Android: Measure transition time
class NavigationPerformanceMonitor {

    companion object {
        fun measureTransition(from: String, to: String) {
            val startTime = SystemClock.elapsedRealtime()

            // On destination composable first frame
            val elapsed = SystemClock.elapsedRealtime() - startTime

            if (elapsed > 300) {
                Log.w("PERF", "SLOW TRANSITION: $from$to: ${elapsed}ms")
            }
        }
    }
}

6.3. Network Conditions

Profile Latency Bandwidth Loss
5G 20ms 100 Mbps 0%
4G Good 50ms 20 Mbps 0.1%
4G Average 100ms 5 Mbps 0.5%
3G 300ms 1 Mbps 2%
Edge 500ms 200 Kbps 5%
Offline N/A 0 100%

6.4. Test Scenarios

Performance Test Scenarios:

  cold_start:
    steps:
      1. Force kill app
      2. Clear memory pressure
      3. Launch app
      4. Measure to first interactive frame
    repeat: 10
    metrics:
      - time_to_first_frame
      - time_to_interactive
      - memory_at_launch

  list_scroll:
    steps:
      1. Load list with 500 items
      2. Scroll to bottom at constant velocity
      3. Scroll back to top
    repeat: 5
    metrics:
      - average_fps
      - dropped_frames
      - jank_percentage

  sync_stress:
    steps:
      1. Queue 100 pending operations
      2. Trigger sync
      3. Measure completion
    conditions:
      - 4G network
      - battery > 50%
    metrics:
      - total_time
      - operations_per_second
      - retry_count

  encryption_benchmark:
    steps:
      1. Generate 1000 random 1KB blobs
      2. Encrypt all blobs
      3. Decrypt all blobs
    repeat: 3
    metrics:
      - encrypt_time_avg
      - decrypt_time_avg
      - memory_peak

  background_battery:
    steps:
      1. Enable all background tasks
      2. Run for 4 hours (simulated)
      3. Measure battery drain
    conditions:
      - screen off
      - WiFi connected
    metrics:
      - battery_drain_percent
      - wake_lock_duration
      - network_operations

7. Optimization Strategies

7.1. Quick Wins

Optimization Impact Effort Priority
Lazy initialization High Low P0
Image caching High Low P0
List virtualization High Medium P0
Query optimization Medium Medium P1
Memory cleanup Medium Low P1
Background batching Medium Medium P2

7.2. Advanced Optimizations

Advanced Optimizations:

  startup:
    - Baseline Profile (Android)
    - App thinning (iOS)
    - Deferred initialization
    - Precomputed layouts

  rendering:
    - Compose stability annotations (Android)
    - View recycling optimization
    - Offscreen composition
    - Hardware acceleration

  memory:
    - Image downsampling
    - LRU cache tuning
    - Weak references for caches
    - Aggressive GC hints

  network:
    - HTTP/2 multiplexing
    - Request batching
    - Predictive prefetching
    - Compression optimization

  database:
    - WAL mode
    - Index optimization
    - Query plan analysis
    - Connection pooling

7.3. Monitoring in Production

Production Monitoring:

  ios:
    tools:
      - MetricKit (Apple)
      - Firebase Performance
      - Sentry
    metrics:
      - launch_time
      - hang_rate
      - memory_peak
      - battery_drain

  android:
    tools:
      - Android Vitals
      - Firebase Performance
      - Sentry
    metrics:
      - startup_time
      - slow_rendering
      - anr_rate
      - excessive_wakeups

  alerts:
    p95_launch_time:
      ios: "> 3s"
      android: "> 4s"
    crash_free_rate:
      both: "< 99.5%"
    anr_rate:
      android: "> 0.5%"

8. Apendice

8.1. Profiling Commands

# iOS
# Launch with Instruments
xcrun xctrace record --template 'Time Profiler' --launch com.medtime.app

# Android
# Launch with profiler
adb shell am start -W -n com.medtime.app/.MainActivity

# Capture systrace
python systrace.py -o trace.html sched freq idle am wm gfx view

# Memory dump
adb shell dumpsys meminfo com.medtime.app

8.2. Benchmark Results Template

Metric Target Actual Status Device
Cold Start 2s 1.8s PASS iPhone 12
Cold Start 2.5s 2.3s PASS Pixel 6
List Scroll 60 FPS 58 FPS WARN iPhone SE
Encryption 5ms 4ms PASS Both

Documento generado por SpecQueen Technical Division - IT-14 "Smooth is fast. Fast is smooth."