Android 개발에 Kotlin와 Anko를 적용해 볼까요?
시작하기에 앞서 말씀드리자면
안드로이드는 마치 Java 6의 늪에 빠진 것 같습니다.
안드로이드 개발을 시작하면서 처음 몇 년간은 C#을 사용해 왔습니다. Java와 비교해보면 제네릭 지원도 부족하고 람다 표현 식도 없으며 일반적인 것에도 이상한 구문을 사용해야 해서 마치 언어에 갇힌듯한 기분이었습니다. 8년이 지난 지금, 아직도 저는 여전히 간결하지 않은 Java 6을 사용하고 있습니다. Java 8이 나온 지도 꽤 지났고, retrolambda 덕분에 바이트 코드를 이리저리 꼬지 않아도 몇몇 기능을 사용할 수 있지만, 언제가 돼야 온전히 Java 8이 지원될는지 여전히 알 수가 없습니다.
다행히 안드로이드를 위해 Kotlin이라는 멋진 언어가 등장했습니다.
Kotlin은 JetBrain이 만든 새 JVM 호환 언어로 최근 안드로이드 애플리케이션 공식 개발 언어가 되었습니다. 초보자라면 Droidcon NYC에서 Michael Pardo가 강연한 Kotlin을 보시는 것을 추천합니다. 또한 Anko로 레이아웃 뷰를 간결하게 만들 수도 있습니다. 안드로이드를 위한 DSL (Domain-Specific Language)인 Anko는 Kotlin을 사용합니다. Anko 소개도 한 번 확인해 보세요.
Kotlin은 Java 6보다 신선한 언어로, 한 번 구문을 익히고 나면 훨씬 간결하게 코딩할 수 있습니다. Kotlin은 JVM을 호환하므로 안드로이드가 이해할 수 있는 JVM 바이트 코드로 컴파일됩니다.
중요 사항: 이후에는 독자가 Kotlin과 Anko에 대한 기본 지식이 있다는 가정에 따라 설명합니다.
안드로이드 프로젝트에 Kotlin 사용하기
아마 전체 프로젝트에 적용하기 전에 Kotlin을 철저히 검증해 보고 싶을 겁니다. Kotlin과 안드로이드 스튜디오를 사용해서 애플리케이션에 사용한 Java 코드를 차근차근 Kotlin으로 옮길 수 있습니다. 어떤 화면이나 간단한 커스텀 위젯을 하나 정해서 Kotlin으로 작성해보기 시작하는 것이 좋습니다. Kotlin을 기존 코드에 적용해 나간다면, 언어에 대한 테스트 실행도 할 수 있고 기존 애플리케이션 코드도 해치지 않습니다.
안드로이드 스튜디오의 Kotlin 플러그인을 사용하면 쉽게 Kotlin을 기존 안드로이드 애플리케이션에 적용할 수 있습니다. 먼저 플러그인을 설치한 후 안드로이드 스튜디오를 열고 Configure > Plugins
로 이동합니다. 아래 화면이 보이지 않는 경우 모든 프로젝트를 닫은 뒤 Welcome to Android Studio
창을 다시 띄우세요.
다음으로 아래처럼 Install JetBrains Plugin
을 선택합니다.
이제 Kotlin
을 검색한 다음 아래처럼 Kotlin
플러그인을 설치하세요. 메인 Kotlin 플러그인에는 안드로이드 익스텐션이 포함돼 있습니다.
자, 이제 Kotlin으로 첫 코드를 작성할 준비가 끝났습니다!
예제 애플리케이션
지금부터 예제로 만들 앱은 간단한 to-do 앱입니다. 메인 화면은 다음과 같은 to-do 리스트가 보이게 될 겁니다.
사용자는 플로팅 액션 버튼을 사용해서 to-do를 추가할 수 있습니다. 편집하려면 to-do를 눌러서 추가/편집 화면으로 이동합니다. 추가/편집 화면은 Kotlin과 Anko로 만들 예정입니다.
Anko란?
Anko는 Kotlin으로 작성된 안드로이드 DSL (Domain-Specific Language)입니다. 원래 안드로이드 뷰는 XML 레이아웃으로 표현되고, 보통 앱의 여러 부분에서 복제되면서 재사용되지 않습니다. 런타임 동안 XML은 CPU와 배터리를 낭비하면서 Java 표현으로 변환됩니다. Anko를 사용하면 Kotlin으로 액티비티나 프래그먼트를 위한 뷰를 만들 수 있고, 나아가서 뷰를 대체하는 AnkoComponent도 만들 수 있습니다.
간단한 XML 파일이 어떻게 Anko로 변환되는지 보여드리겠습니다.
XML
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<EditText
android:id="@+id/todo_title"
android:layout_width="match_parent"
android:layout_heigh="wrap_content"
android:hint="@string/title_hint" />
<!-- 인라인 클릭 리스너를 직접 추가해서
액티비티의 온클릭 델리게이트를 구현할 수는 없습니다. -->
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/add_todo" />
</LinearLayout>
같은 뷰를 Anko로 표현하면 아래와 같습니다.
verticalLayout {
var title = editText {
id = R.id.todo_title
hintResource = R.string.title_hint
}
button {
textResource = R.string.add_todo
onClick { view -> {
// 코드를 추가하세요.
title.text = "Foo"
}
}
}
}
레이아웃 정의에서 인라인 클릭 리스너를 볼까요? Kotlin을 사용했으므로 title
과 같은 다른 뷰 멤버에도 접근할 수 있고 이를 클릭 리스너 안에서 사용할 수도 있습니다.
시작하기
시작을 위한 기초 앱를 준비해 두었습니다. (완성 코드는 여기에서 확인하세요) 구성 요소는 다음과 같습니다.
- 앱의 간단한 컨트롤러가 될 액티비티(
MainActivity
) - 첫 화면(TodosFragment)에 to-do들을 보여줄 RecyclerView
- to-do를 저장할 Realm 데이터베이스
-
Todo.java
Realm 모델 - RecyclerView 어댑터
이제 Kotlin과 Anko를 사용해서 추가/편집 화면을 만들어 보겠습니다.
Kotlin과 Anko를 애플리케이션에 설정하기
Kotlin 확장 기능을 설치했으니 Configure Kotlin in Project
액션으로 애플리케이션을 설정하겠습니다. 안드로이드 스튜디오에서 CMD+SHIFT+A
를 눌러서 액션 검색 창을 연 후 Kotlin
을 입력하고 아래처럼 Configure Kotlin in Project
를 선택하세요.
이렇게 하면 build.gradle
파일의 맨 위에 kotlin-android
가 적용되고, Kotlin sourceSet
도 추가되며 디펜던시에도 Kotlin이 추가될 겁니다.
Anko도 디펜던시에 추가해 보겠습니다. 다음과 비슷한 내용으로 build.gradle
을 구성해야겠죠?
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "com.donnfelker.kotlinmix"
minSdkVersion 16
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
packagingOptions {
exclude 'META-INF/services/javax.annotation.processing.Processor'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
// Kotlin
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
// Anko
compile 'org.jetbrains.anko:anko-sdk15:0.8.2' // sdk19, sdk21, sdk23 are also available
compile 'org.jetbrains.anko:anko-support-v4:0.8.2' // In case you need support-v4 bindings
compile 'org.jetbrains.anko:anko-appcompat-v7:0.8.2' // For appcompat-v7 bindings
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
compile 'io.realm:realm-android:0.87.1'
compile 'com.github.thorbenprimke:realm-recyclerview:0.9.12'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.android.support:support-v4:23.1.1'
}
buildscript {
ext.kotlin_version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
repositories {
mavenCentral()
}
이제 to-do 추가/편집 화면을 만들 준비를 마쳤습니다.
Kotlin으로 프래그먼트 추가하기
src/main/kotlin/com.donnfelker.kotlinmix/
디렉터리가 없다면 생성하세요. 소스 폴더라는 뜻으로 kotlin
폴더가 파란색으로 변할 겁니다.
/src/main/kotlin/com.donnfelker.kotlinmix/
폴더를 클릭하고 New > Kotlin File/Class
를 선택한 다음 EditFragment
라고 이름 짓습니다. 그러면 패키지 선언만 포함된 새 파일이 만들어집니다.
아래 코드를 복사해서 EditFragment
파일에 붙여넣으세요.
package com.donnfelker.kotlinmix
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import com.donnfelker.kotlinmix.models.Todo
import io.realm.Realm
import org.jetbrains.anko.*
import org.jetbrains.anko.support.v4.UI
import org.jetbrains.anko.support.v4.find
import java.util.*
class EditFragment : Fragment() {
val TODO_ID_KEY: String = "todo_id_key"
val realm: Realm = Realm.getDefaultInstance()
var todo: Todo? = null
companion object {
fun newInstance(id: String): EditFragment {
var args: Bundle = Bundle()
args.putString("todo_id_key", id)
var editFragment: EditFragment = newInstance()
editFragment.arguments = args
return editFragment
}
fun newInstance(): EditFragment {
return EditFragment()
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if(arguments != null && arguments.containsKey(TODO_ID_KEY)) {
val todoId = arguments.getString(TODO_ID_KEY)
todo = realm.where(Todo::class.java).equalTo("id", todoId).findFirst()
val todoTitle = find<EditText>(R.id.todo_title)
todoTitle.setText(todo?.title)
val todoDesc = find<EditText>(R.id.todo_desc)
todoDesc.setText(todo?.description)
val add = find<Button>(R.id.todo_add)
add.setText(R.string.save)
}
}
override fun onDestroy() {
super.onDestroy()
realm.close()
}
/**
* TODO item을 만드는 private 함수
*
* @param title - title edit text
* @param desc - edit text 설명
*/
private fun createTodoFrom(title: EditText, desc: EditText) {
realm.beginTransaction()
// Either update the edited object or create a new one.
var t = todo?: realm.createObject(Todo::class.java)
t.id = todo?.id?: UUID.randomUUID().toString()
t.title = title.text.toString()
t.description = desc.text.toString()
realm.commitTransaction()
// Go back to previous activity
activity.supportFragmentManager.popBackStack();
}
}
위 코드에서 newInstance
, onActivityCreated
, onDestroy
, createTodoFrom
등 몇 가지 메서드를 볼 수 있습니다. createTodoFrom
은 두 개의 EditText
위젯을 매개 변수로 받으며 새 Todo
를 만들거나 기존 Todo
를 업데이트하는데 사용합니다.
var t = todo?: realm.createObject(Todo::class.java)
이 코드는 필드 레벨에서 todo
값이 널인지 확인합니다. 널인 경우 새 Todo
인스턴스를 만들고, 널이 아닌 경우 파일 윗 부분의 onActivityCreated
메서드에서 초기화된 로컬 필드 인스턴스를 사용합니다.
onActivityCreated
에서는 프래그먼트의 인자를 체크합니다. 널이 아닌 경우 인텐트 엑스트라에서 Todo
의 id를 가져오고, 이 id로 Realm에서 Todo
객체를 가져옵니다. todo
필드 초기화로 Todo
객체가 변경됐으니 관련 값으로 뷰를 업데이트해야 합니다.
Anko로 뷰 추가하기
이제 프래그먼트에 뷰를 추가하겠습니다. 아래 코드를 프래그먼트에 붙여넣으세요.
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return UI {
verticalLayout {
padding = dip(30)
var title = editText {
id = R.id.todo_title
hintResource = R.string.title_hint
}
var desc = editText {
id = R.id.todo_desc
hintResource = R.string.description_hint
}
button {
id = R.id.todo_add
textResource = R.string.add_todo
onClick { view -> createTodoFrom(title, desc) }
}
}
}.view
}
verticalLayout
으로 LinearLayout을 생성했고, verticalLayout
블럭 안에 두 개의 editText
와 한 개의 button
, 총 세 개의 안드로이드 위젯을 생성했습니다. 뷰 속성도 설정했습니다. 버튼이 클릭 리스너 셋을 뷰 선언 안에 가진다는 점이 특이합니다. 또한, 버튼 이전에 선언된 title
과 desc
변수와 함께 createTodoFrom
메서드가 호출됩니다. 마지막으로, UI
클래스인 AnkoContext의 view
속성을 호출하면서 뷰가 반환됩니다.
R.id.<id_name>
으로 id
를 설정하는데, 이 id
는 app/src/main/res/values/ids.xml
에 있는 ids.xml
파일에서 수동으로 만들어집니다. 이 파일이 없는 경우 아래 컨텐츠를 포함하도록 새로 생성하세요.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="todo_title" type="id" />
<item name="todo_desc" type="id" />
<item name="todo_add" type="id" />
</resources>
이 ids.xml
파일에서는 id
를 선언해서 안드로이드 애플리케이션이 다양한 뷰의 id
로 사용할 수 있도록 합니다.
Java와 Kotlin 혼용
이제 사용자가 아이템을 탭 하면 보일 프래그먼트만 만들면 화면에 뷰를 표시할 수 있습니다.
TodosFragment
를 열고 다음 코드를 onTodoClick
메서드에 넣으세요.
EditFragment editFragment = EditFragment.Companion.newInstance(task.getId());
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content_main, editFragment, editFragment.getClass().getSimpleName())
.addToBackStack(editFragment.getClass().getSimpleName())
.commit();
EditFragment
는 오로지 Kotlin만으로 작성됐지만, 안드로이드 코드에서 일반 Java 객체처럼 쉽게 호출할 수 있습니다.
Kotlin에는 정적 메서드가 없으므로 EditFragment.Companion.newInstance
호출을 해야 합니다. 따라서 Kotlin에서 이런 작업을 하려면 companion object가 필요합니다.
마지막으로 플로팅 액션 버튼에 프래그먼트를 시작하는 기능을 연결해야 합니다. MainActivity
에 있는 플로팅 액션 버튼의 클릭 리스너에 다음 코드를 추가하세요.
EditFragment editFragment = EditFragment.Companion.newInstance();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content_main, editFragment, editFragment.getClass().getSimpleName())
.addToBackStack(editFragment.getClass().getSimpleName())
.commit();
애플리케이션을 빌드하고 설치한 다음 플로팅 액션 버튼을 탭하면 Kotlin으로 만든 부분이 시작됩니다. to-do를 하나 만들고 Add
를 눌러 추가합니다. to-do 리스트로 돌아간 다음 추가한 to-do를 탭하면 내용을 편집할 수 있습니다. 편집 시에는 Kotlin EditFagment
의 버튼 텍스트가 ‘save’로 바뀝니다. to-do 내용을 업데이트했으면 ‘save’를 탭하세요.
이제 Java와 Kotlin을 함께 사용할 수 있습니다! 👏
애플리케이션의 다른 부분은 안드로이드에서 일상적으로 사용하는 Java로 유지한 상태에서 Kotlin으로 기능 하나를 추가했습니다. 이런 방식으로 Kotlin 개발을 단계별로 이어갈 수 있습니다.
Kotlin으로 뷰를 만들기 위해 앞서 말씀드린 Anko를 사용할 수도 있지만, 혹시 XML을 선호한다면 XML 레이아웃을 계속 사용해도 됩니다. 예를 들어 위에서 본 onCreateView
메서드 호출부를 다음 코드로 교체할 수도 있습니다.
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater?.inflate(R.layout.your_layout, container, false)
}
이런 방식으로 Anko 없이도 Kotlin으로 개발할 수 있습니다.
Kotlin으로 멋진 앱을 만드시길 기대합니다!
샘플 코드
컨텐츠에 대하여
이 컨텐츠는 저자의 허가 하에 이곳에서 공유합니다.