Чому слід використовувати RxJava в Android – короткий вступ в RxJava

Здравствуйте все.

Ми продовжуємо знайомити вас з нашим видавничим пошуком, і хотіли прозондувати громадська думка на тему RxJava.



Найближчим часом збираємося опублікувати більш загальний матеріал по реактивному програмування, яке нас також цікавить не перший рік, а сьогодні пропонуємо почитати про застосування RxJava в Android, так як саме на цій платформі особливо важлива динамічність і швидкість реагування. Ласкаво просимо під кат

У більшості додатків Android ми реагуємо на дії користувача (клацання, змахування, тощо), а тим часом у фоновому режимі йде якась інша робота (мережева).

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

Саме в таких випадках як не можна до речі буде RxJava (ReactiveX) – бібліотека, що дозволяє соорганизовать безліч дій, обумовлених певними подіями в системі.

Працюючи з RxJava, можна буде забути про зворотні виклики і пекельному управлінні глобальним станом.

— Чому?

Повернемося до нашого прикладу:

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


Якщо розібрати цю ситуацію детальніше, знайдемо в ній три основних етапи, причому всі три відбуваються у фоновому режимі:

  1. Вибрати користувача з бази даних
  2. Одночасно вибрати настройки і повідомлення
  3. Скомбінувати результати обох запитів в один


Щоб зробити те ж саме в Java SE і Android, нам би треба було:

  1. Зробити 3-4 різні
    AsyncTasks
  2. Створити семафор, який дочекається завершення обох запитів (з налагодження і за повідомленнями)
  3. Реалізувати поля на рівні об'єктів для зберігання результатів


Вже зрозуміло, що для цього потрібно керувати станом, а також задіяти деякі механізми блокування, існуючі в Java.

Усього цього можна уникнути, працюючи з RxJava (див. приклади нижче) – весь код виглядає як потік, розташований в одному місці і будується на базі функціональної парадигми (див. тут).

Швидкий запуск Android

Щоб отримати бібліотеки, які, швидше за все, знадобляться вам для проекту, вставте в ваш файл build.gradle наступні рядки:

compile 'io.reactivex:rxjava:1.1.0'
compile 'io.reactivex:rxjava-async-util:0.21.0'

compile 'io.reactivex:rxandroid:1.1.0'

compile 'com.jakewharton.rxbinding:rxbinding:0.3.0'

compile 'com.trello:rxlifecycle:0.4.0'
compile 'com.trello:rxlifecycle-components:0.4.0'


Таким чином будуть включені:

  • RxJava – основна бібліотека ReactiveX для Java.
  • RxAndroid — розширення RxJava для Android, які допоможуть працювати з потоками в Android і з Loopers.
  • RxBinding – прив'язки між RxJava і елементами інтерфейсу Android, зокрема, кнопками Buttons і текстовими уявленнями TextViews
  • RxJavaAsyncUtil – допомагає склеювати код Callable і Future.


Приклад

Почнімо з прикладу:

Вами.just("1", "2")
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
}
});


Тут ми створили Вами, який згенерує два елемента — 1 і 2.
Ми передплатили вами, і тепер, як тільки елемент буде отриманий, ми виведемо його на екран.

Деякі деталі

Об'єкт Вами – така сутність, на яку можна підписатися, а потім приймати генеруються Вами елементи. Вони можуть створюватися самими різними способами. Однак Вами зазвичай не починає генерувати елементи, поки ви на них не підписатися.

Після того, як ви підпишетеся на вами, ви отримаєте Subscription (підписку). Підписка буде приймати об'єкти, що надходять від вами, поки він сам не просигналізує, що завершив роботу (не поставить таку позначку), або (в дуже рідкісних випадках) прийом буде тривати нескінченно.

Більш того, всі ці дії будуть виконуватися в головному потоці.

Розширений приклад

Вами.from(fetchHttpNetworkContentFuture())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
});


