Android Studio custom page template

historical background

With the rich and colorful Android native environment, many architectures have been born, such as MVC, MVP, MVVM and MVI. However, using these architectures, we have to create many class files (the project structure will be clearer). In MVC, we will create layout files for Activity/Fragment, Model, Controller and View, and in MVP, we will create Activity/Fragment, Model, Presenter MVVM will create new layout files for Activity/Fragment, Model, ViewModel and View. As we have more and more project pages, creating new files will become more and more cumbersome.

Google dad may have long recognized that our patience is limited. He has provided us with a project template for creating activity / fragment from a very early time. File - > New - > activity / fragment will see that tears will burst into our eyes....

You can see that Google provides many templates. Let's take an example to see how to use these templates.
File - > New - > activity - > empty activity

Activity Name: That is, the name of the new page.
Generate a Layout File: Whether to create a layout file.
Layout Name: That is, the name of the new layout file.
Laucher Activity: That is, whether to run this Activity. 
Package name: That is, the package name where the new page is located.
Source Language: That is, the language of the new page.

Modify these parameters and click Finish. You can see that AS is already creating Activity and layout files for us, and corresponding declarations will be added in AndroidManifest.xml.

Custom template

The template for creating pages provided by Google can easily solve our problems on the premise that we do not use the architecture. However, if we use the architecture and use our own encapsulated framework, the template provided by Google is not so convenient. Therefore, some development leaders began to provide page generation templates for their own framework five years ago, For example, Jess yancoding's MvpArms , it not only provides a template for a single page, but also provides a template for the creation of the whole module. Here, I pay tribute to the leaders who support the spirit of open source.

According to the method of creating pages based on mvparlmstemplate, we only need to put the customized template in the AS installation directory \ plugins\android\lib\templates\activities, and then restart the AS. This method has no problem under Android Studio version 4.0. However, with the update of the AS version, there is no corresponding template folder in the AS installation directory, We are not allowed to operate like this. Later, I found that some students had encountered the same problem and had found a solution Android studio 4.1 custom template . I took photos of gourds and ladles naturally, and unexpectedly successfully rolled up a frame template. The specific steps are as follows:

open Template repository for creating plugins for IntelliJ Platform Click the green #Use this template # step by step to create a template on your github. Then download the template locally using Android studio.
(as of November 29, 2021, the template version is 1.1.0, the IDE version used is 2021.2.3, Java environment: java11)

Add wizard-template.jar

Add the Lib folder in the root directory and add wizard-template.jar in the Android studio installation directory, which is located in / Applications/Android Studio.app/Contents/plugins/android/lib/directory.

Modify build.gradle.kts

Add the dependency of wizard-template.jar in dependencies

dependencies {
    detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.18.1")
    compileOnly(files("lib/wizard-template.jar"))
}

Modify gradle.properties

Modify plugingroup, pluginame_ in gradle.properties, platformPlugins, platformVersion.
pluginSinceBuild indicates the minimum version of the plug-in adapter, and pluginsuntilbuild indicates the maximum version of the plug-in adapter.
Note: pluginSinceBuild doesn't mean that you can adapt to which version you want. It needs to be kept consistent with the JDK version of the corresponding Android Studio. It's too low to support.

# IntelliJ Platform Artifacts Repositories
# -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html

pluginGroup = me.soushin
pluginName = tin-mvvm
# SemVer format -> https://semver.org
pluginVersion = 1.0.71

# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
# for insight into build numbers and IntelliJ Platform versions.
pluginSinceBuild = 201
pluginUntilBuild = 213.*
pluginVerifierIdeVersions = 2020.2.4, 2020.3.4, 2021.1
# IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties
platformType = IC
platformVersion = 2020.2.4

# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
platformPlugins = java, com.intellij.java, org.jetbrains.android, android, org.jetbrains.kotlin

# Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3
javaVersion = 11

# Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion = 7.3

# Opt-out flag for bundling Kotlin standard library.
# See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details.
# suppress inspection "UnusedProperty"
kotlin.stdlib.default.dependency = false

Modify MyProjectManagerListener

Adjust the parent package name of the default listeners to what you want, such as me.soushin. Modify MyProjectManagerListener.

