Proper way to inject a class that implements an interface using Hilt - android

I have
BeatPlayer.kt
interface BeatPlayer {
fun getSession(): MediaSessionCompat
fun playSong(extras: Bundle = bundleOf(BY_UI_KEY to true))
fun playSong(id: Long)
fun playSong(song: Song)
}
class BeatPlayerImplementation(
private val context: Application,
private val musicPlayer: AudioPlayer,
private val songsRepository: SongsRepository,
private val queueUtils: QueueUtils,
private val audioFocusHelper: AudioFocusHelper
) : BeatPlayer {
.........
}
MusicService.kt
#AndroidEntryPoint
class MusicService : CoroutineService(Main) {
#Inject
lateinit var beatPlayer: BeatPlayer
}
When I run it says:
[Dagger/MissingBinding] BeatPlayer cannot be provided without an #Provides-annotated method.
So I added this:
#Module
#InstallIn(SingletonComponent::class)
abstract class StorageModule {
#Singleton
#Binds
abstract fun bindBeatPlayer(beatPlayer: BeatPlayer): BeatPlayerImplementation
}
Now, I run, it says:
error: #Binds methods' parameter type must be assignable to the return type hilt
How to do it properly?

I will answer my question based on comment from #HenryTest.
StorageModule.kt
#Module
#InstallIn(SingletonComponent::class)
abstract class StorageModule {
#Binds
abstract fun bindsPreferenceStorage(preferenceStorageImpl:
PreferenceStorageImpl): PreferenceStorage
#Singleton
#Binds
abstract fun bindBeatPlayer(beatPlayer: BeatPlayerImplementation):
BeatPlayer
}
Now Provide BeatPlayerImplementation.
AppModule.kt
#Singleton
#Provides
fun providesBeatPlayerImplementation(#ApplicationContext context:
Context.,.,.,.) =
BeatPlayerImplementation(
context, .., .., ..,
)
OR
In BeatPlayerImplementation
class BeatPlayerImplementation #Inject constructor(
#ApplicationContext private val context: Application,
.....
) : BeatPlayer {
.........
}

Related

Android Hilt cannot be provided without an #Provides-annotated method. in repository Interface Class

I'm getting this error error: [Dagger / MissingBinding] com.eduramza.domain.repositories.RemoteRepository cannot be provided without an # Provides-annotated method. when implementing my repository interface with android hilt.
That's because my useCase implements my repository interface. What may be wrong with my implementation, below is the code:
app.Viewmodel:
#HiltViewModel
class RemoteListViewModel #Inject constructor(
private val useCase: GetTickersUseCase
): ViewModel() {
}
domain.usecase:
class GetTickersUseCase #Inject constructor(
private val remoteRepository: RemoteRepository)
: SingleUseCase<MainCoins> {
override suspend fun executeCall(): Flow<Result<MainCoins>> = remoteRepository.readAllTickers()
}
domain.repository:
interface RemoteRepository {
suspend fun readAllTickers(): Flow<Result<MainCoins>>
}
core.repositoryImpl:
class RemoteRepositoryImpl #Inject constructor(
private val apiService: BraziliexService,
private val tickersMapper: TickersMapper
) : RemoteRepository{
override suspend fun readAllTickers(): Flow<Result<MainCoins>> {
TODO("Not yet implemented")
}
}
core.module:
#Module
#InstallIn(ActivityComponent::class)
abstract class RemoteModule {
#Binds
abstract fun bindRemoteRepository(
remoteRepositoryImpl: RemoteRepositoryImpl
): RemoteRepository
}
My multimodule app in this structure
where core implement domain, and app implement both.
why is the bind method not being initialized?
You using the ActivityComponent but the RemoteRepository is the indirect dependency of ViewModel so it should be tied with the ViewModel Lifecycle
so instead of ActivityComponent
#Module
#InstallIn(ActivityComponent::class)
abstract class RemoteModule {
#Binds
abstract fun bindRemoteRepository(
remoteRepositoryImpl: RemoteRepositoryImpl
): RemoteRepository
}
Use this ViewModelComponent
#Module
#InstallIn(ViewModelComponent::class)
abstract class RemoteModule {
#Binds
abstract fun bindRemoteRepository(
remoteRepositoryImpl: RemoteRepositoryImpl
): RemoteRepository
}

