Kotlin과 Anko로 Android 개발하기

Android 개발에 Kotlin와 Anko를 적용해 볼까요?

시작하기에 앞서 말씀드리자면

안드로이드는 마치 Java 6의 늪에 빠진 것 같습니다.


ADB Hell

안드로이드 개발을 시작하면서 처음 몇 년간은 C#을 사용해 왔습니다. Java와 비교해보면 제네릭 지원도 부족하고 람다 표현 식도 없으며 일반적인 것에도 이상한 구문을 사용해야 해서 마치 언어에 갇힌듯한 기분이었습니다. 8년이 지난 지금, 아직도 저는 여전히 간결하지 않은 Java 6을 사용하고 있습니다. Java 8이 나온 지도 꽤 지났고, retrolambda 덕분에 바이트 코드를 이리저리 꼬지 않아도 몇몇 기능을 사용할 수 있지만, 언제가 돼야 온전히 Java 8이 지원될는지 여전히 알 수가 없습니다.

다행히 안드로이드를 위해 Kotlin이라는 멋진 언어가 등장했습니다.

Kotlin Logo

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 창을 다시 띄우세요.

Configure Plugin

다음으로 아래처럼 Install JetBrains Plugin을 선택합니다.

Install Plugin

이제 Kotlin을 검색한 다음 아래처럼 Kotlin 플러그인을 설치하세요. 메인 Kotlin 플러그인에는 안드로이드 익스텐션이 포함돼 있습니다.

자, 이제 Kotlin으로 첫 코드를 작성할 준비가 끝났습니다!

예제 애플리케이션

지금부터 예제로 만들 앱은 간단한 to-do 앱입니다. 메인 화면은 다음과 같은 to-do 리스트가 보이게 될 겁니다.

Example Screenshot

사용자는 플로팅 액션 버튼을 사용해서 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를 선택하세요.

Actions

이렇게 하면 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, 총 세 개의 안드로이드 위젯을 생성했습니다. 뷰 속성도 설정했습니다. 버튼이 클릭 리스너 셋을 뷰 선언 안에 가진다는 점이 특이합니다. 또한, 버튼 이전에 선언된 titledesc 변수와 함께 createTodoFrom 메서드가 호출됩니다. 마지막으로, UI 클래스인 AnkoContext의 view 속성을 호출하면서 뷰가 반환됩니다.

R.id.<id_name>으로 id를 설정하는데, 이 idapp/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’를 탭하세요.

Example

이제 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으로 멋진 앱을 만드시길 기대합니다!

샘플 코드

다음: Kotlin를 마스터하는 길 #7 Kotlin과 Java 전격 비교

General link arrow white

컨텐츠에 대하여

이 컨텐츠는 저자의 허가 하에 이곳에서 공유합니다.


Donn Felker

Donn Felker is the co-host of the Fragmented Podcast and the founder of Caster.IO, a bite-sized Android developer video training site. He’s the curator of the #AndroidDev Digest, a best selling mobile author, international speaker, and consultant on all things Android. He also loves Chipotle.

4 design patterns for a RESTless mobile integration »

close