Fun main kotlin android

Fun main kotlin android

Статья проплачена кошками — всемирно известными производителями котят.

Если статья вам понравилась, то можете поддержать проект.

Коты забавные, поэтому ввели ключевое слово fun (есть спорное мнение, что на самом деле это сокращение от «function» для обозначения функций, которые являются аналогами методов в Java).

Объявление функции начинается с ключевого слова fun, затем идёт имя функции, в круглых скобках указываются параметры. Тип возвращаемого значения указывается после списка параметров и отделяется от него двоеточием. Функция всегда возвращает значение. Если вы сами не указали возвращаемое значение, то функция вернёт Unit, который схож с void, но является объектом.

Параметры в функциях объявляется немного иначе, чем в Java — сначала имя параметра, потом его тип.

С функциями можно работать как с значениями — можно сохранить в переменной, передать в качестве параметра, возвратить из другой функции.

Стандартный вывод «Hello Kitty» для Kotlin-программы (Desktop, не Android):

Данная функция ничего не возвращает. Напишем другую функцию, возвращающую результат.

Обратите внимание, что if является выражением в Kotlin, а не Java-оператором и соответствует тернарному оператору в Java:

Простую функцию, в которой блок состоит из одной строки кода, можно переписать в одну строчку.

Можно даже убрать возвращаемый тип. Гулять так гулять.

Такой способ подходит только для функций, в которых Kotlin способен самостоятельно разобраться, чего хотел разработчик, т.е. с телом-выражением в правой части. В правой части мы вычисляем какой-то результат, который обычно передавали в return. Теперь мы можем отказаться от return и фигурных скобок, и сразу присваивать результат функции.

В других случаях (тело-блок) вы обязаны указывать возвращаемый тип и использовать инструкцию return.

Функции верхнего уровня можно импортировать для сокращения кода.

Доступен вариант со звёздочкой.

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

Именованные параметры

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

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

Несмотря на то, что мы поменяли местами параметры, итоговый результат всё равно будет работать правильно. Такой подход может пригодиться, когда вы не помните порядок и вам лень смотреть документацию.

Данный приём не сработает при работе с методами, написанными на Java. Поддержка именованных аргументов есть в Java 8, но Kotlin поддерживает совместимость с Java 6, поэтому приходится смириться. Возможно, в будущем, эта проблема решится автоматически, когда откажутся от поддержки старых версий.

Параметры по умолчанию

Очень удобная функциональность — создание параметров по умолчанию. Если вы предполагаете, что какой-то параметр будет часто использовать какое-то конкретное значение, то мы можем сразу его указать. При вызове функции мы можем опустить этот параметр, он применится автоматически. Если нам нужно указать другое значение, то параметр добавим.

Читайте также:  Epic war для android

Добавим в класс активности новую функцию для вывода всплывающего сообщения (в примере используется функция-расширение).

Второй параметр использует значение по умолчанию и мы можем его не указывать при вызове. Вызываем функцию.

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

Вызываем функцию с двумя параметрами, надеясь, что третий подставится самостоятельно. Но Kotlin не может решить, какой параметр пропущен.

В этом случае на помощь приходят именованные параметры.

Третий параметр теперь нам известен, опущенный параметр относится ко второму, оставшийся относится к первому.

У класса Thread имеется восемь конструкторов! Вы можете создавать гораздо удобные решения с параметрами по умолчанию.

Unit. Если функция ничего не возвращает

Стоит немного рассказать о функциях, которые не возвращают никаких значений. В Java мы используем ключевое слово void для подобных случаев. В Kotlin был придуман новый тип Unit для подобных ситуаций. Получается, что функция всегда что-то возвращает, в нашем случае Unit, который мы никак не используем.

Но Kotlin достаточно умен и понимает, что мы не хотим ничего возвращать. Поэтому мы можем сократить код.

Можно сократить код, убрав фигурные скобки, так как у функции только одно выражение.

Ключевое слово vararg — переменное число параметров

В Java при вызове методов с разным числом аргументов использовалось троеточие (. ). В Kotlin существует другой подход — ключевое слово vararg.

Вызываем функцию с любым количеством аргументов.

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

По сути vararg работает с массивом, но простое добавление массива Kotlin не пропустит. Следует использовать специальный оператор *.

Вложенные (локальные) функции

Внутри одной функции можно создать ещё одну локальную функцию.

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