Android Hilt dagger inject interface in viewModel #ViewModelInject got UninitializedPropertyAccessException

I trying on Hilt codelab
https://codelabs.developers.google.com/codelabs/android-hilt#10
It's working fine with Activity and Fragment
logger is a RoomDB
Then I try to inject logger into viewModel with this article
By add
implementation "androidx.hilt:hilt-lifecycle-viewmodel
:1.0.0-alpha02"
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
ViewModelCode
class RecordFragmentViewModel #ViewModelInject constructor(#Assisted private val savedStateHandle: SavedStateHandle) :
ViewModel() {
#DatabaseLogger
#Inject
lateinit var logger: LoggerDataSource
Class logger to inject
class LoggerLocalDataSource
#Inject constructor(private val logDao: LogDao) : LoggerDataSource {
LoggingModule
#Qualifier
annotation class InMemoryLogger
#Qualifier
annotation class DatabaseLogger
#InstallIn(ApplicationComponent::class)
#Module
abstract class LoggingDatabaseModule {
#DatabaseLogger
#Singleton
#Binds
abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}
#InstallIn(ActivityComponent::class)
#Module
abstract class LoggingInMemoryModule {
#InMemoryLogger
#ActivityScoped
#Binds
abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}
DatabaseModule
#InstallIn(ApplicationComponent::class)
#Module
object DatabaseModule {
#Provides
#Singleton
fun provideDatabase(#ApplicationContext appContext: Context): AppDatabase {
return Room.databaseBuilder(
appContext,
AppDatabase::class.java,
"logging.db"
).build()
}
#Provides
fun provideLogDao(database: AppDatabase): LogDao {
return database.logDao()
}
}
It's compile and run without error.
However, I use debug to watch logger and its got.
Method threw 'kotlin.UninitializedPropertyAccessException' exception.
I call logger.something() at run time its throw
Fatal Exception: kotlin.UninitializedPropertyAccessException
lateinit property logger has not been initialized
More info
https://dagger.dev/hilt/migration-guide.html
https://codelabs.developers.google.com/codelabs/android-hilt#10
https://medium.com/mobile-app-development-publication/injecting-viewmodel-with-dagger-hilt-54ca2e433865
Since LoggerDataSource is a interface we need to specify which implementation we need to inject. Thanks to #Andrew for the idea of inject to constructor
class RecordFragmentViewModel
#ViewModelInject
constructor(#Assisted private val savedStateHandle: SavedStateHandle,
#DatabaseLogger private val logger: LoggerDataSource) :
ViewModel(), LifecycleObserver {
To specify
#Qualifier
annotation class InMemoryLogger
#Qualifier
annotation class DatabaseLogger
#InstallIn(ApplicationComponent::class)
#Module
abstract class LoggingDatabaseModule {
#DatabaseLogger
#Singleton
#Binds
abstract fun bindDatabaseLogger(impl: LoggerLocalDataSource): LoggerDataSource
}
#InstallIn(ActivityComponent::class)
#Module
abstract class LoggingInMemoryModule {
#InMemoryLogger
#ActivityScoped
#Binds
abstract fun bindInMemoryLogger(impl: LoggerInMemoryDataSource): LoggerDataSource
}

Dagger2 Binding issue

I have two modules one for ViewModelModule Providers and another for Application level which contains retrofit,intent. So before passing to the component I have included the ViewModelModule to the ApplicationModule like this
#Module(includes = [ViewModelModule::class])
class ApplicationModule {
And My component interface is Like:
#Singleton
#Component(modules = [ApplicationModule::class, ContextModule::class])
interface AppComponent {
fun inject(activity: LoginActivity)
fun inject(activity: RegisterActivity)
fun inject(activity: SplashActivity)
}
ApplicationModule class
#Module(includes = [ViewModelModule::class])
class ApplicationModule {
#Singleton
#Named("GotoLogin")
#Provides
fun provideSplashIntent(appCompatActivity: AppCompatActivity): Intent {
return Intent(appCompatActivity, LoginActivity::class.java)
}
#Singleton
#Named("GotoDashboard")
#Provides
fun provideLoginIntent(appCompatActivity: AppCompatActivity): Intent {
return Intent(appCompatActivity, DashboardActivity::class.java)
}
#Singleton
#Named("GotoRegister")
#Provides
fun provideRegisterIntent(appCompatActivity: AppCompatActivity): Intent {
return Intent(appCompatActivity, RegisterActivity::class.java)
}
#Singleton
#Provides
fun provideTimer(): Timer {
return Timer()
}
}
ViewModelModule class
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindviewmodelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(LoginViewModel::class)
abstract fun bindLoginViewModel(loginViewModel: LoginViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(RegisterViewModel::class)
abstract fun bindRegisterViewModel(registerViewModel: RegisterViewModel): ViewModel
}
Scenario:
Suppose when I trying to use a function from ApplicationModule Class in LoginActivity then the Error is coming.
Usage:
class LoginActivity : AppCompatActivity() {
#Inject
lateinit var factory: ViewModelProvider.Factory
lateinit var loginViewModel: LoginViewModel
lateinit var context: Context
#Named("GotoRegister")
#Inject
lateinit var regiseterIntent: Intent
But when I am trying to use anything from the ApplicationModule class a compile error is coming
Crony\app\build\tmp\kapt3\stubs\debug\com\app\crony\di\AppComponent.java:8: error: [Dagger/MissingBinding] androidx.appcompat.app.AppCompatActivity cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract interface AppComponent {
^
androidx.appcompat.app.AppCompatActivity is injected at
com.app.crony.di.ApplicationModule.provideRegisterIntent(appCompatActivity)
#javax.inject.Named("GotoRegister") android.content.Intent is injected at
com.app.crony.LoginActivity.regiseterIntent
Full Source Code:
Github Link
I can feel that I am missing something but not able to sort out the issue.
Replace activity with context. It will work well.

How to get Class from KClass for Dagger2 ViewModelFactory? [duplicate]

I have followed this tutorial in order to do DI in my viewmodels. But I currently am stuck.
I have created a ViewModelFactory for my viewmodel which is as follows:
class HomeViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>,
Provider<ViewModel>>
): ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return creators[modelClass]?.get() as T
}
}
Followed by a ViewModel:
class HomeViewModel #Inject constructor(private val songsRepository: SongsRepository): ViewModel()
For DI I have created two components. One is my main application component, the other is a component which depends on the main application.
#Singleton
#Component(modules = [AppModule::class])
public interface AppComponent {
fun songRepository(): SongsRepository
fun libraryManager(): LibraryManager
fun inject(mainActivity: MainActivity)
}
#Module
public class AppModule(val application: Application){
#Provides #Singleton
fun providesApplication(): Application{
return application
}
#Provides #Singleton
fun providesLibraryManager(): LibraryManager {
return LibraryManager(application)
}
#Provides #Singleton
fun providesSongRepository(libraryManager: LibraryManager): SongsRepository {
return SongsRepository(libraryManager)
}
}
And my ViewModelModule is as follows:
#Module
public class ViewModelModule {
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
#AppScope
#Provides
fun providesHomeViewModelFactory(providerMap: Map<Class<out ViewModel>, Provider<ViewModel>>): HomeViewModelFactory {
return HomeViewModelFactory(providerMap)
}
#AppScope
#IntoMap
#Provides
#ViewModelKey(HomeViewModel::class)
fun providesHomeViewModel(songsRepository: SongsRepository): HomeViewModel{
return HomeViewModel(songsRepository)
}
}
#AppScope
#Component(modules = [ViewModelModule::class], dependencies = [AppComponent::class])
public interface ViewModelComponent {
fun homeViewModelFactory(): HomeViewModelFactory
fun homeViewModel(): HomeViewModel
fun inject(homeFragment: HomeFragment)
}
The error I'm getting is this:
error: [Dagger/MissingBinding] java.util.Map,? extends
javax.inject.Provider> cannot be
provided without an #Provides-annotated method.
I seriously don't have any idea why is this happening since all my classes have #Inject constructors. Dagger's documentation is not helping me either. I would be grateful if you could advise me on this matter.
The error message indicates that the following code is wrong:
fun providesHomeViewModelFactory(providerMap: Map<Class<out ViewModel>, Provider<ViewModel>>): HomeViewModelFactory {
return HomeViewModelFactory(providerMap)
}
It should be
fun providesHomeViewModelFactory(providerMap: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>): HomeViewModelFactory {
return HomeViewModelFactory(providerMap)
}
It is because the signature of Map interface is Map<K, out V>, that means the Map<..., Provider<ViewModel>> will be compiled to Map<..., ? extends Provider<ViewModel>> Java code, so you are asking dagger for latter one but it only has former one in its object graph, then the compiler throws you the error.

ViewModel cannot be provided without an #Inject constructor or an #Provides-annotated

Question EDITED
I am injecting ViewModelProvider.Factory to BaseActivity like below
open class BaseActivity : DaggerAppCompatActivity() {
#Inject
lateinit var factories: ViewModelProvider.Factory
inline fun <reified T : ViewModel> getViewModel(): T {
return ViewModelProvider(this, factories).get(T::class.java)
}
}
viewModel only works when we inject then like below.
class MainViewModel #Inject constructor( private val alertStore: AlertStore)
: BaseViewModel(){
fun showDialog(){
viewModelScope.launch {
delay(4000)
alertStore.showToast("Alert after 4 seconds.")
}
}
}
Why this #Inject constructor is necessary in my current implementation
class MainActivity : BaseActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = getViewModel()
viewModel.showDialog()
}
}
App.kt
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().addContext(this).build()
}
}
AppComponent.kt
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
ActivityBuilder::class,
ViewModelInjector::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
fun addContext(#BindsInstance context: Context): Builder
fun build(): AppComponent
}
}
AppModule.kt
#Module
class AppModule {
#Provides
fun provideViewModelFactories(viewModels: Map<Class<out ViewModel>,
#JvmSuppressWildcards Provider<ViewModel>>):
ViewModelProvider.Factory {
return object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val factory = viewModels[modelClass]?.get() ?: error(
"No factory provided against ${modelClass.name}"
)
#Suppress("UNCHECKED_CAST")
return factory as T
}
}
}
}
ActivityBuilder.kt
#Module
abstract class ActivityBuilder {
//#Scope("")
#ContributesAndroidInjector ///(modules = {MainModelFactory.class})
public abstract MainActivity bindMainActivity();
}
ViewModelInjector.kt
#Module
public abstract class ViewModelInjector {
#Binds
#IntoMap
#ViewModelKey(MainViewModel.class)
public abstract ViewModel providesMainViewModel(MainViewModel model);
}
ViewModelKey.kt
#MapKey
#Retention(AnnotationRetention.SOURCE)
annotation class ViewModelKey(
val value: KClass<out ViewModel>
)
Why do I have to append #Inject constructor to each ViewModel and kindly explain a little why we need #Binds #IntoMap and with ViewModel
When you use dagger android you should make your activities and fragments as extensions of DaggerActivity (and DaggerFragment for fragments).
class MainActivity : DaggerActivity() {
#Inject
lateinit var viewModel: MainViewModel
}
Next you should prepare infrastructure for injection:
Create injectors for each your activity:
// All your injectors can be defined in this module
#Module(includes = [AndroidInjectionModule::class])
interface AppInjectorModule {
#ContributesAndroidInjector(modules = [MainActivityVmModule::class, /*other dependecies*/])
fun getMainActivityInjector(): MainActivity
}
Create modules to provide view models (can be multiple for one activity) and factory
#Module(includes = [VmFactoryModule::class])
abstract class MainActivityVmModule {
// bind implementation of ViewModel into map for ViewModelFactory
#Binds
#IntoMap
#ClassKey(MainViewModelImpl::class)
abstract fun bindMainVm(impl: MainViewModelImpl): ViewModel
#Module
companion object {
#Provides
#JvmStatic
fun getMainVm(activity: MainActivity, factory: ViewModelProvider.Factory): MainViewModel {
// create MainViewModelImpl in scope of MainActivity and inject dependecies by ViewModelFactory
return ViewModelProviders.of(activity, factory)[MainViewModelImpl::class.java]
}
}
}
Factory can be provided by different module to avoid duplication
#Module
interface VmFactoryModule {
#Binds
// bind your implementation of factory
fun bindVmFactory(impl: ViewModelFactory): ViewModelProvider.Factory
}
Add activities injectors to AppComponent graph
#Component(
modules = [
AppInjectorModule::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App>
Additional info: Dagger & Android

Categories

Resources