2022년 10월 10일 월요일

AndroidStudio-템플릿으로 일관되고 빠르게 프로젝트 생성

AndroidStudio-템플릿으로 일관되고 빠르게 프로젝트 생성

템플릿으로 일관되고 빠르게 프로젝트 생성

액티비티,플래그먼트,리사이클뷰세트 등을 만들때, 템플릿을 이용해서 기준포맷된 화일을 빠르게 생성하도록할수 있다. 협업작업때 기존화일을 카피해서 안쓸꺼만 지우고 하다보면 시간도 가고 실수도 하는데 차라리 템플릿으로 만들어 두면 좋다.
또, 새로운 프로젝트를 템플릿으로 만들어 두면 빠르게 새로운 프로젝트를 시작할수 있다.
일단 , 참고 했던곳,
https://medium.com/androidstarters/mastering-android-studio-templates-ed8fdd98cb78
https://medium.com/gits-apps-insight/tutorial-create-your-own-template-for-android-studio-1aaa9b4cb18

AndroidStudio\plugins\android\lib\templates\밑에 있는 파일대로 만들면된다.

Android Studio 4.1 에서부터는 안된다. Intellij 에서 플러그인을 만들어서 제공하는 방법으로 해야한단다. 왜…좋았는데…왜…?

그래서 다시 만들었다.

Android Studio 4.1 이상에서 Template Plugin 으로 템플릿생성

기존의 file template 기능이 Android Studio 4.1 부터 갑자기! 없어져서 템플릿파일들을 생성해서 사용하는것이 불가능하게 되었다.
대신 IntelliJ Plug-In형태로 만들어서 사용하면 되는데, 무려 Plug-in을 만들어야 되기 때문에 귀찮은 면도 있다.

대신 프로그램만들듯이 템플릿 만들수 있기때문에 익숙해진다면 나쁘지도 않을듯하다.

  1. 먼저 intelliJ 에서 Gradle 에서 플러그인 프로젝트를 생성한다.
    enter image description here

enter image description here
Artifact 항목의 GroupId,ArtifactId,Version은 일단 대충입력하자.

  1. Android Studio 가 익숙하니까 Android Studio에서 위의 plugin프로젝트를 열자.
  2. 아래그림 처럼 buildPlugin 태스크를 실행하면 /build/libs 폴더에 xxx-SNAPSHOT.jar 파일이 보인다. 실행해봤자 Android Studio에서는 설치가 안된다.
    enter image description here
build.gradle파일의 

intellij {  
  version '2020.3.3'  
}

이부분에 android와 kotlin을 추가하자
intellij {  
  plugins = ['Kotlin', 'android']  
    localPath '/Applications/Android Studio.app/Contents'  
  version '2020.3.3'  
}
  1. 이제 다시 빌드하고 나서 , AndroidStudio->Preference->Plugins 에서 [톱니바퀴]->install plugin from disk… 로 /build/libs 폴더에 xxx-SNAPSHOT.jar 파일을 선택하면 설치가 되고 목록에도 나타난다.
    enter image description here

  2. 껍데기 뿐인 플러그인이지만 , 이런식으로 플러그인을 추가/변경할수 있다. 프로젝트에서만 사용할것이기 때문에 마켓에는 안올리고 이렇게 팀내부에서 플러그인형태로 공유해서 쓰는것을 전제로 작성할거다

  3. IntellJ Plugin 은 resources/META-INF/plugin.xml 파일에서 설정값들을 수정하거나 추가하여 작성한다.

  4. plugin 은 intelliJ 에디터를 좀더 편하게 쓰기위해서 기능을 확장하는 것이기에 src 폴더에 갖가지 기능을 구현할수도 있지만 우리 목적은 android 템플릿을 만드는것이니까 템플릿구성까지만 해보자

  5. build.gradle 파일에 안드로이드 관련 설정을 추가하고 JDK도 8버젼으로 맞춰준다.

plugins {  
  id 'org.jetbrains.intellij' version '0.7.2'  
  id 'org.jetbrains.kotlin.jvm' version '1.4.10'  
}  
  
group 'com.miserutv'  
version '0.0.1'  
  
repositories {  
  mavenCentral()  
}  
  
dependencies {  
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"  
  testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'  
  testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'  
}  
  
compileKotlin {  
  kotlinOptions.jvmTarget = "1.8"  
}  
  
compileTestKotlin {  
  kotlinOptions.jvmTarget = "1.8"  
}  
  
// See https://github.com/JetBrains/gradle-intellij-plugin/  
intellij {  
  plugins = ['Kotlin', 'android']  
    localPath '/Applications/Android Studio.app/Contents'  
  version '2020.3.3'  
}  
patchPluginXml {  
  changeNotes """  
 Add change notes here.<br> <em>most HTML tags may be used</em>"""}  
