Unable to inject instance variable using dagger - android

I am creating the new class manually without injecting through dagger, but all the instances variable in that class should be injected through dagger.
For Example:
class TestingClass constructor() {
#Inject
lateinit var test: Test
fun testing() {
test.doSomeThing()
}
}
class Test #Inject constructor() {
fun doSomeThing() {
}
}
TestingClass().testing()
I am trying to achieve the above scenario, but it throws error
kotlin.UninitializedPropertyAccessException: lateinit property test has not been initialized
Is it possible to achieve this scenario, Can anyone help me with this?

Related

Dagger hilt injecting into activity results in UninitializedPropertyAccessException error

I am trying to inject a class into an activity using dagger hilt using a Module. I've looked through tutorials and countless SO posts. I cannot tell what I'm doing wrong.
I have a DataStoreManger class that i'm trying to use in an activity.
class DataStoreManager (#ApplicationContext appContext: Context) {...}
I have an AppModule that provides a DataStoreManager.
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
#Singleton
fun provideDataStoreManager(#ApplicationContext appContext: Context):
DataStoreManager = DataStoreManager(appContext)
}
Then I'm trying to use DataStoreManager in MainActivity.
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
#Inject lateinit var dataStoreManager: DataStoreManager
private val userPreferencesFlow = dataStoreManager.userPreferencesFlow
}
This results in an uninitialized property access exception
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property
dataStoreManager has not been initialized
When you use
#Inject lateinit var dataStoreManager
The dataStoreManager is only actually injected during the call to super.onCreate().
However, when you create a property such as
private val userPreferencesFlow = dataStoreManager.userPreferencesFlow
This property is created immediately as part of the construction of your MainActivity class - it doesn't wait until onCreate() runs, hence the error you are receiving.
Generally, the easiest way to avoid this is by only access the userPreferencesFlow from your injected manager when you actually want to collect it, which is presumably after super.onCreate().
However, if you want to still create your flow as a member variable at the activity level, you can use by lazy:
private val userPreferencesFlow by lazy {
dataStoreManager.userPreferencesFlow
}
The lazy block will only be executed lazily - e.g., the first time you actually use the member variable, thus ensuring that your #Inject variable has actually been injected.

Hilt field injection in the super Fragment or ViewModel

