feat: add dynamic URL plugin, Supabase config, and PlatformBuildConfig#115
feat: add dynamic URL plugin, Supabase config, and PlatformBuildConfig#115therajanmaurya wants to merge 5 commits intoopenMF:devfrom
Conversation
Add reusable networking and UI components: Network: - DynamicUrlConfigProvider: Interface for runtime URL configuration - MultiUrlConfigProvider: Extended interface for multi-endpoint apps - DynamicBaseUrlPlugin: Ktor plugin for dynamic URL switching - DynamicLoggableHosts: Dynamic list for Ktor logging filter - RemoteConfigLoader: Generic interface for remote config loading - BaseRemoteConfigLoader: Abstract base with fallback support - RemoteConfigCredentials: Configuration holder for remote services UI: - detectMultiTapGesture: Detects N taps within timeout (for hidden features) - detectLongPressGesture: Long press detection - detectDoubleTapGesture: Double tap detection These components enable: - Runtime server switching without recreating HTTP clients - Multi-tenant app support with dynamic endpoints - Hidden developer features via gesture detection - Flexible remote configuration loading Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove abstract RemoteConfigLoader (too project-specific) - Add SupabaseConfigClient for generic Supabase integration - Add SupabaseCredentials interface for credentials management - Add Supabase postgrest dependency to core-base/network - Add core-base/common dependency for DataState SupabaseConfigClient provides: - Lazy Supabase client initialization - Generic fetchSingle<T> and fetchList<T> methods - Custom query support via fetchWithQuery<T> - Built-in error handling with DataState - Credential validation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds Supabase support and dynamic networking to the KMP project: a Gradle convention plugin that generates Supabase credentials from a secrets file, new Supabase client and dynamic base-URL abstractions/plugins, dependency additions, platform build config objects, and Compose gesture utilities. Changes
Sequence Diagram(s)mermaid mermaid Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ionPlugin - Remove fetchSingle, fetchList, fetchWithQuery from SupabaseConfigClient - Keep it simple: just provide client and postgrest access - Project-specific fetching logic belongs in the app - Add SupabaseConfigConventionPlugin for generating credentials - Reads from secrets/supabaseCredentialsFile.json - Generates SupabaseCredentials implementing the interface - Configurable package name via extension - Register plugin as org.convention.kmp.supabase.config Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace @composable modifier functions with Modifier.composed { ... } as recommended by Compose rules to avoid unnecessary recompositions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Fix all issues with AI agents
In `@build-logic/convention/src/main/kotlin/SupabaseConfigConventionPlugin.kt`:
- Around line 109-133: The generated Kotlin interpolates raw url and anonKey
into double-quoted string literals (in the outputFile.writeText block creating
object SupabaseCredentials), which can break the produced source if values
contain ", \, $, or control chars; add and call a helper like
escapeKotlinString(value: String): String that escapes backslashes, double
quotes, dollar signs and control/newline characters (or encodes them) and use
escapeKotlinString(url) and escapeKotlinString(anonKey) in the template
interpolation so the resulting Kotlin source is always syntactically valid.
- Around line 71-89: The task eagerly reads extension.packageName, generatedDir,
and secretsFile during registration (generateTask), which breaks lazy
configuration; instead make the extension expose a Property<String> for
packageName and wire task inputs/outputs to Providers so values are resolved at
execution time — replace direct reads of extension.packageName,
generatedDir.get(), and secretsFile.readText() with provider-backed values
(e.g., use extension.packageName.orElse(...).map { ... } and
project.providers.fileProperty()/map to compute packagePath/outputFile and use
inputs.file(secretsFile) or inputs.property("credentialsContent",
secretsFile.asFile.map { it.readText() }), or defer the reads into doLast) so
inputs.property and outputs.file receive provider-backed values and up-to-date
checks work correctly.
In `@cmp-android/dependencies/prodReleaseRuntimeClasspath.tree.txt`:
- Around line 2077-2105: The transitive supabase/postgrest dependency tree shows
ktor-client-core/content-negotiation/serialization at 3.3.3 while
postgrest-kt:3.1.1 and supabase-kt:3.1.1 target Ktor 3.0.3, so either upgrade
postgrest-kt/supabase-kt to a release built for Ktor 3.3.3 or force Ktor back to
3.0.3 with a Gradle dependency constraint/resolution so versions converge;
locate references to postgrest-kt, supabase-kt and the Ktor modules
(ktor-client-core, ktor-client-content-negotiation,
ktor-serialization-kotlinx-json) in your build.gradle(.kts) and update the
dependency version or add the constraint, then run the integration test suite to
verify runtime compatibility.
In
`@core-base/network/src/commonMain/kotlin/template/core/base/network/DynamicBaseUrlPlugin.kt`:
- Around line 99-125: The rebuildUrl function drops query params and fragment
from the parsed base URL (baseUrlParsed) — fix by merging
baseUrlParsed.parameters into the URLBuilder.parameters before appending
originalUrl.parameters (so base params are present but allow originalUrl
parameters to override by appending originals after), and set fragment to
originalUrl.fragment if present otherwise to baseUrlParsed.fragment; adjust
URLBuilder usage in rebuildUrl (encodedPath, parameters, fragment) accordingly
to preserve base URL query params and fragment.
- Around line 172-181: The DynamicLoggableHosts class currently calls
configProvider.getLoggableHosts() separately from size and get, causing TOCTOU
inconsistencies during iteration; fix it by changing DynamicLoggableHosts to
delegate to a snapshot-per-access model: implement it as an
AbstractCollection<String> and List<String>, add a private inline fun snapshot()
= configProvider.getLoggableHosts(), and override size, get(index), iterator(),
listIterator(), and listIterator(index) to all use snapshot() so a single
consistent list is used for each iteration or access; keep the class name
DynamicLoggableHosts and the configProvider reference to locate and replace the
existing implementation.
In
`@core-base/network/src/commonMain/kotlin/template/core/base/network/DynamicUrlConfigProvider.kt`:
- Line 8: Remove the duplicated word in the license header comment (the line
that currently reads "See See https://...") by deleting the extra "See" so it
reads "See https://..." in the file's top-of-file license/header comment; locate
the header comment block near the top of DynamicUrlConfigProvider.kt and correct
that single-word duplication.
In
`@core-base/network/src/commonMain/kotlin/template/core/base/network/SupabaseConfigClient.kt`:
- Line 8: The copyright header in SupabaseConfigClient.kt contains a duplicated
word "See See" at the start of the URL comment; remove the extra "See" so the
header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE" (i.e., replace
"See See" with a single "See").
In `@core-base/ui/src/commonMain/kotlin/template/core/base/ui/GestureDetector.kt`:
- Line 8: The file header in GestureDetector.kt contains a duplicated word "See"
in the license comment; open the top-of-file comment where the license URL is
referenced (near the GestureDetector declaration) and remove the extra "See" so
the header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE".
- Around line 69-89: The pointerInput(Unit) coroutine captures stale values
(onGestureDetected, tapCount, tapTimeoutMs) so updates on recomposition are
ignored; fix by using rememberUpdatedState for any callback and changing
parameters used inside the pointerInput scope to their current-state holders
(e.g., val currentOnGesture = rememberUpdatedState(onGestureDetected), val
currentTapCountTarget = rememberUpdatedState(tapCount), val currentTapTimeout =
rememberUpdatedState(tapTimeoutMs)) and then reference currentOnGesture.value(),
currentTapCountTarget.value, currentTapTimeout.value inside the
detectTapGestures block; apply the same pattern for detectLongPressGesture and
detectDoubleTapGesture by wrapping onLongPress and onDoubleTap with
rememberUpdatedState and using their .value inside the pointerInput coroutine.
In `@gradle/libs.versions.toml`:
- Around line 71-72: Update the Supabase Kotlin SDK entry by changing the
version string for the supabase dependency key from "3.1.1" to the latest stable
"3.3.0"; locate the supabase property in gradle/libs.versions.toml (the line
reading supabase = "3.1.1") and replace the value with "3.3.0" so the project
uses the current SDK.
🧹 Nitpick comments (7)
core-base/ui/src/commonMain/kotlin/template/core/base/ui/GestureDetector.kt (1)
69-69: Consider migrating fromModifier.composedtoModifier.Node.
Modifier.composedallocates a new composition for every element it's applied to, which makes it more expensive at scale. The Compose team recommendsModifier.Nodeas the modern, more performant alternative. Not urgent, but worth considering as a follow-up.Also applies to: 110-110, 136-136
core-base/network/src/commonMain/kotlin/template/core/base/network/DynamicUrlConfigProvider.kt (1)
60-67:UrlTypeenum is domain-specific for a generic/reusable plugin.The enum values
SELF_SERVICEandINTERBANKare specific to a financial services domain. For a generic KMP project template, consider making this extensible (e.g., aString-keyed map or a sealed interface that consumers can extend) so downstream projects aren't forced to use irrelevant enum values.core-base/network/src/commonMain/kotlin/template/core/base/network/DynamicBaseUrlPlugin.kt (1)
132-149: Config allows setting both providers simultaneously — consider validation.Both
configProviderandmultiConfigProvidercan be set, withmultiConfigProvidersilently winning. Consider logging a warning or throwing inprepare()if both are set, to avoid confusing misconfiguration.core-base/network/build.gradle.kts (1)
28-28: Consider whetherapiscope is appropriate forsupabase-postgrestin a base networking module.Using
apihere exposes the Supabase Postgrest dependency to every consumer ofcore-base:network. This adds Supabase (and its transitive dependencies including auth-kt, supabase-kt, kotlincrypto, atomicfu, etc.) to all modules — even those that don't use Supabase.Two alternatives to consider:
- Use
implementationif onlySupabaseConfigClientneeds it internally and consumers don't need direct access to Postgrest types.- Extract to a separate module (e.g.,
core-base:supabase) if Supabase is optional, keeping the base network module lean.Option 1: Change to implementation
- api(libs.supabase.postgrest) + implementation(libs.supabase.postgrest)core-base/network/src/commonMain/kotlin/template/core/base/network/SupabaseConfigClient.kt (1)
55-68: AccessingclientorpostgrestwhenisConfigured == falsewill throw.The
lazyblock callscreateSupabaseClientwith potentially empty/blank URL and key. If a consumer forgets theisConfiguredguard and accessespostgrestdirectly, this will crash at runtime.Consider either:
- Throwing a descriptive exception in the lazy block when credentials are invalid, so the error is clear.
- Making
client/postgrestnullable or returning aResulttype.Option: Add a guard with a clear error message
val client: SupabaseClient by lazy { + require(credentials.isConfigured) { + "Supabase credentials are not configured. " + + "Ensure secrets/supabaseCredentialsFile.json contains valid 'url' and 'anonKey' fields." + } createSupabaseClient( supabaseUrl = credentials.url, supabaseKey = credentials.anonKey,build-logic/convention/src/main/kotlin/SupabaseConfigConventionPlugin.kt (2)
138-144:supabase-postgrestdependency is also declared asapiincore-base/network/build.gradle.kts.Modules that depend on
core-base:networkwill already getsupabase-postgresttransitively. Adding it again here means modules applying this plugin will have it declared twice. Not harmful, but redundant if those modules already depend oncore-base:network.Consider whether both declarations are needed, or if one should be removed to keep the dependency graph cleaner.
160-168: Regex-based JSON parsing is pragmatic but fragile.The comment explains the rationale (avoiding a serialization dependency in build-logic). This is fine for Supabase URLs and base64 keys, but note that escaped quotes in values (e.g.,
\") would break the regex[^"]*. Consider adding a brief comment noting this limitation.Also, Gradle's build-logic classpath already has access to
org.jsonor Gson via transitive dependencies from the Android Gradle Plugin. Using one of those would be more robust without adding a new dependency.
| val generateTask = tasks.register("generateSupabaseConfig") { | ||
| // Determine package name from extension or derive from namespace | ||
| val packageName = extension.packageName | ||
| ?: project.findProperty("android.namespace")?.toString()?.let { "$it.config" } | ||
| ?: "${project.group}.config" | ||
|
|
||
| val packagePath = packageName.replace(".", "/") | ||
| val outputFile = generatedDir.get().asFile | ||
| .resolve("$packagePath/SupabaseCredentials.kt") | ||
|
|
||
| // Track file content as input property for proper up-to-date checking | ||
| val fileContent = if (secretsFile.exists() && secretsFile.length() > 0) { | ||
| secretsFile.readText() | ||
| } else { | ||
| "" | ||
| } | ||
| inputs.property("credentialsContent", fileContent) | ||
| inputs.property("packageName", packageName) | ||
| outputs.file(outputFile) |
There was a problem hiding this comment.
Extension properties are evaluated eagerly at configuration time — packageName set after plugin application will be ignored.
Lines 73-75 read extension.packageName during task registration (configuration phase). If the user configures the extension after applying the plugin (which is the standard Gradle pattern), packageName will still be null.
Similarly, the outputFile path (line 78-79) and secretsFile content (lines 82-86) are computed eagerly. This violates Gradle's configuration avoidance principle and breaks up-to-date checking when inputs change.
The idiomatic fix is to use Property<String> in the extension and providers in the task:
Suggested approach — use lazy evaluation
open class SupabaseConfigExtension {
- var packageName: String? = null
+ val packageName: Property<String> = objects.property(String::class.java)
}In the task registration, defer evaluation to doLast or use task inputs with providers:
val generateTask = tasks.register("generateSupabaseConfig") {
- val packageName = extension.packageName
- ?: project.findProperty("android.namespace")?.toString()?.let { "$it.config" }
- ?: "${project.group}.config"
- val packagePath = packageName.replace(".", "/")
- val outputFile = generatedDir.get().asFile
- .resolve("$packagePath/SupabaseCredentials.kt")
- val fileContent = if (secretsFile.exists() && secretsFile.length() > 0) {
- secretsFile.readText()
- } else {
- ""
- }
- inputs.property("credentialsContent", fileContent)
- inputs.property("packageName", packageName)
- outputs.file(outputFile)
+ val resolvedPackageName = extension.packageName.orElse(
+ provider {
+ project.findProperty("android.namespace")?.toString()?.let { "$it.config" }
+ ?: "${project.group}.config"
+ }
+ )
+ inputs.property("packageName", resolvedPackageName)
+ inputs.file(secretsFile).optional()
+ outputs.dir(generatedDir)
doLast {
- val currentContent = inputs.properties["credentialsContent"] as String
- val currentPackage = inputs.properties["packageName"] as String
+ val currentPackage = resolvedPackageName.get()
+ val currentContent = if (secretsFile.exists()) secretsFile.readText() else ""
// ... rest of doLast
}
}🤖 Prompt for AI Agents
In `@build-logic/convention/src/main/kotlin/SupabaseConfigConventionPlugin.kt`
around lines 71 - 89, The task eagerly reads extension.packageName,
generatedDir, and secretsFile during registration (generateTask), which breaks
lazy configuration; instead make the extension expose a Property<String> for
packageName and wire task inputs/outputs to Providers so values are resolved at
execution time — replace direct reads of extension.packageName,
generatedDir.get(), and secretsFile.readText() with provider-backed values
(e.g., use extension.packageName.orElse(...).map { ... } and
project.providers.fileProperty()/map to compute packagePath/outputFile and use
inputs.file(secretsFile) or inputs.property("credentialsContent",
secretsFile.asFile.map { it.readText() }), or defer the reads into doLast) so
inputs.property and outputs.file receive provider-backed values and up-to-date
checks work correctly.
| outputFile.writeText( | ||
| """ | ||
| |/* | ||
| | * Copyright 2025 Mifos Initiative | ||
| | * | ||
| | * This Source Code Form is subject to the terms of the Mozilla Public | ||
| | * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| | * file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
| | * | ||
| | * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE | ||
| | */ | ||
| |package $currentPackage | ||
| | | ||
| |import template.core.base.network.SupabaseCredentials as BaseSupabaseCredentials | ||
| | | ||
| |/** | ||
| | * Generated Supabase credentials from secrets/supabaseCredentialsFile.json | ||
| | * DO NOT EDIT - This file is generated by SupabaseConfigConventionPlugin | ||
| | */ | ||
| |object SupabaseCredentials : BaseSupabaseCredentials { | ||
| | override val url: String = "$url" | ||
| | override val anonKey: String = "$anonKey" | ||
| |} | ||
| """.trimMargin() | ||
| ) |
There was a problem hiding this comment.
Parsed url and anonKey are interpolated into generated Kotlin source without escaping — risk of broken output or code injection.
If the JSON values contain characters like ", \, or $, the generated Kotlin file will have syntax errors or unintended behavior. While Supabase URLs and keys are typically safe, this is a correctness gap for a reusable plugin.
Proposed fix: escape special characters before interpolation
+ private fun escapeKotlinString(value: String): String =
+ value.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\$", "\\\$")
+
// In the doLast block:
- | override val url: String = "$url"
- | override val anonKey: String = "$anonKey"
+ | override val url: String = "${escapeKotlinString(url)}"
+ | override val anonKey: String = "${escapeKotlinString(anonKey)}"🤖 Prompt for AI Agents
In `@build-logic/convention/src/main/kotlin/SupabaseConfigConventionPlugin.kt`
around lines 109 - 133, The generated Kotlin interpolates raw url and anonKey
into double-quoted string literals (in the outputFile.writeText block creating
object SupabaseCredentials), which can break the produced source if values
contain ", \, $, or control chars; add and call a helper like
escapeKotlinString(value: String): String that escapes backslashes, double
quotes, dollar signs and control/newline characters (or encodes them) and use
escapeKotlinString(url) and escapeKotlinString(anonKey) in the template
interpolation so the resulting Kotlin source is always syntactically valid.
| | | | +--- io.github.jan-tennert.supabase:postgrest-kt:3.1.1 | ||
| | | | | \--- io.github.jan-tennert.supabase:postgrest-kt-android:3.1.1 | ||
| | | | | +--- io.github.jan-tennert.supabase:auth-kt:3.1.1 | ||
| | | | | | \--- io.github.jan-tennert.supabase:auth-kt-android:3.1.1 | ||
| | | | | | +--- androidx.startup:startup-runtime:1.2.0 (*) | ||
| | | | | | +--- androidx.browser:browser:1.8.0 -> 1.9.0 (*) | ||
| | | | | | +--- io.github.jan-tennert.supabase:supabase-kt:3.1.1 | ||
| | | | | | | \--- io.github.jan-tennert.supabase:supabase-kt-android:3.1.1 | ||
| | | | | | | +--- androidx.lifecycle:lifecycle-process:2.8.7 -> 2.9.4 (*) | ||
| | | | | | | +--- org.jetbrains.kotlinx:kotlinx-datetime:0.6.1 -> 0.7.1 (*) | ||
| | | | | | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1 -> 1.10.2 (*) | ||
| | | | | | | +--- co.touchlab:kermit:2.0.5 -> 2.0.8 (*) | ||
| | | | | | | +--- io.ktor:ktor-client-core:3.0.3 -> 3.3.3 (*) | ||
| | | | | | | +--- io.ktor:ktor-client-content-negotiation:3.0.3 -> 3.3.3 (*) | ||
| | | | | | | +--- io.ktor:ktor-serialization-kotlinx-json:3.0.3 -> 3.3.3 (*) | ||
| | | | | | | +--- org.jetbrains.kotlinx:atomicfu:0.27.0 | ||
| | | | | | | | \--- org.jetbrains.kotlinx:atomicfu-jvm:0.27.0 | ||
| | | | | | | | \--- org.jetbrains.kotlin:kotlin-stdlib:{prefer 2.1.0} -> 2.2.21 (*) | ||
| | | | | | | \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.10 -> 2.2.21 (*) | ||
| | | | | | +--- com.squareup.okio:okio:3.10.2 -> 3.16.4 (*) | ||
| | | | | | +--- org.jetbrains.kotlin:kotlin-stdlib:2.1.10 -> 2.2.21 (*) | ||
| | | | | | +--- com.russhwolf:multiplatform-settings-no-arg:1.3.0 (*) | ||
| | | | | | +--- com.russhwolf:multiplatform-settings-coroutines:1.3.0 (*) | ||
| | | | | | \--- org.kotlincrypto:secure-random:0.3.2 | ||
| | | | | | \--- org.kotlincrypto:secure-random-jvm:0.3.2 | ||
| | | | | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -> 2.2.21 (*) | ||
| | | | | +--- org.jetbrains.kotlin:kotlin-reflect:2.1.10 | ||
| | | | | | \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.10 -> 2.2.21 (*) | ||
| | | | | \--- org.jetbrains.kotlin:kotlin-stdlib:2.1.10 -> 2.2.21 (*) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, verify the file exists and understand the repo structure
find . -name "build.gradle.kts" -o -name "build.gradle" -o -name "libs.versions.toml" | head -20Repository: openMF/kmp-project-template
Length of output: 744
🏁 Script executed:
# Look for the cmp-android module structure
fd -t d "cmp-android" | head -5Repository: openMF/kmp-project-template
Length of output: 83
🏁 Script executed:
# Check the dependency tree file itself
wc -l cmp-android/dependencies/prodReleaseRuntimeClasspath.tree.txt 2>/dev/null || echo "File path may vary"Repository: openMF/kmp-project-template
Length of output: 137
🏁 Script executed:
# Search for where these dependencies are declared
rg "postgrest-kt|supabase-kt" --type gradle --type toml -A 2 -B 2Repository: openMF/kmp-project-template
Length of output: 504
🏁 Script executed:
# Read the versions file to see actual version values
cat gradle/libs.versions.toml | head -100Repository: openMF/kmp-project-template
Length of output: 2205
🏁 Script executed:
# Find where Ktor, Kotlin, Coroutines versions are defined
rg "ktor|kotlin|coroutines|supabase" gradle/libs.versions.toml -iRepository: openMF/kmp-project-template
Length of output: 5989
🏁 Script executed:
# Check the cmp-android build file
cat cmp-android/build.gradle.ktsRepository: openMF/kmp-project-template
Length of output: 5137
🌐 Web query:
postgrest-kt 3.1.1 ktor 3.3.3 compatibility
💡 Result:
postgrest-kt:3.1.1 is not a clean match for io.ktor:ktor-* :3.3.3.
postgrest-kt-jvm:3.1.1is built against Kotlin stdlib 2.1.10 (andauth-kt-jvm:3.1.1). [1]auth-kt:3.1.1pulls Ktor server artifacts at 3.0.3 (ktor-server-core,ktor-server-cio). [2]Ktor 3.3.3updates Kotlin to 2.2.20. [3]
What this means
- If your app uses
ktor-* :3.3.3, Gradle will likely resolve Ktor to 3.3.3 whilepostgrest-kt:3.1.1expects 3.0.3-era Ktor (and older Kotlin). That combination can work sometimes, but it’s not guaranteed (possible binary/source incompatibilities and dependency convergence issues). [2][3]
Recommended options
- Upgrade
postgrest-ktto a newer version that aligns better with newer Ktor/Kotlin (preferred). (3.1.1 is from Feb 2025; newer lines exist.) [4] - If you must stay on
postgrest-kt:3.1.1, use Gradle dependency alignment (e.g., enforceio.ktor:ktor-bom:3.3.3) and run tests—treat it as an unsupported override.
Sources: [1] [2] [3] [4]
🌐 Web query:
supabase postgrest-kt 3.1.1 security vulnerabilities CVE
💡 Result:
No public CVE (or GitHub Security Advisory / GHSA) specifically targeting io.github.jan-tennert.supabase:postgrest-kt version 3.1.1 turned up in major public sources I checked (NVD + common package vulnerability aggregators). MavenRepository’s entry for postgrest-kt-jvm also shows no known/indexed vulnerabilities for 3.1.1 (released 2025‑02‑05). [1]
A CVE that sometimes gets cited in “Supabase + PostgREST” searches—CVE‑2024‑24213—is disputed and concerns a SQL-query endpoint in the Supabase dashboard/pg_meta context, not the Kotlin client library postgrest-kt. [2]
References
- [1] MavenRepository –
io.github.jan-tennert.supabase:postgrest-kt-jvm(shows 3.1.1 release date and no vulnerabilities listed). (mvnrepository.com) - [2] NVD – CVE‑2024‑24213 (disputed; scoped to Supabase dashboard/pg_meta query endpoint). (nvd.nist.gov)
Citations:
- 1: https://mvnrepository.com/artifact/io.github.jan-tennert.supabase/postgrest-kt-jvm?utm_source=openai
- 2: https://nvd.nist.gov/vuln/detail/CVE-2024-24213?utm_source=openai
🌐 Web query:
Jan-Tennert supabase 3.1.1 ktor client compatibility
💡 Result:
For Jan Tennert’s supabase-kt version 3.1.1, the published artifacts are built against Ktor 3.0.3 (for example, supabase-kt-linuxx64:3.1.1 depends on io.ktor:ktor-client-core-* / content-negotiation-* / serialization-kotlinx-json-* 3.0.3) [1].
So, for best Ktor client compatibility, use Ktor 3.0.3 for your client engine dependencies (e.g., ktor-client-okhttp, ktor-client-cio, ktor-client-darwin, etc.). The project also explicitly recommends using the same Ktor version as supabase-kt to avoid dependency/version mismatches [2].
Sources: [1] [2]
Resolve Ktor version compatibility with PostgREST dependency.
The dependency tree shows Ktor 3.3.3, but postgrest-kt:3.1.1 (and transitive supabase-kt:3.1.1) are built for Ktor 3.0.3. While Gradle will converge to 3.3.3, this mismatch is unsupported and risks compatibility issues at runtime. Either upgrade postgrest-kt to a version compatible with Ktor 3.3.3 (or constrain Ktor back to 3.0.3 if that's a requirement). Verify the resolution with integration tests.
No known CVEs exist for these library versions.
🤖 Prompt for AI Agents
In `@cmp-android/dependencies/prodReleaseRuntimeClasspath.tree.txt` around lines
2077 - 2105, The transitive supabase/postgrest dependency tree shows
ktor-client-core/content-negotiation/serialization at 3.3.3 while
postgrest-kt:3.1.1 and supabase-kt:3.1.1 target Ktor 3.0.3, so either upgrade
postgrest-kt/supabase-kt to a release built for Ktor 3.3.3 or force Ktor back to
3.0.3 with a Gradle dependency constraint/resolution so versions converge;
locate references to postgrest-kt, supabase-kt and the Ktor modules
(ktor-client-core, ktor-client-content-negotiation,
ktor-serialization-kotlinx-json) in your build.gradle(.kts) and update the
dependency version or add the constraint, then run the integration test suite to
verify runtime compatibility.
| private fun rebuildUrl(originalUrl: Url, baseUrl: String): Url { | ||
| val baseUrlParsed = Url(baseUrl) | ||
|
|
||
| return URLBuilder().apply { | ||
| protocol = baseUrlParsed.protocol | ||
| host = baseUrlParsed.host | ||
| port = baseUrlParsed.port | ||
|
|
||
| // Combine base path with original path | ||
| val basePath = baseUrlParsed.encodedPath.trimEnd('/') | ||
| val originalPath = originalUrl.encodedPath.trimStart('/') | ||
|
|
||
| encodedPath = if (originalPath.isNotEmpty()) { | ||
| "$basePath/$originalPath" | ||
| } else { | ||
| basePath | ||
| } | ||
|
|
||
| // Preserve query parameters | ||
| originalUrl.parameters.forEach { name, values -> | ||
| parameters.appendAll(name, values) | ||
| } | ||
|
|
||
| // Preserve fragment | ||
| fragment = originalUrl.fragment | ||
| }.build() | ||
| } |
There was a problem hiding this comment.
Query parameters from the base URL are silently dropped.
rebuildUrl extracts protocol, host, port, and encodedPath from the parsed base URL, but ignores any query parameters or fragment that might be present in baseUrl. If a consumer configures a base URL like https://api.com/v1?apiKey=xyz, the query param will be lost. Consider either merging base URL query params or documenting this limitation.
🤖 Prompt for AI Agents
In
`@core-base/network/src/commonMain/kotlin/template/core/base/network/DynamicBaseUrlPlugin.kt`
around lines 99 - 125, The rebuildUrl function drops query params and fragment
from the parsed base URL (baseUrlParsed) — fix by merging
baseUrlParsed.parameters into the URLBuilder.parameters before appending
originalUrl.parameters (so base params are present but allow originalUrl
parameters to override by appending originals after), and set fragment to
originalUrl.fragment if present otherwise to baseUrlParsed.fragment; adjust
URLBuilder usage in rebuildUrl (encodedPath, parameters, fragment) accordingly
to preserve base URL query params and fragment.
| class DynamicLoggableHosts( | ||
| private val configProvider: DynamicUrlConfigProvider, | ||
| ) : AbstractList<String>() { | ||
|
|
||
| override val size: Int | ||
| get() = configProvider.getLoggableHosts().size | ||
|
|
||
| override fun get(index: Int): String = | ||
| configProvider.getLoggableHosts()[index] | ||
| } |
There was a problem hiding this comment.
TOCTOU bug: size and get call getLoggableHosts() independently, risking inconsistency.
Each call to size and get(index) invokes configProvider.getLoggableHosts() separately. If the provider's list changes between these calls (e.g., during iteration), get(index) can throw IndexOutOfBoundsException or return stale data. Since this is designed for dynamic/runtime-changing hosts, this is a real risk.
Snapshot the list once per access to ensure consistency:
Proposed fix: snapshot the delegate list
class DynamicLoggableHosts(
private val configProvider: DynamicUrlConfigProvider,
) : AbstractList<String>() {
+ private val currentHosts: List<String>
+ get() = configProvider.getLoggableHosts()
+
override val size: Int
- get() = configProvider.getLoggableHosts().size
+ get() = currentHosts.size
override fun get(index: Int): String =
- configProvider.getLoggableHosts()[index]
+ currentHosts[index]
}Actually, the above still has the same issue. A proper fix would be:
class DynamicLoggableHosts(
private val configProvider: DynamicUrlConfigProvider,
-) : AbstractList<String>() {
-
- override val size: Int
- get() = configProvider.getLoggableHosts().size
-
- override fun get(index: Int): String =
- configProvider.getLoggableHosts()[index]
-}
+) : List<String> by object : AbstractList<String>() {
+ // This still won't fix it. See below for the right approach.
+}The simplest correct approach—delegate to a List implementation that snapshots on each iteration:
class DynamicLoggableHosts(
private val configProvider: DynamicUrlConfigProvider,
) : AbstractCollection<String>(), List<String> {
private inline fun snapshot() = configProvider.getLoggableHosts()
override val size: Int get() = snapshot().size
override fun get(index: Int): String = snapshot()[index]
override fun iterator(): Iterator<String> = snapshot().iterator()
override fun listIterator(): ListIterator<String> = snapshot().listIterator()
override fun listIterator(index: Int): ListIterator<String> = snapshot().listIterator(index)
}The key fix is overriding iterator() / listIterator() to snapshot once, so a single iteration pass is consistent. The size/get pair from AbstractList is still racy, but iterators (the common access pattern for Ktor's logging filter) will be safe.
🤖 Prompt for AI Agents
In
`@core-base/network/src/commonMain/kotlin/template/core/base/network/DynamicBaseUrlPlugin.kt`
around lines 172 - 181, The DynamicLoggableHosts class currently calls
configProvider.getLoggableHosts() separately from size and get, causing TOCTOU
inconsistencies during iteration; fix it by changing DynamicLoggableHosts to
delegate to a snapshot-per-access model: implement it as an
AbstractCollection<String> and List<String>, add a private inline fun snapshot()
= configProvider.getLoggableHosts(), and override size, get(index), iterator(),
listIterator(), and listIterator(index) to all use snapshot() so a single
consistent list is used for each iteration or access; keep the class name
DynamicLoggableHosts and the configProvider reference to locate and replace the
existing implementation.
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
| * | ||
| * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE |
There was a problem hiding this comment.
Minor typo: duplicated "See" in license header.
Line 8 reads See See https://... — one "See" should be removed.
🤖 Prompt for AI Agents
In
`@core-base/network/src/commonMain/kotlin/template/core/base/network/DynamicUrlConfigProvider.kt`
at line 8, Remove the duplicated word in the license header comment (the line
that currently reads "See See https://...") by deleting the extra "See" so it
reads "See https://..." in the file's top-of-file license/header comment; locate
the header comment block near the top of DynamicUrlConfigProvider.kt and correct
that single-word duplication.
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
| * | ||
| * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE |
There was a problem hiding this comment.
Typo: duplicate "See" in the copyright header.
- * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
+ * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE | |
| * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE |
🤖 Prompt for AI Agents
In
`@core-base/network/src/commonMain/kotlin/template/core/base/network/SupabaseConfigClient.kt`
at line 8, The copyright header in SupabaseConfigClient.kt contains a duplicated
word "See See" at the start of the URL comment; remove the extra "See" so the
header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE" (i.e., replace
"See See" with a single "See").
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
| * | ||
| * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE |
There was a problem hiding this comment.
Typo: duplicate "See" in license header.
- * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE
+ * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| * See See https://github.com/openMF/kmp-project-template/blob/main/LICENSE | |
| * See https://github.com/openMF/kmp-project-template/blob/main/LICENSE |
🤖 Prompt for AI Agents
In `@core-base/ui/src/commonMain/kotlin/template/core/base/ui/GestureDetector.kt`
at line 8, The file header in GestureDetector.kt contains a duplicated word
"See" in the license comment; open the top-of-file comment where the license URL
is referenced (near the GestureDetector declaration) and remove the extra "See"
so the header reads "See
https://github.com/openMF/kmp-project-template/blob/main/LICENSE".
| ): Modifier = composed { | ||
| var currentTapCount by remember { mutableIntStateOf(0) } | ||
| var lastTapTime by remember { mutableLongStateOf(0L) } | ||
|
|
||
| this.pointerInput(Unit) { | ||
| detectTapGestures( | ||
| onTap = { | ||
| val currentTime = Clock.System.now().toEpochMilliseconds() | ||
| if (currentTime - lastTapTime > tapTimeoutMs) { | ||
| currentTapCount = 0 | ||
| } | ||
| currentTapCount++ | ||
| lastTapTime = currentTime | ||
|
|
||
| if (currentTapCount >= tapCount) { | ||
| onGestureDetected() | ||
| currentTapCount = 0 | ||
| } | ||
| }, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Stale closure: pointerInput(Unit) won't pick up new callback or parameter values.
pointerInput(Unit) launches its gesture-detection coroutine once and never restarts it, even if onGestureDetected, tapCount, or tapTimeoutMs change on recomposition. This means callers who pass a new lambda (e.g., one that closes over updated state) will silently run the old one.
Use rememberUpdatedState for the callback (and parameters if they can change):
Proposed fix
): Modifier = composed {
+ val updatedOnGestureDetected by rememberUpdatedState(onGestureDetected)
+ val updatedTapCount by rememberUpdatedState(tapCount)
+ val updatedTapTimeoutMs by rememberUpdatedState(tapTimeoutMs)
var currentTapCount by remember { mutableIntStateOf(0) }
var lastTapTime by remember { mutableLongStateOf(0L) }
this.pointerInput(Unit) {
detectTapGestures(
onTap = {
val currentTime = Clock.System.now().toEpochMilliseconds()
- if (currentTime - lastTapTime > tapTimeoutMs) {
+ if (currentTime - lastTapTime > updatedTapTimeoutMs) {
currentTapCount = 0
}
currentTapCount++
lastTapTime = currentTime
- if (currentTapCount >= tapCount) {
- onGestureDetected()
+ if (currentTapCount >= updatedTapCount) {
+ updatedOnGestureDetected()
currentTapCount = 0
}
},
)
}
}The same pattern applies to detectLongPressGesture and detectDoubleTapGesture below — wrap onLongPress / onDoubleTap with rememberUpdatedState as well.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ): Modifier = composed { | |
| var currentTapCount by remember { mutableIntStateOf(0) } | |
| var lastTapTime by remember { mutableLongStateOf(0L) } | |
| this.pointerInput(Unit) { | |
| detectTapGestures( | |
| onTap = { | |
| val currentTime = Clock.System.now().toEpochMilliseconds() | |
| if (currentTime - lastTapTime > tapTimeoutMs) { | |
| currentTapCount = 0 | |
| } | |
| currentTapCount++ | |
| lastTapTime = currentTime | |
| if (currentTapCount >= tapCount) { | |
| onGestureDetected() | |
| currentTapCount = 0 | |
| } | |
| }, | |
| ) | |
| } | |
| ): Modifier = composed { | |
| val updatedOnGestureDetected by rememberUpdatedState(onGestureDetected) | |
| val updatedTapCount by rememberUpdatedState(tapCount) | |
| val updatedTapTimeoutMs by rememberUpdatedState(tapTimeoutMs) | |
| var currentTapCount by remember { mutableIntStateOf(0) } | |
| var lastTapTime by remember { mutableLongStateOf(0L) } | |
| this.pointerInput(Unit) { | |
| detectTapGestures( | |
| onTap = { | |
| val currentTime = Clock.System.now().toEpochMilliseconds() | |
| if (currentTime - lastTapTime > updatedTapTimeoutMs) { | |
| currentTapCount = 0 | |
| } | |
| currentTapCount++ | |
| lastTapTime = currentTime | |
| if (currentTapCount >= updatedTapCount) { | |
| updatedOnGestureDetected() | |
| currentTapCount = 0 | |
| } | |
| }, | |
| ) | |
| } |
🤖 Prompt for AI Agents
In `@core-base/ui/src/commonMain/kotlin/template/core/base/ui/GestureDetector.kt`
around lines 69 - 89, The pointerInput(Unit) coroutine captures stale values
(onGestureDetected, tapCount, tapTimeoutMs) so updates on recomposition are
ignored; fix by using rememberUpdatedState for any callback and changing
parameters used inside the pointerInput scope to their current-state holders
(e.g., val currentOnGesture = rememberUpdatedState(onGestureDetected), val
currentTapCountTarget = rememberUpdatedState(tapCount), val currentTapTimeout =
rememberUpdatedState(tapTimeoutMs)) and then reference currentOnGesture.value(),
currentTapCountTarget.value, currentTapTimeout.value inside the
detectTapGestures block; apply the same pattern for detectLongPressGesture and
detectDoubleTapGesture by wrapping onLongPress and onDoubleTap with
rememberUpdatedState and using their .value inside the pointerInput coroutine.
| # Supabase | ||
| supabase = "3.1.1" |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
supabase-kt latest version 2025 2026 kotlin sdk releases
💡 Result:
Latest supabase-kt version (as of Feb 10, 2026)
- Stable latest:
3.3.0(released Jan 5, 2026) (github.com) - Maven Central also shows
3.3.0published Jan 4, 2026 (date can differ slightly from GitHub release timezones) (mvnrepository.com)
Gradle coordinate (example):
io.github.jan-tennert.supabase:<module>:3.3.0 (github.com)
Notable 2025–2026 releases (from GitHub/Maven)
3.3.0— Jan 5, 2026 (stable) (github.com)3.3.0-rc-1— Dec 20, 2025 (pre-release) (github.com)3.3.0-beta-1— Nov 9, 2025 (pre-release) (github.com)3.2.6— Oct 30, 2025 (github.com)3.1.4— Apr 1, 2025 (shown on Maven Central) (mvnrepository.com)
Citations:
- 1: https://github.com/supabase-community/supabase-kt/releases
- 2: https://mvnrepository.com/artifact/io.github.jan-tennert.supabase/supabase-kt?utm_source=openai
- 3: https://github.com/supabase-community/supabase-kt
- 4: https://github.com/supabase-community/supabase-kt/releases
- 5: https://github.com/supabase-community/supabase-kt/releases
- 6: https://github.com/supabase-community/supabase-kt/releases?utm_source=openai
- 7: https://github.com/supabase-community/supabase-kt/releases?utm_source=openai
- 8: https://mvnrepository.com/artifact/io.github.jan-tennert.supabase/supabase-kt?utm_source=openai
Update Supabase Kotlin SDK to version 3.3.0 (latest stable).
Version 3.1.1 is outdated. The latest stable release is 3.3.0 (released Jan 5, 2026), with additional patch releases (3.1.4, 3.2.6) available between the current version and latest.
🤖 Prompt for AI Agents
In `@gradle/libs.versions.toml` around lines 71 - 72, Update the Supabase Kotlin
SDK entry by changing the version string for the supabase dependency key from
"3.1.1" to the latest stable "3.3.0"; locate the supabase property in
gradle/libs.versions.toml (the line reading supabase = "3.1.1") and replace the
value with "3.3.0" so the project uses the current SDK.
…ction Add expect/actual PlatformBuildConfig object that provides isDebug flag: - Android: checks ApplicationInfo.FLAG_DEBUGGABLE via Koin context - Non-Android: checks if assertions are enabled (disabled in release) This enables commonMain code to detect debug/release builds without platform-specific parameters. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
`@core-base/platform/src/androidMain/kotlin/template/core/base/platform/PlatformBuildConfig.android.kt`:
- Around line 19-24: PlatformBuildConfig as an object implements KoinComponent
and uses inject() which creates an implicit initialization-order dependency on
startKoin(); this can throw IllegalStateException if isDebug is accessed early.
Change PlatformBuildConfig to remove the Koin dependency by adding a manual
init(context: Context) function that stores a private nullable Context used by
the isDebug getter (and stop using KoinComponent/inject()), or alternatively
convert PlatformBuildConfig into a class instantiated after startKoin; ensure
references to PlatformBuildConfig.isDebug check/init the context or document
that startKoin() must run before first access.
In
`@core-base/platform/src/nonAndroidMain/kotlin/template/core/base/platform/PlatformBuildConfig.nonAndroid.kt`:
- Around line 21-27: The checkAssertionsEnabled() implementation using
assert(...) is not cross-platform and fails on Kotlin/Native and Kotlin/JS;
replace this trick with platform-specific detection: remove assert-based logic
in checkAssertionsEnabled() and implement per-target solutions—use
kotlin.native.Platform.isDebugBinary (via `@OptIn`(ExperimentalNativeApi::class))
for native/iOS, provide a JS-specific runtime/build-time flag or source-set
implementation for JS, and for JVM keep a simple check based on assertions if
desired; alternatively expose a single expected/actual isDebug or
checkAssertionsEnabled() API across source sets and implement it in each target
source set or via a Gradle-generated BuildConfig constant so all targets compile
and correctly detect debug/release.
| actual object PlatformBuildConfig : KoinComponent { | ||
| private val context: android.content.Context by inject() | ||
|
|
||
| actual val isDebug: Boolean | ||
| get() = (context.applicationInfo.flags and | ||
| android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0 |
There was a problem hiding this comment.
Koin dependency on a global object creates an implicit initialization order requirement.
PlatformBuildConfig is an object (singleton) that implements KoinComponent. Any access to isDebug before startKoin() completes (e.g., during Application.onCreate setup, content providers, or static initializers) will throw an IllegalStateException. Since this is a low-level utility, it may be accessed earlier than typical DI-wired components.
Consider either:
- Documenting the requirement that Koin must be initialized before first access, or
- Accepting
Contextvia a manualinit(context)method to decouple from DI timing.
🤖 Prompt for AI Agents
In
`@core-base/platform/src/androidMain/kotlin/template/core/base/platform/PlatformBuildConfig.android.kt`
around lines 19 - 24, PlatformBuildConfig as an object implements KoinComponent
and uses inject() which creates an implicit initialization-order dependency on
startKoin(); this can throw IllegalStateException if isDebug is accessed early.
Change PlatformBuildConfig to remove the Koin dependency by adding a manual
init(context: Context) function that stores a private nullable Context used by
the isDebug getter (and stop using KoinComponent/inject()), or alternatively
convert PlatformBuildConfig into a class instantiated after startKoin; ensure
references to PlatformBuildConfig.isDebug check/init the context or document
that startKoin() must run before first access.
| private fun checkAssertionsEnabled(): Boolean { | ||
| var assertionsEnabled = false | ||
| // This assignment will only happen if assertions are enabled | ||
| @Suppress("KotlinConstantConditions", "AssertionInFunctionCall") | ||
| assert(true.also { assertionsEnabled = true }) | ||
| return assertionsEnabled | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Kotlin Native assert function behavior release debug builds
💡 Result:
In Kotlin/Native, kotlin.assert(...) is disabled by default and will only throw AssertionError if you enable runtime assertions at compile time. The stdlib docs explicitly say that for Native assertions must be “enabled during compilation.” [1]
Debug vs Release
- Debug (
linkDebug*) vs Release (linkRelease*) mainly controls things like debug info (for example,-g/ DWARF symbols) and optimization levels—not assertions automatically. Debug binaries are typically produced bylinkDebug*tasks, but that alone doesn’t imply assertions are on. [2] - Assertions are controlled separately by a compiler flag.
How to enable assertions (Native)
Compile with the Kotlin/Native compiler option:
-enable-assertions(alias:-ea) — “Enable runtime assertions in the generated code.” [3]
In Gradle (Kotlin Multiplatform), this typically means adding -ea to the Native compilation’s freeCompilerArgs.
Summary: Kotlin/Native assert() behavior is not inherently “debug vs release”; it’s on only when you compile with -ea. [1][3]
References:
[1] Kotlin stdlib assert (Native behavior) [kotlinlang.org] (kotlinlang.org)
[2] Debug binaries via linkDebug* tasks (debug info behavior) [kotlinlang.org] (kotlinlang.org)
[3] Kotlin/Native compiler option -enable-assertions (-ea) [kotlinlang.org] (kotlinlang.org)
Citations:
- 1: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/assert.html?utm_source=openai
- 2: https://kotlinlang.org/docs/native-debugging.html?utm_source=openai
- 3: https://kotlinlang.org/docs/compiler-reference.html?utm_source=openai
🌐 Web query:
Kotlin/JS assert function behavior debug release
💡 Result:
kotlin.assert()is not part of Kotlin/JS stdlib, so there is no “debug vs release” runtime-assert toggle like JVM’s-ea. In the official API docs,assertis provided only for JVM and Native targets (no JS/Wasm). [1]- If you want assertions in Kotlin/JS tests, use
kotlin.testassertion functions (assertEquals,assertTrue, etc.). These are regular test checks and don’t depend on a “release/debug” switch. [2] - If your goal is debug-only checks in JS production bundles, you’ll need your own mechanism (for example a build-time constant/flag and dead-code elimination), because Kotlin/JS doesn’t offer a built-in “enable assertions” runtime switch for
assert(). (The Kotlin Power-assert plugin can improve assertion messages, but it doesn’t create a JS runtime enable/disable semantics by itself.) [3]
Sources:
[1] Kotlin stdlib assert API (platform tabs show only JVM/Native) https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/assert.html
[2] Kotlin kotlin-test overview (assertion functions for JS tests) https://kotlinlang.org/api/core/kotlin-test/index.html
[3] Kotlin Power-assert compiler plugin docs https://kotlinlang.org/docs/power-assert.html
🌐 Web query:
Kotlin Platform.isDebugBinary Native debug detection
💡 Result:
In Kotlin/Native you can detect whether the current binary was built as a debug binary via kotlin.native.Platform.isDebugBinary:
// nativeMain / iosMain
import kotlin.native.ExperimentalNativeApi
import kotlin.native.Platform
`@OptIn`(ExperimentalNativeApi::class)
val isDebug: Boolean = Platform.isDebugBinaryPlatform.isDebugBinaryis a Kotlin/Native-only API (@ExperimentalNativeApi) and istrueif the binary was compiled in debug mode. [1]- In Gradle, “debug binaries” typically come from
linkDebug*tasks (vslinkRelease*), which aligns with Kotlin/Native DEBUG/RELEASE build types. [2][3]
KMP pattern (commonMain):
// commonMain
expect val isDebug: Boolean// iosMain (or other native*)
actual val isDebug: Boolean = Platform.isDebugBinarySources: [1] [2] [3]
🏁 Script executed:
# Search for any existing debug detection patterns in the repo
rg "isDebugBinary|assert.*also" --type kotlin -A 3Repository: openMF/kmp-project-template
Length of output: 594
🏁 Script executed:
# Check the actual implementation context
cat -n core-base/platform/src/nonAndroidMain/kotlin/template/core/base/platform/PlatformBuildConfig.nonAndroid.kt | head -40Repository: openMF/kmp-project-template
Length of output: 1288
🏁 Script executed:
# Find all PlatformBuildConfig implementations
fd "PlatformBuildConfig" --type f -e ktRepository: openMF/kmp-project-template
Length of output: 370
🏁 Script executed:
# Check which source sets exist for the platform module
fd "src" core-base/platform --type dRepository: openMF/kmp-project-template
Length of output: 94
🏁 Script executed:
# Check build.gradle or similar to see target configurations
fd "build.gradle" core-base/platform -A 5Repository: openMF/kmp-project-template
Length of output: 301
🏁 Script executed:
# Find build.gradle files in core-base/platform
find core-base/platform -name "build.gradle*" -type fRepository: openMF/kmp-project-template
Length of output: 106
🏁 Script executed:
# Check the commonMain expect declaration
cat -n core-base/platform/src/commonMain/kotlin/template/core/base/platform/PlatformBuildConfig.ktRepository: openMF/kmp-project-template
Length of output: 878
🏁 Script executed:
# Check if this is part of a multiplatform build
find . -name "build.gradle.kts" -path "*/core-base/platform/*" -o -name "build.gradle.kts" -path "*/core-base/*" | head -5Repository: openMF/kmp-project-template
Length of output: 262
🏁 Script executed:
# Check the platform module's build.gradle.kts
cat -n core-base/platform/build.gradle.ktsRepository: openMF/kmp-project-template
Length of output: 2201
🏁 Script executed:
# Also check the androidMain implementation for reference
cat -n core-base/platform/src/androidMain/kotlin/template/core/base/platform/PlatformBuildConfig.android.ktRepository: openMF/kmp-project-template
Length of output: 1096
The assert() trick is not cross-platform compatible and won't work reliably on non-JVM targets.
On Kotlin/Native, assert() is disabled by default and only runs if the code is compiled with the -ea compiler flag—it does not automatically match debug vs. release builds. Use Platform.isDebugBinary instead, which correctly detects debug binaries compiled via linkDebug* tasks:
// iosMain / nativeMain
import kotlin.native.ExperimentalNativeApi
import kotlin.native.Platform
`@OptIn`(ExperimentalNativeApi::class)
actual val isDebug: Boolean = Platform.isDebugBinaryOn Kotlin/JS, assert() does not exist in the standard library, so this code will not compile for JS targets. You'll need platform-specific implementations for each target (Desktop, iOS, Web).
Consider splitting into separate source sets with appropriate APIs per target, or use a build-time constant generated via Gradle (e.g., a BuildConfig-style plugin that writes a const val).
🤖 Prompt for AI Agents
In
`@core-base/platform/src/nonAndroidMain/kotlin/template/core/base/platform/PlatformBuildConfig.nonAndroid.kt`
around lines 21 - 27, The checkAssertionsEnabled() implementation using
assert(...) is not cross-platform and fails on Kotlin/Native and Kotlin/JS;
replace this trick with platform-specific detection: remove assert-based logic
in checkAssertionsEnabled() and implement per-target solutions—use
kotlin.native.Platform.isDebugBinary (via `@OptIn`(ExperimentalNativeApi::class))
for native/iOS, provide a JS-specific runtime/build-time flag or source-set
implementation for JS, and for JVM keep a simple check based on assertions if
desired; alternatively expose a single expected/actual isDebug or
checkAssertionsEnabled() API across source sets and implement it in each target
source set or via a Gradle-generated BuildConfig constant so all targets compile
and correctly detect debug/release.
Summary
This PR adds a generic dynamic URL plugin and Supabase configuration client for runtime server configuration management.
Key Features
Modifier.composedNew Files
core-base/network
DynamicBaseUrlPlugin.kt- Ktor plugin for dynamic URL switchingSupabaseConfigClient.kt- Supabase client wrapper with Postgrest supportcore-base/platform
PlatformBuildConfig.kt- Cross-platform debug detection (expect/actual)ApplicationInfo.FLAG_DEBUGGABLEcore-base/ui
GestureDetector.kt- Multi-tap, long-press, double-tap gesture utilitiesbuild-logic/convention
SupabaseConfigConventionPlugin.kt- GeneratesSupabaseCredentialsobject from env varsUsage
Environment Variables for Supabase
Test Plan
Modifier.composed🤖 Generated with Claude Code