Slide 1

Slide 1 text

Write your own Android Studio plugin and automate everything @Orbycius Marcos Holgado

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/droidcon18-plugin https://github.com/marcosholgado/droidcon18 @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 { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-runtime:$kotlin_version" compile 'com.squareup.retrofit2:retrofit:2.4.0' compile 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' compile 'com.squareup.retrofit2:converter-gson:2.4.0' compile 'com.google.dagger:dagger:2.17' kapt 'com.google.dagger:dagger-compiler:2.17' compile 'io.reactivex.rxjava2:rxjava:2.1.10' compile "com.github.akarnokd:rxjava2-swing:0.3.0" } @orbycius

Slide 12

Slide 12 text

dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-runtime:$kotlin_version" compile 'com.squareup.retrofit2:retrofit:2.4.0' compile 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' compile 'com.squareup.retrofit2:converter-gson:2.4.0' compile 'com.google.dagger:dagger:2.17' kapt 'com.google.dagger:dagger-compiler:2.17' compile 'io.reactivex.rxjava2:rxjava:2.1.10' compile "com.github.akarnokd:rxjava2-swing:0.3.0" } compile 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 'droidcon18' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false localPath ASRunPath } @orbycius

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

intellij { pluginName 'droidcon18' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath version 2018.1.6' alternativeIdePath ‘/Applications/Android Studio.app' } // localPath ASRunPath version '2018.1.6' alternativeIdePath '/Applications/Android Studio.app' @orbycius

Slide 17

Slide 17 text

intellij { pluginName 'droidcon18' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath version 2018.1.6' alternativeIdePath '/Applications/Android Studio.app' } version '2018.1.6' @orbycius

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

intellij { pluginName 'droidcon18' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false localPath ASRunPath } @orbycius

Slide 20

Slide 20 text

intellij { pluginName 'droidcon18' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false localPath ASRunPath } plugins = ['Kotlin', 'git4idea', 'android'] @orbycius

Slide 21

Slide 21 text

@orbycius

Slide 22

Slide 22 text

org.jetbrains.android org.jetbrains.kotlin Git4Idea @orbycius

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

intellij { pluginName 'droidcon18' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath version alternativeIdePath ‘/Applications/Android Studio.app' } version 'IC-2017.3.3' alternativeIdePath '/Applications/Android Studio.app' Android Studio 3.1.4 Android Studio 3.2 intellij { pluginName 'droidcon18' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath version alternativeIdePath ‘/Applications/Android Studio.app' } version 'IC-2018.1.6' alternativeIdePath '/Applications/Android Studio.app' @orbycius

Slide 29

Slide 29 text

intellij { pluginName 'droidcon18' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath version alternativeIdePath ‘/Applications/Android Studio.app' } version 'IC-2017.3.3' alternativeIdePath '/Applications/Android Studio.app' Android Studio 3.1.4 Android Studio 3.2 intellij { pluginName 'droidcon18' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false // localPath ASRunPath version alternativeIdePath ‘/Applications/Android Studio.app' } version 'IC-2018.1.6' alternativeIdePath '/Applications/Android Studio.app' @orbycius

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Plugin Structure @orbycius

Slide 32

Slide 32 text

Plugin Components @orbycius

Slide 33

Slide 33 text

Application level components @orbycius

Slide 34

Slide 34 text

Application level components Project level components @orbycius

Slide 35

Slide 35 text

Application level components Project level components Module level components @orbycius

Slide 36

Slide 36 text

class DroidconComponent { } @orbycius

Slide 37

Slide 37 text

class DroidconComponent: ApplicationComponent { } @orbycius

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

com.marcosholgado.droidcon18.plugin.components.DroidconComponent @orbycius

Slide 45

Slide 45 text

Actions @orbycius

Slide 46

Slide 46 text

@orbycius

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

@orbycius

Slide 49

Slide 49 text

@orbycius

Slide 50

Slide 50 text

@orbycius

Slide 51

Slide 51 text

control == cmd @orbycius

Slide 52

Slide 52 text

@orbycius

Slide 53

Slide 53 text

@orbycius

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Extensions and extension points @orbycius

Slide 56

Slide 56 text

Show me the code @orbycius

Slide 57

Slide 57 text

You are still writing code! @orbycius

Slide 58

Slide 58 text

@orbycius

Slide 59

Slide 59 text

@orbycius

Slide 60

Slide 60 text

@orbycius

Slide 61

Slide 61 text

@orbycius

Slide 62

Slide 62 text

Java Swing Forms @orbycius

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

@orbycius

Slide 66

Slide 66 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 67

Slide 67 text

@orbycius

Slide 68

Slide 68 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 69

Slide 69 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(...) } DialogWrapper(true) @orbycius

Slide 70

Slide 70 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(...) } private val panel: JiraMovePanel = JiraMovePanel() override fun createCenterPanel(): JComponent = panel } @orbycius

Slide 71

Slide 71 text

DialogWrapper Panel @orbycius

Slide 72

Slide 72 text

@orbycius

Slide 73

Slide 73 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(...) } override fun doOKAction() = presenter.doTransition(...) } @orbycius

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

Settings @orbycius

Slide 78

Slide 78 text

class JiraComponent: ProjectComponent { } @orbycius

Slide 79

Slide 79 text

@State(name = "JiraConfiguration", storages = [ Storage(value = "jiraConfiguration.xml") ]) class JiraComponent: ProjectComponent, Serializable, PersistentStateComponent { var jiraUrl: String = "" var username: String = "" var password: String = "" var regex: String = "" 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) } } @orbycius

Slide 80

Slide 80 text

