В этой теме рассматриваются некоторые наиболее полезные аспекты языка Kotlin при разработке для Android.
Работа с фрагментами
 В следующих разделах на примерах Fragment показаны некоторые из лучших возможностей Kotlin.
Наследование
 В Kotlin класс можно объявить с помощью ключевого слова class . В следующем примере LoginFragment является подклассом Fragment . Наследование можно обозначить, используя оператор : между подклассом и его родителем:
class LoginFragment : Fragment()
 В этом объявлении класса LoginFragment отвечает за вызов конструктора своего суперкласса Fragment .
 В LoginFragment вы можете переопределить ряд обратных вызовов жизненного цикла для реагирования на изменения состояния вашего Fragment . Чтобы переопределить функцию, используйте ключевое слово override , как показано в следующем примере:
override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.login_fragment, container, false)
}
 Чтобы сослаться на функцию в родительском классе, используйте ключевое слово super , как показано в следующем примере:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
}
Обнуление и инициализация
 В предыдущих примерах типы некоторых параметров в переопределенных методах были дополнены вопросительным знаком ? . Это означает, что аргументы, передаваемые для этих параметров, могут иметь значение NULL. Убедитесь, что возможность принимать значение NULL реализована безопасно .
 В Kotlin необходимо инициализировать свойства объекта при его объявлении. Это означает, что при получении экземпляра класса можно немедленно ссылаться на любое из его доступных свойств. Однако объекты View во Fragment не готовы к расширению до вызова Fragment#onCreateView , поэтому необходим способ отложить инициализацию свойств для View .
 lateinit позволяет отложить инициализацию свойства. При использовании lateinit следует инициализировать свойство как можно скорее.
 В следующем примере демонстрируется использование lateinit для назначения объектов View в onViewCreated :
class LoginFragment : Fragment() {
    private lateinit var usernameEditText: EditText
    private lateinit var passwordEditText: EditText
    private lateinit var loginButton: Button
    private lateinit var statusTextView: TextView
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        usernameEditText = view.findViewById(R.id.username_edit_text)
        passwordEditText = view.findViewById(R.id.password_edit_text)
        loginButton = view.findViewById(R.id.login_button)
        statusTextView = view.findViewById(R.id.status_text_view)
    }
    ...
}
Конверсия ЗРК
 В Android можно отслеживать события нажатия, реализовав интерфейс OnClickListener . Объекты Button содержат функцию setOnClickListener() , которая принимает реализацию OnClickListener .
 OnClickListener есть единственный абстрактный метод, onClick() , который необходимо реализовать. Поскольку setOnClickListener() всегда принимает OnClickListener в качестве аргумента, и поскольку OnClickListener всегда имеет один и тот же абстрактный метод, эту реализацию можно представить с помощью анонимной функции в Kotlin. Этот процесс называется преобразованием одного абстрактного метода ( SAM-преобразованием) .
 Преобразование SAM может значительно улучшить ваш код. В следующем примере показано, как использовать преобразование SAM для реализации OnClickListener для Button :
loginButton.setOnClickListener {
    val authSuccessful: Boolean = viewModel.authenticate(
            usernameEditText.text.toString(),
            passwordEditText.text.toString()
    )
    if (authSuccessful) {
        // Navigate to next screen
    } else {
        statusTextView.text = requireContext().getString(R.string.auth_failed)
    }
}
 Код внутри анонимной функции, переданной в setOnClickListener() выполняется, когда пользователь нажимает loginButton .
Сопутствующие объекты
 Сопутствующие объекты предоставляют механизм для определения переменных или функций, концептуально связанных с типом, но не привязанных к конкретному объекту. Сопутствующие объекты аналогичны ключевому слову static в Java для переменных и методов.
 В следующем примере TAG — это String константа. Вам не нужен уникальный экземпляр String для каждого экземпляра LoginFragment , поэтому следует определить её в сопутствующем объекте:
class LoginFragment : Fragment() {
    ...
    companion object {
        private const val TAG = "LoginFragment"
    }
}
 Вы можете определить TAG на верхнем уровне файла, но файл также может содержать большое количество переменных, функций и классов, которые также определены на верхнем уровне. Сопутствующие объекты помогают связать переменные, функции и определение класса, не ссылаясь на какой-либо конкретный экземпляр этого класса.
Делегирование собственности
 При инициализации свойств вы можете повторить некоторые распространённые шаблоны Android, например, обращение к ViewModel внутри Fragment . Чтобы избежать дублирования кода, можно использовать синтаксис делегирования свойств Kotlin.
