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

Write your own Android Studio plugin and automate everything

Marcos
March 15, 2019

Write your own Android Studio plugin and automate everything

Let’s face it, we waste a lot of time on things that don’t add any value. Maybe you use the same pattern in different places and copy&paste the same files over and over. Probably the same happens whenever you create a new app or module. What about all the time that you spend in Jira or other tools on tasks like moving tickets or logging time. What if you could automate all that? 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 using Kotlin, create templates to generate code automatically, write live templates to insert frequently-used constructors in your code, easily integrate Android Studio with Jira or other tools, create a brand new full App in minutes using feature modules and much more.

Marcos

March 15, 2019
Tweet

More Decks by Marcos

Other Decks in Programming

Transcript

  1. intellij { pluginName ‘demo-plugin’ plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild

    false @orbycius ASRunPath = /Applications/Android Studio.app/Contents gradle.properties localPath ASRunPath }
  2. localPath ASRunPath intellij { pluginName ‘demo-plugin’ plugins = ['Kotlin', 'git4idea',

    'android'] updateSinceUntilBuild false @orbycius localPath ASRunPath }
  3. localPath ASRunPath // alternativeIdePath '/Applications/Android Studio.app' intellij { pluginName ‘demo-plugin’

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

    plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false @orbycius } version '2018.2.2'
  5. localPath ASRunPath <depends>org.jetbrains.kotlin</depends> <depends>Git4Idea</depends> @orbycius <depends>org.jetbrains.android</depends> intellij { pluginName 'demo-plugin'

    plugins = ['Kotlin', 'git4idea', ] updateSinceUntilBuild false 'android' version 'IC-2018.2.2' alternativeIdePath '/Applications/Android Studio.app' // }
  6. 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
  7. 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
  8. 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 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'
  9. 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 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'
  10. class DemoComponent @orbycius } { : ApplicationComponent override fun initComponent()

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

    { super.initComponent() if (isANewVersion()) { // do something } } public var version = 1 public var localVersion = 0
  12. class DemoComponent @orbycius } { : 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 }
  13. @orbycius 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()
  14. @orbycius 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<DemoComponent> XmlSerializerUtil.copyBean(state, this)
  15. @orbycius 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<DemoComponent>, Serializable XmlSerializerUtil.copyBean(state, this) } } } updateVersion() override fun initComponent() { super.initComponent() if (isANewVersion()) { // do something
  16. @orbycius 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<DemoComponent>, Serializable XmlSerializerUtil.copyBean(state, this) } } } updateVersion() override fun initComponent() { super.initComponent() if (isANewVersion()) { // do something @State(name = "DemoConfiguration", storages = [ Storage(value = "demoConfiguration.xml") ])
  17. <actions> </actions> @orbycius <action id="JiraMoveTicket" class=".JiraMoveAction" text="Move Ticket" description="Move Ticket

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

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

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

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

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

    in Jira"> </action> <keyboard-shortcut first-keystroke="control alt k" keymap="$default"/> <add-to-group group-id="CutCopyPasteGroup" anchor="last"/>
  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): { @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(...) } @orbycius private val panel: JiraMovePanel = JiraMovePanel() DialogWrapper(true) override fun createCenterPanel(): JComponent = panel
  25. 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(...) } @orbycius private val panel: JiraMovePanel = JiraMovePanel() DialogWrapper(true) override fun createCenterPanel(): JComponent = panel
  26. 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(...) } @orbycius private val panel: JiraMovePanel = JiraMovePanel() DialogWrapper(true) override fun createCenterPanel(): JComponent = panel
  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(...) @orbycius
  28. If you are using RxJava… Completable.merge(completable) .subscribeOn(Schedulers.io()) .subscribe( { view.success(selectedItem,

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

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

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

    ticket) }, { error -> view.error(error) } ) @orbycius .observeOn( SwingSchedulers.edt()) compile "com.github.akarnokd:rxjava2-swing:0.3.0"
  32. @State(name = "JiraConfiguration", storages = [ Storage(value = "jiraConfiguration.xml") ])

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

    class JiraComponent: ProjectComponent, Serializable, PersistentStateComponent<JiraComponent> { 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 var jiraUrl: String = "" var username: String = "" var password: String = "" var regex: String = ""
  34. class JiraSettings { private var userField: JTextField? = null private

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

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

    String {} override fun apply() {} override fun createComponent(): JComponent? {} @orbycius private var modified = false override fun isModified(): Boolean = modified }
  37. 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 }
  38. 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" @orbycius override fun apply() {} }
  39. 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 } @orbycius override fun createComponent(): JComponent? {} }
  40. 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 } @orbycius override fun createComponent(): JComponent? {} }
  41. 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 } }
  42. class JiraSettings(private val project: Project): Configurable { … private var

    modified = false override fun isModified(): Boolean = modified @orbycius 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 } } {
  43. class JiraSettings(private val project: Project): … private var modified =

    false override fun isModified(): Boolean = modified @orbycius 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, {
  44. 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 DocumentListener {
  45. How do you share them? @orbycius • Export to a

    .jar file and import • Copy & paste template xml https://www.jetbrains.com/help/idea/2018.1/sharing-live-templates.html
  46. How do you share them? $ mv ~/Library/Preferences/AndroidStudio3.3/templates/Demo.xml ~/Sandbox/demo-plugin/src/main/resources/liveTemplates/Demo.xml @orbycius

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

    class DemoLiveTemplateProvider: DefaultLiveTemplatesProvider { override fun getDefaultLiveTemplateFiles(): Array<String> = arrayOf("liveTemplates/Demo") override fun getHiddenLiveTemplateFiles(): Array<String>? = null } <extensions defaultExtensionNs="com.intellij"> <defaultLiveTemplatesProvider implementation=".DemoLiveTemplateProvider"/> </extensions>
  48. /Applications/Android\ Studio.app/Contents/plugins/android/lib/templates/ @orbycius $ tree -L 2 -d ├── activities

    │ ├── AndroidTVActivity │ ├── AndroidThingsActivity │ ├── AndroidThingsPeripheralActivity │ ├── BasicActivity │ ├── BlankWearActivity │ ├── BottomNavigationActivity │ ├── EmptyActivity │ ├── FullscreenActivity │ ├── GoogleAdMobAdsActivity │ ├── GoogleMapsActivity │ ├── GoogleMapsWearActivity │ ├── LoginActivity │ ├── MasterDetailFlow │ ├── NavigationDrawerActivity │ ├── ScrollActivity │ ├── SettingsActivity │ ├── TabbedActivity │ ├── ViewModelActivity
  49. @orbycius │ ├── NewInstantFeatureModule │ ├── NewJavaLibrary │ └── common

    └── other ├── AidlFile ├── AidlFolder ├── AndroidAutoMediaService ├── AndroidAutoMessagingService ├── AndroidManifest ├── AppActionsResourceFile ├── AppWidget ├── AssetsFolder ├── BlankFragment ├── BroadcastReceiver ├── ContentProvider ├── CustomView ├── Daydream ├── FontFolder ├── IntentService ├── JavaFolder ├── JniFolder ├── LayoutResourceFile
  50. $ cat template.xml <?xml version="1.0"?> <globals file="globals.xml.ftl" /> <execute file="recipe.xml.ftl"

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

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

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

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

    /> </template> @orbycius <template format="5" revision="1" name="MVP" minApi="23" minBuildApi="23" description="MVP pattern for fragments in modules"> <category value="Demo" /> <parameter id="name" name="Name" type="string" constraints="nonempty" default="MyClass" help="This is the name of the main class." />
  55. $ cat recipe.xml.ftl <?xml version="1.0"?> <recipe> </recipe> @orbycius <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" />
  56. $ cat recipe.xml.ftl <?xml version="1.0"?> <recipe> </recipe> @orbycius <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" />
  57. $ cat recipe.xml.ftl <?xml version="1.0"?> <recipe> </recipe> @orbycius <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"/>
  58. $ cat recipe.xml.ftl <?xml version="1.0"?> <recipe> </recipe> @orbycius <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" />
  59. $ cat recipe.xml.ftl <?xml version="1.0"?> <recipe> </recipe> @orbycius <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" />
  60. $ 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
  61. @State(name = "DemoConfiguration", storages = [ Storage(value = "demoConfiguration.xml") ])

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

    class DemoComponent: ApplicationComponent, Serializable, PersistentStateComponent<DemoComponent> { override fun initComponent() { super.initComponent() if (shouldUpdateTemplates()) { FileUtils.copyTemplates( "/androidTemplates/", "/.android/templates/other", event.project!!) @orbycius … } } }
  63. class DemoModuleProvider: ModuleDescriptionProvider { override fun getDescriptions(): Collection<ModuleTemplateGalleryEntry> { @orbycius

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

    val res = ArrayList<ModuleTemplateGalleryEntry>() 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
  65. private class DemoModuleEntry(…): ModuleTemplateGalleryEntry { @orbycius … override fun createStep(model:

    NewModuleModel): SkippableWizardStep<*> { val basePackage = NewProjectModel .getSuggestedProjectPackage(model.project.value, false) return ConfigureAndroidModuleStep( model, formFactor, minSdkLevel, basePackage, isLibrary, false, name) } }
  66. Write your own Android Studio plugin and automate everything Icons

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

    Marcos Holgado (plugin) https://github.com/marcosholgado/demo-plugin (demo) https://github.com/marcosholgado/plugin-app