#java #android #kotlin #android-fragments #dagger-hilt
Вопрос:
Я пытаюсь реализовать пользовательский launchFragmentInHiltContainer()
метод в проекте Java, и я уже прошел все этапы настройки kotlin и рефакторинга овеществленных параметров. Однако, когда я пытаюсь скомпилировать проект, меня встречает эта загадочная ошибка:
C:UsersjedwaAndroidStudioProjectsAppNameappsrcandroidTestjavacomexampleappnameMainActivityTest.java:55: error: cannot access Hilt_FragmentPersonalDetails
HiltExtKt.launchFragmentInHiltContainer(FragmentPersonalDetails.class);
^
class file for com.example.appname.fragments.Hilt_FragmentPersonalDetails not found
FragmentPersonalDetails
является фрагментом с поддержкой эфеса и отлично работает в производственном коде. Что странно, так это то, что замена FragmentPersonalDetails.class с помощью NonHiltFragment.class позволит проекту скомпилироваться просто отлично.
Однако чего он не сделает, так это не допустит возникновения ошибки во время выполнения, которая может быть связана с этим. При замене FragmentPersonalDetails
на NonHiltFragment я получаю:
java.lang.RuntimeException: Hilt test, MainActivityTest, is missing generated file: com.example.appname.MainActivityTest_TestComponentDataSupplier. Check that the test class is annotated with @HiltAndroidTest and that the processor is running over your test.
что я видел раньше, за исключением того, что на этот раз у меня определенно есть @HiltAndroidTest в моем тестовом классе. Я, наконец, достиг точки, когда ошибка настолько внутренняя, что я понятия не имею, как ее исправить, хотя это похоже на какую-то ошибку зависимости. Файлы, представленные ниже для справки.
Фрагмент с поддержкой рукояти PersonalDetails
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import com.example.atease.R;
import com.example.atease.databinding.FragmentPersonalDetailsBinding;
import com.example.atease.viewmodels.LoginViewModel;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class FragmentPersonalDetails extends Fragment {
private FragmentPersonalDetailsBinding binding;
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentPersonalDetailsBinding.inflate(inflater, container, false);
binding.setLifecycleOwner(this);
NavController navController = NavHostFragment.findNavController(this);
ViewModelStoreOwner store = navController.getViewModelStoreOwner(R.id.login_graph);
binding.setViewModel(new ViewModelProvider(store).get(LoginViewModel.class));
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.nextButton.setOnClickListener(view1 ->
NavHostFragment.findNavController(FragmentPersonalDetails.this)
.navigate(R.id.action_FragmentPersonalDetails_to_FragmentEmploymentDetails));
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
The test class that won’t compile
import androidx.test.espresso.IdlingRegistry;
import androidx.test.espresso.accessibility.AccessibilityChecks;
import com.example.atease.fragments.SecondFragment;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import javax.inject.Inject;
import dagger.hilt.android.testing.HiltAndroidRule;
import dagger.hilt.android.testing.HiltAndroidTest;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@HiltAndroidTest
public class MainActivityTest {
@Inject DataBindingIdlingResource bindingIdlingResource;
@BeforeClass
public static void enableAccessibility() {
AccessibilityChecks.enable().setRunChecksFromRootView(true);
}
@Before
public void init() {
hiltRule.inject();
IdlingRegistry.getInstance().register(bindingIdlingResource);
}
@After
public void tearDown() {
IdlingRegistry.getInstance().unregister(bindingIdlingResource);
}
@Rule
public HiltAndroidRule hiltRule = new HiltAndroidRule(this);
//cycles through the nav-bar
@Test
public void testNavbar() {
HiltExtKt.launchFragmentInHiltContainer(SecondFragment.class);
}
}
Моя реализация launchFragmentinHiltContainer
. Единственное отличие состоит в том, что я убрал овеществленные типы параметров и добавил дополнительный параметр класса, чтобы иметь возможность ссылаться на методы из Java.
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
import androidx.annotation.StyleRes
import androidx.core.util.Preconditions
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentFactory
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
@JvmOverloads
@ExperimentalCoroutinesApi
inline fun <T : Fragment> launchFragmentInHiltContainer(
fragmentType: Class<T>,
fragmentArgs: Bundle? = null,
@StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
crossinline action: Fragment.() -> Unit = {}
) {
val startActivityIntent = Intent.makeMainActivity(
ComponentName(
ApplicationProvider.getApplicationContext(),
HiltTestActivity::class.java
)
).putExtra("androidx.fragment.app.testing.FragmentScenario.EmptyFragmentActivity.THEME_EXTRAS_BUNDLE_KEY",
themeResId)
ActivityScenario.launch<HiltTestActivity>(startActivityIntent).onActivity { activity ->
val fragment: Fragment = activity.supportFragmentManager.fragmentFactory.instantiate(
Preconditions.checkNotNull(fragmentType.classLoader),
fragmentType.name
)
fragment.arguments = fragmentArgs
activity.supportFragmentManager
.beginTransaction()
.add(android.R.id.content, fragment, "")
.commitNow()
fragment.action()
}
}
@JvmOverloads
@ExperimentalCoroutinesApi
inline fun <T : Fragment> launchFragmentInHiltContainer(
fragmentType: Class<T>,
fragmentArgs: Bundle? = null,
@StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
factory: FragmentFactory,
crossinline action: Fragment.() -> Unit = {}
) {
val startActivityIntent = Intent.makeMainActivity(
ComponentName(
ApplicationProvider.getApplicationContext(),
HiltTestActivity::class.java
)
).putExtra("androidx.fragment.app.testing.FragmentScenario.EmptyFragmentActivity.THEME_EXTRAS_BUNDLE_KEY",
themeResId)
ActivityScenario.launch<HiltTestActivity>(startActivityIntent).onActivity { activity ->
activity.supportFragmentManager.fragmentFactory = factory
val fragment: Fragment = activity.supportFragmentManager.fragmentFactory.instantiate(
Preconditions.checkNotNull(fragmentType.classLoader),
fragmentType.name
)
fragment.arguments = fragmentArgs
activity.supportFragmentManager
.beginTransaction()
.add(android.R.id.content, fragment, "")
.commit()
fragment.action()
}
}
Ответ №1:
Просто перепишите функцию на Java. Я изменил подпись, чтобы она соответствовала оригиналу FragmentScenario.launchInContainer
.
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.test.core.app.ActivityScenario;
import androidx.test.core.app.ApplicationProvider;
import java.util.Objects;
public class HiltHelper {
public static void launchFragmentInHiltContainer(Class<? extends Fragment> fragmentClass, Bundle fragmentArgs, @StyleRes int themeResId) {
Intent intent = Intent.makeMainActivity(new ComponentName(ApplicationProvider.getApplicationContext(), HiltTestActivity.class))
.putExtra("androidx.fragment.app.testing.FragmentScenario.EmptyFragmentActivity.THEME_EXTRAS_BUNDLE_KEY", themeResId);
ActivityScenario.launch(intent)
.onActivity(activity -> {
Fragment fragment = ((AppCompatActivity) activity).getSupportFragmentManager().getFragmentFactory()
.instantiate(Objects.requireNonNull(fragmentClass.getClassLoader()), fragmentClass.getName());
fragment.setArguments(fragmentArgs);
((AppCompatActivity) activity).getSupportFragmentManager()
.beginTransaction()
.add(android.R.id.content, fragment, "")
.commitNow();
});
}
}