Can't Pass Strings Through Constructor in HiltViewModel - Error: [Dagger/MissingBinding] java.lang.String cannot be provided

Viewed 56

I am migrating from Dagger 2 to Hilt in Android. Most of my migrations have been pretty straight forward, but the last viewmodel I'm converting is giving me trouble

ContactDetailViewModel

@HiltViewModel
class ContactDetailViewModel
@Inject
constructor(
    application: Application,
    contactId: String?,
    name: String?,
    refId: String,
) : NetworkBaseViewModel(application) {
    .... (non-DI related code)

    @Inject
    lateinit var client: ApolloClient

    .... (non-DI related code and references to constructor params)
}

ContactDetailVMFactory

@Singleton
class ContactDetailVMFactory @Inject constructor(
    val application: Application,
    val contactId: String?,
    val name: String?,
    val refId: String
) : ViewModelProvider.AndroidViewModelFactory(application) {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return ContactDetailViewModel(application, contactId, name, refId) as T
    }
}

ContactDetailEditFragment

@AndroidEntryPoint
open class ContactDetailEditFragment: Fragment() {

    val args: ContactDetailEditFragmentArgs by navArgs()

    open val vm by activityViewModels<ContactDetailViewModel>(
        factoryProducer = {
            ContactDetailVMFactory(
                    application = requireActivity().application,
                    contactId = args.contactId,
                    name = args.name,
                    refId = args.contactId ?: args.vmKey
            )
        },
        key = { args.vmKey }
    )
        ....

    @Inject
    lateinit var client: ApolloClient



        ....
}

Errors / Build Output

/BaseApplication_HiltComponents.java:141: error: [Dagger/MissingBinding] java.lang.String cannot be provided
without an @Inject constructor or an @Provides-annotated method.
public abstract static class SingletonC implements BaseApplication_GeneratedInjector,
                        ^
    java.lang.String is injected at
        com.companyname.contacts.vm.ContactDetailViewModel(…, name, …)
    com.companyname.contacts.vm.ContactDetailViewModel is injected at
        com.companyname.contacts.vm.ContactDetailViewModel_HiltModules.BindsModule.binds(vm)
    @dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject
.Provider<androidx.lifecycle.ViewModel>> is requested at
    dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint
.getHiltViewModelMap() [com.companyname.BaseApplication_HiltComponents.SingletonC → com.companyname
.BaseApplication_HiltComponents.ActivityRetainedC → com.companyname.BaseApplication_HiltComponents
.ViewModelC]
It is also requested at:
    com.companyname.contacts.vm.ContactDetailViewModel(…, contactId, …)
    com.companyname.contacts.vm.ContactDetailViewModel(…, refId)

I'm pretty baffled on this. I've been working on it for days. I have other strings that are being passed into other HiltViewModels that don't seem to have a problem. I wonder if it's an issue with the ViewModelFactory.

1 Answers

There are a lot of issues within code provided (check my comments), but I would simplify it as follows:

@HiltViewModel
class ContactDetailViewModel @Inject constructor(
    @ApplicationContext val application: Context, // cast to Application later
    val client: ApolloClient
    savedStateHandle: SavedStateHandle
) : NetworkBaseViewModel(application) { 

    // get fragment arguments via SavedStateHandle
    private val args = ContactDetailEditFragmentArgs.fromSavedStateHandle(savedStateHandle)

}

ContactDetailVMFactory - remove

ContactDetailEditFragment - use open val vm: ContactDetailViewModel by viewModels().

This answer is with assumption that you don't really need to use by activityViewModels(), (it would be very weird if you needed to inject Fragment's arguments into Activity-bound ViewModel, though I can see why some people would want to do that)

Related