Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Bind/Provide Activity or Fragment with Hilt?

I'm trying to implement Hilt on an Android App, while It's pretty easy to implement and removes a lot of the boilerplate code when comparing with Dagger, there are some things I miss, Like building my own components and scoping them myself so i'll have my own hirerchy.

To the point: Example: let's say I have a simple App with a RecyclerView, Adapter, Acitivity, and a Callback nested in my Adapter that I pass into my Adapter constructor in order to detect clicks or whatever, and I'm letting my activity implement that Callback, and of course I want to inject the adapter.

class @Inject constructor (callBack: Callback): RecyclerView.Adapter...

When I let Hilt know that I want to inject my adapter I need to let Hilt know how to provide all the Adapter dependencies - the Callback.

In Dagger I was able to achieve this by just binding the Activity to the Callback in one of my modules:

@Binds fun bindCallback(activity: MyActivity): Adapter.Callback

Dagger knew how to bind the Activity(or any Activity/Fragment) and then it was linked to that Callback, but with Hilt it does'nt work.

How can I achieve this? How can I provide or Bind Activity or Fragment with Hilt?

like image 891
Danny Avatar asked Aug 01 '20 16:08

Danny


2 Answers

The solution is quite simple.

So a few days ago I came back to look at my question only to see that there was still no new solution, so I tried Bartek solution and wasn't able to make it work, and even if it did work, the clean Hilt code was becoming too messy, so I did a little investigation and played a little and discovered that the solution is actually stupidly easy.

It goes like this:

App:

@HiltAndroidApp
class MyApp: Application()

Activity: (implements callback)

@AndroidEntryPoint
class MainActivity : AppCompatActivity(), SomeClass.Callback {

    @Inject
    lateinit var someClass: SomeClass

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)  
    }

    override fun onWhatEver() {
        // implement
    }
}

SomeClass: (with inner callback)

class SomeClass @Inject constructor(
    private val callback: Callback
) {

    fun activateCallback(){
        callback.onWhatEver()
    }

    interface Callback{
        fun onWhatEver()
    }
}

SomeModule: (providing/binding the activity to the callback)

@Module
@InstallIn(ActivityComponent::class)
object SomeModule{

    @Provides
    fun provideCallback(activity: Activity) =
        activity as SomeClass.Callback
    
}

And that's all we need. We cannot bind the activity to the callback with @Bind because it needs to be explicitly provided and cast to the callback so that the app can build.

The module is installed in ActivityComponent and is aware of a generic 'activity', if we cast it to the callback, Hilt is content and the activity is bound to the callback, and Hilt will know how to provide the callback as long as its in the specific activity scope.

Multiple Activities/Fragments

App:

@HiltAndroidApp
class MyApp: Application()

BooksActivity:

@AndroidEntryPoint
class BooksActivity : AppCompatActivity(), BooksAdapter.Callback{

        @Inject
        lateinit var adapter: BooksAdapter

        ...

        override fun onItemClicked(book: Book) {...}
    }
}

AuthorsActivity:

@AndroidEntryPoint
class AuthorsActivity : AppCompatActivity(), AuthorsAdapter.Callback{

    @Inject
    lateinit var adapter: AuthorsAdapter

    ...

    override fun onItemClicked(author: Author) {...}
}

BooksAdapter

class BooksAdapter @Inject constructor (
    val callback: Callback
) ... {

    ...

    interface Callback{
        fun onItemClicked(book: Book)
    }
}

AuthorsAdapter

class AuthorsAdapter @Inject constructor (
    val callback: Callback
) ... {

    ...

    interface Callback{
        fun onItemClicked(auhtor: Auhtor)
    }
}

AuhtorsModule

@Module
@InstallIn(ActivityComponent::class)
object AuthorsModule {
    @Provides
    fun provideCallback(activity: Activity) =
        activity as AuthorsAdapter.Callback
}

BooksModule

@Module
@InstallIn(ActivityComponent::class)
object BooksModule {
    @Provides
    fun provideCallback(activity: Activity) =
        activity as BooksAdapter.Callback
}

The Modules can be joined to one module with no problem, just change the names of the functions.

This is offcourse applicable for more activities and/or multiple fragments.. for all logical cases.

like image 52
Danny Avatar answered Nov 06 '22 16:11

Danny


Hilt can provide an instance of the generic Activity (and Fragment for that matter) as a dependency within the ActivityComponent (and FragmentComponent respectively). It's just not able to provide an instance of your specific MyActivity.

You can still create your own Component in Hilt. You will just have to manage the component instance on your own. Add the MyActivity as the seed data for the component builder and you should be able to @Bind your Callback with no problem.

like image 39
Bartek Lipinski Avatar answered Nov 06 '22 18:11

Bartek Lipinski