Debugging doesn't have to be a headache.
Kick simplifies inspection and control across Android, iOS and desktop with a unified, modular toolkit built on Compose Multiplatform. Replace scattered utilities with a single customizable solution embedded right into your app—no more juggling separate tools, no more command-line headaches.
Effortlessly manage logs, network traffic, SQLite databases, file systems and multiplatform settings directly from within your application. Enable only what you need, easily extend functionality with custom modules and keep your app lean and efficient.
Less complexity, faster development, total visibility. That's Kick.
- Cross-platform, unified toolkit — inspect and debug Android, iOS and desktop apps with one seamless solution built on Compose Multiplatform
- Fully modular — activate only what you need, or extend effortlessly with custom modules
- Embedded viewer — inspect logs, network traffic, SQLite databases, file systems and multiplatform settings directly from within your app
- Easy shortcuts — launch inspection tools quickly via shortcuts with a single click
- Simple integration — just initialize with
Kick.init
Kick is initialized once with a platform context and a list of modules. Add every module dependency in shared/build.gradle.kts
and choose real or stub implementations using the isRelease
flag:
val isRelease = /* your logic to determine release vs. debug */
kotlin {
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "shared"
isStatic = true
export("ru.bartwell.kick:main-core:1.0.0")
if (isRelease) {
export("ru.bartwell.kick:main-runtime-stub:1.0.0")
} else {
export("ru.bartwell.kick:main-runtime:1.0.0")
}
}
}
sourceSets {
commonMain.dependencies {
implementation("ru.bartwell.kick:main-core:1.0.0")
if (isRelease) {
implementation("ru.bartwell.kick:main-runtime-stub:1.0.0")
implementation("ru.bartwell.kick:ktor3-stub:1.0.0")
implementation("ru.bartwell.kick:sqlite-runtime-stub:1.0.0")
implementation("ru.bartwell.kick:sqlite-sqldelight-adapter-stub:1.0.0")
implementation("ru.bartwell.kick:sqlite-room-adapter-stub:1.0.0")
implementation("ru.bartwell.kick:logging-stub:1.0.0")
implementation("ru.bartwell.kick:multiplatform-settings-stub:1.0.0")
implementation("ru.bartwell.kick:file-explorer-stub:1.0.0")
} else {
implementation("ru.bartwell.kick:main-runtime:1.0.0")
implementation("ru.bartwell.kick:ktor3:1.0.0")
implementation("ru.bartwell.kick:sqlite-core:1.0.0")
implementation("ru.bartwell.kick:sqlite-runtime:1.0.0")
implementation("ru.bartwell.kick:sqlite-sqldelight-adapter:1.0.0")
implementation("ru.bartwell.kick:sqlite-room-adapter:1.0.0")
implementation("ru.bartwell.kick:logging:1.0.0")
implementation("ru.bartwell.kick:multiplatform-settings:1.0.0")
implementation("ru.bartwell.kick:file-explorer:1.0.0")
}
}
}
}
Note: stub modules provide no-op implementations instead of the full implementations so your release build stays lightweight.
Because many Android API calls require a Context, you need to wrap it using PlatformContext
. Here is a sample of initialization:
// val context = androidContext.toPlatformContext() // For Android
// val context = getPlatformContext() // For iOS and desktop
// val context = platformContext() // In Compose
// let context: PlatformContext = PlatformContextKt.getPlatformContext() // For Swift
Kick.init(context) {
module(SqliteModule(SqlDelightWrapper(sqlDelightDriver)))
module(SqliteModule(RoomWrapper(roomDatabase)))
module(LoggingModule(context))
module(Ktor3Module(context))
module(MultiplatformSettingsModule(listOf("MySettings1" to settings1, "MySettings2" to settings2)))
module(FileExplorerModule())
}

Monitor HTTP traffic performed with Ktor3. Just install the provided plugin:
val client = HttpClient(getEngineFactory()) {
install(KickKtor3Plugin)
}

View and edit SQLite databases. Use one of the provided adapters (or both if you are really using Room and SqlDelight in one application) for your favorite library.
// SqlDelight
module(SqliteModule(SqlDelightWrapper(sqlDelightDriver)))
// Room
module(SqliteModule(RoomWrapper(roomDatabase)))