@State(name = "JiraConfiguration", storages = [ Storage(value = "jiraConfiguration.xml") ]) class JiraComponent: ProjectComponent, Serializable, PersistentStateComponent { var jiraUrl: String = "" var username: String = "" var password: String = “ var regex: String = "" 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 81

Slide 81 text

com.marcosholgado.droidcon18.plugin.components.JiraComponent @orbycius

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 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 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 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? {} } 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 } @orbycius

Slide 88

Slide 88 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 } } 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 89

Slide 89 text

class JiraSettings(private val project: Project): Configurable { … private var modified = false override fun isModified(): Boolean = modified } private var modified = false override fun isModified(): Boolean = modified @orbycius

Slide 90

Slide 90 text

class JiraSettings(private val project: Project): Configurable, DocumentListener { override fun createComponent(): JComponent? { jiraURLField?.document?.addDocumentListener(this) userField?.document?.addDocumentListener(this) passwordField?.document?.addDocumentListener(this) regExField?.document?.addDocumentListener(this) return mainPanel } } DocumentListener { override fun createComponent(): JComponent? { jiraURLField?.document?.addDocumentListener(this) userField?.document?.addDocumentListener(this) passwordField?.document?.addDocumentListener(this) regExField?.document?.addDocumentListener(this) return mainPanel } @orbycius

Slide 91

Slide 91 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 } @orbycius

Slide 92

Slide 92 text

@orbycius

Slide 93

Slide 93 text

Live Templates @orbycius

Slide 94

Slide 94 text

@orbycius

Slide 95

Slide 95 text

@orbycius

Slide 96

Slide 96 text

@orbycius

Slide 97

Slide 97 text

@orbycius

Slide 98

Slide 98 text

How do you share them? @orbycius

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

How do you share them? $ mv ~/Library/Preferences/AndroidStudio3.2/templates/Droidcon.xml ~/Sandbox/droidcon18-plugin/src/main/resources/liveTemplates/Droidcon.xml @orbycius

Slide 101

Slide 101 text

How do you share them? $ mv ~/Library/Preferences/AndroidStudio3.2/templates/Droicon.xml ~/Sandbox/droidcon18-plugin/src/main/resources/liveTemplates/Droidcon.xml class DroidconLiveTemplateProvider: DefaultLiveTemplatesProvider { override fun getDefaultLiveTemplateFiles(): Array = arrayOf("liveTemplates/Droidcon") override fun getHiddenLiveTemplateFiles(): Array? = null } @orbycius

Slide 102

Slide 102 text

How do you share them? $ mv ~/Library/Preferences/AndroidStudio3.2/templates/Droicon.xml ~/Sandbox/droidcon18-plugin/src/main/resources/liveTemplates/Droidcon.xml class DroidconLiveTemplateProvider: DefaultLiveTemplatesProvider { override fun getDefaultLiveTemplateFiles(): Array = arrayOf("liveTem plates/Droidcon") override fun getHiddenLiveTemplateFiles(): Array? = null } @orbycius

Slide 103

Slide 103 text

Templates @orbycius

Slide 104

Slide 104 text

@orbycius

Slide 105

Slide 105 text

@orbycius

Slide 106

Slide 106 text

@orbycius

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

$ tree -L 2 -d /Applications/Android\ Studio.app/Contents/plugins/android/lib/templates/ └── other ├── AidlFile ├── AidlFolder ├── AndroidAutoMediaService ├── AndroidAutoMessagingService ├── AndroidManifest ├── AppActionsResourceFile ├── AppWidget ├── AssetsFolder ├── BlankFragment ├── BroadcastReceiver ├── ContentProvider ├── CustomView ├── Daydream ├── FontFolder ├── IntentService ├── JavaFolder ├── JniFolder ├── LayoutResourceFile @orbycius

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

$ cat template.xml @orbycius

Slide 112

Slide 112 text

$ cat template.xml @orbycius

Slide 113

Slide 113 text

$ cat template.xml @orbycius

Slide 114

Slide 114 text

$ cat template.xml @orbycius

Slide 115

Slide 115 text

$ cat recipe.xml.ftl @orbycius

Slide 116

Slide 116 text

$ cat recipe.xml.ftl @orbycius

Slide 117

Slide 117 text

$ cat recipe.xml.ftl @orbycius

Slide 118

Slide 118 text

$ cat recipe.xml.ftl @orbycius

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

What happens when you upgrade to Android Studio 3.3? @orbycius

Slide 125

Slide 125 text

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

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

How do you share them? @orbycius

Slide 128

Slide 128 text

~/Sandbox/droidcon18-plugin/src/main/resources/androidTemplates/ @orbycius

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

@State(name = "DroidconConfiguration", storages = [ Storage(value = "droidconConfiguration.xml") ]) class DroidconComponent: ApplicationComponent, Serializable, PersistentStateComponent { … override fun initComponent() { super.initComponent() if (shouldUpdateTemplates()) { // update templates and notify user } } } @orbycius

Slide 131

Slide 131 text

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

Slide 132

Slide 132 text

@orbycius

Slide 133

Slide 133 text

@orbycius

Slide 134

Slide 134 text

class DroidconModuleProvider: ModuleDescriptionProvider { override fun getDescriptions(): Collection { } } @orbycius

Slide 135

Slide 135 text

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 136

Slide 136 text

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

Slide 137

Slide 137 text

private class DroidconModuleEntry(…): ModuleTemplateGalleryEntry { } @orbycius

Slide 138

Slide 138 text

private class DroidconModuleEntry(…): 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 139

Slide 139 text

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

Slide 140

Slide 140 text

No content

Slide 141

Slide 141 text

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