New to kotlin and trying to migrate over a small Android project written in Java. Don't know what to make of this error:
Caused by: java.lang.IllegalStateException:
com.mydomain.example.dagger.BusModule must be set
> at com.mydomain.example.dagger.DaggerAppComponent$Builder.build(DaggerAppComponent.java:87)
> at com.mydomain.example.MyApplication.buildComponent(MyApplication.kt:29)
> at com.mydomain.example.MyApplication.onCreate(MyApplication.kt:17)
> at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1118)
> at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5791)
Here is the BusModule class:
#Module
object BusModule {
const val PROVIDER_FRAGMENT_SELECTION = "fragment_selection"
#Provides
#Singleton
#Named(PROVIDER_FRAGMENT_SELECTION)
fun provideNewFragmentSelection(): PublishSubject<String> {
return PublishSubject.create()
}
}
And here is the AppComponent class:
#Component(modules = [(AppModule::class), (DataModule::class), (BusModule::class)])
#Singleton
interface AppComponent {
#get:Named(BusModule.PROVIDER_FRAGMENT_SELECTION)
val selectedFragmentName: PublishSubject<String>
fun inject(mainActivity: MainActivity)
}
And finally, the application class:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
appComponent = buildComponent()
}
private fun buildComponent(): AppComponent {
return DaggerAppComponent.builder().build()
}
companion object {
var appComponent: AppComponent? = null
private set
}
}
Can't figure out what the error message means. How is the BusModule not 'set'? Thanks!
BusModule should be a class instead of an object and const val PROVIDER_FRAGMENT_SELECTION should be moved to companion object of the class. So the module becomes:
#Module
class BusModule {
companion object {
const val PROVIDER_FRAGMENT_SELECTION = "fragment_selection"
}
#Provides
#Singleton
#Named(PROVIDER_FRAGMENT_SELECTION)
fun provideNewFragmentSelection(): PublishSubject<String> {
return PublishSubject.create()
}
}
I think all you would have had to do was add a #JvmStatic annotation.
#Module
object BusModule {
#Provides
#Singleton
#Named(PROVIDER_FRAGMENT_SELECTION)
#JvmStatic
fun provideNewFragmentSelection(): PublishSubject<String> {
return PublishSubject.create()
}
Related
I'm new to hilt. So i want to try dependency injection with hilt on my project which use MVVM architecture.
The structure look like this: JsonHelper -> RemoteDataSource -> Repository -> ViewModel.
The problems occur when i try to inject my DI on RemoteDataSource and Repository since these classes are singleton class and have a private constructor.
The error codes look like this
..location\RemoteDataSource.java:40: error: Dagger does not support injection into Kotlin objects
public static final class Companion {
..location\Repository.java:30: error: Dagger does not support injection into Kotlin objects
public static final class Companion {
And these are my RemoteDataSource and Repository codes, i have tried injecting it on the constructor but it says Dagger can't inject on private constructors so then i tried to inject it on the function but still didn't work
RemoteDataSource.kt
#Singleton
class RemoteDataSource private constructor(private val jsonHelper: JsonHelper) {
companion object {
#Volatile
private var instance: RemoteDataSource? = null
#Inject
fun getInstance(jsonHelper: JsonHelper): RemoteDataSource =
instance ?: synchronized(this) {
instance ?: RemoteDataSource(jsonHelper).apply { instance = this }
}
}
fun getAllRemoteMovies(moviesCallback: LoadMoviesCallback) {
moviesCallback.onAllMoviesReceived(jsonHelper.loadRemoteMovies())
}
fun getAllRemoteTVShows(tvshowCallback: LoadTVShowCallback) {
tvshowCallback.onAllTVShowsReceived(jsonHelper.loadRemoteTVShows())
}
interface LoadMoviesCallback {
fun onAllMoviesReceived(moviesResponses: ArrayList<MovieItem>)
}
interface LoadTVShowCallback {
fun onAllTVShowsReceived(tvshowResponses: ArrayList<TVShowItem>)
}
}
Repository.kt
#Singleton
class Repository private constructor(private val remoteDataSource: RemoteDataSource) : DataSource {
companion object {
#Volatile
private var instance: Repository? = null
#Inject
fun getInstance(remoteDataSource: RemoteDataSource): Repository =
instance ?: synchronized(this) {
instance ?: Repository(remoteDataSource).apply { instance = this }
}
}
override fun getAllRemoteMovies(): LiveData<ArrayList<MovieItem>> {
val remoteMoviesResult = MutableLiveData<ArrayList<MovieItem>>()
remoteDataSource.getAllRemoteMovies(object : RemoteDataSource.LoadMoviesCallback {
override fun onAllMoviesReceived(moviesResponses: ArrayList<MovieItem>) {
remoteMoviesResult.value = moviesResponses
}
})
return remoteMoviesResult
}
override fun getAllRemoteTVShows(): LiveData<ArrayList<TVShowItem>> {
val remoteTVShowsResult = MutableLiveData<ArrayList<TVShowItem>>()
remoteDataSource.getAllRemoteTVShows(object : RemoteDataSource.LoadTVShowCallback {
override fun onAllTVShowsReceived(tvshowResponses: ArrayList<TVShowItem>) {
remoteTVShowsResult.value = tvshowResponses
}
})
return remoteTVShowsResult
}
}
And this is my injection module
RemoteDataSourceModule.kt
#Module
#InstallIn(ActivityComponent::class)
object RemoteDataSourceModule {
#Singleton
#Provides
fun provideJsonHelper(context: Context): JsonHelper {
return JsonHelper(context)
}
#Singleton
#Provides
fun provideRemoteDataSource(jsonHelper: JsonHelper): RemoteDataSource {
return RemoteDataSource.getInstance(jsonHelper)
}
#Singleton
#Provides
fun provideRepository(remoteDataSource: RemoteDataSource): Repository {
return Repository.getInstance(remoteDataSource)
}
}
So how can i solve this problem without changing the class constructor to public?
#Singleton annotation is enough to notify that the class is a singleton class, so i just remove the companion object and changes private constructor with a public constructor so the code will look like this:
#Singleton
class RemoteDataSource #Inject constructor(private val jsonHelper: JsonHelper) {
// Your codes
}
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()
}
#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.
In the following code, the field serviceUtil is not being injected by Dagger:
AppController.kt
class App : Application() {
#Inject
lateinit var serviceUtil: ServiceUtil
init {
DaggerAppComponent
.builder()
.build()
.inject(this)
}
override fun onCreate() {
super.onCreate()
context = this
}
fun startService() {
serviceUtil.startService()
}
companion object {
lateinit var context: App
}
}
AppComponent.kt
#Singleton
#Component(modules = [(ServiceUtilModule::class)])
interface AppComponent {
fun inject(app: Application)
}
ServiceUtilModule.kt
#Module
class ServiceUtilModule {
#Provides
fun provideServiceUtil() : ServiceUtil {
return ServiceUtil()
}
}
From my main activity I call:
App.context.startService()
You mistyped here
#Singleton
#Component(modules = [(ServiceUtilModule::class)])
interface AppComponent {
fun inject(app: Application)
}
You should pass you App class as argument, not the basic one.
#Singleton
#Component(modules = [(ServiceUtilModule::class)])
interface AppComponent {
fun inject(app: App)
}
There have been many other similar questions, but none of the answers have been applicable to my code. I cannot figure out what I have done wrong.
First I have a NetworkModule that is used as a module for the ApplicationComponent:
#Module
open class NetworkModule {
companion object {
private val BASE = "http://www.example.com/"
}
#Provides #ApplicationScope
fun provideClient(): OkHttpClient = OkHttpClient()
#Provides #ApplicationScope
fun provideMoshi(): Moshi {
return Moshi.Builder().add(InstantAdapter).add(UriAdapter).build()
}
#Provides #ApplicationScope
fun provideRetrofit(client: OkHttpClient, moshi: Moshi): Retrofit {
return Retrofit.Builder().client(client).baseUrl(BASE)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}
#Provides #ApplicationScope
fun provideArticleService(retrofit: Retrofit): ArticleService {
return retrofit.create(ArticleService::class.java)
}
}
#ApplicationScope #Component(modules = arrayOf(ContextModule::class, RealmModule::class, NetworkModule::class))
interface ApplicationComponent {}
Then the ApplicationComponent is built in my Application class:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
AndroidThreeTen.init(this)
plantLog()
drawDagger()
}
private fun drawDagger() {
Injector.initializeApplicationComponent(this)
}
// ...
}
object Injector {
lateinit var applicationComponent: ApplicationComponent
private set
fun initializeApplicationComponent(context: Context) {
applicationComponent = DaggerApplicationComponent.builder()
.contextModule(ContextModule(context))
.networkModule(NetworkModule())
.realmModule(RealmModule())
.build()
}
// ...
}
Then I have an ActivityModule that is used in the ActivityComponent (which has ApplicationComponent as a dependency):
#Module
open class ActivityModule(private val activity: AppCompatActivity) {
#Provides #ActivityScope #ActivityContext
fun provideContext(): Context = activity
#Provides #ActivityScope
fun provideFragmentManager(): FragmentManager = activity.supportFragmentManager
}
#ActivityScope #Component(dependencies = arrayOf(ApplicationComponent::class), modules = arrayOf(ActivityModule::class))
interface ActivityComponent {
fun inject(activity: MainActivity)
}
Finally, I create a new ActivityComponent in the MainActivity and #Inject the ArticleService:
class MainActivity : AppCompatActivity() {
#Inject lateinit var service: ArticleService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerActivityComponent.builder()
.applicationComponent(Injector.applicationComponent)
.activityModule(ActivityModule(this))
.build().inject(this)
service.getNewsArticles()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.subscribe(
{ response -> onNext(response) },
{ error -> onError(error) })
}
// ...
}
But when I try to build I get the following error, even though I believe the provideArticleService() function in NetworkModule is annotated correctly:
ArticleService cannot be provided without an #Provides- or
#Produces-annotated method.
You're missing the provision methods to inherit to your Activity scoped component. Either use subcomponents instead of component dependency, or define the provision methods in your application component.
#ApplicationScope #Component(modules = arrayOf(ContextModule::class, RealmModule::class, NetworkModule::class))
interface ApplicationComponent {
ArticleService articleService();
}