Gather and review log messages right from the viewer. Add logs with a simple call:
Kick.log(LogLevel.INFO, "message")
You can also pipe existing Napier logs into Kick so you only configure logging only once:
Napier.base(object : Antilog() {
override fun performLog(priority: NapierLogLevel, tag: String?, throwable: Throwable?, message: String?) {
val level = when (priority) {
NapierLogLevel.VERBOSE -> LogLevel.VERBOSE
NapierLogLevel.DEBUG -> LogLevel.DEBUG
NapierLogLevel.INFO -> LogLevel.INFO
NapierLogLevel.WARNING -> LogLevel.WARNING
NapierLogLevel.ERROR -> LogLevel.ERROR
NapierLogLevel.ASSERT -> LogLevel.ASSERT
}
Kick.log(level, message)
}
})

Edit values stored with Multiplatform Settings. Register as many storages as you need and switch between them at runtime. Note: Multiplatform Settings doesn’t expose metadata about field types, so Kick can only display and edit values as plain text. When type information becomes available, it will be possible to implement type‑specific views — for example, a switch for Boolean or a numeric input for Int, Long, Double, or Float.

Create configuration options, such as an endpoint URL or debug flags, available during app runtime.
Provide a list of ConfigurationItem
objects to ConfigurationModule
. Each item defines its default ValueType
and can optionally include an editor UI:
ConfigurationModule(
context = context,
items = listOf(
ConfigurationItem(
name = "featureEnabled",
default = ValueType.Boolean(true),
),
ConfigurationItem(
name = "maxItems",
default = ValueType.Int(DEFAULT_MAX_ITEMS),
editor = Editor.InputNumber(min = 1.0, max = 10.0),
),
ConfigurationItem(
name = "endpoint",
default = ValueType.String("https://example.com"),
editor = Editor.InputString(singleLine = true),
),
ConfigurationItem(
name = "list",
default = ValueType.String("Item 2"),
editor = Editor.List(
listOf(
ValueType.String("Item 1"),
ValueType.String("Item 2"),
ValueType.String("Item 3"),
)
),
),
)
)
Access these values anywhere using the convenient Kick.configuration.get*()
methods:
Kick.configuration.getBoolean("featureEnabled")
Kick.configuration.getInt("maxItems")
Kick.configuration.getString("endpoint")
Kick.configuration.getString("list")

Browse the file system directly within the viewer—handy for quick checks of generated files or cached data.
You don't need to add all the available modules. Just include the ones you need. Here only logging and network inspection are enabled:
val isRelease = /* your logic to determine release vs. debug */
if (isRelease) {
implementation("ru.bartwell.kick:logging-stub:1.0.0")
implementation("ru.bartwell.kick:ktor3-stub:1.0.0")
} else {
implementation("ru.bartwell.kick:logging:1.0.0")
implementation("ru.bartwell.kick:ktor3:1.0.0")
}
Kick.init(context) {
module(LoggingModule(context))
module(Ktor3Module(context))
}
Call Kick.launch(context)
whenever you want to open the viewer:
In Kotlin:
val context = platformContext()
Button(
onClick = { Kick.launch(context) },
content = { Text(text = "Kick") }
)
In Swift:
Button("Kick") {
KickKt.shared.launch(context: PlatformContextKt.getPlatformContext())
}
By default, Kick adds a shortcut to your app’s launcher icon (accessible via long-press). To disable it, pass enableShortcut = false
during initialization:
Kick.init(context) {
enableShortcut = false
// modules...
}
On iOS you need to configure your AppDelegate
or UISceneDelegate
as follows:
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
return ShortcutActionHandler.shared.getConfiguration(session: connectingSceneSession)
}
}
Contributions are welcome! Feel free to open issues or submit pull requests with improvements and new modules.
Copyright 2025 Artem Bazhanov
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Kick is distributed under the Apache License, Version 2.0.
Happy debugging! If you have any questions or need further assistance, feel free to open an issue.