среда, 24 июня 2015 г.

Про Gradle: #2 Синтаксис

В першій частині ми говорили про таски та їх стадії життєвого циклу. Але вже після публікації я зрозумів що перед ти як ми поринемо в особливості Gradle важливо зрозуміти з чим саме ми маємо справу - зрозуміти його синтаксис та перестати боятися вигляду файлу build.gradle. І за допомогою цієї статті я спробую заповнити дану прогалину.

Синтаксис

Скрипти Gradle написані мовою Groovy, тож до того як ми почнемо їх аналізувати, я хотів би зачепити (коротко) деякі ключові концепти Groovy. Синтаксис Groovy деяким чином схожий на  Java, тож сподіваюсь у вас не буде великих проблем з його розумінням.
Якщо з Groovy почуваєтесь на ти - не бачу проблем щоб не пропустити декілька абзаців.
Є один дужу важливий нюанс в Groovy, який вам необхідно зрозуміти для повного розуміння скриптів Gradle - Closure.

Closures

Closure - це ключовий компонент який нам необхідно зрозуміти перед освоєнням Gradle. Closure це незалежний блок коду, який може приймати аргументи, повертати значення та бути присвоєним змінній. Це такий собі мікс між інтерфейсами Callable, Future, та покажчиком на функцію.
По суті це блок коду, який виконуються при виклику, а не при стоворенні. Подивимось на простй приклад Closure:

def myClosure = { println 'Hello world!' }

//виелик closure
myClosure()

#output: Hello world!

Або тут closure який приймає параметр:

def myClosure = {String str -> println str }

//виклик closure
myClosure('Hello world!')

#output: Hello world!

Або якщо closure приймає тільки один параметр, він може виглядати як it:

def myClosure = {println it }

//виклик closure
myClosure('Hello world!')

#output: Hello world!

Чи коли closure приймає декілька параметрів:

def myClosure = {String str, int num -> println "$str : $num" }

//виклик closure
myClosure('my string', 21)

#output: my string : 21

Крім того, тип аргументу опціональний, далі бачимо спрощений приклад без типів:

def myClosure = {str, num -> println "$str : $num" }

//виклик closure
myClosure('my string', 21)

#output: my string : 21

Ще одна гарна можливість closure може посилатися на змінні з поточного контексту(читай класу). За замовчуванням, поточний контекст - це клас створений в даному closure:

def myVar = 'Hello World!'
def myClosure = {println myVar}
myClosure()

#output: Hello world!

Ще одна можливість в тому що поточний контекст для closure може бути змінений викликом Closure#setDelegate(). Ця можливість буде дуже важливою трохи пізніше:

def myClosure = {println myVar} //Зсилаємося на myVar з класу MyClass 
MyClass m = new MyClass()
myClosure.setDelegate(m)
myClosure()

class MyClass {
    def myVar = 'Hello from MyClass!'
}

#output: Hello from MyClass!

Як ви могли помітити, в момент коли ми створюємо closure, змінна myVar ще не існує. І тут це повністю нормально - вона має бути присутньою в контексті closure в момент виконання.
В цьому випадку я модифікував теперішній стан контексту для closure прямо перед тим як виконати його, тож myVar доступна.

Передача closure як аргумента

Користь від closures - в можливості передавати closure іншим методам, що допомагає нам відокремити логіку від вииконання.
Раніше ми також викоритсовували closure для передачі екземпляру іншого класу. А тепер переглянемо інші методи closure:
  1. Методи приймає один парамет - closure
    myMethod(myClosure)
  2. Якщо метод приймає один парамет - дужки можна не використовувати
    myMethod myClosure
  3. Внутрішній closure
    myMethod {println 'Hello World'}
  4. Метод приймає два параметри
    myMethod(arg1, myClosure)
  5. Те ж що й 4 
    myMethod(arg1, { println 'Hello World' })
  6. Якщо останній параметр closure - то його можна винести
    myMethod(arg1) { println 'Hello World' }
Зверніть увагу на пункти 3 та 4. Нічого не нагадує? Це ж і є Gradle скрипти.

Gradle

З синтаксисом трохи ознайомилися, тепер розберемося як же, все-таки, це відноситься до скриптів Gradle? Подивимось на простий приклад скрипту Gradle і постараємось його зрозуміти:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.2.3'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

