Hilt is generating my public @BindValue kotlin properties as private and complaining about it:
@HiltAndroidTest
@UninstallModules(
AuthenticationStorageModule::class,
)
@RunWith(AndroidJUnit4::class)
//@Config(application = HiltTestApplication::class) // For Robolectric tests
class MainActivityInstrumentedTest {
@get:Rule(order = 0) var hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1) var activityScenarioRule = activityScenarioRule<MainActivity>(null)
@Before fun init() = hiltRule.inject()
@BindValue
val ftueData = object : FirstTimeUserExperienceData {
override var hasOnboardingBeenCompleted = false
override var hasNotificationAllowed = false
override var hasFindMyDeviceCompleted = false
}
@BindValue
val authenticationData = object : AuthenticationData {
override var name = ""
override var emailAdress = ""
}
@Test fun test() = runBlockingTest { }
}
below is the error from Hilt:
.../build/tmp/kapt3/stubs/debugAndroidTest/com/my/app/MainActivityInstrumentedTest.java:31: error: [Hilt]
private final com.my.app.storage.FirstTimeUserExperienceData ftueData = null;
^
@BindValue fields cannot be private. Found: ftueData
[Hilt] Processing did not complete. See error above for details.warning: File for type
'com.my.app.MainActivityInstrumentedTest_HiltComponents' created in the last round will not be subject to annotation processing.
warning: File for type 'com.my.app.MainActivityInstrumentedTest_ComponentDataHolder' created in the last round will not be subject to annotation processing.
warning: File for type 'dagger.hilt.android.internal.testing.TestComponentDataSupplierImpl' created in the last round will not be subject to annotation processing.
warning: The following options were not recognized by any processor: '[dagger.fastInit, dagger.hilt.android.internal.disableAndroidSuperclassValidation, kapt.kotlin.generated]'
[WARN] Incremental annotation processing requested, but support is disabled because the following processors are not incremental: android.databinding.annotationprocessor.ProcessDataBinding (DYNAMIC).
This Kotlin code:
@BindValue
val ftueData = object : FirstTimeUserExperienceData {
override var hasOnboardingBeenCompleted = false
override var hasNotificationAllowed = false
override var hasFindMyDeviceCompleted = false
}
implements the following interface:
interface FirstTimeUserExperienceData {
var hasOnboardingBeenCompleted: Boolean
var hasNotificationAllowed: Boolean
var hasFindMyDeviceCompleted: Boolean
}
and in production code, it its @Provide ed by a module.
In the test, I uninstall it at the top of my test-code in the first post.
I want to replace it with the anonymous object for the duration of the test, so I @BindValue it as a kotlin test class property.
Here is the java code that was generated from my
@org.jetbrains.annotations.NotNull()
@dagger.hilt.android.testing.BindValue()
private final com.my.app.android.storage.FirstTimeUserExperienceData ftueData = null;
I am not sure why the generated code makes this into a private field, or why it is then complaining about it.
Hi @Computr0n , for now I think either @BindValue lateinit var or @BindValue @JvmField val will work.
We do have plans to make this work with just @BindValue var, but we haven't gotten to this yet.
Excellent feedback, fast and accurate.
I think it may be a good idea to add @BindValue @JvmField val to the Kotlin testing documentation since I figure there are many developers who are more familiar with the Kotlin idioms and may not know to put@JvmFieldon their val
Currently the Android developers site has this:

I believe that is not even valid Kotlin syntax -- I think it may have been hastily copied from the Java example.
Thanks for the heads up on the documentation! That should be fixed now.
The @BindValue @JvmField val approach works correctly.
The @BindValue lateinit var compiles correctly but it doesn't fulfill the dependency that was uninstalled.
The @BindValue lateinit var compiles correctly but it doesn't fulfill the dependency that was uninstalled.
@ziglee What's the error that you get?
When using like this:
@BindValue
lateinit var repository: KittensRepository
@Before
fun setUp() {
repository = mockk()
hiltRule.inject()
}
I get:
java.lang.NullPointerException: Cannot return null from a non-@Nullable @Provides method
at dagger.internal.Preconditions.checkNotNull(Preconditions.java:48)
at net.cassiolandim.kittychallenge.ui.main.MainFragmentTest_BindValueModule_ProvidesRepositoryFactory.providesRepository(MainFragmentTest_BindValueModule_ProvidesRepositoryFactory.java:36)
In this case the lateinit var is working as expected, i.e. the binding is correctly recognized by Dagger at compile time.
The issue in this case is that at runtime something in your test is requesting access to the repository before you have a chance to set the value of the repository in your @Before set up.
If you post the entire test, and entire stack trace I might be able to tell you what's causing it. However, in general initializing the @BindValue field directly is the way to go because it ensures the value is set before anything requests it.
@HiltAndroidTest
@UninstallModules(KittensModule::class)
@RunWith(AndroidJUnit4::class)
class FavoritesFragmentTest {
@get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
var activityRule = MainActivityTestRule(R.id.navigation_favorites)
@BindValue
lateinit var repository: KittensRepository
@Before
fun setUp() {
repository = mockk<KittensRepository>().also {
coEvery { repository.favoritesLocal() } returns emptyList()
}
hiltRule.inject()
}
@Test
fun given_list_of_favorites_When_click_to_unfavorite_Should_delete() {
onView(ViewMatchers.withId(R.id.recyclerView))
.perform(RecyclerViewActions.actionOnItemAtPosition<KittenViewHolder>(0,
PerformClickViewWithIdAction(R.id.deleteLayout)
))
}
}
class MainActivityTestRule(
private val initialNavId: Int
) : ActivityTestRule<MainActivity>(MainActivity::class.java) {
override fun getActivityIntent(): Intent {
return Intent(Intent.ACTION_MAIN).apply {
putExtra(MainActivity.EXTRA_NAVIGATION_ID, initialNavId)
}
}
}
java.lang.NullPointerException: Cannot return null from a non-@Nullable @Provides method
at dagger.internal.Preconditions.checkNotNull(Preconditions.java:48)
at net.cassiolandim.kittychallenge.ui.favorites.FavoritesFragmentTest_BindValueModule_ProvidesRepositoryFactory.providesRepository(FavoritesFragmentTest_BindValueModule_ProvidesRepositoryFactory.java:36)
at net.cassiolandim.kittychallenge.ui.favorites.DaggerFavoritesFragmentTest_HiltComponents_ApplicationC.getKittensRepository(DaggerFavoritesFragmentTest_HiltComponents_ApplicationC.java:67)
at net.cassiolandim.kittychallenge.ui.favorites.DaggerFavoritesFragmentTest_HiltComponents_ApplicationC.access$700(DaggerFavoritesFragmentTest_HiltComponents_ApplicationC.java:50)
at net.cassiolandim.kittychallenge.ui.favorites.DaggerFavoritesFragmentTest_HiltComponents_ApplicationC$ActivityRetainedCImpl$ActivityCImpl.getFavoritesUseCase(DaggerFavoritesFragmentTest_HiltComponents_ApplicationC.java:193)
at net.cassiolandim.kittychallenge.ui.favorites.DaggerFavoritesFragmentTest_HiltComponents_ApplicationC$ActivityRetainedCImpl$ActivityCImpl.access$1700(DaggerFavoritesFragmentTest_HiltComponents_ApplicationC.java:175)
at net.cassiolandim.kittychallenge.ui.favorites.DaggerFavoritesFragmentTest_HiltComponents_ApplicationC$ActivityRetainedCImpl$ActivityCImpl$SwitchingProvider.get(DaggerFavoritesFragmentTest_HiltComponents_ApplicationC.java:390)
at net.cassiolandim.kittychallenge.ui.MainViewModel_AssistedFactory.create(MainViewModel_AssistedFactory.java:38)
at net.cassiolandim.kittychallenge.ui.MainViewModel_AssistedFactory.create(MainViewModel_AssistedFactory.java:15)
at androidx.hilt.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:76)
at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:69)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:54)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)
at net.cassiolandim.kittychallenge.ui.favorites.FavoritesFragment.getViewModel(Unknown Source:6)
at net.cassiolandim.kittychallenge.ui.favorites.FavoritesFragment.onCreateView(FavoritesFragment.kt:71)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2699)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:320)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1199)
at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2236)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2009)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1965)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1861)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2641)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2589)
at androidx.fragment.app.Fragment.performActivityCreated(Fragment.java:2723)
at androidx.fragment.app.FragmentStateManager.activityCreated(FragmentStateManager.java:346)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1200)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1368)
at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1446)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1509)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2637)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2589)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:247)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:541)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:210)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1391)
at androidx.test.runner.MonitoringInstrumentation.callActivityOnStart(MonitoringInstrumentation.java:714)
at android.app.Activity.performStart(Activity.java:7157)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:2937)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
@ziglee the problem is that ActivityTestRule calls Activity.onCreate() before @Before is called, which means your activity and fragments try to inject themselves before @Before is called, which creates issues because you don't set the @BindValue until @Before method.
Thanks @bcorso. I will use the approach where I declare the module using an object with a method annotated with @Provides inside the test class. That's the only way I think can circumvent this problem of Activity.onCreate() being called before the test dependencies are correctly set.
Most helpful comment
Hi @Computr0n , for now I think either
@BindValue lateinit varor@BindValue @JvmField valwill work.We do have plans to make this work with just
@BindValue var, but we haven't gotten to this yet.