Explore in Dagger - Component

همونطور که قبلا هم گفته شده:

کامپوننت ها عامل اصلی injection هستند.

پس قطعا پیچیدگی‌ها و نکات بیشتری برای بیان دارند.

در این مطلب ابتدا نگاهی به ساختار کلی آن خواهیم داشت و سپس موارد مختلف را پیاده‌سازی خواهیم کرد.

ساختار Component

@Component(modules = { ... })
public abstract class AppComponent {
    abstract public void inject(MainActivity mainActivity);
}
public final class DaggerAppComponent extends AppComponent {
  private DaggerAppComponent() {}

  public static Builder builder() {
    return new Builder();
  }

  @Override
  public void inject(MainActivity mainActivity) {
      ...
  }

  public static final class Builder {
    private Builder() {}

    public AppComponent build() {
      return new DaggerAppComponent();
    }
  }
}
public class MainActivity extends AppCompatActivity {
    AppComponent mAppComponent;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mAppComponent = DaggerAppComponent.builder()
                .build();
        mAppComponent.inject(this);
    }
}

از آنجایی که کامپوننت ها همواره abstract-class هستند (یا هم interface) ، در فرآیند code generation کلاسی با پیشوند Dagger آنها را implement میکند. و در اینجا نیز کلاس DaggerAppComponent که AppComponent را implement کرده که متدهای override شده از داخل کلاسی که نیازمند injection هست فراخوانی میشود. (inject(MainActivity mainActivity))

نکته قابل توجه بعدی وجود یک constructor با دسترسی private است که به صورت مستقیم برای کلاسهایی که به injection نیاز دارند، قابل دسترسی نیست. و برای instantiate کردن این کلاس باید از builder آن استفاده کرد.

Component Builder

پترن Builder ساختار (construction) اشیاء پیچیده را از نمایش (representation) آن جدا کرده و شی پیچیده را به صورت جز به جز میسازد.

اما این Component چه مواردی را نیاز دارد؟ (چه موراردی را در constructor خود میگیرد؟)

Non-static Module

ماژول‌های non-static که نیاز به instantiate دارند. کامیت

public final class DaggerAppComponent extends AppComponent {
  private final AppModule appModule;

  private DaggerAppComponent(AppModule appModuleParam) {
    this.appModule = appModuleParam;
  }

  public static Builder builder() {
    return new Builder();
  }

  public static AppComponent create() {
    return new Builder().build();
  }

  private CoffeeMaker getCoffeeMaker() {
    return AppModule_ProvideCoffeeMakerFactory.provideCoffeeMaker(
        appModule, new ElectricHeater(), new Pump());
  }

  @Override
  public void inject(MainActivity mainActivity) {
    injectMainActivity(mainActivity);
  }

  private MainActivity injectMainActivity(MainActivity instance) {
    MainActivity_MembersInjector.injectMCoffeeMaker(instance, getCoffeeMaker());
    return instance;
  }

  public static final class Builder {
    private AppModule appModule;

    private Builder() {}

    public Builder appModule(AppModule appModule) {
      this.appModule = Preconditions.checkNotNull(appModule);
      return this;
    }

    public AppComponent build() {
      if (appModule == null) {
        this.appModule = new AppModule();
      }
      return new DaggerAppComponent(appModule);
    }
  }
}

همانطور که از کد مشخص است، instance ی از ماژول non-abstract را نگهداری میکند. و این فیلد را در builder خود مقدار دهی میکند. لینک

اما در صورتیکه این متد builder فراخوانی و مقداردهی نشود در متد build() براحتی مقداردهی میشوند. لینک

Non-instantiative arguments in factories

Factory هایی که نیاز به آرگومان هایی دارند که توسط دیگر factory تا تامین نشده‌اند و همانطور که گفته شد، از طریق کامپوننت تامین میشوند. مانند Context یا Application

برای پیاده سازی این مورد آرگومانی از جنس Context به CoffeeMaker اضافه میکنیم. کامیت

public class CoffeeMaker {
    private final Heater heater;
    private final Pump pump;
    private final Context context;

    public CoffeeMaker(Heater heater, Pump pump, Context context) {
        this.heater = heater;
        this.pump = pump;
        this.context = context;
    }

    public void brew() { ... }
}

