@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,770 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: platform-kmp
|
|
3
|
+
description: Kotlin Multiplatform (KMP) architecture, shared logic patterns, expect/actual mechanism, iOS framework integration, and Compose Multiplatform UI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Kotlin Multiplatform (KMP) Guide
|
|
7
|
+
|
|
8
|
+
> **Philosophy:** Share business logic, not UI (unless using Compose Multiplatform).
|
|
9
|
+
> **Core Principle:** Write once in Kotlin, compile to native iOS/Android.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 🎯 When to Use KMP
|
|
14
|
+
|
|
15
|
+
### ✅ GOOD Use Cases:
|
|
16
|
+
- **Business Logic Sharing**: Networking, data models, repositories, use cases
|
|
17
|
+
- **Type-Safe APIs**: Generate shared API clients from OpenAPI/GraphQL
|
|
18
|
+
- **Database Logic**: SQLDelight for shared database code
|
|
19
|
+
- **Validation Logic**: Form validation, business rules
|
|
20
|
+
- **Utilities**: Date formatting, crypto, parsing
|
|
21
|
+
- **Analytics/Logging**: Shared tracking logic
|
|
22
|
+
|
|
23
|
+
### ❌ NOT Recommended For:
|
|
24
|
+
- **UI Code** (unless Compose Multiplatform)
|
|
25
|
+
- **Platform-Specific Features**: Camera, contacts, biometrics (use expect/actual)
|
|
26
|
+
- **Simple Apps**: Overhead not worth it for CRUD apps
|
|
27
|
+
- **Teams Without Kotlin Expertise**: Steep learning curve
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 🏗️ KMP Architecture Layers
|
|
32
|
+
```
|
|
33
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
34
|
+
│ PLATFORM LAYER │
|
|
35
|
+
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
|
36
|
+
│ │ iOS (SwiftUI) │ │ Android (Compose) │ │
|
|
37
|
+
│ │ - Views │ │ - Composables │ │
|
|
38
|
+
│ │ - ViewModels │ │ - ViewModels │ │
|
|
39
|
+
│ │ - Navigation │ │ - Navigation │ │
|
|
40
|
+
│ └──────────────────────┘ └──────────────────────┘ │
|
|
41
|
+
├─────────────────────────────────────────────────────────────┤
|
|
42
|
+
│ SHARED KOTLIN CODE │
|
|
43
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
44
|
+
│ │ PRESENTATION LAYER │ │
|
|
45
|
+
│ │ - ViewModels (if sharing) │ │
|
|
46
|
+
│ │ - State classes │ │
|
|
47
|
+
│ │ - UI Events │ │
|
|
48
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
49
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
50
|
+
│ │ DOMAIN LAYER │ │
|
|
51
|
+
│ │ - Use Cases │ │
|
|
52
|
+
│ │ - Business Logic │ │
|
|
53
|
+
│ │ - Domain Models │ │
|
|
54
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
55
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
56
|
+
│ │ DATA LAYER │ │
|
|
57
|
+
│ │ - Repositories │ │
|
|
58
|
+
│ │ - API Clients (Ktor) │ │
|
|
59
|
+
│ │ - Database (SQLDelight) │ │
|
|
60
|
+
│ │ - Data Models / DTOs │ │
|
|
61
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
62
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
63
|
+
│ │ PLATFORM-SPECIFIC (expect/actual) │ │
|
|
64
|
+
│ │ - File I/O │ │
|
|
65
|
+
│ │ - Platform APIs │ │
|
|
66
|
+
│ │ - Native Features │ │
|
|
67
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
68
|
+
└─────────────────────────────────────────────────────────────┘
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 🔧 Project Structure
|
|
74
|
+
```
|
|
75
|
+
my-kmp-app/
|
|
76
|
+
├── shared/
|
|
77
|
+
│ ├── src/
|
|
78
|
+
│ │ ├── commonMain/kotlin/
|
|
79
|
+
│ │ │ ├── data/
|
|
80
|
+
│ │ │ │ ├── api/
|
|
81
|
+
│ │ │ │ │ └── ApiClient.kt
|
|
82
|
+
│ │ │ │ ├── database/
|
|
83
|
+
│ │ │ │ │ └── Database.kt (SQLDelight)
|
|
84
|
+
│ │ │ │ └── repository/
|
|
85
|
+
│ │ │ │ └── UserRepository.kt
|
|
86
|
+
│ │ │ ├── domain/
|
|
87
|
+
│ │ │ │ ├── model/
|
|
88
|
+
│ │ │ │ │ └── User.kt
|
|
89
|
+
│ │ │ │ └── usecase/
|
|
90
|
+
│ │ │ │ └── GetUserUseCase.kt
|
|
91
|
+
│ │ │ └── Platform.kt (expect declaration)
|
|
92
|
+
│ │ │
|
|
93
|
+
│ │ ├── androidMain/kotlin/
|
|
94
|
+
│ │ │ └── Platform.android.kt (actual implementation)
|
|
95
|
+
│ │ │
|
|
96
|
+
│ │ └── iosMain/kotlin/
|
|
97
|
+
│ │ └── Platform.ios.kt (actual implementation)
|
|
98
|
+
│ │
|
|
99
|
+
│ └── build.gradle.kts
|
|
100
|
+
│
|
|
101
|
+
├── androidApp/
|
|
102
|
+
│ ├── src/main/
|
|
103
|
+
│ │ ├── java/
|
|
104
|
+
│ │ │ └── com/myapp/
|
|
105
|
+
│ │ │ ├── ui/
|
|
106
|
+
│ │ │ │ └── MainActivity.kt
|
|
107
|
+
│ │ │ └── viewmodel/
|
|
108
|
+
│ │ │ └── UserViewModel.kt
|
|
109
|
+
│ │ └── AndroidManifest.xml
|
|
110
|
+
│ └── build.gradle.kts
|
|
111
|
+
│
|
|
112
|
+
└── iosApp/
|
|
113
|
+
├── iosApp/
|
|
114
|
+
│ ├── ContentView.swift
|
|
115
|
+
│ └── UserViewModel.swift
|
|
116
|
+
└── iosApp.xcodeproj
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 📦 Core Libraries
|
|
122
|
+
|
|
123
|
+
### Essential KMP Libraries
|
|
124
|
+
```kotlin
|
|
125
|
+
// shared/build.gradle.kts
|
|
126
|
+
|
|
127
|
+
kotlin {
|
|
128
|
+
// Ktor for networking
|
|
129
|
+
sourceSets {
|
|
130
|
+
val commonMain by getting {
|
|
131
|
+
dependencies {
|
|
132
|
+
// Networking
|
|
133
|
+
implementation("io.ktor:ktor-client-core:2.3.7")
|
|
134
|
+
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
|
|
135
|
+
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
|
|
136
|
+
|
|
137
|
+
// Serialization
|
|
138
|
+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
|
|
139
|
+
|
|
140
|
+
// Coroutines
|
|
141
|
+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
|
142
|
+
|
|
143
|
+
// DateTime
|
|
144
|
+
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
|
|
145
|
+
|
|
146
|
+
// DI (optional)
|
|
147
|
+
implementation("io.insert-koin:koin-core:3.5.3")
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
val androidMain by getting {
|
|
152
|
+
dependencies {
|
|
153
|
+
implementation("io.ktor:ktor-client-okhttp:2.3.7")
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
val iosMain by getting {
|
|
158
|
+
dependencies {
|
|
159
|
+
implementation("io.ktor:ktor-client-darwin:2.3.7")
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### SQLDelight Setup
|
|
167
|
+
```kotlin
|
|
168
|
+
// shared/build.gradle.kts
|
|
169
|
+
|
|
170
|
+
plugins {
|
|
171
|
+
id("app.cash.sqldelight") version "2.0.1"
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
sqldelight {
|
|
175
|
+
databases {
|
|
176
|
+
create("AppDatabase") {
|
|
177
|
+
packageName.set("com.myapp.database")
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 🎭 expect/actual Mechanism
|
|
186
|
+
|
|
187
|
+
### Pattern 1: Platform-Specific Implementation
|
|
188
|
+
```kotlin
|
|
189
|
+
// commonMain/Platform.kt
|
|
190
|
+
expect class Platform() {
|
|
191
|
+
val name: String
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
expect fun getPlatform(): Platform
|
|
195
|
+
|
|
196
|
+
// androidMain/Platform.android.kt
|
|
197
|
+
actual class Platform actual constructor() {
|
|
198
|
+
actual val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
actual fun getPlatform(): Platform = Platform()
|
|
202
|
+
|
|
203
|
+
// iosMain/Platform.ios.kt
|
|
204
|
+
import platform.UIKit.UIDevice
|
|
205
|
+
|
|
206
|
+
actual class Platform actual constructor() {
|
|
207
|
+
actual val name: String =
|
|
208
|
+
UIDevice.currentDevice.systemName() + " " +
|
|
209
|
+
UIDevice.currentDevice.systemVersion
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
actual fun getPlatform(): Platform = Platform()
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Pattern 2: Platform-Specific API Access
|
|
216
|
+
```kotlin
|
|
217
|
+
// commonMain/storage/SecureStorage.kt
|
|
218
|
+
expect class SecureStorage {
|
|
219
|
+
suspend fun save(key: String, value: String)
|
|
220
|
+
suspend fun get(key: String): String?
|
|
221
|
+
suspend fun delete(key: String)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// androidMain/storage/SecureStorage.android.kt
|
|
225
|
+
import androidx.security.crypto.EncryptedSharedPreferences
|
|
226
|
+
import androidx.security.crypto.MasterKey
|
|
227
|
+
|
|
228
|
+
actual class SecureStorage(private val context: Context) {
|
|
229
|
+
private val masterKey = MasterKey.Builder(context)
|
|
230
|
+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
231
|
+
.build()
|
|
232
|
+
|
|
233
|
+
private val prefs = EncryptedSharedPreferences.create(
|
|
234
|
+
context,
|
|
235
|
+
"secure_prefs",
|
|
236
|
+
masterKey,
|
|
237
|
+
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
238
|
+
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
actual suspend fun save(key: String, value: String) {
|
|
242
|
+
prefs.edit().putString(key, value).apply()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
actual suspend fun get(key: String): String? {
|
|
246
|
+
return prefs.getString(key, null)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
actual suspend fun delete(key: String) {
|
|
250
|
+
prefs.edit().remove(key).apply()
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// iosMain/storage/SecureStorage.ios.kt
|
|
255
|
+
import platform.Security.*
|
|
256
|
+
import platform.Foundation.*
|
|
257
|
+
|
|
258
|
+
actual class SecureStorage {
|
|
259
|
+
actual suspend fun save(key: String, value: String) {
|
|
260
|
+
val query = mapOf<Any?, Any?>(
|
|
261
|
+
kSecClass to kSecClassGenericPassword,
|
|
262
|
+
kSecAttrAccount to key,
|
|
263
|
+
kSecValueData to value.encodeToByteArray().toNSData()
|
|
264
|
+
)
|
|
265
|
+
SecItemDelete(query as CFDictionaryRef)
|
|
266
|
+
SecItemAdd(query as CFDictionaryRef, null)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
actual suspend fun get(key: String): String? {
|
|
270
|
+
val query = mapOf<Any?, Any?>(
|
|
271
|
+
kSecClass to kSecClassGenericPassword,
|
|
272
|
+
kSecAttrAccount to key,
|
|
273
|
+
kSecReturnData to kCFBooleanTrue,
|
|
274
|
+
kSecMatchLimit to kSecMatchLimitOne
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
val result = memScoped {
|
|
278
|
+
val resultPtr = alloc<CFTypeRefVar>()
|
|
279
|
+
val status = SecItemCopyMatching(query as CFDictionaryRef, resultPtr.ptr)
|
|
280
|
+
|
|
281
|
+
if (status == errSecSuccess) {
|
|
282
|
+
val data = resultPtr.value as NSData
|
|
283
|
+
NSString.create(data, NSUTF8StringEncoding) as String
|
|
284
|
+
} else null
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return result
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
actual suspend fun delete(key: String) {
|
|
291
|
+
val query = mapOf<Any?, Any?>(
|
|
292
|
+
kSecClass to kSecClassGenericPassword,
|
|
293
|
+
kSecAttrAccount to key
|
|
294
|
+
)
|
|
295
|
+
SecItemDelete(query as CFDictionaryRef)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## 🌐 Networking with Ktor
|
|
303
|
+
```kotlin
|
|
304
|
+
// commonMain/api/ApiClient.kt
|
|
305
|
+
|
|
306
|
+
import io.ktor.client.*
|
|
307
|
+
import io.ktor.client.call.*
|
|
308
|
+
import io.ktor.client.plugins.contentnegotiation.*
|
|
309
|
+
import io.ktor.client.request.*
|
|
310
|
+
import io.ktor.serialization.kotlinx.json.*
|
|
311
|
+
import kotlinx.serialization.json.Json
|
|
312
|
+
|
|
313
|
+
class ApiClient {
|
|
314
|
+
private val client = HttpClient {
|
|
315
|
+
install(ContentNegotiation) {
|
|
316
|
+
json(Json {
|
|
317
|
+
ignoreUnknownKeys = true
|
|
318
|
+
isLenient = true
|
|
319
|
+
})
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
suspend fun getUsers(): List<User> {
|
|
324
|
+
return client.get("https://api.example.com/users").body()
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
suspend fun createUser(user: User): User {
|
|
328
|
+
return client.post("https://api.example.com/users") {
|
|
329
|
+
setBody(user)
|
|
330
|
+
}.body()
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
@Serializable
|
|
335
|
+
data class User(
|
|
336
|
+
val id: String,
|
|
337
|
+
val name: String,
|
|
338
|
+
val email: String
|
|
339
|
+
)
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## 💾 Database with SQLDelight
|
|
345
|
+
```sql
|
|
346
|
+
-- shared/src/commonMain/sqldelight/com/myapp/User.sq
|
|
347
|
+
|
|
348
|
+
CREATE TABLE User (
|
|
349
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
350
|
+
name TEXT NOT NULL,
|
|
351
|
+
email TEXT NOT NULL,
|
|
352
|
+
createdAt INTEGER NOT NULL
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
selectAll:
|
|
356
|
+
SELECT * FROM User;
|
|
357
|
+
|
|
358
|
+
selectById:
|
|
359
|
+
SELECT * FROM User WHERE id = ?;
|
|
360
|
+
|
|
361
|
+
insert:
|
|
362
|
+
INSERT OR REPLACE INTO User(id, name, email, createdAt)
|
|
363
|
+
VALUES (?, ?, ?, ?);
|
|
364
|
+
|
|
365
|
+
deleteById:
|
|
366
|
+
DELETE FROM User WHERE id = ?;
|
|
367
|
+
```
|
|
368
|
+
```kotlin
|
|
369
|
+
// commonMain/database/UserDatabase.kt
|
|
370
|
+
|
|
371
|
+
class UserDatabase(driver: SqlDriver) {
|
|
372
|
+
private val database = AppDatabase(driver)
|
|
373
|
+
private val queries = database.userQueries
|
|
374
|
+
|
|
375
|
+
fun getAllUsers(): List<User> {
|
|
376
|
+
return queries.selectAll().executeAsList().map { it.toDomain() }
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
fun getUserById(id: String): User? {
|
|
380
|
+
return queries.selectById(id).executeAsOneOrNull()?.toDomain()
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
fun insertUser(user: User) {
|
|
384
|
+
queries.insert(
|
|
385
|
+
id = user.id,
|
|
386
|
+
name = user.name,
|
|
387
|
+
email = user.email,
|
|
388
|
+
createdAt = Clock.System.now().toEpochMilliseconds()
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
fun deleteUser(id: String) {
|
|
393
|
+
queries.deleteById(id)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Mapper
|
|
398
|
+
private fun com.myapp.User.toDomain(): User {
|
|
399
|
+
return User(
|
|
400
|
+
id = this.id,
|
|
401
|
+
name = this.name,
|
|
402
|
+
email = this.email
|
|
403
|
+
)
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Platform-Specific Drivers
|
|
408
|
+
```kotlin
|
|
409
|
+
// androidMain/database/DriverFactory.android.kt
|
|
410
|
+
import app.cash.sqldelight.db.SqlDriver
|
|
411
|
+
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
|
412
|
+
import com.myapp.database.AppDatabase
|
|
413
|
+
|
|
414
|
+
actual class DriverFactory(private val context: Context) {
|
|
415
|
+
actual fun createDriver(): SqlDriver {
|
|
416
|
+
return AndroidSqliteDriver(
|
|
417
|
+
AppDatabase.Schema,
|
|
418
|
+
context,
|
|
419
|
+
"app.db"
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// iosMain/database/DriverFactory.ios.kt
|
|
425
|
+
import app.cash.sqldelight.db.SqlDriver
|
|
426
|
+
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
|
427
|
+
import com.myapp.database.AppDatabase
|
|
428
|
+
|
|
429
|
+
actual class DriverFactory {
|
|
430
|
+
actual fun createDriver(): SqlDriver {
|
|
431
|
+
return NativeSqliteDriver(
|
|
432
|
+
AppDatabase.Schema,
|
|
433
|
+
"app.db"
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## 🎨 Compose Multiplatform (Optional UI Sharing)
|
|
442
|
+
|
|
443
|
+
### Setup
|
|
444
|
+
```kotlin
|
|
445
|
+
// shared/build.gradle.kts
|
|
446
|
+
|
|
447
|
+
kotlin {
|
|
448
|
+
androidTarget()
|
|
449
|
+
|
|
450
|
+
listOf(
|
|
451
|
+
iosX64(),
|
|
452
|
+
iosArm64(),
|
|
453
|
+
iosSimulatorArm64()
|
|
454
|
+
).forEach {
|
|
455
|
+
it.binaries.framework {
|
|
456
|
+
baseName = "shared"
|
|
457
|
+
isStatic = true
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
sourceSets {
|
|
462
|
+
val commonMain by getting {
|
|
463
|
+
dependencies {
|
|
464
|
+
implementation(compose.runtime)
|
|
465
|
+
implementation(compose.foundation)
|
|
466
|
+
implementation(compose.material3)
|
|
467
|
+
implementation(compose.ui)
|
|
468
|
+
implementation(compose.components.resources)
|
|
469
|
+
implementation(compose.components.uiToolingPreview)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
compose.experimental {
|
|
476
|
+
web.application {}
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Shared UI Example
|
|
481
|
+
```kotlin
|
|
482
|
+
// commonMain/ui/UserListScreen.kt
|
|
483
|
+
|
|
484
|
+
@Composable
|
|
485
|
+
fun UserListScreen(
|
|
486
|
+
viewModel: UserViewModel
|
|
487
|
+
) {
|
|
488
|
+
val users by viewModel.users.collectAsState()
|
|
489
|
+
val isLoading by viewModel.isLoading.collectAsState()
|
|
490
|
+
|
|
491
|
+
if (isLoading) {
|
|
492
|
+
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
493
|
+
CircularProgressIndicator()
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
LazyColumn {
|
|
497
|
+
items(users) { user ->
|
|
498
|
+
UserItem(user = user)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
@Composable
|
|
505
|
+
fun UserItem(user: User) {
|
|
506
|
+
Card(
|
|
507
|
+
modifier = Modifier
|
|
508
|
+
.fillMaxWidth()
|
|
509
|
+
.padding(16.dp)
|
|
510
|
+
) {
|
|
511
|
+
Column(Modifier.padding(16.dp)) {
|
|
512
|
+
Text(
|
|
513
|
+
text = user.name,
|
|
514
|
+
style = MaterialTheme.typography.titleMedium
|
|
515
|
+
)
|
|
516
|
+
Text(
|
|
517
|
+
text = user.email,
|
|
518
|
+
style = MaterialTheme.typography.bodyMedium
|
|
519
|
+
)
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## 🍎 iOS Integration
|
|
528
|
+
|
|
529
|
+
### Creating Framework for iOS
|
|
530
|
+
```kotlin
|
|
531
|
+
// shared/build.gradle.kts
|
|
532
|
+
|
|
533
|
+
kotlin {
|
|
534
|
+
listOf(
|
|
535
|
+
iosX64(),
|
|
536
|
+
iosArm64(),
|
|
537
|
+
iosSimulatorArm64()
|
|
538
|
+
).forEach { iosTarget ->
|
|
539
|
+
iosTarget.binaries.framework {
|
|
540
|
+
baseName = "shared"
|
|
541
|
+
isStatic = true // or false for dynamic
|
|
542
|
+
|
|
543
|
+
// Export dependencies for iOS
|
|
544
|
+
export("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Swift Integration
|
|
551
|
+
```swift
|
|
552
|
+
// iosApp/UserViewModel.swift
|
|
553
|
+
|
|
554
|
+
import shared
|
|
555
|
+
import Combine
|
|
556
|
+
|
|
557
|
+
@MainActor
|
|
558
|
+
class UserViewModel: ObservableObject {
|
|
559
|
+
@Published var users: [User] = []
|
|
560
|
+
@Published var isLoading = false
|
|
561
|
+
@Published var error: String?
|
|
562
|
+
|
|
563
|
+
private let repository = UserRepository()
|
|
564
|
+
|
|
565
|
+
func loadUsers() {
|
|
566
|
+
isLoading = true
|
|
567
|
+
|
|
568
|
+
repository.getUsers { result, error in
|
|
569
|
+
DispatchQueue.main.async {
|
|
570
|
+
self.isLoading = false
|
|
571
|
+
|
|
572
|
+
if let users = result {
|
|
573
|
+
self.users = users
|
|
574
|
+
} else if let error = error {
|
|
575
|
+
self.error = error.localizedDescription
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Handling Kotlin Coroutines in Swift
|
|
584
|
+
```kotlin
|
|
585
|
+
// shared/src/iosMain/kotlin/util/CoroutineHelper.kt
|
|
586
|
+
|
|
587
|
+
// Wrapper for iOS to convert suspend functions to callbacks
|
|
588
|
+
class IOSCoroutineHelper {
|
|
589
|
+
fun <T> execute(
|
|
590
|
+
block: suspend () -> T,
|
|
591
|
+
onSuccess: (T) -> Unit,
|
|
592
|
+
onError: (Throwable) -> Unit
|
|
593
|
+
) {
|
|
594
|
+
MainScope().launch {
|
|
595
|
+
try {
|
|
596
|
+
val result = block()
|
|
597
|
+
onSuccess(result)
|
|
598
|
+
} catch (e: Throwable) {
|
|
599
|
+
onError(e)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
```swift
|
|
606
|
+
// Usage in Swift
|
|
607
|
+
let helper = IOSCoroutineHelper()
|
|
608
|
+
|
|
609
|
+
helper.execute(
|
|
610
|
+
block: { try await repository.getUsers() },
|
|
611
|
+
onSuccess: { users in
|
|
612
|
+
self.users = users
|
|
613
|
+
},
|
|
614
|
+
onError: { error in
|
|
615
|
+
self.error = error.localizedDescription
|
|
616
|
+
}
|
|
617
|
+
)
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
## 🔄 State Management in KMP
|
|
623
|
+
|
|
624
|
+
### ViewModel Sharing (Optional)
|
|
625
|
+
```kotlin
|
|
626
|
+
// commonMain/viewmodel/UserViewModel.kt
|
|
627
|
+
|
|
628
|
+
class UserViewModel(
|
|
629
|
+
private val getUsersUseCase: GetUsersUseCase
|
|
630
|
+
) {
|
|
631
|
+
private val _state = MutableStateFlow<UiState>(UiState.Loading)
|
|
632
|
+
val state: StateFlow<UiState> = _state.asStateFlow()
|
|
633
|
+
|
|
634
|
+
fun loadUsers() {
|
|
635
|
+
viewModelScope.launch {
|
|
636
|
+
_state.value = UiState.Loading
|
|
637
|
+
try {
|
|
638
|
+
val users = getUsersUseCase()
|
|
639
|
+
_state.value = UiState.Success(users)
|
|
640
|
+
} catch (e: Exception) {
|
|
641
|
+
_state.value = UiState.Error(e.message ?: "Unknown error")
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
sealed class UiState {
|
|
647
|
+
object Loading : UiState()
|
|
648
|
+
data class Success(val users: List<User>) : UiState()
|
|
649
|
+
data class Error(val message: String) : UiState()
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## ⚡ Performance Considerations
|
|
657
|
+
|
|
658
|
+
### iOS Framework Size
|
|
659
|
+
```bash
|
|
660
|
+
# Check framework size
|
|
661
|
+
du -sh iosApp/Pods/shared/shared.framework
|
|
662
|
+
|
|
663
|
+
# Optimize:
|
|
664
|
+
# 1. Use static framework (smaller)
|
|
665
|
+
# 2. Enable code stripping
|
|
666
|
+
# 3. Use ProGuard/R8 for Android
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### Memory Management
|
|
670
|
+
```kotlin
|
|
671
|
+
// iOS: Be careful with cycles
|
|
672
|
+
class MyClass {
|
|
673
|
+
private var callback: (() -> Unit)? = null
|
|
674
|
+
|
|
675
|
+
fun setCallback(cb: @escaping () -> Void) {
|
|
676
|
+
// Weak reference to avoid retain cycles
|
|
677
|
+
callback = { [weak self] in
|
|
678
|
+
cb()
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
## 🚫 KMP Anti-Patterns
|
|
687
|
+
|
|
688
|
+
| ❌ NEVER | ✅ ALWAYS | Why |
|
|
689
|
+
|----------|----------|-----|
|
|
690
|
+
| Share UI code (without Compose MP) | Share logic only | Platform UI feels native |
|
|
691
|
+
| Use Android APIs in commonMain | Use expect/actual | Won't compile for iOS |
|
|
692
|
+
| Block main thread | Use coroutines | Freezing on iOS |
|
|
693
|
+
| Ignore iOS memory model | Understand freezing/thawing | Crashes on iOS |
|
|
694
|
+
| Skip platform testing | Test on both platforms | Subtle bugs per platform |
|
|
695
|
+
| Use mutable shared state carelessly | Immutable data or StateFlow | Thread safety issues |
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## 📋 KMP Checklist
|
|
700
|
+
|
|
701
|
+
### Before Starting KMP Project
|
|
702
|
+
|
|
703
|
+
- [ ] **Team has Kotlin expertise?**
|
|
704
|
+
- [ ] **Business logic complex enough to justify KMP?**
|
|
705
|
+
- [ ] **Both iOS and Android targets needed?**
|
|
706
|
+
- [ ] **UI will be platform-specific or Compose MP?**
|
|
707
|
+
- [ ] **CI/CD supports iOS + Android builds?**
|
|
708
|
+
|
|
709
|
+
### Project Setup
|
|
710
|
+
|
|
711
|
+
- [ ] **Gradle configured for multiplatform?**
|
|
712
|
+
- [ ] **expect/actual declarations planned?**
|
|
713
|
+
- [ ] **SQLDelight or other DB set up?**
|
|
714
|
+
- [ ] **Ktor configured with platform engines?**
|
|
715
|
+
- [ ] **iOS framework exported correctly?**
|
|
716
|
+
- [ ] **CocoaPods or SPM integration working?**
|
|
717
|
+
|
|
718
|
+
### Development
|
|
719
|
+
|
|
720
|
+
- [ ] **Shared code in commonMain only?**
|
|
721
|
+
- [ ] **Platform-specific code in androidMain/iosMain?**
|
|
722
|
+
- [ ] **Coroutines used correctly (MainScope for iOS)?**
|
|
723
|
+
- [ ] **Serialization working on both platforms?**
|
|
724
|
+
- [ ] **Database migrations planned?**
|
|
725
|
+
|
|
726
|
+
### iOS Integration
|
|
727
|
+
|
|
728
|
+
- [ ] **Framework builds successfully?**
|
|
729
|
+
- [ ] **Swift can import and use shared code?**
|
|
730
|
+
- [ ] **Memory leaks checked (Xcode Instruments)?**
|
|
731
|
+
- [ ] **Callback-based APIs for iOS (if needed)?**
|
|
732
|
+
- [ ] **iOS-specific permissions handled?**
|
|
733
|
+
|
|
734
|
+
---
|
|
735
|
+
|
|
736
|
+
## 🎯 Decision: KMP vs React Native vs Flutter
|
|
737
|
+
```
|
|
738
|
+
USE KMP IF:
|
|
739
|
+
├── Existing native iOS/Android apps
|
|
740
|
+
├── Team has strong Kotlin expertise
|
|
741
|
+
├── Need native UI feel on both platforms
|
|
742
|
+
├── Complex business logic to share
|
|
743
|
+
└── Performance critical (games, heavy computation)
|
|
744
|
+
|
|
745
|
+
USE REACT NATIVE IF:
|
|
746
|
+
├── Team has JavaScript/React expertise
|
|
747
|
+
├── Need OTA updates
|
|
748
|
+
├── Rapid iteration important
|
|
749
|
+
└── Large ecosystem of libraries needed
|
|
750
|
+
|
|
751
|
+
USE FLUTTER IF:
|
|
752
|
+
├── Need pixel-perfect custom UI
|
|
753
|
+
├── Team comfortable with Dart
|
|
754
|
+
├── Strong animation requirements
|
|
755
|
+
└── Greenfield project
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
---
|
|
759
|
+
|
|
760
|
+
## 📚 Resources
|
|
761
|
+
|
|
762
|
+
- [Official KMP Docs](https://kotlinlang.org/docs/multiplatform.html)
|
|
763
|
+
- [KMP Samples](https://github.com/Kotlin/kmm-samples)
|
|
764
|
+
- [Ktor Documentation](https://ktor.io/)
|
|
765
|
+
- [SQLDelight](https://cashapp.github.io/sqldelight/)
|
|
766
|
+
- [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/)
|
|
767
|
+
|
|
768
|
+
---
|
|
769
|
+
|
|
770
|
+
> **Remember:** KMP is about sharing logic, not UI (unless you choose Compose Multiplatform). Keep platform UI native for best user experience.
|