@amorydev/antigravity-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +487 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +76 -0
- package/.agent/ARCHITECTURE.md +225 -0
- package/.agent/agents/backend-specialist.md +263 -0
- package/.agent/agents/database-architect.md +226 -0
- package/.agent/agents/debugger.md +225 -0
- package/.agent/agents/devops-engineer.md +242 -0
- package/.agent/agents/documentation-writer.md +104 -0
- package/.agent/agents/explorer-agent.md +73 -0
- package/.agent/agents/frontend-specialist.md +527 -0
- package/.agent/agents/game-developer.md +162 -0
- package/.agent/agents/mobile-developer.md +1126 -0
- package/.agent/agents/orchestrator.md +400 -0
- package/.agent/agents/penetration-tester.md +188 -0
- package/.agent/agents/performance-optimizer.md +187 -0
- package/.agent/agents/project-planner.md +403 -0
- package/.agent/agents/security-auditor.md +170 -0
- package/.agent/agents/seo-specialist.md +111 -0
- package/.agent/agents/test-engineer.md +158 -0
- package/.agent/rules/GEMINI.md +252 -0
- package/.agent/skills/api-patterns/SKILL.md +81 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +82 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +100 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +106 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +101 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +93 -0
- package/.agent/skills/architecture/SKILL.md +55 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/bash-linux/SKILL.md +199 -0
- package/.agent/skills/behavioral-modes/SKILL.md +242 -0
- package/.agent/skills/brainstorming/SKILL.md +163 -0
- package/.agent/skills/brainstorming/dynamic-questioning.md +350 -0
- package/.agent/skills/clean-code/SKILL.md +201 -0
- package/.agent/skills/code-review-checklist/SKILL.md +109 -0
- package/.agent/skills/database-design/SKILL.md +52 -0
- package/.agent/skills/database-design/database-selection.md +43 -0
- package/.agent/skills/database-design/indexing.md +39 -0
- package/.agent/skills/database-design/migrations.md +48 -0
- package/.agent/skills/database-design/optimization.md +36 -0
- package/.agent/skills/database-design/orm-selection.md +30 -0
- package/.agent/skills/database-design/schema-design.md +56 -0
- package/.agent/skills/database-design/scripts/schema_validator.py +172 -0
- package/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/docker-expert/SKILL.md +409 -0
- package/.agent/skills/documentation-templates/SKILL.md +194 -0
- package/.agent/skills/frontend-design/SKILL.md +396 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +311 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +541 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/game-development/2d-games/SKILL.md +119 -0
- package/.agent/skills/game-development/3d-games/SKILL.md +135 -0
- package/.agent/skills/game-development/SKILL.md +167 -0
- package/.agent/skills/game-development/game-art/SKILL.md +185 -0
- package/.agent/skills/game-development/game-audio/SKILL.md +190 -0
- package/.agent/skills/game-development/game-design/SKILL.md +129 -0
- package/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
- package/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
- package/.agent/skills/game-development/pc-games/SKILL.md +144 -0
- package/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
- package/.agent/skills/game-development/web-games/SKILL.md +150 -0
- package/.agent/skills/geo-fundamentals/SKILL.md +156 -0
- package/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/lint-and-validate/SKILL.md +45 -0
- package/.agent/skills/lint-and-validate/scripts/lint_runner.py +172 -0
- package/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/mobile-design/SKILL.md +937 -0
- package/.agent/skills/mobile-design/decision-trees.md +516 -0
- package/.agent/skills/mobile-design/mobile-backend.md +491 -0
- package/.agent/skills/mobile-design/mobile-color-system.md +420 -0
- package/.agent/skills/mobile-design/mobile-debugging.md +122 -0
- package/.agent/skills/mobile-design/mobile-design-thinking.md +598 -0
- package/.agent/skills/mobile-design/mobile-navigation.md +458 -0
- package/.agent/skills/mobile-design/mobile-performance.md +1050 -0
- package/.agent/skills/mobile-design/mobile-testing.md +356 -0
- package/.agent/skills/mobile-design/mobile-typography.md +433 -0
- package/.agent/skills/mobile-design/platform-android.md +666 -0
- package/.agent/skills/mobile-design/platform-ios.md +561 -0
- package/.agent/skills/mobile-design/platform-kmp.md +770 -0
- package/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/.agent/skills/mobile-design/touch-psychology.md +537 -0
- package/.agent/skills/nestjs-expert/SKILL.md +552 -0
- package/.agent/skills/nextjs-best-practices/SKILL.md +203 -0
- package/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
- package/.agent/skills/parallel-agents/SKILL.md +175 -0
- package/.agent/skills/performance-profiling/SKILL.md +143 -0
- package/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/.agent/skills/plan-writing/SKILL.md +152 -0
- package/.agent/skills/powershell-windows/SKILL.md +167 -0
- package/.agent/skills/prisma-expert/SKILL.md +355 -0
- package/.agent/skills/python-patterns/SKILL.md +441 -0
- package/.agent/skills/react-patterns/SKILL.md +198 -0
- package/.agent/skills/red-team-tactics/SKILL.md +199 -0
- package/.agent/skills/seo-fundamentals/SKILL.md +129 -0
- package/.agent/skills/seo-fundamentals/scripts/seo_checker.py +219 -0
- package/.agent/skills/server-management/SKILL.md +161 -0
- package/.agent/skills/systematic-debugging/SKILL.md +109 -0
- package/.agent/skills/tailwind-patterns/SKILL.md +269 -0
- package/.agent/skills/tdd-workflow/SKILL.md +149 -0
- package/.agent/skills/testing-patterns/SKILL.md +178 -0
- package/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/.agent/skills/typescript-expert/SKILL.md +429 -0
- package/.agent/skills/typescript-expert/references/tsconfig-strict.json +92 -0
- package/.agent/skills/typescript-expert/references/typescript-cheatsheet.md +383 -0
- package/.agent/skills/typescript-expert/references/utility-types.ts +335 -0
- package/.agent/skills/typescript-expert/scripts/ts_diagnostic.py +203 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +351 -0
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/scripts/core.py +257 -0
- package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +487 -0
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +76 -0
- package/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
- package/.agent/skills/vulnerability-scanner/checklists.md +121 -0
- package/.agent/skills/vulnerability-scanner/scripts/security_scan.py +458 -0
- package/.agent/skills/webapp-testing/SKILL.md +187 -0
- package/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +237 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +80 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +231 -0
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/bin/cli.js +81 -0
- package/package.json +29 -0
|
@@ -0,0 +1,1050 @@
|
|
|
1
|
+
# Mobile Performance Reference
|
|
2
|
+
|
|
3
|
+
> Deep dive into React Native, Flutter, Jetpack Compose and Kotlin Multiplatform performance optimization, 60fps animations, memory management, and battery considerations.
|
|
4
|
+
> **This file covers the #1 area where AI-generated code FAILS.**
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. The Mobile Performance Mindset
|
|
9
|
+
|
|
10
|
+
### Why Mobile Performance is Different
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
DESKTOP: MOBILE:
|
|
14
|
+
├── Unlimited power ├── Battery matters
|
|
15
|
+
├── Abundant RAM ├── RAM is shared, limited
|
|
16
|
+
├── Stable network ├── Network is unreliable
|
|
17
|
+
├── CPU always available ├── CPU throttles when hot
|
|
18
|
+
└── User expects fast anyway └── User expects INSTANT
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Performance Budget Concept
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Every frame must complete in:
|
|
25
|
+
├── 60fps → 16.67ms per frame
|
|
26
|
+
├── 120fps (ProMotion) → 8.33ms per frame
|
|
27
|
+
|
|
28
|
+
If your code takes longer:
|
|
29
|
+
├── Frame drops → Janky scroll/animation
|
|
30
|
+
├── User perceives as "slow" or "broken"
|
|
31
|
+
└── They WILL uninstall your app
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. React Native Performance
|
|
37
|
+
|
|
38
|
+
### 🚫 The #1 AI Mistake: ScrollView for Lists
|
|
39
|
+
```javascript
|
|
40
|
+
// ❌ NEVER DO THIS - AI's favorite mistake
|
|
41
|
+
<ScrollView>
|
|
42
|
+
{items.map(item => (
|
|
43
|
+
<ItemComponent key={item.id} item={item} />
|
|
44
|
+
))}
|
|
45
|
+
</ScrollView>
|
|
46
|
+
|
|
47
|
+
// Why it's catastrophic:
|
|
48
|
+
// ├── Renders ALL items immediately (1000 items = 1000 renders)
|
|
49
|
+
// ├── Memory explodes
|
|
50
|
+
// ├── Initial render takes seconds
|
|
51
|
+
// └── Scroll becomes janky
|
|
52
|
+
|
|
53
|
+
// ✅ ALWAYS USE FlatList
|
|
54
|
+
<FlatList
|
|
55
|
+
data={items}
|
|
56
|
+
renderItem={renderItem}
|
|
57
|
+
keyExtractor={item => item.id}
|
|
58
|
+
/>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### FlatList Optimization Checklist
|
|
62
|
+
```javascript
|
|
63
|
+
// ✅ CORRECT: All optimizations applied
|
|
64
|
+
|
|
65
|
+
// 1. Memoize the item component
|
|
66
|
+
const ListItem = React.memo(({ item }: { item: Item }) => {
|
|
67
|
+
return (
|
|
68
|
+
<Pressable style={styles.item}>
|
|
69
|
+
<Text>{item.title}</Text>
|
|
70
|
+
</Pressable>
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// 2. Memoize renderItem with useCallback
|
|
75
|
+
const renderItem = useCallback(
|
|
76
|
+
({ item }: { item: Item }) => <ListItem item={item} />,
|
|
77
|
+
[] // Empty deps = never recreated
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// 3. Stable keyExtractor (NEVER use index!)
|
|
81
|
+
const keyExtractor = useCallback((item: Item) => item.id, []);
|
|
82
|
+
|
|
83
|
+
// 4. Provide getItemLayout for fixed-height items
|
|
84
|
+
const getItemLayout = useCallback(
|
|
85
|
+
(data: Item[] | null, index: number) => ({
|
|
86
|
+
length: ITEM_HEIGHT, // Fixed height
|
|
87
|
+
offset: ITEM_HEIGHT * index,
|
|
88
|
+
index,
|
|
89
|
+
}),
|
|
90
|
+
[]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// 5. Apply to FlatList
|
|
94
|
+
<FlatList
|
|
95
|
+
data={items}
|
|
96
|
+
renderItem={renderItem}
|
|
97
|
+
keyExtractor={keyExtractor}
|
|
98
|
+
getItemLayout={getItemLayout}
|
|
99
|
+
// Performance props
|
|
100
|
+
removeClippedSubviews={true} // Android: detach off-screen
|
|
101
|
+
maxToRenderPerBatch={10} // Items per batch
|
|
102
|
+
windowSize={5} // Render window (5 = 2 screens each side)
|
|
103
|
+
initialNumToRender={10} // Initial items
|
|
104
|
+
updateCellsBatchingPeriod={50} // Batching delay
|
|
105
|
+
/>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Why Each Optimization Matters
|
|
109
|
+
|
|
110
|
+
| Optimization | What It Prevents | Impact |
|
|
111
|
+
|--------------|------------------|--------|
|
|
112
|
+
| `React.memo` | Re-render on parent change | 🔴 Critical |
|
|
113
|
+
| `useCallback renderItem` | New function every render | 🔴 Critical |
|
|
114
|
+
| Stable `keyExtractor` | Wrong item recycling | 🔴 Critical |
|
|
115
|
+
| `getItemLayout` | Async layout calculation | 🟡 High |
|
|
116
|
+
| `removeClippedSubviews` | Memory from off-screen | 🟡 High |
|
|
117
|
+
| `maxToRenderPerBatch` | Blocking main thread | 🟢 Medium |
|
|
118
|
+
| `windowSize` | Memory usage | 🟢 Medium |
|
|
119
|
+
|
|
120
|
+
### FlashList: The Better Option
|
|
121
|
+
```javascript
|
|
122
|
+
// Consider FlashList for better performance
|
|
123
|
+
import { FlashList } from "@shopify/flash-list";
|
|
124
|
+
|
|
125
|
+
<FlashList
|
|
126
|
+
data={items}
|
|
127
|
+
renderItem={renderItem}
|
|
128
|
+
estimatedItemSize={ITEM_HEIGHT}
|
|
129
|
+
keyExtractor={keyExtractor}
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
// Benefits over FlatList:
|
|
133
|
+
// ├── Faster recycling
|
|
134
|
+
// ├── Better memory management
|
|
135
|
+
// ├── Simpler API
|
|
136
|
+
// └── Fewer optimization props needed
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Animation Performance
|
|
140
|
+
```javascript
|
|
141
|
+
// ❌ JS-driven animation (blocks JS thread)
|
|
142
|
+
Animated.timing(value, {
|
|
143
|
+
toValue: 1,
|
|
144
|
+
duration: 300,
|
|
145
|
+
useNativeDriver: false, // BAD!
|
|
146
|
+
}).start();
|
|
147
|
+
|
|
148
|
+
// ✅ Native-driver animation (runs on UI thread)
|
|
149
|
+
Animated.timing(value, {
|
|
150
|
+
toValue: 1,
|
|
151
|
+
duration: 300,
|
|
152
|
+
useNativeDriver: true, // GOOD!
|
|
153
|
+
}).start();
|
|
154
|
+
|
|
155
|
+
// Native driver supports ONLY:
|
|
156
|
+
// ├── transform (translate, scale, rotate)
|
|
157
|
+
// └── opacity
|
|
158
|
+
//
|
|
159
|
+
// Does NOT support:
|
|
160
|
+
// ├── width, height
|
|
161
|
+
// ├── backgroundColor
|
|
162
|
+
// ├── borderRadius changes
|
|
163
|
+
// └── margin, padding
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Reanimated for Complex Animations
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
// For animations native driver can't handle, use Reanimated 3
|
|
170
|
+
|
|
171
|
+
import Animated, {
|
|
172
|
+
useSharedValue,
|
|
173
|
+
useAnimatedStyle,
|
|
174
|
+
withSpring,
|
|
175
|
+
} from 'react-native-reanimated';
|
|
176
|
+
|
|
177
|
+
const Component = () => {
|
|
178
|
+
const offset = useSharedValue(0);
|
|
179
|
+
|
|
180
|
+
const animatedStyles = useAnimatedStyle(() => ({
|
|
181
|
+
transform: [{ translateX: withSpring(offset.value) }],
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
return <Animated.View style={animatedStyles} />;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Benefits:
|
|
188
|
+
// ├── Runs on UI thread (60fps guaranteed)
|
|
189
|
+
// ├── Can animate any property
|
|
190
|
+
// ├── Gesture-driven animations
|
|
191
|
+
// └── Worklets for complex logic
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Memory Leak Prevention
|
|
195
|
+
```javascript
|
|
196
|
+
// ❌ Memory leak: uncleared interval
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
const interval = setInterval(() => {
|
|
199
|
+
fetchData();
|
|
200
|
+
}, 5000);
|
|
201
|
+
// Missing cleanup!
|
|
202
|
+
}, []);
|
|
203
|
+
|
|
204
|
+
// ✅ Proper cleanup
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
const interval = setInterval(() => {
|
|
207
|
+
fetchData();
|
|
208
|
+
}, 5000);
|
|
209
|
+
|
|
210
|
+
return () => clearInterval(interval); // CLEANUP!
|
|
211
|
+
}, []);
|
|
212
|
+
|
|
213
|
+
// Common memory leak sources:
|
|
214
|
+
// ├── Timers (setInterval, setTimeout)
|
|
215
|
+
// ├── Event listeners
|
|
216
|
+
// ├── Subscriptions (WebSocket, PubSub)
|
|
217
|
+
// ├── Async operations that update state after unmount
|
|
218
|
+
// └── Image caching without limits
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### React Native Performance Checklist
|
|
222
|
+
```markdown
|
|
223
|
+
## Before Every List
|
|
224
|
+
- [ ] Using FlatList or FlashList (NOT ScrollView)
|
|
225
|
+
- [ ] renderItem is useCallback memoized
|
|
226
|
+
- [ ] List items are React.memo wrapped
|
|
227
|
+
- [ ] keyExtractor uses stable ID (NOT index)
|
|
228
|
+
- [ ] getItemLayout provided (if fixed height)
|
|
229
|
+
|
|
230
|
+
## Before Every Animation
|
|
231
|
+
- [ ] useNativeDriver: true (if possible)
|
|
232
|
+
- [ ] Using Reanimated for complex animations
|
|
233
|
+
- [ ] Only animating transform/opacity
|
|
234
|
+
- [ ] Tested on low-end Android device
|
|
235
|
+
|
|
236
|
+
## Before Any Release
|
|
237
|
+
- [ ] console.log statements removed
|
|
238
|
+
- [ ] Cleanup functions in all useEffects
|
|
239
|
+
- [ ] No memory leaks (test with profiler)
|
|
240
|
+
- [ ] Tested in release build (not dev)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 3. Flutter Performance
|
|
246
|
+
|
|
247
|
+
### 🚫 The #1 AI Mistake: setState Overuse
|
|
248
|
+
```dart
|
|
249
|
+
// ❌ WRONG: setState rebuilds ENTIRE widget tree
|
|
250
|
+
class BadCounter extends StatefulWidget {
|
|
251
|
+
@override
|
|
252
|
+
State<BadCounter> createState() => _BadCounterState();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
class _BadCounterState extends State<BadCounter> {
|
|
256
|
+
int _counter = 0;
|
|
257
|
+
|
|
258
|
+
void _increment() {
|
|
259
|
+
setState(() {
|
|
260
|
+
_counter++; // This rebuilds EVERYTHING below!
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@override
|
|
265
|
+
Widget build(BuildContext context) {
|
|
266
|
+
return Column(
|
|
267
|
+
children: [
|
|
268
|
+
Text('Counter: $_counter'),
|
|
269
|
+
ExpensiveWidget(), // Rebuilds unnecessarily!
|
|
270
|
+
AnotherExpensiveWidget(), // Rebuilds unnecessarily!
|
|
271
|
+
],
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### The `const` Constructor Revolution
|
|
278
|
+
|
|
279
|
+
```dart
|
|
280
|
+
// ✅ CORRECT: const prevents rebuilds
|
|
281
|
+
|
|
282
|
+
class GoodCounter extends StatefulWidget {
|
|
283
|
+
const GoodCounter({super.key}); // CONST constructor!
|
|
284
|
+
|
|
285
|
+
@override
|
|
286
|
+
State<GoodCounter> createState() => _GoodCounterState();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
class _GoodCounterState extends State<GoodCounter> {
|
|
290
|
+
int _counter = 0;
|
|
291
|
+
|
|
292
|
+
@override
|
|
293
|
+
Widget build(BuildContext context) {
|
|
294
|
+
return Column(
|
|
295
|
+
children: [
|
|
296
|
+
Text('Counter: $_counter'),
|
|
297
|
+
const ExpensiveWidget(), // Won't rebuild!
|
|
298
|
+
const AnotherExpensiveWidget(), // Won't rebuild!
|
|
299
|
+
],
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// RULE: Add `const` to EVERY widget that doesn't depend on state
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Targeted State Management
|
|
308
|
+
```dart
|
|
309
|
+
// ❌ setState rebuilds whole tree
|
|
310
|
+
setState(() => _value = newValue);
|
|
311
|
+
|
|
312
|
+
// ✅ ValueListenableBuilder: surgical rebuilds
|
|
313
|
+
class TargetedState extends StatelessWidget {
|
|
314
|
+
final ValueNotifier<int> counter = ValueNotifier(0);
|
|
315
|
+
|
|
316
|
+
@override
|
|
317
|
+
Widget build(BuildContext context) {
|
|
318
|
+
return Column(
|
|
319
|
+
children: [
|
|
320
|
+
// Only this rebuilds when counter changes
|
|
321
|
+
ValueListenableBuilder<int>(
|
|
322
|
+
valueListenable: counter,
|
|
323
|
+
builder: (context, value, child) => Text('$value'),
|
|
324
|
+
child: const Icon(Icons.star), // Won't rebuild!
|
|
325
|
+
),
|
|
326
|
+
const ExpensiveWidget(), // Never rebuilds
|
|
327
|
+
],
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Riverpod/Provider Best Practices
|
|
334
|
+
```dart
|
|
335
|
+
// ❌ WRONG: Reading entire provider in build
|
|
336
|
+
Widget build(BuildContext context) {
|
|
337
|
+
final state = ref.watch(myProvider); // Rebuilds on ANY change
|
|
338
|
+
return Text(state.name);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ✅ CORRECT: Select only what you need
|
|
342
|
+
Widget build(BuildContext context) {
|
|
343
|
+
final name = ref.watch(myProvider.select((s) => s.name));
|
|
344
|
+
return Text(name); // Only rebuilds when name changes
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### ListView Optimization
|
|
349
|
+
```dart
|
|
350
|
+
// ❌ WRONG: ListView without builder (renders all)
|
|
351
|
+
ListView(
|
|
352
|
+
children: items.map((item) => ItemWidget(item)).toList(),
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
// ✅ CORRECT: ListView.builder (lazy rendering)
|
|
356
|
+
ListView.builder(
|
|
357
|
+
itemCount: items.length,
|
|
358
|
+
itemBuilder: (context, index) => ItemWidget(items[index]),
|
|
359
|
+
// Additional optimizations:
|
|
360
|
+
itemExtent: 56, // Fixed height = faster layout
|
|
361
|
+
cacheExtent: 100, // Pre-render distance
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
// ✅ EVEN BETTER: ListView.separated for dividers
|
|
365
|
+
ListView.separated(
|
|
366
|
+
itemCount: items.length,
|
|
367
|
+
itemBuilder: (context, index) => ItemWidget(items[index]),
|
|
368
|
+
separatorBuilder: (context, index) => const Divider(),
|
|
369
|
+
)
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Image Optimization
|
|
373
|
+
```dart
|
|
374
|
+
// ❌ WRONG: No caching, full resolution
|
|
375
|
+
Image.network(url)
|
|
376
|
+
|
|
377
|
+
// ✅ CORRECT: Cached with proper sizing
|
|
378
|
+
CachedNetworkImage(
|
|
379
|
+
imageUrl: url,
|
|
380
|
+
width: 100,
|
|
381
|
+
height: 100,
|
|
382
|
+
fit: BoxFit.cover,
|
|
383
|
+
memCacheWidth: 200, // Cache at 2x for retina
|
|
384
|
+
memCacheHeight: 200,
|
|
385
|
+
placeholder: (context, url) => const Skeleton(),
|
|
386
|
+
errorWidget: (context, url, error) => const Icon(Icons.error),
|
|
387
|
+
)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Dispose Pattern
|
|
391
|
+
```dart
|
|
392
|
+
class MyWidget extends StatefulWidget {
|
|
393
|
+
@override
|
|
394
|
+
State<MyWidget> createState() => _MyWidgetState();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
class _MyWidgetState extends State<MyWidget> {
|
|
398
|
+
late final StreamSubscription _subscription;
|
|
399
|
+
late final AnimationController _controller;
|
|
400
|
+
late final TextEditingController _textController;
|
|
401
|
+
|
|
402
|
+
@override
|
|
403
|
+
void initState() {
|
|
404
|
+
super.initState();
|
|
405
|
+
_subscription = stream.listen((_) {});
|
|
406
|
+
_controller = AnimationController(vsync: this);
|
|
407
|
+
_textController = TextEditingController();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
@override
|
|
411
|
+
void dispose() {
|
|
412
|
+
// ALWAYS dispose in reverse order of creation
|
|
413
|
+
_textController.dispose();
|
|
414
|
+
_controller.dispose();
|
|
415
|
+
_subscription.cancel();
|
|
416
|
+
super.dispose();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
@override
|
|
420
|
+
Widget build(BuildContext context) => Container();
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Flutter Performance Checklist
|
|
425
|
+
|
|
426
|
+
```markdown
|
|
427
|
+
## Before Every Widget
|
|
428
|
+
- [ ] const constructor added (if no runtime args)
|
|
429
|
+
- [ ] const keywords on static children
|
|
430
|
+
- [ ] Minimal setState scope
|
|
431
|
+
- [ ] Using selectors for provider watches
|
|
432
|
+
|
|
433
|
+
## Before Every List
|
|
434
|
+
- [ ] Using ListView.builder (NOT ListView with children)
|
|
435
|
+
- [ ] itemExtent provided (if fixed height)
|
|
436
|
+
- [ ] Image caching with size limits
|
|
437
|
+
|
|
438
|
+
## Before Any Animation
|
|
439
|
+
- [ ] Using Impeller (Flutter 3.16+)
|
|
440
|
+
- [ ] Avoiding Opacity widget (use FadeTransition)
|
|
441
|
+
- [ ] TickerProviderStateMixin for AnimationController
|
|
442
|
+
|
|
443
|
+
## Before Any Release
|
|
444
|
+
- [ ] All dispose() methods implemented
|
|
445
|
+
- [ ] No print() in production
|
|
446
|
+
- [ ] Tested in profile/release mode
|
|
447
|
+
- [ ] DevTools performance overlay checked
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## 4. Jetpack Compose Performance
|
|
453
|
+
|
|
454
|
+
### 🚫 The #1 AI Mistake: Side Effects in Composition
|
|
455
|
+
```kotlin
|
|
456
|
+
// ❌ WRONG: Side effects directly in composition
|
|
457
|
+
@Composable
|
|
458
|
+
fun BadScreen(viewModel: MyViewModel) {
|
|
459
|
+
// This runs on EVERY recomposition!
|
|
460
|
+
viewModel.loadData() // CATASTROPHIC!
|
|
461
|
+
|
|
462
|
+
val data by viewModel.data.collectAsState()
|
|
463
|
+
Text(data)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Why it's catastrophic:
|
|
467
|
+
// ├── Composition can happen multiple times per second
|
|
468
|
+
// ├── loadData() called hundreds of times
|
|
469
|
+
// ├── API hammered, battery drained
|
|
470
|
+
// └── App becomes unusable
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### LaunchedEffect for Side Effects
|
|
474
|
+
```kotlin
|
|
475
|
+
// ✅ CORRECT: Side effects in LaunchedEffect
|
|
476
|
+
@Composable
|
|
477
|
+
fun GoodScreen(viewModel: MyViewModel) {
|
|
478
|
+
// Runs ONCE when composition enters
|
|
479
|
+
LaunchedEffect(Unit) {
|
|
480
|
+
viewModel.loadData()
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
val data by viewModel.data.collectAsState()
|
|
484
|
+
Text(data)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ✅ CORRECT: Run when key changes
|
|
488
|
+
@Composable
|
|
489
|
+
fun UserProfile(userId: String, viewModel: UserViewModel) {
|
|
490
|
+
LaunchedEffect(userId) { // Re-runs when userId changes
|
|
491
|
+
viewModel.loadUser(userId)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
val user by viewModel.user.collectAsState()
|
|
495
|
+
// Render user...
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### remember vs rememberSaveable
|
|
500
|
+
```kotlin
|
|
501
|
+
// ❌ WRONG: Recomputes on every recomposition
|
|
502
|
+
@Composable
|
|
503
|
+
fun BadList(items: List<Item>) {
|
|
504
|
+
val filteredItems = items.filter { it.isActive } // Recomputes always!
|
|
505
|
+
LazyColumn {
|
|
506
|
+
items(filteredItems) { ItemRow(it) }
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ✅ CORRECT: Cache computation with remember
|
|
511
|
+
@Composable
|
|
512
|
+
fun GoodList(items: List<Item>) {
|
|
513
|
+
val filteredItems = remember(items) {
|
|
514
|
+
items.filter { it.isActive } // Only when items change
|
|
515
|
+
}
|
|
516
|
+
LazyColumn {
|
|
517
|
+
items(filteredItems) { ItemRow(it) }
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ✅ EVEN BETTER: Use derivedStateOf for computed state
|
|
522
|
+
@Composable
|
|
523
|
+
fun BestList(items: List<Item>, query: String) {
|
|
524
|
+
val filteredItems by remember {
|
|
525
|
+
derivedStateOf {
|
|
526
|
+
items.filter { it.name.contains(query, ignoreCase = true) }
|
|
527
|
+
}
|
|
528
|
+
} // Only recomputes when items OR query changes
|
|
529
|
+
|
|
530
|
+
LazyColumn {
|
|
531
|
+
items(filteredItems, key = { it.id }) { item ->
|
|
532
|
+
ItemRow(item)
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### LazyColumn Optimization
|
|
539
|
+
```kotlin
|
|
540
|
+
// ❌ WRONG: No stable keys, unstable content
|
|
541
|
+
@Composable
|
|
542
|
+
fun BadList(items: List<Item>) {
|
|
543
|
+
LazyColumn {
|
|
544
|
+
items(items) { item -> // No key!
|
|
545
|
+
ItemRow(item = item) // Recreates on every change
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ✅ CORRECT: Stable keys + memoization
|
|
551
|
+
@Composable
|
|
552
|
+
fun GoodList(items: List<Item>) {
|
|
553
|
+
LazyColumn(
|
|
554
|
+
contentPadding = PaddingValues(16.dp),
|
|
555
|
+
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
556
|
+
) {
|
|
557
|
+
items(
|
|
558
|
+
items = items,
|
|
559
|
+
key = { item -> item.id } // Stable key for animations
|
|
560
|
+
) { item ->
|
|
561
|
+
ItemRow(
|
|
562
|
+
item = item,
|
|
563
|
+
modifier = Modifier.animateItemPlacement() // Smooth animations
|
|
564
|
+
)
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Item should be stable (data class with @Immutable/@Stable)
|
|
570
|
+
@Immutable
|
|
571
|
+
data class Item(
|
|
572
|
+
val id: String,
|
|
573
|
+
val name: String,
|
|
574
|
+
val description: String
|
|
575
|
+
)
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### State Hoisting vs Local State
|
|
579
|
+
```kotlin
|
|
580
|
+
// ❌ WRONG: Unnecessary state hoisting
|
|
581
|
+
@Composable
|
|
582
|
+
fun Parent() {
|
|
583
|
+
var expanded by remember { mutableStateOf(false) }
|
|
584
|
+
Child(expanded = expanded, onToggle = { expanded = !expanded })
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
@Composable
|
|
588
|
+
fun Child(expanded: Boolean, onToggle: () -> Unit) {
|
|
589
|
+
// Only Child uses this state, why hoist?
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ✅ CORRECT: Keep state local when possible
|
|
593
|
+
@Composable
|
|
594
|
+
fun Parent() {
|
|
595
|
+
Child() // No state passing needed
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
@Composable
|
|
599
|
+
fun Child() {
|
|
600
|
+
var expanded by remember { mutableStateOf(false) }
|
|
601
|
+
// Use expanded locally
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// RULE: Hoist state ONLY when:
|
|
605
|
+
// ├── Multiple composables need it
|
|
606
|
+
// ├── Parent needs to control it
|
|
607
|
+
// └── State needs to survive configuration changes (use rememberSaveable)
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Recomposition Optimization
|
|
611
|
+
```kotlin
|
|
612
|
+
// ❌ WRONG: Entire screen recomposes on every state change
|
|
613
|
+
@Composable
|
|
614
|
+
fun BadScreen(viewModel: MyViewModel) {
|
|
615
|
+
val state by viewModel.state.collectAsState()
|
|
616
|
+
|
|
617
|
+
Column {
|
|
618
|
+
Header(state.title)
|
|
619
|
+
Content(state.items)
|
|
620
|
+
Footer(state.count)
|
|
621
|
+
} // All recompose when ANY state changes
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ✅ CORRECT: Separate state flows for independent sections
|
|
625
|
+
@Composable
|
|
626
|
+
fun GoodScreen(viewModel: MyViewModel) {
|
|
627
|
+
Column {
|
|
628
|
+
Header(viewModel.title.collectAsState().value)
|
|
629
|
+
Content(viewModel.items.collectAsState().value)
|
|
630
|
+
Footer(viewModel.count.collectAsState().value)
|
|
631
|
+
} // Each recomposes independently
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// ✅ EVEN BETTER: Extract to separate composables
|
|
635
|
+
@Composable
|
|
636
|
+
fun BestScreen(viewModel: MyViewModel) {
|
|
637
|
+
Column {
|
|
638
|
+
HeaderSection(viewModel)
|
|
639
|
+
ContentSection(viewModel)
|
|
640
|
+
FooterSection(viewModel)
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
@Composable
|
|
645
|
+
private fun HeaderSection(viewModel: MyViewModel) {
|
|
646
|
+
val title by viewModel.title.collectAsState()
|
|
647
|
+
Header(title) // Only this recomposes when title changes
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### Animation Performance
|
|
652
|
+
```kotlin
|
|
653
|
+
// ❌ WRONG: Animating offset (causes recomposition)
|
|
654
|
+
@Composable
|
|
655
|
+
fun BadAnimation() {
|
|
656
|
+
var offset by remember { mutableStateOf(0f) }
|
|
657
|
+
|
|
658
|
+
LaunchedEffect(Unit) {
|
|
659
|
+
animate(
|
|
660
|
+
initialValue = 0f,
|
|
661
|
+
targetValue = 100f
|
|
662
|
+
) { value, _ ->
|
|
663
|
+
offset = value // Recomposition on every frame!
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
Box(Modifier.offset { IntOffset(offset.toInt(), 0) })
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ✅ CORRECT: Use graphicsLayer (no recomposition)
|
|
671
|
+
@Composable
|
|
672
|
+
fun GoodAnimation() {
|
|
673
|
+
val offset by animateFloatAsState(
|
|
674
|
+
targetValue = 100f,
|
|
675
|
+
animationSpec = spring()
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
Box(
|
|
679
|
+
Modifier.graphicsLayer {
|
|
680
|
+
translationX = offset // GPU-accelerated, no recomposition!
|
|
681
|
+
}
|
|
682
|
+
)
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// ✅ Compose animation APIs (all optimized):
|
|
686
|
+
animateFloatAsState() // Single value
|
|
687
|
+
animateDpAsState() // Dp values
|
|
688
|
+
animateColorAsState() // Colors
|
|
689
|
+
updateTransition() // Multiple values
|
|
690
|
+
Animatable() // Manual control
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### Memory Management
|
|
694
|
+
```kotlin
|
|
695
|
+
// ❌ WRONG: No cleanup
|
|
696
|
+
@Composable
|
|
697
|
+
fun BadLocationTracker() {
|
|
698
|
+
val context = LocalContext.current
|
|
699
|
+
|
|
700
|
+
LaunchedEffect(Unit) {
|
|
701
|
+
val locationManager = context.getSystemService<LocationManager>()
|
|
702
|
+
val listener = object : LocationListener {
|
|
703
|
+
override fun onLocationChanged(location: Location) {
|
|
704
|
+
// Update UI
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
locationManager?.requestLocationUpdates(
|
|
708
|
+
LocationManager.GPS_PROVIDER,
|
|
709
|
+
1000L,
|
|
710
|
+
0f,
|
|
711
|
+
listener
|
|
712
|
+
)
|
|
713
|
+
// Missing cleanup!
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// ✅ CORRECT: DisposableEffect for cleanup
|
|
718
|
+
@Composable
|
|
719
|
+
fun GoodLocationTracker() {
|
|
720
|
+
val context = LocalContext.current
|
|
721
|
+
|
|
722
|
+
DisposableEffect(Unit) {
|
|
723
|
+
val locationManager = context.getSystemService<LocationManager>()
|
|
724
|
+
val listener = object : LocationListener {
|
|
725
|
+
override fun onLocationChanged(location: Location) {
|
|
726
|
+
// Update UI
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
locationManager?.requestLocationUpdates(
|
|
731
|
+
LocationManager.GPS_PROVIDER,
|
|
732
|
+
1000L,
|
|
733
|
+
0f,
|
|
734
|
+
listener
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
onDispose {
|
|
738
|
+
locationManager?.removeUpdates(listener) // CLEANUP!
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
### Jetpack Compose Performance Checklist
|
|
745
|
+
```markdown
|
|
746
|
+
## Before Every Composable
|
|
747
|
+
- [ ] Side effects in LaunchedEffect/DisposableEffect (NOT in composition)
|
|
748
|
+
- [ ] Expensive computations in remember/derivedStateOf
|
|
749
|
+
- [ ] Data classes marked @Immutable or @Stable
|
|
750
|
+
- [ ] Local state when possible (avoid unnecessary hoisting)
|
|
751
|
+
|
|
752
|
+
## Before Every LazyColumn/LazyRow
|
|
753
|
+
- [ ] Stable keys provided (key = { item.id })
|
|
754
|
+
- [ ] Items are @Immutable data classes
|
|
755
|
+
- [ ] Using contentPadding instead of outer padding
|
|
756
|
+
- [ ] animateItemPlacement() for smooth animations
|
|
757
|
+
|
|
758
|
+
## Before Every Animation
|
|
759
|
+
- [ ] Using graphicsLayer (NOT offset/size modifiers)
|
|
760
|
+
- [ ] Animating transform/opacity only
|
|
761
|
+
- [ ] Using Compose animation APIs (not manual state)
|
|
762
|
+
- [ ] Tested at 60fps
|
|
763
|
+
|
|
764
|
+
## Before Any Release
|
|
765
|
+
- [ ] No side effects in composition
|
|
766
|
+
- [ ] DisposableEffect cleanup implemented
|
|
767
|
+
- [ ] Layout Inspector checked for recomposition
|
|
768
|
+
- [ ] Tested in release build
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
---
|
|
772
|
+
|
|
773
|
+
## 5. Kotlin Multiplatform Performance
|
|
774
|
+
|
|
775
|
+
### 🚫 The #1 AI Mistake: Blocking iOS Main Thread
|
|
776
|
+
```kotlin
|
|
777
|
+
// ❌ WRONG: Blocks iOS main thread (UI freezes)
|
|
778
|
+
class BadRepository {
|
|
779
|
+
suspend fun getUsers(): List<User> {
|
|
780
|
+
return withContext(Dispatchers.Default) {
|
|
781
|
+
apiClient.fetchUsers() // Blocks iOS main thread!
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Why it fails on iOS:
|
|
787
|
+
// ├── Kotlin/Native coroutines run on main thread by default
|
|
788
|
+
// ├── Dispatchers.Default = main thread on iOS
|
|
789
|
+
// ├── Network call blocks UI
|
|
790
|
+
// └── iOS watchdog kills app
|
|
791
|
+
|
|
792
|
+
// ✅ CORRECT: Use MainScope for iOS compatibility
|
|
793
|
+
class GoodRepository {
|
|
794
|
+
private val scope = MainScope() // iOS-safe
|
|
795
|
+
|
|
796
|
+
fun getUsers(onResult: (List<User>) -> Unit, onError: (Throwable) -> Unit) {
|
|
797
|
+
scope.launch {
|
|
798
|
+
try {
|
|
799
|
+
val users = apiClient.fetchUsers()
|
|
800
|
+
onResult(users)
|
|
801
|
+
} catch (e: Exception) {
|
|
802
|
+
onError(e)
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### Thread Safety with Immutability
|
|
810
|
+
```kotlin
|
|
811
|
+
// ❌ WRONG: Mutable shared state (crashes on iOS pre-new memory model)
|
|
812
|
+
class BadViewModel {
|
|
813
|
+
var users: MutableList<User> = mutableListOf() // DANGER on iOS!
|
|
814
|
+
|
|
815
|
+
fun addUser(user: User) {
|
|
816
|
+
users.add(user) // Can crash on iOS
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Why it fails:
|
|
821
|
+
// ├── Kotlin/Native (pre-1.7) requires objects to be frozen when shared
|
|
822
|
+
// ├── Mutable collections can't be frozen
|
|
823
|
+
// ├── Accessing from different threads = crash
|
|
824
|
+
// └── StateFlow helps but data must be immutable
|
|
825
|
+
|
|
826
|
+
// ✅ CORRECT: Immutable data + StateFlow
|
|
827
|
+
class GoodViewModel {
|
|
828
|
+
private val _users = MutableStateFlow<List<User>>(emptyList())
|
|
829
|
+
val users: StateFlow<List<User>> = _users.asStateFlow()
|
|
830
|
+
|
|
831
|
+
fun addUser(user: User) {
|
|
832
|
+
_users.update { currentList ->
|
|
833
|
+
currentList + user // New list, not mutating
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Data classes should be immutable
|
|
839
|
+
@Serializable
|
|
840
|
+
data class User(
|
|
841
|
+
val id: String,
|
|
842
|
+
val name: String,
|
|
843
|
+
val email: String
|
|
844
|
+
) // No var properties!
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
### iOS Framework Size Optimization
|
|
848
|
+
```kotlin
|
|
849
|
+
// build.gradle.kts
|
|
850
|
+
|
|
851
|
+
kotlin {
|
|
852
|
+
listOf(
|
|
853
|
+
iosX64(),
|
|
854
|
+
iosArm64(),
|
|
855
|
+
iosSimulatorArm64()
|
|
856
|
+
).forEach { iosTarget ->
|
|
857
|
+
iosTarget.binaries.framework {
|
|
858
|
+
baseName = "shared"
|
|
859
|
+
|
|
860
|
+
// ✅ Use static framework (smaller size)
|
|
861
|
+
isStatic = true
|
|
862
|
+
|
|
863
|
+
// ✅ Export only needed dependencies
|
|
864
|
+
export("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
|
865
|
+
// Don't export everything!
|
|
866
|
+
|
|
867
|
+
// ✅ Strip debug symbols in release
|
|
868
|
+
freeCompilerArgs += listOf(
|
|
869
|
+
"-Xbinary=stripDebugInfo=true"
|
|
870
|
+
)
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Typical framework sizes:
|
|
876
|
+
// ├── Debug: 20-50 MB (with symbols)
|
|
877
|
+
// ├── Release (unoptimized): 10-20 MB
|
|
878
|
+
// └── Release (optimized): 5-10 MB
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
### Database Performance (SQLDelight)
|
|
882
|
+
```kotlin
|
|
883
|
+
// ❌ WRONG: Individual queries in loop (N+1 problem)
|
|
884
|
+
fun loadUsersWithPosts(): List<UserWithPosts> {
|
|
885
|
+
val users = database.userQueries.selectAll().executeAsList()
|
|
886
|
+
return users.map { user ->
|
|
887
|
+
val posts = database.postQueries.selectByUserId(user.id).executeAsList()
|
|
888
|
+
UserWithPosts(user, posts) // N queries!
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// ✅ CORRECT: Single JOIN query
|
|
893
|
+
// In .sq file:
|
|
894
|
+
selectUsersWithPosts:
|
|
895
|
+
SELECT
|
|
896
|
+
User.*,
|
|
897
|
+
Post.id AS post_id,
|
|
898
|
+
Post.title AS post_title,
|
|
899
|
+
Post.content AS post_content
|
|
900
|
+
FROM User
|
|
901
|
+
LEFT JOIN Post ON User.id = Post.userId;
|
|
902
|
+
|
|
903
|
+
fun loadUsersWithPosts(): List<UserWithPosts> {
|
|
904
|
+
return database.userQueries.selectUsersWithPosts()
|
|
905
|
+
.executeAsList()
|
|
906
|
+
.groupBy { it.id }
|
|
907
|
+
.map { (userId, rows) ->
|
|
908
|
+
UserWithPosts(
|
|
909
|
+
user = rows.first().toUser(),
|
|
910
|
+
posts = rows.mapNotNull { it.toPost() }
|
|
911
|
+
)
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// ✅ CORRECT: Batch inserts with transaction
|
|
916
|
+
fun insertUsers(users: List<User>) {
|
|
917
|
+
database.transaction {
|
|
918
|
+
users.forEach { user ->
|
|
919
|
+
database.userQueries.insert(user.id, user.name, user.email)
|
|
920
|
+
}
|
|
921
|
+
} // Single transaction = 10-100x faster
|
|
922
|
+
}
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
### Network Performance (Ktor)
|
|
926
|
+
```kotlin
|
|
927
|
+
// ❌ WRONG: No connection pooling, sequential requests
|
|
928
|
+
class BadApiClient {
|
|
929
|
+
private val client = HttpClient()
|
|
930
|
+
|
|
931
|
+
suspend fun loadDashboard(): Dashboard {
|
|
932
|
+
val user = client.get("https://api.example.com/user").body<User>()
|
|
933
|
+
val posts = client.get("https://api.example.com/posts").body<List<Post>>()
|
|
934
|
+
val notifications = client.get("https://api.example.com/notifications").body<List<Notification>>()
|
|
935
|
+
return Dashboard(user, posts, notifications)
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// ✅ CORRECT: Connection pooling + parallel requests
|
|
940
|
+
class GoodApiClient {
|
|
941
|
+
private val client = HttpClient {
|
|
942
|
+
// Platform-specific engines
|
|
943
|
+
engine {
|
|
944
|
+
// Android: OkHttp
|
|
945
|
+
threadsCount = 4
|
|
946
|
+
pipelining = true
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
install(HttpTimeout) {
|
|
950
|
+
requestTimeoutMillis = 10_000
|
|
951
|
+
connectTimeoutMillis = 5_000
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
install(ContentNegotiation) {
|
|
955
|
+
json(Json {
|
|
956
|
+
ignoreUnknownKeys = true
|
|
957
|
+
isLenient = true
|
|
958
|
+
})
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
install(Logging) {
|
|
962
|
+
level = if (BuildConfig.DEBUG) LogLevel.BODY else LogLevel.NONE
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
suspend fun loadDashboard(): Dashboard = coroutineScope {
|
|
967
|
+
// Parallel requests
|
|
968
|
+
val userDeferred = async { client.get("https://api.example.com/user").body<User>() }
|
|
969
|
+
val postsDeferred = async { client.get("https://api.example.com/posts").body<List<Post>>() }
|
|
970
|
+
val notificationsDeferred = async { client.get("https://api.example.com/notifications").body<List<Notification>>() }
|
|
971
|
+
|
|
972
|
+
Dashboard(
|
|
973
|
+
user = userDeferred.await(),
|
|
974
|
+
posts = postsDeferred.await(),
|
|
975
|
+
notifications = notificationsDeferred.await()
|
|
976
|
+
)
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
### Memory Model Considerations
|
|
982
|
+
```kotlin
|
|
983
|
+
// Kotlin/Native Memory Models:
|
|
984
|
+
// ├── Old (pre-1.7): Strict freezing, immutability required
|
|
985
|
+
// └── New (1.7+): Similar to JVM, more flexible
|
|
986
|
+
|
|
987
|
+
// Enable new memory model (gradle.properties):
|
|
988
|
+
kotlin.native.binary.memoryModel=experimental
|
|
989
|
+
|
|
990
|
+
// ✅ With new memory model:
|
|
991
|
+
class ModernViewModel {
|
|
992
|
+
// Mutable state is now safe!
|
|
993
|
+
private val _state = MutableStateFlow(UiState.Loading)
|
|
994
|
+
val state = _state.asStateFlow()
|
|
995
|
+
|
|
996
|
+
fun updateState(newState: UiState) {
|
|
997
|
+
_state.value = newState // Safe on both platforms
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// ❌ With old memory model (Kotlin < 1.7):
|
|
1002
|
+
// Would need to freeze objects:
|
|
1003
|
+
data class User(val id: String, val name: String) {
|
|
1004
|
+
init {
|
|
1005
|
+
freeze() // Required for sharing between threads
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
### Compose Multiplatform Performance
|
|
1011
|
+
```kotlin
|
|
1012
|
+
// Same performance principles as Jetpack Compose apply
|
|
1013
|
+
|
|
1014
|
+
// ✅ CORRECT: Platform-specific optimization
|
|
1015
|
+
@Composable
|
|
1016
|
+
expect fun PlatformOptimizedImage(
|
|
1017
|
+
url: String,
|
|
1018
|
+
modifier: Modifier = Modifier
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
// androidMain
|
|
1022
|
+
@Composable
|
|
1023
|
+
actual fun PlatformOptimizedImage(url: String, modifier: Modifier) {
|
|
1024
|
+
// Use Coil on Android
|
|
1025
|
+
AsyncImage(
|
|
1026
|
+
model = ImageRequest.Builder(LocalContext.current)
|
|
1027
|
+
.data(url)
|
|
1028
|
+
.crossfade(true)
|
|
1029
|
+
.build(),
|
|
1030
|
+
contentDescription = null,
|
|
1031
|
+
modifier = modifier
|
|
1032
|
+
)
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// iosMain
|
|
1036
|
+
@Composable
|
|
1037
|
+
actual fun PlatformOptimizedImage(url: String, modifier: Modifier) {
|
|
1038
|
+
// Use native iOS image loading
|
|
1039
|
+
// (Compose Multiplatform doesn't have Coil for iOS yet)
|
|
1040
|
+
}
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
### Kotlin Multiplatform Performance Checklist
|
|
1044
|
+
```markdown
|
|
1045
|
+
## Before Every Shared Module
|
|
1046
|
+
- [ ] Immutable data classes (no var properties)
|
|
1047
|
+
- [ ] StateFlow for reactive state (not mutable collections)
|
|
1048
|
+
- [ ] Transactions for batch database operations
|
|
1049
|
+
- [ ] Parallel network requests (async/await)
|
|
1050
|
+
- [ ] New
|