Tyler
Tyler

Reputation: 19848

Dagger2 singleton not actually a singleton issue

I have an object that I'm trying to inject into 3 fragments as a singleton, using @Inject annotations.

Component:

@Subcomponent(modules = [(MyModule::class)])
interface MyComponent {

  fun inject(fragment: OneFragment)
  fun inject(fragment: TwoFragment)
  fun inject(fragment: ThreeFragment)

}

Module:

@Module
class MyModule(val view: MyView) {

  @Provides
  fun provideView(): MyView = view

  @Provides
  fun providePresenter(view: MyView,
                       myService: MyService): MyPresenter =
      MyPresenterImpl(view, myService)

}

MyPresenterImpl:

@Singleton
class MyPresenterImpl(override val view: MyView,
                      override val myService: MyService): MyPresenter {

  private val TAG = javaClass.simpleName

  // other code (irrelevant)
}

Fragments:

class OneFragment : Fragment() {

  @Inject
  lateinit var myPresenter: MyPresenter

  override fun onCreateView(inflater: LayoutInflater, viewGroup: ViewGroup?,
                            bundle: Bundle?): View? {
    activity?.mainApplication?.appComponent?.plus(
        MyModule(activity as MyActivity))?.inject(this)

    val view = inflater.inflate(R.layout.fragment_one, viewGroup, false)

    //other meaningless code

    return view
  }
}

However, the presenters that are injected into the fragments are not the same unique instance. What am I doing wrong?

Upvotes: 2

Views: 1922

Answers (1)

David Medenjak
David Medenjak

Reputation: 34532

It's not a @Singleton because you did not tell Dagger that it is. You put the scope on the presenter which would be fine if you were using constructor injection, but you aren't.

@Singleton // does nothing. you're not doing constructor injection.
class MyPresenterImpl(override val view: MyView,
                  override val myService: MyService): MyPresenter


// ...and in your module...


@Provides // where's the scope? it's unscoped.
fun providePresenter(view: MyView,
                  myService: MyService): MyPresenter =
    MyPresenterImpl(view, myService)

So you have two options. Either you use constructor injection, or you use a @Provides. You can't cherry pick and use a bit of each one.

1. Constructor injection

Just remove the @Provides annotated method in your module and slap an @Inject on your constructor.

@Singleton // NOW we're doing constructor injection -> singleton!
class MyPresenterImpl @Inject constructor(override val view: MyView,
                  override val myService: MyService): MyPresenter

There is no need for a module declaring it now. If you want to bind it to an interface you can use the @Binds annotation and continue using constructor injection...

@Binds // bind implementation to interface
abstract fun providePresenter(presenter : MyPresenterImpl) : MyPresenter

Since MyPresenterImpl is a @Singleton you don't have to declare MyPresenter as a singleton also. It will always use the singleton under the hood. (I believe it might even have a slight bigger performance penalty doing scopes on both.)

2. @Provides method

Remove the scope from your class (where it does nothing but confuse), and put it on the @Provides method instead. That's it.

@Singleton // singleton!
@Provides
fun providePresenter(view: MyView,
                  myService: MyService): MyPresenter =
    MyPresenterImpl(view, myService)

Scope on the class for constructor injection, or scope on the @Provides method if you're using that. Don't mix them up.

I'd personally recommend to go for constructor injetion whenever possible to do so as you don't have to manage/update the constructor calls yourself.

Upvotes: 4

Related Questions