good day, i'm trying to create custom component in hilt so i followed this tutorial medium and this tutorial as well documentation.
but when i run the application i get this error indicating that it failed to create the viewholdermanager
kotlin.UninitializedPropertyAccessException: lateinit property viewHolderManager has not been initialized.
so i believe that i'm missing something as per my understanding "We use the provider Dagger generates for us to create instances of ViewHolderComponent( custom component)"
really appreciate your help. thanks in advance
here is what i done :-
#Scope
#MustBeDocumented
#Retention(value = AnnotationRetention.RUNTIME)
annotation class ViewHolderScope
#ViewHolderScope
#DefineComponent(parent = ApplicationComponent::class)
interface ViewHolderComponent{
#DefineComponent.Builder
interface Builder {
fun build(): ViewHolderComponent
fun viewHolder(#BindsInstance viewHolder: RecyclerView.ViewHolder?): ViewHolderComponent.Builder?
}
}
#Singleton
class ViewHolderManager #Inject constructor(
private val viewHolderComponentProvider: Provider<ViewHolderComponent.Builder>
) {
var viewHolderComponent: ViewHolderComponent? = null
private set
fun setViewHolder(viewHolder: RecyclerView.ViewHolder) {
viewHolderComponent = viewHolderComponentProvider.get().viewHolder(viewHolder)?.build()
}
}
#Module
#InstallIn(ViewHolderComponent::class)
class ViewHolderModule{
#Provides
#ViewHolderScope
fun provideLifecycleRegistry(viewHolder: RecyclerView.ViewHolder): LifecycleRegistry = LifecycleRegistry(viewHolder)
#Provides
#ViewHolderScope
fun provideArrayAdapter(viewHolder: RecyclerView.ViewHolder): ArrayAdapter<DataItem> = ArrayAdapter((viewHolder as BaseItemViewHolder<*, *>).parent.context, R.layout.item_dropdown_menu_popup,
ArrayList())
}
class SavedCarItemViewHolder(): RecyclerView.ViewHolder(...){
#EntryPoint
#InstallIn(ViewHolderComponent::class)
interface ViewHolderEntryPoint {
fun getLifecycleRegistry(): LifecycleRegistry
fun getDataItemArrayAdapter(): ArrayAdapter<DataItem>
}
protected fun onCreate() {
injectDependencies()
}
lateinit var lifecycleRegistry: LifecycleRegistry
#Inject lateinit var viewHolderManager:ViewHolderManager
fun injectDependencies() {
val hiltEntryPoint = EntryPoints.get(viewHolderManager.viewHolderComponent, ViewHolderEntryPoint::class.java)
lifecycleRegistry = hiltEntryPoint.getLifecycleRegistry()
}
}
I think you have to change your class ViewHolderModule to object ViewHolderModule otherwise #Provides is not executed. Please share your experience with this result and when it does not work, I will try to dig deeper in.
You should use #Inject annotation to constructor and inject ViewHolderManager
Related
I'm quite new with Hilt injection. I started to migrate my whole project to DI.
It works almost everywhere, but I'm facing an issue when it comes to the leanback presenters. I don't know if it is related to the leanback stuff or juste Hilt
class LiveShowCardPresenter constructor(context: Context, listener: ShowCardViewListener, val hasVariableWidth: Boolean = false) : ShowCardPresenter(context, listener) {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
val viewholder = ViewHolder(LiveShowCardView(context, hasVariableWidth))
viewholder.prepareViewHolderForeground(context, settings.isATV)
return viewholder
}
...
}
abstract class ShowCardPresenter constructor(val context: Context, var listener: ShowCardViewListener?) : Presenter() {
#Inject lateinit var detailsRepository: DetailsRepository
#Inject lateinit var settings: BackendSettings
... }
#Singleton
class BackendSettings #Inject constructor(#ApplicationContext val context: Context) {
val isATV = true // TODO
The following error occurs
kotlin.UninitializedPropertyAccessException: lateinit property settings has not been initialized
at ch.netplus.tv.ui.presenters.ShowCardPresenter.getSettings(ShowCardPresenter.kt:43)
at ch.netplus.tv.ui.presenters.LiveShowCardPresenter.onCreateViewHolder(LiveShowCardPresenter.kt:23)
It means it crashes when the settings.isATV is called because the 'settings' var is not initialized at that time. What should I do to have the injection done on time ?
Thanks !
How do you inject dependencies into the LiveShowCardPresenter?
Since your abstract class(ShowCardPresenter) performs field injection, you somehow need to inject these fields when you create LiveShowCardPresenter. To perform those injections, you need to inject LiveShowCardPresenter as well. So, here is how it will look:
class LiveShowCardPresenter #Inject constructor(context: Context) : ShowCardPresenter(context) {
var hasVariableWidth: Boolean = false
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
val viewholder = ViewHolder(LiveShowCardView(context, hasVariableWidth))
viewholder.prepareViewHolderForeground(context, settings.isATV)
return viewholder
}
...
}
abstract class ShowCardPresenter constructor(val context: Context) : Presenter() {
var listener: ShowCardViewListener? = null
#Inject lateinit var detailsRepository: DetailsRepository
#Inject lateinit var settings: BackendSettings
... }
YourFragment.kt
#AndroidEntryPoint
class YourFragment: BrowseFragment() {
#Inject
lateinit var liveShowCardPresenterProvider: Provider<LiveShowCardPresenter>
...
private void setupUIElements() {
...
//new header
setHeaderPresenterSelector(object : PresenterSelector() {
override fun getPresenter(o: Any): Presenter {
// Everytime when [liveShowCardPresenterProvider.get()] is called - new instance will be created
val presenter = liveShowCardPresenterProvider.get().apply {
// You can set your parameters here
// hasVariableWidth = true
// listener = yourCustomListener
}
return presenter;
}
});
}
...
If you need a single instance of the LiveShowCardPresenter in your fragment, you can perform a field injection on it without the Provider.
Alternativerly, you can inject all of your dependencies in the Fragment and pass them to the LiveShowCardPresenter constructor.
You need to set the inject method in BackendSettings
Like:
class BackendSettings #Inject constructor() {
}
Ok, I walk through your codes step by step:
Your LiveShowCardPresenter class should be changed as below:
class LiveShowCardPresenter #Inject constructor(
#ApplicationContext context: Context,
listener: ShowCardViewListener
) : ShowCardPresenter(context, listener) {
var hasVariableWidth = false
//your codes ...
}
As can be seen, #Inject is added before the constructor and also #ApplicationContext is added before context to provide context through the Hilt. also, hasVariableWidth is set outside of the constructor, if you don't like you can put it inside the constructor and provide it through the module and #Provide annotation. Now we should provide showCardViewListener. as I don't have access to your codes I provide it in a simple way.
#Module
#InstallIn(SingletonComponent::class)
abstract class ShowCardListenerModule {
#Binds
#Singleton
abstract fun bindShowCardViewListener(showCardViewListenerImpl: ShowCardViewListenerImpl) : ShowCardViewListener
}
ShowCardPresenter class has no changes. Finally, your BackendSettings class is changed like below:
#Singleton
class BackendSettings #Inject constructor() {
val isATV = true
//your codes ...
}
#Inject is added and #Singleton is removed because doesn't need to it. I ran the above codes and it works without any problem.
I have created a custom component AuthUserComponent using Hilt and need to provide multiple implementation to DataRepository interface.
class Sample #Inject constructor(
#DemoMode private val demoRepository: DataRepository,
#ProductionMode private val productionRepository: DataRepository
) {}
I have created the below #Provides implementations of the interface:
Module
#InstallIn(AuthUserComponent::class)
object DIModule {
#AuthUserScope
#DemoMode
#Provides
fun provideDataRepositoryImplDemo(): DataRepository =
DataRepositoryImplDemo()
#AuthUserScope
#Provides
#ProductionMode
fun provideDataRepositoryImpl(): DataRepository =
DataRepositoryImpl()
}
How do I provide multiple repository implementations via Entrypoint and bridge it with SingletonComponent? I get the below error:
DataRepository is bound multiple times
error
#InstallIn(AuthUserComponent::class)
#EntryPoint
interface AuthUserDataEntryPoint {
#ProductionMode
fun dataRepositoryImpl(): DataRepository
#DemoMode
fun dataRepositoryImplDemo(): DataRepository
}
#Module
#InstallIn(SingletonComponent::class)
internal object AuthUserDataEntryBridge {
#DemoMode
#Provides
internal fun provideDataRepositoryImplDemo(
authUserComponentManager: AuthUserComponentManager
): DataRepository {
return EntryPoints
.get(authUserComponentManager, AuthUserDataEntryPoint::class.java)
.dataRepositoryImplDemo()
}
#ProductionMode
#Provides
internal fun provideDataRepositoryImpl(
authUserComponentManager: AuthUserComponentManager
): DataRepository {
return EntryPoints
.get(authUserComponentManager, AuthUserDataEntryPoint::class.java)
.dataRepositoryImpl()
}
}
The issue to me seems to be that you are defining implementations in both DIModule and AuthUserDataEntryBridge for the same things. To be honest, I'm not really sure what the purpose of AuthUserDataEntryBridge is and I'm unfamiliar with the usage of EntryPoints inside a class annotated with #Module.
Is there are reason you can't just do this install this in the SingletonComponent directly:
Module
#InstallIn(SingletonComponent::class)
object DIModule {
#DemoMode
#Provides
fun provideDataRepositoryImplDemo(): DataRepository =
DataRepositoryImplDemo()
#Provides
#ProductionMode
fun provideDataRepositoryImpl(): DataRepository =
DataRepositoryImpl()
}
That seems to be the core of the issue here. I'm guessing its because of the custom component / scope. In that case, I think you just need to use EntryPoints where you are actually injecting the repositories:
class Sample {
private lateinit var demoRepository: DataRepository
private lateinit var productionRepository: DataRepository
fun inject(authUserComponent: AuthUserComponent) {
val entryPoint = EntryPoints.get(authUserComponent, AuthUserDataEntryPoint:class.java)
demoRepository = entryPoint.demoRepositoryImpl()
productionRepository = entryPoint.productionRepositoryImpl()
}
}
But I don't think you're going to get away with trying to hide the EntryPoints usage inside a module definition.
I have a class named NetworkManager. Since it is not one of the Android Components, I am using custom entry point, NetworkManagerEntryPoint with one fun that returns NetworkClient object which is what I want to inject.
Now, to inject an instance of this class using Hilt, I believe I need to use one of the Helper methods in EntryPointAccessors. But all of them requires a reference to android components. So, do I really have to pass an android component like Context to my class to inject an object using Hilt?
class NetworkManager() {
#InstallIn(SingletonComponent::class)
#EntryPoint
interface NetworkManagerEntryPoint {
fun getNetworkClient(): NetworkClient
}
var defaultNetworkClient: NetworkClient = EntryPointAccessors.fromApplication(
context, // Do I have to pass a context to this class to use Hilt?
NetworkManagerEntryPoint::class.java
).getNetworkClient()
fun <R : Any> executeRequest(
request:Request<R>,
networkClient: NetworkClient = defaultNetworkClient
): Response<R> {
// Do some operation
}
}
Hi there maybe you can try this way i have done , i follow the mvvm pattern
My RetrofitApi
interface RetrofitApi {
#GET("endpoint")
suspend fun getApi():Response<RetrofitApiResponse>
}
My NetworkModule
#Module
#InstallIn(SingletonComponent::class)
object NetworkModule{
#Singleton
#Provides
fun provideApi(): RetrofitApi = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(RetrofitApi::class.java)
#Singleton
#Provides
fun provideRepository(retrofitApi:RetrofitApi) : MainRepository =
DefualtMainRepository(retrofitApi)
}
and this module gets injected in my repository
class DefualtMainRepository #Inject constructor(
val retrofitApi: RetrofitApi
):MainRepository {
override suspend fun getQuotes(): Resource<RetrofitApiResponse> {
val response = retrofitApi.getApi()
val result = response.body()
if (response.successful){
}
}
}
If you are interested i have full project in my github and even wrote a medium article explaining it, Hopefully my answer is helpful to you
https://zaidzakir.medium.com/a-simple-android-app-using-mvvm-dagger-hilt-e9f45381f1bc
Okay. #Zaid Zakir's answer showed me that I can inject objects via constructor parameters, if not field injection. So, the solution for me ended up looking like this.
#Singleton
class NetworkManager #Inject constructor(
var defaultNetworkClient: NetworkClient
) {
fun <R : Any> executeRequest(
request:Request<R>,
networkClient: NetworkClient = defaultNetworkClient
): Response<R> {
// Do some operation
}
}
In another class named NetworkClientModule, I have this,
#Module
#InstallIn(SingletonComponent::class)
abstract class NetworkClientModule {
#Binds #Singleton
abstract fun bindDefaultNetworkClient(impl: DefaultNetworkClient): NetworkClient
}
I am new to Dagger 2 in android. I am having trouble understanding how to inject ViewModel with dynamic value. So Far I have successfully injected ViewModel using dagger multi binding with pre-defined repository dependency. Here's my code.
ApplicationComponent
#Singleton
#Component(modules = [AppModule::class, SubComponentsModule::class, ViewModelFactoryModule::class])
interface ApplicationComponent {
#Component.Factory
interface Factory {
fun create(#BindsInstance applicationContext: Context): ApplicationComponent
}
fun activityComponent(): ActivitySubComponent.Factory
fun fragmentComponent(): FragmentSubComponent.Factory
}
FragmentModule
#Module
abstract class FragmentModule {
#Binds
#IntoMap
#ViewModelKey(WeatherViewModel::class)
abstract fun bindWeatherView(weatherViewModel: WeatherViewModel) : ViewModel
}
ViewModelFactoryModule
#Module
class ViewModelFactoryModule {
#Provides
#Singleton
fun viewModelFactory(providerMap: Map<Class<out ViewModel>, Provider<ViewModel>>): ViewModelProvider.Factory {
return ViewModelFactory(providerMap)
}
}
Application class
class ThisApplication: Application(),InjectorProvider {
override fun onCreate() {
super.onCreate()
Stetho.initializeWithDefaults(this)
}
override val component by lazy {
DaggerApplicationComponent.factory().create(applicationContext)
}
}
I'm using InjectorProvider interface to get dagger to fragments and activity without having to cast every time.
InjectorProvider
interface InjectorProvider {
val component: ApplicationComponent
}
val Activity.injector get() = (application as InjectorProvider).component
val Fragment.injector get() = (requireActivity().application as InjectorProvider).component
This is the simple ViewModel I used for testing ViewModel injection.
WeatherViewModel
class WeatherViewModel #Inject constructor(val repository: WeatherRepository): ViewModel() {
fun printMessage(){
Log.d("WeatherViewModel","ViewModel binding is working")
repository.printMessage()
}
}
Finally, I Injected this view model into a fragment like below.
WeatherFragment
class WeatherFragment : Fragment() {
#Inject
lateinit var viewModelFactory: ViewModelFactory
override fun onAttach(context: Context) {
injector.fragmentComponent().create().injectWeatherFragment(this)
super.onAttach(context)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val mainActivityViewModel =
ViewModelProvider(this,viewModelFactory)[WeatherViewModel::class.java]
mainActivityViewModel.printMessage()
}
}
This part is working fine. The Log message inside printMessage() getting printed. I saw in the dagger issue discussion that using AssistedInject is the best approach to handle this kind of scenario. I changed my ViewModle by adding a simple int value as a parameter.
Edited WeatherViewModel
class WeatherViewModel #AssistedInject constructor(val repository: WeatherRepository,
#Assisted val id: Int): ViewModel() {
#AssistedInject.Factory
interface Factory{ fun create(id: Int) : WeatherViewModel }
fun printMessage(){
Log.d("WeatherViewModel","ViewModel binding is working")
repository.printMessage()
}
}
Edited ApplicationComponent
#Singleton
#Component(modules = [AppModule::class, SubComponentsModule::class, ViewModelFactoryModule::class, AssistedInjectModule::class])
interface ApplicationComponent {
#Component.Factory
interface Factory {
fun create(#BindsInstance applicationContext: Context): ApplicationComponent
}
fun activityComponent(): ActivitySubComponent.Factory
fun fragmentComponent(): FragmentSubComponent.Factory
}
#AssistedModule
#Module(includes = [AssistedInject_AssistedInjectModule::class])
interface AssistedInjectModule
From this point onwards I don't understand how to inject ViewModel into fragment with repository plus dynamic "id" value. If I inject WeatherViewModel.Factory into the fragment by calling the create method (val mainActivityViewModel = factory.create(5)) it won't fulfill the repository dependency in ViewModel. How to combine these two solutions to have pre-defined repository dependency with dynamic value? OR is there any other better way of approaching this?
Not quite sure why your setup wont fulfill repository dependency by using create() method of factory. The repository dependency will be provided by Dagger's Acyclic Dependency Graph.
For example, below I'm saying to Dagger that I am responsible for providing SavedStateHandle and the NavigationDispatcher so don't even bother looking these up in your acyclic dependency graph.
class ProfileViewModel #AssistedInject constructor(
#Assisted val handle: SavedStateHandle,
#Assisted val navigationDispatcher: NavigationDispatcher,
private val eventTracker: EventTracker,
private val getUserUseCase: GetUserUseCase,
private val logOutUseCase: LogOutUseCase
) : ViewModel(), ProfileHandler {
#AssistedInject.Factory
interface Factory {
fun create(
handle: SavedStateHandle,
navigationDispatcher: NavigationDispatcher
): ProfileViewModel
}
In Fragment side, all I have to provide in the create method will be the dependencies i marked with #Assisted to fulfil my side of promise.
class ProfileFragment : Fragment() {
private val navigationDispatcher by getActivityViewModel {
getBaseComponent().navigationDispatcher
}
private val eventTracker by lazy {
getProfileComponent().eventTracker
}
private val viewModel by getViewModel { savedStateHandle ->
getProfileComponent().profileViewModelFactory.create(savedStateHandle, navigationDispatcher)
}
getViewModel is simply an extension function as follows:
inline fun <reified T : ViewModel> Fragment.getViewModel(crossinline provider: (handle: SavedStateHandle) -> T) =
viewModels<T> {
object : AbstractSavedStateViewModelFactory(this, arguments) {
override fun <T : ViewModel?> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
) = provider(handle) as T
}
}
I am using Dagger 2.14.1 in my Android application and migrated recently from 2.10. The new version makes it much easier to inject Activities and Fragments, but I can't find a way to inject a custom class where I cannot change the constructor as well. With 2.10 I could write custom inject functions and then use them to simply inject any class:
Simplified Dagger 2.10 Injection with Kotlin:
#Singleton
#Component(dependencies = [], modules = [ApplicationModule::class, ...])
interface ApplicationComponent {
fun application(): App
fun inject(authenticationActivity: AuthenticationActivity)
fun inject(converters: Converters)
// ...
}
class App : Application() {
companion object {
#JvmStatic
lateinit var component: ApplicationComponent
}
override fun onCreate() {
super.onCreate()
component = DaggerApplicationComponent.builder()
.applicationModule(ApplicationModule(this))
.build()
}
}
// This class is used by the Room database framwork and I cannot change the constructor and do class Converters #Inject constructor(private val gson: Gson) {
class Converters {
#Inject
protected lateinit var gson: Gson
init {
App.component.inject(this)
}
// ...
}
Simplified Dagger 2.14.1 Injection with Kotlin doesn't provide me with a ApplicationComponent Object to inject my custom classess:
#Singleton
#Component(modules = [ApplicationModule::class, ...])
interface ApplicationComponent : AndroidInjector<App> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<App>()
fun inject(converters: Converters) // doesn't work!
}
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
// From where do I get my component to call .inject() from another class?
DaggerApplicationComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
In short: the DaggerApplicationComponent.builder().create(this) returns an AndroidInjector<App!> Object. This Object only has one .inject() functions which only accepts my App Class. So I cannot inject anything else.
Of course there is a lot missing, but I wanted to only post the relevant code. My Dagger 2.14.1 implementation works and all my dependencies in Activities, Fragments and View Models get injected.
I am quite new to Dagger and the more I use it the more I love it, but I couldn't figure out on how to inject custom classes where I cannot change the constructor. Thanks for you help!
Try this:
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
companion object {
#JvmStatic
lateinit var instance: App
}
#Inject
lateinit var component: ApplicationComponent
override fun onCreate() {
super.onCreate()
instance = this
DaggerApplicationComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
Then
object Injector {
#JvmStatic fun get(): ApplicationComponent = App.instance.component
}
Now you can do
class Converters {
#Inject
protected lateinit var gson: Gson
init {
Injector.get().inject(this)
}