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.

Table of Contents