dave mankoff
dave mankoff

Reputation: 17779

Implementing a Singleton Builder in Dagger

I have a class that I would like to inject via Dagger. It requires a builder, as some of its parameters are only known at runtime. I would also like this class to be a singleton.

That is to say, several parts of our codebase require the instance of this class, but only one part of the codebase actually knows how to set it up.

The setup happens early on in the application - before anyone would actually try to use the singleton - but after Dagger has already initialized its root component.

What is the right way for other parts of the code to get access to the object?

class S {
  private S(Foobar foobar) {
    // ...
  }

  @Singleton
  public static class Builder {
    Foobar foobar;
    @Inject
    public Builder() {}

    public Builder setFoobar(Foobar foobar) {
      this.foobar = foobar;
    }

    public S build() {
      return new S(foobar);
    }
  }
}

class Main {
  private Foobar foobar = new Foobar();
  private final S s;

  @Inject
  public Main(S.Builder sBuilder) {
    s = sBuilder.setFoobar(foobar).build();
  }
}

class Other {
  private final S s;

  @Inject
  public Other(/* What do I put here to get access to S? */) {
  }

Edit: For clarity, let me state that Foobar in this example is created early in the application, but after Dagger and the high level structure of the app has been instantiated. This specific program is an Android app; the Foobar in this case a View that is inflated, and S a controller for that View.

There will only ever be one Foobar and one S. Various parts of our code want to be able to communicate with S, but only one part of our code can actually create it.

Upvotes: 1

Views: 535

Answers (2)

dave mankoff
dave mankoff

Reputation: 17779

Building off of a hint I gleamed from @MyDogTom's answer, introducing our first Subcomponent was the solution.

Wait to construct the S until after Foobar has been constructed. Then initialize a subcomponent, passing S to its Builder, and use the subcomponent to construct Other.

@Subcomponent
public interface MySubComponent {
    @Subcomponent.Builder
    interface Builder {
        @BindsInstance Builder s(S s);
        StatusBarComponent build();
    }

    /**
     * Scope annotation for singleton items within the MySubComponent.
     */
    @Documented
    @Retention(RUNTIME)
    @Scope
    @interface MySubComponentScope {}

    @MySubComponentScope
    Ohter getOther();

}

// Attach MySubComponent to Dagger where appropriate, per their documentation.
// Then, in Main, do something like the following:


class Main {
  private final MySubComponent.Builder mySubComponentBuilder;
  private final S.Builder sBuilder;
  private Foobar foobar;

  @Inject
  public Main(MySubComponent.Builder mySubComponentBuilder, S.Builder sBuilder) {
    this.mySubComponentBuilder = mySubComponentBuilder;
    this.sBuilder = sBuilder;
  }

  // At some point, foobar gets created. Then we call the following method.
  private void afterInit();
    S s = sBuilder.setFoobar(foobar).build();

    Other other = mySubComponentBuilder.s(s).build().getOther();
  }
}

This is a slightly contrived example, obviously, but demonstrates the solution to the problem.

Upvotes: 0

MyDogTom
MyDogTom

Reputation: 4606

You can use BindsInstance for that https://dagger.dev/api/2.10/dagger/BindsInstance.html

Your Application creates the RootComponent. During creation it should provide the instance of the Foobar. Let's take a look at the pseudocode

class S {
    private S(Foobar foobar) {
        // ...
    }

    public static class Builder {
        Foobar foobar;

        public Builder() {}

        public Builder setFoobar(Foobar foobar) {
            this.foobar = foobar;
        }

        public S build() {
            return new S(foobar);
        }
    }
}

@Module
public static class RootModule {

    @Provides
    @Singleton
    public static S provideS(Foobar foobar) {
        return new S.Builder().setFoobar(foobar).build();
    }
}

@Singleton
@Component(module = {RootModule.class})
public interface RootComponent {
    @Component.Factory
    interface Factory {
        public RootComponent create(@BindsInstance Foobar foobar)
    }
}

public class Application {
    private RootComponent createComponent() {
        return DaggerRootComponent.factory().create(new Foobar())
    }
}

Update

This specific program is an Android app; the Foobar in this case a View that is inflated, and S a controller for that View.

I strongly discourage you from keeping a reference to a View. This might produce subtle bugs and memory leaks. Instead I suggest you to introduce some sort of event bus which will be a singleton in your Dagger graph and shared between S (view controller) and consumers. This even bus will be used for communication between consumers and S (view controller).

Upvotes: 1

Related Questions