Dyno is a powerful Android debugging library that allows you to modify UI state parameters and trigger methods in real-time without rebuilding your app. Think of it as Unity Inspector for Android development.
- π Real-time parameter modification - Change variables instantly without rebuilding
- π― Annotation-based - Minimal boilerplate with simple annotations
- π Notification access - Quick access via persistent notification (like Chucker/Pluto)
- π¨ Beautiful UI - Modern Compose-based debug interface
- π± Type support - Boolean, Int, Long, Float, Double, String, Enum
- π·οΈ Grouping - Organize parameters by groups for better UX
- β‘ Method triggers - Execute methods from debug interface
- π StateFlow manipulation - Direct data class field editing with @DynoFlow
- π‘οΈ Debug-only - Automatically disabled in release builds
Add to your app-level build.gradle.kts:
dependencies {
implementation("com.ardayucesan.dyno:dyno-core:1.0.0")
ksp("com.ardayucesan.dyno:dyno-processor:1.0.0")
}
plugins {
id("com.google.devtools.ksp") version "2.0.21-1.0.28"
}In your Application or MainActivity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize Dyno (only active in debug builds)
Dyno.initialize(this, enableInDebug = BuildConfig.DEBUG)
}
}@DynoGroup(name = "Button Manager", description = "Manages UI button states")
class ButtonManager {
@DynoExpose(
name = "Has Active Trip",
group = "Trip Status",
description = "Whether user has an active trip"
)
var hasTrip: Boolean = false
@DynoExpose(
name = "Trip Status",
group = "Trip Status",
description = "Current trip status (0=None, 1=Active, 2=Paused)"
)
var tripStatus: Int = 0
@DynoTrigger(
name = "Update UI",
group = "Actions",
description = "Refresh button states"
)
fun updateButtonStates() {
// Your UI update logic
}
}class MainActivity : ComponentActivity() {
private val buttonManager = ButtonManager()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Register instance for debugging
Dyno.register(buttonManager)
}
override fun onDestroy() {
super.onDestroy()
Dyno.unregister(buttonManager)
}
}- Check your notification panel for "π§ Dyno Debug Active"
- Tap the notification to open the debug interface
- Or call
Dyno.launchDebugInterface(context)programmatically
Marks a class for Dyno monitoring:
@DynoGroup(
name = "Custom Name", // Display name (optional)
description = "Description", // Description text (optional)
enabled = true // Enable by default (optional)
)
class MyClass { }Exposes a field for runtime modification:
@DynoExpose(
name = "Display Name", // UI display name (optional)
group = "Group Name", // Group for organization (optional)
description = "Description", // Help text (optional)
min = 0.0, // Minimum value for numbers (optional)
max = 100.0, // Maximum value for numbers (optional)
step = 1.0 // Step size for sliders (optional)
)
var myParameter: Int = 50Marks a method to be triggerable from UI:
@DynoTrigger(
name = "Action Name", // UI display name (optional)
group = "Group Name", // Group for organization (optional)
description = "Description" // Help text (optional)
)
fun myAction() { }Enables direct manipulation of StateFlow data class fields:
@DynoFlow(
name = "User State", // UI display name (optional)
group = "User Management", // Group for organization (optional)
description = "Current user state", // Help text (optional)
fields = ["isLoggedIn", "userName", "userLevel"] // Fields to expose
)
private val _userStateFlow = MutableStateFlow(UserState())
val userStateFlow: StateFlow<UserState> = _userStateFlow.asStateFlow()Benefits of @DynoFlow:
- β No boilerplate - Eliminate separate override variables
- β Clean ViewModels - No debug clutter in production code
- β Real-time manipulation - Direct StateFlow field editing
- β Type-safe - Automatic field type detection and conversion
Boolean- Toggle switchInt- Number input fieldLong- Number input fieldFloat- Decimal input fieldDouble- Decimal input fieldString- Text input fieldEnum- Dropdown selection
// Initialize Dyno
Dyno.initialize(context, enableInDebug = true, autoShowNotification = true)
// Register/unregister instances
Dyno.register(instance)
Dyno.unregister(instance)
// Show/hide debug interface
Dyno.showDebugNotification(context)
Dyno.hideDebugNotification(context)
Dyno.launchDebugInterface(context)
// Check status
Dyno.isInitialized()
Dyno.isDebugMode()// Alternative registration methods
myObject.dynoRegister()
myObject.dynoUnregister()Dyno is built with a multi-module architecture:
- dyno-annotations - Annotation definitions
- dyno-processor - KSP annotation processor
- dyno-runtime - Core parameter management
- dyno-ui - Compose-based debug interface
- dyno-core - Main API and initialization
- sample-app - Example implementation
Before @DynoFlow, debugging StateFlow data class fields required ugly boilerplate:
// β UGLY: Separate override variables cluttering ViewModel
var bookingStatusOverride: Int = -1
var tripStatusOverride: Int = -1
var searchAgainOverride: Boolean? = null
// β UGLY: Manual override logic in getters
fun bookingStatus(): Int? = if (bookingStatusOverride != -1)
bookingStatusOverride else passengerInfoFlow.value?.bookingStatusWith @DynoFlow, achieve clean, direct StateFlow manipulation:
// β
CLEAN: Single annotation, no boilerplate
@field:DynoFlow(
name = "Passenger Info",
group = "Trip States",
description = "Debug passenger booking and trip status",
fields = ["hasBooking", "bookingStatus", "hasTrip", "tripStatus"]
)
private val _passengerInfoFlow = MutableStateFlow<PassengerInfoModel?>(null)
val passengerInfoFlow: StateFlow<PassengerInfoModel?> = _passengerInfoFlow.asStateFlow()
// β
CLEAN: No override variables needed!
// StateFlow manipulation happens automatically via Dyno UI- Annotation Processing - KSP processor detects @DynoFlow annotations
- Runtime Registration - Dyno registers StateFlow fields for manipulation
- UI Generation - Debug interface shows expandable cards with field controls
- Data Class Copying - Uses Kotlin reflection to create new instances with overrides
- StateFlow Update - Automatically updates StateFlow with new data class instance
- Must use
@field:DynoFlowprefix for backing field annotation - StateFlow must contain data class (not primitives)
- Specify field names in
fieldsarray parameter
Extend DynoApplication for automatic initialization:
class MyApplication : DynoApplication() {
override fun onCreate() {
super.onCreate() // Automatically initializes Dyno
// Your app initialization
}
}Use DynoUtils for programmatic access:
// Get current parameter value
val value = DynoUtils.getParameterValue("com.example.ButtonManager", "hasTrip")
// Set parameter value
DynoUtils.setParameterValue("com.example.ButtonManager", "hasTrip", true)
// Trigger method
DynoUtils.triggerMethod("com.example.ButtonManager", "updateButtonStates")Dyno includes consumer ProGuard rules, but if needed:
# Keep Dyno annotations
-keep @interface com.ardayucesan.dyno.annotations.** { *; }
-keepclassmembers class * {
@com.ardayucesan.dyno.annotations.DynoExpose *;
@com.ardayucesan.dyno.annotations.DynoTrigger *;
}
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by Unity Inspector
- Built with Jetpack Compose
- Uses KSP for annotation processing
Made with β€οΈ for Android developers who want to debug UI states efficiently