Skip to content

Commit 54d8c01

Browse files
committed
docs: plugin dev docs revision
1 parent eec036c commit 54d8c01

2 files changed

Lines changed: 134 additions & 117 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![License](https://img.shields.io/github/license/RedMadRobot/debug-panel-android?style=flat-square)][license]
99
[![Android](https://img.shields.io/badge/Android-3DDC84?style=flat-square&logo=android&logoColor=white)](#)
1010

11-
**[Changelog][changelog]** | **[Миграция на новые версии][migration-guide]**
11+
**[Changelog][changelog]** | **[Миграция на новые версии][migration-guide]** | **[Документация по разработке плагинов][plugin-development-doc]**
1212

1313
Библиотека избавляет от необходимости пересобирать приложение для смены сервера или переключения feature toggle. Цель проекта — упростить процесс отладки приложения.
1414

docs/plugin_development.md

Lines changed: 133 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,182 +1,199 @@
11
# Разработка новых плагинов
22

3-
**[!]Важно.**
4-
1. Библиотека находится в статусе разработки и миграции на более актуальные решения.
5-
Актуальность данного документа стоит уточнить у холдера библиотеки. В данный момент это **r.choryev@redmadrobot.com**
6-
2. В библиотеке млогут быть спорные решения, но она открыта для предложений.
7-
3. В текущих плагинах, для работы со списками используется [Groupie](https://github.com/lisawray/groupie).
8-
Т.к. эта библиотека требует использование `jcenter` и уже не кажется таким уж подходящим решением, поэтому она будет удаляться из библиотеки.
9-
Поэтому это стоит учесть при разработке ваших новых плагинов и использовать какое-то другое решение.
3+
## Общая структура
104

5+
Debug Panel построена на подходе с использованием плагинов — каждая функциональность реализуется в виде отдельного модуля-плагина.
116

12-
## Общая структура
13-
Debug panel разрабатывается опираясь на подход разработки функционала отдельными плагинами.
14-
В данный момент есть несколько модулей на которых основана работа самой панели и разработка и работа плагинов.
7+
Базовые модули, на которых основана работа панели:
158

16-
* **debug-panel-core** - Реализация самой панели и базовых классов для поддержки системы плагинов.
17-
* **debug-panel-common** - Модуль содержащий общие библиотеки, классы и ресурсы переиспользуемые в плагинах.
18-
Библиотеки пробрасываются как сквозные зависимости при помощи типа зависимости `api`.
19-
Список предоставляемых библиотек можно посмотреть в файле [build.gradle](../debug-panel-common/build.gradle.kts)
9+
* **panel-core** — реализация панели, базовые классы системы плагинов и событийная модель.
10+
* **panel-no-op** — пустые реализации публичных API для релизных сборок (исключает отладочный код из продакшена).
2011

2112
## Создание нового плагина
22-
Для добавления нового плагина необходимо сделать несколько шагов:
23-
1. Создать в дирректории **plugins** новый модуль для реализации своего плагина.
24-
25-
```
26-
plugins
27-
| your-plugin
28-
```
29-
2. Объявить новый модуль в файле **settings.gradle** по примеру уже существующих плагинов.
30-
31-
```
32-
include ':your-plugin'
3313

34-
project(':your-plugin').projectDir = new File(rootDir, 'plugins/your-plugin')
14+
### 1. Создать модуль
15+
16+
Создайте новый модуль в директории `plugins/`:
17+
18+
```
19+
plugins/
20+
└── plugin-your-feature/
3521
```
36-
3. Добавить в **build.gradle** файл вашего модуля следующие настройки:
3722

38-
```groovy
39-
android {
40-
/*.......*/
41-
42-
compileSdkVersion build_versions.compile_sdk
23+
### 2. Зарегистрировать модуль в settings.gradle.kts
4324

44-
defaultConfig {
45-
minSdkVersion build_versions.min_sdk
46-
targetSdkVersion build_versions.target_sdk
25+
Добавьте модуль по аналогии с существующими плагинами:
4726

48-
versionCode getVersionCodeFromProperties()
49-
versionName getVersionNameFromProperties()
50-
}
27+
```kotlin
28+
// Plugins
29+
include(
30+
":plugins:plugin-servers",
31+
":plugins:plugin-konfeature",
32+
":plugins:plugin-about-app",
33+
":plugins:plugin-your-feature",
34+
)
35+
```
5136

52-
/*.......*/
37+
### 3. Настроить build.gradle.kts
5338

39+
Примените convention-плагин, который содержит всю необходимую конфигурацию (compileSdk, minSdk, `explicitApi()`, зависимость на `panel-core` и Compose):
5440

55-
kotlinOptions {
56-
freeCompilerArgs += "-Xexplicit-api=strict"
57-
}
41+
```kotlin
42+
plugins {
43+
id("convention.debug.panel.plugin")
5844
}
5945

60-
dependencies {
61-
implementation(
62-
project(path: ':debug-panel-core'),
63-
project(path: ':debug-panel-common'),
46+
description = "Plugin description"
6447

65-
deps.kotlin.stdlib
66-
)
48+
android {
49+
namespace = "com.redmadrobot.debug.plugin.yourfeature"
6750
}
6851

52+
dependencies {
53+
// Только специфичные для плагина зависимости
54+
implementation(androidx.core)
55+
}
6956
```
70-
**[!]Важно. Конфигурация будет меняться при дальнейшей миграции с Groovy на Kotlin**
7157

72-
4. Создать в своем модуле класс-плагин который и будет отвечать за взаимодействие с DebugPanel.
73-
Для этого класс должен унаследоваться от класса `Plugin()` и реализовать необходимые методы.
74-
В качестве аргументов класса можно передать необходимые для инициализации плагина данные.\
75-
**(О методе `getPluginContainer()` и `PluginDependencyContainer()` можно будет почитать ниже)**
76-
58+
### 4. Создать класс плагина
59+
60+
Класс плагина — точка входа, отвечающая за взаимодействие с DebugPanel.
61+
Унаследуйтесь от `Plugin()` и реализуйте обязательные методы.
62+
Подробнее о `getPluginContainer()` и `PluginDependencyContainer` — в разделе ниже.
63+
7764
```kotlin
7865
public class YourPlugin(
79-
/*some arguments*/
66+
/* аргументы для инициализации */
8067
) : Plugin() {
8168

82-
internal companion object {
83-
const val NAME = "AWESOME PLUGIN"
84-
}
85-
86-
override fun getName(): String = NAME
69+
override fun getName(): String = "YOUR PLUGIN"
8770

88-
/*Plugin dependency container initializing*/
8971
override fun getPluginContainer(commonContainer: CommonContainer): PluginDependencyContainer {
90-
return YourPluginContainer(sharedPreferences)
72+
return YourPluginContainer(commonContainer)
9173
}
9274

93-
/*Plugin Fragment initializing*/
94-
override fun getFragment(): Fragment? {
95-
return YourPluginFragment()
75+
@Composable
76+
override fun content() {
77+
YourScreen()
78+
}
79+
}
80+
```
81+
82+
Если плагин поддерживает редактирование через экран настроек панели, реализуйте интерфейс `EditablePlugin`:
83+
84+
```kotlin
85+
public class YourPlugin : Plugin(), EditablePlugin {
86+
// ...
87+
88+
@Composable
89+
override fun content() {
90+
YourScreen(isEditMode = false)
9691
}
9792

98-
/*Plugin Setting Fragment initializing.*/
99-
override fun getSettingFragment(): Fragment { //Нужно только если есть отдельный экран для настройки плагина.
100-
return YourPluginSettingFragment()
93+
@Composable
94+
override fun settingsContent() {
95+
YourScreen(isEditMode = true)
10196
}
10297
}
10398
```
104-
5. Создать **Fragment** экрана плагина(если он нужен) и унаследовать его от `PluginFragment()`.
105-
К фрагменту создать **ViewModel** и унаследовать от `PluginViewModel()`.
106-
В этой связке (Fragment+ViewModel), реализовывать пользовательское взаимодействие пользователя с плагином.
107-
108-
## PluginDependencyContainer
10999

110-
В библиотеке не используются библиотеки для реализации DI, т.к.:
111-
1. Не хочется тащить их зависимости в библиотеку.
112-
2. Библиотека не такая большая чтобы реализовывать полноценный DI.
100+
### 5. Создать UI на Jetpack Compose
113101

114-
Вместо этого, в библиотеке используется подход с **Service Locator**.
115-
Для этого необходимо создать свой класс-контейнер, унаследовать его от **PluginDependencyContainer** и внутри него инициировать необходимые зависимости.
116-
Если вам для этого понадобится **Context**, его можно получить из **CommonContainer** который прилетает в качестве аргумента в методе `getPluginContainer()` при инициализации плагина.
117-
Пример реализации можно [посмотреть тут](../plugins/accounts-plugin/src/main/kotlin/com/redmadrobot/account/plugin/AccountsPluginContainer.kt)
102+
UI плагина реализуется с помощью Composable-функций. Для инъекции ViewModel используется хелпер `provideViewModel`:
118103

119-
## Работа с классом плагина
104+
```kotlin
105+
@Composable
106+
internal fun YourScreen(
107+
viewModel: YourViewModel = provideViewModel {
108+
getPlugin<YourPlugin>()
109+
.getContainer<YourPluginContainer>()
110+
.createYourViewModel()
111+
},
112+
) {
113+
val state by viewModel.state.collectAsState()
114+
// UI
115+
}
116+
```
117+
118+
## PluginDependencyContainer
119+
120+
В библиотеке не используются DI-фреймворки, чтобы не добавлять лишних зависимостей. Вместо этого применяется подход **Service Locator**.
120121

121-
Класс-плагин, описание которого было в пункте **4**, является точкой инициализации вашего плагина.
122-
Поэтому доступ к данным и различным экземплярам классов нужно реализовывать через него.
123-
Чтобу получить доступ к самому плагину, нужно использовать метод `getPlugin<YourPlugin>()`.
124-
Например для получения контейнера зависимостей плагина, нужно вызвать:
122+
Для этого создайте класс-контейнер, реализующий интерфейс `PluginDependencyContainer`, и инициализируйте в нём необходимые зависимости.
123+
`Context` доступен через `CommonContainer`, который передаётся в метод `getPluginContainer()` при инициализации плагина.
125124

126125
```kotlin
127-
getPlugin<YourPlugin>()
128-
.getContainer<YourPluginContainer>()
126+
internal class YourPluginContainer(
127+
private val container: CommonContainer,
128+
) : PluginDependencyContainer {
129+
130+
private val dataStore by lazy { YourDataStore(container.context) }
131+
132+
val repository by lazy { YourRepository(dataStore) }
133+
134+
fun createYourViewModel(): YourViewModel {
135+
return YourViewModel(repository)
136+
}
137+
}
129138
```
130139

131-
**Пример использования плагина для получения ViewModel во Fragment:**
140+
Пример реализации: [ServersPluginContainer](../plugins/plugin-servers/src/main/kotlin/com/redmadrobot/debug/plugin/servers/ServersPluginContainer.kt)
141+
142+
## Работа с классом плагина
143+
144+
Класс плагина является точкой доступа к данным и зависимостям.
145+
Для получения экземпляра плагина используйте `getPlugin<YourPlugin>()`:
132146

133147
```kotlin
134-
private val viewModel by lazy {
135-
obtainShareViewModel {
136-
getPlugin<ServersPlugin>()
137-
.getContainer<ServersPluginContainer>()
138-
.createServersViewModel()
139-
}
140-
}
148+
getPlugin<YourPlugin>()
149+
.getContainer<YourPluginContainer>()
141150
```
142151

143152
## Области видимости
144153

145-
Все внутренние классы используемые только для работы плагина и не требующиеся для работы клиентского приложения, должны иметь область видимости **inner**
154+
Все модули используют `explicitApi()` — модификаторы видимости обязательны для всех объявлений.
155+
Внутренние классы, не предназначенные для использования в клиентском приложении, должны иметь модификатор `internal`.
146156

147157
## Тестирование
148158

149-
Для тестирования плагина, необходимо:
150-
1. Подключить его как зависимость в модуль `sample`.
159+
Для тестирования плагина:
160+
161+
1. Подключите его как зависимость в модуль `sample`:
151162

152-
```groovy
153-
debugImplementation(project(path: ':your-plugin'))
163+
```kotlin
164+
debugImplementation(project(":plugins:plugin-your-feature"))
154165
```
155166

156-
2. Инициировать плагин в **App** классе **sample** приложения.
167+
2. Инициализируйте плагин в классе `App` sample-приложения:
157168

158169
```kotlin
159-
DebugPanel.initialize(
160-
application = this,
161-
plugins = listOf(YourPluggin())
170+
DebugPanel.initialize(
171+
application = this,
172+
plugins = listOf(
173+
YourPlugin(/* ... */)
174+
)
162175
)
163176
```
164177

165-
3. Запустить **sample** проект
178+
3. Запустите sample-проект.
166179

167-
## No-op зависимости
180+
## No-op реализации
168181

169-
Для того чтобы в релизную сборку не попадали реализации публичных классов вашего модуля, необходимо добавить их в модуль no-op зависимостей.
170-
([Подробнее в статье](https://medium.com/@orhanobut/no-op-versions-for-dev-tools-b0a865934398)). \
171-
Для этого создайте пакет с именем вашего плагина в модуле **debug-panel-no-op** и скопируйте ваши публичные классы доступные пользователю в этот пакет.
182+
Чтобы отладочный код не попадал в релизную сборку, для каждого плагина необходимо создать no-op реализацию в модуле **panel-no-op**.
172183

173-
[!]Важно. Поле **package** должно остаться оригинальным.
174-
Таким, каким оно было в вашем модуле.
184+
Создайте пакет с публичными классами плагина, доступными пользователю, и предоставьте пустые реализации.
175185

176-
## Публикация
186+
Важно: **package** должен совпадать с оригинальным пакетом вашего модуля.
187+
188+
В sample-приложении подключение выглядит так:
177189

178-
Публикация новых плагнинов в основном репозитории должна проходить через создание **Merge Request** в ветку **develop**.
190+
```kotlin
191+
debugImplementation(project(":plugins:plugin-your-feature"))
192+
releaseImplementation(project(":panel-no-op"))
193+
```
194+
195+
Подробнее о подходе: [No-op versions for dev tools](https://medium.com/@orhanobut/no-op-versions-for-dev-tools-b0a865934398)
196+
197+
## Публикация
179198

180-
Публикация на внутренний Maven пока делается вручную.
181-
За публикацией обращаться к r.choryev@redmadrobot.com.
182-
В ближайшее время есть планы пересмотреть этот подход.
199+
Публикация новых плагинов проходит через создание **Pull Request** в ветку **main**.

0 commit comments

Comments
 (0)