I followed a lot of tutorials/ articles and the googles architecture sample
No matter what I try I keep getting an error saying that I need to attach Fragment to @AndroidEntryPoint annotated Activity. I've setup everything correctly but still can't get this to work properly.
java.lang.IllegalStateException: Hilt Fragments must be attached to an @AndroidEntryPoint Activity. Found: class com.nikolam.colorme.HiltTestActivity
at dagger.hilt.internal.Preconditions.checkState(Preconditions.java:83)
at dagger.hilt.android.internal.managers.FragmentComponentManager.createComponent(FragmentComponentManager.java:75)
at dagger.hilt.android.internal.managers.FragmentComponentManager.generatedComponent(FragmentComponentManager.java:64)
at com.nikolam.main_feature.presenter.main_screen.Hilt_MainFragment.generatedComponent(Hilt_MainFragment.java:80)
at com.nikolam.main_feature.presenter.main_screen.Hilt_MainFragment.inject(Hilt_MainFragment.java:102)
at com.nikolam.main_feature.presenter.main_screen.Hilt_MainFragment.initializeComponentContext(Hilt_MainFragment.java:63)
at com.nikolam.main_feature.presenter.main_screen.Hilt_MainFragment.onAttach(Hilt_MainFragment.java:55)
at androidx.fragment.app.Fragment.onAttach(Fragment.java:1783)
at com.nikolam.main_feature.presenter.main_screen.Hilt_MainFragment.onAttach(Hilt_MainFragment.java:45)
at androidx.fragment.app.Fragment.performAttach(Fragment.java:2911)
at androidx.fragment.app.FragmentStateManager.attach(FragmentStateManager.java:464)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:275)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1971)
at androidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:305)
at com.nikolam.colorme.main_feature.MainFragmentTest$whenMainActivityLaunchedNavigatorIsInvokedForFragment$$inlined$launchFragmentInHiltContainer$1.perform(HiltExt.kt:46)
at androidx.test.core.app.ActivityScenario.lambda$onActivity$2$ActivityScenario(ActivityScenario.java:660)
at androidx.test.core.app.ActivityScenario$$Lambda$4.run(Unknown Source)
at androidx.test.core.app.ActivityScenario.onActivity(ActivityScenario.java:670)
at com.nikolam.colorme.main_feature.MainFragmentTest.whenMainActivityLaunchedNavigatorIsInvokedForFragment(MainFragmentTest.kt:85)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at dagger.hilt.android.internal.testing.MarkThatRulesRanRule$1.evaluate(MarkThatRulesRanRule.java:106)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:575)
at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:263)
at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Now here is my setup.
Dependencies
testImplementation(TestLibraryDependency.HILT_ANDROID_TESTING)
kaptTest(TestLibraryDependency.HILT_ANDROID_TESTING_COMPILER)
testImplementation(TestLibraryDependency.TEST_RUNNER)
testImplementation(TestLibraryDependency.ESPRESSO_CORE)
testImplementation(TestLibraryDependency.ANDROIDX_TEST_RULES)
testImplementation(TestLibraryDependency.ANDROIDX_CORE_TESTING)
testImplementation(TestLibraryDependency.ANDROIDX_TEST_EXT)
This is my test
@HiltAndroidTest
@Config(application = HiltTestApplication::class, maxSdk = Build.VERSION_CODES.P)
@RunWith(RobolectricTestRunner::class)
class MainFragmentTest {
@get:Rule()
var hiltAndroidRule = HiltAndroidRule(this)
@Before
fun init() {
hiltAndroidRule.inject()
}
@Test
fun whenMainActivityLaunchedNavigatorIsInvokedForFragment() {
// launchActivity()
// GIVEN - On the home screen
val navController = mock(NavController::class.java)
var fragment = launchFragmentInHiltContainer<MainFragment>() {
Navigation.setViewNavController(this.view!!, navController)
}
// WHEN - Click on the "+" button
onView(withId(R.id.add_floating_action)).perform(ViewActions.click())
// THEN - Verify that we navigate to the add screen
verify(navController).navigate(
Uri.parse(UPLOAD_DEEPLINK)
)
}
Launch in hilt container per google
inline fun <reified T : Fragment> launchFragmentInHiltContainer(
fragmentArgs: Bundle? = null,
@StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
crossinline action: Fragment.() -> Unit = {}
) {
val startActivityIntent = Intent.makeMainActivity(
ComponentName(
ApplicationProvider.getApplicationContext(),
HiltTestActivity::class.java
)
).putExtra(EmptyFragmentActivity.THEME_EXTRAS_BUNDLE_KEY, themeResId)
ActivityScenario.launch<HiltTestActivity>(startActivityIntent).onActivity { activity ->
val fragment: Fragment = activity.supportFragmentManager.fragmentFactory.instantiate(
Preconditions.checkNotNull(T::class.java.classLoader),
T::class.java.name
)
fragment.arguments = fragmentArgs
activity.supportFragmentManager
.beginTransaction()
.add(android.R.id.content, fragment, "")
.commitNow()
fragment.action()
}
}
(I also have their custom runner)
I also have a debug source set that contains HiltTestActivity like here, alongside debug manifest.
Besides this, I'd like to somehow inject and initialize my NavigationManager (wrapper class around navController) in my Activity. Is there a way to achieve this? When I tried to use my MainActivity for tests I kept getting errors because my lateinit @Injects weren't getting initialized...
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding;
private lateinit var navController : NavController
@Inject
lateinit var navManager: NavManager
private fun initNavManager() {
navManager.setOnNavEvent {
navController.navigate(it)
}
In my case I had to use
hilt {
enableTransformForLocalTests = true
}
Since the Roboelectric/ Hilt has 20 bugs currently being tracked it was hard to pinpoint what workaround was the case :)