Hilt particular feature dependency - android

#Module(includes = [AuthModule.ViewModel::class, AuthModule.UseCase::class, AuthModule.Api::class, AuthModule.Repository::class, AuthModule.Interactor::class])
#InstallIn(ActivityComponent::class)
class AuthModule {
#Module
#InstallIn(ActivityComponent::class)
class ViewModel() {
#Provides
fun providesRegisterViewModel(activity:FragmentActivity): RegisterViewModel {
val registerViewModel = ViewModelProvider(activity).get(RegisterViewModel::class.java)
return registerViewModel
}
}
#Module
#InstallIn(ActivityComponent::class)
class UseCase {
#Provides
fun providesRegisterationUseCase(registrationApi: RegistrationApi, userRepository: UserRepository): RegistrationUsecase {
val registrationUsecase = RegistrationUsecase(registrationApi, userRepository)
return registrationUsecase
}
}
#Module
#InstallIn(ActivityComponent::class)
class Api {
#Provides
fun provideRegistrationApi(retrofit: Retrofit): RegistrationApi {
val registrationService = retrofit.create(RegistrationService::class.java)
return RegistrationApi(registrationService)
}
}
#Module
#InstallIn(ActivityComponent::class)
class Repository {
#Provides
fun provideUserRepository(realm: Realm): UserRepository = UserRepository(realm)
}
#Module
#InstallIn(ActivityComponent::class)
class Interactor {
#Provides
fun provideSignInInteractor(registerViewModel: RegisterViewModel): SignInInteractor = SignInInteractor(registerViewModel)
}
}
#Component(modules = [AuthModule::class])
interface AuthComponent {
fun inject(signInFragment: SignInFragment)
#Component.Builder
interface Builder {
fun activity(#BindsInstance fragmentActivity: FragmentActivity): Builder
fun build(): AuthComponent
}
}
class SignInFragment : Fragment() {
#Inject lateinit var signInInteractor:SignInInteractor
private lateinit var fragmentSignInBinding: FragmentSignInBinding
override fun onCreate(savedInstanceState: Bundle?) {
DaggerAuthModuleComponent.builder()
.activity(requireActivity()).build().inject(this)
super.onCreate(savedInstanceState)
}
i was trying to inject it in other fragments but it does not work. my aim is to make feature wise component so that only these fragment related to particular feature gets the dependency.
Hilt requires every module to use install annotation if i want hilt to install to my custom component is it possible.

Related

dagger 2 Subcomponent

while Dagger implementation i have below error. please help me resolve it.
dagger/component/AppComponent.java:10: error: [com.example.testproject.app.dagger.component.PostLoginComponent.inject(com.example.testproject.view.photo.PhotoListActivity)] com.example.testproject.data.UserStore cannot be provided without an #Inject constructor or from an #Provides-annotated method.
My understanding - I have created AppComponent and AppModule which is parent for PostLoginComponent and PostLoginModule (as they are subcomponent).
photoListViewModel is injected in PhotoListActivity. photoListViewModel is provide by postLoginComponent and PostLoginModule.Now photoListViewModel need UserStore as dependency which is present in AppModule.
So as per my understanding all object from parent module are available for child module.
in this case UserStore from AppModule should be available for PhotoListViewModel in PostLoginModule.
But as per error UserStore is not provided.
#Module
abstract class AppModule {
#Module
companion object {
#Provides
fun provideUserStore(context: Context): UserStore {
return UserStore(context)
}
}
}
#Component(modules = [AppModule::class])
interface AppComponent {
fun postLoginComponent(): PostLoginComponent.Builder
#Component.Builder
interface Builder {
#BindsInstance
fun bindData(context: Context): Builder
fun build(): AppComponent
}
}
#Module
abstract class PostLoginModule {
#Binds
#IntoMap
#ViewModelKey(PhotoListViewModel::class)
abstract fun bindViewModel(viewModel: PhotoListViewModel): ViewModel
#MustBeDocumented
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
}
#Subcomponent(modules = [PostLoginModule::class])
interface PostLoginComponent {
fun inject(activity: PhotoListActivity)
#Subcomponent.Builder
interface Builder {
fun build(): PostLoginComponent
}
}
class PhotoListActivity() : PostLoginActivity() {
var adapter = PhotoListAdapter(
mutableListOf(
PhotoItem("", "akash"),
PhotoItem("", "akash"),
PhotoItem("", "akash"),
PhotoItem("", "akash")
)
)
#Inject
lateinit var factory: ViewModelFactory
private val photoListViewModel: PhotoListViewModel by lazy {
ViewModelProvider(this, factory).get(PhotoListViewModel::class.java)
}
override fun setLayoutId(): Int? {
return R.layout.activity_photo_list
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
TestApp.get(this).appComponent().postLoginComponent().build().inject(this)
initView()
}
private fun initView() {
textView.setOnClickListener {
adapter.clearData()
}
rvPhotoList.initRecyclerView {
it.layoutManager = LinearLayoutManager(this)
it.adapter = adapter
}
adapter.setItemClickListener(OnRecyclerViewOnItemClickListener { parent, view, position -> })
rvPhotoList.addErrorView(EmptyErrorView(this))
getPhotoList()
}
private fun getPhotoList() {
adapter.addItems(photoListViewModel.getPhotoList())
}
}
class PhotoListViewModel #Inject constructor(userStore: UserStore) : BaseViewModel() {
fun getPhotoList(): List<PhotoItem> {
return mutableListOf(
PhotoItem("", "aher"),
PhotoItem("", "aher"),
PhotoItem("", "aher"),
PhotoItem("", "aher")
)
}
}

Hilt injecting child class as parent type

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()
}

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

