Issue
I have MVVM architecture in my app. Every screen where I would like to make a request to the api has such code scope:
val retrofitService = RetrofitService.getInstance(requireContext())
val mainRepository = MainRepository(retrofitService)
val viewModel =
ViewModelProvider(
viewOwner,
AppVMFactory(mainRepository)
)[AppViewModel::class.java]
and due to the fact that I have a lot of screens in the app I have a lot of similar code parts. In such case as I understood I can use Dependency Injection. I found video tutorial with provided sample_project where got some info for support hilt in the app. I added all dependencies for hilt and then added application class and AppModule class:
@Module
@InstallIn(SingletonComponent::class)
object AppModule{
@Singleton
fun provideApp(@ApplicationContext app: Context):BaseApplication = app as BaseApplication
@Singleton
@Provides
fun someStr() = "Hello World from DI"
}
in my test activity I added such lines:
@Inject
lateinit var str: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("Test $str")
.....
}
and I got string for the AppModule object. Then the next step was adding returning method with AppViewModel:
@Module
@InstallIn(SingletonComponent::class)
object AppModule{
....
@Singleton
@Provides
fun vmSupport(context: Context, owner: ViewModelStoreOwner): AppViewModel {
val retrofitService = RetrofitService.getInstance(context)
val mainRepository = MainRepository(retrofitService)
return ViewModelProvider(
owner,
AppVMFactory(mainRepository)
)[AppViewModel::class.java]
}
....
}
but during my build I got this error:
[Dagger/MissingBinding] android.content.Context cannot be provided without an @Provides-annotated method.
public abstract static class SingletonC implements HiltWrapper_ActivityRetainedComponentManager_ActivityRetainedComponentBuilderEntryPoint,
^
android.content.Context is injected at
pkg.di.AppModule.vmSupport(context, …)
pkg.viewModel.AppViewModel is injected at
pkg.HomeScreen.vm
pkg.HomeScreen is injected at
pkg.HomeScreen_GeneratedInjector.injectHomeScreen(pkg.HomeScreen) [pkg.di.BaseApplication_HiltComponents.SingletonC → pkg.di.BaseApplication_HiltComponents.ActivityRetainedC → pkg.di.BaseApplication_HiltComponents.ActivityC]
and I also added such line to the test activity:
@Inject
lateinit var vm:AppViewModel
As I understood the problem is connected with passing arguments to the function, but how I can do it via hilt and so on? Or I did it in totally wrong way? My VM class you can see below:
class AppViewModel(private val mainRepository: MainRepository) : ViewModel(){...}
And MainRepository:
class MainRepository constructor(private val retrofitService: RetrofitService){...}
and retrofit service is simple interface.
update
My factory for viewmodel:
class AppVMFactory(private val repository: MainRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(AppViewModel::class.java)) {
AppViewModel(this.repository) as T
} else {
throw IllegalArgumentException("ViewModel Not Found")
}
}
}
Solution
You need to have a method with the @Provides annotation for all or almost all the parameters that you will pass in the other @Provider methods. In this case, the error is self-explanatory. You pass the Context as a parameter, but you don't have a method that makes the Provider of the Context. You needed something like this:
interface ContextProvider {
fun getLatestContext(): Context
}
/**
* Provider to give the current available context to be possible to inject
* the context inside classes
*/
class ContextProviderImpl @Inject constructor(private val context: Context) :
Application.ActivityLifecycleCallbacks,
ContextProvider {
private var currentActivityRef: WeakReference<Activity> = WeakReference(null)
override fun getLatestContext(): Context {
return currentActivityRef.get() ?: context
}
override fun onActivityPaused(activity: Activity) {
// not used
}
override fun onActivityResumed(activity: Activity) {
currentActivityRef = WeakReference(activity)
}
override fun onActivityStarted(activity: Activity) {
currentActivityRef = WeakReference(activity)
}
override fun onActivityDestroyed(activity: Activity) {
if (activity === currentActivityRef.get()) {
currentActivityRef = WeakReference(null)
}
}
override fun onActivitySaveInstanceState(activity: Activity, p1: Bundle) {
// not used
}
override fun onActivityStopped(activity: Activity) {
// not used
}
override fun onActivityCreated(activity: Activity, p1: Bundle?) {
currentActivityRef = WeakReference(activity)
}
}
And after that the Provider method for the context. It is very important that you return the Interface and not the implementation, but that this method provides the implementation and not the interface. Take a look at the code:
@Module
@InstallIn(SingletonComponent::class)
object ProviderModule {
@Provides
@Singleton
fun provideContextProvider(context: Context): ContextProvider =
ContextProviderImpl(context)
}
Answered By - R0ck
Answer Checked By - David Goodson (JavaFixing Volunteer)