internal class MyProjectManagerListener : ProjectManagerListener {

    override fun projectOpened(project: Project) {
        projectInstance = project
        project.getService(MyProjectService::class.java)
    }

    override fun projectClosing(project: Project) {
        projectInstance = null
        super.projectClosing(project)
    }

    companion object {
        var projectInstance: Project? = null
    }
}

Create a tinmvvm folder in src/main/kotlin to place custom templates

Formally start writing custom templates, taking Activity as an example.

1. Implement WizardTemplateProvider

SamplePluginTemplateProviderImpl

package me.soushin.tinmvvm

import com.android.tools.idea.wizard.template.Template
import com.android.tools.idea.wizard.template.WizardTemplateProvider
import me.soushin.tinmvvm.activity.mvvmActivityTemplate
import me.soushin.tinmvvm.fragment.mvvmFragmentTemplate

class SamplePluginTemplateProviderImpl: WizardTemplateProvider() {
    override fun getTemplates(): List<Template> = listOf(
        mvvmActivityTemplate,
        mvvmFragmentTemplate
    )
}

2. Create mvvmActivityTemplate.kt

This file is used to set the input information when creating an Activity, such as ActivityName, layoutname and packagename

mvvmActivityTemplate.kt

package me.soushin.tinmvvm.activity

import com.android.tools.idea.wizard.template.WizardUiContext
import com.android.tools.idea.wizard.template.template
import com.android.tools.idea.wizard.template.*
import com.android.tools.idea.wizard.template.impl.activities.common.MIN_API
import java.lang.StringBuilder

val mvvmActivityTemplate get() = template {
//    revision = 1
    name = "Tin MVVM Activity"
    description = "Apply to TinMVVM Framed Activity"
    minApi = MIN_API
    minApi = MIN_API
    category = Category.Other
    formFactor = FormFactor.Mobile
    screens = listOf(WizardUiContext.ActivityGallery, WizardUiContext.MenuEntry, WizardUiContext.NewProject, WizardUiContext.NewModule)

    val activityClass = stringParameter {
        name = "Activity Name"
        default = "Main"
        help = "Enter only the name, do not include Activity"
        constraints = listOf(Constraint.NONEMPTY)
    }

    val layoutName = stringParameter {
        name = "Layout Name"
        default = "activity_main"
        help = "Please enter the name of the layout"
        constraints = listOf(Constraint.LAYOUT, Constraint.UNIQUE, Constraint.NONEMPTY)
        suggest = { activityToLayout(createLayoutName(activityClass.value)) }
    }

    val packageName = stringParameter {
        name = "Package name"
        visible = { !isNewModule }
        default = "com.mycompany.myapp"
        constraints = listOf(Constraint.PACKAGE)
        suggest = { packageName }
    }

    val language= enumParameter<Language> {
        name = "Source Language"
        help = "Please select a language"
        default = Language.Kotlin
    }

    widgets(
        TextFieldWidget(activityClass),
        TextFieldWidget(layoutName),
        PackageNameWidget(packageName),
        EnumWidget(language),
    )
//        thumb { File("logo.png") }
    recipe = { data: TemplateData ->
        mvvmActivityRecipe(
            data as ModuleTemplateData,
            activityClass.value,
            layoutName.value,
            packageName.value,
            language.value,
        )
    }
}

fun createLayoutName(className:String):String{
    val array=className.toCharArray()
    val string= StringBuilder()
    array.forEach {
        if (it.isUpperCase()){
            //If the first initial is capitalized, do not underline
            if (string.isNotEmpty()){
                string.append("_")
            }
            string.append(it.toLowerCase())
        }else {
            string.append(it)
        }
    }
    return string.toString()
}

3. Create mvvmActivityRecipe.kt.

This file is used to save the created files to folders, such as Activity, layout files, etc. The original plan was to add the Activity directly to the AndroidManifest, but it was found that there was a problem with the generateManifest, which made the generated plug-in unusable. It was temporarily removed and can only be added manually.

mvvmActivityRecipe.kt

package me.soushin.tinmvvm.activity

