Предпосылки к задаче
Я люблю читать книги с телефона и люблю твитить из этих книг цитаты. Но поскольку эти книги как правило содержат цитаты длиной более чем 140 символов, ситуация несколько раздражала. Решить проблему можно было бы двумя путями:
- отправлять цитаты несколькими твитами
- твитить текст картинами.
Именно второй путь мне показался менее ужасным. В результате получилось приложение, используемое в цепочке действий: расшарить текст из любого приложения -> попасть в нашу программу -> расшарить уже картинку в опять же любое приложение
Что можно узнать из получившегося проекта
- посмотреть на плюсы Kotlin
- посмотреть на типовый скрипт сборки в Gradle
- утащить функцию вычисления размера шрифта для вписывания текста в прямоугольник, приема текста, расшаренного из чужого приложения, функцию расшаривания картинки в чужие приложения и прочие мелочи.
Бенефиты 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-сборке) выглядит вот так.
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'ов и комментариев!
* слава очень ограниченная, статья с перечислением килотонн подводных камней уже в пути
Комментарии (16)
HotIceCream
28.07.2015 12:08+1Возможно ли использовать Kotlin Android Extensions поиска вьюх в элементе списка? Или же все равно придется вспоминать о findViewById? Кстати, есть github.com/JakeWharton/kotterknife — аналог ButterKnife для котлин.
Newbilius Автор
28.07.2015 13:47Спасибо за ссылку! Посмотрю.
Вопрос про поиск вьюх внутри элемента списка отличный, я проверю в ближайшем будущем — есть идея следующего небольшого (но полезного как минимум мне ;-) приложения. Там будет список и ORM, посмотрю как они дружат.
potan
28.07.2015 12:16+1А по сравнению со Scala какие есть плюсы? Все, что здесь написано, на Scala делается не сложнее.
Newbilius Автор
28.07.2015 13:49Я не писал на Scala, так что чисто умозрительно:
1) более простой старт в смысле интеграции с IDE, синтаксиса и т.п. (субъективно)
2) более быстрая компиляция Kotlin (по слухам и отзывам знакомых)
injecto
28.07.2015 14:16+1Kotlin на первый взгляд более ортогонален и, очевидно, проще для понимания кода и его отладки. Вообще есть такое сравнение.
epahomov
28.07.2015 12:24Котлин сейчас активно ищет PMM. Задание — текст не больше 800 слов. Тут 832 считая код. Наверно просто совпадение.
atetc
29.07.2015 09:45+1Кстати есть отдельный чатик про разработку на Kotlin gitter.im/rus-speaking/android-kotlin
fogone
Подскажите, не касательно котлина, почему бы не постить текст туда, где нет ограничения?
Newbilius Автор
Вполне разумный вопрос. Но это как с переездом со Skype/ICQ куда-то ещё. Для этого нужно всех собеседников перетащить, а это — дополнительные усилия…