I'm migrating from Dagger2 to Hilt however I'm unable to solve this part:
#Module
class HistoryPurchaseModule(private val historyPurchaseFragment: HistoryPurchaseFragment) {
#Provides
fun provideHistoryPurchaseFragment(): HistoryPurchaseFragment = historyPurchaseFragment
#Provides
fun provideUseCase() = HistoryPurchaseUseCase()
#Provides
fun provideSession() = HistoryPurchaseSession(
isHistoryLoading = MutableLiveData(true),
isThisTheFirstTime = true,
isHistoryEmpty = MutableLiveData(false)
)
#Provides
fun provideViewModel(session: HistoryPurchaseSession): HistoryPurchaseViewModel {
return historyPurchaseFragment.createViewModel {
HistoryPurchaseViewModel(session)
}
}
#Provides
fun provideLogic(
useCases: HistoryPurchaseUseCase,
viewModel: HistoryPurchaseViewModel
) = HistoryPurchaseLogic(viewModel.viewModelScope.coroutineContext, useCases, historyPurchaseFragment, viewModel)
}
My logic class takes two interfaces in the constructor that the viewModel(HistoryPurchaseContract.ViewModel) and fragment(HistoryPurchaseContract.View) implement.
Fragment:
class HistoryPurchaseFragment : BaseFragment<HistoryPurchaseEvent>(), HistoryPurchaseContract.View
ViewModel:
class HistoryPurchaseViewModel #Inject constructor(override var session: HistoryPurchaseSession) : BaseViewModel<HistoryPurchaseEvent>(),
HistoryPurchaseContract.ViewModel
I managed to pass the viewModel however I'm unable to pass the fragment.
Is there a way I can do it?
Related
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 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 have 3 repositories:
interface MainRepository {
...
}
interface LocalRepository {
...
}
interface WebRepository {
...
}
Each Repository has it's own implementation:
#Singleton
class MainRepositoryImpl #Inject constructor(
private val localRepository: LocalRepository,
private val webRepository: WebRepository
) : MainRepository {
...
}
#Singleton
class LocalRepositoryImpl #Inject constructor(
private val localMapper: LocalMapper
private val popularMovieDao: PopularMovieDao
) : LocalRepository {
...
}
#Singleton
class WebRepositoryImpl #Inject constructor(
private val webMapper: WebMapper,
private val popularMovieApi: PopularMovieApi
) : WebRepository {
...
}
As you can see, MainRepository needs to have both other repositories injected into it, however,I can't really figure out how to do it.
Of course I can inject it with type of LocalRepositoryImpl or WebRepositoryImpl but I want to inject it with type of LocalRepository or WebRepository for more generalized approach.
Here is the module that I tried writing:
#InstallIn(ApplicationComponent::class)
#Module
object Module {
#Singleton
#Provides
fun provideWebRepository(): WebRepository {
return WebRepositoryImpl(mapper = WebMapper(), popularMovieApi = PopularMovieApi.getInstance())
}
#Singleton
#Provides
fun provideLocalRepository(): LocalRepository {
return LocalRepositoryImpl(mapper = LocalMapper(), // Here I can't really
// figure out how to get #Dao since it requires DB
// which requires context and etc
// which makes me think that I've got completely wrong approach to this)
}
}
My Module of LocalData:
#InstallIn(ApplicationComponent::class)
#Module
object LocalDataSourceModule {
#Singleton
#Provides
fun provideMainDatabase(#ApplicationContext context: Context): MainDatabase = MainDatabase.getInstance(context)
#Provides
fun providePopularMovieDao(mainDatabase: MainDatabase): PopularMovieDao = mainDatabase.popularMovieDao()
}
My Module of WebData:
#InstallIn(ApplicationComponent::class)
#Module
object RemoteDataSourceModule {
#Singleton
#Provides
fun providePopularMovieApi(): PopularMovieApi = PopularMovieApi.getInstance()
}
How do I properly Inject Implementations that I have (LocalRepositoryImpl & WebRepositoryImpl) while maintaining types of interfaces(LocalRepository & `WebRepository)??
Use #Binds. Instead of your object Module use following module:
#InstallIn(ApplicationComponent::class)
#Module
interface Module {
#Binds
fun bindWebRepository(repository: WebRepositoryImpl): WebRepository
#Binds
fun bindLocalRepository(repository: LocalRepositoryImpl): LocalRepository
}
It tells Dagger that if you need WebRepository dependency then it must provide WebRepositoryImpl and the same for LocalRepository and LocalRepositoryImpl.
What is the use case for #Binds vs #Provides annotation in Dagger2
Your Repositories
interface MainRepository {
...
}
interface LocalRepository {
...
}
interface WebRepository {
...
}
Implementation (No #Inject or #Singleton here!)
class MainRepositoryImpl constructor(
private val localRepository: LocalRepository,
private val webRepository: WebRepository
) : MainRepository {
...
}
class LocalRepositoryImpl constructor(
private val localMapper: LocalMapper
private val popularMovieDao: PopularMovieDao
) : LocalRepository {
...
}
class WebRepositoryImpl constructor(
private val webMapper: WebMapper,
private val popularMovieApi: PopularMovieApi
) : WebRepository {
...
}
Di.Module (Repository Module)
#Module
#InstallIn(ApplicationComponent::class)
object RepositoryModule {
#Singleton
#Provides
fun provideMainRepository(
localRepository: LocalRepository,
webRepository: WebRepository
): MainRepository = MainRepositoryImpl(localRepository, webRepository)
#Singleton
#Provides
fun provideLocalRepository(
localMapper: LocalMapper,
popularMovieDao: PopularMovieDao
): LocalRepository = LocalRepositoryImpl(localMapper, popularMovieDao)
#Singleton
#Provides
fun provideWebRepository(
webMapper: WebMapper,
popularMovieApi: PopularMovieApi
): WebRepository = WebRepositoryImpl(webMapper, popularMovieApi)
Try this and tell me if it worked. Since you have provided all your Repositories with #Provides, Dagger-Hilt knows how to create them. Are you using Room for your localDatabase? If yes, then creating the Database like you did might not be right. If you're not using Room that you should start to, as it makes your life easier.
Here is the right way to create a room database with dagger hilt:
Entity Module
#Entity(tableName = "exampleTableName")
data class ExampleEntity(
#PrimaryKey(autoGenerate = true)
val id: Int,
// ... whatever you need
val header: String = "",
val title: String = "",
val description: String = "",
)
ExampleDatabase
#Database(entities = [ExampleEntity::class], version = 1)
abstract class ExampleDatabase : RoomDatabase() {
abstract fun exampleDao(): ExampleDao
companion object {
const val DATABASE_NAME = "example_db"
}
}
DAO
#Dao
interface DocumentDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(exampleEntity: List<ExampleEntity>)
#Query("SELECT * FROM exampleTableName")
suspend fun getList(): List<ExampleEntity>
}
Room Di.Module
#Module
#InstallIn(ApplicationComponent::class)
object RoomModule {
#Singleton
#Provides
fun provideExampleDB(#ApplicationContext context: Context): ExampleDatabase = Room.databaseBuilder(
context,
ExampleDatabase::class.java,
ExampleDatabase.DATABASE_NAME,
).fallbackToDestructiveMigration().build()
#Singleton
#Provides
fun provideExampleDAO(exampleDatabase: ExampleDatabase): ExampleDao = exampleDatabase.exampleDao()
}
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
I'm getting this error with Dagger 2
C:\Users\Anon\AndroidStudioProjects\BarreChat1082\app\build\tmp\kapt3\stubs\debu
g\com\example\barrechat108\Dependencies\RepositoryComponent.java:8: error:
[Dagger/MissingBinding] com.example.barrechat108.MainActivity cannot be
provided without an #Inject constructor or an #Provides-annotated method.
public abstract interface RepositoryComponent {
^
com.example.barrechat108.MainActivity is injected at
com.example.barrechat108.Dependencies.RepositoryComponent.inject(com.example.barrechat108.MainActivity)
I thought I had set up the injection correctly, My modules are
#Module
class RepositoryModule {
//Provide singleton instance of our database for use by entire app.
//Set up in application's onCreate method
#Provides
#Singleton
fun providesDatabase(applicationContext: Application) : AppDatabase {
val db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "AppDatabase"
).build()
return db
}
#Provides
#Singleton
fun providesRepository(database: AppDatabase) : Repository {
val repos = Repository(database)
return repos
}
}
and
#Module
class AppModule (application: Application) {
val mApplication = application
#Provides
#Singleton
fun providesApplication() : Application {
return mApplication
}
}
The component used for injection to the MainActivity is
#Singleton
#Component(modules=[AppModule::class, RepositoryModule::class])
interface RepositoryComponent{
fun inject(activity: MainActivity)
}
Which is where the error is contained.
To build the instance of Dagger2, I extended from the Application class and in the manifest I told the app to use this code,
class BarreApp : Application() {
private lateinit var mRepositoryComponent: RepositoryComponent
override fun onCreate() {
super.onCreate()
mRepositoryComponent = DaggerRepositoryComponent.builder()
.appModule(AppModule(this))
.repositoryModule(RepositoryModule())
.build()
}
fun getRepositoryComponent(): RepositoryComponent {
return mRepositoryComponent
}
}
Finally, the activity I use the data injection in is the MainActivity. In this Main Activity, I create an instance of the Repository and send it to the view model from this start activity. I get rid of re-assignment by checking whether or not the repository was already set in the viewmodel. So each time the MainActiviy goes through on create, it opens the viewmodel and checks whether it has a repository and if it doesn't it pulls the repository that was injected on it and puts it into the ViewModel.
class MainActivity : AppCompatActivity() {
private lateinit var mPager: ViewPager
#Inject
private lateinit var mRepository: Repository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.barreBar))
//Get Repository Component using Dagger2
val mApp = application as BarreApp
mApp.getRepositoryComponent().inject(this)
//Get the main ViewModel
val barreViewModel = ViewModelProviders.of(this).get(BarreViewModel :: class.java)
//Add repository to ViewModel
barreViewModel.setRepository(mRepository)
}