Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Write your own Android Studio plugin and automa...

Marcos
October 25, 2018

Write your own Android Studio plugin and automate everything

Let’s face it, you spend a lot of time writing the same code over and over again. Maybe you are using the same pattern in different places.

Maybe you copy&paste some classes and layouts when you create a new Activity using MVP or MVVM. Maybe you “reuse” the same files when you create a new App. What if you could generate that code automatically? Well, you can, and I’m going to show you how by writing your own Android Studio Plugin.

In this talk, you will learn how to create a new Android Studio Plugin, create templates to generate code automatically, write live templates to insert frequently-used constructors in your code and how you can create a brand new full app in minutes putting together feature modules and templates.

Marcos

October 25, 2018
Tweet

More Decks by Marcos

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. ASRunPath = /Applications/Android Studio.app/Contents gradle.properties intellij { pluginName 'droidcon18' plugins

    = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false localPath ASRunPath } localPath ASRunPath @orbycius
  4. 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
  5. 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
  6. intellij { pluginName 'droidcon18' plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild

    false localPath ASRunPath } plugins = ['Kotlin', 'git4idea', 'android'] @orbycius
  7. <depends>org.jetbrains.android</depends> <depends>org.jetbrains.kotlin</depends> <depends>Git4Idea</depends> <depends>org.jetbrains.android</depends> 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. class DroidconComponent: ApplicationComponent { private var version = 1 private

    var localVersion = 0 override fun initComponent() { super.initComponent() if (isANewVersion()) { // do something } } } @orbycius
  13. 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
  14. class DroidconComponent: ApplicationComponent, PersistentStateComponent<DroidconComponent> { 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
  15. class DroidconComponent: ApplicationComponent, PersistentStateComponent<DroidconComponent>, 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
  16. @State(name = "DroidconConfiguration", storages = [ Storage(value = "droidconConfiguration.xml") ])

    class DroidconComponent: ApplicationComponent, PersistentStateComponent<DroidconComponent>, 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
  17. <actions> <action id="JiraMoveTicket" class=".JiraMoveAction" text="Move Ticket" description="Move Ticket in Jira">

    <add-to-group group-id="CutCopyPasteGroup" anchor="last"/> <keyboard-shortcut first-keystroke="control alt k" keymap="$default"/> </action> </actions> @orbycius
  18. <actions> <action id="JiraMoveTicket" class=".JiraMoveAction" text="Move Ticket" description="Move Ticket in Jira">

    <add-to-group group-id="CutCopyPasteGroup" anchor="last"/> <keyboard-shortcut first-keystroke="control alt k" keymap="$default"/> </action> </actions> <action id="JiraMoveTicket" class=".JiraMoveAction" text="Move Ticket" description="Move Ticket in Jira"> </action> @orbycius
  19. <actions> <action id="JiraMoveTicket" class=".JiraMoveAction" text="Move Ticket" description="Move Ticket in Jira">

    <add-to-group group-id="CutCopyPasteGroup" anchor="last"/> <keyboard-shortcut first-keystroke="control alt k" keymap="$default"/> </action> </actions> <keyboard-shortcut first-keystroke="control alt k" keymap="$default"/> @orbycius
  20. <actions> <action id="JiraMoveTicket" class=".JiraMoveAction" text="Move Ticket" description="Move Ticket in Jira">

    <add-to-group group-id="CutCopyPasteGroup" anchor="last"/> <keyboard-shortcut first-keystroke="control alt k" keymap="$default"/> </action> </actions> <keyboard-shortcut first-keystroke="control alt k" keymap="$default"/> control == cmd @orbycius
  21. <actions> <action id="JiraMoveTicket" class=".JiraMoveAction" text="Move Ticket" description="Move Ticket in Jira">

    <add-to-group group-id="CutCopyPasteGroup" anchor="last"/> <keyboard-shortcut first-keystroke="control alt k" keymap="$default"/> </action> </actions> <add-to-group group-id="CutCopyPasteGroup" anchor="last"/> @orbycius
  22. <actions> <action id="JiraMoveTicket" class=".JiraMoveAction" text="Move Ticket" description="Move Ticket in Jira">

    <add-to-group group-id="CutCopyPasteGroup" anchor="last"/> <keyboard-shortcut first-keystroke="control alt k" keymap="$default"/> </action> </actions> <add-to-group group-id="CutCopyPasteGroup" anchor="last"/> @orbycius
  23. class JiraMovePanel: JPanel() { private val comboTransitions = ComboBox<Transition>() 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. @State(name = "JiraConfiguration", storages = [ Storage(value = "jiraConfiguration.xml") ])

    class JiraComponent: ProjectComponent, Serializable, PersistentStateComponent<JiraComponent> { 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
  31. @State(name = "JiraConfiguration", storages = [ Storage(value = "jiraConfiguration.xml") ])

    class JiraComponent: ProjectComponent, Serializable, PersistentStateComponent<JiraComponent> { 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
  32. class JiraSettings { private var userField: JTextField? = null private

    var passwordField: JPasswordField? = null private var jiraURLField: JTextField? = null … } @orbycius
  33. class JiraSettings(private val project: Project): Configurable { override fun isModified():

    Boolean {} override fun getDisplayName(): String {} override fun apply() {} override fun createComponent(): JComponent? {} } @orbycius
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. • 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
  42. 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<String> = arrayOf("liveTemplates/Droidcon") override fun getHiddenLiveTemplateFiles(): Array<String>? = null } @orbycius
  43. 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<String> = arrayOf("liveTem plates/Droidcon") override fun getHiddenLiveTemplateFiles(): Array<String>? = null } <extensions defaultExtensionNs="com.intellij"> <defaultLiveTemplatesProvider implementation=".DroidconLiveTemplateProvider"/> </extensions> @orbycius
  44. $ 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
  45. $ 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
  46. $ cat template.xml <?xml version="1.0"?> <template format="5" revision="1" name="MVP" minApi="23"

    minBuildApi="23" description="MVP pattern for fragments in modules"> <category value="Droidcon18" /> <parameter id="name" name="Name" type="string" constraints="nonempty" default="MyClass" help="This is the name of the main class." /> <globals file="globals.xml.ftl" /> <execute file="recipe.xml.ftl" /> </template> @orbycius
  47. $ cat template.xml <?xml version="1.0"?> <template format="5" revision="1" name="MVP" minApi="23"

    minBuildApi="23" description="MVP pattern for fragments in modules"> <category value="Droidcon18" /> <parameter id="name" name="Name" type="string" constraints="nonempty" default="MyClass" help="This is the name of the main class." /> <globals file="globals.xml.ftl" /> <execute file="recipe.xml.ftl" /> </template> @orbycius
  48. $ cat template.xml <?xml version="1.0"?> <template format="5" revision="1" name="MVP" minApi="23"

    minBuildApi="23" description="MVP pattern for fragments in modules"> <category value="Droidcon18" /> <parameter id="name" name="Name" type="string" constraints="nonempty" default="MyClass" help="This is the name of the main class." /> <globals file="globals.xml.ftl" /> <execute file="recipe.xml.ftl" /> </template> @orbycius
  49. $ cat template.xml <?xml version="1.0"?> <template format="5" revision="1" name="MVP" minApi="23"

    minBuildApi="23" description="MVP pattern for fragments in modules"> <category value="Droidcon18" /> <parameter id="name" name="Name" type="string" constraints="nonempty" default="MyClass" help="This is the name of the main class." /> <globals file="globals.xml.ftl" /> <execute file="recipe.xml.ftl" /> </template> @orbycius
  50. $ cat recipe.xml.ftl <?xml version="1.0"?> <recipe> <instantiate from="root/src/app_package/di/DaggerComponent.${ktOrJavaExt}.ftl" to="${escapeXmlAttribute(srcOut)}/di/${name}Component.${ktOrJavaExt}" />

    <copy from="root://common/gitignore" to="${escapeXmlAttribute(projectOut)}/.gitignore" /> <mkdir at="${escapeXmlAttribute(resOut)}/drawable" /> </recipe> @orbycius
  51. $ cat recipe.xml.ftl <?xml version="1.0"?> <recipe> <instantiate from="root/src/app_package/di/DaggerComponent.${ktOrJavaExt}.ftl" to="${escapeXmlAttribute(srcOut)}/di/${name}Component.${ktOrJavaExt}" />

    <copy from="root://common/gitignore" to="${escapeXmlAttribute(projectOut)}/.gitignore" /> <mkdir at="${escapeXmlAttribute(resOut)}/drawable" /> <#include "root://common/proguard_recipe.xml.ftl"/> </recipe> @orbycius
  52. $ cat recipe.xml.ftl <?xml version="1.0"?> <recipe> <instantiate from="root/src/app_package/di/DaggerComponent.${ktOrJavaExt}.ftl" to="${escapeXmlAttribute(srcOut)}/di/${name}Component.${ktOrJavaExt}" />

    <copy from="root://common/gitignore" to="${escapeXmlAttribute(projectOut)}/.gitignore" /> <mkdir at="${escapeXmlAttribute(resOut)}/drawable" /> <#include "root://common/proguard_recipe.xml.ftl"/> <merge from="root/build.gradle.ftl" to="${escapeXmlAttribute(projectOut)}/build.gradle" /> </recipe> @orbycius
  53. $ cat recipe.xml.ftl <?xml version="1.0"?> <recipe> <instantiate from="root/src/app_package/di/DaggerComponent.${ktOrJavaExt}.ftl" to="${escapeXmlAttribute(srcOut)}/di/${name}Component.${ktOrJavaExt}" />

    <copy from="root://common/gitignore" to="${escapeXmlAttribute(projectOut)}/.gitignore" /> <mkdir at="${escapeXmlAttribute(resOut)}/drawable" /> <#include "root://common/proguard_recipe.xml.ftl"/> <merge from="root/build.gradle.ftl" to="${escapeXmlAttribute(projectOut)}/build.gradle" /> <open file="${escapeXmlAttribute(srcOut)}/${name}Fragment.kt" /> </recipe> @orbycius
  54. $ cat globals.xml.ftl <globals> <global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}" /> <global id="resOut"

    value="${resDir}" /> <#assign generateKotlin= (((includeKotlinSupport!false) || (language!'Java')?string == 'Kotlin'))> <global id="generateKotlin" type="boolean" value="${generateKotlin?string}" /> <global id="ktOrJavaExt" type="string" value="${generateKotlin?string('kt','java')}" /> </globals> @orbycius
  55. class CopyTemplatesAction: AnAction() { override fun actionPerformed(event: AnActionEvent) { FileUtils.copyTemplates(

    "/androidTemplates/", "/.android/templates/other", event.project!!) } } ~/Sandbox/droidcon18-plugin/src/main/resources/androidTemplates/ @orbycius
  56. @State(name = "DroidconConfiguration", storages = [ Storage(value = "droidconConfiguration.xml") ])

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

    class DroidconComponent: ApplicationComponent, Serializable, PersistentStateComponent<DroidconComponent> { … override fun initComponent() { super.initComponent() if (shouldUpdateTemplates()) { FileUtils.copyTemplates( "/androidTemplates/", "/.android/templates/other", event.project!!) } } } @orbycius
  58. override fun getDescriptions(): Collection<ModuleTemplateGalleryEntry> { val res = ArrayList<ModuleTemplateGalleryEntry>() val

    manager = TemplateManager.getInstance() val templateDirectories = TemplateManager.getExtraTemplateRootFolders() for (dir in templateDirectories) { if (dir.parent.endsWith(".android")) { } } return res } @orbycius
  59. override fun getDescriptions(): Collection<ModuleTemplateGalleryEntry> { val res = ArrayList<ModuleTemplateGalleryEntry>() 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
  60. 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
  61. Write your own Android Studio plugin and automate everything Icons

    made by Smashicons from www.flaticon.com @Orbycius Marcos Holgado
  62. 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