Injecting a repository with lateinit is leading to crash - android

I have injected my repository in an Activity with lateinit declaration. However, when I am calling the method of repository it is resulting in a crash saying lateinit property clearDbRepository has not been initialized.
class StartActivity : BaseActivity() {
private lateinit var binding: StartEmptyPageBinding
#Inject
lateinit var clearDbRepository: ClearDbRepository
override fun setupViews() {
lifecycleScope.launch {
clearDbRepository.clearLocalDatabase()
}
}
}
My ClearDbRepository is:
#Singleton
class ClearDbRepository #Inject constructor(
private val mainDatabase: LocalDB
) {
suspend fun clearLocalDatabase() = withContext(Dispatchers.IO) {
mainDatabase.clearAllTables()
}
}

If you are using the Hilt library, then most probably, according to your code snippet, you're missing an annotation. You must add the appropriate annotation above your Activity class, like this:
#AndroidEntryPoint
class StartActivity : BaseActivity() { }

Related

Hilt injection in leanback Presenter

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.

Getting error in hilt HiltViewModel annotated class should contain exactly one #Inject annotated constructor

Error: How to resolve this, getting this wiered error even though I am not doing any inject in view model
/Users/user/Documents/Personal/android-in-app-review-engine/Application/app/build/tmp/kapt3/stubs/debug/com/inappreview/code/MainActivityViewModel.java:7: error: [Hilt]
public final class MainActivityViewModel extends androidx.lifecycle.ViewModel {
^
#HiltViewModel annotated class should contain exactly one #Inject annotated constructor.
[Hilt] Processing did not complete. See error above for details.
MainActivity.kt
#AndroidEntryPoint
class MainActivity : AppCompatActivity(), InAppReviewView {
#Inject
lateinit var inAppReviewManager: InAppReviewManager
private val viewModel : MainActivityViewModel by viewModels()
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel.setInAppReviewView(this)
setOnClickListener()
}
private fun setOnClickListener() {
binding.startReviewProcess.setOnClickListener {
viewModel.startReview()
}
}
override fun showReviewFlow() {
val dialog = InAppReviewPromptDialog()
dialog.show(supportFragmentManager, null)
}
}
MainActivityViewModel.kt
#HiltViewModel
class MainActivityViewModel : ViewModel() {
private lateinit var inAppReviewView: InAppReviewView
/**
* Sets an interface that backs up the In App Review prompts.
* */
fun setInAppReviewView(inAppReviewView: InAppReviewView) {
this.inAppReviewView = inAppReviewView
}
/**
* Start In App Review
* */
fun startReview() {
inAppReviewView.showReviewFlow()
}
}
App.kt
#HiltAndroidApp
class App : Application()
Since you are not injecting anything remove the #HiltViewModel, you only need that when you want to inject something into your ViewModel

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

android hilt Inject into ViewModel

i am try to inject a module to MyViewModel
here is my Module
#Module
#InstallIn(ViewModelComponent::class)
object EngineModule {
#Provides
fun getEngine(): String = "F35 Engine"
}
and this my viewModel
#HiltViewModel
class MyViewModel #Inject constructor(): ViewModel() {
#Inject lateinit var getEngine: String
fun getEngineNameFromViewModel(): String = getEngineName()
}
and it throws
kotlin.UninitializedPropertyAccessException: lateinit property getEngine
has not been initialized
however if i change ViewModelComponent::class to ActivityComponent::class and inject like this
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
#Inject
lateinit var getEngine: String
it works perfectly
any idea how to inject viewModels?
Also you can just remove #Inject constructor since you are already providing the dependency using dagger module:
#HiltViewModel
class MyViewModel (private val engineName: String): ViewModel() {
fun getEngineNameFromViewModel(): String = engineName
}
So, Basically you can either provide the dependency using dagger module or constructor injection.
Since required dependency is going to be injected in the ViewModel's constructor, you just need to modify your code in the following way to make it work:
#HiltViewModel
class MyViewModel #Inject constructor(private val engineName: String): ViewModel() {
fun getEngineNameFromViewModel(): String = engineName
}

#Inject lateinit property has not been initialized Dagger2

I try to use Dagger2 to my project. I have a Firebase service and a class called SyncFactory that makes a specific request. When i get a call from Firebase i make my request.
I have created a Mangers Module
#Module(includes = [RepositoryModule::class, NetworkModule::class, AppModule::class])
class ManagersModule {
...
#Singleton
#Provides
fun provideSyncFactory(context: Context, accountsRepository: AccountsRepository, messagesRepository: MessagesRepository) : SyncFactory {
return SyncFactory(context, accountsRepository, messagesRepository)
}
...
}
The SyncFactory class is like below
class SyncFactory #Inject constructor(
private val context: Context,
private val accountsRepository: AccountsRepository,
private val messagesRepository: MessagesRepository
) {
fun getAccounts(){....}
and i also have an interface
#Singleton
#Component(modules = [ViewModelsModule::class, DatabaseModule::class, RepositoryModule::class, AppModule::class, NetworkModule::class, ManagersModule::class])
interface ViewModelComponent {
fun inject(viewModels: ViewModels)
fun inject(firebaseService: AppFirebase)
}
And finally inside my firebase service i Inject the SyncFactory
class AppFirebase : FirebaseMessagingService(), SyncFactoryCallback {
#Inject
lateinit var syncFactory: SyncFactory
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
// lateinit property syncFactory has not been initialized
syncFactory.getAccounts()
}
And when my service gets called i get a lateinit property syncFactory has not been initialized exception.
What do i do wrong..?
The solution is to implement a HasServiceInjector in your Application class
class MyApplication : Application(), HasServiceInjector {
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Service>
companion object {
private lateinit var instance: MyApplication
}
override fun serviceInjector(): AndroidInjector<Service> {
return dispatchingAndroidInjector
}
}

Categories

Resources