Ви тільки подивіться! Знаючи синтаксис Groovy ми розуміємо що саме коїться в даному прикладі!
  • Метод buildscript що приймає значення closure:
    def buildscript(Closure closure)
  • Метод allprojects приймає closure:
    def allprojects(Closure closure)
...і так далі.
Прекрасно, але ця інформація сама по собі насправді не допомагає... Тепер треба визначити де саме це все виконується.
А відповіддю буде - Project

Project

Це ключ для розуміння скриптів Gradle:
Всі вирази на вищому рівні в скрипті build заделеговані в екземплярі Project
Це означає, що Project - є початковою точкою.
Спробуємо знайти метод buildscript.
Якщо будемо шукати buildscript - ми знайдемо блок скрипту buildscript {}. Секундочку.. А що ж це таке, чорт забирай??? Згідно з документацією:
Блок скриптів це виклик методу який бере closure як параметр
Добре! Знайшли! Ось що насправді трапляється, коли ми викликаємо buildscript { ... } - ми запускаємо метод buildscript який приймає Closure.
Якщо продовжимо читати документацію по  buildscript то дізнаємось: Delegates to:
ScriptHandler from buildscript
. Це означає, що обсяг виконня для closure, який ми передаємо як вхідний параметр, буде змінений на ScriptHandler. В нашому випадку ми передаємо closure який запускає метод repositories(Closure) і метод dependencies(Closure). З того часу як closure заделегований в ScriptHandler, спробуємо знайти метод dependencies в класі ScriptHandler.
І ми його знаходимо - void dependencies(Closure configureClosure), щ згідно з документацією, конфігурує залежність для скрипту. Тут ми натикаємось на ще один термін: Executes the given closure against the DependencyHandler. Що означає те ж саме що й "lделегується до [чогось]" - цей closure буде виконано в рамках іншого класу (в нашому випадку - DependencyHandler)
"delegates to [something]" та "configures [something]" - 2 вирази, що мають одне значення - closure буде виконано в зазначеному класі.
Gradle постійно використовує стратегію делегатів, отже важливо тут розуміти термінологію.
Для повноти, давайте подивимось, що трапиться коли ми виконаємо closure {classpath 'com.android.tools.build:gradle:1.2.3'} в контексті DependencyHandler. Згідно з документацією цей клас конфігурує залежності і має виглядати як:
<configurationName> <dependencyNotation1>
Отже в нашому closure ми зконфігуруємо classpath для використання com.android.tools.build:gradle:1.2.3 як залежності.

Блоки скриптів

За замовчуванням, існують попередньо встановлені блоки в Project, але плагіни Gradle дозволяють додавати нові блоки самостійно!
Це означає, що якщо ви бачите щось на кшталт something { ... } на верхньому рівні вашого білд скрипту і ви не можете знайти ні блок скриптів, ні метод, який приймає closure в документації - схоже на те, що один з плагінів, які ви використовувал, додав цей блок скриптів.

android Script block

Подивимось на скрипт білду Android app/build.gradle за замовчуванням:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "com.trickyandroid.testapp"
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
Як ми можемо бачити, здається, що тут має бути методи android який приймає Closure як параметр. Але якщо ми спробуємо знайти такий метод в документації Project то не знайдемо нічого подібного. Причина проста - такого методу просто не має :)
Якщо придивитися до скрипту build - ви побачите що перед запуском методу android - ми застосовуємо плагін com.android.application! Це і є відповідь на наше запитання! Android наслідує Project з блоком скриптів android (що є просто методом, який приймає Closure та делегує його до lкласу1 AppExtension ).
Але де нам знайти документацію плагінів Android? ЇЇ можна завантажити з офіційного веб сайту Android Tools.
Якщо ми відкриємо документацію AppExtension то знайдемо всі методи та атрибути, які необхідні.

Вправа

Спробуємо самотушки щось переналаштувати, бо ми тепер знаємо як це робиться :)
В AppExtension є блок скриптів testOptions який делегує Closure в клас TestOptions. Перейдемо до класу TestOptions ми бачимо дві властивості: reportDir та resultsDir. Згідно з документацією, reportDir несе відповідальність за місце розташування протоколу тестування. Змінимо його.

