androjack-mcp 1.3.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/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +34 -0
- package/.github/pull_request_template.md +16 -0
- package/CONTRIBUTING.md +27 -0
- package/LICENSE +21 -0
- package/README.md +592 -0
- package/SECURITY.md +26 -0
- package/assets/AndroJack banner.png +0 -0
- package/assets/killer_argument.png +0 -0
- package/build/constants.js +412 -0
- package/build/http-server.js +163 -0
- package/build/http.js +151 -0
- package/build/index.js +553 -0
- package/build/install.js +379 -0
- package/build/logger.js +57 -0
- package/build/tools/api-level.js +170 -0
- package/build/tools/api36-compliance.js +282 -0
- package/build/tools/architecture.js +75 -0
- package/build/tools/build-publish.js +362 -0
- package/build/tools/component.js +90 -0
- package/build/tools/debugger.js +82 -0
- package/build/tools/gradle.js +234 -0
- package/build/tools/kmp.js +348 -0
- package/build/tools/kotlin-patterns.js +500 -0
- package/build/tools/large-screen.js +366 -0
- package/build/tools/m3-expressive.js +447 -0
- package/build/tools/navigation3.js +331 -0
- package/build/tools/ondevice-ai.js +283 -0
- package/build/tools/permissions.js +404 -0
- package/build/tools/play-policy.js +221 -0
- package/build/tools/scalability.js +621 -0
- package/build/tools/search.js +89 -0
- package/build/tools/testing.js +439 -0
- package/build/tools/wear.js +337 -0
- package/build/tools/xr.js +274 -0
- package/config/antigravity_mcp.json +32 -0
- package/config/claude_desktop_config.json +17 -0
- package/config/cursor_mcp.json +21 -0
- package/config/jetbrains_mcp.json +28 -0
- package/config/kiro_mcp.json +40 -0
- package/config/vscode_mcp.json +24 -0
- package/config/windsurf_mcp.json +18 -0
- package/package.json +51 -0
- package/src/constants.ts +436 -0
- package/src/http-server.ts +186 -0
- package/src/http.ts +190 -0
- package/src/index.ts +702 -0
- package/src/install.ts +441 -0
- package/src/logger.ts +67 -0
- package/src/tools/api-level.ts +198 -0
- package/src/tools/api36-compliance.ts +289 -0
- package/src/tools/architecture.ts +94 -0
- package/src/tools/build-publish.ts +379 -0
- package/src/tools/component.ts +106 -0
- package/src/tools/debugger.ts +111 -0
- package/src/tools/gradle.ts +288 -0
- package/src/tools/kmp.ts +352 -0
- package/src/tools/kotlin-patterns.ts +534 -0
- package/src/tools/large-screen.ts +391 -0
- package/src/tools/m3-expressive.ts +473 -0
- package/src/tools/navigation3.ts +338 -0
- package/src/tools/ondevice-ai.ts +287 -0
- package/src/tools/permissions.ts +445 -0
- package/src/tools/play-policy.ts +229 -0
- package/src/tools/scalability.ts +646 -0
- package/src/tools/search.ts +112 -0
- package/src/tools/testing.ts +460 -0
- package/src/tools/wear.ts +343 -0
- package/src/tools/xr.ts +278 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool 5 – gradle_dependency_checker
|
|
3
|
+
*
|
|
4
|
+
* Looks up the latest stable version of any Android/Kotlin/Jetpack library
|
|
5
|
+
* from Google Maven and Maven Central.
|
|
6
|
+
*
|
|
7
|
+
* Correctness guarantees:
|
|
8
|
+
* - BOM-managed libraries (Compose, Firebase) emit platform() wrapper
|
|
9
|
+
* - Plugin declarations emit plugins {} block, not implementation()
|
|
10
|
+
* - Falls back through: Google Maven → Maven Central → Jetpack releases page
|
|
11
|
+
*
|
|
12
|
+
* Source of truth: dl.google.com/dl/android/maven2 + search.maven.org
|
|
13
|
+
*/
|
|
14
|
+
import { secureFetchJson, secureFetch, extractPageText } from "../http.js";
|
|
15
|
+
import { MAVEN_SEARCH_URL, GOOGLE_MAVEN_URL } from "../constants.js";
|
|
16
|
+
const LIBRARY_CATALOGUE = {
|
|
17
|
+
// ── Compose ────────────────────────────────────────────────────────────────
|
|
18
|
+
"compose-bom": { groupId: "androidx.compose", artifactId: "compose-bom" },
|
|
19
|
+
"compose": { groupId: "androidx.compose.ui", artifactId: "ui", bomManaged: true },
|
|
20
|
+
"compose-ui": { groupId: "androidx.compose.ui", artifactId: "ui", bomManaged: true },
|
|
21
|
+
"compose-material3": { groupId: "androidx.compose.material3", artifactId: "material3", bomManaged: true },
|
|
22
|
+
"compose-foundation": { groupId: "androidx.compose.foundation", artifactId: "foundation", bomManaged: true },
|
|
23
|
+
"compose-runtime": { groupId: "androidx.compose.runtime", artifactId: "runtime", bomManaged: true },
|
|
24
|
+
"compose-animation": { groupId: "androidx.compose.animation", artifactId: "animation", bomManaged: true },
|
|
25
|
+
"material3": { groupId: "androidx.compose.material3", artifactId: "material3", bomManaged: true },
|
|
26
|
+
// ── Architecture ───────────────────────────────────────────────────────────
|
|
27
|
+
"lifecycle": { groupId: "androidx.lifecycle", artifactId: "lifecycle-runtime-ktx", releasePageSlug: "lifecycle" },
|
|
28
|
+
"viewmodel": { groupId: "androidx.lifecycle", artifactId: "lifecycle-viewmodel-ktx", releasePageSlug: "lifecycle" },
|
|
29
|
+
"lifecycle-compose": { groupId: "androidx.lifecycle", artifactId: "lifecycle-runtime-compose", releasePageSlug: "lifecycle" },
|
|
30
|
+
"navigation": { groupId: "androidx.navigation", artifactId: "navigation-compose", releasePageSlug: "navigation" },
|
|
31
|
+
"navigation-compose": { groupId: "androidx.navigation", artifactId: "navigation-compose", releasePageSlug: "navigation" },
|
|
32
|
+
"room": { groupId: "androidx.room", artifactId: "room-runtime", releasePageSlug: "room" },
|
|
33
|
+
"paging": { groupId: "androidx.paging", artifactId: "paging-runtime", releasePageSlug: "paging" },
|
|
34
|
+
"paging-compose": { groupId: "androidx.paging", artifactId: "paging-compose", releasePageSlug: "paging" },
|
|
35
|
+
"datastore": { groupId: "androidx.datastore", artifactId: "datastore-preferences", releasePageSlug: "datastore" },
|
|
36
|
+
"workmanager": { groupId: "androidx.work", artifactId: "work-runtime-ktx", releasePageSlug: "work" },
|
|
37
|
+
"hilt": { groupId: "com.google.dagger", artifactId: "hilt-android", releasePageSlug: "hilt" },
|
|
38
|
+
"hilt-compose": { groupId: "androidx.hilt", artifactId: "hilt-navigation-compose", releasePageSlug: "hilt" },
|
|
39
|
+
"hilt-navigation-compose": { groupId: "androidx.hilt", artifactId: "hilt-navigation-compose", releasePageSlug: "hilt" },
|
|
40
|
+
// ── Network ────────────────────────────────────────────────────────────────
|
|
41
|
+
"retrofit": { groupId: "com.squareup.retrofit2", artifactId: "retrofit" },
|
|
42
|
+
"retrofit-gson": { groupId: "com.squareup.retrofit2", artifactId: "converter-gson" },
|
|
43
|
+
"retrofit-moshi": { groupId: "com.squareup.retrofit2", artifactId: "converter-moshi" },
|
|
44
|
+
"okhttp": { groupId: "com.squareup.okhttp3", artifactId: "okhttp" },
|
|
45
|
+
"okhttp-logging": { groupId: "com.squareup.okhttp3", artifactId: "logging-interceptor" },
|
|
46
|
+
// Ktor — KMP HTTP client (replaces Retrofit in KMP projects)
|
|
47
|
+
// Note: ktor-client-android is the Android engine; use ktor-client-core in commonMain
|
|
48
|
+
"ktor": { groupId: "io.ktor", artifactId: "ktor-client-core" },
|
|
49
|
+
"ktor-core": { groupId: "io.ktor", artifactId: "ktor-client-core" },
|
|
50
|
+
"ktor-android": { groupId: "io.ktor", artifactId: "ktor-client-okhttp" },
|
|
51
|
+
"ktor-ios": { groupId: "io.ktor", artifactId: "ktor-client-darwin" },
|
|
52
|
+
"ktor-json": { groupId: "io.ktor", artifactId: "ktor-serialization-kotlinx-json" },
|
|
53
|
+
"ktor-negotiation": { groupId: "io.ktor", artifactId: "ktor-client-content-negotiation" },
|
|
54
|
+
// KMP — Kotlin Multiplatform core libraries
|
|
55
|
+
"kotlinx-serialization": { groupId: "org.jetbrains.kotlinx", artifactId: "kotlinx-serialization-json" },
|
|
56
|
+
"serialization": { groupId: "org.jetbrains.kotlinx", artifactId: "kotlinx-serialization-json" },
|
|
57
|
+
"kotlinx-datetime": { groupId: "org.jetbrains.kotlinx", artifactId: "kotlinx-datetime" },
|
|
58
|
+
"datetime": { groupId: "org.jetbrains.kotlinx", artifactId: "kotlinx-datetime" },
|
|
59
|
+
// Room KMP (stable since Room 2.7 — different from Android-only Room)
|
|
60
|
+
"room-kmp": { groupId: "androidx.room", artifactId: "room-runtime" },
|
|
61
|
+
"sqlite-bundled": { groupId: "androidx.sqlite", artifactId: "sqlite-bundled" },
|
|
62
|
+
// DataStore KMP (different from Android DataStore)
|
|
63
|
+
"datastore-kmp": { groupId: "androidx.datastore", artifactId: "datastore-preferences-core" },
|
|
64
|
+
// Koin — KMP-compatible DI (Hilt is Android-only, cannot be used in commonMain)
|
|
65
|
+
"koin": { groupId: "io.insert-koin", artifactId: "koin-core" },
|
|
66
|
+
"koin-android": { groupId: "io.insert-koin", artifactId: "koin-android" },
|
|
67
|
+
"koin-compose": { groupId: "io.insert-koin", artifactId: "koin-compose" },
|
|
68
|
+
// ── Image loading ──────────────────────────────────────────────────────────
|
|
69
|
+
// Coil 3 is the current major (io.coil-kt.coil3 group)
|
|
70
|
+
"coil": { groupId: "io.coil-kt.coil3", artifactId: "coil-compose" },
|
|
71
|
+
"coil3": { groupId: "io.coil-kt.coil3", artifactId: "coil-compose" },
|
|
72
|
+
"glide": { groupId: "com.github.bumptech.glide", artifactId: "glide" },
|
|
73
|
+
"glide-compose": { groupId: "com.github.bumptech.glide", artifactId: "compose" },
|
|
74
|
+
// ── Serialization ──────────────────────────────────────────────────────────
|
|
75
|
+
"gson": { groupId: "com.google.code.gson", artifactId: "gson" },
|
|
76
|
+
"moshi": { groupId: "com.squareup.moshi", artifactId: "moshi-kotlin" },
|
|
77
|
+
// Note: kotlinx-serialization is also listed in the KMP section above
|
|
78
|
+
// ── Kotlin ────────────────────────────────────────────────────────────────
|
|
79
|
+
"coroutines": { groupId: "org.jetbrains.kotlinx", artifactId: "kotlinx-coroutines-android" },
|
|
80
|
+
"kotlin-stdlib": { groupId: "org.jetbrains.kotlin", artifactId: "kotlin-stdlib" },
|
|
81
|
+
// ── Firebase ──────────────────────────────────────────────────────────────
|
|
82
|
+
"firebase-bom": { groupId: "com.google.firebase", artifactId: "firebase-bom" },
|
|
83
|
+
"firebase": { groupId: "com.google.firebase", artifactId: "firebase-bom" },
|
|
84
|
+
"firebase-analytics": { groupId: "com.google.firebase", artifactId: "firebase-analytics-ktx", bomManaged: true },
|
|
85
|
+
"firebase-auth": { groupId: "com.google.firebase", artifactId: "firebase-auth-ktx", bomManaged: true },
|
|
86
|
+
"firebase-firestore": { groupId: "com.google.firebase", artifactId: "firebase-firestore-ktx", bomManaged: true },
|
|
87
|
+
"firebase-crashlytics": { groupId: "com.google.firebase", artifactId: "firebase-crashlytics-ktx", bomManaged: true },
|
|
88
|
+
"firebase-messaging": { groupId: "com.google.firebase", artifactId: "firebase-messaging-ktx", bomManaged: true },
|
|
89
|
+
"firebase-storage": { groupId: "com.google.firebase", artifactId: "firebase-storage-ktx", bomManaged: true },
|
|
90
|
+
// ── Google Play Services ───────────────────────────────────────────────────
|
|
91
|
+
"play-services-auth": { groupId: "com.google.android.gms", artifactId: "play-services-auth" },
|
|
92
|
+
"play-services-maps": { groupId: "com.google.android.gms", artifactId: "play-services-maps" },
|
|
93
|
+
"credentials": { groupId: "androidx.credentials", artifactId: "credentials" },
|
|
94
|
+
"credentials-play": { groupId: "androidx.credentials", artifactId: "credentials-play-services-auth" },
|
|
95
|
+
// ── Paging / Data ─────────────────────────────────────────────────────────
|
|
96
|
+
"profileinstaller": { groupId: "androidx.profileinstaller", artifactId: "profileinstaller" },
|
|
97
|
+
"startup": { groupId: "androidx.startup", artifactId: "startup-runtime", releasePageSlug: "startup" },
|
|
98
|
+
"splashscreen": { groupId: "androidx.core", artifactId: "core-splashscreen", releasePageSlug: "core" },
|
|
99
|
+
"window": { groupId: "androidx.window", artifactId: "window", releasePageSlug: "window" },
|
|
100
|
+
"adaptive": { groupId: "androidx.compose.material3.adaptive", artifactId: "adaptive", releasePageSlug: "compose-material3-adaptive" },
|
|
101
|
+
"adaptive-compose": { groupId: "androidx.compose.material3.adaptive", artifactId: "adaptive-navigation" },
|
|
102
|
+
// ── Testing ───────────────────────────────────────────────────────────────
|
|
103
|
+
"mockk": { groupId: "io.mockk", artifactId: "mockk" },
|
|
104
|
+
"turbine": { groupId: "app.cash.turbine", artifactId: "turbine" },
|
|
105
|
+
"espresso": { groupId: "androidx.test.espresso", artifactId: "espresso-core" },
|
|
106
|
+
"compose-testing": { groupId: "androidx.compose.ui", artifactId: "ui-test-junit4", bomManaged: true },
|
|
107
|
+
// ── Plugins ───────────────────────────────────────────────────────────────
|
|
108
|
+
"agp": { groupId: "com.android.tools.build", artifactId: "gradle", isPlugin: true, pluginId: "com.android.application" },
|
|
109
|
+
"ksp": { groupId: "com.google.devtools.ksp", artifactId: "com.google.devtools.ksp.gradle.plugin", isPlugin: true, pluginId: "com.google.devtools.ksp" },
|
|
110
|
+
"kotlin-android": { groupId: "org.jetbrains.kotlin", artifactId: "kotlin-gradle-plugin", isPlugin: true, pluginId: "org.jetbrains.kotlin.android" },
|
|
111
|
+
};
|
|
112
|
+
// ── Maven fetchers ────────────────────────────────────────────────────────────
|
|
113
|
+
async function fromGoogleMaven(groupId, artifactId) {
|
|
114
|
+
const groupPath = groupId.replace(/\./g, "/");
|
|
115
|
+
const url = `${GOOGLE_MAVEN_URL}/${groupPath}/${artifactId}/maven-metadata.xml`;
|
|
116
|
+
try {
|
|
117
|
+
const xml = await secureFetch(url);
|
|
118
|
+
const release = xml.match(/<release>([^<]+)<\/release>/)?.[1];
|
|
119
|
+
const latest = xml.match(/<latest>([^<]+)<\/latest>/)?.[1];
|
|
120
|
+
return release ?? latest ?? null;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function fromMavenCentral(groupId, artifactId) {
|
|
127
|
+
try {
|
|
128
|
+
const q = encodeURIComponent(`g:"${groupId}" AND a:"${artifactId}"`);
|
|
129
|
+
const url = `${MAVEN_SEARCH_URL}?q=${q}&rows=1&wt=json`;
|
|
130
|
+
const data = await secureFetchJson(url);
|
|
131
|
+
return data?.response?.docs?.[0]?.latestVersion ?? null;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function fromJetpackReleasesPage(slug) {
|
|
138
|
+
const url = `https://developer.android.com/jetpack/androidx/releases/${slug}`;
|
|
139
|
+
try {
|
|
140
|
+
const html = await secureFetch(url);
|
|
141
|
+
return extractPageText(html, 1800);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return `Could not fetch release page. Check: https://developer.android.com/jetpack/androidx/versions`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// ── Output formatters ─────────────────────────────────────────────────────────
|
|
148
|
+
function formatPlugin(entry, version) {
|
|
149
|
+
const pluginId = entry.pluginId ?? `${entry.groupId}.${entry.artifactId}`;
|
|
150
|
+
return (`**Plugin id:** \`${pluginId}\`\n` +
|
|
151
|
+
`**Latest Stable:** \`${version}\`\n\n` +
|
|
152
|
+
`\`\`\`kotlin\n// libs.versions.toml\n[plugins]\n${entry.artifactId.split(".")[0]} = { id = "${pluginId}", version = "${version}" }\n\n` +
|
|
153
|
+
`// build.gradle.kts\nplugins {\n alias(libs.plugins.${entry.artifactId.split(".")[0]})\n}\n\`\`\``);
|
|
154
|
+
}
|
|
155
|
+
function formatBom(entry, version) {
|
|
156
|
+
const isFirebase = entry.groupId.startsWith("com.google.firebase");
|
|
157
|
+
const bomCoords = isFirebase
|
|
158
|
+
? `com.google.firebase:firebase-bom:${version}`
|
|
159
|
+
: `${entry.groupId}:${entry.artifactId}:${version}`;
|
|
160
|
+
return (`**BOM managed — use \`platform()\` wrapper**\n` +
|
|
161
|
+
`**BOM version:** \`${version}\`\n\n` +
|
|
162
|
+
`\`\`\`kotlin\ndependencies {\n implementation(platform("${bomCoords}"))\n implementation("${entry.groupId}:${entry.artifactId}") // no version — BOM pins it\n}\n\`\`\`\n\n` +
|
|
163
|
+
`> ✅ Import the BOM once; all matching artifacts pick up the pinned version automatically.`);
|
|
164
|
+
}
|
|
165
|
+
function formatLibrary(entry, version) {
|
|
166
|
+
const coords = `${entry.groupId}:${entry.artifactId}:${version}`;
|
|
167
|
+
return (`**Latest Stable:** \`${version}\`\n\n` +
|
|
168
|
+
`\`\`\`kotlin\n// Kotlin DSL\nimplementation("${coords}")\n\`\`\`\n\n` +
|
|
169
|
+
`\`\`\`groovy\n// Groovy DSL\nimplementation '${coords}'\n\`\`\`\n\n` +
|
|
170
|
+
`\`\`\`toml\n# libs.versions.toml\n[libraries]\n${entry.artifactId} = { group = "${entry.groupId}", name = "${entry.artifactId}", version = "${version}" }\n\`\`\``);
|
|
171
|
+
}
|
|
172
|
+
// ── Main handler ──────────────────────────────────────────────────────────────
|
|
173
|
+
export async function gradleDependencyChecker(libraryName) {
|
|
174
|
+
if (!libraryName || libraryName.trim().length < 2) {
|
|
175
|
+
return "ERROR: Library name must be at least 2 characters.";
|
|
176
|
+
}
|
|
177
|
+
const name = libraryName.trim().toLowerCase().replace(/\s+/g, "-").slice(0, 100);
|
|
178
|
+
const header = `## AndroJack Gradle Dependency Checker\n**Library:** \`${libraryName}\`\n\n`;
|
|
179
|
+
const entry = LIBRARY_CATALOGUE[name];
|
|
180
|
+
if (entry) {
|
|
181
|
+
// Try Google Maven first (authoritative for Jetpack + Firebase)
|
|
182
|
+
let version = await fromGoogleMaven(entry.groupId, entry.artifactId);
|
|
183
|
+
// Fallback: Maven Central (for third-party: Retrofit, OkHttp, Coil, etc.)
|
|
184
|
+
if (!version)
|
|
185
|
+
version = await fromMavenCentral(entry.groupId, entry.artifactId);
|
|
186
|
+
if (version) {
|
|
187
|
+
let body;
|
|
188
|
+
if (entry.isPlugin) {
|
|
189
|
+
body = formatPlugin(entry, version);
|
|
190
|
+
}
|
|
191
|
+
else if (entry.bomManaged && (name.endsWith("-bom") || name === "firebase" || name === "firebase-bom" || name === "compose-bom")) {
|
|
192
|
+
body = formatBom(entry, version);
|
|
193
|
+
}
|
|
194
|
+
else if (entry.bomManaged) {
|
|
195
|
+
// Individual BOM-managed artifact — show both BOM and direct approaches
|
|
196
|
+
body =
|
|
197
|
+
formatLibrary(entry, version) +
|
|
198
|
+
`\n\n> 💡 Prefer importing the BOM (\`compose-bom\` or \`firebase-bom\`) to keep all artifacts version-aligned.`;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
body = formatLibrary(entry, version);
|
|
202
|
+
}
|
|
203
|
+
const releaseUrl = entry.releasePageSlug
|
|
204
|
+
? `https://developer.android.com/jetpack/androidx/releases/${entry.releasePageSlug}`
|
|
205
|
+
: `https://search.maven.org/artifact/${entry.groupId}/${entry.artifactId}`;
|
|
206
|
+
return (header + body +
|
|
207
|
+
`\n\n**Official release notes:** ${releaseUrl}\n\n` +
|
|
208
|
+
`> ✅ GROUNDING GATE: Use version \`${version}\` as confirmed by authoritative Maven source.`);
|
|
209
|
+
}
|
|
210
|
+
// Version not found — serve release page content
|
|
211
|
+
if (entry.releasePageSlug) {
|
|
212
|
+
const text = await fromJetpackReleasesPage(entry.releasePageSlug);
|
|
213
|
+
return (header +
|
|
214
|
+
`**Version lookup inconclusive via automated search.**\n\n` +
|
|
215
|
+
`**Jetpack release page:**\n${text}\n\n` +
|
|
216
|
+
`> Check https://developer.android.com/jetpack/androidx/versions for current stable.`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Unknown library — try Maven Central by name
|
|
220
|
+
const mcResult = await fromMavenCentral(name, name);
|
|
221
|
+
if (mcResult) {
|
|
222
|
+
return (header +
|
|
223
|
+
`**Not in local catalogue — Maven Central result:**\n` +
|
|
224
|
+
formatLibrary({ groupId: "?", artifactId: name }, mcResult) +
|
|
225
|
+
`\n\n> ⚠️ Verify group ID and artifact ID at https://search.maven.org`);
|
|
226
|
+
}
|
|
227
|
+
return (header +
|
|
228
|
+
`**Library \`${libraryName}\` not found in local catalogue or Maven search.**\n\n` +
|
|
229
|
+
`Try searching manually:\n` +
|
|
230
|
+
`- https://search.maven.org/search?q=${encodeURIComponent(name)}\n` +
|
|
231
|
+
`- https://developer.android.com/jetpack/androidx/versions\n` +
|
|
232
|
+
`- https://firebase.google.com/docs/android/setup#available-libraries\n\n` +
|
|
233
|
+
`Common patterns: \`compose-bom\`, \`hilt\`, \`room\`, \`firebase-bom\`, \`retrofit\`, \`coil\`, \`credentials\``);
|
|
234
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// Tool 16: Kotlin Multiplatform (KMP) Guide
|
|
2
|
+
// 900+ new KMP libraries published in 2025. Room, DataStore, Ktor all have KMP variants.
|
|
3
|
+
// AI tools silently generate Android-only code for KMP projects — iOS build fails.
|
|
4
|
+
export async function androidKmpGuide(topic) {
|
|
5
|
+
const t = topic.toLowerCase().trim();
|
|
6
|
+
const overview = `
|
|
7
|
+
# Kotlin Multiplatform (KMP) — Official Reference (2025)
|
|
8
|
+
Source: https://kotlinlang.org/docs/multiplatform.html
|
|
9
|
+
|
|
10
|
+
## What KMP Is — And Is Not
|
|
11
|
+
|
|
12
|
+
KMP shares **business logic and data layer** (domain, data, networking, storage) across
|
|
13
|
+
Android and iOS while keeping **native UIs** on each platform. It is NOT Compose Multiplatform
|
|
14
|
+
(CMP) — CMP shares UI as well, using Compose for iOS. They are separate, both valid.
|
|
15
|
+
|
|
16
|
+
| Approach | What's Shared | UI |
|
|
17
|
+
|----------|--------------|-----|
|
|
18
|
+
| KMP only | Domain + Data layer | Native (SwiftUI on iOS, Compose on Android) |
|
|
19
|
+
| KMP + CMP | Domain + Data + UI | Compose on all platforms |
|
|
20
|
+
|
|
21
|
+
## Why AI Tools Get KMP Wrong
|
|
22
|
+
|
|
23
|
+
When you ask "add Room to my KMP project," AI tools generate Android-only Room code.
|
|
24
|
+
It compiles. Android works. iOS build fails. Silent correctness failure.
|
|
25
|
+
Every major Jetpack library now has a KMP variant — always use the KMP artifact in KMP projects.
|
|
26
|
+
|
|
27
|
+
## Project Structure
|
|
28
|
+
|
|
29
|
+
\`\`\`
|
|
30
|
+
shared/
|
|
31
|
+
src/
|
|
32
|
+
commonMain/kotlin/ ← shared code (use expect/actual for platform differences)
|
|
33
|
+
androidMain/kotlin/ ← Android-specific implementations
|
|
34
|
+
iosMain/kotlin/ ← iOS-specific implementations
|
|
35
|
+
androidApp/ ← Android Compose UI
|
|
36
|
+
iosApp/ ← SwiftUI (Xcode project)
|
|
37
|
+
\`\`\`
|
|
38
|
+
|
|
39
|
+
## Gradle Setup (Kotlin DSL)
|
|
40
|
+
|
|
41
|
+
\`\`\`kotlin
|
|
42
|
+
// shared/build.gradle.kts
|
|
43
|
+
plugins {
|
|
44
|
+
alias(libs.plugins.kotlinMultiplatform)
|
|
45
|
+
alias(libs.plugins.androidLibrary)
|
|
46
|
+
alias(libs.plugins.kotlinSerialization)
|
|
47
|
+
alias(libs.plugins.ksp) // For Room KMP
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
kotlin {
|
|
51
|
+
androidTarget {
|
|
52
|
+
compilations.all { kotlinOptions { jvmTarget = "11" } }
|
|
53
|
+
}
|
|
54
|
+
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
|
|
55
|
+
iosTarget.binaries.framework {
|
|
56
|
+
baseName = "Shared"
|
|
57
|
+
isStatic = true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
sourceSets {
|
|
62
|
+
commonMain.dependencies {
|
|
63
|
+
implementation(libs.kotlinx.coroutines.core)
|
|
64
|
+
implementation(libs.kotlinx.serialization.json)
|
|
65
|
+
implementation(libs.ktor.client.core) // Networking (NOT Retrofit — not KMP)
|
|
66
|
+
implementation(libs.ktor.client.content.negotiation)
|
|
67
|
+
implementation(libs.ktor.serialization.kotlinx.json)
|
|
68
|
+
implementation(libs.room.runtime) // Room KMP
|
|
69
|
+
implementation(libs.sqlite.bundled)
|
|
70
|
+
implementation(libs.datastore.preferences.core) // DataStore KMP
|
|
71
|
+
}
|
|
72
|
+
androidMain.dependencies {
|
|
73
|
+
implementation(libs.ktor.client.okhttp) // OkHttp engine on Android
|
|
74
|
+
implementation(libs.kotlinx.coroutines.android)
|
|
75
|
+
}
|
|
76
|
+
iosMain.dependencies {
|
|
77
|
+
implementation(libs.ktor.client.darwin) // Darwin engine on iOS
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
\`\`\`
|
|
82
|
+
|
|
83
|
+
Source: https://kotlinlang.org/docs/multiplatform-get-started.html
|
|
84
|
+
`;
|
|
85
|
+
const libraries = `
|
|
86
|
+
# KMP — Library Catalogue (2025)
|
|
87
|
+
Source: https://kotlinlang.org/docs/multiplatform-introduce-frameworks.html
|
|
88
|
+
|
|
89
|
+
## ⚠️ Android vs KMP Artifact Mapping — Where AI Tools Fail
|
|
90
|
+
|
|
91
|
+
| Function | Android-Only (❌ use in KMP) | KMP Equivalent (✅ use in KMP) |
|
|
92
|
+
|----------|---------------------------|-------------------------------|
|
|
93
|
+
| Networking | Retrofit + OkHttp | **Ktor** (ktor-client-core) |
|
|
94
|
+
| Database | Room (Android) | **Room KMP** (room-runtime, sqlite-bundled) |
|
|
95
|
+
| Preferences | DataStore (Android) | **DataStore KMP** (datastore-preferences-core) |
|
|
96
|
+
| JSON | Gson / Moshi | **kotlinx-serialization-json** |
|
|
97
|
+
| DI | Hilt (Android only) | **Koin** (koin-core) or manual |
|
|
98
|
+
| Image loading | Coil / Glide | **Coil3** (io.coil-kt.coil3 — KMP-ready) |
|
|
99
|
+
| Date/Time | java.time | **kotlinx-datetime** |
|
|
100
|
+
| Settings | SharedPreferences | DataStore KMP |
|
|
101
|
+
|
|
102
|
+
## libs.versions.toml — KMP Dependencies
|
|
103
|
+
|
|
104
|
+
\`\`\`toml
|
|
105
|
+
[versions]
|
|
106
|
+
kotlin = "2.1.0"
|
|
107
|
+
ktor = "3.1.0"
|
|
108
|
+
room = "2.7.0" # Room KMP stable since 2.7
|
|
109
|
+
kotlinxCoroutines = "1.10.1"
|
|
110
|
+
kotlinxSerialization = "1.8.0"
|
|
111
|
+
kotlinxDatetime = "0.6.2"
|
|
112
|
+
datastore = "1.1.2"
|
|
113
|
+
koin = "4.0.0"
|
|
114
|
+
coil3 = "3.1.0"
|
|
115
|
+
sqlite = "2.5.0"
|
|
116
|
+
|
|
117
|
+
[libraries]
|
|
118
|
+
# Ktor — Multiplatform HTTP client (replaces Retrofit in KMP)
|
|
119
|
+
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
|
|
120
|
+
ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" } # Android
|
|
121
|
+
ktor-client-darwin = { group = "io.ktor", name = "ktor-client-darwin", version.ref = "ktor" } # iOS
|
|
122
|
+
ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
|
|
123
|
+
ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
|
124
|
+
|
|
125
|
+
# Room KMP
|
|
126
|
+
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
|
|
127
|
+
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } # KSP
|
|
128
|
+
sqlite-bundled = { group = "androidx.sqlite", name = "sqlite-bundled", version.ref = "sqlite" }
|
|
129
|
+
|
|
130
|
+
# DataStore KMP
|
|
131
|
+
datastore-preferences-core = { group = "androidx.datastore", name = "datastore-preferences-core", version.ref = "datastore" }
|
|
132
|
+
|
|
133
|
+
# kotlinx
|
|
134
|
+
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
|
|
135
|
+
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
|
|
136
|
+
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
|
137
|
+
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
|
|
138
|
+
|
|
139
|
+
# Koin — KMP-compatible DI (Hilt is Android-only)
|
|
140
|
+
koin-core = { group = "io.insert-koin", name = "koin-core", version.ref = "koin" }
|
|
141
|
+
koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" }
|
|
142
|
+
koin-compose = { group = "io.insert-koin", name = "koin-compose", version.ref = "koin" }
|
|
143
|
+
|
|
144
|
+
# Coil3 — KMP-ready image loading
|
|
145
|
+
coil3-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil3" }
|
|
146
|
+
coil3-network-ktor = { group = "io.coil-kt.coil3", name = "coil-network-ktor3", version.ref = "coil3" }
|
|
147
|
+
\`\`\`
|
|
148
|
+
|
|
149
|
+
Source: https://kotlinlang.org/docs/multiplatform-introduce-frameworks.html
|
|
150
|
+
`;
|
|
151
|
+
const roomKmp = `
|
|
152
|
+
# KMP — Room KMP Setup (Stable since Room 2.7)
|
|
153
|
+
Source: https://developer.android.com/kotlin/multiplatform/room
|
|
154
|
+
|
|
155
|
+
## Room KMP Gradle Setup
|
|
156
|
+
|
|
157
|
+
\`\`\`kotlin
|
|
158
|
+
// shared/build.gradle.kts
|
|
159
|
+
plugins {
|
|
160
|
+
alias(libs.plugins.ksp)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
kotlin {
|
|
164
|
+
sourceSets {
|
|
165
|
+
commonMain.dependencies {
|
|
166
|
+
implementation(libs.room.runtime)
|
|
167
|
+
implementation(libs.sqlite.bundled)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// KSP for Room compiler — apply to each target
|
|
173
|
+
dependencies {
|
|
174
|
+
add("kspAndroid", libs.room.compiler)
|
|
175
|
+
add("kspIosX64", libs.room.compiler)
|
|
176
|
+
add("kspIosArm64", libs.room.compiler)
|
|
177
|
+
add("kspIosSimulatorArm64", libs.room.compiler)
|
|
178
|
+
}
|
|
179
|
+
\`\`\`
|
|
180
|
+
|
|
181
|
+
## Room KMP Database — In commonMain
|
|
182
|
+
|
|
183
|
+
\`\`\`kotlin
|
|
184
|
+
// shared/src/commonMain/kotlin/Database.kt
|
|
185
|
+
@Database(entities = [UserEntity::class], version = 1)
|
|
186
|
+
abstract class AppDatabase : RoomDatabase() {
|
|
187
|
+
abstract fun userDao(): UserDao
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@Dao
|
|
191
|
+
interface UserDao {
|
|
192
|
+
@Query("SELECT * FROM users") fun getAllUsers(): Flow<List<UserEntity>>
|
|
193
|
+
@Insert suspend fun insert(user: UserEntity)
|
|
194
|
+
@Delete suspend fun delete(user: UserEntity)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@Entity(tableName = "users")
|
|
198
|
+
data class UserEntity(
|
|
199
|
+
@PrimaryKey val id: String,
|
|
200
|
+
val name: String,
|
|
201
|
+
val email: String
|
|
202
|
+
)
|
|
203
|
+
\`\`\`
|
|
204
|
+
|
|
205
|
+
## Platform-Specific Database Builder
|
|
206
|
+
|
|
207
|
+
\`\`\`kotlin
|
|
208
|
+
// shared/src/androidMain/kotlin/DatabaseBuilder.kt
|
|
209
|
+
fun getDatabaseBuilder(ctx: Context): RoomDatabase.Builder<AppDatabase> {
|
|
210
|
+
val appContext = ctx.applicationContext
|
|
211
|
+
val dbFile = appContext.getDatabasePath("app.db")
|
|
212
|
+
return Room.databaseBuilder<AppDatabase>(appContext, dbFile.absolutePath)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// shared/src/iosMain/kotlin/DatabaseBuilder.kt
|
|
216
|
+
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
|
|
217
|
+
val dbFilePath = NSHomeDirectory() + "/app.db"
|
|
218
|
+
return Room.databaseBuilder<AppDatabase>(name = dbFilePath)
|
|
219
|
+
}
|
|
220
|
+
\`\`\`
|
|
221
|
+
|
|
222
|
+
Source: https://developer.android.com/kotlin/multiplatform/room
|
|
223
|
+
`;
|
|
224
|
+
const ktor = `
|
|
225
|
+
# KMP — Ktor HTTP Client (Replaces Retrofit in KMP)
|
|
226
|
+
Source: https://ktor.io/docs/client-create-multiplatform-application.html
|
|
227
|
+
|
|
228
|
+
## Why Retrofit Cannot Be Used in KMP
|
|
229
|
+
Retrofit depends on Java reflection and OkHttp — neither is available in iosMain.
|
|
230
|
+
Ktor is the official KMP HTTP client with platform-specific engines.
|
|
231
|
+
|
|
232
|
+
## Ktor API Client in commonMain
|
|
233
|
+
|
|
234
|
+
\`\`\`kotlin
|
|
235
|
+
// shared/src/commonMain/kotlin/ApiClient.kt
|
|
236
|
+
import io.ktor.client.*
|
|
237
|
+
import io.ktor.client.call.*
|
|
238
|
+
import io.ktor.client.plugins.contentnegotiation.*
|
|
239
|
+
import io.ktor.client.request.*
|
|
240
|
+
import io.ktor.serialization.kotlinx.json.*
|
|
241
|
+
import kotlinx.serialization.json.Json
|
|
242
|
+
|
|
243
|
+
class ApiClient {
|
|
244
|
+
private val client = HttpClient {
|
|
245
|
+
install(ContentNegotiation) {
|
|
246
|
+
json(Json { ignoreUnknownKeys = true; isLenient = true })
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
suspend fun getUser(userId: String): User =
|
|
251
|
+
client.get("https://api.example.com/users/$userId").body()
|
|
252
|
+
|
|
253
|
+
suspend fun createPost(post: CreatePostRequest): Post =
|
|
254
|
+
client.post("https://api.example.com/posts") {
|
|
255
|
+
setBody(post)
|
|
256
|
+
}.body()
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@Serializable
|
|
260
|
+
data class User(val id: String, val name: String, val email: String)
|
|
261
|
+
\`\`\`
|
|
262
|
+
|
|
263
|
+
## Repository Pattern with Ktor
|
|
264
|
+
|
|
265
|
+
\`\`\`kotlin
|
|
266
|
+
// commonMain — same pattern as Android MVVM, works on all platforms
|
|
267
|
+
interface UserRepository {
|
|
268
|
+
suspend fun getUser(id: String): Result<User>
|
|
269
|
+
fun observeUsers(): Flow<List<User>>
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
class UserRepositoryImpl(
|
|
273
|
+
private val apiClient: ApiClient,
|
|
274
|
+
private val userDao: UserDao
|
|
275
|
+
) : UserRepository {
|
|
276
|
+
override suspend fun getUser(id: String): Result<User> = runCatching {
|
|
277
|
+
val entity = userDao.getUser(id) ?: run {
|
|
278
|
+
val user = apiClient.getUser(id)
|
|
279
|
+
userDao.insert(user.toEntity())
|
|
280
|
+
user
|
|
281
|
+
}
|
|
282
|
+
entity.toDomain()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
override fun observeUsers(): Flow<List<User>> =
|
|
286
|
+
userDao.getAllUsers().map { entities -> entities.map { it.toDomain() } }
|
|
287
|
+
}
|
|
288
|
+
\`\`\`
|
|
289
|
+
|
|
290
|
+
Source: https://ktor.io/docs/client-create-multiplatform-application.html
|
|
291
|
+
`;
|
|
292
|
+
const expectActual = `
|
|
293
|
+
# KMP — expect/actual Pattern
|
|
294
|
+
Source: https://kotlinlang.org/docs/multiplatform-expect-actual.html
|
|
295
|
+
|
|
296
|
+
## When to Use expect/actual
|
|
297
|
+
|
|
298
|
+
Use expect/actual only for platform-specific capabilities with no KMP equivalent:
|
|
299
|
+
- Platform-specific APIs (GPS, biometrics, camera)
|
|
300
|
+
- Platform SDKs (Firebase iOS SDK vs Firebase Android SDK)
|
|
301
|
+
- Performance-critical native code
|
|
302
|
+
|
|
303
|
+
Do NOT use expect/actual for things that already have KMP libraries (networking, storage, etc.).
|
|
304
|
+
|
|
305
|
+
## expect/actual Example — Platform Info
|
|
306
|
+
|
|
307
|
+
\`\`\`kotlin
|
|
308
|
+
// commonMain
|
|
309
|
+
expect fun getPlatformName(): String
|
|
310
|
+
expect class PlatformContext
|
|
311
|
+
|
|
312
|
+
// androidMain
|
|
313
|
+
actual fun getPlatformName(): String = "Android \${Build.VERSION.RELEASE}"
|
|
314
|
+
actual typealias PlatformContext = Context
|
|
315
|
+
|
|
316
|
+
// iosMain
|
|
317
|
+
actual fun getPlatformName(): String = UIDevice.currentDevice.systemName()
|
|
318
|
+
actual class PlatformContext
|
|
319
|
+
\`\`\`
|
|
320
|
+
|
|
321
|
+
## expect/actual for Coroutines Dispatcher
|
|
322
|
+
|
|
323
|
+
\`\`\`kotlin
|
|
324
|
+
// commonMain
|
|
325
|
+
expect val backgroundDispatcher: CoroutineDispatcher
|
|
326
|
+
|
|
327
|
+
// androidMain
|
|
328
|
+
actual val backgroundDispatcher: CoroutineDispatcher = Dispatchers.IO
|
|
329
|
+
|
|
330
|
+
// iosMain
|
|
331
|
+
actual val backgroundDispatcher: CoroutineDispatcher = Dispatchers.Default
|
|
332
|
+
\`\`\`
|
|
333
|
+
|
|
334
|
+
Source: https://kotlinlang.org/docs/multiplatform-expect-actual.html
|
|
335
|
+
`;
|
|
336
|
+
if (t.includes("room") || t.includes("database") || t.includes("db"))
|
|
337
|
+
return roomKmp;
|
|
338
|
+
if (t.includes("ktor") || t.includes("network") || t.includes("http") || t.includes("retrofit"))
|
|
339
|
+
return ktor;
|
|
340
|
+
if (t.includes("expect") || t.includes("actual") || t.includes("platform"))
|
|
341
|
+
return expectActual;
|
|
342
|
+
if (t.includes("librar") || t.includes("depend") || t.includes("gradle") || t.includes("toml"))
|
|
343
|
+
return libraries;
|
|
344
|
+
return overview + "\n\n---\n\n" +
|
|
345
|
+
"**Query topics:** 'libraries' (KMP dependency catalogue), 'room' (Room KMP setup), " +
|
|
346
|
+
"'ktor' (networking — replaces Retrofit), 'expect actual' (platform-specific code)\n\n" +
|
|
347
|
+
"Source: https://kotlinlang.org/docs/multiplatform.html";
|
|
348
|
+
}
|