import com.android.tools.idea.wizard.template.Language
import com.android.tools.idea.wizard.template.ModuleTemplateData
import com.android.tools.idea.wizard.template.RecipeExecutor
import com.android.tools.idea.wizard.template.impl.activities.common.generateManifest
import me.soushin.tinmvvm.activity.src.app_package.ui.mvvmActivityJava
import me.soushin.tinmvvm.activity.src.app_package.ui.mvvmActivityKt
import me.soushin.tinmvvm.common.repository.mvvmRepositoryJava
import me.soushin.tinmvvm.common.repository.mvvmRepositoryKt
import me.soushin.tinmvvm.common.res.layout.mvvmXml
import me.soushin.tinmvvm.common.viewmodel.mvvmViewModelJava
import me.soushin.tinmvvm.common.viewmodel.mvvmViewModelKt

fun RecipeExecutor.mvvmActivityRecipe(
    moduleData: ModuleTemplateData,
    activityClass: String,
    layoutName: String,
    packageName: String,
    language: Language
) {
    val (projectData, srcOut, resOut) = moduleData
    val ktOrJavaExt = language.extension//projectData.language.extension
    generateManifest(
            moduleData = moduleData,
            activityClass = "${activityClass}Activity",
//            activityTitle = activityClass,
            packageName = "${packageName}.ui",
            isLauncher = false,
            hasNoActionBar = false,
            generateActivityTitle = true,
//            requireTheme = false,
//            useMaterial2 = false
    )

    if (language == Language.Kotlin){
        //applicationPackage
        val mvvmActivity = mvvmActivityKt(projectData.applicationPackage, activityClass, layoutName, packageName)
        // Save Activity
        save(mvvmActivity, srcOut.resolve("ui/${activityClass}Activity.${ktOrJavaExt}"))
        // Save xml
        save(mvvmXml(packageName, activityClass), resOut.resolve("layout/${layoutName}.xml"))
        // Save viewmodel
        save(mvvmViewModelKt(packageName, activityClass), srcOut.resolve("viewmodel/${activityClass}ViewModel.${ktOrJavaExt}"))
        // Save repository
        save(mvvmRepositoryKt(packageName, activityClass), srcOut.resolve("repository/${activityClass}Repository.${ktOrJavaExt}"))
        //Save model
//    save(mvvmModel(packageName, activityClass), srcOut.resolve("model/${activityClass}Model.${ktOrJavaExt}"))
    }else if (language == Language.Java){
        //applicationPackage
        val mvvmActivity = mvvmActivityJava(projectData.applicationPackage, activityClass, layoutName, packageName)
        // Save Activity
        save(mvvmActivity, srcOut.resolve("ui/${activityClass}Activity.${ktOrJavaExt}"))
        // Save xml
        save(mvvmXml(packageName, activityClass), resOut.resolve("layout/${layoutName}.xml"))
        // Save viewmodel
        save(mvvmViewModelJava(packageName, activityClass), srcOut.resolve("viewmodel/${activityClass}ViewModel.${ktOrJavaExt}"))
        // Save repository
        save(mvvmRepositoryJava(packageName, activityClass), srcOut.resolve("repository/${activityClass}Repository.${ktOrJavaExt}"))
        //Save model
//    save(mvvmModel(packageName, activityClass), srcOut.resolve("model/${activityClass}Model.${ktOrJavaExt}"))
    }

}

4. Create mvvmAcitivityKt.

This file is used to create the template code of Activity and adjust it according to your own situation.

mvvmAcitivityKt.kt

package me.soushin.tinmvvm.activity.src.app_package.ui

fun mvvmActivityKt(
    applicationPackage:String?,
    activityClass:String,
    layoutName:String,
    packageName:String
)="""
package ${packageName}.ui

import android.os.Bundle
import ${applicationPackage}.BR
import ${applicationPackage}.R
import ${applicationPackage}.databinding.Activity${activityClass}Binding
import ${applicationPackage}.mvvm.viewmodel.${activityClass}ViewModel
import me.soushin.tinmvvm.base.DataBindingActivity
import me.soushin.tinmvvm.config.DataBindingConfig


class ${activityClass}Activity : DataBindingActivity<Activity${activityClass}Binding,${activityClass}ViewModel>() {

    //Configure the content of the current page. All parameters can be blank
    //Br.xxxviewmodel is the xxxviewmodel in the corresponding xml file generated by the kotlin kapt plug-in by default
    override fun getDataBindingConfig(): DataBindingConfig? {
        return DataBindingConfig(layoutId = R.layout.${layoutName},variableId = BR.${activityClass}ViewModel,
        vmClass = ${activityClass}ViewModel::class.java);
    }
    
    override fun initView(savedInstanceState:Bundle?) {

    }
} 
"""