Implementing a simple Dagger2 sample

I'm new using Dagger2 (I always used Koin) and I'm trying to implement a simple sample but I don't really know what I'm missing. This is what I got so far.
app.gradle:
ext.daggerVersion = '2.23.2'
implementation "com.google.dagger:dagger:$daggerVersion"
implementation "com.google.dagger:dagger-android-support:$daggerVersion"
kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
AppModule.kt:
#Module
class AppModule {
#Provides
#Singleton
fun provideApplication(app: App): Application = app
#Provides
#Singleton
fun provideTestOperator(testOperator: TestOperator) = testOperator
#Provides
#Singleton
fun provideTestClass(testClass: TestClass) = testClass
}
AppComponent.kt:
#Singleton
#Component(modules = [
AndroidInjectionModule::class,
AppModule::class
])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: App): Builder
fun build(): AppComponent
}
}
TestClass.kt & TestOperator.kt in the same file:
class TestClass #Inject constructor(private val testOperator: TestOperator) {
fun getRandomValueFromCTest(): Int = testOperator.generateRandomNumber()
}
class TestOperator #Inject constructor() {
fun generateRandomNumber(): Int = Random.nextInt()
}
App.kt:
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().application(this#App).build()
}
}
MainActivity.kt:
class MainActivity : AppCompatActivity() {
#Inject
lateinit var testClass: TestClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
val x = testClass.getRandomValueFromCTest()
}
}
Error: testClass == null
AppModule.kt: Provide the application context. No need to write #singleton #provides for your Test* classes (will see why)
#Module
class AppModule {
#Provides
#Singleton
fun provideApplication(app: App): Context = app.applicationContext
}
AppComponent.kt: #Component.Builder is deprecated IIRC. Use #Component.Factory. And replace AndroidInjectionModule::class with AndroidSupportInjectionModule::class since we are using dagger-android-support and android's *Compat* stuff. Refer a new module here called ActivityModule::class.
#Singleton
#Component(modules = [
ActivityModule::class
AndroidSupportInjectionModule::class,
AppModule::class
])
interface AppComponent : AndroidInjector<App> {
#Component.Factory
abstract class Factory : AndroidInjector.Factory<App>
}
TestClass.kt & TestOperator.kt: Since you were providing singletons by writing #singleton and #provides method, I assume you want them to be singletons. Just annotate the class definition with #Singleton and dagger will take care of it. No need to write #Provides methods.
#Singleton
class TestClass #Inject constructor(private val testOperator: TestOperator) {
fun getRandomValueFromCTest(): Int = testOperator.generateRandomNumber()
}
#Singleton
class TestOperator #Inject constructor() {
fun generateRandomNumber(): Int = Random.nextInt()
}
App.kt: Using factory instead of builder since #Component.Builder is deprecated.
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.factory().create(this)
}
}
ActivityModule.kt: Provide a module to dagger to create your activities.
#Module
interface ActivityModule {
#ContributesAndroidInjector
fun provideMainActivity(): MainActivity
}
MainActivity.kt: Finally, extend from DaggerAppCompatActivity.
class MainActivity : DaggerAppCompatActivity() {
#Inject
lateinit var testClass: TestClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
val x = testClass.getRandomValueFromCTest()
}
}
I believe this should run without issues. For more reference you could look into this sample and the new simpler docs at dagger.dev/android
You are missing the actual injection call.
class MainActivity : AppCompatActivity() {
#Inject
lateinit var testClass: TestClass
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
MainActivity should extends DaggerActivity, not AppCompatActivity

Dagger not initizalize var

I have problems when starting Dagger in Android project with Kotlin.
This estructure is the next one
Dagger is included in an Android module that is called by the client application
MagicBox.kt
interface MagicBox {
fun getDate(): Long?
}
MagicBoxImpl.kt
class MagicBoxImpl (): MagicBox{
var date: Long = Date().time
override fun getDate(): Long {
return date
}
}
MainModule.kt
#Module
class MainModule (private val app: Application) {
#Provides
#Singleton
fun provideMagicBox(): MagicBox {
return MagicBoxImpl()
}
}
MainComponent.kt
#Singleton
#Component(modules = [MainModule::class, PresenterModule::class])
interface MainComponent{
fun inject(target: Activity)
}
Application.kt
class Application: Application() {
lateinit var mainComponent: MainComponent
override fun onCreate() {
super.onCreate()
mainComponent = initDagger(this)
}
private fun initDagger(app: Application): MainComponent =
DaggerMainComponent.builder()
.mainModule(MainModule(app))
.build()
}
MainActivity.kt
#Inject
lateinit var magicBox: MagicBox
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
(application as ClientSdk).mainComponent.inject(this)
tvDaggerTest = findViewById(R.id.tvDaggerTest)
tvDaggerTest!!.text = magicBox.getDate().toString()
}
Get the following error
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property magicBox has not been initialized
fun inject(target: Activity) should be fun inject(target: MainActivity)
Also for better Dagger usage, the following should be:
#Module
abstract class MainModule {
#Binds
abstract fun magicBox(impl: MagicBoxImpl): MagicBox
}
and
#Singleton class MagicBoxImpl #Inject constructor(): MagicBox {

Categories

Resources