android {
......
    testOptions {
        reportDir "$rootDir/test_reports"
    }
}

Ми використали властивість rootDir з класу Project що вказує на корінь папки проекту.
Тож якщо зараз ми запустимо ./gradlew connectedCheck, звіт про тестування відправиться в папку[rootProject]/test_reports .
На практиці цього краще не робити.

Happy gradling!

Source.

вторник, 23 июня 2015 г.

Про Gradle: #1 Таски

Цією публікацією, я б хотів розпочати серію статей пов'язаних з Gradle, ох, хотілося б мені знати це, коли я почав писати скрипти на Gradle.
Сьогодні ми поговоримо про таски в Gradle, а особливо про їх конфігурацію та виконання частин таску. Зважаючи на те, що багато термінів можуть виявитися невідомими більшості читачів, буде набагато легше приводити приклади. По суті ми постараємось виявити в чому різниця між цими трьома прикладами:

task myTask {
    println "Hello, World!"
}

task myTask {
    doLast {
        println "Hello, World!"
    }
}

task myTask << {
    println "Hello, World!"
}

Моя основна ціль написати таск, який виводить на екран текст "Hello, World!" при виконанні.
Коли я починав розбиратися з Gradle, першими з моїх варіантів, була реалізація подібним чином:

task myTask {
    println "Hello, World!"
}

Зараз, спробуємо запустити таск!

user$ gradle myTask
Hello, World!
:myTask UP-TO-DATE

Здається все працює! Ми бачимо "Hello, World!".
АЛЕ! Воно працює не так, як ми очікували. І ось чому. Давайте-спробуємо викликати gradle tasks і побачимо, які інші такски в нас є:

user$ gradle tasks
Hello, World!
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
..........

Секундочку! Чому наше "Hello, World!" вивелось на екран? Ми ж просто викликали tasks, Чому ж запустився наш таск!
Причина чому це трапляється в тому, що таски Gradle матюь 2 стадії життєвого циклу:
  • Стадія конфігурації
  • Стадія виконання
Насправді, я не був дуже точний в термінології, але ця аналогія допомогла мені зрозуміти, що ж таке таски.
Справа в тому, що Gradle має сконфігурувати всі таски зазначені в скрипті перед тим як білд буде запущено. Немає значення, якщо деякі таски будуть запущені - вони все-одно мають бути сконфігуровані.
Знаючи це, як нам знати, яка частина таску виконується під час конфігурації, а яка під час виконання?
Відповіддю є - частина вказана у верхній частині таску - це і є секція конфігурації таску:

task myTask {
    def name = "Pavel" //<-- this is evaluated during configuration
    println "Hello, World!"////<-- this is also evaluated during configuration
}

Ось чому, коли ми викликали gradle tasks то було видно текст "Hello, World!" - це була виконана наша частина конфігурції. Але це не те, що нам було потрібно - нам потрібно що текст "Hello, World!" був надрукований, коли ми явно викликаємо таск.
То як нам сказати Gradle зробити щось коли наш таск буде виконуватись?
Щоб зробити це нам необхідно зазначити дію. Найлегший спосіб зробити це через Task#doLast() метод:

task myTask {
    def text = 'Hello, World!' //configure my task
    doLast {
        println text //this is executed when my task is called
    }
}

Тепер наш "Hello, World!" текст буде виведено тільки тоді, коли буде явно введена команда gradle myTask
Круто, тепер ми знаємо як зконфігурувати і змусити наш таск виконувати ту роботу, яку ми йому задамо тільки тоді, коли ми його викликаємо. Залишився тільки третій варіант, що використовує символ "<<"?:

task myTask2 << {
    println "Hello, World!" 
}

Насправді, це просто коротка версія від doLast. Буде виконано теж саме, коли ми напишемо:

task myTask {
    doLast {
        println 'Hello, World!' //this is executed when my task is called
    }
}

Отже, тепер все знаходиться в блоці виконання, я не можу зконфігурувати мій таск таким же чином отже ми це зробимо завдяки конструкції doLast (це досі можливо, але трохи по-іншому). Стає зрозуміло, що цей спосіб буде незамінними при невеликих таксках без блоку конфігурації, але якщо ви маєте щось більше ніж "Hello, World!" - можливо вам буде в нагоді doLast.