Функции верхнего уровня

Функцию можно объявить в начале файла, не обязательно размещать его в теле класса. Это удобно, когда вам нужны методы, которые не относятся к конкретному классу или вы не хотите перезагружать имеющийся класс лишним кодом. Часто для этих целей программисты создавали отдельные классы со словом Util.

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

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

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

Если имя класса вас не устраивает, то добавьте аннотацию @JvmName перед именем пакета.

Тогда вызов в Java-коде будет другим.

Функция TODO()

В стандартную библиотеку Kotlin входит функция TODO() (надо сделать). Её описание выглядит следующим образом.

Функция TODO() возбуждает исключение, т.е. вызов функции гарантированно завершится ошибкой — она возвращает тип Nothing. Считайте функцию временной заглушкой. Разработчик знает, что некоторая функция должна вернуть строку или другой объект, но пока отсутствуют другие функции, необходимые для ее реализации. Создадим для примера две функции.

Обратите внимание, что возвращаемое значение для shouldReturnAString() — это String, но на самом деле функция ничего не возвращает. Аналогично у shouldReturnACat().

Возвращаемый тип Nothing у TODO() показывает компилятору, что функция гарантированно вызовет ошибку, поэтому проверять возвращаемое значение после TODO() не имеет смысла, так как shouldReturnAString() и shouldReturnACat() ничего не вернут. Компилятор не будет ругаться, а разработчик может продолжать разработку, отложив на потом реализацию функции-заглушки.

Читайте также:  Удаленный доступ android termux

Функцию можно вызвать без аргументов. Код, который будет следовать за функцией, будет недостижим.

Имена функций в обратных кавычках

