Producers
У цьому уроці розберемо асинхронні механізми Dagger: ProductionComponent, ProducerModule, Produces, Producer. А також розберемо як за допомогою Produced обробляти помилки.
За аналогією з анотаціями @Module, @Provides, і @Component даггер надає анотації @ProducerModule, @Produces, і @ProductionComponent. Компоненти і модулі з такими анотаціями працюватимуть асинхронно, тобто створюватимуть об'єкт в іншому потоці. Для забезпечення асинхронності, компонент повертає нам не сам об'єкт, а ListenableFuture, на який ви можете повісити колбек.
Щоб використовувати Produced-анотації, додайте в build.gradle, в список dependencies рядок:
compile 'com.google.dagger:dagger-producers:2.7'
Розглянемо приклад, у якому ми від сервера отримуватимемо дані юзера. Тобто у нас є об'єкт User, а в NetworkUtils є метод getUserData(User user), який отримає від сервера дані за вказаним юзером. Отримання даних від сервера може зайняти деякий час, тому будемо використовувати асинхронні механізми дагера, щоб не блокувати UI-потік.
Вміст модуля:
@ProducerModule(includes = NetworkModule.class)
public class UserDataModule {
User mUser;
public UserDataModule(User user) {
mUser = user;
}
@Produces
UserData getUserData(NetworkUtils networkUtils) {
return networkUtils.getUserData(mUser);
}
}
Зверніть увагу, що використовуємо анотації ProducerModule і Produces замість Module і Provides.
Модуль надає об'єкт UserData. Для цього він викликає метод NetworkUtils.getUserData. Об'єкт NetworkUtils буде отримано з модуля NetworkModule (вказано в include). Щоб виконати код в іншому потоці, компоненту потрібен Executor. Його необхідно надати у звичайному модулі
@Module
public class ExecutorModule {
@Provides
@Production
static Executor executor() {
return Executors.newSingleThreadExecutor();
}
}
До анотації Provides необхідно додати анотацію Production. Це дасть зрозуміти даггеру, що він може використовувати цей Executor для своїх асинхронних цілей.
Компонент з анотацією ProductionComponent:
@ProductionComponent(modules={UserDataModule.class, ExecutorModule.class})
public interface UserComponent {
ListenableFuture<UserData> getUserData();
}
У ньому описано метод getUserData, який повертає UserData, обгорнутий у ListenableFuture
Залишилося в Activity отримати ListenableFuture від компонента, і повісити на нього колбек:
User user = new User();
UserComponent userComponent = DaggerUserComponent.builder().userDataModule(new UserDataModule(user)).build();
ListenableFuture<UserData> listenableFutureUserData = userComponent.getUserData();
Futures.addCallback(listenableFutureUserData, new FutureCallback<UserData>() {
@Override
public void onSuccess(UserData result) {
}
@Override
public void onFailure(Throwable t) {
}
});
У метод onSuccess прийде потрібний нам об'єкт, а onFailure викличеться в разі будь-якого Exception, що виник у процесі створення об'єкта. І оскільки все це виконувалося асинхронно, то і результат прийде не в main потоці.
Produced
Помилки можна обробляти і до того, як вони прийдуть в onFailure метод вашого колбека. Для цього можна використовувати провайдер Produced.
Трохи розширимо попередній приклад. Припустимо, тепер нам необхідно спочатку завантажити з сервера json і розпарсити його, щоб отримати UserData.
@ProducerModule(includes = NetworkModule.class)
public class UserDataModule {
User mUser;
public UserDataModule(User user) {
mUser = user;
}
@Produces
String getUserDataJson(NetworkUtils networkUtils) {
return networkUtils.getUserDataJson(mUser);
}
@Produces
UserData getUserData(String userDataJson) {
return UserData.parseFromJson(userDataJson);
}
}
Коли ми попросимо компонент дати нам UserData, компонент викличе getUserData, побачить, що там потрібен String (тобто json), і викличе getUserDataJson, щоб отримати String. Якщо під час виклику getUserDataJson відбудеться IOException, то в ListenableFuture замість onSuccess буде викликано метод onFailure.
Даггер надає можливість обробити помилку прямо в методі getUserData. Перепишемо приклад із використанням Produced.
@ProducerModule(includes = NetworkModule.class)
public class UserDataModule {
User mUser;
public UserDataModule(User user) {
mUser = user;
}
@Produces
String getUserDataJson(NetworkUtils networkUtils) throws IOException {
return networkUtils.getUserDataJson(mUser);
}
@Produces
UserData getUserData(Produced<String> userDataJson) {
try {
return UserData.parseFromJson(userDataJson.get());
} catch (ExecutionException ex) {
return UserData.WRONG_USER;
}
}
}
Ми обернули String у Produced, і він тепер при виклику методу get поверне об'єкт, або викине ExecutionException. Тобто метод getUserDataJson буде викликаний тільки при виклику get. І якщо станеться помилка, ми прямо тут її обробляємо, і в ListenableFuture буде викликаний метод onSuccess, в який прийде об'єкт UserData.WRONG_USER.
ExecutionException - це просто обгортка, з якої ви зможете витягнути реальний Exception, який стався під час роботи.
Producer
Аналогічний Lazy. Якщо компонент повертає об'єкт, обгорнутий у Producer, то створення об'єкта почнеться тільки при виклику методу get.
У хелпі є чудовий приклад, який демонструє сенс Producer
@Produces
ListenableFuture<Heater> getHeater(
HeaterFlag flag,
@Electric Producer<Heater> electricHeater,
@Gas Producer<Heater> gasHeater) {
return flag.useElectricHeater() ? electricHeater.get() : gasHeater.get();
}
Методу getHeater приходить прапор і два Producer. І залежно від прапора, метод викликає метод get в одного з Producer. У підсумку створюється тільки один Heater-об'єкт, тому що процес створення об'єкта починається тільки під час виклику методу get.