Happy gradling!

Source.

понедельник, 22 июня 2015 г.

Залізна людина тепер реактивна: RxJava

Дана стаття присвячена перевагам, що можуть бути надані нашим проектам деякими функціональними штрихами.
Фреймворки типу RxJava та ReactiveX можуть легко вправитися з середовищами, що працюють у фоні та в UI потоці.
Ця стаття також фокусується на тому, як оператори можуть мінімізувати час на загальну розробку завдань, реактивні розширення пропонують широкий спектр операторів, щоб зробити ваше життя простіше.
Як завжди, код на Github, не соромтеся коментувати, відкрити питання або скаржитися!.

RetroLambda

Інколи, у великих додатках, розроблених з Java, або великих фреймворках як android дуже важко, чи навіть неможливо (android) використовувати можливості Java 8 такі як лямда-вирази
Retrolambda це бекпорт, що з'явився для вирішення даної проблеми, переклад байткодів Java 8 на попередні версії Java дає можливість користуватися лямда-виразами та іншими можливостями нової платформи.

Retrolamda може працювати з Gradle та Maven, я обрав Gradle так як він йде в комплекті з Android Studio, для використання вам досить додати в корінь файлу build.gradle плагін Retrolambda та застосувати у вашому модулі build.gradle, встановити рівень мови на 1.8 в Android Studio і все готово.

build.gradle (root)
    dependencies {
        classpath 'me.tatarka:gradle-retrolambda:3.1.0'
{ваш модуль}/build.gradle
    apply plugin: 'me.tatarka.retrolambda'

    android { 

        ...

        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }
}

Retrolambda дозволяє вам писати менше шаблонного коду, а також робить код зрозумілішим. У цьому прикладі написаному Den Lew ви можете побачити різницю.

Без використання RetroLambda
Observable.just("Hello, world!")
    .subscribe(new Action1<String>() {
        @Override
        public void call(String s) {
              System.out.println(s);
        }
    });
З використанням RetroLambda
Observable.just("Hello, world!") .subscribe(
    s -> System.out.println(s)
);

А тепер на прикладі додатку Месники:

mCharacterSubscription = mGetCharacterInformationUsecase
    .execute().subscribe(
        character -> onAvengerReceived(character),
        error     -> manageError(error)
    );

mComicsSubscription = mGetCharacterComicsUsecase
    .execute().subscribe(
        comics -> Observable.from(comics).subscribe(
            comic -> onComicReceived(comic)),
        error  -> manageError(throwable)
    );

ReactiveX

ReactiveX - це колекція з проектів з відкритим вихідним кодом серед їх основних принципів є шаблон проектування Спостерігач, шаблон Ітератор та функціональне програмування.
ReactiveX також має API для асинхронного програмування, з цим фреймворком насправді дуже легко реалізовувати завдання асинхронно.

ReactiveX як асинхронний клієнт

Гарна штука, що користуєчись ReativeX можна створювати повністю асинхронний API чи клієнт, а потім вже при реалізації вирішувати, який код буде працювати асинхронно чи в окремому потоці (Thread) чи синхронно.  
Таким чином, ми маємо спостережуваний API, а не блокуючий API.

public interface Usecase<T> {

    Observable<T> execute();
}
public interface Repository {

    Observable<Character> getCharacter (final int characterId);

    Observable<List<Comic>> getCharacterComics (final int characterId);
} 
 

Чим же насправді є RxJava

Клас Observable створює об'єкт чи серію об'єктів ці об'єкти отримуються методом Observer, що наледить класу "Observable".


Необхідно щоб Observer був прив'язаний до Observable, в іншому випадку Observer не видасть нічого. Коли Observer пррив'язаний, об'єкт типу Subscription створюється, він  використовується для відв'язування Observable, це крисно при роботі з Activity та Fragment в методах onStop чи onPause, наприклад.

mCharacterSubscription = mGetCharacterInformationUsecase
    .execute().subscribe( ... );
@Override
public void onStop() {

    if (!mCharacterSubscription.isUnsubscribed())
        mCharacterSubscription.unsubscribe();

    if (!mComicsSubscription.isUnsubscribed())
        mComicsSubscription.unsubscribe();
} 
 
