티스토리 뷰
개발을 하면서 문득 그런 생각을 하였습니다. 반복적으로 생성하는 클래스를 한번에 생성할 수는 없을까?
개발을 하다보면 여러 프로젝트를 경험할테고 반복적으로 생성되는 클래스들이 많을 것입니다. 또한 협업을 하다보면 동일한 화면을 구현한다고 해도 개발자 각자의 스타일로 개발하다보면 보일러 플레이트 코드가 발생하게 될 수 있습니다.
이러한 문제점을 해결하기 위해서 Template을 만들어 정형화 한다면, 개발자간의 소통 부재와 각자의 스타일로 만들어서 발생할 수 있는 보일러플레이트 코드들을 최소화 할 수 있을 것입니다.
이번 시간은 Android Studio 에서 Template 을 만들 수 있는 방법에 대해서 공유하려고 합니다.
플러그인 만들기
Android Studio Dolphin(213.7172.25) 을 활용해서 플러그인을 만들었습니다.
Android Studio 버전은 Android Studio
> About Android Studio
에서 확인 할 수 있습니다.
플로그인을 만들기 앞써 IntelliJ Platform Plugin Tempalte GitHub Repository을 개인 GitHub 에서 사용할 수 있도록 Use this template
해야합니다.
IntelliJ Platform Plugin Tempalte 에 접속하면 아래와 같이 Use this template
가 노출될 것입니다.
해당 버튼을 클릭하여 개인 GitHub의 Repository로 설정합니다.
gradle.properties
StudioCompilePath
,StudioRunPath
는 IDE 실행 시 Intellij 대신 Android Studio를 사용하기 위해 경로를 설정합니다.platformPlugins
에서 사용 할 java, android, kotlin 을 세팅합니다.
# IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html
pluginGroup = com.github.faithdeveloper.testplugintemplate
pluginName = TestPlugInTemplate // 플러그인 이름
# SemVer format -> https://semver.org
pluginVersion = 0.0.3 // 플러그인 버전
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 213
pluginUntilBuild = 222.*
# IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension
platformType = IC
platformVersion = 2021.3.3
# 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
# Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion = 7.5.1
# Opt-out flag for bundling Kotlin standard library -> https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library
# suppress inspection "UnusedProperty"
kotlin.stdlib.default.dependency = false
StudioCompilePath = /Applications/Android Studio.app/Contents
StudioRunPath=/Applications/Android Studio.app/Contents
Build gradle.kts 설정하기
- build.gradle.kts
intellij.localPath
에StudioRunPath
를 설정합니다. 위와 같이 설정하는 이유는 나중에runIde
시 Intellij가 아닌 Android Studio가 열리게 하기 위해서입니다.
import org.jetbrains.changelog.markdownToHTML
fun properties(key: String) =project.findProperty(key).toString()
plugins{
// Java support
id("java")
// Kotlin support
id("org.jetbrains.kotlin.jvm")version"1.7.10"
// Gradle IntelliJ Plugin
id("org.jetbrains.intellij")version"1.0"
// Gradle Changelog Plugin
id("org.jetbrains.changelog")version"1.3.1"
// Gradle Qodana Plugin
id("org.jetbrains.qodana")version"0.1.13"
// detekt linter - read more: https://detekt.github.io/detekt/gradle.html
id("io.gitlab.arturbosch.detekt")version"1.17.1"
// ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
id("org.jlleitschuh.gradle.ktlint")version"10.0.0"
}
...
// Configure Gradle IntelliJ Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
intellij{
pluginName.set(properties("pluginName"))
version.set(properties("platformVersion"))
type.set(properties("platformType"))
intellij.localPath.set(properties("StudioRunPath"))
// Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file.
plugins.set(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty))
}
...
tasks{
instrumentCode{
compilerVersion.set("213.7172.25") // Android Studio Version
}
...
Plugin.xml 설정하기
- src/main/resource/META-INF/plugin.xml
gradle.properties
에서 정의한pluginGroup
,pluginName
을 id와 name에 설정합니다.- 프로젝트 구조를
pluginGroup
과 동일하게 설정합니다. - vendor는
faithdeveloper
으로 설정하였습니다. custom 하게 하시면 됩니다.
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
<idea-plugin>
<id>com.github.faithdeveloper.testplugintemplate</id>
<name>TestPlugInTemplate</name>
<vendor>faithdeveloper</vendor>
<depends>com.intellij.modules.platform</depends>
<!-- 우리가 사용할 디펜던시를 정의합니다. -->
<depends>org.jetbrains.android</depends>
<depends>org.jetbrains.kotlin</depends>
<depends>com.intellij.modules.java</depends>
<depends>com.intellij.modules.platform</depends>
<depends>com.intellij.modules.androidstudio</depends>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="com.github.faithdeveloper.testplugintemplate.services.MyApplicationService"/>
<projectService serviceImplementation="com.github.faithdeveloper.testplugintemplate.services.MyProjectService"/>
</extensions>
<!-- WizardTemplateProviderImpl 파일을 생성해줍니다. 해당 클래스에서 실행될 template 을 정의할 예정입니다. -->
<extensions defaultExtensionNs="com.android.tools.idea.wizard.template">
<wizardTemplateProvider implementation="com.github.faithdeveloper.testplugintemplate.WizardTemplateProviderImpl" />
</extensions>
<applicationListeners>
<listener class="com.github.faithdeveloper.testplugintemplate.listeners.MyProjectManagerListener"
topic="com.intellij.openapi.project.ProjectManagerListener"/>
</applicationListeners>
</idea-plugin>
MyProjectManagerListener 설정하기
MyProjectManagerListener.kt
Project Open 과 Close에 따른 리스너 처리를 합니다.
internal class MyProjectManagerListener : ProjectManagerListener {
//Android Studio에서 프로젝트를 열 때마다 해당 function 호출
override fun projectOpened(project: Project) {
println("######### WellCommon Project Name : ${project.name} #########")
// 만약 특정 prefix Name 만 동작하고 싶을 시 주석 삭제
// project.name.startsWith("Test", ignoreCase = true)
projectInstance = project
// }
project.service<MyProjectService>()
}
//Android Studio에서 프로젝트를 닫을 때마다 해당 function 호출
override fun projectClosing(project: Project) {
// 만약 특정 prefix Name 만 동작하고 싶을 시 주석 삭제
// project.name.startsWith("Test", ignoreCase = true)
projectInstance = null
// }
super.projectClosing(project)
}
companion object {
var projectInstance: Project? = null
}
}
WizardTemplateProviderImpl 설정하기
WizardTemplateProviderImpl.kt
listOf 로 Template 리스트를 정의합니다.
아래 코드는 recyclerActivitySetupTemplate 라는 Template 을 선언한 것입니다.
만약, scrollActivitySetupTemplate 도 추가한다면 listOf(recyclerActivitySetupTemplate scrollActivitySetupTemplate) 이렇게 구성하면 됩니다.
class WizardTemplateProviderImpl : WizardTemplateProvider() {
override fun getTemplates(): List<Template> =listOf(recyclerActivitySetupTemplate)
}
RecyclerActivitySetupTemplate 설정하기
RecyclerActivitySetupTemplate.kt
생성할 Template 의 기본적인 정보를 정의합니다.
Template 을 선택하면 사용자에게 보여질 화면에서 입력 받을 파라미터와 Recipe를 정의합니다.
valrecyclerActivitySetupTemplate
get() = template{
name = "MVVM RecyclerView Activity"
description = "This Template make RecyclerView Template with MVVM Architecture."
minApi = 21
category = Category.Other// Check other categories
formFactor = FormFactor.Mobile
screens =listOf(
WizardUiContext.FragmentGallery, WizardUiContext.MenuEntry,
WizardUiContext.NewProject, WizardUiContext.NewModule
)
val packageNameParam =defaultPackageNameParameter
val className = stringParameter{
name = "Class Name"
default = "" //ex) default = "RecyclerViewActivity"
help = "Please, Input Class Name."
constraints =listOf(Constraint.NONEMPTY)
}
val activityLayoutName = stringParameter{
name = "Activity Layout Name."
default = "" //ex) default = "RecyclerView"
help = "Please, Input Layout Name"
constraints =listOf(Constraint.LAYOUT, Constraint.UNIQUE, Constraint.NONEMPTY)
suggest ={activityToLayout(className.value.toSnakeCase())}
}
widgets(
TextFieldWidget(className),
TextFieldWidget(activityLayoutName),
PackageNameWidget(packageNameParam)
)
recipe = {
data: TemplateData->
// Template 생성
mvvmRecyclerActivitySetup(
data as ModuleTemplateData,
packageNameParam.value,
className.value,
activityLayoutName.value
)
}
}
RecyclerActivitySetupRecipe 설정
RecyclerActivitySetupRecipe.kt
mvvmRecyclerActivitySetup
이름의 RecipeExecutor
확장 함수 정의를 합니다.
Template 생성 할 위치 설정 및 파일을 생성하게 됩니다.
fun RecipeExecutor.mvvmRecyclerActivitySetup(
moduleData: ModuleTemplateData,
packageName: String,
className: String,
activityLayoutName: String,
) {
val (projectData, _, _, manifestOut) = moduleData
val project = projectInstance ?: run {
println("projectInstance is null")
return
}
addAllKotlinDependencies(moduleData)
val virtualFiles = ProjectRootManager.getInstance(project).contentSourceRoots
val virtSrc = virtualFiles.firstOrNull { it.path.contains("app/src/main/java") } ?: return
val virtRes = virtualFiles.firstOrNull { it.path.contains("app/src/main/res") } ?: return
val directorySrc = PsiManager.getInstance(project).findDirectory(virtSrc) ?: return
val directoryRes = PsiManager.getInstance(project).findDirectory(virtRes) ?: return
val activityClass = "${className}Activity".replaceFirstChar { it }
val adapterClass = "${className}RecyclerAdapter".replaceFirstChar { it }
val viewHolderClass = "${className}ItemViewHolder".replaceFirstChar { it }
val viewModelClass = "${className}ViewModel".replaceFirstChar { it }
println("[check]packageName = $packageName")
// Activity 추가 시 Manifest 에 추가를 원할 시 주석제거 (개선필요)
// mergeXml(
// manifestTemplateXml(projectData = projectData, packageName = packageName, activityClassName = "${className}Activity"),
// manifestOut.resolve("AndroidManifest.xml")
// )
createRecyclerActivity(packageName, className, activityLayoutName, projectData)
.save(directorySrc, packageName, "$activityClass.kt")
createRecyclerAdapter(packageName, className)
.save(directorySrc, "$packageName.adapter", "$adapterClass.kt")
createViewHolder(packageName, className)
.save(directorySrc, "$packageName.viewholder", "$viewHolderClass.kt")
createViewModel(packageName, className)
.save(directorySrc, "$packageName.viewmodel", "$viewModelClass.kt")
createRecyclerActivityLayout(packageName, className)
.save(directoryRes, "layout", "${activityLayoutName}.xml")
createViewHolderLayout()
.save(directoryRes, "layout", "item_${className.toSnakeCase()}.xml")
}
Template 생성 시 각 파일 정의하기
Activity.kt
Adapter.kt
AndroidManifest.kt
ViewHolder.kt
ViewModel.kt
선언된 String 이 그대로 생성되므로 import나 ClassName 등의 Template 생성 시 구성하려는 것을 정확히 입력합니다.
fun createRecyclerActivity(
packageName: String,
className: String,
activityLayoutName: String,
projectData: ProjectTemplateData
) = """
package $packageName
import ${projectData.applicationPackage}.R
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class ${className}Activity : AppCompatActivity() {
}
""".trimIndent()
Plugin Install
Gradle Run Plugin을 실행하면 Build/libs 안에 jar 파일을 볼 수 있습니다.
Gradle Run Plugin 하는 방법은 여러방법이 있는데 쉽게 할 수 있는 방법은 Android Studio의 Build 를 Run Plugin
로 설정하면됩니다.
생성된 jar 파일을 Android Studio Plugins에서 세팅하면 지금 만든 Template을 사용할 수 있습니다.
PlugIn 사용 예시
마무리
이미 저와 같은 Template 을 만드는 것에 대해 고민 한 사람들을 쉽게 발견할 수 있었으며, 개발 포스트로 다양한 예제 소스를 제공 있었습니다. 이번 포스트는 개발 포스트에 올라온 것을 참고하여 공유하였는데요.
Template을 자신의 프로젝트의 특성에 맞게 구성한다면 보일러플레이트 코드를 최소화 할 수 있으며, 개발 속도도 향상될 것으로 예상됩니다.
참고
스마트한 개발을 위한 Android Studio 플러그인 템플릿
https://github.com/JetBrains/intellij-platform-plugin-template
https://github.com/FaithDeveloper/TestPlugInTemplate
'프로그래밍 > Android' 카테고리의 다른 글
Android Kotlin Coroutines 과 Flow 사용해보기 (0) | 2023.05.23 |
---|---|
Kotlin Lamda Return 가능한가? (2) | 2023.05.09 |
[Android] ViewModel와 AAC ViewModel의 차이는 무엇일까? (ViewModel vs AAC ViewModel) (0) | 2022.06.29 |
MacBookPro M1 환경에서 Android Studio 사용하기 (0) | 2021.07.17 |
[이슈] sing WebView from more than one process at once with the same data directory... (2) | 2021.04.09 |
- java
- 고시문
- DI
- Android Studio
- 패턴
- MCC
- missioon
- 점수판
- 선교
- 디자인패턴
- 임용고시
- issue
- 안드로이드
- 코틀린
- 고시문헬퍼
- missionchina
- Kotlin
- 미션차이나센터
- view
- Android
- flutter
- RXjava
- 스코어헬퍼
- 탁구
- IT
- IOS
- swift
- 알고리즘
- push
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |