Debug Panel построена на подходе с использованием плагинов — каждая функциональность реализуется в виде отдельного модуля-плагина.
Базовые модули, на которых основана работа панели:
- panel-core — реализация панели, базовые классы системы плагинов и событийная модель.
- panel-no-op — пустые реализации публичных API для релизных сборок (исключает отладочный код из продакшена).
Создайте новый модуль в директории plugins/:
plugins/
└── plugin-your-feature/
Добавьте модуль по аналогии с существующими плагинами:
// Plugins
include(
":plugins:plugin-your-feature",
)Примените convention-плагин, который содержит всю необходимую конфигурацию (compileSdk, minSdk, explicitApi(), зависимость на panel-core и Compose):
plugins {
id("convention.debug.panel.plugin")
}
description = "Plugin description"
android {
namespace = "com.redmadrobot.debug.plugin.yourfeature"
}
dependencies {
// Только специфичные для плагина зависимости
}Класс плагина — точка входа, отвечающая за взаимодействие с DebugPanel.
Унаследуйтесь от Plugin() и реализуйте обязательные методы.
Подробнее о getPluginContainer() и PluginDependencyContainer — в разделе ниже.
public class YourPlugin(
/* аргументы для инициализации */
) : Plugin() {
override fun getName(): String = "YOUR PLUGIN"
override fun getPluginContainer(commonContainer: CommonContainer): PluginDependencyContainer {
return YourPluginContainer(commonContainer)
}
@Composable
override fun content() {
YourScreen()
}
}Если плагин поддерживает редактирование через экран настроек панели, реализуйте интерфейс EditablePlugin:
public class YourPlugin : Plugin(), EditablePlugin {
// ...
@Composable
override fun content() {
YourScreen(isEditMode = false)
}
@Composable
override fun settingsContent() {
YourScreen(isEditMode = true)
}
}UI плагина реализуется с помощью Composable-функций. Для инъекции ViewModel используется хелпер provideViewModel:
@Composable
internal fun YourScreen(
viewModel: YourViewModel = provideViewModel {
getPlugin<YourPlugin>()
.getContainer<YourPluginContainer>()
.createYourViewModel()
},
) {
val state by viewModel.state.collectAsState()
// UI
}В библиотеке не используются DI-фреймворки, чтобы не добавлять лишних зависимостей. Вместо этого применяется подход Service Locator.
Для этого создайте класс-контейнер, реализующий интерфейс PluginDependencyContainer, и инициализируйте в нём необходимые зависимости.
Context доступен через CommonContainer, который передаётся в метод getPluginContainer() при инициализации плагина.
internal class YourPluginContainer(
private val container: CommonContainer,
) : PluginDependencyContainer {
private val dataStore by lazy { YourDataStore(container.context) }
val repository by lazy { YourRepository(dataStore) }
fun createYourViewModel(): YourViewModel {
return YourViewModel(repository)
}
}Пример реализации: ServersPluginContainer
Класс плагина является точкой доступа к данным и зависимостям.
Для получения экземпляра плагина используйте getPlugin<YourPlugin>():
getPlugin<YourPlugin>()
.getContainer<YourPluginContainer>()Все модули используют explicitApi() — модификаторы видимости обязательны для всех объявлений.
Внутренние классы, не предназначенные для использования в клиентском приложении, должны иметь модификатор internal.
Для тестирования плагина:
- Подключите его как зависимость в модуль
sample:
debugImplementation(project(":plugins:plugin-your-feature"))- Инициализируйте плагин в классе
Appsample-приложения:
DebugPanel.initialize(
application = this,
plugins = listOf(
YourPlugin(/* ... */)
)
)- Запустите sample-проект.
Чтобы отладочный код не попадал в релизную сборку, для каждого плагина необходимо создать no-op реализацию в модуле panel-no-op.
Создайте пакет с публичными классами плагина, доступными пользователю, и предоставьте пустые реализации.
Важно: package должен совпадать с оригинальным пакетом вашего модуля.
В sample-приложении подключение выглядит так:
debugImplementation(project(":plugins:plugin-your-feature"))
releaseImplementation(project(":panel-no-op"))Подробнее о подходе: No-op versions for dev tools
Публикация новых плагинов проходит через создание Pull Request в ветку main.