Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin: Ожидание и Реальность

Kotlin: Ожидание и Реальность

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

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

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

More Decks by St. Petersburg Kotlin User Group

Other Decks in Programming

Transcript

  1. Kotlin: Ожидание - Реальность Или почему ожидания Java программистов от

    Kotlin далеки от реальности Виталий Маркус, Tinkoff.ru
  2. О себе Tinkoff Core Tinkoff ASDK Money Talk Тинькофф No-Name

    Виталий Маркус, контакты: email - [email protected] telegram - @vitaliy_markus
  3. Содержание • Немного о проекте • Data Classes как ответы

    на API • Взаимодействие Room с Data Classes • Проблемы с Kotlin Annotations • Primary Constructor • Java vs Kotlin Interfaces
  4. Kotlin: Описание проекта Проект представляет собой встраиваемую библиотеку, которая инкапсулирует

    определенную последовательность действий App Activity Library Activity Library Activity App Activity больше одного экрана
  5. API + Kotlin Data Classes data class Citizenship(val value: String,

    val resident: Boolean) : Serializable { value: "string_example", resident: true } { resident: true } Caused by: java.lang.NullPointerException
  6. API + Kotlin Data Classes. Что происходит? 1. Сериализатор использует

    конструктор без аргументов 2. Если он отсутствует, то сериализатор при создании объекта просто выделяет память 3. Сетит поля, которые присутствуют в JSON
  7. API + Kotlin Data Classes. Что делать? 1. Кастомный десериализатор

    2. Явно указывать @Nullable типы 3. Конструктор по умолчанию data class Citizenship(@SerializedName("value") val value: String = "", @SerializedName("resident") val resident: Boolean = false) 4. 'Moshi (Проблемы с типизированными ответами)
  8. Room + Kotlin Data Classes @Entity data class Citizenship(@PrimaryKey val

    value: String, val resident: Boolean) : Serializable while (_cursor.moveToNext()) { final String _tmpValue = _tmpValue = _cursor.getString(_indexOfValue); final int _tmp = _cursor.getInt(_indexOfResident); final boolean _tmpResident = _tmpResident = _tmp != 0; final Citizenship _item = new Citizenship(_tmpValue, _tmpResident); _result.add(_item); }
  9. Room + Kotlin Data Classes + API @Entity data class

    Citizenship(@SerializedName("value") @PrimaryKey val value: String = "", @SerializedName("resident") val resident: Boolean = false) while(_cursor.moveToNext()) { final Citizenship _item = new Citizenship(); _item.getValue = _cursor.getString(_cursorIndexOfValue); final int _tmp = _cursor.getInt(_cursorIndexOfResident); _item.getResident = _tmp != 0; _result.add(_item); }
  10. Room + Kotlin Data Classes + API. Что происходит? 1.

    Room перебирает конструкторы по порядку и выбирает первый попавшийся. 2. Читает требуемые поля из БД 3. Создает объект через найденный конструктор с прочитанными значениями полей 4. Затем записывает оставшиеся значения с помощью set методов
  11. Room + Kotlin Data Classes + API. Что делать? @Entity

    data class Citizenship(@SerializedName("value") @PrimaryKey val value: String, @SerializedName("resident") val resident: Boolean) : { @Ignore constructor() : this("", false) } Использовать аннотации
  12. Room + Kotlin Data Classes + API + Library Объявление

    интерфейса общения с БД в модуле, а в основном приложение описание самой БД и весь сгенерированный код. Не работает) (правда это только касается иммутабельных объектов) Поэтому сейчас висит Issue в Google трекере: https://issuetracker.google.com/issues/68837279
  13. Аннотация @JvmName @get:JvmName("hasValue") val hasValue = nullableValue != null @get:JvmName("hasValue")

    val hasValue: Boolean get() = nullableValue != null Caused by: java.lang.NoSuchMethodError: No virtual method getHasValue()
  14. Аннотация @JvmName. Что происходит? val optional = Optional.of("VALUE") if (optional.hasValue)

    { val res = optional.value } Optional optional = Optional.Companion.of("VALUE"); if(optional.hasValue()) { String res = (String)optional.getValue(); } Optional optional = Optional.Companion.of("VALUE"); if(optional.getHasValue()) { String res = (String)optional.getValue(); } Kotlin Kotlin Decompiled APK Decompiled
  15. Аннотация @JvmOverloads class SwitchAppCompat @JvmOverloads constructor( context: Context, attrs: AttributeSet?

    = null, defStyleAttr: Int = 0) : SwitchCompat(context, attrs, defStyleAttr) { init { init(attrs) } }
  16. Аннотация @JvmOverloads class SwitchAppCompat @JvmOverloads constructor( context: Context, attrs: AttributeSet?

    = null, defStyleAttr: Int = 0) : SwitchCompat(context, attrs, defStyleAttr) { init { init(attrs) } } Caused by: java.lang.NullPointerException: on CharSequence.length()
  17. Аннотация @JvmOverloads. Что происходит? mOnLayout = makeLayout(mTextOn); public StaticLayout(CharSequence source,

    TextPaint paint, int width, Alignment align, float spacingmult, float spacingadd, boolean includepad) { this(source, 0, source.length(), paint, width, align, spacingmult, spacingadd, includepad); } class SwitchAppCompat @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.switchStyle) : SwitchCompat(context, attrs, defStyleAttr) { init { init(attrs) } }
  18. BackingField data class Bank ( @SerializedName("name") val name: String =

    "", @SerializedName("bankCode") val bankCode: String = "", @SerializedName("bankStages") val bankStages: List<BankStage> = emptyList()) : { val hasMultipleStages = bankStages.size > 1 } val hasMultipleStages: Boolean get() = bankStages.size > 1
  19. Статика и компаньоны class Summary { companion object { @JvmStatic

    fun of(decisions: Optional<DecisionsPromo>): Summary { ... } } } rx.map { Summary.of(it) } rx.map { Summary.Companion.of(it) } rx.map(Summary::of) rx.map(Summary.Companion::of) Unresolved reference: of
  20. Primary Constructor class AppSupplements (val context: Context) : ApiFieldSupplements(context) {

    override fun createApiConfigurator() = AppApiConfigurator(context) override fun getSuggestUrlProvider() = SuggestUrlProvider { ... } override fun getTimeZone() = DateUtils.SERVER_TIME_ZONE override fun createInputServiceFactory() = AppInputServiceFactory() } class AppApiConfigurator(val context: Context) : SmartFieldsApiConfigurator { override fun getApiBaseUrl() = Uri.parse(context.getString(R.string.app_api_url)) override fun getRequestParameters(request: String?) = null }
  21. Primary Constructor class AppSupplements (val context: Context) : ApiFieldSupplements(context) {

    override fun createApiConfigurator() = AppApiConfigurator(context) override fun getSuggestUrlProvider() = SuggestUrlProvider { ... } override fun getTimeZone() = DateUtils.SERVER_TIME_ZONE override fun createInputServiceFactory() = AppInputServiceFactory() } class AppApiConfigurator(val context: Context) : SmartFieldsApiConfigurator { override fun getApiBaseUrl() = Uri.parse(context.getString(R.string.app_api_url)) override fun getRequestParameters(request: String?) = null } Caused by: java.lang.NullPointerException:
  22. Primary Constructor. Что происходит? public abstract class ApiFieldSupplements { public

    ApiFieldSupplements(Context context) { this.apiConfigurator = createApiConfigurator(); registerSupplements(context); } public abstract SmartFieldsApiConfigurator createApiConfigurator(); } public final class AppSupplements extends ApiFieldSupplements { private final Context context; public AppSupplements(@NotNull Context context) { Intrinsics.checkParameterIsNotNull(context, "context"); super(context); this.context = context; } }
  23. support.Fragment public Context getContext() { return mHost == null ?

    null : mHost.getContext(); } final public FragmentActivity getActivity() { return mHost == null ? null : (FragmentActivity) mHost.getActivity(); } @Nullable @Nullable @NonNull final public Resources getResources() { if (mHost == null) { throw new IllegalStateException("Fragment not attached to Activity"); } return mHost.getContext().getResources(); }
  24. Интерфейсы Java public interface JavaInterface { boolean isEnabled(); void setEnabled(boolean

    enabled); } public class JavaJavaView extends View implements JavaInterface { public JavaJavaView(Context context) { super(context); } } class KotlinJavaView(context: Context) : View(context), JavaInterface
  25. Интерфейсы Kotlin interface KotlinInterface { var isEnabled: Boolean } public

    class JavaKotlinView extends View implements KotlinInterface { public JavaJavaView(Context context) { super(context); } } public interface KotlinInterface { boolean isEnabled(); void setEnabled(boolean var1); } class KotlinKotlinView(context: Context) : View(context), KotlinInterface { override var isEnabled: Boolean get() = super.isEnabled() set(value) { super.setEnabled(value) } }