I'm using Dagger-Hilt for dependency injection in my Android project, now I have this situation where I have a base abstract Fragment
BaseViewModel.kt
abstract class BaseViewModel constructor(
val api: FakeApi,
) : ViewModel() {
//...
}
Here, I have a dependency which is FakeApi. What I'm trying to do is to inject the FakeApi into the BaseViewModel to be available in the BaseViewModel and all its children.
The first approach I tried is using the constructor injection and inject it to the child and pass it to the super using the constructor.
TaskViewModel.kt
#HiltViewModel
class TaskViewModel #Inject constructor(
api: FakeApi
) : BaseViewModel(api){
}
This approach works fine, but I don't need to pass the dependency from the child to the super class, I need the FakeApi to be automatically injected in the BaseViewModel without having to pass it as I have three levels of abstraction (There is another class inheriting from the TaskViewModel) So I have to pass it two times.
The second approach was to use the field injection as follows
BaseViewModel.kt
abstract class BaseViewModel: ViewModel() {
#Inject
lateinit var api: FakeApi
//...
}
TaskViewModel.kt
#HiltViewModel
class TaskViewModel #Inject constructor(): BaseViewModel() {
}
This approach didn't work for me and the FakeApi wasn't injected and I've got an Exception
kotlin.UninitializedPropertyAccessException: lateinit property api has not been initialized
My questions are
Why field injection doesn't work for me?
Is there any way to use constructor injection for the super class instead of passing the dependency from the child?
Thanks to this Github Issue I figured out that the problem is that you can't use the field injected properties during the ViewModel constructor initialization, but you still use it after the constructor -including all the properties direct initialization- has been initialized.
Dagger firstly completes the constructor injection process then the field injection process takes place. that's why you can't use the field injection before the constructor injection is completed.
❌ Wrong use
abstract class BaseViewModel : ViewModel() {
#Inject
protected lateinit var fakeApi: FakeApi
val temp = fakeApi.doSomething() // Don't use it in direct property declaration
init {
fakeApi.doSomething() // Don't use it in the init block
}
}
✔️ Right use
abstract class BaseViewModel : ViewModel() {
#Inject
protected lateinit var fakeApi: FakeApi
val temp: Any
get() = fakeApi.doSomething() // Use property getter
fun doSomething(){
fakeApi.doSomething() // Use it after constructor initialization
}
}
Or you can use the by lazy to declare your properties.
I tested and I see that field injection in base class still work with Hilt 2.35. I can not get the error like you so maybe you can try to change the Hilt version or check how you provide FakeApi
abstract class BaseViewModel : ViewModel() {
#Inject
protected lateinit var fakeApi: FakeApi
}
FakeApi
// Inject constructor also working
class FakeApi {
fun doSomeThing() {
Log.i("TAG", "do something")
}
}
MainViewModel
#HiltViewModel
class MainViewModel #Inject constructor() : BaseViewModel() {
// from activity, when I call this function, the logcat print normally
fun doSomeThing() {
fakeApi.doSomeThing()
}
}
AppModule
#Module
#InstallIn(SingletonComponent::class)
class AppModule {
#Provides
fun provideAPI(
): FakeApi {
return FakeApi()
}
}
https://github.com/PhanVanLinh/AndroidHiltInjectInBaseClass
After many searches on the Internet, I think the best solution is to not use initializer blocks init { ... } on the ViewModel, and instead create a function fun initialize() { ... } that will be called on the Fragment.
BaseViewModel.kt
#HiltViewModel
open class BaseViewModel #Inject constructor() : ViewModel() {
#Inject
protected lateinit var localUserRepository: LocalUserRepository
}
OnboardingViewModel.kt
#HiltViewModel
class OnboardingViewModel #Inject constructor() : BaseViewModel() {
// Warning: don't use "init {}", the app will crash because of BaseViewModel
// injected properties not initialized
fun initialize() {
if (localUserRepository.isLoggedIn()) {
navigateToHomeScreen()
}
}
}
OnBoardingFragment.kt
#AndroidEntryPoint
class OnBoardingFragment() {
override val viewModel: OnboardingViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.initialize()
}
}
Sources:
https://github.com/google/dagger/issues/2507
the answers on this question

Can't use injected value as constructor of another injection

I would like to define 2 injected classes, but one needs to use the second class method for the constructor. I am using Koin framework
class MainActivity : AppCompatActivity() {
private val connectionService : ConnectionService by inject()
private val resourcesHelper : ResourcesHelper by inject()
private val addressPropertyName = "connection.address"
private val portPropertyName = "connection.port"
private val appModule = module {
single { ResourcesHelperImpl(androidContext(), R.raw.config) }
single {
ConnectionServiceTcp(
resourcesHelper.getConfigValueAsString(addressPropertyName),
resourcesHelper.getConfigValueAsInt(portPropertyName)
)
}
}
And then I get an error because I cannot instantiate ConnectionServiceTcp using resourcesHelper. Is there a way to use injected field to inject another field?
Edit
Changing to get() helped, but now I struggle with module configuration.
I moved start koin to MainApplication class:
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this#MainApplication)
androidLogger()
modules(appModule)
}
}
}
And module to AppModule.kt
val appModule = module {
single { ResourcesHelperImpl(androidContext(), R.raw.drone) }
single {
ConnectionServiceTcp(
get<ResourcesHelper>().getConfigValueAsString(ResourcesHelper.droneAddressPropertyName),
get<ResourcesHelper>().getConfigValueAsInt(ResourcesHelper.dronePortPropertyName)
)
}
scope(named<MainActivity>()) {
scoped {
ConnectionServiceTcp(get(), get())
}
}
}
And then I try to inject some object to activities and I am getting
Caused by: org.koin.core.error.NoBeanDefFoundException: No definition found for has been found. Check your module definitions.
Okay, I encountered two problems, firstly I could not instantiate bean using other bean in constructor, it was resolved by changing my invoke to
ConnectionServiceTcp(
get<ResourcesHelper>().getConfigValueAsString(ResourcesHelper.droneAddressPropertyName),
get<ResourcesHelper>().getConfigValueAsInt(ResourcesHelper.dronePortPropertyName)
)
Secondly, there was a problem with NoBeanDefFoundException, it was due androidContext() in ResourcesHelperImpl, I needed there Context from the activity, not the koin context.

Dagger2 Provider in koin

Is there any alternative to javax.inject.Provider in koin?
To react to actions, I am injecting Commands to my activity.
Command is a single-run object, for example WriteToFile.
In dagger I could make it like this:
class MainPresenter : Presenter() {
#Inject
lateinit var writeFile: Provider<WriteFileCommand>
fun onSaveClicked() {
writeFile.get().run()
}
}
in koin, when I try to use:
class MainPresenter : Presenter() {
lateinit var writeFile: Provider<WriteFileCommand> by inject()
fun onSaveClicked() {
writeFile.get().run()
}
}
My koin module:
val appModule = module {
factory { WriteFileCommand(get(), get()) }
factory { FileProvider() }
single { DataStore() }
}
Than I got error saying:
Can't create definition for 'Factory [name='WriteFileCommand',class='com.test.WriteFileCommand']' due to error :
No compatible definition found. Check your module definition
I understand that I can call:
var command: WriteFileCommand = StandAloneContext.getKoin().koinContext.get()
command.run()
But It looks so cumbersome
There's nothing like a provider directly. If you use inject, you'll use a lazy delegate. If you use get, you'll create a new instance you declared the dependency with a factory. So get is what you need in your case. Just let your MainPresenter implement KoinComponent and you'll be able to use get directly:
class MainPresenter : Presenter(), KoinCompontent {
fun onSaveClicked() = get<WriteFileCommand>().run()
}

Dagger-2 "lateinit property application has not been initialized"

I am trying to inject application context in a class which is giving
”lateinit property application has not been initialized"
exception.
CoreModule.kt
#Module
open class CoreModule {
#Singleton
#Provides
fun provideRealmHelper(): RealmHelper {
return RealmHelper()
}
}
MyApplication.kt
open class MyApplication : MultiDexApplication(), HasActivityInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
val log = LoggerFactory.getLogger(this.javaClass)!!
companion object {
var application: MyApplication? = null
fun getInstance(): MyApplication {
return application!!
}
}
override fun onCreate() {
try {
super.onCreate()
application = this
DaggerAppComponent.builder().application(this).build().inject(this)
} catch (e: Exception) {
log.error("Exception in Application", e)
Thread.setDefaultUncaughtExceptionHandler(GlobalExceptionHandler())
}
}
override fun activityInjector() = dispatchingAndroidInjector
}
AppComponent.kt
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class,CoreModule::class])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: MyApplication): Builder
fun build(): AppComponent
}
fun inject(myApplication: MyApplication)
fun inject(realmHelper: RealmHelper)
}
//I need application context in this class. I am inject applicationContext here.
Is injecting is correct way to do or I should use constructor injection?
RealmHelper.kt
class RealmHelper #Inject constructor() {
//need application context here but getting "lateinit property application has not been initialized
#Inject
lateinit var application: MyApplication
init {
Realm.init(application) // null application
}
}
Note: MyApplication is added to AndoridManifest.xml
The problem is you annotated your field but not injected. You can inject field like you did in application class : DaggerAppComponent.builder().application(this).build().inject(this)
or you can move your application field to RealmHelper constructor and in core module you need to write a provide function to return application. If you want to see an example I have an applicaton. https://github.com/volkansahin45/Moneycim
I need application context in this class. I am inject
applicationContext here. Is injecting is correct way to do or I should
use constructor injection?
Always favor constructor injection over field injection if possible.
Your CoreModule is not needed. The code below is enough.
#Singleton
class RealmHelper #Inject constructor(private val application: MyApplication) {
//Your implementation
}
fun inject(realmHelper: RealmHelper) in your Component is also unnecessary.
Removing those lines should fix it, I quickly threw together a demo project just to test it to make sure. Here is a quick gist with the code.
this might be too late but this may help other developer..
#set:Inject
internal var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>? = null
use this instead of
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
and in case you get same error elsewhere, do the same. Use #set:Inject instead of #Inject and use internal instead of lateinit.
This worked for me like charm.

Categories

Resources