Статьи

Kotlin для разработчиков Android

У нас, разработчиков Android, сложная ситуация в связи с ограничениями нашего языка. Как вы, возможно, знаете, современные разработки для Android поддерживают только Java 6 (с некоторыми небольшими улучшениями по сравнению с Java 7), поэтому нам нужно каждый день работать с действительно старым языком, который снижает нашу производительность и вынуждает нас писать тонны шаблонного и хрупкого кода, который трудно читать содержание.

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

Что такое Котлин?

Kotlin — это язык, который работает на JVM. Он создается Jetbrains , компанией, которая разрабатывает мощные инструменты, такие как IntelliJ, один из самых известных IDE для разработчиков Java.

Котлин — это действительно простой язык. Одна из его основных целей — предоставить мощный язык с простым и сокращенным синтаксисом . Некоторые из его особенностей:

  • Он легкий : этот пункт очень важен для Android. Библиотека, которую мы должны добавить в наши проекты, настолько мала, насколько это возможно. В Android у нас жесткие ограничения на количество методов, и Котлин добавляет только около 6000 дополнительных методов.
  • Это совместимо : Kotlin может легко общаться с языком Java. Это означает, что мы можем использовать любую существующую библиотеку Java в нашем коде Kotlin, поэтому, хотя язык молодой, у нас уже есть тысячи библиотек, с которыми мы можем работать. Кроме того, код Kotlin также может быть использован из кода Java, что означает, что мы можем создавать программное обеспечение, которое использует оба языка. Вы можете начать писать новые функции в Kotlin и оставить остальную часть кода на Java.
  • Это язык со строгой типизацией : хотя вам практически не нужно указывать какие-либо типы в коде, потому что компилятор может определить тип переменных или возвращаемых типов функций практически в любых ситуациях. Таким образом, вы получаете лучшее из обоих миров: лаконичный и безопасный язык.
  • Это абсолютно безопасно : одна из самых больших проблем Java — это null. Вы не можете указать, когда переменная или параметр могут быть нулевыми, поэтому возникает много NullPointerException , и их действительно трудно обнаружить при кодировании. Kotlin использует явное значение NULL, что заставит нас проверять NULL при необходимости.

Kotlin в настоящее время находится в версии 1.0.0 Beta 3 , но может ожидать финальную версию очень скоро. Во всяком случае, он вполне готов к производству, его уже успешно используют многие компании.

Почему Kotlin отлично подходит для Android?

В основном потому, что все его функции прекрасно вписываются в экосистему Android. Библиотека достаточно мала, чтобы мы могли работать без проблем во время разработки. Его размер эквивалентен библиотеке support-v4 , и есть некоторые другие библиотеки, которые мы используем в большинстве проектов, которые даже больше.

Кроме того, Android Studio (официальная Android IDE) построена на основе IntelliJ. Это означает, что наша IDE имеет отличную поддержку для работы с этим языком. Мы можем настроить наш проект за считанные секунды и продолжать использовать IDE, как мы привыкли делать. Мы можем продолжать использовать Gradle и все функции запуска и отладки, которые предоставляет IDE. Это буквально то же самое, что написать приложение на Java.

И, очевидно, благодаря его совместимости мы можем без проблем использовать Android SDK из кода Kotlin . Фактически, некоторые части SDK еще проще в использовании, потому что функциональная совместимость интеллектуальна, и, например, она отображает методы получения и установки в свойствах Kotlin, или позволяет нам писать слушатели как замыкания.

Как начать использовать Kotlin в Android

Это действительно легко. Просто следуйте этим шагам:

  • Загрузите плагин Kotlin из разделов плагинов IDE.
  • Создайте класс Kotlin в своем модуле
  • Используйте действие «Настроить Kotlin в Project…»
  • наслаждаться

Некоторые особенности

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

Нулевая безопасность

Как я уже упоминал ранее, Kotlin абсолютно безопасен. Если тип может быть нулевым, мы должны указать его, установив ? после типа. С этого момента, каждый раз, когда мы хотим использовать переменную, которая использует этот тип, мы должны проверять нулевое значение.