Можно объявить или вызвать функцию с именем, содержащим нестандартные символы. Для этого достаточно заключить имя в обратные кавычки `. Например, объявим функцию:

Данная возможность нужна, чтобы поддерживать совместимость с Java в тех моментах, когда встречаются зарезервированные ключевые слова. Использование обратных кавычек позволяют избежать несовместимости в случаях, если это необходимо. На практике такое почти не встречается.

На данный момент под Android такой способ не работает, студия будет ругаться.

Источник

Рефакторинг функций расширения в Kotlin: использование объекта-компаньона

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

Точка отсчёта

Допустим, у нас есть такая функция:

Описанный далее подход к рефакторингу можно применять и к функциям верхнего уровня, а также к методам синглтона:

Как видите, функции проверяют, доступна ли биометрия на текущем устройстве. Довольно простая и понятная логика для реализации в виде метода расширения.

Тестируемость

Функции расширения по сути являются @JVMStatic методами конкретного вспомогательного класса. Вот Java-эквивалент этого метода:

Вообще мы используем старомодный синглтон (определённый в области видимости класса с помощью статического модификатора), к которому можем обращаться из любого места кода, чтобы воспользоваться его логикой. А в чём главная проблема синглтонов? В тестируемости.

Рассмотрим такой случай:

Удобен ли этот код для тестирования? Не очень. Ведь даже если вы подставите в тесте заглушку Context , эффективно её использовать всё равно не получится, поскольку она опосредованно используется в BiometricManager . Вам нужно знать подробности реализации BiometricManager и то, как именно он использует Context , чтобы правильно настроить эту заглушку.

Решить эту проблему можно с помощью Robolectric или запуска теста на устройстве. Но нужно ли нам это? Эти варианты тестирования займут намного больше времени.

Усложнение логики

Что ещё может произойти со вспомогательными функциями? Они могут сильно усложниться, а мы поздно это заметим. В контексте предыдущего примера представим, что у нас появилось новое требование: при каждой проверке доступности аппаратной биометрии нужно также проверять результат А/В-теста, чтобы активировать функциональность.

Во-первых, не следует отправлять в продакшен код, который я сейчас покажу (это просто пример). Во-вторых, рано или поздно вы всё равно столкнётесь с проблемой усложнения функций расширения в продакшен-коде. Никто не идеален.

Получилась весьма неприглядная функция расширения, в которой смешаны бизнес-логика и логика данных. Её может быть трудно отрефакторить, если она применяется во многих местах.

Проблема большой кодовой базы

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

В больших кодовых базах функция расширения может использоваться в десятках и даже сотнях разных мест. И для каждого места в коде потребуется вносить изменения в соответствующий конструктор класса. Если у вас многомодульное приложение с прямыми и явными зависимостями, то может потребоваться явно объявить новый класс в виде зависимости в каждом модуле, который его использует.

Читайте также:  Убрать рекламу google андроиде

Из-за всех этих изменений ваш пул-реквест может раздуться до гигантских размеров — и его будет сложно проверить. К тому же возрастёт риск пропустить ошибку.

С помощью описанного ниже подхода мы сможем реализовать каждый этап в виде отдельного пул-реквеста.

Замена функции расширения на синглтон

Сначала признаем проблему использования синглтонов. Нам нужно заменить неявный синглтон на явный:

Волшебство объекта-компаньона интерфейса

Теперь у нас есть класс, с которым можно работать. Поскольку мы стремимся к тестируемости, в будущем мы заменим прямые использования класса BiometricsUtils на интерфейс. Сейчас интерфейс выглядит так:

Но потом нам понадобится убрать из списка параметров метода Context и AbTestStore , потому что они одинаковы во всех местах вызова метода: Context — это контекст приложения, а AbTestStore — локальный синглтон в графе зависимостей.

Вернёмся к варианту с параметрами в методе и в конце дополнительным этапом мигрируем на вариант без них.

Теперь у нас есть интерфейс и синглтон-класс. Как нам их соединить друг с другом, чтобы потом не пришлось вносить изменения во всех случаях использования метода?

Нам поможет объект-компаньон.

Объекты-компаньоны появились на заре развития Kotlin, ещё до выхода версии 1.0. В то время их преимущества перед обычными объектами и статическими методами Java не были очевидны. Особенно потому, что при каждом обращении к компаньону приходилось использовать слово Companion .

К счастью, требование использовать Companion отменили. И теперь мы можем обращаться к объектам-компаньонам в привычной манере — как к статическим функциям Java.

Более того, компилятор Kotlin достаточно сообразителен, чтобы различать вызовы методов интерфейса и его компаньона.

И поскольку объекты-компаньоны в Kotlin — это обычный синглтон-класс, они могут расширять классы и интерфейсы. Это приводит нас к объекту-компаньону, который реализует интерфейс своего родителя:

Мы можем вызвать абстрактную функцию bar применительно к интерфейсу Foo , делегируя её объекту-компаньону Foo . Воспользуемся этой методикой для рефакторинга кода:

Мы по-прежнему можем спокойно использовать BiometricsUtils.canUseBiometrics(applicationContext, abTestStore) . Теперь мы на шаг ближе к завершению рефакторинга.

Внедрение интерфейса в качестве значения по умолчанию

Раз у нас теперь есть интерфейс, мы можем передать его в качестве параметра конструктора.

Как значение по умолчанию параметра biometricsUtils используем BiometricsUtils.Companion . Тогда нам не придётся менять код, создающий этот класс. Но это изменение важно и ещё по одной причине. Мы наконец-то можем протестировать ScreenViewModel с помощью JVM-тестов. BiometricsUtils является интерфейсом, и мы можем применить в тесте заглушку:

Поскольку в качестве параметров canUseBiometrics мы используем только applicationContext и abTestStore , можно подставить пустые заглушки. Больше нет нужды в заглушках для других методов этих классов, как раньше. С помощью модульных тестов можно отдельно проверить логику метода canUseBiometrics , чтобы убедиться, что всё работает как нужно.

Убираем значение по умолчанию

Теперь можно убрать значение по умолчанию параметра biometricsUtils и через DI-систему подставить реальное значение.

Улучшаем интерфейс

Перенесём параметры biometricsUtils в конструктор класса и обновим все места его использования. Затем текущую функцию отметим как @Deprecated и добавим новую. Кроме того, поскольку мы уже убрали все использования BiometricsUtils.Companion , можно избавиться и от него самого.

Добавим новую реализацию BiometricsUtils :

Теперь через DI-систему предоставим новый класс:

Можем убрать все применения старого метода и обновить использовавшие его тесты.

Заключение

Мы смогли аккуратно отрефакторить метод расширения, поэтапно внося изменения. При этом мы не создали помехи коллегам и минимизировали количество конфликтов слияния.

Объект-компаньон интерфейса — это мощная функция, позволяющая использовать синглтоны, которые легко внедрить в конструктор и заменить заглушками.

Источник

Оцените статью