Pull to refresh

Щупаем Kotlin (и чуть-чуть Gradle) на примере Отправлялки-длинных-твитов (открытые исходники)

Reading time 5 min
Views 13K
Я никогда не был любителем HelloWorld и туториалов. В них как правило решаются проблемы, которые данным инструментом решаются хорошо, а вот острые углы и недостатки деликатно обходятся. По-настоящему пощупать язык или библиотеку можно только на реальном приложении, написании «бизнес-кода», а не «сервиса фабрики сервисов фабрик моделей сервисов». Пример такого простого приложения — на видео. Ну что, поехали?



Предпосылки к задаче


Я люблю читать книги с телефона и люблю твитить из этих книг цитаты. Но поскольку эти книги как правило содержат цитаты длиной более чем 140 символов, ситуация несколько раздражала. Решить проблему можно было бы двумя путями:

  1. отправлять цитаты несколькими твитами
  2. твитить текст картинами.

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

Что можно узнать из получившегося проекта


  1. посмотреть на плюсы Kotlin
  2. посмотреть на типовый скрипт сборки в Gradle
  3. утащить функцию вычисления размера шрифта для вписывания текста в прямоугольник, приема текста, расшаренного из чужого приложения, функцию расшаривания картинки в чужие приложения и прочие мелочи.

Бенефиты Kotlin


Последние месяцы, слава* Xamarin'у, я писал под мобилки на .NET (C#). После него возвращаться в Android (где царит Java 7 с ограничениями, если вы хотите писать под Android 4.1+) не то что бы неприятно, но контраст очень заметен. Сахарку не хватает. Kotlin же дико радует возможностью писать код лаконично, машина за вас делает нехилую часть рутины.

Нужно объявить просто POJO-класс с readonly полям? Легко.

public class Size(val width:Int,val height:Int);

Самостоятельно объявлять тип переменных? Но зачем, если компилятор может вывести их за вас!

val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG);
var paint = Paint();

Причем var — это переменная, а val — это final объект, так что случайно вам её иным значением не заменить.
Так же код позволяют сократить операторы вроде with:

fun alertError(text:String){
    val builder = AlertDialog.Builder(this);
    with(builder) {
        setTitle(R.string.error_title)
        setMessage(text)
        setPositiveButton(R.string.ok, { dialogInterface, button -> })
    }
    builder.create().show();
}

Или возможность втыкать переменные внутри строки без функции format или аналогичной по смыслу:

val cachePath = File(getExternalCacheDir(), "temp");
cachePath.mkdirs();
val fileName = "$cachePath/long_text_image.png";

Callback'и для кликов тоже назначаются достаточно более коротким кодом:

buttonSend.setOnClickListener {
    val text = editText.getText().toString();
    //и т.п....
}

И как вы наверное уже заметили их примеров выше, оператор «new» тоже упразднён.

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

package com.newbilius.longtextsharer
import [...]

public fun getMaxFontSizeOfMultilineText(text: String, maxSize: Size, maxTextSize: Int): Float {
    fun getHeightOfMultiLineText(text: String, textSize: Int, maxWidth: Int): Int {
    //[...]
    }

    var textSize = maxTextSize;
    while (getHeightOfMultiLineText(text, textSize, maxSize.width) > maxSize.height)
        textSize--;

    return textSize.toFloat();
}

А ещё под Kotlin+Android у Kotlin есть такая классная штука — Kotlin Android Extensions. Она позволяет забыть findViewById() как страшный сон и делать следующий финт ушами:

import kotlinx.android.synthetic.activity.имя_xml_файла_activity.*
//просто используем в коде ID-щники, объявленный в XML'е
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_send)
    editText.setText(getIntent().getStringExtra(Intent.EXTRA_TEXT));
    //[...]
}

На порядок удобнее, чем аннотации RoboGuice.

Пожалуй единственное, что мне показалось странным — формат досрочного выхода из CallBack'а:

buttonSend.setOnClickListener {
    val text = editText.getText().toString();
    if (text.length()==0)
    {
        alertError(R.string.error_empty_text);
        return@setOnClickListener; //вот тут вот просто так берём и выходим
    }
    //[...]
} 

Используем Gradle


Сегодня сборка Android-приложений с помощью Gradle считается стандартом. Правда немного напрягает то, что разработка Groovy лежащего в основе Gradle остановлена (?) в начале года. Ну да ладно, побудем оптимистами. Минимальный скрипт сборки приложения (в debug- и release-сборке) выглядит вот так.

Gradle-скрипт
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
import groovy.swing.SwingBuilder

buildscript {
    ext.kotlin_version = '0.12.1218'
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:1.1.1"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
    }
}

repositories {
    jcenter()
    mavenCentral()
}

gradle.taskGraph.whenReady { taskGraph ->
    if(taskGraph.hasTask(':longtextsharer:assembleRelease')) {
        def pass = '';
        pass = System.console().readPassword("\nPlease enter key passphrase: ")
        pass = new String(pass)
        if(pass.size() <= 0) {
            throw new InvalidUserDataException("You must enter a password to proceed.")
        }
        android.signingConfigs.release.storePassword = pass
        android.signingConfigs.release.keyPassword = pass
    }
}

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    signingConfigs {
        release {
            storeFile file("sign/SET_YOU_KEY.jks")
            storePassword ""
            keyAlias "SET_YOU_KEY"
            keyPassword ""
        }
    }

    defaultConfig {
        applicationId "com.newbilius.longtextsharer"
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        debug {
            debuggable true
        }
        release {
            debuggable false
            minifyEnabled true
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.android.support:design:22.2.1'
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}

К слову, крайне рекомендую использовать proguard для сборки релизной версии — без неё у меня Kotlin-приложение весило раз в 5 больше, чем аналогичное Java-приложение. После обработки proguard'ом же разница в размере была в 100 килобайт или около того. В proguard-правила для сборки пришлось добавить всего одно правило:

-dontwarn org.w3c.dom.events.*

Впрочем при сборке того же скрипта не из консоли, а из например IntelliJ IDEA вы наткнётесь на проблему — консоли для ввода пароля для сертификата у вас в этом случае нет. Для данной проблемы вроде как есть решение — но мне оно не подошло, при попытке его использовать я получил ошибку:

Error:(29, 0) Gradle: Failed to create component for ‘dialog’ reason: java.awt.HeadlessException
> java.awt.HeadlessException (no error message)

Возможно, вам повезёт больше и вы поможете найти мне ошибку? Версии библиотек, Java и самой IDE самые свежие.

Локальные решения и выводы


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

Ссылка на приложение на GitHub.

В общем, как по мне, на Kotlin код получается более лаконичным и простым, так что для своих небольших проектов я его использовать (как и делиться исходниками) продолжу и дальше. Если вам показалось, что я не использовал в этом приложении ещё какие-либо классные (и уместные) возможности Kotlin или Gradle — жду pull-request'ов и комментариев!

* слава очень ограниченная, статья с перечислением килотонн подводных камней уже в пути
Tags:
Hubs:
+14
Comments 15
Comments Comments 15

Articles