Тут спостерігаємо дещо нове:
  1. subscribeOn(Schedulers.io()) – завдяки цьому методу Вами буде виконувати очікування та обчислення в пулі потоків ThreadPool, призначеному для вводу/виводу (Schedulers.io()).
  2. observeOn(AndroidSchedulers.mainThread()) – завдяки цьому методу, результат дії передплатника буде виконаний в головному потоці Android. Це потрібно у випадках, коли вам потрібно щось змінити в інтерфейсі Android.
  3. У другому аргументі до .subscribe() з'являється обробник помилок для операцій з підпискою на випадок, якщо щось піде не так. Така штука повинна бути присутні майже завжди.


Управління складним потоком

Пам'ятайте складний потік, описаний нами на самому початку?

Ось як він буде виглядати з RxJava:

Вами.fromCallable(createNewUser())
.subscribeOn(Schedulers.io())
.flatMap(new Func1<User, Вами<Pair<Settings, List<Message>>>>() {
@Override
public Вами<Pair<Settings, List<Message>>> call(User user) {
return Observable.zip(
Вами.from(fetchUserSettings(user)),
Вами.from(fetchUserMessages(user))
, new Func2<Settings, List<Message>, Pair<Settings, List<Message>>>() {
@Override
public Pair<Settings, List<Message>> call(Settings settings, List<Message> messages) {
return Pair.create(settings, messages);
}
});
}
})
.doOnNext(new Action1<Pair<Settings, List<Message>>>() {
@Override
public void call(Pair<Settings, List<Message>> pair) {
System.out.println("Received settings" + pair.first);
}
})
.flatMap(new Func1<Pair<Settings, List<Message>>, Вами<Message>>() {
@Override
public Вами<Message> call(Pair<Settings, List<Message>> settingsListPair) {
return Вами.from(settingsListPair.second);
}
})
.subscribe(new Action1<Message>() {
@Override
public void call(Message message) {
System.out.println("New message " + message);
}
});


У такому разі буде створено новий користувач (createNewUser()), і на етапі його створення і повернення результату у той же самий час продовжиться вибір користувальницьких повідомлень (fetchUserMessages()) і налаштувань (fetchUserSettings). Ми дочекаємося завершення обох дій і повернемо скомбінований результат (Pair.create()).

Не забувайте, що все це відбувається в окремому потоці (у фоновому режимі).

Потім програма виведе на екран отримані результати. Нарешті, список повідомлень буде пристосований ще один вами, який буде виводити повідомлення поодинці, а не цілим списком, причому кожне повідомлення буде з'являтися у вікні терміналу.

Функціональний підхід

Працювати з RxJava буде набагато простіше, якщо ви знайомі з функціональним програмуванням, зокрема, з концепціями map і zip. Крім того, в RxJava і ФП дуже схоже вибудовується узагальнена логіка.

Як створити власний вами?

Якщо код стає значною мірою зав'язаний на RxJava (наприклад, тут ), то часто вам доведеться писати власні вами, так, щоб вони вкладалися в логіку вашої програми.

Розглянемо приклад:

public Вами<String> customObservable() {
return rx.Вами.create(new rx.Вами.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> subscriber) {
// Виконується у фоновому режимі
Scheduler.Worker inner = Schedulers.io().createWorker();
subscriber.add(inner);

inner.schedule(new Action0() {

@Override
public void call() {
try {
String fancyText = getJson();
subscriber.onNext(fancyText);
} catch (Exception e) {
subscriber.onError(e);
} finally {
subscriber.onCompleted();
}
}

});
}
});
}


А ось схожий варіант, що не вимагає виконувати дію строго в конкретному потоці:

Вами<String> вами = Вами.create(
new Вами.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Ні");
subscriber.onCompleted();
}
}
);



Тут важливо відзначити три методу:

  1. onNext(v) – відправляє передплатнику нове значення
  2. onError(e) – повідомляє спостерігача говорити про помилку
  3. onCompleted() – повідомляє підписувача про те, що слід відмовитися, оскільки від цього вами більше не надійде жодного контенту


Крім того, ймовірно, буде зручно користуватися RxJavaAsyncUtil.

Інтеграція з іншими бібліотеками