test {  
  useJUnitPlatform()  
}
  1. plugin.xml 파일을 수정한다. 여기서 중요한것이 intelliJ 의 Template Wizard기능을 이용할것이기 때문에 List형식의 Template 클래스목록을 돌려주는 클래스를 지정하고 해당 만들어야한다.
<idea-plugin>  
    <id>com.miserutv.templates</id>  
    <name>Plugin display name here</name>  
    <vendor email="support@yourcompany.com"  
  url="http://www.yourcompany.com">  
        YourCompany  
    </vendor>  
  
    <description><![CDATA[  
    Enter short description for your plugin here.<br>  
    <em>most HTML tags may be used</em>  
    ]]></description>  
  
    <!-- please see https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html  
 on how to target different products -->  <depends>com.intellij.modules.platform</depends>  
    <depends>org.jetbrains.android</depends>  
    <depends>com.intellij.modules.androidstudio</depends>  
  
    <extensions defaultExtensionNs="com.android.tools.idea.wizard.template"> 
        <!-- Add your extensions here -->  
  <tools.idea.wizard.template.wizardTemplateProvider  
  implementation="com.miserutv.templates.recipes.TemplateWizardProvider"/>  
    </extensions>  
  
    <actions>  
        <!-- Add your actions here -->  
  </actions>  
</idea-plugin>
  1. 자,이제 src/main/kotlin 밑에com.miserutv.templates.recipe 라는 패키지경로를 만들고 TemplateWizardProvider.kt 파일을 만들어서 템플릿목록을 반환하도록 하자.
//com.miserutv.templates.recipe  폴더에 TemplateWizardProvider.kt
import com.android.tools.idea.wizard.template.WizardTemplateProvider  
  
class TemplateWizardProvider: WizardTemplateProvider() {  
    override fun getTemplates() = listOf(HelloWorldTemplate)  
}
  1. listOf(HelloWorldTemplate) 에는 여러개의 템플릿을 지정할수도있는데, 즉 한번에 여러군데의 템플릿을 지정할수 있어서 프로젝트 전반에 걸친 다양한 템플릿을 등록할수도 있는거다.
    일단 FeatureScreenTemplate 파일이 없으니 com.miserutv.templates.recipe 폴더에(아니면 template 별로 폴더를 만들어서 지정해도 좋다.) HelloWorldTemplate object 파일을 만들어서 Template interface를 구현해보자.
object HelloWorldTemplate : Template {  
    override val category: Category  
        get() = Category.Activity // New 할때, Activity목록,Fragment목록,Other 목록등 나타날 카테고리지정한다.  
  override val constraints: Collection<TemplateConstraint>  
        get() = emptyList()     //AndroidX, kotlin 사용제약을 할지 결정  
  override val description: String  
        get() = "My description" // 설명  
  override val documentationUrl: String?  
        get() = null // 설명주소  
  override val formFactor: FormFactor  
        get() = FormFactor.Mobile //어떤 종류 Mobile,Wear등  
  override val minCompileSdk: Int  
        get() = MIN_API // 컴파일 최소 API버젼  
  override val minSdk: Int  
        get() = MIN_API // 컴파일 최소 API버젼  
  override val name: String  
        get() = "Hello Android Template" // 선택목록에 표시될 템플릿이름  
  override val recipe: Recipe  
        get() = { // 레시피에는 생성하고자할 Acvitiy,Fragment,Layout,Value XMl등을 지정하여 프로젝트폴더에 복사되도록한다.  
  helloWorldActivityRecipe(  
                    it as ModuleTemplateData,  
                    activityClassInputParameter.value,  
                    activityTitleInputParameter.value,  
                    layoutNameInputParameter.value,  
                    packageName.value  
  ) // .value 는 화면에서 받은 입력값이다.  helloWorldActivityRecipe파일은 밑에서 만들꺼다
  }  
  override val revision: Int  
        get() = 1 // 템플릿 버젼  
  override val uiContexts: Collection<WizardUiContext>  
        get() = listOf(WizardUiContext.ActivityGallery, WizardUiContext.MenuEntry, WizardUiContext.NewProject, WizardUiContext.NewModule) // 어떤 메뉴등에서 템필릿이 표시될지 지정  
  override val widgets: Collection<Widget<*>>  
        get() = listOf(  
                  TextFieldWidget(activityTitleInputParameter),  
                  TextFieldWidget(activityClassInputParameter),  
                  TextFieldWidget(layoutNameInputParameter),  
                  PackageNameWidget(packageName),  
                  LanguageWidget()  
            ) // 지금 템플릿을 New로 대화상자가 떴을때 , 필요한 위젯(액티비티이름, 레이아웃이름등)들을 입력할수있도록한다.  
  
