how to provide different handling function to the

2019-08-16 21:31发布

问题:

This is related to using Dagger2 to provide a type with params got error "cannot be provided without an @Provides-annotated method" mean

Seems I find a work around (thanks @David Medenjak for explain the error), it works, but not sure if there is better way to do it, especially to provide the flexibility of proving different handling function to the injected InfiniteScrollListener instance. Current solution the handling function is hard coded in the PresentorModule. For that I don't think it is a really solution for the error of

@javax.inject.Named("func") kotlin.jvm.functions.Function0<kotlin.Unit> cannot be provided without an @Provides-annotated method.

Here is the current solution:

The subcomponent with ViewScope, now it provides LinearLayoutManager and InfiniteScrollListenerwhich are singlets to this scope

@ViewScope
@Subcomponent(modules = arrayOf(PresentorModule::class))
interface PresentorComponent {

    fun inject (fragment: ArticlesFragment)

    fun getPresenter(): Presentor

    fun getLinearLayoutManager(): LinearLayoutManager
    fun getInfiniteScrollListener(): InfiniteScrollListener

}

The PresentorModule provides linearLayoutManager (has dependency on the current context of this module), and InfiniteScrollListener which depends on the presentor and the linearLayoutManager, with a hard coded implementation of the handling function

@Module
class PresentorModule {

    var mContext: Context
    var mPresentor: Presentor

    constructor(context: Context) {
        mContext = context
        mPresentor = Presentor() 
    }
    @Provides
    @ViewScope
    internal fun context(): Context {// not sure if it has to implement to provide this mContext
        return mContext
    }

    @Provides
    @ViewScope

    fun presentor() : Presentor {
      return mPresentor      
    }

    @Provides
    @ViewScope

    fun linearLayoutManager() : LinearLayoutManager {
        return LinearLayoutManager(mContext)
    }

    @Provides
    @ViewScope
    fun infiniteScrollListener() : InfiniteScrollListener {
        return InfiniteScrollListener(
            {
                presentor().pullDataFromRemoteServer()
            },
            linearLayoutManager())
    }

 // really would like to have the flexibility of letting the consumer provides the 
 // handling function to the InfiniteScrollListener instance,  
 // but don’t know how to do it, so have to hard code the handling function
 // in the provider of InfiniteScrollListener listed above

 //    @Provides
 //    @ViewScope
 //    fun infiniteScrollListener(func: () -> Unit, layoutManager: LinearLayoutManager) :     InfiniteScrollListener {
 //        return InfiniteScrollListener(func, layoutManager)
 //    }
}

The definition of InfiniteScrollListener class, which takes a handling function and it is called in onScrolled()

class InfiniteScrollListener (val func: () -> Unit,
                          val layoutManager: LinearLayoutManager) : RecyclerView.OnScrollListener() {
    init{
    … …
}

override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
    super.onScrolled(recyclerView, dx, dy) 

    … …
    func()
    … …
}

The use of presentorComponent:

private lateinit var presentorComponent: PresentorComponent
var infiniteScrollListener: InfiniteScrollListener? = null

@Inject
lateinit var presentor: Presentor

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

   //use dagger to inject viewScope presentor
    if (MyApp.graph != null) {
        presentorComponent = MyApp.graph
            .addChildModle(PresentorModule(getActivity()))

        presentorComponent
            .inject(this)
    }
    return view
}


override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    articlesList!!.apply {
        setHasFixedSize(true)
        clearOnScrollListeners()

        infiniteScrollListener = presentorComponent.getInfiniteScrollListener()

// old non-injection way to instantiate the InfiniteScrollListener with
// a custom handling function passed in at this moment

//            infiniteScrollListener = InfiniteScrollListener(
//                    {
//                        presentor.pullDataFromRemoteServer()                   
//                    },
//                    linearLayout)

        addOnScrollListener(infiniteScrollListener)

I really would like to have the flexibility of provide different handling function when inject the class InfiniteScrollListener,

Like the old code was doing:

infiniteScrollListener = InfiniteScrollListener(
    {
        presentor.pullDataFromRemoteServer()           
    },
    linearLayout)

Instead of hardcoded in the

@Module
class PresentorModule {

… …
    @Provides
    @ViewScope
    fun infiniteScrollListener() : InfiniteScrollListener {
        return InfiniteScrollListener(
            {
                presentor()
            },
            linearLayoutManager())
    }

回答1:

If I understand you correctly you want the Class that injects your InfiniteScrollListener not to know the LinearLayoutManager but to be in charge of writing the code that is executed?

Alternatively you could inject your InfiniteScrollMethod and give it a setter method for the function.

class InfiniteScrollListener (val layoutManager: LinearLayoutManager) : ...

    var func: (() -> Unit)?=null

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy) 
        ...
        func()
        ...
     }
}

And then in your View:

infiniteOnScollListener.func = { code() }
addOnScrollListener(infiniteScrollListener)

Also on a side note: In your dagger Modules there is no need to call the @Provides methods directly. E.g.

    @Provides
    @ViewScope
    fun provideInfiniteScrollListener(presenter:Presenter,linearLayoutManager: LinearLayoutManager) : InfiniteScrollListener {
        return InfiniteScrollListener(
            {
                presenter
            },
            linearLayoutManager)
    }

Dagger will take care of providing the arguments of the provides method.

Also the function passed into InfiniteScollListener {presentor()} will not do anything apart from creating a new presenter when it is called (In case you are not aware and it is not just for the meantime of figuring out your solution).

Edit: I think I might have understood what you want to do: You want the Listener to call a function on your presenter or something else, without the view knowing about the code?

How about this:

 @Provides
@ViewScope
fun provideInfiniteScrollListener(@Named("scrollFunc1") func:()->Unit,presenter:Presenter,linearLayoutManager: LinearLayoutManager) : InfiniteScrollListener {
    return InfiniteScrollListener(func,linearLayoutManager)
}

@Provides
@Named("scrollFunc1")
fun scrollFunc(presenter:Presenter):()->Unit{
   return { presenter.onScrolled() }
}

I think what I am mostly confused about it what you mean with "provide different implementations of the method to the InfiniteScrollListener". Who is in fact the provider of the func?