private val viewModel: LoginViewModel by viewModels()
 Делегирование свойств обеспечивает общую реализацию, которую можно использовать повторно в вашем приложении. Android KTX предоставляет несколько делегатов свойств. Например, viewModels извлекает ViewModel , область действия которого ограничена текущим Fragment .
Делегирование свойств использует рефлексию, что приводит к некоторому снижению производительности. Зато краткий синтаксис экономит время разработки.
Обнуляемость
 Kotlin обеспечивает строгие правила допустимости значений NULL, которые обеспечивают типобезопасность во всем приложении. В Kotlin ссылки на объекты по умолчанию не могут содержать значения NULL. Чтобы присвоить переменной значение NULL, необходимо объявить тип переменной, допускающий значение NULL , добавив ? в конец базового типа.
 Например, следующее выражение недопустимо в Kotlin. name имеет тип String и не может иметь значение NULL:
val name: String = null
 Чтобы разрешить значение NULL, необходимо использовать тип String допускающий значение NULL, String? как показано в следующем примере:
val name: String? = null
Взаимодействие
 Строгие правила Kotlin делают ваш код безопаснее и лаконичнее. Эти правила снижают вероятность возникновения исключения NullPointerException , которое может привести к сбою приложения. Более того, они сокращают количество проверок на null, которые необходимо выполнять в коде.
Зачастую при написании приложения для Android приходится обращаться к коду, отличному от Kotlin, поскольку большинство API-интерфейсов Android написаны на языке программирования Java.
Допустимость значений NULL — ключевая область, в которой поведение Java и Kotlin различается. В Java синтаксис допустимости значений NULL менее строг.
 Например, класс Account имеет несколько свойств, включая String свойство с именем name . В Java нет правил Kotlin относительно допустимости значений NULL, вместо этого используются необязательные аннотации допустимости значений NULL , которые явно указывают, можно ли присвоить значение NULL.
Поскольку фреймворк Android написан в основном на Java, вы можете столкнуться с такой ситуацией при вызове API без аннотаций допустимости значений NULL.
Типы платформ
 Если вы используете Kotlin для ссылки на неаннотированный член name , определённый в классе Java Account , компилятор не знает, соответствует ли String типу String или String? в Kotlin. Эта неоднозначность представлена платформенным типом String!
 String! не имеет особого значения для компилятора Kotlin. String! может представлять как String , так и String? , и компилятор позволяет присваивать значение любого из этих типов. Обратите внимание, что вы рискуете получить исключение NullPointerException , если представляете тип как String и присваиваете значение null.
Чтобы решить эту проблему, следует использовать аннотации, определяющие допустимость значений NULL, при написании кода на Java. Эти аннотации полезны как разработчикам на Java, так и на Kotlin.
 Например, вот класс Account , как он определен в Java: 
public class Account implements Parcelable {
    public final String name;
    public final String type;
    private final @Nullable String accessId;
    ...
}
Одна из переменных-членов, accessId , аннотирована @Nullable , что указывает на возможность хранения значения NULL. В таком случае Kotlin будет обрабатывать accessId как String?
 Чтобы указать, что переменная никогда не может быть равна null, используйте аннотацию @NonNull : 
public class Account implements Parcelable {
    public final @NonNull String name;
    ...
}
В этом сценарии name считается ненулевым значением String в Kotlin.
Аннотации об отсутствии значений включены во все новые и многие существующие API Android. Многие библиотеки Java добавили аннотации об отсутствии значений для лучшей поддержки разработчиков как на Kotlin, так и на Java.
Обработка допустимости значений NULL
 Если вы не уверены в типе Java, следует считать его допускающим значение NULL. Например, член name класса Account не аннотирован, поэтому следует считать его допускающим значение NULL типом String?
 Если вы хотите обрезать name так, чтобы его значение не включало начальные и конечные пробелы, можно использовать функцию trim из Kotlin. Строку String String? можно безопасно обрезать несколькими способами. Один из них — использовать оператор проверки непустого значения !! , как показано в следующем примере: 
val account = Account("name", "type")
val accountName = account.name!!.trim()
Оператор !! обрабатывает всё в левой части как ненулевое, поэтому в данном случае вы обрабатываете name как ненулевую String . Если результат выражения слева от него равен NULL, ваше приложение генерирует исключение NullPointerException . Этот оператор работает быстро и просто, но его следует использовать с осторожностью, так как он может повторно добавить экземпляры NullPointerException в ваш код.
 Более безопасным выбором будет использование оператора безопасного вызова ?. , как показано в следующем примере: 
