Когда какой использовать?

Есть два способа сделать Subcomponent: связать его через ParentComponent или связать через ParentModule. Это показано в статье ниже



Сделать Subcomponent, связав через ParentComponent, тоже можно двумя способами.

1. Иметь функцию, которая обращается к самому ChildComponent

@Component
interface ParentComponent {
    val childComponent: ChildComponent
}

2. Иметь функцию, которая обращается к Builder ChildComponent.

@Component
interface ParentComponent {
    val childComponentBuilder: ChildComponent.Builder
}

Они очень похожи. Когда какой подход выбрать?

Сравнение

1. Стандартный код

  • Доступ к самому ChildComponent имеет гораздо меньший объем шаблонного кода, так как ему не нужно кодировать Builder. Даже если нужно создать экземпляр модуля, это можно сделать, как показано ниже, без необходимости создавать для него сборщик ✅
@Component
interface ParentComponent {
    val childComponent(module: ChildModule): ChildComponent
}
  • Получите доступ к конструктору дочерних компонентов, нужно сделать Builder на подкомпоненте, как показано ниже ❎
@Subcomponent(modules = [ChildModule::class])
interface ChildCompoenent {
    val requestHandler: RequestHandler

    @Subcomponent.Builder
    interface Builder {
        // The below module injection API is option if the  
        // ChildModule doesn't take parameter.
        // But it is required if the Child module need to have 
        // parameter
        fun childModule(module: ChildModule): Builder
        fun build(): RequestComponent
    }
}

2. Генерация кода

Помимо шаблонного кода, давайте посмотрим на сгенерированный код.

  • Доступ к самому ChildComponent генерирует меньшие коды. Сгенерированный код довольно прост ✅
// Generated codes
@Override
public ChildComponent getChildComponent() {
  return new ChildComponentImpl();
}
  • Получите доступ к самому Builder ChildComponent, сгенерируйте дополнительный код Builder. Это добавит еще один слой кода ❎
// Generated codes
@Override
public ChildComponent.Builder getChildComponentBuilder() {
  return new ChildComponentBuilder();
}
private final class ChildComponentBuilder implements ChildComponent.Builder {
  @Override
  public ChildComponent build() {
    return new ChildComponentImpl(new ChildModule());
  }
}

3. Родительский доступ к ChildComponent (обратная зависимость)

Как отмечалось в этом блоге, у нас может быть доступ Parent к ChildComponent и его объекту. (не только дочерний компонент, обращающийся к родительскому объекту)

  • Доступ к ChildComponent не позволяет привязанному объекту ParentComponent напрямую обращаться к ChildComponent. Код ниже не работает ❎
@Singleton
@Component
interface ParentComponent {
    val childComponent: ChildComponent
    val myObject: MyObject
}
// This will fail with `cannot be provided` error
@Singleton
class MyObject @Inject constructor(childComponent: ChildComponent)

Это должно быть обходным путем с дополнительным обеспечением

@Singleton
@Component
interface ParentComponent {
    val childComponentBuilder: ChildComponent.Builder
    val myObject: MyObject
}

@Module
class ParentModule {
    @Provides
    fun getChildComponent(
       childComponentBuilder: ChildComponent.Builder) 
       = childComponentBuilder.build()
}

@Singleton
class MyObject @Inject constructor(childCompoenent: ChildComponent)
  • Получите доступ к Builder ChildComponent, разрешите привязанному объекту ParentComponent доступ к ChildComponent ✅
@Singleton
@Component
interface ParentComponent {
    val childComponentBuilder: ChildComponent.Builder
    val myObject: MyObject
}
@Singleton
class MyObject 
  @Inject constructor(childComponentBuilder: ChildComponent.Builder)

4. Привязка экземпляра к подкомпоненту

  • Доступ к ChildComponent, мы не можем привязать экземпляр, который есть у ChildComponent ❎
  • Получите доступ к Builder ChildComponent, можно легко привязать экземпляр к ChildComponent ✅
@Subcomponent(modules = [ChildModule::class])
interface ChildComponent {
    @Subcomponent.Builder
    interface Builder {
        @BindsInstance
        fun data(data: Data): Builder
        fun build(): ChildComponent
    }
}

Как показано выше, мы можем привязать экземпляр data к ChildModule через Builder.

5. Одна и та же копия против дубликатов

Как видно из сравнения 2, оба доступа к ChildComponent и его Builder также генерируют новую копию.

// Direct ChildComponent Access
val parentComponent = DaggerParentComponent.create()
parentComponent.childComponent // copy A
parentComponent.childComponent // copy B (new one, not A)
// Direct ChildComponent Builder Access
val parentComponent = DaggerParentComponent.create()
parentComponent.childComponentBuilder // copy A
parentComponent.childComponentBuilder // copy B (new one, not A)

Таким образом, независимо от того, какой подход, всегда предоставляется новый объект. Следовательно, объект всегда дублируется.

Чтобы обеспечить создание того же объекта из ChildComponent, сохраните ChildComponent (либо тот, который получен непосредственно из ParentComponent, либо тот, который был создан из Builder ChildComponent), установите пользовательскую область для ChildComponent, а также установите пользовательскую область для объект, который вам нравится сохранять.

@ChildScope
@Subcomponent(modules = [ChildModule::class])
interface ChildComponent {
    val childObject: ChildObject
}
@ChildScope
class ChildObject @Inject constructor()
@Scope
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class ChildScope

Ознакомьтесь с ниже для более подробной информации о Scope



TL;DR

Если требуются обратные зависимости или дополнительный экземпляр необходимо привязать к дочернему компоненту, используйте вместо этого построитель дочерних компонентов. В противном случае используйте подход прямого доступа к дочерним компонентам вместо доступа к его Builder.

Спасибо за чтение. Вы можете ознакомиться с другими моими темами здесь.

Подпишитесь на меня в medium, Twitter, Facebook или Reddit, чтобы получать небольшие советы и узнавать новое о разработке мобильных приложений и т. д. . ~Элье~