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

от автора

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


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

Я люблю читать книги с телефона и люблю твитить из этих книг цитаты. Но поскольку эти книги как правило содержат цитаты длиной более чем 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’ов и комментариев!

* слава очень ограниченная, статья с перечислением килотонн подводных камней уже в пути

ссылка на оригинал статьи http://habrahabr.ru/post/263657/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *