Slide 1

Slide 1 text

Write your own Android Studio plugin and automate everything @Orbycius Marcos Holgado (AndroidMakers version)

Slide 2

Slide 2 text

Stop wasting time on things that don’t add value @Orbycius

Slide 3

Slide 3 text

Writing a plugin is easy… @Orbycius

Slide 4

Slide 4 text

…and you can do it in kotlin! @Orbycius

Slide 5

Slide 5 text

https://github.com/marcosholgado/demo-plugin https://github.com/marcosholgado/plugin-app @Orbycius

Slide 6

Slide 6 text

https://github.com/JetBrains/gradle-intellij-plugin @Orbycius

Slide 7

Slide 7 text

File > New... > Project @Orbycius

Slide 8

Slide 8 text

@Orbycius

Slide 9

Slide 9 text

@Orbycius

Slide 10

Slide 10 text

@Orbycius

Slide 11

Slide 11 text

dependencies { "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 'com.squareup.retrofit2:retrofit:2.5.0' 'com.squareup.retrofit2:adapter-rxjava2:2.5.0' 'com.squareup.retrofit2:converter-gson:2.5.0' 'com.google.dagger:dagger:2.20' 'com.google.dagger:dagger-compiler:2.20' 'io.reactivex.rxjava2:rxjava:2.2.5' 'com.github.akarnokd:rxjava2-swing:0.3.0' } compile compile compile compile compile kapt compile compile @Orbycius

Slide 12

Slide 12 text

dependencies { "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 'com.squareup.retrofit2:retrofit:2.5.0' 'com.squareup.retrofit2:adapter-rxjava2:2.5.0' 'com.squareup.retrofit2:converter-gson:2.5.0' 'com.google.dagger:dagger:2.20' 'com.google.dagger:dagger-compiler:2.20' 'io.reactivex.rxjava2:rxjava:2.2.5' 'com.github.akarnokd:rxjava2-swing:0.3.0' } compile compile compile compile compile kapt compile compile @Orbycius

Slide 13

Slide 13 text

publishPlugin { token yourToken password yourPassword channels 'nightly' } @Orbycius

Slide 14

Slide 14 text

intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false localPath ASRunPath } @Orbycius

Slide 15

Slide 15 text

intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false ASRunPath = /Applications/Android Studio.app/Contents gradle.properties localPath ASRunPath } @Orbycius

Slide 16

Slide 16 text

localPath ASRunPath intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false localPath ASRunPath } @Orbycius

Slide 17

Slide 17 text

localPath ASRunPath // alternativeIdePath '/Applications/Android Studio.app' intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false } version '2018.2.2' @Orbycius

Slide 18

Slide 18 text

localPath ASRunPath // alternativeIdePath '/Applications/Android Studio.app' intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false } version '2018.2.2' @Orbycius

Slide 19

Slide 19 text

https://developer.android.com/studio/releases/ @Orbycius

Slide 20

Slide 20 text

intellij { pluginName 'demo-plugin' updateSinceUntilBuild false localPath ASRunPath } plugins = ['Kotlin', 'git4idea', 'android'] @Orbycius

Slide 21

Slide 21 text

intellij { pluginName 'demo-plugin' updateSinceUntilBuild false localPath ASRunPath } plugins = ['Kotlin', 'git4idea', 'android'] @Orbycius

Slide 22

Slide 22 text

@Orbycius

Slide 23

Slide 23 text

org.jetbrains.kotlin Git4Idea org.jetbrains.android @Orbycius

Slide 24

Slide 24 text

org.jetbrains.kotlin Git4Idea org.jetbrains.android intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', ] updateSinceUntilBuild false 'android' localPath ASRunPath } @Orbycius

Slide 25

Slide 25 text

localPath ASRunPath org.jetbrains.kotlin Git4Idea org.jetbrains.android intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', ] updateSinceUntilBuild false 'android' localPath ASRunPath } @Orbycius

Slide 26

Slide 26 text

localPath ASRunPath org.jetbrains.kotlin Git4Idea org.jetbrains.android intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', ] updateSinceUntilBuild false 'android' version 'IC-2018.2.2' alternativeIdePath '/Applications/Android Studio.app' // } @Orbycius

Slide 27

Slide 27 text

intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath } version 'IC-2017.3.3' alternativeIdePath '/Applications/Android Studio.app' Android Studio 3.1.4 @Orbycius

Slide 28

Slide 28 text

intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath } version 'IC-2017.3.3' alternativeIdePath '/Applications/Android Studio.app' Android Studio 3.1.4 @Orbycius

Slide 29

Slide 29 text

intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath } version 'IC-2017.3.3' alternativeIdePath '/Applications/Android Studio.app' Android Studio 3.1.4 Android Studio 3.3.1 intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath } version 'IC-2018.2.2' alternativeIdePath '/Applications/Android Studio.app' @Orbycius

Slide 30

Slide 30 text

intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath } version 'IC-2017.3.3' alternativeIdePath '/Applications/Android Studio.app' Android Studio 3.1.4 Android Studio 3.3.1 intellij { pluginName 'demo-plugin' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath } version 'IC-2018.2.2' alternativeIdePath '/Applications/Android Studio.app' @Orbycius

Slide 31

Slide 31 text

org.jetbrains.kotlin Git4Idea org.jetbrains.android intellij { pluginName 'droidcon18' plugins = ['Kotlin', ‘git4idea', ] updateSinceUntilBuild false } 'android' localPath ASRunPath @Orbycius

Slide 32

Slide 32 text

Plugin Structure @Orbycius

Slide 33

Slide 33 text

Plugin Components @Orbycius

Slide 34

Slide 34 text

Application level components @Orbycius

Slide 35

Slide 35 text

Application level components Project level components @Orbycius

Slide 36

Slide 36 text

Application level components Project level components Module level components @Orbycius

Slide 37

Slide 37 text

Application level components Project level components Module level components @Orbycius

Slide 38

Slide 38 text

class DemoComponent } { @Orbycius

Slide 39

Slide 39 text

class DemoComponent } { : ApplicationComponent @Orbycius

Slide 40

Slide 40 text

class DemoComponent } { : ApplicationComponent override fun initComponent() { super.initComponent() if (isANewVersion()) { // do something } } @Orbycius

Slide 41

Slide 41 text

class DemoComponent } { : ApplicationComponent override fun initComponent() { super.initComponent() if (isANewVersion()) { // do something } } public var version = 1 public var localVersion = 0 @Orbycius

Slide 42

Slide 42 text

class DemoComponent } { : ApplicationComponent override fun initComponent() { super.initComponent() if (isANewVersion()) { // do something } } public var version = 1 public var localVersion = 0 private fun isANewVersion() = localVersion < version private fun updateVersion() { localVersion = version } @Orbycius

Slide 43

Slide 43 text

class DemoComponent } { : ApplicationComponent override fun initComponent() { super.initComponent() if (isANewVersion()) { // do something public var version = 1 public var localVersion = 0 private fun isANewVersion() = localVersion < version private fun updateVersion() { localVersion = version } } } updateVersion() @Orbycius

Slide 44

Slide 44 text

class DemoComponent } { : ApplicationComponent override fun initComponent() { super.initComponent() if (isANewVersion()) { // do something public var version = 1 public var localVersion = 0 private fun isANewVersion() = localVersion < version private fun updateVersion() { localVersion = version } } } updateVersion() override fun getState(): DemoComponent? = this override fun loadState(state: DemoComponent) = , PersistentStateComponent XmlSerializerUtil.copyBean(state, this) @Orbycius

Slide 45

Slide 45 text

class DemoComponent { : ApplicationComponent public var version = 1 public var localVersion = 0 private fun isANewVersion() = localVersion < version private fun updateVersion() { localVersion = version } override fun getState(): DemoComponent? = this override fun loadState(state: DemoComponent) = , PersistentStateComponent, Serializable XmlSerializerUtil.copyBean(state, this) } } } updateVersion() override fun initComponent() { super.initComponent() if (isANewVersion()) { // do something @Orbycius

Slide 46

Slide 46 text

class DemoComponent { : ApplicationComponent public var version = 1 public var localVersion = 0 private fun isANewVersion() = localVersion < version private fun updateVersion() { localVersion = version } override fun getState(): DemoComponent? = this override fun loadState(state: DemoComponent) = , PersistentStateComponent, Serializable XmlSerializerUtil.copyBean(state, this) } } } updateVersion() override fun initComponent() { super.initComponent() if (isANewVersion()) { // do something @State(name = "DemoConfiguration", storages = [ Storage(value = "demoConfiguration.xml") ]) @Orbycius

Slide 47

Slide 47 text

com.marcosholgado.demo.plugin.components.DemoComponent @Orbycius

Slide 48

Slide 48 text

Actions @Orbycius

Slide 49

Slide 49 text

@Orbycius

Slide 50

Slide 50 text

class JiraMoveAction : AnAction() { override fun actionPerformed(event: AnActionEvent) { // do something } } @Orbycius

Slide 51

Slide 51 text

@Orbycius

Slide 52

Slide 52 text

@Orbycius

Slide 53

Slide 53 text

@Orbycius

Slide 54

Slide 54 text

control == cmd @Orbycius

Slide 55

Slide 55 text

@Orbycius

Slide 56

Slide 56 text

@Orbycius

Slide 57

Slide 57 text

http://www.jetbrains.org/intellij/sdk/docs/basics/action_system.html @Orbycius

Slide 58

Slide 58 text

Extensions and extension points @Orbycius

Slide 59

Slide 59 text

Show me the code @Orbycius

Slide 60

Slide 60 text

You are still writing code! @Orbycius

Slide 61

Slide 61 text

JiraMoveAction @Orbycius

Slide 62

Slide 62 text

JiraMoveAction JiraMoveDialog JiraMoveDialogPresenter Model @Orbycius

Slide 63

Slide 63 text

JiraMoveAction JiraMoveDialog JiraMoveDialogPresenter Model JiraMovePanel @Orbycius

Slide 64

Slide 64 text

JiraMoveAction JiraMoveDialog JiraMoveDialogPresenter Model JiraMovePanel @Orbycius

Slide 65

Slide 65 text

Java Swing Forms @Orbycius

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

@orbycius JiraMoveAction JiraMoveDialog JiraMoveDialogPresenter Model JiraMovePanel @Orbycius

Slide 69

Slide 69 text

class JiraMovePanel: JPanel() { private val comboTransitions = ComboBox() val txtJiraTicket = JTextField() val txtComment = JTextArea() init { initComponents() } private fun initComponents() { layout = null val lblJiraTicket = JLabel(message("jira.move.label.ticket")) lblJiraTicket.setBounds(25, 33, 77, 16) add(lblJiraTicket) ... } } @Orbycius

Slide 70

Slide 70 text

JiraMoveAction JiraMoveDialog JiraMoveDialogPresenter Model JiraMovePanel @Orbycius

Slide 71

Slide 71 text

class JiraMoveDialog constructor(val project: Project): { @Inject lateinit var presenter: JiraMoveDialogPresenter init { DaggerJiraComponent.builder() .jiraModule(JiraModule(this, project)) .build().inject(this) isModal = true presenter.load() init() } override fun doOKAction() = presenter.doTransition(...) } private val panel: JiraMovePanel = JiraMovePanel() DialogWrapper(true) override fun createCenterPanel(): JComponent = panel @Orbycius

Slide 72

Slide 72 text

class JiraMoveDialog constructor(val project: Project): { @Inject lateinit var presenter: JiraMoveDialogPresenter init { DaggerJiraComponent.builder() .jiraModule(JiraModule(this, project)) .build().inject(this) isModal = true presenter.load() init() } override fun doOKAction() = presenter.doTransition(...) } private val panel: JiraMovePanel = JiraMovePanel() DialogWrapper(true) override fun createCenterPanel(): JComponent = panel @Orbycius

Slide 73

Slide 73 text

class JiraMoveDialog constructor(val project: Project): { @Inject lateinit var presenter: JiraMoveDialogPresenter init { DaggerJiraComponent.builder() .jiraModule(JiraModule(this, project)) .build().inject(this) isModal = true presenter.load() init() } override fun doOKAction() = presenter.doTransition(...) } private val panel: JiraMovePanel = JiraMovePanel() DialogWrapper(true) override fun createCenterPanel(): JComponent = panel @Orbycius

Slide 74

Slide 74 text

DialogWrapper @Orbycius

Slide 75

Slide 75 text

DialogWrapper Panel @Orbycius

Slide 76

Slide 76 text

@Orbycius

Slide 77

Slide 77 text

class JiraMoveDialog constructor(val project: Project): DialogWrapper(true) { @Inject lateinit var presenter: JiraMoveDialogPresenter private val panel: JiraMovePanel = JiraMovePanel() init { DaggerJiraComponent.builder() .jiraModule(JiraModule(this, project)) .build().inject(this) isModal = true presenter.load() init() } override fun createCenterPanel(): JComponent = panel } override fun doOKAction() = presenter.doTransition(...) @Orbycius

Slide 78

Slide 78 text

If you are using RxJava… Completable.merge(completable) .subscribeOn(Schedulers.io()) .subscribe( { view.success(selectedItem, ticket) }, { error -> view.error(error) } ) .observeOn( AndroidSchedulers.mainThread()) @Orbycius

Slide 79

Slide 79 text

If you are using RxJava… Completable.merge(completable) .subscribeOn(Schedulers.io()) .subscribe( { view.success(selectedItem, ticket) }, { error -> view.error(error) } ) .observeOn( AndroidSchedulers.mainThread()) @Orbycius

Slide 80

Slide 80 text

If you are using RxJava… Completable.merge(completable) .subscribeOn(Schedulers.io()) .subscribe( { view.success(selectedItem, ticket) }, { error -> view.error(error) } ) .observeOn( SwingSchedulers.edt()) compile "com.github.akarnokd:rxjava2-swing:0.3.0" @Orbycius

Slide 81

Slide 81 text

If you are using RxJava… Completable.merge(completable) .subscribeOn(Schedulers.io()) .subscribe( { view.success(selectedItem, ticket) }, { error -> view.error(error) } ) .observeOn( SwingSchedulers.edt()) compile "com.github.akarnokd:rxjava2-swing:0.3.0" @Orbycius

Slide 82

Slide 82 text

Settings @Orbycius

Slide 83

Slide 83 text

class JiraComponent: ProjectComponent { } @Orbycius

Slide 84

Slide 84 text

@State(name = "JiraConfiguration", storages = [ Storage(value = "jiraConfiguration.xml") ]) class JiraComponent: ProjectComponent, Serializable, PersistentStateComponent { override fun getState(): JiraComponent? = this override fun loadState(state: JiraComponent) = XmlSerializerUtil.copyBean(state, this) companion object { fun getInstance(project: Project): JiraComponent = project.getComponent(JiraComponent::class.java) } } var jiraUrl: String = "" var username: String = "" var password: String = "" var regex: String = "" @Orbycius

Slide 85

Slide 85 text

@State(name = "JiraConfiguration", storages = [ Storage(value = "jiraConfiguration.xml") ]) class JiraComponent: ProjectComponent, Serializable, PersistentStateComponent { override fun getState(): JiraComponent? = this override fun loadState(state: JiraComponent) = XmlSerializerUtil.copyBean(state, this) companion object { fun getInstance(project: Project): JiraComponent = project.getComponent(JiraComponent::class.java) } } var jiraUrl: String = "" var username: String = "" var password: String = "" var regex: String = "" @Orbycius

Slide 86

Slide 86 text

com.marcosholgado.demo.plugin.components.JiraComponent @Orbycius

Slide 87

Slide 87 text

class JiraSettings { private var userField: JTextField? = null private var passwordField: JPasswordField? = null private var jiraURLField: JTextField? = null … } @Orbycius

Slide 88

Slide 88 text

class JiraSettings(private val project: Project): Configurable { } @Orbycius

Slide 89

Slide 89 text

class JiraSettings(private val project: Project): Configurable { override fun isModified(): Boolean {} override fun getDisplayName(): String {} override fun apply() {} override fun createComponent(): JComponent? {} } @Orbycius

Slide 90

Slide 90 text

class JiraSettings(private val project: Project): Configurable { override fun getDisplayName(): String {} override fun apply() {} override fun createComponent(): JComponent? {} private var modified = false override fun isModified(): Boolean = modified } @Orbycius

Slide 91

Slide 91 text

class JiraSettings(private val project: Project): Configurable { private var modified = false override fun isModified(): Boolean = modified override fun apply() {} override fun createComponent(): JComponent? {} override fun getDisplayName(): String = "Jira Settings" } @Orbycius

Slide 92

Slide 92 text

class JiraSettings(private val project: Project): Configurable { private var modified = false override fun isModified(): Boolean = modified override fun createComponent(): JComponent? {} override fun getDisplayName(): String = "Jira Settings" override fun apply() {} } @Orbycius

Slide 93

Slide 93 text

class JiraSettings(private val project: Project): Configurable { … private var modified = false override fun apply() { val config = JiraComponent.getInstance(project) config.jiraUrl = jiraURLField!!.text config.username = userField!!.text config.password = String(passwordField!!.password) config.regex = regExField!!.text modified = false } override fun createComponent(): JComponent? {} } @Orbycius

Slide 94

Slide 94 text

class JiraSettings(private val project: Project): Configurable { … private var modified = false override fun apply() { val config = JiraComponent.getInstance(project) config.jiraUrl = jiraURLField!!.text config.username = userField!!.text config.password = String(passwordField!!.password) config.regex = regExField!!.text modified = false } override fun createComponent(): JComponent? {} } @Orbycius

Slide 95

Slide 95 text

class JiraSettings(private val project: Project): Configurable { … private var modified = false override fun createComponent(): JComponent? { val config = JiraComponent.getInstance(project) userField?.text = config.username passwordField?.text = config.password jiraURLField?.text = config.jiraUrl regExField?.text = config.regex return mainPanel } } @Orbycius

Slide 96

Slide 96 text

class JiraSettings(private val project: Project): Configurable { … private var modified = false override fun isModified(): Boolean = modified override fun createComponent(): JComponent? val config = JiraComponent.getInstance(project) userField?.text = config.username passwordField?.text = config.password jiraURLField?.text = config.jiraUrl regExField?.text = config.regex return mainPanel } } { @Orbycius

Slide 97

Slide 97 text

class JiraSettings(private val project: Project): … private var modified = false override fun isModified(): Boolean = modified override fun createComponent(): JComponent? val config = JiraComponent.getInstance(project) userField?.text = config.username passwordField?.text = config.password jiraURLField?.text = config.jiraUrl regExField?.text = config.regex return mainPanel } jiraURLField?.document?.addDocumentListener(this) userField?.document?.addDocumentListener(this) passwordField?.document?.addDocumentListener(this) regExField?.document?.addDocumentListener(this) } { DocumentListener Configurable, { @Orbycius

Slide 98

Slide 98 text

class JiraSettings(private val project: Project): Configurable, DocumentListener override fun changedUpdate(e: DocumentEvent?) { modified = true } override fun insertUpdate(e: DocumentEvent?) { modified = true } override fun removeUpdate(e: DocumentEvent?) { modified = true } } override fun changedUpdate(e: DocumentEvent?) { modified = true } override fun insertUpdate(e: DocumentEvent?) { modified = true } override fun removeUpdate(e: DocumentEvent?) { modified = true } DocumentListener { @Orbycius

Slide 99

Slide 99 text

@Orbycius

Slide 100

Slide 100 text

Live Templates @Orbycius

Slide 101

Slide 101 text

@Orbycius

Slide 102

Slide 102 text

@Orbycius

Slide 103

Slide 103 text

@Orbycius

Slide 104

Slide 104 text

How do you share them? • Export to a .jar file and import • Copy & paste template xml https://www.jetbrains.com/help/idea/2018.1/sharing-live-templates.html @Orbycius

Slide 105

Slide 105 text

How do you share them? $ mv ~/Library/Preferences/AndroidStudio3.3/templates/Demo.xml ~/Sandbox/demo-plugin/src/main/resources/liveTemplates/Demo.xml @Orbycius

Slide 106

Slide 106 text

How do you share them? $ mv ~/Library/Preferences/AndroidStudio3.3/templates/Demo.xml ~/Sandbox/demo-plugin/src/main/resources/liveTemplates/Demo.xml class DemoLiveTemplateProvider: DefaultLiveTemplatesProvider { override fun getDefaultLiveTemplateFiles(): Array = arrayOf("liveTemplates/Demo") override fun getHiddenLiveTemplateFiles(): Array? = null } @Orbycius

Slide 107

Slide 107 text

How do you share them? $ mv ~/Library/Preferences/AndroidStudio3.3/templates/Demo.xml ~/Sandbox/demo-plugin/src/main/resources/liveTemplates/Demo.xml class DemoLiveTemplateProvider: DefaultLiveTemplatesProvider { override fun getDefaultLiveTemplateFiles(): Array = arrayOf("liveTemplates/Demo") override fun getHiddenLiveTemplateFiles(): Array? = null } @Orbycius

Slide 108

Slide 108 text

Templates @Orbycius

Slide 109

Slide 109 text

@Orbycius

Slide 110

Slide 110 text

@Orbycius

Slide 111

Slide 111 text

@Orbycius

Slide 112

Slide 112 text

/Applications/Android\ Studio.app/Contents/plugins/android/lib/templates/ @Orbycius

Slide 113

Slide 113 text

/Applications/Android\ Studio.app/Contents/plugins/android/lib/templates/ $ tree -L 2 -d ├── activities │ ├── AndroidTVActivity │ ├── AndroidThingsActivity │ ├── AndroidThingsPeripheralActivity │ ├── BasicActivity │ ├── BlankWearActivity │ ├── BottomNavigationActivity │ ├── EmptyActivity │ ├── FullscreenActivity │ ├── GoogleAdMobAdsActivity │ ├── GoogleMapsActivity │ ├── GoogleMapsWearActivity │ ├── LoginActivity │ ├── MasterDetailFlow │ ├── NavigationDrawerActivity │ ├── ScrollActivity │ ├── SettingsActivity │ ├── TabbedActivity │ ├── ViewModelActivity @Orbycius

Slide 114

Slide 114 text

│ ├── NewInstantFeatureModule │ ├── NewJavaLibrary │ └── common └── other ├── AidlFile ├── AidlFolder ├── AndroidAutoMediaService ├── AndroidAutoMessagingService ├── AndroidManifest ├── AppActionsResourceFile ├── AppWidget ├── AssetsFolder ├── BlankFragment ├── BroadcastReceiver ├── ContentProvider ├── CustomView ├── Daydream ├── FontFolder ├── IntentService ├── JavaFolder ├── JniFolder ├── LayoutResourceFile @Orbycius

Slide 115

Slide 115 text

template.xml (UI) @Orbycius

Slide 116

Slide 116 text

template.xml (UI) .ftl files @Orbycius

Slide 117

Slide 117 text

template.xml (UI) .ftl files globals @Orbycius

Slide 118

Slide 118 text

recipe.xml template.xml (UI) .ftl files globals @Orbycius

Slide 119

Slide 119 text

recipe.xml template.xml (UI) .ftl files globals code @Orbycius

Slide 120

Slide 120 text

$ cat template.xml @Orbycius

Slide 121

Slide 121 text

$ cat template.xml @Orbycius

Slide 122

Slide 122 text

$ cat template.xml @Orbycius

Slide 123

Slide 123 text

$ cat template.xml @Orbycius

Slide 124

Slide 124 text

$ cat template.xml @Orbycius

Slide 125

Slide 125 text

$ cat recipe.xml.ftl @Orbycius

Slide 126

Slide 126 text

$ cat recipe.xml.ftl @Orbycius

Slide 127

Slide 127 text

$ cat recipe.xml.ftl @Orbycius

Slide 128

Slide 128 text

$ cat recipe.xml.ftl @Orbycius

Slide 129

Slide 129 text

$ cat recipe.xml.ftl <#include "root://common/proguard_recipe.xml.ftl"/> @Orbycius

Slide 130

Slide 130 text

$ cat recipe.xml.ftl <#include "root://common/proguard_recipe.xml.ftl"/> @Orbycius

Slide 131

Slide 131 text

$ cat recipe.xml.ftl <#include "root://common/proguard_recipe.xml.ftl"/> @Orbycius

Slide 132

Slide 132 text

$ cat globals.xml.ftl <#assign generateKotlin= (((includeKotlinSupport!false) || (language!'Java')?string == 'Kotlin'))> @Orbycius

Slide 133

Slide 133 text

$ cat FeatureContract.kt.ftl package ${escapeKotlinIdentifiers(packageName)} interface ${name}Contract { interface View { } interface Presenter { } } @Orbycius

Slide 134

Slide 134 text

What happens when you upgrade to Android Studio 3.5? @Orbycius

Slide 135

Slide 135 text

https://issuetracker.google.com/issues/37105193 Rebecca Franks @riggaroo @Orbycius

Slide 136

Slide 136 text

$ mv $ASPATH/Contents/plugins/android/lib/templates/other/myTemplate ~/.android/templates/other @Orbycius

Slide 137

Slide 137 text

How do you share them? @Orbycius

Slide 138

Slide 138 text

~/Sandbox/demo-plugin/src/main/resources/androidTemplates/ @Orbycius

Slide 139

Slide 139 text

~/Sandbox/demo-plugin/src/main/resources/androidTemplates/ class CopyTemplatesAction: AnAction() { override fun actionPerformed(event: AnActionEvent) { FileUtils.copyTemplates( "/androidTemplates/", "/.android/templates/other", event.project!!) } } @Orbycius

Slide 140

Slide 140 text

@State(name = "DemoConfiguration", storages = [ Storage(value = "demoConfiguration.xml") ]) class DemoComponent: ApplicationComponent, Serializable, PersistentStateComponent { override fun initComponent() { super.initComponent() if (isANewVersion()) { // do something updateVersion() … } } } @Orbycius

Slide 141

Slide 141 text

@State(name = "DemoConfiguration", storages = [ Storage(value = "demoConfiguration.xml") ]) class DemoComponent: ApplicationComponent, Serializable, PersistentStateComponent { override fun initComponent() { super.initComponent() if (shouldUpdateTemplates()) { FileUtils.copyTemplates( "/androidTemplates/", "/.android/templates/other", event.project!!) … } } } @Orbycius

Slide 142

Slide 142 text

@Orbycius

Slide 143

Slide 143 text

@Orbycius

Slide 144

Slide 144 text

class DemoModuleProvider: ModuleDescriptionProvider { override fun getDescriptions(): Collection { } } @Orbycius

Slide 145

Slide 145 text

class DemoModuleProvider: ModuleDescriptionProvider { override fun getDescriptions(): Collection { val res = ArrayList() val manager = TemplateManager.getInstance() val templateDirectories = TemplateManager.getExtraTemplateRootFolders() for (dir in templateDirectories) { if (dir.parent.endsWith(".android")) { } } } } return res @Orbycius

Slide 146

Slide 146 text

class DemoModuleProvider: ModuleDescriptionProvider { override fun getDescriptions(): Collection { val res = ArrayList() val manager = TemplateManager.getInstance() val templateDirectories = TemplateManager.getExtraTemplateRootFolders() for (dir in templateDirectories) { if (dir.parent.endsWith(".android")) { } } } } return res val applicationTemplates = TemplateManager .getTemplatesFromDirectory(dir, true) for (templateFile in applicationTemplates) { val metadata = manager.getTemplateMetadata(templateFile) if (metadata == null || metadata.category != "Demo") continue res.add(DemoModuleEntry(…)) } break @Orbycius

Slide 147

Slide 147 text

private class DemoModuleEntry(…): ModuleTemplateGalleryEntry { } @Orbycius

Slide 148

Slide 148 text

private class DemoModuleEntry(…): ModuleTemplateGalleryEntry { … override fun createStep(model: NewModuleModel): SkippableWizardStep<*> { val basePackage = NewProjectModel .getSuggestedProjectPackage(model.project.value, false) return ConfigureAndroidModuleStep( model, formFactor, minSdkLevel, basePackage, isLibrary, false, name) } } @Orbycius

Slide 149

Slide 149 text

Write your own Android Studio plugin and automate everything Icons made by Smashicons from www.flaticon.com @Orbycius Marcos Holgado

Slide 150

Slide 150 text

No content

Slide 151

Slide 151 text

(plugin) https://github.com/marcosholgado/demo-plugin (demo) https://github.com/marcosholgado/plugin-app (blog) http://bit.ly/plugin-blog Write your own Android Studio plugin and automate everything @Orbycius Marcos Holgado