Коли Observer прив'язаний до Observable він має отримати три методи.
  • onNext (T) метод для отримання об'єктів позначиених 'Observable' помилковими
  • onError (Exception), метод який викликається при виявленні помилок
  • onCompleted(), метод який викликаєть коли Observable завершує створення об'єктів.

Обожнюю цю картинку:)

Компоненти зв'язку

Тепер подивимось на те як можна використовувати GetCharacterInformationUsecase. Варіаннти реалізації інтерфейсу Usecase <T>:

public interface Usecase<T> {

    Observable<T> execute();
}
 
Коли usecase запущено - повертається об'єкт типу Observable, корисна можливість бути зв'язаним з observable та операторами з меншими зусиллями, ми ще побачимо велику силу операторів в подальшому.
Коли ми запускаємо GetCharacterInformationUsecase ми говоримо нашому репозиторію запросити у відведеного джерела даних:

@Override
public Observable<Character> execute() {

    return mRepository.getCharacter(mCharacterId);
        // .awesomeRxStuff();
}
 
AvengerDetailPresenter буде нашим Observer в даному випадку буде тим хто піписується до подій надісланих Observable, це робиться за допомогою методу subscribe, що зв'язує Observer з Observable.
onNext та onError реалізовані для управління результатом операції. Метод onCompleted не реаалізований в даному випадку так як не є необхідним.

    mCharacterSubscription = mGetCharacterInformationUsecase
        .execute().subscribe(
            character   -> onAvengerReceived(character),
            error       -> manageError(error));

Retrofit & RxJava

Retrofit зі Square, RxJava підтримує методи типу rx. Observable так що запити можна робити за допомогою Observers редагувати та трансформувати.
Ви маєте бути на сторожі в залежності від того де використовується, Retrofit виконує запити в потоці де ваш Observable виходить, отже якщо ви будете його викликати в UI потоці (activity чи Fragment) отримаєте помилку.

Посилання










Source.

воскресенье, 21 июня 2015 г.

Можливо ви не знали: onResumeFragments

Вся суть в одному реченні: якщо ви використовуєте підкласи FragmentActivity (такі як AppCompatActivity) і думаєте над якимись транзакціями в onResume, краще зробіть це в onResumeFragments.
Якщо ви відчуваєте, що бачите деякі прогалини чи неточності в своїх знаннях, запрошую до читання. В іншому випадку сподіваюсь здивувати вас наступного разу.

Все ще тут? Добре.
В чому різниця між onResume та onResumeFragments? З документації для FragmentActivity.onResume:

Dispatch onResume() to fragments. Note that for better inter-operation with older versions of the platform, at the point of this call the fragments attached to the activity are not resumed. This means that in some cases the previous state may still be saved, not allowing fragment transactions that modify the state. To correctly interact with fragments in their proper state, you should instead override onResumeFragments().

Простіше кажучи, ви не можете бути впевнен, що існуючий фрагмент актівіті буде відновлений в актівіті onResume(), і краще уникнути транзакцій фрагментів до onResumeFragments, поки їх стан не поновлено і вони повністю не відновлені.
Притримуючись сказаного ви можете уникнути IllegalStateException, який Android викидає кожного разу як ви намагаєтесь виконати транзакцію з фрагментом після того/, як його стан був збережений (через onSaveInstanceState). Якщо актіві фрагменту знищено і перестворено, ви втратите всі вподальному збережені дані. Для більш повного та детального пояснення, пропоную прочитати статтю, Alex Lockwood's "Fragment Transactions & Activity State Loss".
Правду кажучи, я дізнався про onResumeFragments задовго після того як дізнався про фрагменти та транзакції між ними. Більшість матеріалів по життєвому циклу актівіті опускає його оскільки він існує тільки в FragmentActivity в біблітоці підтримки (FragmentActivity), а не в класі SDK актівіті. Однак, onResumeFragments краще, все ж таки, знати.
Після всйого сказаного, хочу додати, що все ж кращу уникати транзакцій між фрагментамив життєвому циклі актівіті настільки наскільки це можливо і особливо в onResume/onResumeFragments це скоріше гарна підказка для вашої бізнес логіки та UI/UX аніж правило.

Зустрінимось.