Почему компилятор Java не может найти мой класс Hilt в launchFragmentInHiltContainer?

#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();
                });
    }
}