Debug Panel is a Kotlin Multiplatform library built by Mirego that allows mobile developers to generate
boilerplate code to display a debug panel with different component types.
The main goal of this library is to have a class definition in your common code that specifies how the debug panel should be built. Using this definition, the library generates:
- a repository with typed getters
- a use case with a typed parameters function that creates a list of item view data
- property delegates that can be used directly in existing code to reduce the friction from reading debug values
The view data list created by the use case can be passed to a builtin view model that handles the user interactions. You have the choice to either use the default UI that comes with the library or to build your own.
The library is published to Mirego's public Maven repository, so make sure you have it included in your settings.gradle.kts dependencyResolutionManagement
block.
dependencyResolutionManagement {
repositories {
// ...
maven("https://s3.amazonaws.com/mirego-maven/public")
}
}
In your top-level build.gradle.kts file add the reference to the KSP plugin:
plugins {
// ...
id("com.google.devtools.ksp") version "1.9.21-1.0.15" apply false
}
In your common module's build.gradle.kts file add the reference to the KSP plugin:
plugins {
// ...
id("com.google.devtools.ksp")
}
Also add the core
and annotations
dependencies along with the KSP generated source directory:
val commonMain by getting {
dependencies {
// ...
api("com.mirego.trikot:viewmodels-declarative-flow:x.y.z")
api("com.mirego.debugpanel:core:x.y.z")
implementation("com.mirego.debugpanel:annotations:x.y.z")
}
kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
}
Don't forget to export the core dependency to the iOS framework:
kotlin {
cocoapods {
framework {
// ...
export("com.mirego.trikot:viewmodels-declarative-flow:x.y.z")
export("com.mirego.debugpanel:core:x.y.z")
}
}
}
You also need to add the compiler's reference to the dependencies block:
dependencies {
add("kspCommonMainMetadata", "com.mirego.debugpanel:compiler:x.y.z")
}
The sample UI is resolved automatically from the common module since we include the library with the api()
function.
If you have some issues with a duplicated META-INF/versions/9/previous-compilation-data.bin
file during compilation, you can add it to the excluded resources inside the Android app's build.gradle.kts file:
android {
packaging {
resources {
excludes += listOf(
"META-INF/versions/9/previous-compilation-data.bin"
)
}
}
}
If you want to use the sample UI on iOS, include the pod in the application's Podfile:
pod 'Trikot/viewmodels.declarative.SwiftUI.flow', :git => 'git@github.com:mirego/trikot.git', :tag => 'x.y.z', :inhibit_warnings => true
pod 'DebugPanel', :git => 'git@github.com:mirego/debug-panel.git', :tag => 'x.y.z', :inhibit_warnings => true
In your common's module, create a class with the @DebugPanel annotation. You can find the different components that are available in the table below.
@DebugPanel(prefix = "MyProject", packageName = "com.myproject.app.generated")
data class DebugPanelConfig(
val toggle: DebugPanelToggle,
val label: DebugPanelLabel,
val textField: DebugPanelTextField,
val button: DebugPanelButton,
val picker: DebugPanelPicker,
val datePicker: DebugPanelDatePicker,
val enumPicker: SomeEnum
)
Once the configuration is done, you can run the kspCommonMainMetadata
Gradle task to generate the specific files for your project.
You should now have MyProjectDebugPanelUseCase.kt
, MyProjectDebugPanelUseCaseImpl.kt
, MyProjectDebugPanelRepository.kt
and MyProjectDebugPanelRepositoryImpl.kt
available in your classpath.
All you need to do now is to instantiate the implementations:
private val repository: MyProjectDebugPanelRepository = MyProjectDebugPanelRepositoryImpl()
private val useCase: MyProjectDebugPanelUseCase = MyProjectDebugPanelUseCaseImpl(repository)
/* ... */
class ParentViewModelImpl(
coroutineScope: CoroutineScope,
useCase: MyProjectDebugPanelUseCase
) : ParentViewModel, VMDViewModelImpl(coroutineScope) {
override val debugPanel = DebugPanelViewModelImpl(
coroutineScope,
useCase,
useCase.createViewData( /* Configure the components here */)
)
}
You can control the visibility of each components in the componentsVisibility
parameter of the createViewData()
function.
Example:
useCase.createViewData(
/* ... */
componentsVisibility = flowOf(
MyProjectDebugPanelComponentsVisibility(
button1 = false,
button2 = true,
)
)
)
In this case button1
will be hidden and button2
will be visible.
Name | Persisted data type | Configuration |
---|---|---|
DebugPanelToggle | Boolean |
Initial Boolean value |
DebugPanelLabel | - | Flow<String> |
DebugPanelTextField | String |
Initial String value |
DebugPanelButton | - | Initial () -> Unit value |
DebugPanelPicker | String |
Initial String value representing the selected item identifier |
DebugPanelDatePicker | Long |
Initial Long value representing the epoch in milliseconds |
Enum | String |
Initial enum value |
The debug panel is configured using the @DebugPanel(val prefix: String, val packageName: String)
annotation.
- The
prefix
is included in the generated use case and repository classes. - The
packageName
is where the files will be output inside thegenerated
folder.
By default the values are saved in the settings using their field name as identifier. However this behaviour can be overridden using the @Identifier(val value: String)
annotation.
For exemple, this is useful in the case where you would want to replace an old debug panel with this one and keep the original keys.
Example:
@Identifier("PREVIEW_MODE")
val preview: DebugPanelToggle
By default the components are displayed beside a label with the field name as value. You can use the @DisplayName(val value: String)
annotation to give the components a more meaningful label.
Example:
@DisplayName("Preview Mode")
val preview: DebugPanelToggle
You can use the @DebugProperty(val name: String)
annotation to generate a component that is bound to a class property.
For example, you can have a repository with a String
or Flow<String>
property, and by putting the annotation on the field the library will generate a delegate property.
You can then expose this delegate field in the interface and the caller will either receive your internal value or the one from the debug panel (in the case where it is overridden).
Please note that this annotation can only be used with the types: String
, Boolean
, Enum
, Flow<String>
, Flow<Boolean>
and Flow<Enum>
.
Example:
Repository.kt
interface Repository {
val value: Flow<String>
}
RepositoryImpl.kt
class RepositoryImpl : Repository {
@Identifier("custom_value_identifier")
@DebugProperty("value")
val internalValue = flowOf("String value")
override val value by RepositoryImplValueDelegate
}
Caller:
val repository: Repository = RepositoryImpl()
repository.value.map {
println("Repository value: $it")
}
This will either print
Repository value: String value
or Repository value: Overridden value
depending if Overridden value
has been input inside the generated text field.
The generated repository comes with a resetSettings()
method that you can call in order to clear the persisted component values.
Please be aware that the debug panel view models are not bound to these values, so you will need to either exit the debug panel screen or kill the application to make sure the values are reset properly
(see RootViewModelImpl.kt
in the sample application folder).
The generated use case and repository implementations have the open
modifier, which means you can extend them to add more functionalities if you need.
If your project has a dependency injection library like Koin and you have your own extended classes, you can annotate them with either @Factory
or @Single
.
If you don't need to override these classes, you can just put them manually in the dependencies injection modules.
Debug Panel is © 2013-2023 Mirego and may be freely distributed under the New BSD license. See the LICENSE.md
file.
Mirego is a team of passionate people who believe that work is a place where you can innovate and have fun. We’re a team of talented people who imagine and build beautiful Web and mobile applications. We come together to share ideas and change the world.
We also love open-source software and we try to give back to the community as much as we can.