برای تامین اینگونه پارامترها دوراه وجود دارد:

  • اولین مورد که در نسخه‌های قبلی تنها راه اینکار بوده ولی همچنان مرسوم است، استفاده از ماژول‌های non-abstract که این آرگومان را در constructor خود دریافت کنند. کامیت
@Module(includes = {SecondModule.class})
public class AppModule {

    Context context;

    public AppModule(Context context) {
        this.context = context;
    }

    @Provides
    public CoffeeMaker provideCoffeeMaker(Heater heater, Pump pump) {
        return new CoffeeMaker(heater, pump, context);
    }
}

بدین صورت وظیفه تامین Context را بر عهده کامپوننت - که ماژول را instantiate میکند،- گذاشته‌ایم. اما باید در نظر داشته باشیم که کامپوننت‌ها تنها ماژول‌هایی که آرگومانی روی constructor خود نداشته باشند را میتوانند instantiate کنند. پس این وظیفه به خود کاربر محول میشود و builder کامپوننت به صورت زیر تغییر میکند لینک

public static final class Builder {
    private AppModule appModule;

    private Builder() {}

    public Builder appModule(AppModule appModule) {
      this.appModule = Preconditions.checkNotNull(appModule);
      return this;
    }

    public AppComponent build() {
      Preconditions.checkBuilderRequirement(appModule, AppModule.class);
      return new DaggerAppComponent(appModule);
    }
  }

همانطور که میبنید دیگر متد build ماژول را instantiate نکرده و تنها نال بودن آن را چک میکند. پس در کلاسی که میخواهیم injection را انجام دهیم باید متد appModule(AppModule appModule) را فراخوانی کنیم.

mAppComponent = DaggerAppComponent.builder()
      .appModule(new AppModule(this))
      .build();

mAppComponent.inject(this);
  • اما راه دوم برای تامین Context که در اینجا به عنوان یک dependency غیرقابل instantiate شدن استفاده میشود، استفاده از @Component.Builder میباشد.

@Component.Builder

این annotation که بعد ها به Dagger اضافه شد. امکان تعریف یک interface توسط کاربر برای تامین برخی نیازمندی‌های کامپوننت معرفی شد.

بدین صورت که در هر کامپوننت اگر یک interface با @Component.Builder علامتگذاری شود، builder اصلی کامپوننت آن را implement میکند.

@Component(modules = {AppModule.class})
public abstract class AppComponent {

    @Component.Builder
    public interface AppComponentBuilder {
        @BindsInstance
        public AppComponentBuilder builderContext(Context context);

        public AppComponent buildAppComponent();
    }

    abstract public void inject(MainActivity mainActivity);

}

نکته قابل توجه این است که ساختار پترن builder به طور کامل در این interface پیاده‌سازی شده و همه متدها بغیر از متد build آن که خود کامپوننت را return میکند باقی متدها که dependency هارا تامین میکنند باید دارای @BindsInstance باشند و همچنین نوع بازگشتی نیز خود interface باشد.

public final class DaggerAppComponent extends AppComponent {
  private final Context builderContext;

  private final AppModule appModule;    // non-abstract module

  private DaggerAppComponent(AppModule appModuleParam, Context builderContextParam) {
    this.builderContext = builderContextParam;
    this.appModule = appModuleParam;
  }

  public static AppComponent.AppComponentBuilder builder() {
    return new Builder();
  }

  @Override
  public void inject(MainActivity mainActivity) { ...  }

  private static final class Builder implements AppComponent.AppComponentBuilder {
    private Context builderContext;

    @Override
    public Builder builderContext(Context context) {
      this.builderContext = Preconditions.checkNotNull(context);
      return this;
    }

    @Override
    public AppComponent buildAppComponent() {
      Preconditions.checkBuilderRequirement(builderContext, Context.class);
      return new DaggerAppComponent(new AppModule(), builderContext);
    }
  }
}

در اینجا نیز شاهد این هستید متد builder() اصلی کامپوننت بجای بازگرداندن Builder نوع AppComponent.AppComponentBuilder برمیگرداند و کاربر ملزم به فراخوانی متدهای موردنیاز میباشد. لینک

لازم به ذکر است که در صورت استفاده از @Component.Builder نیز اگر ماژول‌های non-abstract داشته باشیم که در constructor آرگومانی نداشته باشند، به صورت خودکار توسط کامپوننت instantiate میشوند. لینک


تمامی فایل های این پست روی برنچ p3-component در دسترس است.

rss facebook twitter github youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora telegram heart