Write your own Android Studio plugin and automate everything

8123b9ca408d9b35d0cf955feb32cfb8?s=47 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.

8123b9ca408d9b35d0cf955feb32cfb8?s=128

Marcos

March 15, 2019
Tweet

Transcript

  1. Write your own Android Studio plugin and automate everything @Orbycius

    Marcos Holgado (AppDevCon version)
  2. Stop wasting time on things that don’t add value @orbycius

  3. Writing a plugin is easy… @orbycius

  4. …and you can do it in kotlin! @orbycius

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

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

  7. File > New... > Project @orbycius

  8. @orbycius

  9. @orbycius

  10. @orbycius

  11. 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'

    } @orbycius compile compile compile compile compile kapt compile compile
  12. 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'

    } @orbycius compile compile compile compile compile kapt compile compile
  13. publishPlugin { token yourToken password yourPassword channels 'nightly' } @orbycius

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

    false @orbycius localPath ASRunPath }
  15. intellij { pluginName ‘demo-plugin’ plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild

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

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

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

    plugins = ['Kotlin', 'git4idea', 'android'] updateSinceUntilBuild false @orbycius } version '2018.2.2'
  19. https://developer.android.com/studio/releases/ @orbycius

  20. intellij { pluginName 'demo-plugin' updateSinceUntilBuild false localPath ASRunPath } @orbycius

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

    plugins = ['Kotlin', 'git4idea', 'android']
  22. @orbycius

  23. <depends>org.jetbrains.kotlin</depends> <depends>Git4Idea</depends> @orbycius <depends>org.jetbrains.android</depends>

  24. <depends>org.jetbrains.kotlin</depends> <depends>Git4Idea</depends> @orbycius <depends>org.jetbrains.android</depends> intellij { pluginName 'demo-plugin' plugins =

    ['Kotlin', 'git4idea', ] updateSinceUntilBuild false 'android' localPath ASRunPath }
  25. 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' // }
  26. 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
  27. 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
  28. 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'
  29. 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'
  30. <depends>org.jetbrains.kotlin</depends> <depends>Git4Idea</depends> <depends>org.jetbrains.android</depends> intellij { pluginName 'droidcon18' plugins = ['Kotlin',

    ‘git4idea', ] updateSinceUntilBuild false } 'android' localPath ASRunPath @orbycius
  31. Plugin Structure @orbycius

  32. Plugin Components @orbycius

  33. Application level components @orbycius

  34. Application level components @orbycius Project level components

  35. Application level components @orbycius Project level components Module level components

  36. Application level components @orbycius Project level components Module level components

  37. class DemoComponent @orbycius } {

  38. class DemoComponent @orbycius } { : ApplicationComponent

  39. class DemoComponent @orbycius } { : ApplicationComponent override fun initComponent()

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

    { super.initComponent() if (isANewVersion()) { // do something } } public var version = 1 public var localVersion = 0
  41. 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 }
  42. @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()
  43. @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)
  44. @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
  45. @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") ])
  46. <application-components> <component> <implementation-class> com.marcosholgado.demo.plugin.components.DemoComponent </implementation-class> </component> </application-components> @orbycius

  47. Actions @orbycius

  48. @orbycius

  49. class JiraMoveAction : AnAction() { override fun actionPerformed(event: AnActionEvent) {

    // do something } } @orbycius
  50. <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"/>
  51. <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"/>
  52. <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"/>
  53. <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
  54. <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"/>
  55. <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"/>
  56. http://www.jetbrains.org/intellij/sdk/docs/basics/action_system.html @orbycius

  57. Extensions and extension points @orbycius

  58. Show me the code @orbycius

  59. You are still writing code! @orbycius

  60. @orbycius JiraMoveAction

  61. @orbycius JiraMoveAction JiraMoveDialog JiraMoveDialogPresenter Model

  62. @orbycius JiraMoveAction JiraMoveDialog JiraMoveDialogPresenter Model JiraMovePanel

  63. @orbycius JiraMoveAction JiraMoveDialog JiraMoveDialogPresenter Model JiraMovePanel

  64. Java Swing Forms @orbycius

  65. None
  66. None
  67. @orbycius @orbycius JiraMoveAction JiraMoveDialog JiraMoveDialogPresenter Model JiraMovePanel

  68. 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
  69. @orbycius JiraMoveAction JiraMoveDialog JiraMoveDialogPresenter Model JiraMovePanel

  70. 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
  71. 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
  72. 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
  73. DialogWrapper @orbycius

  74. DialogWrapper Panel @orbycius

  75. @orbycius

  76. 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
  77. If you are using RxJava… Completable.merge(completable) .subscribeOn(Schedulers.io()) .subscribe( { view.success(selectedItem,

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

    ticket) }, { error -> view.error(error) } ) @orbycius .observeOn( AndroidSchedulers.mainThread())
  79. 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"
  80. 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"
  81. Settings @orbycius

  82. class JiraComponent: ProjectComponent { } @orbycius

  83. @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 = ""
  84. @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 = ""
  85. <project-components> <component> <implementation-class> com.marcosholgado.demo.plugin.components.JiraComponent </implementation-class> </component> </project-components> @orbycius

  86. class JiraSettings { private var userField: JTextField? = null private

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

  88. class JiraSettings(private val project: Project): Configurable { @orbycius override fun

    isModified(): Boolean {} override fun getDisplayName(): String {} override fun apply() {} override fun createComponent(): JComponent? {} }
  89. 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 }
  90. 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 }
  91. 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() {} }
  92. 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? {} }
  93. 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? {} }
  94. 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 } }
  95. 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 } } {
  96. 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, {
  97. 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 {
  98. <extensions defaultExtensionNs="com.intellij"> <defaultProjectTypeProvider type="Android"/> <projectConfigurable instance=".JiraSettings"> </projectConfigurable> </extensions> @orbycius

  99. Live Templates @orbycius

  100. @orbycius

  101. @orbycius

  102. @orbycius

  103. How do you share them? @orbycius

  104. 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
  105. How do you share them? @orbycius

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

  107. 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 }
  108. 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>
  109. Templates @orbycius

  110. @orbycius

  111. @orbycius

  112. @orbycius

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

  114. /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
  115. @orbycius │ ├── NewInstantFeatureModule │ ├── NewJavaLibrary │ └── common

    └── other ├── AidlFile ├── AidlFolder ├── AndroidAutoMediaService ├── AndroidAutoMessagingService ├── AndroidManifest ├── AppActionsResourceFile ├── AppWidget ├── AssetsFolder ├── BlankFragment ├── BroadcastReceiver ├── ContentProvider ├── CustomView ├── Daydream ├── FontFolder ├── IntentService ├── JavaFolder ├── JniFolder ├── LayoutResourceFile
  116. template.xml (UI) @orbycius

  117. template.xml (UI) .ftl files @orbycius

  118. template.xml (UI) .ftl files globals @orbycius

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

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

  121. $ 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." />
  122. $ 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." />
  123. $ 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." />
  124. $ 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." />
  125. $ 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." />
  126. $ cat recipe.xml.ftl <?xml version="1.0"?> <recipe> </recipe> @orbycius

  127. $ 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}" />
  128. $ 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" />
  129. $ 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" />
  130. $ 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"/>
  131. $ 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" />
  132. $ 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" />
  133. $ 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
  134. $ cat FeatureContract.kt.ftl package ${escapeKotlinIdentifiers(packageName)} interface ${name}Contract { interface View

    { } interface Presenter { } } @orbycius
  135. What happens when you upgrade to Android Studio 3.4? @orbycius

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

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

  138. How do you share them? @orbycius

  139. ~/Sandbox/demo-plugin/src/main/resources/androidTemplates/ @orbycius

  140. ~/Sandbox/demo-plugin/src/main/resources/androidTemplates/ @orbycius class CopyTemplatesAction: AnAction() { override fun actionPerformed(event: AnActionEvent)

    { FileUtils.copyTemplates( "/androidTemplates/", "/.android/templates/other", event.project!!) } }
  141. @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 … } } }
  142. @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 … } } }
  143. @orbycius

  144. <extensions defaultExtensionNs="com.android"> <moduleDescriptionProvider implementation=".DemoModuleProvider"/> </extensions> @orbycius

  145. class DemoModuleProvider: ModuleDescriptionProvider { override fun getDescriptions(): Collection<ModuleTemplateGalleryEntry> { @orbycius

    } }
  146. 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
  147. 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
  148. private class DemoModuleEntry(…): ModuleTemplateGalleryEntry { @orbycius }

  149. 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) } }
  150. Write your own Android Studio plugin and automate everything Icons

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