Pull to refresh

Kotlin для Android

Reading time 7 min
Views 36K
Наверное, из тех, кто интересуется разработкой под Android, только ленивый не слышал про Kotlin. На хабре про него уже писали: вот статья, вот видео с MBLTdev. Язык активно развивается, но новых статей все нет. Я решил, что пора это исправить.

Если вам интересно, что же произошло за последнее время в этом прекрасном языке, а также его возможности, которые ранее не освещались на хабре, прошу под кат.

Anko


Несомненно, очень важная библиотека для любого пользователя Kotlin, которая пока еще только развивается (текущая версия 0.6). Рассмотрим ее возможности.

Anko DSL

Как известно, создавать интерфейс можно не только с помощью xml, но и прямо в Java-коде. Однако, это адская боль не очень легко. Библиотека Anko предоставляет другой способ создания интерфейса пользователя, который имеет немало общего с билдерами в Groovy (впрочем, в Kotlin они тоже есть).
В качестве простого примера перепишем стандартный HelloWorld с xml на Anko DSL:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    relativeLayout {
        padding = dip(16)
        textView("Hello world!") {
        }
    }
}

Не могу не сказать, что я сам был немного удивлен, когда, переписывая xml в Anko DSL, написал эти 5 строчек и неожиданно понял, что это все.
Эквивалентная xml-разметка
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                android:paddingBottom="@dimen/activity_vertical_margin">

    <TextView
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>


Конечно, такой подход можно применять и для создания более сложного UI:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    relativeLayout {
        padding = dip(16)

        val w = dip(200)
        val loginEditId = 155;
        val loginEdit = editText {
            id = loginEditId
            hint = "Login"
        }.layoutParams { centerInParent(); width = w }

        button("Sign up") {
            textSize = 18f
            onClick { doWork(loginEdit.getText().toString()) }
        }.layoutParams {
            below(loginEditId); sameLeft(loginEditId);
            width = w; topMargin = dip(8)
        }
    }
}

Результат:


У Anko DSL есть еще одна интересная возможность — сокращенная реализация интерфейсов. Например, стандартный интерфейс TextWatcher содержит 3 метода, и нам нужно реализовать их все, даже если мы хотим только один:
val edit = EditText(this)
edit.addTextChangedListener(object : TextWatcher {
    override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
    }

    override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
        toast(s)
    }

    override fun afterTextChanged(s: Editable) {
    }
})

С помощью Anko DSL мы можем реализовывать только то, что нам нужно (write as less code as possible):
val edit = editText {
    textChangedListener {
        onTextChanged { text, start, before, count -> toast(text.toString()) }
    }
}.layoutParams { centerInParent() }


В качестве небольшого заключения могу сказать, что Anko DSL является скорее альтернативой xml, но не однозначной заменой. Заменой он может являться для создания UI из Java-кода. Anko DSL не дает ощутимого преимущества в скорости, поэтому ради производительности использовать его не стоит.

На мой взгляд, использование Anko DSL хорошо тем, что и свойства, и обработчики событий для элементов вы пишите в одном месте (я подразумеваю, что обычно стиль / позиционирование элементов обычно выполняется с помощью xml, а обработчики событий присваиваются в Java коде) — это может давать плюс к логике. Кроме того, можно попробовать использовать Anko DSL в модном паттерне MVP.

Возможно также, что для кого-то такой builders-style является более приятным, чем xml.
И еще один важный момент — Anko только развивается. Возможно, в будущем мы увидим новые крутые фичи от этой библиотеки. Поэтому стоит обратить на нее внимание.

Anko features

Кроме DSL библиотека Anko позволяет нам писать намного меньше стандартного кода.
Рассмотрим некоторые фрагменты кода и то, как Anko позволяет их менять. Я просто буду приводить примеры — комментарии, на мой взгляд, излишни.
  • Сообщения Toast:
    Toast.makeText(this, "Hello", Toast.LENGTH_SHORT).show()
    //=>
    toast("Hello")
    

  • Открытие браузера для просмотра URL:
    val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.google.com"));
    startActivity(browserIntent);
    //=>
    browse("https://www.google.com")
    

  • Запуск другой Activity:
    val intent = Intent(this, javaClass<MainActivity>())
    intent.putExtra("from", "Peter")
    intent.putExtra("to", "Vasya")
    intent.putExtra("message", "hello")
    startActivity(intent)
    //=>
    startActivity<MainActivity>("from" to "Peter", "to" to "Vasya", "message" to "hello")
    

    Обратите внимание на способ передачи параметров. Аналогично создаются объекты типа Map.
  • Показ диалогов:
    val dialog = AlertDialog.Builder(this)
                .setTitle("Exit")
                .setMessage("Do you really want exit?")
                .setPositiveButton("Yes", { dialog, which ->  finish() })
                .setNegativeButton("No", { dialog, which -> dialog.dismiss() })
                .create()
                .show()
    
     //=>
    
    alert("Exit", "Do you really want exit?") {
        positiveButton("Yes") { finish() }
        negativeButton("No") { dismiss() }
    }.show()
    

  • Работа с AsyncTask:
    class MyTask : AsyncTask<Void, Void, Void>() {
        override fun doInBackground(vararg params: Void?): Void? {
            //do some work
            return null
        }
        override fun onPostExecute(result: Void?) {
            toast("Finished")
        }
    }
    val task = MyTask()
    task.execute()
    
    //=>
    
    async {
        //do some work
        uiThread {
            toast("Finished")
        }
    }
    


