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 WizardTemplateProviderSamplePluginTemplateProviderImpl
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 = "$Activity", // activityTitle = activityClass, 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/$Activity.$")) // Save xml save(mvvmXml(packageName, activityClass), resOut.resolve("layout/$.xml")) // Save viewmodel save(mvvmViewModelKt(packageName, activityClass), srcOut.resolve("viewmodel/$ViewModel.$")) // Save repository save(mvvmRepositoryKt(packageName, activityClass), srcOut.resolve("repository/$Repository.$")) //Save model // save(mvvmModel(packageName, activityClass), srcOut.resolve("model/$Model.$")) }else if (language == Language.Java){ //applicationPackage val mvvmActivity = mvvmActivityJava(projectData.applicationPackage, activityClass, layoutName, packageName) // Save Activity save(mvvmActivity, srcOut.resolve("ui/$Activity.$")) // Save xml save(mvvmXml(packageName, activityClass), resOut.resolve("layout/$.xml")) // Save viewmodel save(mvvmViewModelJava(packageName, activityClass), srcOut.resolve("viewmodel/$ViewModel.$")) // Save repository save(mvvmRepositoryJava(packageName, activityClass), srcOut.resolve("repository/$Repository.$")) //Save model // save(mvvmModel(packageName, activityClass), srcOut.resolve("model/$Model.$")) } }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 $.ui import android.os.Bundle import $.BR import $.R import $.databinding.Activity$Binding import $.mvvm.viewmodel.$ViewModel import me.soushin.tinmvvm.base.DataBindingActivity import me.soushin.tinmvvm.config.DataBindingConfig class $Activity : DataBindingActivity<Activity$Binding,$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.$,variableId = BR.$ViewModel, vmClass = $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 $.repository import me.soushin.tinmvvm.base.BaseRepository class $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 $.viewmodel import android.app.Application import me.soushin.tinmvvm.base.BaseViewModel import $.repository.$Repository class $ViewModel(application: Application) : BaseViewModel<$Repository>(application,$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="$ViewModel" type="$.viewmodel.$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 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
🛠️ A template for Android Studio to create MVPArms and MVPArt Page