1

I'm having a problem with injection for the SearchPresenter object. I've revised the dependency graph more than 50 times and cannot figure out what is the problem:

e: /home/alexandre/dev/projetos/gitlab/Cervejas/app/build/tmp/kapt3/stubs/debug/br/com/alexpfx/cervejas/di/AppComponent.java:8: error: [br.com.alexpfx.cervejas.di.beers.BeersComponent.inject(br.com.alexpfx.cervejas.beers.BeersFragment)] br.com.alexpfx.cervejas.search.SearchPresenter cannot be provided without an @Provides- or @Produces-annotated method.
public abstract interface AppComponent {
                ^
      br.com.alexpfx.cervejas.search.SearchPresenter is injected at
          br.com.alexpfx.cervejas.beers.BeersFragment.searchPresenter
      br.com.alexpfx.cervejas.beers.BeersFragment is injected at
          br.com.alexpfx.cervejas.di.beers.BeersComponent.inject(beersFragment)

I've these components and modules:

@Singleton
@Component(
        modules = [AppModule::class, ServiceModule::class]
)
interface AppComponent {
    fun inject (app: BeerApp)
    fun plus (): LoginSubComponent
    fun plus (searchModule: UntappedSearchModule): BeersComponent

}



@Module
class AppModule(val app: BeerApp) {

    @Singleton
    @Provides
    fun app(): BeerApp = app

}


@Module
class ServiceModule {
    companion object {
        private const val BASE_URL = "https://api.untappd.com/"
    }

    @Singleton
    @Provides
    fun provideUntappedConfig(): UntappdConfig {
        return UntappdConfig(BuildConfig.UNTAPPDCLIENTID, BuildConfig.UNTAPPDCLIENTSECRET)
    }


    @Singleton
    @Provides
    fun retrofit(): Retrofit {
        return Retrofit.Builder().baseUrl(BASE_URL).addCallAdapterFactory(RxJava2CallAdapterFactory
            .create()).addConverterFactory(GsonConverterFactory.create()).build()

    }

    @Singleton
    @Provides
    fun provideUntappedService(retrofit: Retrofit): UntappedService {
        return retrofit.create(UntappedService::class.java)
    }            
}


@Module
class UntappedSearchModule (private val searchView: SearchView) {

    @PerFragment
    @Provides
    fun provideSearchView () = searchView


    @PerFragment
    @Provides
    fun provideSearchInteractor (searchInteractorImpl: SearchInteractorImpl): SearchInteractor {
        return searchInteractorImpl
    }



    @PerFragment
    @Provides
    fun provideSearchPresenter (searchView: SearchView, searchInteractor: SearchInteractor) : SearchPresenter{
        return SearchPresenterImpl(searchView, searchInteractor)
    }

}

SearchPresenter depends on SearchView that is provided in UntappedSearchModule:

@PerFragment
@Provides
fun provideSearchView () = searchView

and SearchInteractor that is provided:

@PerFragment
    @Provides
    fun provideSearchInteractor (searchInteractorImpl: SearchInteractorImpl): SearchInteractor {
        return searchInteractorImpl
    }

SearchInteractorImpl had annotated in constructor:

@PerFragment
class SearchInteractorImpl @Inject constructor(private val beerService: UntappedService,
                                               private val config: UntappdConfig) : SearchInteractor {

    override fun searchBeers(query: String): io.reactivex.Observable<List<SearchedBeerVO>> {
        return beerService.searchBeers(config.clientId, config.clientSecret, query).map({ t -> null })
    }
}

It depends on UntappedService and UntappdConfig that comes from ServiceModule

@Singleton
    @Provides
    fun provideUntappedConfig(): UntappdConfig {
        return UntappdConfig(BuildConfig.UNTAPPDCLIENTID, BuildConfig.UNTAPPDCLIENTSECRET)
    }



@Singleton
@Provides
fun provideUntappedService(retrofit: Retrofit): UntappedService {
    return retrofit.create(UntappedService::class.java)
}

Retrofit is in this module, too. BeersComponent and BeersFragment are:

@PerFragment
@Subcomponent(modules = [UntappedSearchModule::class])
interface BeersComponent {
    fun inject(beersFragment: BeersFragment)
}

class BeersFragment : BaseFragment(), SearchView, AnkoLogger {

    override fun injectDependencies(appComponent: AppComponent) {
        appComponent.plus(UntappedSearchModule(this)).inject(this)
    }


    @Inject
    lateinit var searchPresenter: SearchPresenter

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        return inflater.inflate(R.layout.fragment_beers, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        setupRecycler()

        searchPresenter.search("stella")
    }

    private fun setupRecycler() {

        recyclerSearchResult.layoutManager = LinearLayoutManager(context)

    }


    override fun displayResults(beers: List<SearchedBeerVO>) {
        info("beers: "+beers.size)

        recyclerSearchResult.adapter = BeersAdapter(beers)
    }


}

BaseFragment:

abstract class BaseFragment : Fragment()  {

    private val appComponent by lazy {
        (activity!!.application as BeerApp).component
    }

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        injectDependencies(appComponent)
    }

    abstract fun injectDependencies (appComponent: AppComponent)

}

--------- EDIT

I change the UntappedSearchModule to:

@Module
class UntappedSearchModule (private val searchView: SearchView) {

    @PerFragment
    @Provides
    fun provideSearchView () = searchView

    @PerFragment
    @Provides
    fun provideSearchPresenterImpl (searchView: SearchView, searchInteractor: SearchInteractor) : SearchPresenterImpl{
        return SearchPresenterImpl(searchView, searchInteractor)
    }


    @Module(includes = arrayOf(UntappedSearchModule::class))
    abstract class SearchPresenterModule {

        @Binds
        abstract fun provideSearchPresenter (searchPresenterImpl: SearchPresenterImpl):SearchPresenter
    }


    @PerFragment
    @Provides
    fun provideSearchInteractor (searchInteractorImpl: SearchInteractorImpl): SearchInteractor {
        return searchInteractorImpl
    }

}


@PerFragment
@Subcomponent(modules = [UntappedSearchModule::class, UntappedSearchModule.SearchPresenterModule::class])
interface BeersComponent {
    fun inject(beersFragment: BeersFragment)
}

But again, same problem:

e: /home/alexandre/dev/projetos/gitlab/Cervejas/app/build/tmp/kapt3/stubs/debug/br/com/alexpfx/cervejas/di/AppComponent.java:8: error: [br.com.alexpfx.cervejas.di.beers.BeersComponent.inject(br.com.alexpfx.cervejas.beers.BeersFragment)] br.com.alexpfx.cervejas.search.SearchPresenterImpl cannot be provided without an @Inject constructor or from an @Provides-annotated method.
public abstract interface AppComponent {
                ^
      br.com.alexpfx.cervejas.search.SearchPresenterImpl is injected at
          br.com.alexpfx.cervejas.di.UntappedSearchModule.SearchPresenterModule.provideSearchPresenter(searchPresenterImpl)
      br.com.alexpfx.cervejas.search.SearchPresenter is injected at
          br.com.alexpfx.cervejas.beers.BeersFragment.searchPresenter
      br.com.alexpfx.cervejas.beers.BeersFragment is injected at
          br.com.alexpfx.cervejas.di.beers.BeersComponent.inject(beersFragment)

--- I simplified the SearchPresenter to avoid using @Binds and removed the dependencies:

@PerFragment
class SearchPresenterImpl @Inject constructor() :
    SearchPresenter {


    override fun search(query: String) {
//        interactor.searchBeers(query).subscribeOn(Schedulers.computation()).observeOn(AndroidSchedulers.mainThread())
//            .subscribe({ beers ->
//                beers.forEach({ t: SearchedBeerVO? -> println(t) })
//            })
    }
}


interface SearchPresenter  {
    fun search (query: String)
}



@PerFragment
    @Provides
    fun provideSearchPresenterImpl (searchPresenterImpl: SearchPresenterImpl) : SearchPresenter{
        return searchPresenterImpl

    }
  • Still same problem....

but if I run it with ./gradlew build it shows different errors:

> Configure project :app 
Could not find google-services.json while looking in [src/nullnull/debug, src/debug/nullnull, src/nullnull, src/debug, src/nullnullDebug]
registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)
Could not find google-services.json while looking in [src/nullnull/release, src/release/nullnull, src/nullnull, src/release, src/nullnullRelease]
registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)

> Task :app:processDebugGoogleServices 
Parsing json file: /home/alexandre/dev/projetos/gitlab/Cervejas/app/google-services.json

e: /home/alexandre/dev/projetos/gitlab/Cervejas/app/build/tmp/kapt3/stubs/debug/br/com/alexpfx/cervejas/search/SearchInteractorImpl.java:15: error: cannot find symbol
    public io.reactivex.Observable<java.util.List<br.com.alexpfx.cervejas.common.SearchedBeerVO>> searchBeers(@org.jetbrains.annotations.NotNull()
                       ^
  symbol:   class Observable
  location: package io.reactivex
e: /home/alexandre/dev/projetos/gitlab/Cervejas/app/build/tmp/kapt3/stubs/debug/br/com/alexpfx/cervejas/network/UntappedService.java:9: error: cannot find symbol
    public abstract io.reactivex.Observable<br.com.alexpfx.cervejas.network.search.SearchResponse> searchBeers(@org.jetbrains.annotations.NotNull()
                                ^
  symbol:   class Observable
  location: package io.reactivex
e: /home/alexandre/dev/projetos/gitlab/Cervejas/app/build/tmp/kapt3/stubs/debug/br/com/alexpfx/cervejas/search/SearchInteractor.java:9: error: cannot find symbol
    public abstract io.reactivex.Observable<java.util.List<br.com.alexpfx.cervejas.common.SearchedBeerVO>> searchBeers(@org.jetbrains.annotations.NotNull()
                                ^
  symbol:   class Observable
  location: package io.reactivex
e: /home/alexandre/dev/projetos/gitlab/Cervejas/app/build/tmp/kapt3/stubs/debug/br/com/alexpfx/cervejas/login/LoginActivity.java:8: error: cannot find symbol
    private final com.google.firebase.auth.FirebaseAuth firebaseAuth = null;
                                          ^
  symbol:   class FirebaseAuth
  location: package com.google.firebase.auth
e: /home/alexandre/dev/projetos/gitlab/Cervejas/app/build/tmp/kapt3/stubs/debug/br/com/alexpfx/cervejas/login/LoginActivity.java:17: error: cannot find symbol
    public final com.google.firebase.auth.FirebaseAuth getFirebaseAuth() {
                                         ^
  symbol:   class FirebaseAuth
  location: package com.google.firebase.auth

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:kaptDebugKotlin'.
> Compilation error. See log for more details

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1s
15 actionable tasks: 2 executed, 13 up-to-date
alexandre@alexandre-aero ~/dev/projetos/gitlab/Cervejas $ 

In Android Studio it doesn't shows these problems. If I removed SearchPresenter dependency on Fragment it works fine.

--- Found the problem.

for some bizarre reason the wrongly implemented scope annotation with that @Qualifier:

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
@Scope
annotation class PerFragment
alexpfx
  • 6,412
  • 12
  • 52
  • 88
  • Please have a look at [How to fix cannot be provided](https://stackoverflow.com/q/44912080/1837367). You're not providing `SearchPresenter` from anywhere where Dagger can find it. – David Medenjak Apr 22 '18 at 15:52
  • I was reading this thread just before post this question, but now I realized I missed the last paragraph, I forgot that my SearchPresenter was a abstract class and not an Interface, so I changed my implementation but still having the same problem. (I've edit the question) – alexpfx Apr 22 '18 at 16:38
  • It looks alright now. I'd try to remove one `UntappedSearchModule` since you add it twice (included in module, as well as on the component) – David Medenjak Apr 22 '18 at 17:29
  • 2
    Argh. I had implemented wrong the scope annotation... – alexpfx Apr 22 '18 at 19:32

0 Answers0