5. Write mvvmRepository.

This file is the code for the repository layer of the mvvm.

mvvmRepository.kt

package me.soushin.tinmvvm.common.repository

fun mvvmRepositoryKt(
    packageName:String,
    clazz:String
)="""
package ${packageName}.repository

import me.soushin.tinmvvm.base.BaseRepository

class ${clazz}Repository :BaseRepository(){

}
"""

6. Write mvvmViewModel. This is the viewmodel layer of mvvm

mvvmViewModel.kt

package me.soushin.tinmvvm.common.viewmodel

fun mvvmViewModelKt(
    packageName:String,
    clazz:String
)="""
package ${packageName}.viewmodel

import android.app.Application
import me.soushin.tinmvvm.base.BaseViewModel
import ${packageName}.repository.${clazz}Repository

class ${clazz}ViewModel(application: Application) :
       BaseViewModel<${clazz}Repository>(application,${clazz}Repository()) {

}
"""

7. Write mvvmActivityXml. This file is used to generate layout files

mvvmActivityXml.kt

package me.soushin.tinmvvm.common.res.layout

fun mvvmXml(
    packageName: String,
    fragmentClass: String
) = """
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="${fragmentClass}ViewModel"
            type="${packageName}.viewmodel.${fragmentClass}ViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
"""

8. Modify plugin.xml

1. Add the dependency of org.jetbrains.android, org.jetbrains.kotlin, com.intellij.modules.java

2. Modify the values of applicationService and projectService

3. Modify the listener value in applicationListenersv

4. Add wizardTemplateProvider to the class just added

<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
<idea-plugin>
    <id>me.soushin.tin-mvvm</id>
    <name>TinMVVM</name>
    <vendor>soushin</vendor>

    <!-- Product and plugin compatibility requirements -->
    <!-- https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html -->
    <depends>org.jetbrains.android</depends>
    <depends>org.jetbrains.kotlin</depends>
    <depends>com.intellij.modules.java</depends>
    <depends>com.intellij.modules.platform</depends>

    <extensions defaultExtensionNs="com.intellij">
        <applicationService serviceImplementation="me.soushin.template.services.MyApplicationService"/>
        <projectService serviceImplementation="me.soushin.template.services.MyProjectService"/>
    </extensions>

    <applicationListeners>
        <listener class="me.soushin.template.listeners.MyProjectManagerListener"
                  topic="com.intellij.openapi.project.ProjectManagerListener"/>
    </applicationListeners>
    <extensions defaultExtensionNs="com.android.tools.idea.wizard.template">
        <wizardTemplateProvider implementation="me.soushin.tinmvvm.SamplePluginTemplateProviderImpl" />
    </extensions>

</idea-plugin>

Generate jar file

Double click gradle - > tasks - > build - > jar and wait a few seconds to see the packaged plug-in in build - > lib.
Then, in the AS, plug ins - > set the gear in the upper right corner - > install plug from disk... - > select jar package - > Apply after installation - > restart the AS. If there is an error in the plug-in writing, you can see the corresponding error message in the Event Log in the lower right corner of the AS and modify it.

At present, my framework template has been adapted to Android Studio Arctic Fox 203.

Reference documents:

🛠️ TinMvvm framework Android studio page template plug-in

🛠️   Build the official architecture of mvpals with one click, so that novices can open the world of mvpals in one second and avoid the trouble of cumbersome project configuration

🛠️ A template for Android Studio to create MVPArms and MVPArt Page

Tags: Android Studio IntelliJ IDEA

Posted on Mon, 29 Nov 2021 20:21:16 -0500 by romilbm