Например, этот код не будет компилироваться:

1
2
var artist: Artist? = null

artist.print()

Во второй строке будет отображаться ошибка, потому что пустота не была проверена. Мы могли бы сделать что-то вроде этого:

1
2
3
if (artist != null) {

    artist.print()

}

Это показывает еще одну замечательную особенность Kotlin: умный кастинг. Если мы проверили тип переменной, нам не нужно приводить ее в рамках этой проверки. Теперь мы можем использовать artist как переменную типа Artist внутри if . Это работает с любой другой проверкой, которую мы можем сделать (например, после проверки типа экземпляра).

У нас есть более простой способ проверить недействительность, используя ? перед вызовом функции объекта. И мы можем даже предоставить альтернативу, используя оператор Элвиса ?:

1
val name = artist?.name ?: ""

Классы данных

В Java, если мы хотим создать класс данных или класс POJO (класс, который сохраняет только некоторое состояние), нам нужно создать класс с множеством полей, методов получения и установки, а также, вероятно, класса toString и класса equals :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Artist {
    private long id;
    private String name;
    private String url;
    private String mbid;
 
    public long getId() {
        return id;
    }
 
    public void setId(long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getUrl() {
        return url;
    }
 
    public void setUrl(String url) {
        this.url = url;
    }
 
    public String getMbid() {
        return mbid;
    }
 
    public void setMbid(String mbid) {
        this.mbid = mbid;
    }
 
    @Override public String toString() {
        return "Artist{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", url='" + url + '\'' +
                ", mbid='" + mbid + '\'' +
                '}';
    }
}

В Kotlin весь предыдущий код может быть заменен этим:

1
2
3
4
5
data class Artist (

    var id: Long,
    var name: String,
    var url: String,
    var mbid: String)

Котлин использует свойства вместо полей. Свойство — это, в основном, поле плюс его метод получения и установки . Мы можем объявить эти свойства непосредственно в конструкторе, который, как вы видите, определяется сразу после имени класса, что экономит нам несколько строк, если мы не изменяем входные значения.

Модификатор data предоставляет некоторые дополнительные функции: удобочитаемое toString() , equals() на основе свойств, определенных в конструкторе, функцию copy и даже набор функций component которые позволяют разбивать объект на переменные. Что-то вроде этого:

1
val (id, name, url, mbid) = artist

Interoperability

У нас есть несколько отличных функций взаимодействия, которые очень помогают в Android. Одним из них является отображение интерфейсов с помощью одного метода на лямбда . Итак, слушатель кликов, как этот:

1
2
3
4
5
view.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View) {
        toast("Click")

    }

})

можно преобразовать в это:

1
view.setOnClickListener { toast("Click") }

Кроме того, геттеры и сеттеры автоматически сопоставляются со свойствами . Это не добавляет никаких накладных расходов, поскольку байт-код на самом деле просто вызывает исходные методы получения и установки. Вот несколько примеров:

1
2
3
supportActionBar.title = title
textView.text = title
contactsList.adapter = ContactsAdapter()

Лямбда

Lambdas сэкономит массу кода, но важно то, что он позволит нам делать вещи, которые невозможны (или слишком многословны) без них. С ними мы можем начать думать более функционально. Лямбда — это просто способ указать тип, который определяет функцию. Например, мы можем определить переменную следующим образом:

1
val listener: (View) -> Boolean

Это переменная, которая может объявить функцию, которая получает представление и возвращает функцию. Замыкание — это способ определить, что будет делать функция:

1
val listener = { view: View -> view is TextView }

Предыдущая функция получит View и вернет true если представление является экземпляром TextView . Когда компилятор может определить тип, нам не нужно его указывать. Мы можем быть более явными, если хотим, кстати:

1
val listener: (View) -> Boolean = { view -> view is TextView }

С лямбдами мы можем предотвратить использование интерфейсов обратного вызова. Мы можем просто установить функцию, которую мы хотим вызывать после и операция завершится:

1
2
3
4
5
6
fun asyncOperation(value: Int, callback: (Boolean) -> Unit) {
    ...
    callback(true)

}
 
asyncOperation(5) { result -> println("result: $result") }

Но есть более простая альтернатива, потому что если функция имеет только один параметр, мы можем использовать зарезервированное слово it :

1
asyncOperation(5) { println("result: $it") }

Коллекции

Коллекции в Котлине действительно мощные. Они написаны поверх коллекций Java, поэтому, когда мы получаем результат из любой библиотеки Java (или, например, Android SDK), мы по-прежнему можем использовать все функции, которые предоставляет Kotlin.

У нас есть доступные коллекции:

  • Iterable
  • Коллекция
  • Список
  • Поставил
  • карта

И мы можем применить к ним много операций. Вот некоторые из них:

  • фильтр
  • Сортировать
  • карта
  • застежка-молния
  • dropWhile
  • первый
  • firstOrNull
  • последний
  • lastOrNull
  • складка

Вы можете увидеть полный набор операций в этой статье . Таким образом, сложная операция, такая как фильтры, сортировка и преобразование, может быть явно определена:

1
2
3
4
parsedContacts
    .filter { it.name != null && it.image != null }
    .sortedBy { it.name }
    .map { Contact(it.id, it.name!!, it.image!!) }

Мы можем определить новые неизменяемые списки простым способом:

1
val list = listOf(1, 2, 3, 4, 5)

Или, если мы хотим, чтобы он был изменяемым (мы можем добавлять и удалять элементы), у нас есть очень хороший способ получить доступ к элементам и изменить их так же, как мы это сделали бы с массивом:

1
2
mutableList[0] = 1
val first = mutableList[0]

И то же самое с картами:

1
2
map["key"] = 1
val value = map["key"]

Это возможно, потому что мы можем перегрузить некоторые базовые операторы при реализации наших собственных классов.

Функции расширения

Функции расширений позволят нам добавить дополнительное поведение к классам, которые мы не можем изменить , например, потому что они принадлежат библиотеке или SDK.

Мы могли бы создать функцию ViewGroup inflate() для класса ViewGroup :

1
2
3
fun ViewGroup.inflate(layoutRes: Int): View {
    return LayoutInflater.from(context).inflate(layoutRes, this, false)
}

И теперь мы можем использовать его как любой другой метод:

1
val v = parent.inflate(R.layout.view_item)

Или даже функцию loadUrl для ImageView . Мы можем использовать библиотеку Пикассо внутри функции:

1
2
3
fun ImageView.loadUrl(url: String) {
    Picasso.with(context).load(url).into(this)
}

Все ImageView могут использовать эту функцию сейчас:

1
contactImage.loadUrl(contact.imageUrl)

Интерфейс

Интерфейсы в Kotlin могут содержать код , который имитирует простое множественное наследование . Класс может быть составлен из кода многих классов, а не только родительского. Интерфейсы, однако, не могут сохранять состояние. Поэтому, если мы определяем свойство в интерфейсе, реализующий его класс должен переопределить это свойство и предоставить значение.

Примером может служить класс ToolbarManager который будет работать с Toolbar :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
interface ToolbarManager {

    val toolbar: Toolbar
 

    fun initToolbar() {
        toolbar.inflateMenu(R.menu.menu_main)
        toolbar.setOnMenuItemClickListener {
            when (it.itemId) {
                R.id.action_settings -> App.instance.toast("Settings")
                else -> App.instance.toast("Unknown option")
            }
            true
        }
    }
}

Этот интерфейс может использоваться всеми действиями или фрагментами, которые используют Toolbar :

01
02
03
04
05
06
07
08
09
10
class MainActivity : AppCompatActivity(), ToolbarManager {

    override val toolbar by lazy { find<Toolbar>(R.id.toolbar) }
 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initToolbar()
        ...
    }
}

Когда выражение

When есть альтернатива для switch в Java, но гораздо более мощный. Он может буквально проверить что угодно. Простой пример:

1
2
3
4
5
6
7
val cost = when(x) {
    in 1..10 -> "cheap"
    in 10..100 -> "regular"
    in 100..1000 -> "expensive"
    in specialValues -> "special value!"
    else -> "not rated"
}

Мы можем проверить, что число находится внутри диапазона или даже внутри коллекции ( specialValues — это список). Но если мы не установим параметр на when , мы можем просто проверить все, что нам нужно. Нечто настолько безумное, насколько это возможно:

1
2
3
4
5
6
val res = when {
    x in 1..10 -> "cheap"
    s.contains("hello") -> "it's a welcome!"
    v is ViewGroup -> "child count: ${v.getChildCount()}"
    else -> ""
}

Расширения Kotlin для Android

Еще один инструмент, который команда Kotlin предоставляет разработчикам Android. Он сможет читать XML и вводить набор свойств в действие, фрагмент или представление с представлениями внутри макета, приведенными к его правильному типу.

Если у нас есть этот макет:

01
02
03
04
05
06
07
08
09
10
11
<FrameLayout
    xmlns:android="..."
    android:id="@+id/frameLayout"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/welcomeText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</FrameLayout>

Нам просто нужно добавить этот синтетический импорт:

1
import kotlinx.android.synthetic.main.*

И с этого момента мы можем использовать представления в нашей Activity :

1
2
3
4
5
6
override fun onCreate(savedInstanceState: Bundle?) {
    super<BaseActivity>.onCreate(savedInstanceState)
    setContentView(R.id.main)
    frameLayout.setVisibility(View.VISIBLE)
    welcomeText.setText("I´m a welcome text!!")
}

Это так просто.

Анко

Anko — это библиотека, которую команда Kotlin разрабатывает для упрощения разработки под Android. Его главная цель — предоставить DSL для объявления представлений с использованием кода Kotlin :

1
2
3
4
5
6
verticalLayout {
    val name = editText()
    button("Say Hello") {
        onClick { toast("Hello, ${name.text}!") }
    }
}

Но это включает в себя много других полезных вещей . Например, отличный способ перейти к другим действиям:

1
startActivity<DetailActivity>("id" to res.id, "name" to res.name)

Он просто получает набор Pair и добавляет их в пакет при создании намерения перейти к действию (указанному как тип функции).

У нас также есть прямой доступ к системным сервисам:

1
2
3
4
context.layoutInflater
context.notificationManager
context.sensorManager
context.vibrator

Или простые способы создания тостов и оповещений:

1
2
3
4
5
6
7
toast(R.string.message)
longToast("Wow, such a duration")
 

alert("Yes /no Alert") {
    positiveButton("Yes") { submit() }
    negativeButton("No") {}
}.show()

И один из них, я люблю простой DSL для решения асинхронности :

1
2
3
4
async {
    val result = longRequest()
    uiThread { bindForecast(result) }
}

Он также предоставляет хороший набор инструментов для работы с SQLite и курсорами . ManagedSQLiteOpenHelper предоставляет метод use который получает базу данных и может напрямую вызывать ее функции:

1
2
3
dbHelper.use {
    select("TABLE_NAME").where("_id = {id}", "id" to 20)
}

Как вы можете видеть, он имеет приятный select DSL, но также имеет простую функцию create :

1
2
3
db.createTable("TABLE_NAME", true,
        "_id" to INTEGER + PRIMARY_KEY,
        "name" to TEXT)

Когда вы имеете дело с курсором, вы можете использовать некоторые функции расширения, такие как parseList , parseOpt или parseClass , которые помогут с parseClass результата.

Вывод

Как видите, Kotlin упрощает разработку Android во многих различных аспектах. Это повысит вашу производительность и позволит вам решать обычные проблемы совершенно другим и более простым способом.

Я рекомендую вам хотя бы попробовать и немного поиграть с этим. Это действительно забавный язык, и его очень легко выучить. Если вы считаете, что этот язык для вас, вы можете продолжить его изучение, получив книгу Kotlin for Android Developers .

Ссылка: Kotlin для разработчиков Android от нашего партнера JCG Антонио Лейва в блоге Java Advent Calendar .