I have a project which is primarily written in Java but I'm slowing moving to Kotlin for the new activities. This project applies Dagger2 and work perfectly when used with Java based activities. However, when I create Kotlin activity and try to inject, I get the following error.
LoginIDPresenter cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method. This type supports members injection but cannot be implicitly provided.
void inject(LoginIDActivity loginIDActivity);
com.maxis.mymaxis.ui.logindigitalid.LoginIDPresenter is injected at
com.maxis.mymaxis.ui.logindigitalid.LoginIDActivity.loginIDPresenter
com.maxis.mymaxis.ui.logindigitalid.LoginIDActivity is injected at
com.maxis.mymaxis.injection.component.ActivityComponent.inject(loginIDActivity)
Just to reaffirm again, when I do injection in my Java activities, it works flawlessly. Also, my Module and Component files are all in Java. Only when I create a Kotlin activity and try injecting there, I get the error.
LoginIDPresenter.kt
class LoginIDPresenter : BasePresenter<LoginIDMvpView>() {
lateinit var mContext : Context
#Inject
fun LoginIDPresenter(#ActivityContext context: Context){
mContext = context
}
override fun attachView(loginIDMvpView: LoginIDMvpView) {
super.attachView(loginIDMvpView)
}
override fun detachView() {
super.detachView()
mCompositeSubscription.clear()
}
}
LoginIDActivity.kt
class LoginIDActivity : BaseActivity(), LoginIDMvpView {
#Inject lateinit var loginIDPresenter : LoginIDPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loginIDPresenter.attachView(this)
}
override fun showErrorMessageDialog(errorObject: ErrorObject?) {
if (errorObject != null) {
Util.alertDialogBackToLandingPage(this, errorObject.getErrorUiMessage(), R.drawable.error_name)
}
}
override fun getLayoutResourceId(): Int {
return R.layout.activity_login_id
}
override fun injectActivity(component: ActivityComponent?) {
component?.inject(this)
}
override fun setToolBar() {
//no toolbar
}
}
ActivityComponent .java
#PerActivity
#Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
.
.
.
void inject(LoginIDActivity loginIDActivity);
.
.
.
}
ActivityModule.java
#Module
public class ActivityModule {
private Context mContext;
public ActivityModule(Context context) {
mContext = context;
}
#Provides
#ForActivity
Activity provideActivity() {
return (Activity) mContext;
}
#Provides
#ActivityContext
Context providesContext() {
return mContext;
}
}
Your intention is to use #Inject constructor, but you're not implementing it. Just provide a primary constructor for LoginIDPresenter:
class LoginIDPresenter #Inject constructor(
#ActivityContext val context: Context
) : BasePresenter<LoginIDMvpView>() {
override fun attachView(loginIDMvpView: LoginIDMvpView) {
super.attachView(loginIDMvpView)
}
override fun detachView() {
super.detachView()
mCompositeSubscription.clear()
}
}
Related
I'm quite new with Hilt injection. I started to migrate my whole project to DI.
It works almost everywhere, but I'm facing an issue when it comes to the leanback presenters. I don't know if it is related to the leanback stuff or juste Hilt
class LiveShowCardPresenter constructor(context: Context, listener: ShowCardViewListener, val hasVariableWidth: Boolean = false) : ShowCardPresenter(context, listener) {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
val viewholder = ViewHolder(LiveShowCardView(context, hasVariableWidth))
viewholder.prepareViewHolderForeground(context, settings.isATV)
return viewholder
}
...
}
abstract class ShowCardPresenter constructor(val context: Context, var listener: ShowCardViewListener?) : Presenter() {
#Inject lateinit var detailsRepository: DetailsRepository
#Inject lateinit var settings: BackendSettings
... }
#Singleton
class BackendSettings #Inject constructor(#ApplicationContext val context: Context) {
val isATV = true // TODO
The following error occurs
kotlin.UninitializedPropertyAccessException: lateinit property settings has not been initialized
at ch.netplus.tv.ui.presenters.ShowCardPresenter.getSettings(ShowCardPresenter.kt:43)
at ch.netplus.tv.ui.presenters.LiveShowCardPresenter.onCreateViewHolder(LiveShowCardPresenter.kt:23)
It means it crashes when the settings.isATV is called because the 'settings' var is not initialized at that time. What should I do to have the injection done on time ?
Thanks !
How do you inject dependencies into the LiveShowCardPresenter?
Since your abstract class(ShowCardPresenter) performs field injection, you somehow need to inject these fields when you create LiveShowCardPresenter. To perform those injections, you need to inject LiveShowCardPresenter as well. So, here is how it will look:
class LiveShowCardPresenter #Inject constructor(context: Context) : ShowCardPresenter(context) {
var hasVariableWidth: Boolean = false
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
val viewholder = ViewHolder(LiveShowCardView(context, hasVariableWidth))
viewholder.prepareViewHolderForeground(context, settings.isATV)
return viewholder
}
...
}
abstract class ShowCardPresenter constructor(val context: Context) : Presenter() {
var listener: ShowCardViewListener? = null
#Inject lateinit var detailsRepository: DetailsRepository
#Inject lateinit var settings: BackendSettings
... }
YourFragment.kt
#AndroidEntryPoint
class YourFragment: BrowseFragment() {
#Inject
lateinit var liveShowCardPresenterProvider: Provider<LiveShowCardPresenter>
...
private void setupUIElements() {
...
//new header
setHeaderPresenterSelector(object : PresenterSelector() {
override fun getPresenter(o: Any): Presenter {
// Everytime when [liveShowCardPresenterProvider.get()] is called - new instance will be created
val presenter = liveShowCardPresenterProvider.get().apply {
// You can set your parameters here
// hasVariableWidth = true
// listener = yourCustomListener
}
return presenter;
}
});
}
...
If you need a single instance of the LiveShowCardPresenter in your fragment, you can perform a field injection on it without the Provider.
Alternativerly, you can inject all of your dependencies in the Fragment and pass them to the LiveShowCardPresenter constructor.
You need to set the inject method in BackendSettings
Like:
class BackendSettings #Inject constructor() {
}
Ok, I walk through your codes step by step:
Your LiveShowCardPresenter class should be changed as below:
class LiveShowCardPresenter #Inject constructor(
#ApplicationContext context: Context,
listener: ShowCardViewListener
) : ShowCardPresenter(context, listener) {
var hasVariableWidth = false
//your codes ...
}
As can be seen, #Inject is added before the constructor and also #ApplicationContext is added before context to provide context through the Hilt. also, hasVariableWidth is set outside of the constructor, if you don't like you can put it inside the constructor and provide it through the module and #Provide annotation. Now we should provide showCardViewListener. as I don't have access to your codes I provide it in a simple way.
#Module
#InstallIn(SingletonComponent::class)
abstract class ShowCardListenerModule {
#Binds
#Singleton
abstract fun bindShowCardViewListener(showCardViewListenerImpl: ShowCardViewListenerImpl) : ShowCardViewListener
}
ShowCardPresenter class has no changes. Finally, your BackendSettings class is changed like below:
#Singleton
class BackendSettings #Inject constructor() {
val isATV = true
//your codes ...
}
#Inject is added and #Singleton is removed because doesn't need to it. I ran the above codes and it works without any problem.
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 am trying to inject Context using Dagger 2. I have seen many other questions on this website related to this but still problem is not solved.
AppComponent.kt:
#Singleton
#Component(
modules = [
AppModule::class
]
)
interface AppComponent {
fun context(): Context
fun inject(context: Context)
}
AppModule.kt:
#Module
class AppModule(private val context: Context) {
#Provides
#Singleton
fun providesApplicationContext(): Context = context
}
MainApp.kt:
class MainApp : Application() {
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
appComponent = initDagger()
appComponent.inject(this)
}
private fun initDagger() = DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
}
Manager.kt: (Class where I want to inject Context)
class Manager {
#Inject
lateinit var context: Context
fun foo() {
context.resources
}
}
However, I am getting following error at context.resources when Manager().foo() is called from anywhere, say in onCreate() function of MainActivity:
kotlin.UninitializedPropertyAccessException: lateinit property context has not been initialized
How to fix this? Why is Dagger not injecting Context?
Try to use constructor injection
class Manager #Inject constructor(val context: Context) {
fun foo() {
context.resources
}
}
And then in your Activity/Fragment use manager like below:
#Inject lateinit var manager: Manager
I constantly get kotlin.UninitializedPropertyAccessException: lateinit property xxx has not been initialized in my Mockito test. But the app works just fine. Note: I don't want to inject presenter into activity. Thanks in advance!
Here's my Activity:
class CreateAccountActivity : AppCompatActivity(), CreateAccountView {
private var presenter: CreateAccountPresenter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_create_account)
presenter = CreateAccountPresenter()
((application) as CariumApp).getDaggerComponent().inject(presenter!!)
presenter?.attachView(this)
}
And here's my Presenter:
class CreateAccountPresenter {
private var view: CreateAccountView? = null
#Inject
lateinit var dataManager: DataManager
fun attachView(view: CreateAccountView) {
this.view = view
dataManager.getServiceDocuments(true, object : GetServiceDocumentsListener {
// ...
})
}
Here's my DataManager:
interface DataManager {
fun getServiceDocuments(latest: Boolean, listener: GetServiceDocumentsListener)
}
and AppDataManager:
Singleton
class AppDataManager #Inject constructor(context: Context) : DataManager {
// ...
}
and finally my test that's failing:
class CreateAccountPresenterTest {
val mockDataManager: DataManager = mock()
val mockCreateAccountView: CreateAccountView = mock()
private val createAccountPresenter = CreateAccountPresenter()
#Test
fun getServiceDocuments() {
doAnswer {
val args = it.arguments
(args[1] as GetServiceDocumentsListener).onError()
null
}.`when`(mockDataManager).getServiceDocuments(Mockito.anyBoolean(), anyOrNull())
createAccountPresenter.attachView(mockCreateAccountView)
verify(mockCreateAccountView).hideLoadingDialog()
}
}
gradle file:
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.22.0'
testImplementation "org.mockito:mockito-inline:2.22.0"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0-RC1"
implementation 'com.google.dagger:dagger:2.16'
kapt 'com.google.dagger:dagger-compiler:2.16'
My module class:
#Module
open class MyModule(private var context: Context) {
#Provides
open fun provideContext(): Context {
return context
}
#Provides
#Singleton
internal fun provideDataManager(appDataManager: AppDataManager): DataManager {
return appDataManager
}
}
Actual error is kotlin.UninitializedPropertyAccessException: lateinit property dataManager has not been initialized
You are not assigning your mock to the field. Assign it in your test method. Before calling attachView()
createAccountPresenter.dataManager = mockDataManager
Where do you have DataManager #Provides method? Dagger recognizes #Inject constructor inside AppDataManager but cannot recognize it as interface. Create Module for Dagger that is abstract and uses #Binds
https://proandroiddev.com/dagger-2-annotations-binds-contributesandroidinjector-a09e6a57758f
I am using Dagger 2 and have it working however I now need access to the Android Application Context.
Its not clear to me how to inject and get access to the context. I have tried to do this as follows:
#Module
public class MainActivityModule {
private final Context context;
MainActivityModule(Context context) {
this.context = context;
}
#Provides #Singleton
Context provideContext() {
return context;
}
}
However this results in the following exception:
java.lang.RuntimeException: Unable to create application : java.lang.IllegalStateException: mainActivityModule must be set
If I inspect the Dagger generated code this exception is raised here:
public Graph build() {
if (mainActivityModule == null) {
throw new IllegalStateException("mainActivityModule must be set");
}
return new DaggerGraph(this);
}
I am not sure if this is the correct way to get Context injected - any help will be greatly appreciated.
#Module
public class MainActivityModule {
private final Context context;
public MainActivityModule (Context context) {
this.context = context;
}
#Provides //scope is not necessary for parameters stored within the module
public Context context() {
return context;
}
}
#Component(modules={MainActivityModule.class})
#Singleton
public interface MainActivityComponent {
Context context();
void inject(MainActivity mainActivity);
}
And then
MainActivityComponent mainActivityComponent = DaggerMainActivityComponent.builder()
.mainActivityModule(new MainActivityModule(MainActivity.this))
.build();
It took me a while to find a proper solution, so thought it might save some time for others, as far as I could gather this is the preferred solution with the current Dagger version (2.22.1).
In the following example I need the Application's Context to create a RoomDatabase (happens in StoreModule).
Please if you see any errors or mistakes let me know so I'll learn as well :)
Component:
// We only need to scope with #Singleton because in StoreModule we use #Singleton
// you should use the scope you actually need
// read more here https://google.github.io/dagger/api/latest/dagger/Component.html
#Singleton
#Component(modules = { AndroidInjectionModule.class, AppModule.class, StoreModule.class })
public interface AwareAppComponent extends AndroidInjector<App> {
// This tells Dagger to create a factory which allows passing
// in the App (see usage in App implementation below)
#Component.Factory
interface Factory extends AndroidInjector.Factory<App> {
}
}
AppModule:
#Module
public abstract class AppModule {
// This tell Dagger to use App instance when required to inject Application
// see more details here: https://google.github.io/dagger/api/2.22.1/dagger/Binds.html
#Binds
abstract Application application(App app);
}
StoreModule:
#Module
public class StoreModule {
private static final String DB_NAME = "aware_db";
// App will be injected here by Dagger
// Dagger knows that App instance will fit here based on the #Binds in the AppModule
#Singleton
#Provides
public AppDatabase provideAppDatabase(Application awareApp) {
return Room
.databaseBuilder(awareApp.getApplicationContext(), AppDatabase.class, DB_NAME)
.build();
}
}
App:
public class App extends Application implements HasActivityInjector {
#Inject
DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
#Override
public void onCreate() {
super.onCreate();
// Using the generated factory we can pass the App to the create(...) method
DaggerAwareAppComponent.factory().create(this).inject(this);
}
#Override
public AndroidInjector<Activity> activityInjector() {
return dispatchingAndroidInjector;
}
}
I have read this article and it was very helpful.
https://medium.com/tompee/android-dependency-injection-using-dagger-2-530aa21961b4
Sample code.
Update: I removed from AppComponent.kt these lines because are not necessaries
fun context(): Context
fun applicationContext(): Application
AppComponent.kt
#Singleton
#Component(
modules = [
NetworkModule::class,
AppModule::class
]
)
interface AppComponent {
fun inject(viewModel: LoginUserViewModel)
}
AppModule.kt
#Module
class AppModule(private val application: Application) {
#Provides
#Singleton
fun providesApplication(): Application = application
#Provides
#Singleton
fun providesApplicationContext(): Context = application
#Singleton
#Provides
fun providesNetworkConnectivityHelper(): NetworkConnectivityHelper{
return NetworkConnectivityHelper(application.applicationContext)
}
}
NetworkConnectivityHelper.kt
And only added #Inject constructor to pass the Context
class NetworkConnectivityHelper #Inject constructor(context: Context) {
private val connectivityManager =
context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
#Suppress("DEPRECATION")
fun isNetworkAvailable(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val nc = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
nc != null
&& nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}
val networkInfo = connectivityManager.activeNetworkInfo
return networkInfo != null && networkInfo.isConnected
}
}
App class.kt
class App : Application() {
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
this.appComponent = this.initDagger()
}
private fun initDagger() = DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
}
Finally in my Activity I injected my helper
#Inject lateinit var networkConnectivity: NetworkConnectivityHelper
And YEI! it works for me.
Was not correctly building the Application component, needed to pass in the Application. This Dagger 2 example perfectly shows how to do this: https://github.com/google/dagger/tree/master/examples/android-simple/src/main/java/com/example/dagger/simple
Update:
Working link: https://github.com/yongjhih/dagger2-sample/tree/master/examples/android-simple/src/main/java/com/example/dagger/simple
probably we could inject the context as shown below:
the application component
#Component(
modules = [
(ApplicationModule::class),
(AndroidSupportInjectionModule::class),
(UiBindingModule::class)
]
)
interface ApplicationComponent : AndroidInjector<AndroidApplication> {
override fun inject(application: AndroidApplication)
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: AndroidApplication): Builder
#BindsInstance
fun context(context: Context): Builder
fun build(): ApplicationComponent
}
}
the custom application extending dagger application
class AndroidApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerApplicationComponent.builder().application(this).context(this).build()
}
}
Example ApplicationModule
#Module
abstract class ApplicationModule {
/**
* Binds a background thread executor, which executes threads from a thread pool
* #param jobExecutor
* #return
*/
#Binds
internal abstract fun provideThreadExecutor(jobExecutor: JobExecutor): ThreadExecutor
/**
* Binds main ui looper thread
* #param uiThread
* #return
*/
#Binds
internal abstract fun providePostExecutionThread(uiThread: UIThread): PostExecutionThread
}
Example UI BindingModule
#Module
abstract class UiBindingModule {
#ContributesAndroidInjector(modules = [(MainActivityModule::class)])
internal abstract fun mainActivity(): MainActivity
#ContributesAndroidInjector(modules = [(MapFragmentModule::class)])
internal abstract fun mapFragment(): MapFragment
}
#Singleton
#Component(modules = [YourModule::class, ThatOtherModule::class])
interface ApplicationComponent {
#Component.Builder
interface Builder {
#BindsInstance fun applicationContext(applicationContext: Context): Builder
fun build(): ApplicationComponent
}
}
class YourApplication : Application() {
val component: ApplicationComponent by lazy {
DaggerApplicationComponent.builder()
.applicationContext(applicationContext)
.build()
}
}
Use the #BindInstance to declare a abstract function that provides the context dependency. i.e #BindsInstance fun applicationContext(applicationContext: Context): Builder
set the context using .applicationContext(applicationContext)