  override fun thumb(): Thumb {  
        return Thumb { findResource(this.javaClass, File("template_.png"))}  
  }  
  
    //액티비티 생성이름  
  val activityClassInputParameter = stringParameter {  
  name = "Activity Name"  
  default = "MainActivity"  
  help = "The name of the activity class to create"  
  constraints = listOf(Constraint.CLASS, Constraint.UNIQUE, Constraint.NONEMPTY)  
        suggest = { layoutToActivity(layoutNameInputParameter.value) }  
 }  //레이아웃 생성이름  
  var layoutNameInputParameter : StringParameter = stringParameter {  
  name = "Layout Name"  
  default = "activity_main"  
  help = "The name of the layout to create for the activity"  
  constraints = listOf(Constraint.LAYOUT, Constraint.UNIQUE, Constraint.NONEMPTY)  
        suggest = { activityToLayout(activityClassInputParameter.value) }  
 }  
  //액티비티 생성이름  
  val activityTitleInputParameter = stringParameter {  
  name = "Title"  
  default = "MainActivity"  
  help = "The name of the activity. For launcher activities, the application title"  
  visible = { false }  
  constraints = listOf(Constraint.NONEMPTY)  
        suggest = { activityClassInputParameter.value }  
 }  
  //패키지이름지정  
  val packageName = defaultPackageNameParameter  
}  
  
val defaultPackageNameParameter get() = stringParameter {  
  name = "Package name"  
  default = "com.mycompany.myapp"  
  constraints = listOf(Constraint.UNIQUE, Constraint.NONEMPTY)  
    suggest = { packageName }  
}
  1. 이제 대화상자를 띄우는 것까지 했으니 입력받은 값으로 실제 화일들을 생성하는 부분을 구현하자. RecipeExcutor의 확장함수 형태로 만들면된다.
//HelloWorldRecipe.kt
//대화상자에서 Finish 버튼을 누루면 helloWorldActivityRecipe 함수가 기동된다.  
fun RecipeExecutor.helloWorldActivityRecipe(  
        moduleData: ModuleTemplateData,  
        activityClass: String,  
        activityTitle: String,  
        layoutName: String,  
        packageName: String  
) {  
    val (projectData, srcOut, resOut) = moduleData  
  
  
    generateManifest(  
            moduleData = moduleData,  
            activityClass = activityClass,  
            activityTitle = activityTitle,  
            packageName = packageName,  
            isLauncher = false,  
            hasNoActionBar = false,  
            generateActivityTitle = true,  
    )  
  
    save(helloWorldActivity(packageName, activityClass, layoutName), srcOut.resolve("${activityClass}.${projectData.language.extension}"))  
    save(activityHelloWorldXml(activityClass), resOut.resolve("layout/${layoutName}.xml"))  
  
    open(resOut.resolve("layout/${layoutName}.xml"))  
  
}
  1. Recipe에서는 generateManifest 함수로 Manifest에 새로운 액티비티 추가도 해주고, save명령으로 현재 플러그인안에 있는 원본소스파일과 리소스등을 작업프로젝트에 복사해준다. kotlin소스파일과 레이아웃소스파일을 만들자.
//recipes/myresources/res/src 단,폴더이름은 상관없다.
fun helloWorldActivity(  
  packageName: String,  
  activityClass: String,  
  layoutName: String,  
) = """  
package ${escapeKotlinIdentifier(packageName)}  
  
import android.os.Bundle  
import androidx.appcompat.app.AppCompatActivity  
import ${escapeKotlinIdentifier(packageName)}.R  
  
class ${activityClass} : AppCompatActivity() {  
 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.${layoutName})  
 }}  
 
//recipes/myresources/res/layout 단,폴더이름은 상관없다.
fun activityHelloWorldXml(  
  activityClass: String,  
) = """  
<?xml version="1.0" encoding="utf-8"?>  
<${getMaterialComponentName("android.support.constraint.ConstraintLayout", true)}  
 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" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".${activityClass}">  
  
 <TextView android:id="@+id/tv_hello" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" />  
</${getMaterialComponentName("android.support.constraint.ConstraintLayout", true)}>  
"""
"""
  1. 다시 buildPlugin 태스크를 실행하고, plugin을 재설치한다.
  2. 샘플프로젝트를 하나 만들어서 액티비티를 추가해보자. 액티비티 목록에도 보이고, 액티비티 선택리스트에도 Hello Android Template가 추가되었다.
    enter image description here

github : https://github.com/schoolhompy/androidStudio_template_plugin_sample

0 comments:

댓글 쓰기