val account = Account("name", "type")
val accountName = account.name?.trim()
При использовании оператора безопасного вызова, если name не равно NULL, то результатом name?.trim() будет значение name без начальных и конечных пробелов. Если name равно NULL, то результатом name?.trim() будет null . Это означает, что ваше приложение никогда не сможет сгенерировать исключение NullPointerException при выполнении этого оператора.
 Хотя оператор безопасного вызова избавляет от потенциального NullPointerException , он передаёт значение NULL следующему оператору. Вместо этого вы можете сразу же обрабатывать случаи NULL, используя оператор Элвиса ( ?: ), как показано в следующем примере: 
val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"
Если результат выражения в левой части оператора Elvis равен NULL, то значение в правой части присваивается accountName . Этот приём полезен для предоставления значения по умолчанию, которое в противном случае было бы NULL.
Вы также можете использовать оператор Элвиса для досрочного возврата из функции, как показано в следующем примере:
fun validateAccount(account: Account?) {
    val accountName = account?.name?.trim() ?: "Default name"
    // account cannot be null beyond this point
    account ?: return
    ...
}
Изменения API Android
 API Android становятся всё более дружелюбными к Kotlin. Многие из самых распространённых API Android, включая AppCompatActivity и Fragment , содержат аннотации, разрешающие значение NULL, а некоторые вызовы, такие как Fragment#getContext имеют более удобные для Kotlin альтернативы.
 Например, доступ к Context Fragment почти всегда ненулевой, поскольку большинство вызовов, выполняемых во Fragment происходят, когда Fragment прикреплён к Activity (подклассу Context ). При этом Fragment#getContext не всегда возвращает ненулевое значение, поскольку существуют сценарии, когда Fragment не прикреплён к Activity . Таким образом, возвращаемый тип Fragment#getContext допускает значение NULL.
 Поскольку возвращаемый Fragment#getContext Context допускает значение NULL (и аннотирован как @Nullable), в коде Kotlin его необходимо обрабатывать как Context? Это означает применение одного из ранее упомянутых операторов для решения проблемы допустимости значения NULL перед доступом к его свойствам и функциям. Для некоторых из этих сценариев Android предоставляет альтернативные API, обеспечивающие такое удобство. Например, Fragment#requireContext возвращает ненулевой Context и генерирует исключение IllegalStateException при вызове, когда Context должен быть нулевым. Таким образом, полученный Context можно обрабатывать как ненулевой без необходимости использования операторов безопасного вызова или обходных путей.
Инициализация свойств
Свойства в Kotlin не инициализируются по умолчанию. Они должны быть инициализированы при инициализации включающего их класса.
 Инициализировать свойства можно несколькими способами. В следующем примере показано, как инициализировать index переменную, присвоив ей значение в объявлении класса: 
class LoginFragment : Fragment() {
    val index: Int = 12
}
Эту инициализацию также можно определить в блоке инициализатора:
class LoginFragment : Fragment() {
    val index: Int
    init {
        index = 12
    }
}
В приведенных выше примерах index инициализируется при создании LoginFragment .
 Однако некоторые свойства могут быть не инициализированы во время создания объекта. Например, вам может потребоваться сослаться на View из Fragment , что означает, что макет должен быть предварительно заполнен. Заполнение не происходит при создании Fragment . Вместо этого он заполняется при вызове Fragment#onCreateView .
Одним из способов решения этой проблемы является объявление представления допускающим значение NULL и его инициализация как можно скорее, как показано в следующем примере:
class LoginFragment : Fragment() {
    private var statusTextView: TextView? = null
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView?.setText(R.string.auth_failed)
    }
}
Хотя это работает, как и ожидалось, теперь вам придётся контролировать допустимость значения NULL для View при каждой ссылке на него. Лучшее решение — использовать lateinit для инициализации View , как показано в следующем примере: 
class LoginFragment : Fragment() {
    private lateinit var statusTextView: TextView
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView.setText(R.string.auth_failed)
    }
}
Ключевое слово lateinit позволяет избежать инициализации свойства при создании объекта. Если на ваше свойство ссылаются до его инициализации, Kotlin генерирует исключение UninitializedPropertyAccessException , поэтому обязательно инициализируйте свойство как можно скорее.