diff --git a/README.md b/README.md index b2880e41..dae728d4 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,21 @@ [![License](https://img.shields.io/github/license/RedMadRobot/debug-panel-android?style=flat-square)][license] [![Android](https://img.shields.io/badge/Android-3DDC84?style=flat-square&logo=android&logoColor=white)](#) -**[Changelog][changelog]** | **[Миграция на новые версии][migration-guide]** +**[Changelog][changelog]** | **[Миграция на новые версии][migration-guide]** | **[Документация по разработке плагинов][plugin-development-doc]** -Тебе надоело пересобирать приложение для того чтобы поменять сервер в настройках или переключить feature toggle? Эта библиотека разрабатывается с идеей решить эти и другие проблемы, и сделать процесс отладки приложения более удобным. +Библиотека избавляет от необходимости пересобирать приложение для смены сервера или переключения feature toggle. Цель проекта — упростить процесс отладки приложения. -В данный момент библиотека предоставляет следующий функционал: +Основные возможности: 1. **Добавление, редактирование и выбор сервера.** 2. **Управление feature-toggles и remote config на основе Konfeature.** 3. **Отображение информации о приложении.** -Библиотека разрабатывается используя подход работы с плагинами, когда каждый функционал подключается отдельным модулем в зависимостях. +Библиотека построена на подходе с использованием плагинов: каждая функциональность подключается отдельным модулем в зависимостях. ## Подключение библиотеки -Для работы с библиотекой необходимо: +Для работы с библиотекой необходимо выполнить следующие шаги: 1. Подключить `Core` модуль для работы самой панели: @@ -54,7 +54,7 @@ dependencies { ``` -3. Для того чтобы библиотека не попала в релизную сборку необходимо подключить `no-op` версию библиотеки +3. Для того, чтобы библиотека не попала в релизную сборку, необходимо подключить `no-op` версию библиотеки ```kotlin releaseImplementation("com.redmadrobot.debug:panel-no-op:${debug_panel_version}") @@ -82,7 +82,7 @@ class App : Application() { } ``` -Для того чтобы открыть DebugPanel, нужно вызвать в коде: +Для открытия DebugPanel необходимо вызвать: ```kotlin fun openDebugPanel() { @@ -90,7 +90,7 @@ fun openDebugPanel() { } ``` -Так же в панель можно войти через уведомление которое появляется при запуске приложения использующее библиотеку. Через это же уведомление можно перейти в ручную настройку панели. Для этого нужно нажать кнопку `SETTINGS` в раскрытом уведомлении. +Также панель доступна через уведомление, которое появляется при запуске приложения, использующего библиотеку. Через это уведомление можно перейти к ручной настройке панели, нажав кнопку `SETTINGS` в раскрытом уведомлении. ![Режим редактирования](assets/debug_notification.png) @@ -99,7 +99,7 @@ fun openDebugPanel() { ### ServersPlugin Используется для работы с тестовыми серверами -Можно задать список предустановленных серверов +Доступна возможность задать список предустановленных серверов ```kotlin ServersPlugin( @@ -113,7 +113,7 @@ ServersPlugin( ) ``` -И подписаться на событие смены сервера +Подписка на событие смены сервера ```kotlin DebugPanel.subscribeToEvents(lifecycleOwner = this) { event -> @@ -126,14 +126,14 @@ DebugPanel.subscribeToEvents(lifecycleOwner = this) { event -> } ``` -Для получения выбранного сервера или **default** сервера из кода: +Получение выбранного или сервера по умолчанию: ```kotlin val selectedServer = ServersPlugin.getSelectedServer() val defaultServer = ServersPlugin.getDefaultServer() ``` -Так же если вы используете `OkHttp` в своем сетевом стеке то можете использовать `DebugServerInterceptor` который будет автоматически подменять хост в запросах на выбранный вами. +При использовании `OkHttp` в сетевом стеке можно применить `DebugServerInterceptor`, который автоматически подменяет хост в запросах на выбранный сервер. ```kotlin OkHttpClient.Builder() @@ -141,7 +141,7 @@ OkHttpClient.Builder() .build() ``` -Если запросы должны еще как то модифицироваться, например добавляться Header'ы то это можно сделать используя метод `modifyRequest` +Если запросы требуют дополнительной модификации, например добавления заголовков, можно воспользоваться методом `modifyRequest` ```kotlin OkHttpClient.Builder() @@ -158,7 +158,7 @@ OkHttpClient.Builder() ) .build() ``` -Текущий выбранный сервер можно получить следующим образом +Получение текущего выбранного сервера ```kotlin val selectedServer = getPlugin().getSelectedServer() @@ -170,18 +170,18 @@ val selectedServer = getPlugin().getSelectedServer() В основе плагина лежит библиотека [Konfeature][konfeature], которая позволяет: -- отображать конфигурации feature, которые используются в konfeature -- видеть источник каждого элемента конфигурации (Default, Firebase, AppGallery и т.д.) -- переопределять значение элементов конфигурации с типом Boolean, String, Long, Double +- отображать конфигурации feature, используемые в Konfeature +- просматривать источник каждого элемента конфигурации (Default, Firebase, AppGallery и др.) +- переопределять значения элементов конфигурации с типами Boolean, String, Long, Double -Для подключения плагина, необходимо передать в него объект класса `KonfeatureDebugPanelInterceptor` и `Konfeature` +Для подключения плагина необходимо передать объект класса `KonfeatureDebugPanelInterceptor` и экземпляр `Konfeature` ```kotlin val debugPanelInterceptor = KonfeatureDebugPanelInterceptor(context) val konfeatureInstance = konfeature { if (isDebug) { - addIntercepot(debugPanelInterceptor) + addInterceptor(debugPanelInterceptor) } } @@ -191,17 +191,17 @@ KonfeaturePlugin( ) ``` -В builder konfeture можно настроить следующее: +В builder Konfeature доступны следующие настройки: -- добавить config конкретной фичи - `register(FeatureConfigN())` -- настроить работу с remote config через реализацию интерфейса `FeatureSource` - `addSource(featureSource)` -- настроить логирование - `setLogger(logger)` +- добавление конфигурации конкретной фичи — `register(FeatureConfigN())` +- настройка работы с remote config через реализацию интерфейса `FeatureSource` — `addSource(featureSource)` +- настройка логирования — `setLogger(logger)` ### AboutApp Plugin -Используется для отображения информации о приложении: версии, номера билда и других произвольных данных. +Предназначен для отображения информации о приложении: версии, номера сборки и других произвольных данных. -Для подключения плагина необходимо передать список `AboutAppInfo`. Требуется хотя бы один элемент: +Для подключения плагина необходимо передать список объектов `AboutAppInfo`, содержащий хотя бы один элемент: ```kotlin AboutAppPlugin( @@ -222,8 +222,9 @@ AboutAppPlugin( - `title` — название поля (например, «Версия») - `value` — значение поля (например, «1.0.0») -# Безопасность! -Для того чтобы тестовые данные не попали в релизные сборки рекомендуется не задавать их явно в Application классе, а использовать реализации DebugDataProvider, которые можно разнести по разным buildType. Для release версии следует сделать пустую реализацию. +# Безопасность + +Для предотвращения попадания тестовых данных в релизные сборки рекомендуется не задавать их явно в классе Application, а использовать реализации `DebugDataProvider`, которые можно разнести по разным `buildType`. Для release-версии следует создать пустую реализацию. **buildType** `debug` @@ -247,7 +248,7 @@ class DebugServersProvider : DebugDataProvider> { } } ``` -Добавление в плагин +Передача в плагин ```kotlin ServersPlugin( diff --git a/docs/plugin_development.md b/docs/plugin_development.md index f448f58e..c2268703 100644 --- a/docs/plugin_development.md +++ b/docs/plugin_development.md @@ -1,182 +1,199 @@ # Разработка новых плагинов -**[!]Важно.** -1. Библиотека находится в статусе разработки и миграции на более актуальные решения. -Актуальность данного документа стоит уточнить у холдера библиотеки. В данный момент это **r.choryev@redmadrobot.com** -2. В библиотеке млогут быть спорные решения, но она открыта для предложений. -3. В текущих плагинах, для работы со списками используется [Groupie](https://github.com/lisawray/groupie). -Т.к. эта библиотека требует использование `jcenter` и уже не кажется таким уж подходящим решением, поэтому она будет удаляться из библиотеки. - Поэтому это стоит учесть при разработке ваших новых плагинов и использовать какое-то другое решение. +## Общая структура +Debug Panel построена на подходе с использованием плагинов — каждая функциональность реализуется в виде отдельного модуля-плагина. -## Общая структура -Debug panel разрабатывается опираясь на подход разработки функционала отдельными плагинами. -В данный момент есть несколько модулей на которых основана работа самой панели и разработка и работа плагинов. +Базовые модули, на которых основана работа панели: -* **debug-panel-core** - Реализация самой панели и базовых классов для поддержки системы плагинов. -* **debug-panel-common** - Модуль содержащий общие библиотеки, классы и ресурсы переиспользуемые в плагинах. - Библиотеки пробрасываются как сквозные зависимости при помощи типа зависимости `api`. - Список предоставляемых библиотек можно посмотреть в файле [build.gradle](../debug-panel-common/build.gradle.kts) +* **panel-core** — реализация панели, базовые классы системы плагинов и событийная модель. +* **panel-no-op** — пустые реализации публичных API для релизных сборок (исключает отладочный код из продакшена). ## Создание нового плагина -Для добавления нового плагина необходимо сделать несколько шагов: -1. Создать в дирректории **plugins** новый модуль для реализации своего плагина. - -``` -plugins -| your-plugin -``` -2. Объявить новый модуль в файле **settings.gradle** по примеру уже существующих плагинов. - -``` -include ':your-plugin' -project(':your-plugin').projectDir = new File(rootDir, 'plugins/your-plugin') +### 1. Создать модуль + +Создайте новый модуль в директории `plugins/`: + +``` +plugins/ +└── plugin-your-feature/ ``` -3. Добавить в **build.gradle** файл вашего модуля следующие настройки: -```groovy -android { - /*.......*/ - - compileSdkVersion build_versions.compile_sdk +### 2. Зарегистрировать модуль в settings.gradle.kts - defaultConfig { - minSdkVersion build_versions.min_sdk - targetSdkVersion build_versions.target_sdk +Добавьте модуль по аналогии с существующими плагинами: - versionCode getVersionCodeFromProperties() - versionName getVersionNameFromProperties() - } +```kotlin +// Plugins +include( + ":plugins:plugin-servers", + ":plugins:plugin-konfeature", + ":plugins:plugin-about-app", + ":plugins:plugin-your-feature", +) +``` - /*.......*/ +### 3. Настроить build.gradle.kts +Примените convention-плагин, который содержит всю необходимую конфигурацию (compileSdk, minSdk, `explicitApi()`, зависимость на `panel-core` и Compose): - kotlinOptions { - freeCompilerArgs += "-Xexplicit-api=strict" - } +```kotlin +plugins { + id("convention.debug.panel.plugin") } -dependencies { - implementation( - project(path: ':debug-panel-core'), - project(path: ':debug-panel-common'), +description = "Plugin description" - deps.kotlin.stdlib - ) +android { + namespace = "com.redmadrobot.debug.plugin.yourfeature" } +dependencies { + // Только специфичные для плагина зависимости + implementation(androidx.core) +} ``` -**[!]Важно. Конфигурация будет меняться при дальнейшей миграции с Groovy на Kotlin** -4. Создать в своем модуле класс-плагин который и будет отвечать за взаимодействие с DebugPanel. - Для этого класс должен унаследоваться от класса `Plugin()` и реализовать необходимые методы. - В качестве аргументов класса можно передать необходимые для инициализации плагина данные.\ - **(О методе `getPluginContainer()` и `PluginDependencyContainer()` можно будет почитать ниже)** - +### 4. Создать класс плагина + +Класс плагина — точка входа, отвечающая за взаимодействие с DebugPanel. +Унаследуйтесь от `Plugin()` и реализуйте обязательные методы. +Подробнее о `getPluginContainer()` и `PluginDependencyContainer` — в разделе ниже. + ```kotlin public class YourPlugin( - /*some arguments*/ + /* аргументы для инициализации */ ) : Plugin() { - internal companion object { - const val NAME = "AWESOME PLUGIN" - } - - override fun getName(): String = NAME + override fun getName(): String = "YOUR PLUGIN" - /*Plugin dependency container initializing*/ override fun getPluginContainer(commonContainer: CommonContainer): PluginDependencyContainer { - return YourPluginContainer(sharedPreferences) + return YourPluginContainer(commonContainer) } - /*Plugin Fragment initializing*/ - override fun getFragment(): Fragment? { - return YourPluginFragment() + @Composable + override fun content() { + YourScreen() + } +} +``` + +Если плагин поддерживает редактирование через экран настроек панели, реализуйте интерфейс `EditablePlugin`: + +```kotlin +public class YourPlugin : Plugin(), EditablePlugin { + // ... + + @Composable + override fun content() { + YourScreen(isEditMode = false) } - /*Plugin Setting Fragment initializing.*/ - override fun getSettingFragment(): Fragment { //Нужно только если есть отдельный экран для настройки плагина. - return YourPluginSettingFragment() + @Composable + override fun settingsContent() { + YourScreen(isEditMode = true) } } ``` -5. Создать **Fragment** экрана плагина(если он нужен) и унаследовать его от `PluginFragment()`. -К фрагменту создать **ViewModel** и унаследовать от `PluginViewModel()`. - В этой связке (Fragment+ViewModel), реализовывать пользовательское взаимодействие пользователя с плагином. - -## PluginDependencyContainer -В библиотеке не используются библиотеки для реализации DI, т.к.: -1. Не хочется тащить их зависимости в библиотеку. -2. Библиотека не такая большая чтобы реализовывать полноценный DI. +### 5. Создать UI на Jetpack Compose -Вместо этого, в библиотеке используется подход с **Service Locator**. -Для этого необходимо создать свой класс-контейнер, унаследовать его от **PluginDependencyContainer** и внутри него инициировать необходимые зависимости. -Если вам для этого понадобится **Context**, его можно получить из **CommonContainer** который прилетает в качестве аргумента в методе `getPluginContainer()` при инициализации плагина. -Пример реализации можно [посмотреть тут](../plugins/accounts-plugin/src/main/kotlin/com/redmadrobot/account/plugin/AccountsPluginContainer.kt) +UI плагина реализуется с помощью Composable-функций. Для инъекции ViewModel используется хелпер `provideViewModel`: -## Работа с классом плагина +```kotlin +@Composable +internal fun YourScreen( + viewModel: YourViewModel = provideViewModel { + getPlugin() + .getContainer() + .createYourViewModel() + }, +) { + val state by viewModel.state.collectAsState() + // UI +} +``` + +## PluginDependencyContainer + +В библиотеке не используются DI-фреймворки, чтобы не добавлять лишних зависимостей. Вместо этого применяется подход **Service Locator**. -Класс-плагин, описание которого было в пункте **4**, является точкой инициализации вашего плагина. -Поэтому доступ к данным и различным экземплярам классов нужно реализовывать через него. -Чтобу получить доступ к самому плагину, нужно использовать метод `getPlugin()`. -Например для получения контейнера зависимостей плагина, нужно вызвать: +Для этого создайте класс-контейнер, реализующий интерфейс `PluginDependencyContainer`, и инициализируйте в нём необходимые зависимости. +`Context` доступен через `CommonContainer`, который передаётся в метод `getPluginContainer()` при инициализации плагина. ```kotlin -getPlugin() - .getContainer() +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) + } +} ``` -**Пример использования плагина для получения ViewModel во Fragment:** +Пример реализации: [ServersPluginContainer](../plugins/plugin-servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPluginContainer.kt) + +## Работа с классом плагина + +Класс плагина является точкой доступа к данным и зависимостям. +Для получения экземпляра плагина используйте `getPlugin()`: ```kotlin - private val viewModel by lazy { - obtainShareViewModel { - getPlugin() - .getContainer() - .createServersViewModel() - } - } +getPlugin() + .getContainer() ``` ## Области видимости -Все внутренние классы используемые только для работы плагина и не требующиеся для работы клиентского приложения, должны иметь область видимости **inner** +Все модули используют `explicitApi()` — модификаторы видимости обязательны для всех объявлений. +Внутренние классы, не предназначенные для использования в клиентском приложении, должны иметь модификатор `internal`. ## Тестирование -Для тестирования плагина, необходимо: -1. Подключить его как зависимость в модуль `sample`. +Для тестирования плагина: + +1. Подключите его как зависимость в модуль `sample`: -```groovy - debugImplementation(project(path: ':your-plugin')) +```kotlin +debugImplementation(project(":plugins:plugin-your-feature")) ``` -2. Инициировать плагин в **App** классе **sample** приложения. +2. Инициализируйте плагин в классе `App` sample-приложения: ```kotlin - DebugPanel.initialize( - application = this, - plugins = listOf(YourPluggin()) +DebugPanel.initialize( + application = this, + plugins = listOf( + YourPlugin(/* ... */) + ) ) ``` -3. Запустить **sample** проект +3. Запустите sample-проект. -## No-op зависимости +## No-op реализации -Для того чтобы в релизную сборку не попадали реализации публичных классов вашего модуля, необходимо добавить их в модуль no-op зависимостей. -([Подробнее в статье](https://medium.com/@orhanobut/no-op-versions-for-dev-tools-b0a865934398)). \ -Для этого создайте пакет с именем вашего плагина в модуле **debug-panel-no-op** и скопируйте ваши публичные классы доступные пользователю в этот пакет. +Чтобы отладочный код не попадал в релизную сборку, для каждого плагина необходимо создать no-op реализацию в модуле **panel-no-op**. -[!]Важно. Поле **package** должно остаться оригинальным. -Таким, каким оно было в вашем модуле. +Создайте пакет с публичными классами плагина, доступными пользователю, и предоставьте пустые реализации. -## Публикация +Важно: **package** должен совпадать с оригинальным пакетом вашего модуля. + +В sample-приложении подключение выглядит так: -Публикация новых плагнинов в основном репозитории должна проходить через создание **Merge Request** в ветку **develop**. +```kotlin +debugImplementation(project(":plugins:plugin-your-feature")) +releaseImplementation(project(":panel-no-op")) +``` + +Подробнее о подходе: [No-op versions for dev tools](https://medium.com/@orhanobut/no-op-versions-for-dev-tools-b0a865934398) + +## Публикация -Публикация на внутренний Maven пока делается вручную. -За публикацией обращаться к r.choryev@redmadrobot.com. -В ближайшее время есть планы пересмотреть этот подход. +Публикация новых плагинов проходит через создание **Pull Request** в ветку **main**.