По мірі того, як RxJava стає все популярнішим і де-факто перетворюється в стандарт асинхронного програмування в Android, все більше бібліотек все більшою мірою інтегруються з нею.

Лише кілька прикладів:

Retrofit — «Типобезопасный HTTP-клієнт для Android і Java»
SqlBrite — «Легка обгортка для SQLiteOpenHelper, збагачує SQL-операції семантикою реактивних потоків.»
StorIO — «Красивий API для SQLiteDatabase і ContentResolver»

Всі ці бібліотеки значно спрощують роботу з HTTP-запитами і базами даних.

Інтерактивність з Android UI

Це введення було б неповним, якби ми не розглянули, як використовувати нативні UI-елементи в Android.

TextView finalText;
EditText editText;
Button button;
...

RxView.clicks(button)
.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
System.out.println("Click");
}
});

RxTextView.textChanges(editText)
.subscribe(new Action1<CharSequence>() {
@Override
public void call(CharSequence charSequence) {
finalText.setText(charSequence);
}
});
...


Очевидно, можна просто покластися на setOnClickListener, але в довгостроковій перспективі RxBinding може підійти вам краще, оскільки дозволяє підключити UI до загального потоку RxJava.

Поради

Практика показує, що при роботі з RxJava слід дотримуватися деяких правил.

Завжди використовувати обробник помилок

Пропускати обробник помилок таким чином

.subscribe(new Action1<Void>() {
@Override
public void call(Void aVoid) {
System.out.println("Click");
}
});


зазвичай не слід. Виняток, що викидається в спостерігача або в одному з дій буде викинуто виключення, яке, швидше за все, вб'є вам усе додаток.

Ще краще було б зробити узагальнений обробник:

.subscribe(..., myErrorHandler);


Витягувати методи дій

Якщо у вас буде багато внутрішніх класів, то через деякий час читабельність коду може зіпсуватися (особливо якщо ви не працюєте з RetroLambda).

Тому такий код:

.doOnNext(new Action1<Pair<Settings, List<Message>>>() {
@Override
public void call(Pair<Settings, List<Message>> pair) {
System.out.println("Received settings" + pair.first);
}
})


виглядав би краще після такого рефакторінгу:

.doOnNext(logSettings())

@NonNull
private Action1<Pair<Settings, List<Message>>> logSettings() {
return new Action1<Pair<Settings, List<Message>>>() {
@Override
public void call(Pair<Settings, List<Message>> pair) {
System.out.println("Received settings" + pair.first);
}
};
}


Використовувати власні класи або кортежі

Бувають випадки, в яких певне значення визначається іншим значенням (наприклад, користувач і призначені для користувача налаштування), і ви хотіли б отримати обидва ці значення за допомогою двох асинхронних запитів.
У таких випадках рекомендую використовувати JavaTuples.

Приклад:

Вами.fromCallable(createNewUser())
.subscribeOn(Schedulers.io())
.flatMap(new Func1<User, Вами<Pair<User, Settings>>>() {
@Override
public Вами<Pair<User, Settings>> call(final User user) {
return Вами.from(fetchUserSettings(user))
.map(new Func1<Settings, Pair<User, Settings>>() {
@Override
public Pair<User, Settings> call(Settings o) {
return Pair.create(user, o);
}
});

}
});



Управління життєвим циклом

Часто буває так, що фоновий процес (підписка) має проіснувати довше, ніж активність або фрагмент, у якому (якій) він міститься. Але що якщо результат вас вже не цікавить, як тільки користувач залишить активність?

У таких випадках вам допоможе проект RxLifecycle.

Оберніть ваш вами ось так (взято з документації) і відразу після його руйнування виконається відписка:

public class MyActivity extends RxActivity {
@Override
public void onResume() {
super.onResume();
myObservable
.compose(bindToLifecycle())
.subscribe();
}
}



Висновок

Звичайно, це далеко не повне керівництво про використання RxJava в Android, але, сподіваюся, що зміг вас переконати, що в деяких відносинах RxJava краще звичайних AsyncTask.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.