Android Mockito kotlin.UninitializedPropertyAccessException: lateinit property dataManager has not been initialized - android

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

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.

Injecting a repository with lateinit is leading to crash

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

Dagger 2 - Inject fields in activity

Before I start, I've read a lot of tutorials but each of them contains info about old dagger - using #builder which is now deprecated. I'm using #Factory
What I have?
class LoginActivity : AppCompatActivity() {
#Inject
lateinit var authService: AuthService
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
....
}
}
//----------------
#Singleton
#Component(modules = [TestAppModule::class])
interface TestApplicationComponent : AndroidInjector<TestMyApplication> {
#Component.Factory
abstract class Builder : AndroidInjector.Factory<TestMyApplication>
}
//----------------
class TestMyApplication : MyApplication() {
override fun onCreate() {
super.onCreate()
JodaTimeAndroid.init(this)
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerTestApplicationComponent.factory().create(this)
}
}
//----------------
#Singleton
open class AuthService #Inject constructor(
#AppContext val context: Context, private val authRemoteDataSource: AuthRemoteDataSource
) {
...
}
//----------------
class MockRunner : AndroidJUnitRunner() {
override fun onCreate(arguments: Bundle?) {
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().permitAll().build())
super.onCreate(arguments)
}
override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
return super.newApplication(cl, TestMyApplication::class.qualifiedName, context)
}
}
Notes:
I show you, constructor in AuthService because it has more than 0 args
Mock runner applies my TestMyApplication class
And TestClass
#RunWith(AndroidJUnit4::class)
class LoginActivityTest {
#Mock
lateinit var mockAuthService: AuthService
#Rule
#JvmField
val activityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java, false, false)
#Before
fun beforeEach() {
MockitoAnnotations.initMocks(this)
Mockito.doReturn(NOT_SIGNED).`when`(mockAuthService).getUserSignedStatus(ArgumentMatchers.anyBoolean())
println(mockAuthService.getUserSignedStatus(true)) //test
}
#Test
fun buttonLogin() {
activityRule.launchActivity(Intent())
onView(withText("Google")).check(matches(isDisplayed()));
}
}
What do I want?
- In the simplest way attach mocked AuthService to LoginActivity
What I've got? Error:
While calling method: android.content.Context.getSharedPreferences
In line:
Mockito.doReturn(NOT_SIGNED).`when`(mockAuthService).getUserSignedStatus(ArgumentMatchers.anyBoolean())
Method getSharedPreferences is called in real method getUserSignedStatus. So now, I'm getting an error because Mockito.when calls the real function which is public. I think, the second problem will be that mocked AuthService is not injected to LoginActivity
So you should probably provide the AuthService through a module, one for the normal app and one for the android test, which supplies the mocked version. That would mean removing the Dagger annotations from the AuthService class. I don't use Component.Factory but this example should be enough to for you to use as a guide.
In androidTest folder :
Create test module :
// normal app should include the module to supply this dependency
#Module object AndroidTestModule {
val mock : AuthService = Mockito.mock(AuthService::class.java)
#Provides
#Singleton
#JvmStatic
fun mockService() : AuthService = mock
}
Create test component :
#Component(modules = [AndroidTestModule::class])
#Singleton
interface AndroidTestComponent : AndroidInjector<AndroidTestApp> {
#Component.Builder interface Builder {
#BindsInstance fun app(app : Application) : Builder
fun build() : AndroidTestComponent
}
}
Create test app :
class AndroidTestApp : DaggerApplication() {
override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
DaggerAndroidTestAppComponent.builder().app(this).build()
}
then the runner :
class AndroidTestAppJunitRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
return super.newApplication(cl, AndroidTestApp::class.java.canonicalName, context)
}
}
include in android closure in Gradle :
testInstrumentationRunner "com.package.name.AndroidTestAppJunitRunner"
add these deps :
kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
kaptAndroidTest "com.google.dagger:dagger-android-processor:$daggerVersion"
androidTestImplementation "org.mockito:mockito-android:2.27.0"
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
then a test :
#RunWith(AndroidJUnit4::class) class LoginActivityTest {
#Rule
#JvmField
val activityRule = ActivityTestRule<LoginActivity>(LoginActivity::class.java, false, false)
#Before
fun beforeEach() {
Mockito.doReturn(NOT_SIGNED).`when`(AndroidTestModule.mock).getUserSignedStatus(ArgumentMatchers.anyBoolean()
}
#Test
fun buttonLogin() {
activityRule.launchActivity(Intent())
onView(withText("Google")).check(matches(isDisplayed()));
}
}
Your dependency will then supplied through the generated test component graph to LoginActivity

Dagger 2 not injecting, lateinit property not initialized

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

#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