Таким образом, библиотека Anko предоставляет набор полезных вкусностей, которые несомненно пойдут на пользу вашему проекту на Kotlin. Так что вперед:
compile 'org.jetbrains.anko:anko:0.6.1-19s'

For Android with love


Нельзя не упомянуть и о том, что команда Kotlin-а очень хорошо старается во имя Android и добавляет в язык очень важные для любого Android-разработчика возможности.

Annotation Processing

Буквально несколько дней назад у Android-разработчиков наконец появилась возможность использовать различные DI-фреймворки в сочетании с Kotlin. По этому поводу в блоге разработчиков Kotlin-а появилась статья, в которой объясняются некоторые детали. Кроме того, можно посмотреть пример с использованием Dagger. Пока что данная возможность еще сыровата и будет дорабатываться, но общее направление радует.
Я не считаю, что могу добавить что-то полезное к ссылкам выше, поэтому этот пункт пойдет просто как новость.

Secondary constructors

Изначально в Kotlin планировалось использовать для каждого класса только один primary конструктор. Это аргументировалось тем, что этого всегда достаточно. Вообще, это почти всегда правда. Однако с таким ограничением Android-разработчики не могли создавать собственные View классы.
Теперь это возможно:
public class MyView : LinearLayout {

    public constructor(context: Context) : super(context) {
    }

    public constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
    }

    //...
}

Естественно, применение secondary конструкторов не ограничивается только View классами.

Delegated properties

Вообще, делегирование не создано специально для Android, эта возможность появилась еще в версии языка M5.3, но, так как на хабре эта тема еще не освещалась, я ее затрону.

Делегирование в данном контексте означает передачу обработки поля / переменной определенному классу. Этот класс содержит get и set методы для данного поля. Такая передача позволяет реализовать некоторые концепции, например:
  • Ленивая инициализация;
  • Observable properties — мониторинг и реакция на изменение значения поля.

Kotlin определяет набор стандартных важных делегатов, которые находятся в файле Delegation.kt в объекте Delegates. Например, можно очень легко использовать lazy initialization:
val someBigValue by Delegates.lazy {
    var result = BigInteger.ONE
    //some hard computations
    result
}

При обращении к полю someBigValue в первый раз, его значение будет посчитано и сохранено, во второй и следующей раз будет возвращаться сохраненное значение.
Примечание: не используйте эту конструкцию для создания различных Singleton-ов, для этого в Kotlin есть object declarations.
Другие примеры вы можете найти в документации.

Чем же так замечательно делегирование применительно к Android? Вспоминаем одну из ключевых особенностей Kotlin — null-safety. Обычно мы объявляем элементы UI как поля Activity / Fragment, например, так:
private var mHelloWorldTextView : TextView = //???

Однако в Kotlin мы не можем присвоить полю типа TextView значение null. Поэтому пишем что-то такое:
private var mHelloWorldTextView : TextView? = null

Но это ведет к страшным конструкциям с повсеместным использованием операторов "?." и "!!.", что не добавляет красоты коду:
override fun onCreate(savedInstanceState: Bundle?) {
    //...
    mHelloWorldTextView = findViewById(R.id.helloWorldTextView) as TextView?
    mHelloWorldTextView?.setOnClickListener { /*...*/ }
    val text = mHelloWorldTextView!!.getText()
}

И здесь на помощь спешит очень полезный метод notNull объекта Delegates:
private var mHelloWorldTextView : TextView by Delegates.notNull()

override fun onCreate(savedInstanceState: Bundle?) {
    //...
    mHelloWorldTextView = findViewById(R.id.helloWorldTextView) as TextView
    mHelloWorldTextView.setOnClickListener { /*...*/ }
    val text = mHelloWorldTextView.getText()
}

При попытке обратиться к полю mHelloWorldText перед его инициализацией мы получаем IllegalStateException. Весьма похоже на Optional из Java 8.

UPD 1.
В Activity можно использовать альтернативный вариант:
private val mHelloWorldTextView : TextView by Delegates.lazy { findViewById(R.id.helloWorldTextView) as TextView }

Спасибо пользователю Sp0tted_0wl

UPD 2.
А вообще, для View используйте Kotlin Android Extensions
Спасибо abreslav и kivsiak

Заключение


На этом я закончу рассмотрение основных интересных возможностей Kotlin для Android-разработки. Спасибо, что дочитали до конца.
Изучайте Kotlin, пишите на Kotlin, любите Kotlin!

P.S. Присоединяйтесь к крупнейшему в мире сообществу Android разработчиков в Slack.
P.P.S. Хотите пообщаться с другими разработчиками, интересующимися Kotlin? Добро пожаловать в Gitter.
Tags:
Hubs:
+13
Comments 22
Comments Comments 22

Articles